import type pg from 'pg'; import type { PosCashSessionRepository } from '../domain/ports.js'; import type { PosCashSession, OpenCashSessionInput, CloseCashSessionInput } from '../domain/cash-session.js'; interface SessionRow { id: string; terminal_id: string; store_id: string; user_id: string; status: 'OPEN' | 'CLOSED'; opened_at: Date; closed_at: Date | null; opening_cash_cents: number; closing_cash_cents: number | null; expected_cash_cents: number | null; actual_cash_cents: number | null; difference_cents: number | null; notes: string | null; created_at: Date; updated_at: Date; } function toSession(row: SessionRow): PosCashSession { return { id: row.id, terminalId: row.terminal_id, storeId: row.store_id, userId: row.user_id, status: row.status, openedAt: row.opened_at, closedAt: row.closed_at, openingCashCents: row.opening_cash_cents, closingCashCents: row.closing_cash_cents, expectedCashCents: row.expected_cash_cents, actualCashCents: row.actual_cash_cents, differenceCents: row.difference_cents, notes: row.notes, createdAt: row.created_at, updatedAt: row.updated_at, }; } export class PgCashSessionRepository implements PosCashSessionRepository { constructor(private readonly pool: pg.Pool) {} async findById(id: string): Promise { const result = await this.pool.query( 'SELECT * FROM pos_cash_sessions WHERE id = $1', [id], ); return result.rows[0] ? toSession(result.rows[0]) : undefined; } async findOpenByTerminal(terminalId: string): Promise { const result = await this.pool.query( "SELECT * FROM pos_cash_sessions WHERE terminal_id = $1 AND status = 'OPEN'", [terminalId], ); return result.rows[0] ? toSession(result.rows[0]) : undefined; } async open(input: OpenCashSessionInput): Promise { // Get store_id from terminal const terminal = await this.pool.query<{ store_id: string }>( 'SELECT store_id FROM pos_terminals WHERE id = $1', [input.terminalId], ); if (!terminal.rows[0]) throw new Error(`Terminal ${input.terminalId} not found`); const storeId = terminal.rows[0].store_id; const result = await this.pool.query( `INSERT INTO pos_cash_sessions (terminal_id, store_id, user_id, opening_cash_cents) VALUES ($1, $2, $3, $4) RETURNING *`, [input.terminalId, storeId, input.userId, input.openingCashCents], ); return toSession(result.rows[0]); } async close(input: CloseCashSessionInput): Promise { const difference = input.actualCashCents - input.closingCashCents; const result = await this.pool.query( `UPDATE pos_cash_sessions SET status = 'CLOSED', closed_at = now(), closing_cash_cents = $2, actual_cash_cents = $3, difference_cents = $4, notes = $5, updated_at = now() WHERE id = $1 RETURNING *`, [input.sessionId, input.closingCashCents, input.actualCashCents, difference, input.notes ?? null], ); if (!result.rows[0]) throw new Error(`Session ${input.sessionId} not found`); return toSession(result.rows[0]); } async listByStore( storeId: string, limit = 50, offset = 0, ): Promise<{ sessions: PosCashSession[]; total: number }> { const [countResult, listResult] = await Promise.all([ this.pool.query<{ count: string }>( 'SELECT COUNT(*) as count FROM pos_cash_sessions WHERE store_id = $1', [storeId], ), this.pool.query( `SELECT * FROM pos_cash_sessions WHERE store_id = $1 ORDER BY opened_at DESC LIMIT $2 OFFSET $3`, [storeId, limit, offset], ), ]); return { sessions: listResult.rows.map(toSession), total: parseInt(countResult.rows[0].count, 10), }; } }