feat(POS-002): completed feature
This commit is contained in:
149
project/migrations/045_pos_orders_source.js
Normal file
149
project/migrations/045_pos_orders_source.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* POS-002 — Add `source`, `terminal_id`, `cash_session_id` to `orders_orders`;
|
||||
* drop NOT NULL on `user_id`; add `COMPLETED` to the state CHECK.
|
||||
*
|
||||
* The `source` column distinguishes ecommerce / pos / admin sales. POS sales
|
||||
* may have `user_id IS NULL` (walk-in customer); ecommerce and admin sales
|
||||
* must have a non-null `user_id` (enforced by the application layer; this
|
||||
* migration only relaxes the column).
|
||||
*
|
||||
* The `terminal_id` and `cash_session_id` FKs use `ON DELETE SET NULL` so
|
||||
* decommissioning a terminal or closing its session does not delete
|
||||
* historical sales.
|
||||
*
|
||||
* The state CHECK is rewritten with the new `COMPLETED` value. POS sales
|
||||
* transition PENDING → AWAITING_PAYMENT → PAID → COMPLETED, skipping
|
||||
* SHIPPED / DELIVERED.
|
||||
*
|
||||
* @param {import('node-pg-migrate').MigrationBuilder} pgm
|
||||
*/
|
||||
|
||||
export const up = (pgm) => {
|
||||
// ── Add columns ──────────────────────────────────────────────────────────
|
||||
pgm.sql(`
|
||||
ALTER TABLE orders_orders
|
||||
ADD COLUMN IF NOT EXISTS source text NOT NULL DEFAULT 'ecommerce'
|
||||
`);
|
||||
pgm.sql(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'orders_orders_source_check'
|
||||
) THEN
|
||||
ALTER TABLE orders_orders
|
||||
ADD CONSTRAINT orders_orders_source_check
|
||||
CHECK (source IN ('ecommerce','pos','admin'));
|
||||
END IF;
|
||||
END $$
|
||||
`);
|
||||
|
||||
pgm.sql(`ALTER TABLE orders_orders ADD COLUMN IF NOT EXISTS terminal_id uuid`);
|
||||
pgm.sql(`ALTER TABLE orders_orders ADD COLUMN IF NOT EXISTS cash_session_id uuid`);
|
||||
|
||||
pgm.sql(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'orders_orders_terminal_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE orders_orders
|
||||
ADD CONSTRAINT orders_orders_terminal_id_fkey
|
||||
FOREIGN KEY (terminal_id) REFERENCES pos_terminals(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$
|
||||
`);
|
||||
pgm.sql(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'orders_orders_cash_session_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE orders_orders
|
||||
ADD CONSTRAINT orders_orders_cash_session_id_fkey
|
||||
FOREIGN KEY (cash_session_id) REFERENCES pos_cash_sessions(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$
|
||||
`);
|
||||
|
||||
// ── Indexes ──────────────────────────────────────────────────────────────
|
||||
pgm.createIndex('orders_orders', 'source', { name: 'orders_orders_source_idx' });
|
||||
pgm.createIndex('orders_orders', 'terminal_id', {
|
||||
name: 'orders_orders_terminal_idx',
|
||||
where: 'terminal_id IS NOT NULL',
|
||||
});
|
||||
pgm.createIndex('orders_orders', 'cash_session_id', {
|
||||
name: 'orders_orders_cash_session_idx',
|
||||
where: 'cash_session_id IS NOT NULL',
|
||||
});
|
||||
|
||||
// ── Walk-in customers (POS only) ─────────────────────────────────────────
|
||||
pgm.sql(`ALTER TABLE orders_orders ALTER COLUMN user_id DROP NOT NULL`);
|
||||
|
||||
// CHECK: walk-in customers (user_id NULL) are allowed only for POS sales.
|
||||
// Ecommerce and admin sales must have a non-null user_id. DB-level invariant
|
||||
// enforced in addition to the application-level check.
|
||||
pgm.sql(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'orders_orders_user_required_when_not_pos'
|
||||
) THEN
|
||||
ALTER TABLE orders_orders
|
||||
ADD CONSTRAINT orders_orders_user_required_when_not_pos
|
||||
CHECK (source = 'pos' OR user_id IS NOT NULL);
|
||||
END IF;
|
||||
END $$
|
||||
`);
|
||||
|
||||
// ── Add COMPLETED to state CHECK ─────────────────────────────────────────
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP CONSTRAINT IF EXISTS orders_orders_state_check`);
|
||||
pgm.sql(`
|
||||
ALTER TABLE orders_orders
|
||||
ADD CONSTRAINT orders_orders_state_check
|
||||
CHECK (state IN (
|
||||
'PENDING',
|
||||
'AWAITING_PAYMENT',
|
||||
'PAID',
|
||||
'PROCESSING',
|
||||
'SHIPPED',
|
||||
'DELIVERED',
|
||||
'COMPLETED',
|
||||
'CANCELLED',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
))
|
||||
`);
|
||||
};
|
||||
|
||||
export const down = (pgm) => {
|
||||
// Re-tighten state CHECK to the pre-COMPLETED set; this will fail if any
|
||||
// COMPLETED rows exist.
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP CONSTRAINT IF EXISTS orders_orders_state_check`);
|
||||
pgm.sql(`
|
||||
ALTER TABLE orders_orders
|
||||
ADD CONSTRAINT orders_orders_state_check
|
||||
CHECK (state IN (
|
||||
'PENDING','AWAITING_PAYMENT','PAID','PROCESSING',
|
||||
'SHIPPED','DELIVERED','CANCELLED','REFUNDED','PARTIALLY_REFUNDED'
|
||||
))
|
||||
`);
|
||||
|
||||
// user_id becomes NOT NULL again; fails if any NULL rows exist.
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP CONSTRAINT IF EXISTS orders_orders_user_required_when_not_pos`);
|
||||
pgm.sql(`ALTER TABLE orders_orders ALTER COLUMN user_id SET NOT NULL`);
|
||||
|
||||
pgm.sql(`DROP INDEX IF EXISTS orders_orders_cash_session_idx`);
|
||||
pgm.sql(`DROP INDEX IF EXISTS orders_orders_terminal_idx`);
|
||||
pgm.sql(`DROP INDEX IF EXISTS orders_orders_source_idx`);
|
||||
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP CONSTRAINT IF EXISTS orders_orders_cash_session_id_fkey`);
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP CONSTRAINT IF EXISTS orders_orders_terminal_id_fkey`);
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP COLUMN IF EXISTS cash_session_id`);
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP COLUMN IF EXISTS terminal_id`);
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP CONSTRAINT IF EXISTS orders_orders_source_check`);
|
||||
pgm.sql(`ALTER TABLE orders_orders DROP COLUMN IF EXISTS source`);
|
||||
};
|
||||
Reference in New Issue
Block a user