MukeshKapoor25's picture
chore(database): add comprehensive database schema and migration scripts
69f2a47
-- ============================================================================
-- Database Schema for SCM Microservice
-- Includes: Trans Schema, SCM Tables, and Sales Order Tables
-- ============================================================================
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create Role if not exists
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'trans_owner') THEN
CREATE ROLE trans_owner WITH LOGIN PASSWORD 'trans_password'; -- Set a default password or leave it nologin
END IF;
END
$$;
-- Create Schema
CREATE SCHEMA IF NOT EXISTS trans;
ALTER SCHEMA trans OWNER TO trans_owner;
-- ============================================================================
-- Core Reference Tables
-- ============================================================================
-- Catalogue Reference
CREATE TABLE IF NOT EXISTS trans.catalogue_ref (
catalogue_id TEXT PRIMARY KEY,
catalogue_code TEXT,
catalogue_type TEXT NOT NULL,
catalogue_name TEXT NOT NULL,
sku TEXT,
barcode_number TEXT,
hsn_code TEXT,
gst_rate NUMERIC(5,2),
mrp NUMERIC(12,2),
base_price NUMERIC(12,2),
track_inventory BOOLEAN DEFAULT FALSE,
batch_managed BOOLEAN DEFAULT FALSE,
status TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
merchant_id TEXT[],
pricing_levels JSON
);
CREATE INDEX IF NOT EXISTS idx_catalogue_ref_catalogue_code ON trans.catalogue_ref (catalogue_code);
CREATE INDEX IF NOT EXISTS idx_catalogue_ref_catalogue_type ON trans.catalogue_ref (catalogue_type);
CREATE INDEX IF NOT EXISTS idx_catalogue_ref_sku ON trans.catalogue_ref (sku);
CREATE INDEX IF NOT EXISTS idx_catalogue_ref_status ON trans.catalogue_ref (status);
CREATE INDEX IF NOT EXISTS idx_catalogue_ref_pricing_levels_currency ON trans.catalogue_ref USING GIN ((pricing_levels->>'currency'));
-- UOM Groups
CREATE TABLE IF NOT EXISTS trans.scm_uom_group_ref (
uom_group_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
uom_group_code VARCHAR(50) UNIQUE,
name VARCHAR(100) NOT NULL,
base_unit VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
units JSON NOT NULL DEFAULT '[]',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_uom_group_code ON trans.scm_uom_group_ref (uom_group_code);
CREATE INDEX IF NOT EXISTS idx_uom_group_name ON trans.scm_uom_group_ref (name);
-- Stored Objects (Documents)
CREATE TABLE IF NOT EXISTS trans.stored_objects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id TEXT NOT NULL,
domain TEXT NOT NULL,
entity_id TEXT NOT NULL,
bucket_name TEXT NOT NULL,
object_key TEXT NOT NULL,
category TEXT NOT NULL,
file_name TEXT NOT NULL,
mime_type TEXT NOT NULL,
file_size BIGINT,
checksum_sha256 TEXT,
visibility VARCHAR(16) NOT NULL DEFAULT 'private',
created_by TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP,
legal_hold BOOLEAN NOT NULL DEFAULT FALSE,
CONSTRAINT uq_stored_object_active_key UNIQUE (tenant_id, domain, entity_id, object_key, deleted_at)
);
CREATE INDEX IF NOT EXISTS ix_stored_objects_active ON trans.stored_objects (tenant_id, domain, entity_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS ix_stored_objects_checksum ON trans.stored_objects (tenant_id, checksum_sha256) WHERE deleted_at IS NULL;
-- ============================================================================
-- SCM Purchase Orders
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_po (
po_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
po_no VARCHAR(50) UNIQUE NOT NULL,
buyer_id UUID NOT NULL,
buyer_type VARCHAR(20) NOT NULL,
supplier_id UUID NOT NULL,
supplier_type VARCHAR(20) NOT NULL,
po_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
exp_delivery_dt DATE,
currency VARCHAR(3) NOT NULL DEFAULT 'INR',
total_amt NUMERIC(14, 2) NOT NULL,
tax_amt NUMERIC(14, 2) DEFAULT 0,
net_amt NUMERIC(14, 2) NOT NULL,
status VARCHAR(20) NOT NULL,
remarks TEXT,
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.scm_po_item (
po_item_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
po_id UUID NOT NULL REFERENCES trans.scm_po(po_id) ON DELETE CASCADE,
catalogue_id UUID NOT NULL,
sku VARCHAR(64) NOT NULL,
batch_managed BOOLEAN,
ord_qty NUMERIC(12, 3) NOT NULL,
ord_uom_qty NUMERIC(12, 3) NOT NULL,
rcvd_qty NUMERIC(12, 3) DEFAULT 0,
invoiced_qty NUMERIC(12, 3) DEFAULT 0,
returned_qty NUMERIC(12, 3) DEFAULT 0,
dispatched_qty NUMERIC(12, 3) DEFAULT 0,
rejected_qty NUMERIC(12, 3) DEFAULT 0,
uom VARCHAR(10) NOT NULL,
ord_uom VARCHAR(10) NOT NULL,
unit_price NUMERIC(12, 2) NOT NULL,
line_amt NUMERIC(14, 2) NOT NULL,
tax_rate NUMERIC(5, 2) DEFAULT 0,
tax_amt NUMERIC(12, 2) DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.scm_po_status_log (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
po_id UUID NOT NULL REFERENCES trans.scm_po(po_id),
status VARCHAR(20) NOT NULL,
changed_by VARCHAR(64),
changed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
remarks TEXT
);
-- ============================================================================
-- SCM PO Returns
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_po_return (
po_return_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
po_return_no VARCHAR(40) UNIQUE NOT NULL,
po_id UUID NOT NULL,
supplier_id VARCHAR(64) NOT NULL,
client_id VARCHAR(64) NOT NULL,
warehouse_id VARCHAR(64),
return_date TIMESTAMP NOT NULL,
status VARCHAR(20) NOT NULL,
reason_code VARCHAR(50),
remarks TEXT,
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_po_return_po_id ON trans.scm_po_return(po_id);
CREATE INDEX IF NOT EXISTS idx_po_return_supplier_id ON trans.scm_po_return(supplier_id);
CREATE INDEX IF NOT EXISTS idx_po_return_client_id ON trans.scm_po_return(client_id);
CREATE TABLE IF NOT EXISTS trans.scm_po_return_item (
po_return_item_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
po_return_id UUID NOT NULL REFERENCES trans.scm_po_return(po_return_id) ON DELETE CASCADE,
po_item_id UUID NOT NULL,
catalogue_id UUID NOT NULL,
batch_no VARCHAR(50) NOT NULL,
expiry_date TIMESTAMP,
return_qty NUMERIC(12, 3) NOT NULL,
uom VARCHAR(20) NOT NULL DEFAULT 'PCS',
cost_price NUMERIC(12, 2) NOT NULL,
return_value NUMERIC(14, 2) GENERATED ALWAYS AS (return_qty * cost_price) STORED,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_po_return_item_po_item_id ON trans.scm_po_return_item(po_item_id);
-- ============================================================================
-- SCM Goods Receipt Notes (GRN)
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_grn (
grn_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
grn_no VARCHAR(50) UNIQUE NOT NULL,
po_id UUID REFERENCES trans.scm_po(po_id),
shipment_id UUID,
receiver_id VARCHAR(64),
supplier_id UUID NOT NULL,
received_by VARCHAR(64),
transporter VARCHAR(64),
recv_dt TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
wh_location VARCHAR(64),
status VARCHAR(20) NOT NULL,
total_qty NUMERIC(14, 3),
remarks TEXT,
created_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.scm_grn_item (
grn_item_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
grn_id UUID NOT NULL REFERENCES trans.scm_grn(grn_id) ON DELETE CASCADE,
po_item_id UUID REFERENCES trans.scm_po_item(po_item_id),
catalogue_id UUID,
sku VARCHAR(64) NOT NULL,
recv_qty NUMERIC(12, 3) NOT NULL,
acc_qty NUMERIC(12, 3) NOT NULL,
rej_qty NUMERIC(12, 3) DEFAULT 0,
uom VARCHAR(10) NOT NULL,
batch_no VARCHAR(50),
mfg_dt DATE,
exp_dt DATE,
qc_status VARCHAR(20) DEFAULT 'accepted',
remarks TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.scm_grn_issue (
issue_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
grn_item_id UUID NOT NULL REFERENCES trans.scm_grn_item(grn_item_id),
issue_type VARCHAR(50) NOT NULL,
description TEXT,
status VARCHAR(20) NOT NULL,
created_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================================
-- SCM Inventory / Stock
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_stock_ledger (
ledger_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
merchant_id VARCHAR(64) NOT NULL,
warehouse_id VARCHAR(64) NOT NULL,
catalogue_id VARCHAR(64),
sku VARCHAR(64) NOT NULL,
batch_no VARCHAR(50),
txn_type VARCHAR(20) NOT NULL,
qty NUMERIC(12, 3) NOT NULL,
uom VARCHAR(20),
ref_type VARCHAR(20) NOT NULL,
ref_id UUID NOT NULL,
ref_no VARCHAR(50),
remarks TEXT,
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.scm_stock (
stock_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
merchant_id VARCHAR(64) NOT NULL,
warehouse_id VARCHAR(64) NOT NULL,
sku VARCHAR(64) NOT NULL,
batch_no VARCHAR(50),
catalogue_id VARCHAR(64),
uom VARCHAR(20) DEFAULT 'PCS',
qty_on_hand NUMERIC(12, 3) DEFAULT 0 CHECK (qty_on_hand >= 0),
qty_reserved NUMERIC(12, 3) DEFAULT 0 CHECK (qty_reserved >= 0),
qty_available NUMERIC(12, 3) DEFAULT 0 CHECK (qty_available >= 0),
cost_price NUMERIC(12, 2),
expiry_date DATE,
ledger_id UUID,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_scm_stock_sku_batch UNIQUE (merchant_id, warehouse_id, catalogue_id, batch_no)
);
-- Stock Adjustment (Master/Detail)
CREATE TABLE IF NOT EXISTS trans.scm_stock_adjustment_master (
adjustment_master_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
adjustment_number VARCHAR(50) NOT NULL,
merchant_id VARCHAR(64) NOT NULL,
warehouse_id VARCHAR(64) NOT NULL,
adjustment_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
description TEXT,
additional_notes TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'pending', 'approved', 'rejected', 'applied', 'cancelled')),
total_items INTEGER DEFAULT 0,
total_adjustment_value NUMERIC(15, 2) DEFAULT 0,
requires_approval BOOLEAN DEFAULT TRUE,
approval_threshold NUMERIC(15, 2),
created_by VARCHAR(64) NOT NULL,
approved_by VARCHAR(64),
rejected_by VARCHAR(64),
applied_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP WITH TIME ZONE,
rejected_at TIMESTAMP WITH TIME ZONE,
applied_at TIMESTAMP WITH TIME ZONE,
rejection_reason TEXT
);
CREATE TABLE IF NOT EXISTS trans.scm_stock_adjustment_details (
adjustment_detail_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
adjustment_master_id UUID NOT NULL REFERENCES trans.scm_stock_adjustment_master(adjustment_master_id),
sku VARCHAR(64) NOT NULL,
batch_no VARCHAR(50),
adj_type VARCHAR(20) NOT NULL CHECK (adj_type IN ('damage', 'expired', 'shrinkage', 'cycle_count')),
qty NUMERIC(12, 3) NOT NULL CHECK (qty > 0),
system_qty NUMERIC(12, 3) NOT NULL DEFAULT 0 CHECK (system_qty >= 0),
direction VARCHAR(10) NOT NULL DEFAULT 'OUT' CHECK (direction IN ('IN', 'OUT')),
reason TEXT NOT NULL,
unit_cost NUMERIC(12, 4),
adjustment_value NUMERIC(15, 2),
line_status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (line_status IN ('pending', 'approved', 'rejected', 'applied')),
ledger_id UUID,
remarks TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Stock Take (Master/Detail)
CREATE TABLE IF NOT EXISTS trans.scm_stock_take_master (
stock_take_master_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_take_number VARCHAR(50) NOT NULL,
merchant_id VARCHAR(64) NOT NULL,
warehouse_id VARCHAR(64) NOT NULL,
stock_take_date TIMESTAMP WITH TIME ZONE NOT NULL,
description TEXT,
additional_notes TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'in_progress', 'completed', 'submitted', 'approved', 'rejected', 'cancelled')),
total_items INTEGER DEFAULT 0,
total_variance_value NUMERIC(15, 2) DEFAULT 0,
created_by VARCHAR(64) NOT NULL,
approved_by VARCHAR(64),
rejected_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP WITH TIME ZONE,
rejected_at TIMESTAMP WITH TIME ZONE,
rejection_reason TEXT
);
CREATE TABLE IF NOT EXISTS trans.scm_stock_take_details (
stock_take_detail_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_take_master_id UUID NOT NULL REFERENCES trans.scm_stock_take_master(stock_take_master_id),
sku VARCHAR(64) NOT NULL,
batch_no VARCHAR(50),
system_qty NUMERIC(12, 3) NOT NULL CHECK (system_qty >= 0),
physical_qty NUMERIC(12, 3) NOT NULL CHECK (physical_qty >= 0),
variance_qty NUMERIC(12, 3) NOT NULL,
unit_cost NUMERIC(12, 4),
variance_value NUMERIC(15, 2),
line_status VARCHAR(20) NOT NULL DEFAULT 'counted' CHECK (line_status IN ('counted', 'verified', 'adjusted')),
adjustment_id UUID,
remarks TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.scm_stock_take_status_log (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
stock_take_master_id UUID NOT NULL REFERENCES trans.scm_stock_take_master(stock_take_master_id) ON DELETE CASCADE,
status VARCHAR(50) NOT NULL,
changed_by VARCHAR(255),
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
remarks TEXT
);
-- ============================================================================
-- Trade Relationships
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_trade_relationship (
relationship_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
from_merchant_id UUID NOT NULL,
to_merchant_id UUID NOT NULL,
relationship_type VARCHAR(30) NOT NULL DEFAULT 'procurement',
status VARCHAR(20) NOT NULL DEFAULT 'draft',
valid_from DATE NOT NULL DEFAULT CURRENT_DATE,
valid_to DATE,
credit_allowed BOOLEAN DEFAULT FALSE,
credit_limit NUMERIC(14, 2),
payment_terms VARCHAR(50),
price_list_id VARCHAR(50),
allowed_regions TEXT[],
allowed_categories TEXT[],
created_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_different_merchants CHECK (from_merchant_id != to_merchant_id),
CONSTRAINT chk_validity_range CHECK (valid_to IS NULL OR valid_to >= valid_from),
CONSTRAINT chk_credit_configuration CHECK (credit_allowed = false OR (credit_allowed = true AND credit_limit IS NOT NULL AND credit_limit > 0))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_relationship ON trans.scm_trade_relationship (from_merchant_id, to_merchant_id);
CREATE INDEX IF NOT EXISTS idx_trade_from_merchant ON trans.scm_trade_relationship (from_merchant_id);
CREATE INDEX IF NOT EXISTS idx_trade_to_merchant ON trans.scm_trade_relationship (to_merchant_id);
-- ============================================================================
-- Trade Schemes
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.trade_schemes (
scheme_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
scheme_code VARCHAR(50) UNIQUE NOT NULL,
scheme_name VARCHAR(200) NOT NULL,
description TEXT,
scheme_type VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
applicable_level VARCHAR(20) NOT NULL,
merchant_id UUID,
valid_from DATE NOT NULL,
valid_to DATE NOT NULL,
settlement_type VARCHAR(20) NOT NULL,
settlement_frequency VARCHAR(20) NOT NULL,
inherit_to_children BOOLEAN DEFAULT TRUE,
allow_child_override BOOLEAN DEFAULT TRUE,
allow_better_only BOOLEAN DEFAULT TRUE,
currency VARCHAR(3) DEFAULT 'INR',
created_by VARCHAR(100),
approved_by VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP,
version INTEGER DEFAULT 1
);
CREATE INDEX IF NOT EXISTS idx_trade_schemes_type_status ON trans.trade_schemes (scheme_type, status);
CREATE INDEX IF NOT EXISTS idx_trade_schemes_validity ON trans.trade_schemes (valid_from, valid_to);
CREATE TABLE IF NOT EXISTS trans.trade_scheme_conditions (
condition_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
scheme_id UUID NOT NULL REFERENCES trans.trade_schemes(scheme_id),
min_quantity NUMERIC(15, 3),
max_quantity NUMERIC(15, 3),
qualifying_sku VARCHAR(50),
min_order_value NUMERIC(15, 2),
max_order_value NUMERIC(15, 2),
sku_list JSON,
category_list JSON,
brand_list JSON,
excluded_skus JSON,
excluded_categories JSON,
channel VARCHAR(20),
aggregate_basis VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.trade_scheme_benefits (
benefit_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
scheme_id UUID NOT NULL REFERENCES trans.trade_schemes(scheme_id),
discount_type VARCHAR(20),
discount_percentage NUMERIC(5, 2),
discount_amount NUMERIC(15, 2),
free_sku VARCHAR(50),
free_quantity NUMERIC(15, 3),
rebate_type VARCHAR(20),
rebate_percentage NUMERIC(5, 2),
rebate_amount NUMERIC(15, 2),
apply_on VARCHAR(20) DEFAULT 'TOTAL',
max_benefit_amount NUMERIC(15, 2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.trade_scheme_accruals (
accrual_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
scheme_id UUID NOT NULL REFERENCES trans.trade_schemes(scheme_id),
merchant_id UUID NOT NULL,
invoice_id VARCHAR(50),
invoice_date DATE,
accrual_amount NUMERIC(15, 2) NOT NULL,
currency VARCHAR(3) DEFAULT 'INR',
settlement_period VARCHAR(20),
status VARCHAR(20) DEFAULT 'PENDING',
credit_note_id VARCHAR(50),
settled_at TIMESTAMP,
settled_amount NUMERIC(15, 2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_accruals_scheme_merchant ON trans.trade_scheme_accruals (scheme_id, merchant_id);
CREATE TABLE IF NOT EXISTS trans.trade_scheme_applications (
application_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
scheme_id UUID NOT NULL REFERENCES trans.trade_schemes(scheme_id),
merchant_id UUID NOT NULL,
invoice_id UUID NOT NULL,
applied_amount NUMERIC(15, 2) NOT NULL,
benefit_type VARCHAR(20),
status VARCHAR(20) DEFAULT 'APPLIED',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_applications_invoice ON trans.trade_scheme_applications (invoice_id);
-- ============================================================================
-- Trade Shipment (Sales)
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_trade_shipment (
shipment_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
shipment_no VARCHAR(40) UNIQUE NOT NULL,
order_id UUID NOT NULL REFERENCES trans.scm_po(po_id),
supplier_id UUID NOT NULL,
client_id UUID NOT NULL,
warehouse_id UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'shipped', 'closed')),
shipment_date DATE NOT NULL,
transporter VARCHAR(100),
lr_no VARCHAR(50),
vehicle_no VARCHAR(20),
created_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
total_amt NUMERIC(14, 2) NOT NULL DEFAULT 0.00,
tax_amt NUMERIC(14, 2) NOT NULL DEFAULT 0.00,
net_amt NUMERIC(14, 2) NOT NULL DEFAULT 0.00
);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_order_id ON trans.scm_trade_shipment(order_id);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_supplier_id ON trans.scm_trade_shipment(supplier_id);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_client_id ON trans.scm_trade_shipment(client_id);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_date ON trans.scm_trade_shipment(shipment_date);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_status ON trans.scm_trade_shipment(status);
CREATE TABLE IF NOT EXISTS trans.scm_trade_shipment_item (
item_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
shipment_id UUID NOT NULL REFERENCES trans.scm_trade_shipment(shipment_id) ON DELETE CASCADE,
po_item_id UUID NOT NULL REFERENCES trans.scm_po_item(po_item_id),
sku VARCHAR(64) NOT NULL,
batch_no VARCHAR(50),
ordered_qty NUMERIC(12,3) NOT NULL,
shipped_qty NUMERIC(12,3) NOT NULL,
balance_qty NUMERIC(12,3) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
catalogue_id UUID,
exp_dt DATE,
unit_price NUMERIC(14, 2) NOT NULL,
line_amt NUMERIC(14, 2) NOT NULL,
tax_amt NUMERIC(14, 2),
tax_rate NUMERIC(5, 2),
CONSTRAINT chk_trade_shipment_item_qty CHECK (
shipped_qty >= 0 AND
ordered_qty >= 0 AND
balance_qty >= 0 AND
shipped_qty <= ordered_qty
)
);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_item_shipment_id ON trans.scm_trade_shipment_item(shipment_id);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_item_po_item_id ON trans.scm_trade_shipment_item(po_item_id);
CREATE INDEX IF NOT EXISTS idx_trade_shipment_item_sku ON trans.scm_trade_shipment_item(sku);
-- ============================================================================
-- Trade Invoices
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_invoice (
invoice_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
invoice_no VARCHAR(50) UNIQUE NOT NULL,
po_id UUID NOT NULL,
supplier_id UUID NOT NULL,
buyer_id UUID NOT NULL,
invoice_date DATE NOT NULL,
payment_terms VARCHAR(30),
due_date DATE,
currency VARCHAR(3) NOT NULL DEFAULT 'INR',
subtotal_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
discount_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
taxable_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
cgst_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
sgst_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
igst_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
total_tax_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
grand_total_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
status VARCHAR(30) NOT NULL DEFAULT 'draft',
reverse_charge BOOLEAN NOT NULL DEFAULT FALSE,
remarks TEXT,
freight_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
packing_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
other_charges_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
round_off_amt NUMERIC(14, 2) NOT NULL DEFAULT 0,
created_by VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_supplier_id ON trans.scm_invoice(supplier_id);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_buyer_id ON trans.scm_invoice(buyer_id);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_po_id ON trans.scm_invoice(po_id);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_status ON trans.scm_invoice(status);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_date ON trans.scm_invoice(invoice_date);
CREATE TABLE IF NOT EXISTS trans.scm_invoice_item (
invoice_item_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
invoice_id UUID NOT NULL REFERENCES trans.scm_invoice(invoice_id) ON DELETE CASCADE,
po_item_id UUID NOT NULL,
catalogue_id UUID NOT NULL,
sku VARCHAR(64) NOT NULL,
hsn_code VARCHAR(10),
invoice_qty NUMERIC(12, 3) NOT NULL,
unit_price NUMERIC(12, 2) NOT NULL,
discount_pct NUMERIC(5, 2),
discount_amt NUMERIC(12, 2),
tax_rate NUMERIC(5, 2),
tax_amt NUMERIC(12, 2),
line_total NUMERIC(14, 2) NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_item_invoice_id ON trans.scm_invoice_item(invoice_id);
CREATE INDEX IF NOT EXISTS idx_scm_invoice_item_po_item_id ON trans.scm_invoice_item(po_item_id);
CREATE TABLE IF NOT EXISTS trans.scm_invoice_status_log (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
invoice_id UUID NOT NULL REFERENCES trans.scm_invoice(invoice_id) ON DELETE CASCADE,
action VARCHAR(20),
from_status VARCHAR(20),
to_status VARCHAR(20),
remarks TEXT,
performed_by VARCHAR(64),
performed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================================
-- Credit/Debit Notes (Invoice Adjustments)
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_invoice_adjustment_note (
note_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
note_no VARCHAR(50) UNIQUE NOT NULL,
invoice_id UUID NOT NULL,
supplier_id UUID NOT NULL,
buyer_id UUID NOT NULL,
category_code VARCHAR(30) NOT NULL,
reason_code VARCHAR(50) NOT NULL,
note_type VARCHAR(10) NOT NULL,
note_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
original_amount NUMERIC(14, 2),
revised_amount NUMERIC(14, 2),
adjustment_amount NUMERIC(14, 2),
taxable_amount NUMERIC(14, 2),
original_tax_rate NUMERIC(5, 2),
correct_tax_rate NUMERIC(5, 2),
tax_adjustment_amt NUMERIC(14, 2),
description TEXT NOT NULL,
remarks TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
created_by VARCHAR(64),
created_by_username VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(64),
updated_by_username VARCHAR(64),
submitted_by VARCHAR(64),
submitted_at TIMESTAMP WITH TIME ZONE,
approved_by VARCHAR(64),
approved_at TIMESTAMP WITH TIME ZONE,
rejected_by VARCHAR(64),
rejected_at TIMESTAMP WITH TIME ZONE,
applied_by VARCHAR(64),
applied_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_invoice_id ON trans.scm_invoice_adjustment_note(invoice_id);
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_supplier_id ON trans.scm_invoice_adjustment_note(supplier_id);
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_buyer_id ON trans.scm_invoice_adjustment_note(buyer_id);
CREATE TABLE IF NOT EXISTS trans.scm_invoice_adjustment_status_log (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
note_id UUID NOT NULL REFERENCES trans.scm_invoice_adjustment_note(note_id) ON DELETE CASCADE,
action VARCHAR(20),
from_status VARCHAR(20),
to_status VARCHAR(20),
remarks TEXT,
performed_by VARCHAR(64),
performed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================================
-- Spa Partner Orders
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.spa_partner_orders (
order_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
order_number VARCHAR(50) UNIQUE NOT NULL,
partner_id VARCHAR(50) NOT NULL,
merchant_id VARCHAR(50) NOT NULL,
order_status VARCHAR(30) NOT NULL,
total_amount NUMERIC(12, 2) NOT NULL,
tax_amount NUMERIC(12, 2),
discount_amount NUMERIC(12, 2),
net_amount NUMERIC(12, 2),
payment_status VARCHAR(30),
payment_mode VARCHAR(30),
delivery_address TEXT,
city VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trans.spa_partner_order_items (
id SERIAL PRIMARY KEY,
order_id UUID NOT NULL REFERENCES trans.spa_partner_orders(order_id),
product_id VARCHAR(50) NOT NULL,
product_name VARCHAR(200),
quantity INTEGER NOT NULL,
unit_price NUMERIC(10, 2) NOT NULL,
tax_percent NUMERIC(5, 2),
total_price NUMERIC(12, 2)
);
-- ============================================================================
-- Trade Returns
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.scm_return (
return_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
return_no VARCHAR(50) UNIQUE NOT NULL,
return_type VARCHAR(20) NOT NULL CHECK (return_type IN ('PURCHASE', 'SALES')),
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' CHECK (status IN ('DRAFT', 'SUBMITTED', 'APPROVED', 'COMPLETED', 'REJECTED')),
invoice_id UUID NOT NULL,
invoice_no VARCHAR(50) NOT NULL,
buyer_id VARCHAR(64) NOT NULL,
buyer_name VARCHAR(200) NOT NULL,
supplier_id VARCHAR(64) NOT NULL,
supplier_name VARCHAR(200) NOT NULL,
reason_code VARCHAR(50) NOT NULL CHECK (reason_code IN ('DAMAGED_GOODS', 'EXPIRED_STOCK', 'QUALITY_ISSUE', 'WRONG_ITEM', 'EXCESS_STOCK', 'DEFECTIVE', 'RECALL', 'OTHER')),
remarks TEXT,
dispatch_date TIMESTAMP WITH TIME ZONE,
dispatch_reference VARCHAR(100),
received_date TIMESTAMP WITH TIME ZONE,
received_by VARCHAR(100),
qc_completed_date TIMESTAMP WITH TIME ZONE,
qc_performed_by VARCHAR(100),
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by VARCHAR(64),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_scm_return_invoice_id ON trans.scm_return(invoice_id);
CREATE INDEX IF NOT EXISTS idx_scm_return_buyer_id ON trans.scm_return(buyer_id);
CREATE INDEX IF NOT EXISTS idx_scm_return_supplier_id ON trans.scm_return(supplier_id);
CREATE INDEX IF NOT EXISTS idx_scm_return_status ON trans.scm_return(status);
CREATE TABLE IF NOT EXISTS trans.scm_return_item (
return_item_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
return_id UUID NOT NULL REFERENCES trans.scm_return(return_id) ON DELETE CASCADE,
invoice_item_id UUID NOT NULL,
catalogue_id UUID NOT NULL,
sku VARCHAR(64) NOT NULL,
description VARCHAR(500) NOT NULL,
batch_no VARCHAR(50) NOT NULL,
expiry_date TIMESTAMP WITH TIME ZONE,
return_qty NUMERIC(12, 3) NOT NULL,
qc_status VARCHAR(20) NOT NULL DEFAULT 'PENDING' CHECK (qc_status IN ('PENDING', 'ACCEPTED', 'REJECTED')),
qc_qty NUMERIC(12, 3),
qc_rejected_qty NUMERIC(12, 3),
qc_remarks TEXT,
qc_performed_by VARCHAR(100),
qc_performed_at TIMESTAMP WITH TIME ZONE,
unit_price NUMERIC(10, 2),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_scm_return_item_return_id ON trans.scm_return_item(return_id);
CREATE INDEX IF NOT EXISTS idx_scm_return_item_invoice_item_id ON trans.scm_return_item(invoice_item_id);
CREATE INDEX IF NOT EXISTS idx_scm_return_item_sku ON trans.scm_return_item(sku);
CREATE TABLE IF NOT EXISTS trans.scm_return_status_log (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
return_id UUID NOT NULL REFERENCES trans.scm_return(return_id) ON DELETE CASCADE,
action VARCHAR(20) NOT NULL,
from_status VARCHAR(20),
to_status VARCHAR(20) NOT NULL,
performed_by VARCHAR(64) NOT NULL,
performed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
remarks TEXT,
reference_no VARCHAR(100)
);
CREATE INDEX IF NOT EXISTS idx_scm_return_status_log_return_id ON trans.scm_return_status_log(return_id);
-- ============================================================================
-- Legacy Sales Orders (B2C)
-- ============================================================================
CREATE TABLE IF NOT EXISTS trans.branches (
branch_id VARCHAR(26) PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS trans.merchants (
merchant_id VARCHAR(26) PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS trans.customers (
customer_id VARCHAR(26) PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS trans.users (
user_id VARCHAR(26) PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS trans.catalogues (
catalogue_id VARCHAR(26) PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS trans.sales_orders (
sales_order_id VARCHAR(26) PRIMARY KEY,
order_number VARCHAR(50) UNIQUE NOT NULL,
branch_id VARCHAR(26) NOT NULL,
merchant_id VARCHAR(26) NOT NULL,
order_date TIMESTAMP WITH TIME ZONE NOT NULL,
status VARCHAR(20) NOT NULL CHECK (status IN ('draft', 'confirmed', 'processing', 'fulfilled', 'partially_fulfilled', 'cancelled', 'on_hold')),
customer_id VARCHAR(26) NOT NULL,
customer_name VARCHAR(255) NOT NULL,
customer_type VARCHAR(10) NOT NULL CHECK (customer_type IN ('b2b', 'b2c')),
customer_phone VARCHAR(20),
customer_email VARCHAR(255),
customer_gstin VARCHAR(15),
subtotal DECIMAL(12,2) NOT NULL DEFAULT 0,
total_discount DECIMAL(12,2) NOT NULL DEFAULT 0,
total_tax DECIMAL(12,2) NOT NULL DEFAULT 0,
shipping_charges DECIMAL(12,2) NOT NULL DEFAULT 0,
grand_total DECIMAL(12,2) NOT NULL DEFAULT 0,
cgst DECIMAL(12,2) DEFAULT 0,
sgst DECIMAL(12,2) DEFAULT 0,
igst DECIMAL(12,2) DEFAULT 0,
payment_type VARCHAR(20) NOT NULL CHECK (payment_type IN ('prepaid', 'cod', 'credit', 'partial')),
payment_status VARCHAR(20) NOT NULL CHECK (payment_status IN ('unpaid', 'partial', 'paid', 'refunded', 'overdue')),
payment_method VARCHAR(50),
payment_date TIMESTAMP WITH TIME ZONE,
payment_reference VARCHAR(100),
amount_paid DECIMAL(12,2) NOT NULL DEFAULT 0,
amount_due DECIMAL(12,2) NOT NULL DEFAULT 0,
credit_terms VARCHAR(50),
credit_limit DECIMAL(12,2),
fulfillment_status VARCHAR(20) NOT NULL CHECK (fulfillment_status IN ('pending', 'allocated', 'picked', 'packed', 'shipped', 'delivered')),
expected_delivery_date DATE,
actual_delivery_date DATE,
invoice_id VARCHAR(26),
invoice_number VARCHAR(50),
invoice_date TIMESTAMP WITH TIME ZONE,
invoice_pdf_url TEXT,
notes TEXT,
internal_notes TEXT,
created_by VARCHAR(26) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
approved_by VARCHAR(26),
approved_at TIMESTAMP WITH TIME ZONE,
source VARCHAR(20) DEFAULT 'web',
channel VARCHAR(50),
tags TEXT[],
version INTEGER NOT NULL DEFAULT 1,
CONSTRAINT fk_sales_order_branch FOREIGN KEY (branch_id) REFERENCES trans.branches(branch_id),
CONSTRAINT fk_sales_order_merchant FOREIGN KEY (merchant_id) REFERENCES trans.merchants(merchant_id),
CONSTRAINT fk_sales_order_customer FOREIGN KEY (customer_id) REFERENCES trans.customers(customer_id),
CONSTRAINT fk_sales_order_created_by FOREIGN KEY (created_by) REFERENCES trans.users(user_id)
);
CREATE TABLE IF NOT EXISTS trans.sales_order_items (
id VARCHAR(26) PRIMARY KEY,
sales_order_id VARCHAR(26) NOT NULL,
sku VARCHAR(50) NOT NULL,
product_id VARCHAR(26) NOT NULL,
product_name VARCHAR(255) NOT NULL,
item_type VARCHAR(20) NOT NULL CHECK (item_type IN ('product', 'service')),
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(10,2) NOT NULL,
tax_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
line_total DECIMAL(12,2) NOT NULL,
hsn_code VARCHAR(10),
uom VARCHAR(20) NOT NULL,
batch_no VARCHAR(50),
serials TEXT[],
staff_id VARCHAR(26),
staff_name VARCHAR(255),
remarks TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_sales_order_item_order FOREIGN KEY (sales_order_id) REFERENCES trans.sales_orders(sales_order_id) ON DELETE CASCADE,
CONSTRAINT fk_sales_order_item_product FOREIGN KEY (product_id) REFERENCES trans.catalogues(catalogue_id)
);
CREATE TABLE IF NOT EXISTS trans.sales_order_addresses (
id VARCHAR(26) PRIMARY KEY,
sales_order_id VARCHAR(26) NOT NULL,
address_type VARCHAR(20) NOT NULL CHECK (address_type IN ('billing', 'shipping')),
line1 VARCHAR(255) NOT NULL,
line2 VARCHAR(255),
city VARCHAR(100) NOT NULL,
state VARCHAR(100) NOT NULL,
postal_code VARCHAR(20) NOT NULL,
country VARCHAR(100) NOT NULL DEFAULT 'India',
landmark VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_sales_order_address FOREIGN KEY (sales_order_id) REFERENCES trans.sales_orders(sales_order_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS trans.sales_order_shipments (
shipment_id VARCHAR(26) PRIMARY KEY,
sales_order_id VARCHAR(26) NOT NULL,
carrier VARCHAR(100),
tracking_number VARCHAR(100),
awb_number VARCHAR(100),
shipping_method VARCHAR(50),
dispatch_date TIMESTAMP WITH TIME ZONE,
expected_delivery_date DATE,
actual_delivery_date DATE,
status VARCHAR(20) CHECK (status IN ('pending', 'picked', 'in_transit', 'out_for_delivery', 'delivered', 'failed')),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_sales_order_shipment FOREIGN KEY (sales_order_id) REFERENCES trans.sales_orders(sales_order_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS trans.sales_order_documents (
id VARCHAR(26) PRIMARY KEY,
sales_order_id VARCHAR(26) NOT NULL,
filename VARCHAR(255) NOT NULL,
file_url TEXT NOT NULL,
document_type VARCHAR(50) NOT NULL,
file_size INTEGER,
mime_type VARCHAR(100),
uploaded_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
uploaded_by VARCHAR(26),
CONSTRAINT fk_sales_order_document FOREIGN KEY (sales_order_id) REFERENCES trans.sales_orders(sales_order_id) ON DELETE CASCADE
);
-- ============================================================================
-- Additional Indexes for Performance Optimization
-- ============================================================================
-- Invoice Adjustment Note Indexes
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_status ON trans.scm_invoice_adjustment_note(status);
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_type ON trans.scm_invoice_adjustment_note(note_type);
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_category ON trans.scm_invoice_adjustment_note(category_code);
CREATE INDEX IF NOT EXISTS idx_scm_adjustment_note_date ON trans.scm_invoice_adjustment_note(note_date);
-- Trade Return Composite Indexes
CREATE INDEX IF NOT EXISTS idx_returns_type_status ON trans.scm_return (return_type, status);
CREATE INDEX IF NOT EXISTS idx_returns_buyer_status ON trans.scm_return (buyer_id, status);
CREATE INDEX IF NOT EXISTS idx_returns_supplier_status ON trans.scm_return (supplier_id, status);
CREATE INDEX IF NOT EXISTS idx_returns_invoice_type ON trans.scm_return (invoice_id, return_type);
CREATE INDEX IF NOT EXISTS idx_returns_created_status ON trans.scm_return (created_at, status);
-- Trade Return Item Indexes
CREATE INDEX IF NOT EXISTS idx_return_items_sku_batch ON trans.scm_return_item (sku, batch_no);
CREATE INDEX IF NOT EXISTS idx_return_items_qc_status ON trans.scm_return_item (qc_status);
CREATE INDEX IF NOT EXISTS idx_return_items_catalogue_batch ON trans.scm_return_item (catalogue_id, batch_no);
-- Trade Return Log Indexes
CREATE INDEX IF NOT EXISTS idx_return_logs_action_date ON trans.scm_return_status_log (action, performed_at);
CREATE INDEX IF NOT EXISTS idx_return_logs_status_date ON trans.scm_return_status_log (to_status, performed_at);