Appearance
Daily ledger
Trigger: BullMQ cron at 23:30 America/Mexico_City (30 23 * * *). Worker: workers/daily-snapshots.ts. Manual trigger: npx tsx workers/daily-snapshots.ts --run-once.
Replaces Tonder's dailyLedgerCronHandler + XLSX dispatch. Vecnet doesn't render an XLSX every night — snapshot rows are the durable record and the admin UI reads them live. XLSX exports are on-demand (per settlement, not per day).
What runs
lib/snapshots/build.ts:buildSnapshotsForDay({ date }):
- List every
Accountwhereactive = true. - For each account, two
groupByqueries:- Lifetime sums:
direction×amountCentsover every entry withcreatedAt < dayEnd. ProducesbalanceCentsper the account type (asset/expense = DR−CR; liability/revenue/equity = CR−DR). - Day's sums: same shape but bounded
gte dayStart, lt dayEnd. ProducesinflowsCents+outflowsCentsper type.
- Lifetime sums:
BalanceSnapshot.upserton(accountId, date)— idempotent on re-runs.
Day boundaries are computed in America/Mexico_City. The cron fires at local 23:30 so the snapshot reflects the day that's about to close (the last 30 minutes are negligible operationally).
Why 23:30 local (not 10:00 UTC like Tonder)
Tonder's worker writes for the day that just ended. Vecnet writes for the day that's about to end, then reuses the row as the current day's "as of" reading in the morning. The latency difference is irrelevant for treasury reporting; the local time lets finops sanity-check just before shift-change without a "wait for the snapshot to drop at 4 AM" gap.
Surfaces that read from BalanceSnapshot
/finances/snapshots— pivot or single-account view, date-range filtered. The pivot caps at 14 columns for readability./finances/accounts/[id]— the last 7 rows render as a text-bar sparkline next to the live current balance.
Redis-optional pattern
ts
if (process.argv.includes("--run-once")) {
await runOnce();
return;
}
if (!process.env.REDIS_URL) {
logger.warn("REDIS_URL is not set — daily-snapshots worker is a no-op.");
return;
}
// otherwise wire up BullMQ scheduler + workerSame pattern in workers/settlement-scheduler.ts and workers/rolling-release.ts. Without Redis the workers exit cleanly so a fresh clone works in local dev without infrastructure overhead.