User-provided export of the final integrated procurement + inventory ER (DBDatalyzer/dbdata-diagram format). Kept at the root as placed; the older ERD lives in docs/NewERD.txt.
787 lines
12 KiB
Plaintext
787 lines
12 KiB
Plaintext
////////////////////////////////////////////////////////////
|
|
// 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)
|
|
}
|
|
} |