fix(vacay): a day set for a year the plan has not opened adds that year

The calendar offers only the plan's years, but a vacation day or company
holiday could be written for any date. A day set months ahead, as the
assistant did for 6 January 2027, was stored and counted yet never shown,
because 2027 was not a year of the plan. Writing a day now adds its year,
with every member's allowance, the way opening it by hand does; the year
is the writer's leave period that holds the date. This holds for the app,
MCP, plugins and the assistant alike, since they all write through here.
This commit is contained in:
Maurice
2026-10-24 16:52:31 +02:00
parent 01333c6ff3
commit fbc85b05a1
2 changed files with 58 additions and 2 deletions
+23 -2
View File
@@ -1150,16 +1150,35 @@ export class VacayService {
// Removing a stray entry on a blocked day stays possible; keeping one
// there (converted in place) does not.
if (weekendBlocked) return { error: 'weekend_blocked' };
await this.entries.updateFractionKind(existing.id, frac, knd);
await this.uow.transactional(async () => {
await this.entries.updateFractionKind(existing.id, frac, knd);
await this.ensureYearOf(planId, userId, date);
});
await this.notifyPlanUsers(planId, socketId);
return { action: 'updated', fraction: frac, kind: knd };
}
if (weekendBlocked) return { error: 'weekend_blocked' };
await this.entries.insertEntry(planId, userId, date, '', frac, knd);
await this.uow.transactional(async () => {
await this.entries.insertEntry(planId, userId, date, '', frac, knd);
await this.ensureYearOf(planId, userId, date);
});
await this.notifyPlanUsers(planId, socketId);
return { action: 'added', fraction: frac, kind: knd };
}
/**
* A day written for a year the plan has not opened yet adds that year, with
* each member's allowance, the way opening it by hand does. Without it a day
* set months ahead (by the assistant, MCP, a plugin or an old client) was
* stored but never shown, since the calendar only offers the plan's years.
* The year is the writer's leave period that holds the date (#737).
*/
private async ensureYearOf(planId: number, userId: number, date: string): Promise<void> {
const [y, m, d] = date.split('-').map(Number);
const year = await this.currentPeriodYear(userId, new Date(y, m - 1, d));
if (!(await this.years.exists(planId, year))) await this.addYear(planId, year, undefined);
}
/**
* A company holiday on or off, whole or half (#2439). The same click again clears
* it; the other size converts it in place, the way a vacation entry toggles. A
@@ -1179,6 +1198,8 @@ export class VacayService {
await this.companyHolidays.insertHoliday(planId, date, note || '', frac); // VC120
}
await this.makeRoomForCompanyHoliday(planId, date, frac);
const owner = await this.plans.findOwnerId(planId);
if (owner) await this.ensureYearOf(planId, owner.owner_id, date);
return { action: existing ? 'updated' : 'added', fraction: frac };
});
await this.notifyPlanUsers(planId, socketId);
@@ -1875,3 +1875,38 @@ describe('quirk fixes', () => {
expect(await storedFields(VacayYears, { plan: plan.id, year: year + 1 }, ['id'])).toBeUndefined();
});
});
describe('a day written for a year the plan has not opened', () => {
it('VACAY-SVC-079: adds that year with the member allowance, so the day shows in the calendar', async () => {
const { user, plan } = await setupUserWithPlan();
await allowWeekends(plan.id);
expect(await svc.listYears(plan.id)).not.toContain(2031);
const result = await svc.toggleEntry(user.id, plan.id, '2031-01-06', 1, 'vacation');
expect(result.action).toBe('added');
expect(await svc.listYears(plan.id)).toContain(2031);
expect(await storedRow(VacayUserYears, { user: user.id, plan: plan.id, year: 2031 })).toBeDefined();
});
it('VACAY-SVC-080: taking the day away again keeps the year it opened', async () => {
const { user, plan } = await setupUserWithPlan();
await allowWeekends(plan.id);
await svc.toggleEntry(user.id, plan.id, '2032-03-02', 1, 'vacation');
await svc.deleteYear(plan.id, 2032, undefined);
const before = await svc.listYears(plan.id);
await svc.toggleEntry(user.id, plan.id, '2032-03-02', 1, 'vacation');
await svc.toggleEntry(user.id, plan.id, '2032-03-02', 1, 'vacation');
expect(await svc.listYears(plan.id)).toEqual([...before, 2032].sort((a, b) => a - b));
});
it('VACAY-SVC-081: a company holiday in a year not opened yet adds that year too', async () => {
const { plan } = await setupUserWithPlan();
await svc.toggleCompanyHoliday(plan.id, '2033-12-25', 'Christmas', undefined);
expect(await svc.listYears(plan.id)).toContain(2033);
});
});