Files
aura-crm/production/backend/migrations/016_purchase_orders.sql
T

47 lines
2.8 KiB
SQL

-- +goose Up
-- A purchase order to a supplier — deliberately thin lifecycle (draft ->
-- ordered -> partially_received/received, or cancelled from draft/ordered/
-- partially_received). Line items are fixed once the PO leaves draft (no
-- partial line edits after that — cancel and recreate for a real mistake);
-- draft itself is fully replaceable (see internal/purchaseorder.Update).
CREATE TABLE purchase_orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
supplier_id UUID NOT NULL REFERENCES suppliers(id) ON DELETE RESTRICT,
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN
('draft', 'ordered', 'partially_received', 'received', 'cancelled')),
note TEXT,
created_by_staff_id UUID NOT NULL,
created_by_staff_name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX purchase_orders_supplier_id_idx ON purchase_orders (supplier_id);
CREATE INDEX purchase_orders_status_idx ON purchase_orders (status);
-- qty_received is a running total updated transactionally by each GRN
-- receive (internal/purchaseorder.Receive) — not derived from stock_batches
-- on read, since a PO line's fulfillment needs to be checked/updated
-- atomically against concurrent receives on other lines of the same PO.
-- No CHECK tying qty_received to qty_ordered — a supplier over-shipping a
-- line is a real occurrence this shouldn't hard-block.
CREATE TABLE purchase_order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
purchase_order_id UUID NOT NULL REFERENCES purchase_orders(id) ON DELETE CASCADE,
part_id UUID NOT NULL REFERENCES parts(id) ON DELETE RESTRICT,
qty_ordered INT NOT NULL CHECK (qty_ordered > 0),
qty_received INT NOT NULL DEFAULT 0 CHECK (qty_received >= 0),
unit_price NUMERIC(10, 2) NOT NULL CHECK (unit_price >= 0)
);
CREATE INDEX purchase_order_items_po_id_idx ON purchase_order_items (purchase_order_id);
-- GRN linkage — receiving against a PO reuses internal/inventory's existing
-- ReceiveBatch (the same stock_batches/stock_serials/stock_movements
-- machinery a manual receipt uses), just additionally tagged with which
-- PO/line it fulfills. A GRN is not a separate table: the set of
-- stock_batches rows carrying a given purchase_order_id *is* its goods
-- receipt history.
ALTER TABLE stock_batches ADD COLUMN purchase_order_id UUID REFERENCES purchase_orders(id) ON DELETE SET NULL;
ALTER TABLE stock_batches ADD COLUMN purchase_order_item_id UUID REFERENCES purchase_order_items(id) ON DELETE SET NULL;
CREATE INDEX stock_batches_purchase_order_id_idx ON stock_batches (purchase_order_id) WHERE purchase_order_id IS NOT NULL;