Event finance - Finance tab on the event page: P&L summary with a revenue-to-result waterfall, costs and other income in one ledger, and the partner split with payouts. Lifecycle stepper (Selling, Adding costs, Ready to close, Finalized) with what is left to do; closing the books goes through a checklist dialog that freezes the numbers. - Expense modal shows only the fields each calculation type needs, a live preview with the event's real counts, and a category suggested from the description. Date inputs follow the UI language. - Global Finance page: profit per event with outliers clipped and labelled, and a "Ready to close" list of past events whose books are still open. - Calculation service (integer PYG, basis points) with pinned regression scenarios; PYG formatting centralised in lib/money with locale-aware separators. Event team permissions - Per-event members with role presets and requireEventPermission; the header stat "Confirmed" is relabelled "Not checked in yet", which is what it counts. Public sales state - One sales state (online, door, sold out, ended, external, cancelled) for the event page, listings and JSON-LD, with the door price and tenders shown only while people can still pay at the door. Frontend unit tests run with vitest (npm test in frontend/). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33 lines
1.1 KiB
TypeScript
33 lines
1.1 KiB
TypeScript
// finance_audit_log rows. Returned as TxOps so every change and its audit row
|
|
// commit (or roll back) together.
|
|
|
|
import { financeAuditLog } from '../../db/index.js';
|
|
import { generateId, getNow } from '../utils.js';
|
|
import { insertOp, type TxOp } from '../txOps.js';
|
|
|
|
export type FinanceEntity =
|
|
| 'expense' | 'other_income' | 'partner' | 'finance_state' | 'member'
|
|
| 'expense_category' | 'expense_template' | 'expense_template_pack' | 'payment_fee';
|
|
|
|
export function financeAuditOp(entry: {
|
|
eventId: string | null;
|
|
actorUserId: string;
|
|
entityType: FinanceEntity;
|
|
entityId: string | null;
|
|
action: string;
|
|
before?: unknown;
|
|
after?: unknown;
|
|
}): TxOp {
|
|
return insertOp(financeAuditLog, {
|
|
id: generateId(),
|
|
eventId: entry.eventId,
|
|
actorUserId: entry.actorUserId,
|
|
entityType: entry.entityType,
|
|
entityId: entry.entityId,
|
|
action: entry.action,
|
|
beforeJson: entry.before === undefined || entry.before === null ? null : JSON.stringify(entry.before),
|
|
afterJson: entry.after === undefined || entry.after === null ? null : JSON.stringify(entry.after),
|
|
createdAt: getNow(),
|
|
});
|
|
}
|