Files
mercadodevida/project/migrations/049_reporting_payment_lines.js
2026-08-22 12:47:16 +02:00

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 });
};