import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { validateNightWindow, validateRoundingDirections, validateTimeEntryCodeReferences, type WorkRulePremiumRatePercentInput, } from "../lib/_workRuleValidation"; export interface CreateWorkRuleInput { displayName: string; /** Optional HolidayCalendar this rule uses for holiday classification; null = no calendar. */ holidayCalendarId?: string | null; clockInRoundingUnitMinutes: number; clockInRoundingDirection: string; clockOutRoundingUnitMinutes: number; clockOutRoundingDirection: string; breakRoundingUnitMinutes: number; breakRoundingDirection: string; breakDeductionMinutes: number; dailyOvertimeThresholdMinutes: number; nightWindowStart: number; nightWindowEnd: number; premiumRatePercent: WorkRulePremiumRatePercentInput[]; effectiveStart: Date; } /** * Function: createWorkRule * Description: Defines the initial effective-dated generation of a WorkRule — * the complete time-calculation rule: rounding, break deduction, daily * overtime threshold, night window, and premium-category TimeEntryCode * category/key pins. */ export async function run(db: Transaction, input: CreateWorkRuleInput, _ctx: CommandContext) { const roundingDirectionError = validateRoundingDirections(input); if (roundingDirectionError) { return err(roundingDirectionError); } const nightWindowError = validateNightWindow(input); if (nightWindowError) { return err(nightWindowError); } const timeEntryCodeReferenceError = await validateTimeEntryCodeReferences( db, input.premiumRatePercent, ); if (timeEntryCodeReferenceError) { return err(timeEntryCodeReferenceError); } const id = crypto.randomUUID(); const workRule = await db .insertInto("WorkRule") .values({ id, displayName: input.displayName, holidayCalendarId: input.holidayCalendarId ?? null, clockInRoundingUnitMinutes: input.clockInRoundingUnitMinutes, clockInRoundingDirection: input.clockInRoundingDirection as "UP" | "DOWN" | "NEAREST", clockOutRoundingUnitMinutes: input.clockOutRoundingUnitMinutes, clockOutRoundingDirection: input.clockOutRoundingDirection as "UP" | "DOWN" | "NEAREST", breakRoundingUnitMinutes: input.breakRoundingUnitMinutes, breakRoundingDirection: input.breakRoundingDirection as "UP" | "DOWN" | "NEAREST", breakDeductionMinutes: input.breakDeductionMinutes, dailyOvertimeThresholdMinutes: input.dailyOvertimeThresholdMinutes, nightWindowStart: input.nightWindowStart, nightWindowEnd: input.nightWindowEnd, premiumRatePercent: input.premiumRatePercent, effectiveStart: input.effectiveStart, effectiveEnd: null, versionOf: id, }) .returningAll() .executeTakeFirstOrThrow(); return ok({ workRule }); }