115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
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<PosCashSession | undefined> {
|
|
const result = await this.pool.query<SessionRow>(
|
|
'SELECT * FROM pos_cash_sessions WHERE id = $1',
|
|
[id],
|
|
);
|
|
return result.rows[0] ? toSession(result.rows[0]) : undefined;
|
|
}
|
|
|
|
async findOpenByTerminal(terminalId: string): Promise<PosCashSession | undefined> {
|
|
const result = await this.pool.query<SessionRow>(
|
|
"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<PosCashSession> {
|
|
// 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<SessionRow>(
|
|
`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<PosCashSession> {
|
|
const difference = input.actualCashCents - input.closingCashCents;
|
|
const result = await this.pool.query<SessionRow>(
|
|
`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<SessionRow>(
|
|
`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),
|
|
};
|
|
}
|
|
}
|