111 lines
3.8 KiB
JavaScript
111 lines
3.8 KiB
JavaScript
/**
|
|
* F-145 — Reporting: payment lines and POS cash-safe capture.
|
|
*
|
|
* Creates `reporting_payment_lines`, an immutable table of payment events per order
|
|
* enriched with store/terminal/session/method — the reporting layer's view of payments.
|
|
*
|
|
* Immutability: no UPDATE path. A refund is a new row with status='refund'.
|
|
*
|
|
* Idempotency: `pgm.createTable` is already IF NOT EXISTS; the DO $$ block guards
|
|
* against duplicate FK constraint errors on re-run (PG16 does NOT support
|
|
* `ADD CONSTRAINT IF NOT EXISTS`).
|
|
*
|
|
* @param {import('node-pg-migrate').MigrationBuilder} pgm
|
|
*/
|
|
|
|
/** Table name */
|
|
const TABLE = 'reporting_payment_lines';
|
|
|
|
/** FK name for order_id */
|
|
const FK_ORDER = 'reporting_payment_lines_order_id_fkey';
|
|
/** FK name for store_id */
|
|
const FK_STORE = 'reporting_payment_lines_store_id_fkey';
|
|
|
|
export const up = (pgm) => {
|
|
// ── Table ────────────────────────────────────────────────────────────────
|
|
// `createTable` generates CREATE TABLE IF NOT EXISTS (PG>=9.6).
|
|
pgm.createTable(
|
|
TABLE,
|
|
{
|
|
id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') },
|
|
order_id: { type: 'uuid', notNull: true },
|
|
store_id: { type: 'uuid', notNull: true },
|
|
terminal_id: { type: 'uuid' },
|
|
cash_session_id: { type: 'uuid' },
|
|
payment_method_id: { type: 'uuid' },
|
|
provider: { type: 'text', notNull: true },
|
|
amount_cents: { type: 'integer', notNull: true },
|
|
currency: { type: 'text', notNull: true, default: "'EUR'" },
|
|
status: { type: 'text', notNull: true },
|
|
provider_ref: { type: 'text' },
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') },
|
|
updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') },
|
|
},
|
|
{
|
|
// Inline CHECK constraints.
|
|
constraints: {
|
|
check: {
|
|
nonzero_amount: 'amount_cents != 0',
|
|
eur_only: "currency = 'EUR'",
|
|
valid_status: "status IN ('payment', 'refund', 'partial_refund')",
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
// ── Foreign Keys (guarded — PG16 does NOT support ADD CONSTRAINT IF NOT EXISTS) ─
|
|
pgm.sql(`
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = '${FK_ORDER}'
|
|
) THEN
|
|
ALTER TABLE ${TABLE}
|
|
ADD CONSTRAINT ${FK_ORDER}
|
|
FOREIGN KEY (order_id) REFERENCES orders_orders(id)
|
|
ON DELETE RESTRICT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = '${FK_STORE}'
|
|
) THEN
|
|
ALTER TABLE ${TABLE}
|
|
ADD CONSTRAINT ${FK_STORE}
|
|
FOREIGN KEY (store_id) REFERENCES pos_stores(id)
|
|
ON DELETE RESTRICT;
|
|
END IF;
|
|
END
|
|
$$ LANGUAGE plpgsql;
|
|
`);
|
|
|
|
// ── Indexes ─────────────────────────────────────────────────────────────
|
|
pgm.createIndex(TABLE, ['store_id', 'created_at'], {
|
|
name: 'reporting_payment_lines_store_created_idx',
|
|
});
|
|
pgm.createIndex(TABLE, 'order_id', { name: 'reporting_payment_lines_order_idx' });
|
|
pgm.createIndex(
|
|
TABLE,
|
|
'cash_session_id',
|
|
{
|
|
name: 'reporting_payment_lines_session_idx',
|
|
where: 'cash_session_id IS NOT NULL',
|
|
},
|
|
);
|
|
};
|
|
|
|
export const down = (pgm) => {
|
|
pgm.dropIndex(TABLE, 'cash_session_id', {
|
|
name: 'reporting_payment_lines_session_idx',
|
|
ifExists: true,
|
|
});
|
|
pgm.dropIndex(TABLE, 'order_id', {
|
|
name: 'reporting_payment_lines_order_idx',
|
|
ifExists: true,
|
|
});
|
|
pgm.dropIndex(TABLE, ['store_id', 'created_at'], {
|
|
name: 'reporting_payment_lines_store_created_idx',
|
|
ifExists: true,
|
|
});
|
|
pgm.dropTable(TABLE, { ifExists: true });
|
|
};
|