feat(F-189): completed feature

This commit is contained in:
chattie
2026-08-22 23:01:59 +02:00
parent 0e3c488c85
commit 6b93e91ef4
24 changed files with 1535 additions and 63 deletions

View File

@@ -0,0 +1,94 @@
/**
* F-189 — POS returns per-line counters + pos_returns table.
*
* 1. Adds `orders_items.returned_quantity integer NOT NULL DEFAULT 0` with
* `CHECK (returned_quantity >= 0 AND returned_quantity <= quantity)`.
* 2. Creates `pos_returns` as the historical ledger of returns with a unique
* `(order_id, idempotency_key)` index for replay.
* 3. Allows `'return'` in `inventory_movements.operation` so cashiers can
* see the movement in inventory history.
*
* @param {import('node-pg-migrate').MigrationBuilder} pgm
*/
export const up = (pgm) => {
// ── orders_items.returned_quantity ─────────────────────────────────────
pgm.sql(`
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'orders_items' AND column_name = 'returned_quantity'
) THEN
ALTER TABLE orders_items
ADD COLUMN returned_quantity integer NOT NULL DEFAULT 0;
END IF;
END $$
`);
pgm.sql(`ALTER TABLE orders_items DROP CONSTRAINT IF EXISTS orders_items_returned_quantity_check`);
pgm.sql(`
ALTER TABLE orders_items
ADD CONSTRAINT orders_items_returned_quantity_check
CHECK (returned_quantity >= 0 AND returned_quantity <= quantity)
`);
// ── inventory_movements.operation — allow 'return' ─────────────────────
pgm.sql(`ALTER TABLE inventory_movements DROP CONSTRAINT IF EXISTS inventory_movements_operation_check`);
pgm.sql(`
ALTER TABLE inventory_movements
ADD CONSTRAINT inventory_movements_operation_check
CHECK (operation IN ('reserve','release','confirm','set_available','return'))
`);
// ── pos_returns ─────────────────────────────────────────────────────────
pgm.createTable('pos_returns', {
id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') },
order_id: { type: 'uuid', notNull: true, references: 'orders_orders(id)', onDelete: 'RESTRICT' },
idempotency_key: { type: 'text', notNull: true },
terminal_id: { type: 'uuid', notNull: true, references: 'pos_terminals(id)', onDelete: 'RESTRICT' },
cash_session_id: { type: 'uuid', notNull: true, references: 'pos_cash_sessions(id)', onDelete: 'RESTRICT' },
store_id: { type: 'uuid', notNull: true, references: 'pos_stores(id)', onDelete: 'RESTRICT' },
user_id: { type: 'uuid', notNull: true, references: 'backoffice_users(id)', onDelete: 'RESTRICT' },
total_cents: { type: 'integer', notNull: true, check: 'total_cents > 0' },
status: {
type: 'text',
notNull: true,
check: "status IN ('refund','partial_refund')",
},
reason: { type: 'text', notNull: true },
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') },
});
pgm.sql(`
CREATE UNIQUE INDEX IF NOT EXISTS pos_returns_idempotency_unique_idx
ON pos_returns (order_id, idempotency_key)
`);
pgm.createIndex('pos_returns', ['store_id', 'created_at'], {
name: 'pos_returns_store_created_idx',
});
pgm.createIndex('pos_returns', 'terminal_id', {
name: 'pos_returns_terminal_idx',
where: 'terminal_id IS NOT NULL',
});
};
export const down = (pgm) => {
pgm.dropTable('pos_returns', { ifExists: true });
pgm.sql(`ALTER TABLE inventory_movements DROP CONSTRAINT IF EXISTS inventory_movements_operation_check`);
pgm.sql(`
ALTER TABLE inventory_movements
ADD CONSTRAINT inventory_movements_operation_check
CHECK (operation IN ('reserve','release','confirm','set_available'))
`);
pgm.sql(`ALTER TABLE orders_items DROP CONSTRAINT IF EXISTS orders_items_returned_quantity_check`);
pgm.sql(`
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'orders_items' AND column_name = 'returned_quantity'
) THEN
ALTER TABLE orders_items DROP COLUMN returned_quantity;
END IF;
END $$
`);
};