/** * 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 constraint. constraints: { check: 'amount_cents != 0', }, }, ); // ── 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 }); };