- Stats: bars table is now the LEFT side of the join to transactions, so a bar with zero sales still gets a zero row instead of vanishing from the totals table until its first sale (indistinguishable from a deleted bar). by_day stays JS-computed on purpose — a SQL rewrite would trade DST-aware timezone handling for a fixed-hour-offset 'localtime' expression that's wrong on DST transition nights, to fix a cost the original review noted is 'fine today'. Not worth that trade for a money-adjacent report; left a comment explaining why. - CSV export: cells starting with =/+/-/@ are now prefixed with ' before quoting, closing a formula-injection path (an admin-entered drink/bar name like =HYPERLINK(...) would otherwise execute when the export is opened in Excel/LibreOffice). - server/index.ts: PORT is now parsed and range-checked instead of a bare Number(...) (an unparseable value silently became NaN, and Fastify listens on a random free port for that); ADMIN_PASSWORD missing now warns at boot instead of only surfacing as a 500 at the first login attempt; new WUTZ_TRUST_PROXY env flag (off by default) so req.ip can actually reflect the real client behind a reverse proxy, documented in the README alongside the other env vars. - time.ts: WUTZ_DAY_CUTOFF_HOUR gets the same parse+range-check treatment, for the same reason (a typo used to silently disable the business-day rollback with no error). - shared/src/index.ts: Drink.archived is now typed 0 | 1, matching what SQLite actually returns (was boolean, which only worked by accident since 0 is falsy); removed TransactionRecord/ TransactionItemRecord, declared but never returned by any route — leftovers from a planned endpoint that was never built. Verified: pnpm --filter server|client typecheck/build all clean; also ran the built server with a bad PORT and no ADMIN_PASSWORD to confirm both warnings fire and the port falls back correctly.
39 lines
809 B
TypeScript
39 lines
809 B
TypeScript
export interface Bar {
|
|
id: number;
|
|
name: string;
|
|
pfand_cents: number;
|
|
}
|
|
|
|
export interface Drink {
|
|
id: number;
|
|
name: string;
|
|
price_cents: number;
|
|
// SQLite has no boolean type — this is what the driver actually hands
|
|
// back for an INTEGER column, not `boolean`. It happened to work
|
|
// because `0` is falsy, but `archived === false` would silently be
|
|
// wrong the moment someone wrote that comparison.
|
|
archived: 0 | 1;
|
|
}
|
|
|
|
export interface BarConfig {
|
|
bar: Bar;
|
|
drinks: Drink[];
|
|
}
|
|
|
|
export interface CartItem {
|
|
drink_id: number;
|
|
qty: number;
|
|
}
|
|
|
|
export interface CreateTransactionRequest {
|
|
client_uuid: string;
|
|
bar_id: number;
|
|
crew: boolean;
|
|
items: CartItem[];
|
|
pfand_returns: number;
|
|
}
|
|
|
|
export interface CreateTransactionResponse {
|
|
id: number;
|
|
total_cents: number;
|
|
}
|