refactor(api): extract timeline cost load support
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { Prisma, VacationType, type PrismaClient } from "@capakraken/db";
|
||||
import { DEFAULT_CALCULATION_RULES } from "@capakraken/engine";
|
||||
import type { AbsenceDay, CalculationRule } from "@capakraken/shared";
|
||||
import { logger } from "../lib/logger.js";
|
||||
|
||||
function isMissingOptionalTableError(error: unknown, tableHints: string[]): boolean {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
if (error.code !== "P2021") {
|
||||
return false;
|
||||
}
|
||||
const table = typeof error.meta?.table === "string" ? error.meta.table.toLowerCase() : "";
|
||||
const message = error.message.toLowerCase();
|
||||
return tableHints.some((hint) => table.includes(hint) || message.includes(hint));
|
||||
}
|
||||
|
||||
if (typeof error !== "object" || error === null || !("code" in error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = error as {
|
||||
code?: unknown;
|
||||
message?: unknown;
|
||||
meta?: { table?: unknown };
|
||||
};
|
||||
const code = typeof candidate.code === "string" ? candidate.code : "";
|
||||
if (code !== "P2021") {
|
||||
return false;
|
||||
}
|
||||
const table = typeof candidate.meta?.table === "string" ? candidate.meta.table.toLowerCase() : "";
|
||||
const message = typeof candidate.message === "string" ? candidate.message.toLowerCase() : "";
|
||||
return tableHints.some((hint) => table.includes(hint) || message.includes(hint));
|
||||
}
|
||||
|
||||
export async function loadTimelineCalculationRules(
|
||||
db: PrismaClient,
|
||||
): Promise<CalculationRule[]> {
|
||||
const calculationRuleModel = (db as PrismaClient & {
|
||||
calculationRule?: { findMany?: (args: unknown) => Promise<unknown[]> };
|
||||
}).calculationRule;
|
||||
|
||||
if (!calculationRuleModel || typeof calculationRuleModel.findMany !== "function") {
|
||||
return DEFAULT_CALCULATION_RULES;
|
||||
}
|
||||
|
||||
try {
|
||||
const rules = await calculationRuleModel.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: [{ priority: "desc" }],
|
||||
});
|
||||
if (rules.length > 0) {
|
||||
return rules as unknown as CalculationRule[];
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isMissingOptionalTableError(error, ["calculationrule", "calculation_rule", "calculation_rules"])) {
|
||||
logger.error({ err: error }, "Failed to load active calculation rules for timeline");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return DEFAULT_CALCULATION_RULES;
|
||||
}
|
||||
|
||||
export async function buildTimelineAbsenceDays(
|
||||
db: PrismaClient,
|
||||
resourceId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
): Promise<{ absenceDays: AbsenceDay[]; legacyVacationDates: Date[] }> {
|
||||
const absenceDays: AbsenceDay[] = [];
|
||||
const legacyVacationDates: Date[] = [];
|
||||
|
||||
try {
|
||||
const vacations = await db.vacation.findMany({
|
||||
where: {
|
||||
resourceId,
|
||||
status: "APPROVED",
|
||||
startDate: { lte: endDate },
|
||||
endDate: { gte: startDate },
|
||||
},
|
||||
select: { startDate: true, endDate: true, type: true, isHalfDay: true },
|
||||
});
|
||||
|
||||
for (const vacation of vacations) {
|
||||
const cur = new Date(vacation.startDate);
|
||||
cur.setUTCHours(0, 0, 0, 0);
|
||||
const vacationEnd = new Date(vacation.endDate);
|
||||
vacationEnd.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const triggerType = vacation.type === VacationType.SICK
|
||||
? "SICK" as const
|
||||
: vacation.type === VacationType.PUBLIC_HOLIDAY
|
||||
? "PUBLIC_HOLIDAY" as const
|
||||
: "VACATION" as const;
|
||||
|
||||
while (cur <= vacationEnd) {
|
||||
absenceDays.push({
|
||||
date: new Date(cur),
|
||||
type: triggerType,
|
||||
...(vacation.isHalfDay ? { isHalfDay: true } : {}),
|
||||
});
|
||||
if (triggerType === "VACATION") {
|
||||
legacyVacationDates.push(new Date(cur));
|
||||
}
|
||||
cur.setUTCDate(cur.getUTCDate() + 1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isMissingOptionalTableError(error, ["vacation", "vacations"])) {
|
||||
logger.error(
|
||||
{ err: error, resourceId, startDate, endDate },
|
||||
"Failed to load timeline absence days",
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return { absenceDays, legacyVacationDates };
|
||||
}
|
||||
Reference in New Issue
Block a user