diff --git a/spec/64_oms_wms_erp_database_schema.sql b/spec/64_oms_wms_erp_database_schema.sql new file mode 100644 index 00000000..1b84cd16 --- /dev/null +++ b/spec/64_oms_wms_erp_database_schema.sql @@ -0,0 +1,471 @@ +-- OMS·WMS·ERP Unified Platform Database Schema v1.0 +-- PostgreSQL 15+ +-- Principles Applied: +-- 3: Data Consistency (SSOT) +-- 5: Normalization (3NF minimum) +-- 6: Denormalization (performance-justified only) +-- 14: Traceability (audit_log on all mutations) +-- 23: Security (NUMERIC for financial precision) + +CREATE SCHEMA IF NOT EXISTS quantengine; +SET search_path = quantengine, public; + +-- ===== ENUMS (Type Safety - Principle 19) ===== + +CREATE TYPE order_status AS ENUM ('DRAFT', 'CONFIRMED', 'SHIPPED', 'DELIVERED', 'CANCELLED'); +CREATE TYPE order_line_status AS ENUM ('PENDING', 'ALLOCATED', 'SHIPPED', 'CANCELLED'); +CREATE TYPE transfer_status AS ENUM ('REQUESTED', 'APPROVED', 'SHIPPED', 'RECEIVED', 'CANCELLED'); +CREATE TYPE product_status AS ENUM ('ACTIVE', 'INACTIVE', 'OBSOLETE'); +CREATE TYPE supplier_status AS ENUM ('ACTIVE', 'INACTIVE', 'SUSPENDED'); +CREATE TYPE customer_status AS ENUM ('ACTIVE', 'INACTIVE', 'SUSPENDED'); +CREATE TYPE gl_account_type AS ENUM ('ASSET', 'LIABILITY', 'EQUITY', 'REVENUE', 'EXPENSE'); +CREATE TYPE voucher_status AS ENUM ('DRAFT', 'POSTED', 'APPROVED', 'VOIDED'); +CREATE TYPE voucher_document_type AS ENUM ('PURCHASE', 'SALES', 'JOURNAL', 'ADJUSTMENT'); +CREATE TYPE inventory_status AS ENUM ('ACTIVE', 'INACTIVE', 'DAMAGED'); +CREATE TYPE unit_of_measure AS ENUM ('EA', 'KG', 'M', 'L', 'BOX'); +CREATE TYPE audit_operation AS ENUM ('CREATE', 'UPDATE', 'DELETE'); +CREATE TYPE user_role AS ENUM ('ADMIN', 'MANAGER', 'OPERATOR', 'VIEWER', 'ANALYST'); + +-- ===== COMMON/MASTER TABLES ===== + +CREATE TABLE users ( + user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + role user_role NOT NULL DEFAULT 'VIEWER', + status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + created_by UUID, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_users_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT fk_users_modified_by FOREIGN KEY (modified_by) REFERENCES users(user_id), + CONSTRAINT fk_users_deleted_by FOREIGN KEY (deleted_by) REFERENCES users(user_id) +); +CREATE INDEX idx_users_email ON users(email); + +CREATE TABLE warehouses ( + warehouse_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + warehouse_code VARCHAR(50) NOT NULL UNIQUE, + warehouse_name VARCHAR(255) NOT NULL, + location VARCHAR(255), + status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_warehouses_created_by FOREIGN KEY (created_by) REFERENCES users(user_id) +); +CREATE INDEX idx_warehouses_code ON warehouses(warehouse_code); + +CREATE TABLE product_categories ( + category_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + category_name VARCHAR(255) NOT NULL UNIQUE, + description TEXT, + status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT fk_categories_created_by FOREIGN KEY (created_by) REFERENCES users(user_id) +); + +-- ===== OMS: ORDER MANAGEMENT ===== + +CREATE TABLE customers ( + customer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + customer_code VARCHAR(50) NOT NULL UNIQUE, + customer_name VARCHAR(255) NOT NULL, + email VARCHAR(255), + phone VARCHAR(20), + business_registration VARCHAR(50), + status customer_status NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_customers_created_by FOREIGN KEY (created_by) REFERENCES users(user_id) +); +CREATE INDEX idx_customers_code ON customers(customer_code); +CREATE INDEX idx_customers_email ON customers(email); + +CREATE TABLE orders ( + order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + order_no VARCHAR(50) NOT NULL UNIQUE, + customer_id UUID NOT NULL, + order_date DATE NOT NULL DEFAULT CURRENT_DATE, + total_amount NUMERIC(19,4) NOT NULL DEFAULT 0.0, + status order_status NOT NULL DEFAULT 'DRAFT', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id), + CONSTRAINT fk_orders_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT chk_total_amount CHECK (total_amount >= 0) +); +CREATE INDEX idx_orders_no ON orders(order_no); +CREATE INDEX idx_orders_customer_id ON orders(customer_id); +CREATE INDEX idx_orders_status ON orders(status); +CREATE INDEX idx_orders_order_date ON orders(order_date); + +CREATE TABLE order_lines ( + line_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + order_id UUID NOT NULL, + line_no SMALLINT NOT NULL, + product_id UUID NOT NULL, + quantity NUMERIC(19,4) NOT NULL, + quantity_unit unit_of_measure NOT NULL, + unit_price NUMERIC(19,4) NOT NULL, + line_total NUMERIC(19,4) NOT NULL, + status order_line_status NOT NULL DEFAULT 'PENDING', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_order_lines_order FOREIGN KEY (order_id) REFERENCES orders(order_id), + CONSTRAINT fk_order_lines_product FOREIGN KEY (product_id) REFERENCES products(product_id), + CONSTRAINT fk_order_lines_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT uk_order_lines_order_lineno UNIQUE (order_id, line_no), + CONSTRAINT chk_quantity CHECK (quantity > 0), + CONSTRAINT chk_unit_price CHECK (unit_price >= 0), + CONSTRAINT chk_line_total CHECK (line_total >= 0) +); +CREATE INDEX idx_order_lines_order_id ON order_lines(order_id); +CREATE INDEX idx_order_lines_product_id ON order_lines(product_id); + +-- ===== WMS: WAREHOUSE MANAGEMENT ===== + +CREATE TABLE inventory ( + inventory_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + warehouse_id UUID NOT NULL, + product_id UUID NOT NULL, + qty_on_hand NUMERIC(19,4) NOT NULL DEFAULT 0.0, + qty_reserved NUMERIC(19,4) NOT NULL DEFAULT 0.0, + qty_available NUMERIC(19,4) NOT NULL GENERATED ALWAYS AS (qty_on_hand - qty_reserved) STORED, + last_adjustment_date TIMESTAMP, + status inventory_status NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_inventory_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouses(warehouse_id), + CONSTRAINT fk_inventory_product FOREIGN KEY (product_id) REFERENCES products(product_id), + CONSTRAINT fk_inventory_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT uk_inventory_warehouse_product UNIQUE (warehouse_id, product_id), + CONSTRAINT chk_qty_on_hand CHECK (qty_on_hand >= 0), + CONSTRAINT chk_qty_reserved CHECK (qty_reserved >= 0) +); +CREATE INDEX idx_inventory_warehouse_id ON inventory(warehouse_id); +CREATE INDEX idx_inventory_product_id ON inventory(product_id); + +CREATE TABLE stock_transfers ( + transfer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + transfer_no VARCHAR(50) NOT NULL UNIQUE, + from_warehouse_id UUID NOT NULL, + to_warehouse_id UUID NOT NULL, + product_id UUID NOT NULL, + quantity NUMERIC(19,4) NOT NULL, + reason TEXT, + status transfer_status NOT NULL DEFAULT 'REQUESTED', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_transfers_from_warehouse FOREIGN KEY (from_warehouse_id) REFERENCES warehouses(warehouse_id), + CONSTRAINT fk_transfers_to_warehouse FOREIGN KEY (to_warehouse_id) REFERENCES warehouses(warehouse_id), + CONSTRAINT fk_transfers_product FOREIGN KEY (product_id) REFERENCES products(product_id), + CONSTRAINT fk_transfers_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT chk_quantity CHECK (quantity > 0), + CONSTRAINT chk_different_warehouses CHECK (from_warehouse_id != to_warehouse_id) +); +CREATE INDEX idx_stock_transfers_no ON stock_transfers(transfer_no); +CREATE INDEX idx_stock_transfers_from_warehouse ON stock_transfers(from_warehouse_id); +CREATE INDEX idx_stock_transfers_to_warehouse ON stock_transfers(to_warehouse_id); +CREATE INDEX idx_stock_transfers_status ON stock_transfers(status); + +-- ===== ERP: ENTERPRISE RESOURCE PLANNING ===== + +CREATE TABLE products ( + product_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + sku VARCHAR(50) NOT NULL UNIQUE, + product_name VARCHAR(255) NOT NULL, + category_id UUID, + unit_of_measure unit_of_measure NOT NULL DEFAULT 'EA', + status product_status NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_products_category FOREIGN KEY (category_id) REFERENCES product_categories(category_id), + CONSTRAINT fk_products_created_by FOREIGN KEY (created_by) REFERENCES users(user_id) +); +CREATE INDEX idx_products_sku ON products(sku); +CREATE INDEX idx_products_status ON products(status); + +CREATE TABLE suppliers ( + supplier_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + supplier_code VARCHAR(50) NOT NULL UNIQUE, + supplier_name VARCHAR(255) NOT NULL, + email VARCHAR(255), + phone VARCHAR(20), + business_registration VARCHAR(50), + status supplier_status NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_suppliers_created_by FOREIGN KEY (created_by) REFERENCES users(user_id) +); +CREATE INDEX idx_suppliers_code ON suppliers(supplier_code); + +CREATE TABLE gl_accounts ( + account_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + account_code VARCHAR(50) NOT NULL UNIQUE, + account_name VARCHAR(255) NOT NULL, + account_type gl_account_type NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_gl_accounts_created_by FOREIGN KEY (created_by) REFERENCES users(user_id) +); +CREATE INDEX idx_gl_accounts_code ON gl_accounts(account_code); +CREATE INDEX idx_gl_accounts_type ON gl_accounts(account_type); + +CREATE TABLE vouchers ( + voucher_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + voucher_no VARCHAR(50) NOT NULL UNIQUE, + document_date DATE NOT NULL, + document_type voucher_document_type NOT NULL, + description TEXT, + total_debit NUMERIC(19,4) NOT NULL DEFAULT 0.0, + total_credit NUMERIC(19,4) NOT NULL DEFAULT 0.0, + status voucher_status NOT NULL DEFAULT 'DRAFT', + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + modified_by UUID, + modified_at TIMESTAMP, + deleted_by UUID, + deleted_at TIMESTAMP, + + CONSTRAINT fk_vouchers_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT chk_totals_balance CHECK (total_debit = total_credit) +); +CREATE INDEX idx_vouchers_no ON vouchers(voucher_no); +CREATE INDEX idx_vouchers_document_date ON vouchers(document_date); +CREATE INDEX idx_vouchers_status ON vouchers(status); + +CREATE TABLE voucher_lines ( + voucher_line_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + voucher_id UUID NOT NULL, + line_no SMALLINT NOT NULL, + account_id UUID NOT NULL, + debit_amount NUMERIC(19,4) NOT NULL DEFAULT 0.0, + credit_amount NUMERIC(19,4) NOT NULL DEFAULT 0.0, + description TEXT, + created_by UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT fk_voucher_lines_voucher FOREIGN KEY (voucher_id) REFERENCES vouchers(voucher_id), + CONSTRAINT fk_voucher_lines_account FOREIGN KEY (account_id) REFERENCES gl_accounts(account_id), + CONSTRAINT fk_voucher_lines_created_by FOREIGN KEY (created_by) REFERENCES users(user_id), + CONSTRAINT uk_voucher_lines_voucher_lineno UNIQUE (voucher_id, line_no), + CONSTRAINT chk_debit_amount CHECK (debit_amount >= 0), + CONSTRAINT chk_credit_amount CHECK (credit_amount >= 0), + CONSTRAINT chk_either_debit_or_credit CHECK ((debit_amount > 0 OR credit_amount > 0) AND NOT (debit_amount > 0 AND credit_amount > 0)) +); +CREATE INDEX idx_voucher_lines_voucher_id ON voucher_lines(voucher_id); +CREATE INDEX idx_voucher_lines_account_id ON voucher_lines(account_id); + +-- ===== AUDIT TRAIL (Principle 14: Traceability) ===== + +CREATE TABLE audit_logs ( + audit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + entity_type VARCHAR(50) NOT NULL, + entity_id UUID NOT NULL, + operation audit_operation NOT NULL, + old_value JSONB, + new_value JSONB, + changed_by UUID NOT NULL, + changed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + reason TEXT, + + CONSTRAINT fk_audit_logs_changed_by FOREIGN KEY (changed_by) REFERENCES users(user_id) +); +CREATE INDEX idx_audit_logs_entity ON audit_logs(entity_type, entity_id); +CREATE INDEX idx_audit_logs_changed_at ON audit_logs(changed_at); +CREATE INDEX idx_audit_logs_operation ON audit_logs(operation); + +-- ===== AUDIT TRIGGER (Principle 14: Automatic Traceability) ===== + +CREATE OR REPLACE FUNCTION audit_trigger() +RETURNS TRIGGER AS $$ +DECLARE + v_entity_type VARCHAR; + v_operation audit_operation; +BEGIN + v_entity_type := TG_TABLE_NAME; + + IF TG_OP = 'INSERT' THEN + v_operation := 'CREATE'::audit_operation; + INSERT INTO audit_logs (entity_type, entity_id, operation, new_value, changed_by, changed_at) + VALUES (v_entity_type, NEW.id, v_operation, to_jsonb(NEW), NEW.created_by, CURRENT_TIMESTAMP); + RETURN NEW; + + ELSIF TG_OP = 'UPDATE' THEN + v_operation := 'UPDATE'::audit_operation; + INSERT INTO audit_logs (entity_type, entity_id, operation, old_value, new_value, changed_by, changed_at) + VALUES (v_entity_type, OLD.id, v_operation, to_jsonb(OLD), to_jsonb(NEW), NEW.modified_by, CURRENT_TIMESTAMP); + RETURN NEW; + + ELSIF TG_OP = 'DELETE' THEN + v_operation := 'DELETE'::audit_operation; + INSERT INTO audit_logs (entity_type, entity_id, operation, old_value, changed_by, changed_at) + VALUES (v_entity_type, OLD.id, v_operation, to_jsonb(OLD), OLD.deleted_by, CURRENT_TIMESTAMP); + RETURN OLD; + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- Note: Trigger creation for individual tables omitted to keep schema focused. +-- In production: CREATE TRIGGER for orders, inventory, products, etc. + +-- ===== VIEWS (Convenience, Principle 3: Data Consistency) ===== + +CREATE VIEW v_order_summary AS +SELECT + o.order_id, + o.order_no, + c.customer_name, + o.order_date, + COUNT(ol.line_id) as line_count, + SUM(ol.line_total) as calculated_total, + o.total_amount, + o.status, + o.created_at +FROM orders o +LEFT JOIN customers c ON o.customer_id = c.customer_id +LEFT JOIN order_lines ol ON o.order_id = ol.order_id +WHERE o.deleted_at IS NULL +GROUP BY o.order_id, o.order_no, c.customer_name, o.order_date, o.total_amount, o.status, o.created_at; + +CREATE VIEW v_inventory_summary AS +SELECT + i.warehouse_id, + w.warehouse_name, + i.product_id, + p.sku, + p.product_name, + i.qty_on_hand, + i.qty_reserved, + i.qty_available, + i.status, + i.modified_at +FROM inventory i +LEFT JOIN warehouses w ON i.warehouse_id = w.warehouse_id +LEFT JOIN products p ON i.product_id = p.product_id +WHERE i.deleted_at IS NULL; + +CREATE VIEW v_voucher_totals AS +SELECT + v.voucher_id, + v.voucher_no, + v.document_type, + v.document_date, + SUM(COALESCE(vl.debit_amount, 0)) as calculated_debit, + SUM(COALESCE(vl.credit_amount, 0)) as calculated_credit, + v.total_debit, + v.total_credit, + v.status, + COUNT(vl.voucher_line_id) as line_count +FROM vouchers v +LEFT JOIN voucher_lines vl ON v.voucher_id = vl.voucher_id +WHERE v.deleted_at IS NULL +GROUP BY v.voucher_id, v.voucher_no, v.document_type, v.document_date, v.total_debit, v.total_credit, v.status; + +-- ===== INITIAL DATA (Seed) ===== + +-- Create initial admin user +INSERT INTO users (user_id, email, password_hash, name, role, status, created_by, created_at) +VALUES ( + '00000000-0000-0000-0000-000000000001'::UUID, + 'admin@quantengine.dev', + '$2b$12$R9h7cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUe', -- bcrypt: admin123! + 'System Administrator', + 'ADMIN', + 'ACTIVE', + '00000000-0000-0000-0000-000000000001'::UUID, + CURRENT_TIMESTAMP +) ON CONFLICT DO NOTHING; + +-- Create initial warehouses +INSERT INTO warehouses (warehouse_code, warehouse_name, location, status, created_by, created_at) +VALUES + ('WH-SEOUL', 'Seoul Main Warehouse', 'Seoul, KR', 'ACTIVE', '00000000-0000-0000-0000-000000000001'::UUID, CURRENT_TIMESTAMP), + ('WH-BUSAN', 'Busan Distribution Center', 'Busan, KR', 'ACTIVE', '00000000-0000-0000-0000-000000000001'::UUID, CURRENT_TIMESTAMP), + ('WH-INCHEON', 'Incheon Port Warehouse', 'Incheon, KR', 'ACTIVE', '00000000-0000-0000-0000-000000000001'::UUID, CURRENT_TIMESTAMP) +ON CONFLICT DO NOTHING; + +-- Create initial product category +INSERT INTO product_categories (category_name, description, status, created_by, created_at) +VALUES + ('Standard Products', 'Regular inventory items', 'ACTIVE', '00000000-0000-0000-0000-000000000001'::UUID, CURRENT_TIMESTAMP), + ('Premium Products', 'High-value items', 'ACTIVE', '00000000-0000-0000-0000-000000000001'::UUID, CURRENT_TIMESTAMP) +ON CONFLICT DO NOTHING; + +-- ===== GRANTS (Security - Principle 23) ===== + +-- Application role (read-write for normal operations) +CREATE ROLE quantengine_app LOGIN PASSWORD 'CHANGE_ME_PROD'; +GRANT USAGE ON SCHEMA quantengine TO quantengine_app; +GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA quantengine TO quantengine_app; +GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA quantengine TO quantengine_app; +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA quantengine TO quantengine_app; + +-- Read-only role (for analytics/reporting) +CREATE ROLE quantengine_readonly LOGIN PASSWORD 'CHANGE_ME_PROD'; +GRANT USAGE ON SCHEMA quantengine TO quantengine_readonly; +GRANT SELECT ON ALL TABLES IN SCHEMA quantengine TO quantengine_readonly; +GRANT SELECT ON ALL VIEWS IN SCHEMA quantengine TO quantengine_readonly; + +-- ===== COMMENTS (Documentation - Principle 28) ===== + +COMMENT ON SCHEMA quantengine IS 'OMS·WMS·ERP Unified Platform - Phase 0 Database Schema'; +COMMENT ON TABLE orders IS 'Order master records (OMS) - Principle 14: Complete audit trail via audit_logs'; +COMMENT ON TABLE inventory IS 'Warehouse inventory positions (WMS) - qty_available computed from on_hand - reserved'; +COMMENT ON TABLE audit_logs IS 'Universal change audit trail - every mutation logged for compliance + recovery (Principle 14)'; +COMMENT ON TABLE vouchers IS 'Accounting journal entries (ERP) - Principle 23: NUMERIC(19,4) for decimal precision';