feat(plan): resolveGrid for batch cell resolution

This commit is contained in:
BOHA
2026-06-05 12:10:30 +02:00
parent d410e7433e
commit f79967d433
2 changed files with 151 additions and 1 deletions

View File

@@ -96,3 +96,102 @@ export async function resolveCell(
rangeTo: entry.date_to.toISOString().slice(0, 10),
};
}
/**
* Compute the effective plan cell for every (userId, date) in the
* given range. Returns a 2D map: cells[userId][dateStr] = ResolvedCell | null.
*
* Implementation: load all entries and overrides for the range in two
* queries, then iterate the dates and resolve each cell in O(1) per
* (user, date). This avoids N+1 calls to resolveCell.
*/
export async function resolveGrid(
userIds: number[],
dateFromStr: string,
dateToStr: string,
): Promise<Record<number, Record<string, ResolvedCell | null>>> {
const dateFrom = new Date(dateFromStr);
const dateTo = new Date(dateToStr);
const [entries, overrides] = await Promise.all([
prisma.work_plan_entries.findMany({
where: {
user_id: { in: userIds },
is_deleted: false,
date_from: { lte: dateTo },
date_to: { gte: dateFrom },
},
orderBy: { created_at: "desc" },
}),
prisma.work_plan_overrides.findMany({
where: {
user_id: { in: userIds },
is_deleted: false,
shift_date: { gte: dateFrom, lte: dateTo },
},
}),
]);
// Build a date list (inclusive)
const dates: string[] = [];
for (
let d = new Date(dateFrom);
d <= dateTo;
d.setUTCDate(d.getUTCDate() + 1)
) {
dates.push(d.toISOString().slice(0, 10));
}
const result: Record<number, Record<string, ResolvedCell | null>> = {};
for (const uid of userIds) {
result[uid] = {};
for (const dateStr of dates) {
// Override first
const override = overrides.find(
(o) =>
o.user_id === uid &&
o.shift_date.toISOString().slice(0, 10) === dateStr,
);
if (override) {
result[uid][dateStr] = {
source: "override",
entryId: null,
overrideId: override.id,
user_id: override.user_id,
shift_date: dateStr,
project_id: override.project_id,
category: override.category,
note: override.note,
rangeFrom: null,
rangeTo: null,
};
continue;
}
// First (latest by created_at) matching entry
const entry = entries.find(
(e) =>
e.user_id === uid &&
e.date_from <= new Date(dateStr) &&
e.date_to >= new Date(dateStr),
);
if (entry) {
result[uid][dateStr] = {
source: "entry",
entryId: entry.id,
overrideId: null,
user_id: entry.user_id,
shift_date: dateStr,
project_id: entry.project_id,
category: entry.category,
note: entry.note,
rangeFrom: entry.date_from.toISOString().slice(0, 10),
rangeTo: entry.date_to.toISOString().slice(0, 10),
};
} else {
result[uid][dateStr] = null;
}
}
}
return result;
}