//////////////////////////////////////////////////////////// // PROCUREMENT + INVENTORY SYSTEM // FINAL INTEGRATED ER (FOR DBDATALYZER / DBDATADIAGRAM.IO) //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // ENUMS //////////////////////////////////////////////////////////// enum quote_status { offered accepted arrived rejected expired } enum termsheet_status { offered accepted rejected } enum material_status { active discontinued draft } enum attribute_data_type { number integer string enum boolean date } enum discount_type { none percent fixed } enum item_decision_status { pending chosen rejected } enum quote_event_type { offered accepted rejected arrived note edited expired } enum inventory_transaction_reason { PURCHASE WITHDRAWAL RETURN TRANSFER_IN TRANSFER_OUT DAMAGE WASTE LOSS FOUND STOCK_COUNT OTHER } //////////////////////////////////////////////////////////// // USERS //////////////////////////////////////////////////////////// Table users { id uuid [pk, default: `gen_random_uuid()`] first_name text [not null] last_name text [not null] phone text created_at timestamptz [ not null, default: `now()` ] } //////////////////////////////////////////////////////////// // CATALOG //////////////////////////////////////////////////////////// Table units { id integer [pk] code varchar [not null, unique] name_fa varchar dimension varchar si_factor decimal [not null, default: 1.0] is_base boolean [default: false] } Table brands { id uuid [pk, default: `gen_random_uuid()`] name text [not null] country text created_at timestamptz [ not null, default: `now()` ] indexes { (name, country) [unique] } } Table material_categories { id uuid [pk, default: `gen_random_uuid()`] slug text [not null, unique] name text [not null] description text is_active boolean [ not null, default: true ] created_at timestamptz [ not null, default: `now()` ] updated_at timestamptz [ not null, default: `now()` ] } Table attribute_definitions { id uuid [pk, default: `gen_random_uuid()`] attribute_key text [not null, unique] label text [not null] data_type attribute_data_type [not null] unit_id integer [ ref: > units.id ] enum_values jsonb created_at timestamptz [ not null, default: `now()` ] } Table category_attributes { id uuid [pk, default: `gen_random_uuid()`] category_id uuid [ not null, ref: > material_categories.id ] attribute_definition_id uuid [ not null, ref: > attribute_definitions.id ] is_required boolean [ not null, default: false ] is_filterable boolean [ not null, default: false ] conditional_rules jsonb sort_order smallint [ not null, default: 0 ] indexes { (category_id, attribute_definition_id) [unique] } } Table materials { id uuid [pk, default: `gen_random_uuid()`] category_id uuid [ not null, ref: > material_categories.id ] name text [not null] // Main unit of the material. // All inventory quantities are stored in this unit. canonical_unit_id integer [ not null, ref: > units.id ] status material_status [ not null, default: 'active' ] created_at timestamptz [ not null, default: `now()` ] updated_at timestamptz [ not null, default: `now()` ] indexes { (category_id, name) [unique] } } Table brand_materials { id uuid [pk, default: `gen_random_uuid()`] brand_id uuid [ not null, ref: > brands.id ] material_id uuid [ not null, ref: > materials.id ] catalog_code text is_active boolean [ not null, default: true ] created_at timestamptz [ not null, default: `now()` ] indexes { (brand_id, material_id) [unique] } } Table material_units { id uuid [pk, default: `gen_random_uuid()`] material_id uuid [ not null, ref: > materials.id ] // NULL = generic unit // Non-NULL = brand-specific unit brand_material_id uuid [ ref: > brand_materials.id ] unit_id integer [ not null, ref: > units.id ] // Converts this unit to materials.canonical_unit_id conversion_factor decimal(18,6) [ not null ] is_default boolean [ not null, default: false ] indexes { (material_id, brand_material_id, unit_id) [unique] } } Table material_attribute_values { id uuid [pk, default: `gen_random_uuid()`] material_id uuid [ not null, ref: > materials.id ] attribute_definition_id uuid [ not null, ref: > attribute_definitions.id ] value_number decimal(18,6) value_string text value_boolean boolean value_date date indexes { (material_id, attribute_definition_id) [unique] } } //////////////////////////////////////////////////////////// // SUPPLIERS //////////////////////////////////////////////////////////// Table suppliers { id uuid [pk, default: `gen_random_uuid()`] name text [not null] location text phone text contact_person text tax_id text created_at timestamptz [ not null, default: `now()` ] } //////////////////////////////////////////////////////////// // PROJECTS //////////////////////////////////////////////////////////// Table projects { id uuid [pk, default: `gen_random_uuid()`] name text [not null] description text start_date date created_at timestamptz [ not null, default: `now()` ] } //////////////////////////////////////////////////////////// // QUOTATIONS //////////////////////////////////////////////////////////// Table quotes { id uuid [pk, default: `gen_random_uuid()`] supplier_id uuid [ not null, ref: > suppliers.id ] project_id uuid [ ref: > projects.id ] quote_date timestamptz [ not null, default: `now()` ] expiry_date date accepted_date timestamptz arrived_date timestamptz status quote_status [ not null, default: 'offered' ] is_official boolean [ not null, default: true ] notes text created_at timestamptz [ not null, default: `now()` ] updated_at timestamptz [ not null, default: `now()` ] } Table quote_events { id uuid [pk, default: `gen_random_uuid()`] quote_id uuid [ not null, ref: > quotes.id ] event_type quote_event_type [ not null ] payload jsonb [ not null, default: '{}' ] occurred_at timestamptz [ not null, default: `now()` ] actor text } //////////////////////////////////////////////////////////// // QUOTATION ITEMS //////////////////////////////////////////////////////////// Table line_items { id uuid [pk, default: `gen_random_uuid()`] quote_id uuid [ not null, ref: > quotes.id ] material_id uuid [ not null, ref: > materials.id ] brand_material_id uuid [ ref: > brand_materials.id ] unit_id integer [ not null, ref: > units.id ] // Ordered / requested quantity quantity decimal(18,4) [ not null ] notes text created_at timestamptz [ not null, default: `now()` ] } Table line_item_specs { id uuid [pk, default: `gen_random_uuid()`] line_item_id uuid [ not null, ref: > line_items.id ] attribute_definition_id uuid [ not null, ref: > attribute_definitions.id ] value_number decimal(18,6) value_string text value_boolean boolean value_date date unit_id_at_time integer [ ref: > units.id ] indexes { (line_item_id, attribute_definition_id) [unique] } } //////////////////////////////////////////////////////////// // TERMSHEETS //////////////////////////////////////////////////////////// Table termsheets { id uuid [pk, default: `gen_random_uuid()`] quote_id uuid [ not null, ref: > quotes.id ] label text [not null] status termsheet_status [ not null, default: 'offered' ] discount_type discount_type [ not null, default: 'none' ] discount_value decimal(18,4) [ not null, default: 0 ] created_at timestamptz [ not null, default: `now()` ] } Table termsheet_schedule_steps { id uuid [pk, default: `gen_random_uuid()`] termsheet_id uuid [ not null, ref: > termsheets.id ] step_no smallint [ not null ] due_date date [ not null ] amount decimal(18,2) [ not null ] note text indexes { (termsheet_id, step_no) [unique] } } //////////////////////////////////////////////////////////// // ITEM TERMSHEETS //////////////////////////////////////////////////////////// Table item_termsheets { id uuid [pk, default: `gen_random_uuid()`] line_item_id uuid [ not null, ref: > line_items.id ] termsheet_id uuid [ not null, ref: > termsheets.id ] unit_price decimal(18,4) [ not null ] min_order_qty decimal(18,4) total_price decimal(18,2) indexes { (line_item_id, termsheet_id) [unique] } } //////////////////////////////////////////////////////////// // ITEM DECISIONS //////////////////////////////////////////////////////////// Table item_decisions { id uuid [pk, default: `gen_random_uuid()`] line_item_id uuid [ not null, unique, ref: - line_items.id ] chosen_item_termsheet_id uuid [ ref: > item_termsheets.id ] status item_decision_status [ not null, default: 'pending' ] adjusted_unit_price decimal(18,4) adjusted_notes text decided_at timestamptz } //////////////////////////////////////////////////////////// // WAREHOUSES //////////////////////////////////////////////////////////// Table warehouses { id uuid [pk, default: `gen_random_uuid()`] project_id uuid [ not null, ref: > projects.id ] name text [not null] description text created_at timestamptz [ not null, default: `now()` ] } //////////////////////////////////////////////////////////// // WAREHOUSE ACCESS //////////////////////////////////////////////////////////// Table warehouse_users { warehouse_id uuid [ not null, ref: > warehouses.id ] user_id uuid [ not null, ref: > users.id ] indexes { (warehouse_id, user_id) [pk] } } //////////////////////////////////////////////////////////// // CURRENT INVENTORY SNAPSHOT //////////////////////////////////////////////////////////// Table inventory { id uuid [pk, default: `gen_random_uuid()`] warehouse_id uuid [ not null, ref: > warehouses.id ] material_id uuid [ not null, ref: > materials.id ] brand_id uuid [ ref: > brands.id ] item_title text [not null] specs jsonb [default: '{}'] // SHA256 of sorted canonical specs JSON (or 'default' if empty) specs_hash varchar(64) [ not null, default: 'default' ] quantity decimal(18,6) [ not null, default: 0 ] updated_at timestamptz [ not null, default: `now()` ] indexes { (warehouse_id, material_id, brand_id, specs_hash) [unique] (warehouse_id, item_title) } } //////////////////////////////////////////////////////////// // INVENTORY TRANSACTIONS //////////////////////////////////////////////////////////// Table inventory_transactions { id uuid [pk, default: `gen_random_uuid()`] inventory_id uuid [ not null, ref: > inventory.id ] warehouse_id uuid [ not null, ref: > warehouses.id ] line_item_id uuid [ ref: >? line_items.id ] quantity decimal(18,6) [ not null ] reason inventory_transaction_reason [ not null ] performed_by uuid [ not null, ref: > users.id ] transaction_date timestamptz [ not null, default: `now()` ] note text created_at timestamptz [ not null, default: `now()` ] indexes { (inventory_id, transaction_date) (warehouse_id, transaction_date) (inventory_id, line_item_id) } }