Drop empty leftover dateless days when a trip gets a shorter dated range (#1083)

generateDays kept all unused dateless placeholder days after switching to an explicit (shorter) date range, so day_count (COUNT(*) FROM days) stayed inflated. Delete the empty leftovers (no assignments/notes/accommodations) like the dateless path already does, while preserving any that still hold content. Adds TRIP-SVC-017.
This commit is contained in:
Maurice
2026-05-31 21:54:16 +02:00
parent c565f22bf2
commit 48098ef5ec
2 changed files with 43 additions and 2 deletions
+16 -2
View File
@@ -122,12 +122,26 @@ export function generateDays(tripId: number | bigint | string, startDate: string
del.run(dated[i].id); del.run(dated[i].id);
} }
// Any remaining unused dateless days: keep as dateless, just renumber. // Any remaining unused dateless days: drop the empty placeholders so day_count
// reflects the dated range, but keep ones that still hold content (assignments,
// notes, accommodations) — mirrors the dateless-path trimming above (#1083).
// Base must be max(targetDates.length, dated.length) to avoid colliding with // Base must be max(targetDates.length, dated.length) to avoid colliding with
// positives already assigned by the main loop or the overflow loop above. // positives already assigned by the main loop or the overflow loop above.
const isEmptyDay = db.prepare(
`SELECT NOT EXISTS (SELECT 1 FROM day_assignments da WHERE da.day_id = @id)
AND NOT EXISTS (SELECT 1 FROM day_notes dn WHERE dn.day_id = @id)
AND NOT EXISTS (SELECT 1 FROM day_accommodations dac WHERE dac.start_day_id = @id OR dac.end_day_id = @id) AS empty`
);
const maxAssigned = Math.max(targetDates.length, dated.length); const maxAssigned = Math.max(targetDates.length, dated.length);
let keptDateless = 0;
for (let i = datelessIdx; i < dateless.length; i++) { for (let i = datelessIdx; i < dateless.length; i++) {
setDayNumber.run(maxAssigned + (i - datelessIdx) + 1, dateless[i].id); const empty = (isEmptyDay.get({ id: dateless[i].id }) as { empty: number }).empty;
if (empty) {
del.run(dateless[i].id);
} else {
setDayNumber.run(maxAssigned + keptDateless + 1, dateless[i].id);
keptDateless++;
}
} }
// Final renumber to compact and eliminate any gaps/negatives // Final renumber to compact and eliminate any gaps/negatives
@@ -242,6 +242,33 @@ describe('generateDays', () => {
const nums = daysAfter.map(d => d.day_number).sort((a, b) => a - b); const nums = daysAfter.map(d => d.day_number).sort((a, b) => a - b);
expect(nums).toEqual([1, 2, 3, 4, 5]); expect(nums).toEqual([1, 2, 3, 4, 5]);
}); });
it('TRIP-SVC-017: switching a dateless trip to a shorter dated range drops empty leftover days but keeps ones with content (#1083)', () => {
const { user } = createUser(testDb);
// A 7-day trip, then cleared to dateless placeholders (day_count = 7).
const trip = createTrip(testDb, user.id, { start_date: '2025-12-01', end_date: '2025-12-07' });
generateDays(trip.id, null, null);
const dateless = getDays(trip.id);
expect(dateless).toHaveLength(7);
expect(dateless.every(d => d.date === null)).toBe(true);
// Give the LAST dateless day real content so it must be preserved.
const place = createPlace(testDb, trip.id);
const assignment = createDayAssignment(testDb, dateless[6].id, place.id);
// Now set an explicit 2-day range. The first two dateless days are reused for
// the dates; the four empty leftovers must be removed, the one with content kept.
generateDays(trip.id, '2026-01-10', '2026-01-11');
const daysAfter = getDays(trip.id);
const dated = daysAfter.filter(d => d.date !== null);
const stillDateless = daysAfter.filter(d => d.date === null);
expect(dated.map(d => d.date)).toEqual(['2026-01-10', '2026-01-11']);
// day_count is COUNT(*) FROM days: 2 dated + 1 content-bearing dateless = 3 (not the stale 7)
expect(daysAfter).toHaveLength(3);
expect(stillDateless).toHaveLength(1);
expect(getAssignments(stillDateless[0].id)[0].id).toBe(assignment.id);
});
}); });
describe('exportICS', () => { describe('exportICS', () => {