import { type CommonReservedFields, type NoReservedFields, defaultGqlPermission, defaultPermission, } from "@tailor-platform/erp-kit/core"; import { db, type TailorAnyDBField } from "@tailor-platform/sdk"; const builtins = { // References a LeaveType by stable key (not a `.relation()`) — the leave type governed // by this plan; the referenced LeaveType must have requiresBalance = true leaveTypeKey: db .string() .description("Stable key of the balance-backed LeaveType this plan governs"), // Accrual-method discriminator (ADR-025): FRONT_LOAD_TENURE = front-load the entitlement at the // eligibility date and escalate by tenure tier on each anniversary. It is a discriminator seam — // additional methods (e.g. periodic/pro-rata accrual) can be added without a schema migration, // and the anniversary grant batch dispatches on it. accrualMethod: db .enum(["FRONT_LOAD_TENURE"]) .description("Accrual method this plan uses; the grant batch dispatches on it"), // Provenance stamped on grants this plan produces (STATUTORY for statutory entitlements, MANUAL // for company-policy accrual) — replaces a hard-coded STATUTORY in the grant batch. grantType: db .enum(["STATUTORY", "MANUAL"]) .description("Grant provenance produced by this plan's accrual"), // Grant rule fields (ADR-025) eligibilityDelayMonths: db .int() .description( "Non-negative months of service before the first grant applies (0 = day one / current front-load policy, 6 = statutory Labor Standards Act Article 39, 12 = CN/CA); the first grant's date = hire date + eligibilityDelayMonths", ), baseGrantDays: db .decimal() .description( "Non-negative front-loaded amount (half-day increments valid) granted at the eligibility date; also the granted amount below the first tenure tier", ), tenureTiers: db .object( { yearsOfService: db.int().description("Tenure in years this tier applies at"), grantDays: db .int() .description( "Total STATUTORY entitlement at this tenure (e.g. 11 at 1y, 20 at 6y+), not a bonus; must not exceed annualCapDays", ), }, { array: true }, ) .description( "Ordered tenure-to-total-entitlement mapping; the applied tier is the highest with yearsOfService <= tenure, and its grantDays is the full annual grant at that tenure", ), // Statutory grant-condition gate (ADR-026 decision C); EVALUATION happens in the // anniversary grant batch — this generation only defines the field grantCondition: db .object( { type: db .enum(["NONE", "MIN_WORKED_DAYS"]) .description( "Generic gate kind: NONE = unconditional grant, MIN_WORKED_DAYS = worked-days gate", ), minWorkedDays: db .int({ optional: true }) .description( "Required when type = MIN_WORKED_DAYS; positive worked-days threshold, e.g. 240", ), referenceMonths: db .int({ optional: true }) .description( "Positive look-back period in months the gate is evaluated over (default 12)", ), }, { optional: true }, ) .description( "Generic worked-days grant gate; jurisdiction-specific conditions belong in AccrualPlan custom fields and the app eligibility evaluator; null/absent = NONE", ), annualCapDays: db .decimal({ optional: true }) .description( "Upper bound on the annual entitlement (20 for statutory annual leave); null = uncapped. Replaces the hard-coded 20-day cap", ), expirationMonths: db .int() .description( "Positive integer (24 for statutory annual leave) determining a STATUTORY grant's expirationDate = grant date + expirationMonths", ), appliesToEmploymentTypeId: db .uuid({ optional: true }) .description( "Workforce EmploymentType catalog id this plan applies to (cross-module, referenced by id and read at grant time — not a stored FK); null = applies to all employment types", ), // Effective-dating per ADR-013 effectiveStart: db.date().description("Date this plan generation becomes effective"), effectiveEnd: db .date({ optional: true }) .description("Date this plan generation stops being effective; null means current generation"), versionOf: db .uuid() .description( "Stable key grouping all generations of the same logical accrual plan across time", ), }; type ReservedFields = keyof typeof builtins | CommonReservedFields; /** * Every column this model owns: the `builtins` keys above plus the implicit `id` and the * timestamps. Derived from `builtins` rather than hand-listed so the create/update commands' * "which input/row keys are custom fields" split cannot drift when a built-in field is added, * renamed, or removed. */ export const ACCRUAL_PLAN_BUILTIN_COLUMNS: ReadonlySet = new Set([ "id", ...Object.keys(builtins), ...Object.keys(db.fields.timestamps()), ]); export interface CreateAccrualPlanTypeParams> { fields?: NoReservedFields; } export function createAccrualPlanType>( params: CreateAccrualPlanTypeParams, ) { return db .table("AccrualPlan", { ...builtins, ...((params.fields ?? {}) as F), ...db.fields.timestamps(), }) .permission(defaultPermission) .gqlPermission(defaultGqlPermission); } export const accrualPlan = createAccrualPlanType({});