From ad6e1ce8f00cad24d5437b812be96a6ea2b1736d Mon Sep 17 00:00:00 2001 From: jubnl Date: Tue, 22 Sep 2026 03:44:48 +0200 Subject: [PATCH] fix(vacay): type the loop accumulators the async sweep introduced --- server/src/nest/vacay/vacay.service.ts | 23 +++++++++++++------ .../nest/doc-sync/doc-sync.controller.test.ts | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/server/src/nest/vacay/vacay.service.ts b/server/src/nest/vacay/vacay.service.ts index 35f14bba1..5512e27b7 100644 --- a/server/src/nest/vacay/vacay.service.ts +++ b/server/src/nest/vacay/vacay.service.ts @@ -174,7 +174,7 @@ function windowEndYear(end: string): number { * * Post-migration fixes on top of the relocated legacy behavior: the * multi-statement writes (acceptInvite, dissolvePlan, deleteYear, updatePlan's - * carry-over recompute) run in db.transaction(); every outbound fetch carries + * carry-over recompute) run in uow.transactional(); every outbound fetch carries * an AbortSignal timeout and the nager.at responses are ok-checked; * applyHolidayCalendars honors the cache TTL; addYear no longer swallows real * errors; the holiday cache is instance state instead of a module-level map. @@ -883,7 +883,7 @@ export class VacayService { // `map` cannot await, and `shareDisplayColor` mutates `usedColors` as it // hands colors out, so the projection runs as an explicit loop — same rows, // same order, same color-allocation sequence. - const incoming = []; + const incoming: { id: number; owner_id: number; username: string; color: string; hidden: boolean }[] = []; for (const s of incomingRows.filter(s => !coMembers.has(s.owner_id))) { incoming.push({ id: s.id, @@ -980,7 +980,11 @@ export class VacayService { // `map` cannot await, and `shareDisplayColor` mutates `usedColors` as it // hands colors out, so the projection runs as an explicit loop — same rows, // same order, same color-allocation sequence. - const calendars = []; + const calendars: { + share_id: number; owner_id: number; owner_name: string; color: string; hidden: boolean; + entries: { date: string; fraction: number; kind: string | null }[]; + companyHolidays: { date: string }[]; + }[] = []; for (const s of shares.filter(s => !coMembers.has(s.owner_id))) { const color = await this.shareDisplayColor(s.owner_id, usedColors); const plan = await this.peekActivePlan(s.owner_id); @@ -988,7 +992,7 @@ export class VacayService { calendars.push({ share_id: s.id, owner_id: s.owner_id, owner_name: s.username, color, hidden: !!s.hidden, entries: [], companyHolidays: [] }); continue; } - const entries = this.db.all( + const entries = this.db.all<{ date: string; fraction: number; kind: string | null }>( 'SELECT date, fraction, kind FROM vacay_entries WHERE plan_id = ? AND user_id = ? AND date >= ? AND date < ? ORDER BY date', plan.id, s.owner_id, start, end ); @@ -996,7 +1000,7 @@ export class VacayService { // only exposed while the owner has the feature enabled, and dates only — // the note text may be authored by plan members who aren't part of the share. const companyHolidays = plan.company_holidays_enabled - ? this.db.all('SELECT date FROM vacay_company_holidays WHERE plan_id = ? AND date >= ? AND date < ? ORDER BY date', plan.id, start, end) + ? this.db.all<{ date: string }>('SELECT date FROM vacay_company_holidays WHERE plan_id = ? AND date >= ? AND date < ? ORDER BY date', plan.id, start, end) : []; calendars.push({ share_id: s.id, owner_id: s.owner_id, owner_name: s.username, color, hidden: !!s.hidden, entries, companyHolidays }); } @@ -1062,7 +1066,7 @@ export class VacayService { const members = await this.getPlanUsers(planId); // `map` cannot await the per-member window read, so it runs as an explicit // loop — same ids, same order. - const windows = []; + const windows: { start: string; end: string }[] = []; for (const id of members.length > 0 ? members.map(m => m.id) : [owner?.owner_id ?? -1]) { windows.push(await this.resolveYearWindow(id, year)); } @@ -1174,7 +1178,12 @@ export class VacayService { // `map` cannot await, and the body also writes next year's carry-over, so // the projection runs as an explicit loop — same users, same order, same writes. - const rows = []; + const rows: { + user_id: number; person_name: string; person_color: string; + year: number; vacation_days: number; carried_over: number; + total_available: number; used: number; remaining: number; comp_used: number; + window_start: string; window_end: string; + }[] = []; for (const u of users) { const used = await this.usedDays(u.id, planId, year); const compUsed = await this.compUsedDays(u.id, planId, year); diff --git a/server/tests/unit/nest/doc-sync/doc-sync.controller.test.ts b/server/tests/unit/nest/doc-sync/doc-sync.controller.test.ts index e38601a5c..dead09ab5 100644 --- a/server/tests/unit/nest/doc-sync/doc-sync.controller.test.ts +++ b/server/tests/unit/nest/doc-sync/doc-sync.controller.test.ts @@ -182,10 +182,10 @@ let tripId: number; let otherTripId: number; beforeAll(async () => { - config = new DocSyncConfigService(dbs, registry, await createTestUnitOfWork(testDb)); - controller = new DocSyncController(config, sync as unknown as DocSyncService, registry, dbs, realtime as unknown as RealtimeService); createTables(testDb); runMigrations(testDb); + config = new DocSyncConfigService(dbs, registry, await createTestUnitOfWork(testDb)); + controller = new DocSyncController(config, sync as unknown as DocSyncService, registry, dbs, realtime as unknown as RealtimeService); const o = createUser(testDb, { username: 'owner', email: 'owner@test.local' }).user; const m = createUser(testDb, { username: 'member', email: 'member@test.local' }).user; const a = createUser(testDb, { username: 'admin', email: 'admin@test.local', role: 'admin' }).user;