Design for a team-scoped notification center (bell icon, dropdown,
full history page) covering player/role/share-link/invite-link
events, decoupled via @nestjs/event-emitter from a central
notifications module.
runDueRecurringTransactions() always logs a start/finish marker for
observability, even when nothing is due, but the "does nothing" test
still asserted logger.info was never called. Pre-existing baseline
failure, unrelated to the notification-center work about to start.
- LoggingService.findLogs(): the `to` date filter compared a date-only
string (e.g. from a date picker) against a timestamp column, which
parses to midnight and silently excludes the entire last day. Widen
it to end-of-day so the range is genuinely inclusive.
- LogRetentionScheduler.cleanupOldLogs(): wrap the delete in try/catch
and log failures via logger.error, matching the existing convention
in CashboxExportScheduler/RecurringTransactionsScheduler. Without
this, a failed nightly cleanup would fail silently - exactly what
this feature exists to prevent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Global admins couldn't see the app's event log (no read endpoint or UI
existed for it) and had no way to clean up old entries or re-run a
scheduled job without touching the database or server directly.
Backend:
- LoggingService.findLogs() + admin-only LogsController (GET
admin/logs) with level/event/date-range/search filtering and
pagination, mirroring AdminUsersService.findPlayers().
- LogRetentionScheduler deletes log entries older than
LOG_RETENTION_DAYS (default 365, via app.config.ts), following the
existing @Cron scheduler pattern.
- Admin-only POST admin/run endpoints on CashboxExportController and
RecurringTransactionsController that invoke the existing schedulers'
public run methods on demand - both are safe to re-run since their
"due" queries advance nextRunDate only after a successful run.
Frontend:
- New /logs page (global-admin gated, same pattern as /users): AG-Grid
infinite-scroll table with level/event/date-range/search filters,
plus buttons to trigger the two jobs now and see the result land in
the grid immediately.
- LogsApi, and triggerRunNow() added to the existing CashboxExportApi
and RecurringTransactionApi.
- Discoverability link from /users to /logs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Lock in the exact expected page count (4) for the 60+60-row pagination
test, which previously only checked the buffer was non-trivial. This is
the scenario most likely to expose a footer/pagination regression.
- Add short comments explaining the footerY height-bound fix and the
pdfPageCount() regex's coupling to pdfkit's serialization format.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expenses were stored as positive amounts (DB convention) and buildRows()
never negated them, so they were added to the running budget total
instead of subtracted. Negate expense amounts for team-wallet
transactions, mirroring the existing signedFlowAmount() convention in
teams.service.ts.
Separately, addFooters() placed footer text inside the reserved bottom
margin without an explicit height option, which made pdfkit's
LineWrapper treat every footer draw as overflowing the page and call
continueOnNewPage() twice per page - inflating page counts 3x with
blank trailing pages. Bounding the footer text to its own small height
box prevents pdfkit's automatic pagination from firing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both the manual download endpoint and the recurring email subscription
now pass buildReceivableRows() output into buildPdf, so every PDF
report includes the Forderungen section regardless of how it was
generated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The PDF export was an unformatted list of doc.text() lines and only
showed real cash movements (payment type). Rebuilds it as a proper
two-section report: a branded header band, a bordered/zebra-striped
table with colored amounts and bold running balance for cash
movements, and a second "Forderungen" section listing fine/levy/fee
entries created in the period with their own total. Tables paginate
across pages and every page gets a footer with page numbers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cashbox export previously only saw payment transactions. This adds
the query for fine/levy/fee entries (Forderungen) that the redesigned
PDF report will show in a separate section.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MySQL/MariaDB reject a DEFAULT value on TEXT-backed columns (TypeORM's
simple-array maps to TEXT), so table creation failed with
"BLOB, TEXT, GEOMETRY or JSON column 'recipients' can't have a default
value". The service always assigns recipients before saving, so no
DB-level default was ever needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Reject dates that are not strict YYYY-MM-DD (was accepting full ISO
datetimes, which silently produced empty exports instead of a 400)
and reject from > to with a 400 before touching the team/DB.
- Emit the cashbox_export_download and cashbox_export_subscription_update
audit log events that were declared but never fired, matching the
audit trail every sibling feature already has.
- Restore full type checking on the pdfkit import via `import = require()`
instead of an untyped require() with an eslint-disable.
- Tighten a cashbox.spec.ts assertion to check the exact dialog class
instead of expect.anything(), so it can't pass with the wrong dialog
wired to the Export button.
- Style and announce the export dialogs' error messages using this
codebase's established error-message/role=alert pattern.
openExportDialog()/openExportSubscriptionDialog() only guarded on teamId
truthiness, relying solely on the template @if for permission gating.
Every other permission-gated method in Cashbox (submitPlayerBooking,
reverseBooking) re-checks the permission internally too. Add the same
guard here, plus a test asserting direct invocation without booking
rights does not call dialog.open.
Adds an Export button (opens CashboxExportDialog directly) plus a
secondary menu (opens CashboxExportSubscriptionDialog) to the journal
toolbar, gated by the existing canBook permission signal.
- Wrap runOne(subscription) in try/catch to ensure one subscription failure doesn't block remaining subscriptions
- Log failed subscriptions with new 'cashbox_export_subscription_run_fail' event
- Add new LOGEVENT type for subscription run failures
- Add test to verify second subscription processes even when first fails (continues processing independently)
- All 7 tests passing: 6 original + 1 new failure handling test
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Task-by-task TDD plan covering manual CSV/PDF export, the recurring
PDF-mailing subscription, and the frontend wiring, grounded in the
existing recurring-transactions module and mail service as precedent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers on-demand CSV/PDF export of real cash-affecting transactions
and an optional per-team recurring PDF mailing to arbitrary email
addresses, following the same brainstorming process used for
recurring transactions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>