feat(platform): checkpoint current implementation state

This commit is contained in:
2026-04-01 07:42:03 +02:00
parent 3e53471f05
commit 8c5be51251
125 changed files with 10269 additions and 17808 deletions
@@ -96,4 +96,24 @@ describe("checkDuplicateAssignment", () => {
const result = checkDuplicateAssignment("r1", "p1", "2026-04-01", "2026-05-01", [proposed]);
expect(result.isDuplicate).toBe(true);
});
it("formats conflict dates from local-midnight Date objects without shifting the calendar day", () => {
const localDateAssignment: ExistingAssignment = {
...base,
startDate: new Date(2026, 3, 6),
endDate: new Date(2026, 3, 17),
};
const result = checkDuplicateAssignment(
"r1",
"p1",
new Date(2026, 3, 9),
new Date(2026, 3, 10),
[localDateAssignment],
);
expect(result.isDuplicate).toBe(true);
expect(result.message).toContain("2026-04-06");
expect(result.message).toContain("2026-04-17");
});
});
@@ -20,10 +20,28 @@ export interface DuplicateCheckResult {
const ACTIVE_STATUSES = new Set(["CONFIRMED", "ACTIVE", "PROPOSED"]);
function toTime(d: Date | string): number {
const dt = typeof d === "string" ? new Date(d) : d;
dt.setHours(0, 0, 0, 0);
return dt.getTime();
function toCalendarParts(value: Date | string): [year: number, month: number, day: number] {
if (typeof value === "string") {
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})/u);
if (match) {
return [Number(match[1]), Number(match[2]), Number(match[3])];
}
const date = new Date(value);
return [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()];
}
return [value.getFullYear(), value.getMonth() + 1, value.getDate()];
}
function toCalendarTime(value: Date | string): number {
const [year, month, day] = toCalendarParts(value);
return Date.UTC(year, month - 1, day);
}
function formatCalendarDate(value: Date | string): string {
const [year, month, day] = toCalendarParts(value);
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
/**
@@ -45,8 +63,8 @@ export function checkDuplicateAssignment(
existingAssignments: ExistingAssignment[],
excludeAssignmentId?: string,
): DuplicateCheckResult {
const newStart = toTime(startDate);
const newEnd = toTime(endDate);
const newStart = toCalendarTime(startDate);
const newEnd = toCalendarTime(endDate);
for (const existing of existingAssignments) {
// Skip self (for updates)
@@ -60,12 +78,12 @@ export function checkDuplicateAssignment(
if (!ACTIVE_STATUSES.has(existing.status)) continue;
// Check date overlap: existingStart <= newEnd && existingEnd >= newStart
const existStart = toTime(existing.startDate);
const existEnd = toTime(existing.endDate);
const existStart = toCalendarTime(existing.startDate);
const existEnd = toCalendarTime(existing.endDate);
if (existStart <= newEnd && existEnd >= newStart) {
const startStr = new Date(existing.startDate).toISOString().slice(0, 10);
const endStr = new Date(existing.endDate).toISOString().slice(0, 10);
const startStr = formatCalendarDate(existing.startDate);
const endStr = formatCalendarDate(existing.endDate);
return {
isDuplicate: true,
conflictingAssignment: existing,