Files
ERP/docs/erd.txt
T

300 lines
8.0 KiB
Plaintext

// ============================================================================
// PROCUREMENT SCHEMA v2.4 — Final Enterprise Architecture
// ============================================================================
// ---------- 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
}
// ============================================================================
// CATALOG DOMAIN
// ============================================================================
Table units {
id integer [pk]
code varchar [not null, unique]
name_fa varchar
dimension varchar [note: 'length, mass, pressure, area, volume, voltage, count, ratio, time, currency']
si_factor decimal [not null, default: 1.0, note: 'Universal multiply factor to convert to base SI unit within same dimension']
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()`]
}
// Global Attribute Dictionary
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 [note: 'Allowed options for enum types']
created_at timestamptz [not null, default: `now()`]
}
// Category-Attribute Junction (Scopes global attributes & sets category rules)
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 [note: 'UI Wizard rules e.g. {"when": {"is_colored": true}, "then": {"required": ["color"]}}']
sort_order smallint [not null, default: 0]
indexes {
(category_id, attribute_definition_id) [unique]
}
}
// Abstract Base Products / Materials
Table materials {
id uuid [pk, default: `gen_random_uuid()`]
category_id uuid [not null, ref: > material_categories.id]
name text [not null]
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]
}
}
// M:N Brand <-> Material Junction
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]
}
}
// Material & Brand Specific Packaging / Unit Overrides
Table material_units {
id uuid [pk, default: `gen_random_uuid()`]
material_id uuid [not null, ref: > materials.id]
brand_material_id uuid [ref: > brand_materials.id, note: 'NULL = generic material fallback; set = brand-specific packaging override']
unit_id integer [not null, ref: > units.id]
conversion_factor "numeric(18,6)" [not null, note: 'Multiplier to convert to canonical_unit_id']
is_default boolean [not null, default: false]
indexes {
(material_id, brand_material_id, unit_id) [unique]
}
}
// Typed Specs Source of Truth (EAV)
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 "numeric(18,6)"
value_string text
value_boolean boolean
value_date date
indexes {
(material_id, attribute_definition_id) [unique]
}
}
// ============================================================================
// QUOTATION & PROCUREMENT DOMAIN
// ============================================================================
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()`]
}
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()`]
}
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, note: 'True = formal binding quote; False = informal price estimate / draft']
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
}
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, note: 'Optional: set when a specific branded product is quoted']
unit_id integer [not null, ref: > units.id]
quantity "numeric(18,4)" [not null]
notes text
created_at timestamptz [not null, default: `now()`]
}
// Historical Snapshot: Freezes material specs at quote generation time
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 "numeric(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]
}
}
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 "numeric(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 "numeric(18,2)" [not null]
note text
indexes {
(termsheet_id, step_no) [unique]
}
}
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 "numeric(18,4)" [not null]
min_order_qty "numeric(18,4)"
total_price "numeric(18,2)" [note: 'Calculated at query/view time: unit_price * line_items.quantity']
indexes {
(line_item_id, termsheet_id) [unique]
}
}
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 "numeric(18,4)"
adjusted_notes text
decided_at timestamptz
}
Table item_receipts {
id uuid [pk, default: `gen_random_uuid()`]
item_decision_id uuid [not null, unique, ref: - item_decisions.id]
received_at timestamptz [not null, default: `now()`]
received_qty "numeric(18,4)" [not null]
on_time boolean
quality_note text}