import { asRecord, bool, fieldPath, has, intIn, makeParser, optionalIntIn, report, stringIn, type Scope, } from './parse-helpers'; import type { PlanStepConfig, SplitScheduleConfig } from './rule-config.types'; type SplitVariant = 'RAISE_OWN' | 'QUEEN_CELL' | 'QUEEN'; const MAX_STEPS_PER_VARIANT = 10; const MAX_OFFSET_DAYS = 120; const MAX_WINDOW_DAYS = 30; const STEP_NAME = /^[A-Z][A-Z_]{1,39}$/; export const DEFAULT_SPLIT_SCHEDULE_CONFIG: SplitScheduleConfig = { RAISE_OWN: [ { step: 'RAISE_CELLS_CHECK', offsetDays: 9 }, { step: 'MATING_CHECK', offsetDays: 21, windowDays: 3 }, { step: 'EGGS_CHECK', offsetDays: 28 }, ], QUEEN_CELL: [{ step: 'EGGS_CHECK', offsetDays: 21 }], QUEEN: [{ step: 'ACCEPTANCE_CHECK', offsetDays: 7 }], notify: false, }; function clonedSteps(variant: SplitVariant): PlanStepConfig[] { return DEFAULT_SPLIT_SCHEDULE_CONFIG[variant].map((step) => ({ ...step })); } /** Scope of one list entry: its path is `VARIANT[index]`. */ function stepScope( scope: Scope, variant: SplitVariant, index: number, raw: unknown, ): Scope | null { const record = asRecord(raw); const path = `${fieldPath(scope, variant)}[${index}]`; if (record === null) { scope.issues.push({ field: path, message: 'Bitte jeden Schritt als Gruppe senden.', }); return null; } return { raw: record, path, issues: scope.issues }; } function readSteps(scope: Scope, variant: SplitVariant): PlanStepConfig[] { if (!has(scope, variant)) { return clonedSteps(variant); } const list = scope.raw[variant]; if (!Array.isArray(list) || list.length > MAX_STEPS_PER_VARIANT) { report( scope, variant, `Bitte eine Liste mit höchstens ${MAX_STEPS_PER_VARIANT} Schritten senden.`, ); return clonedSteps(variant); } const before = scope.issues.length; const steps: PlanStepConfig[] = []; list.forEach((entry: unknown, index) => { const item = stepScope(scope, variant, index, entry); if (item === null) { return; } if (!has(item, 'step')) { scope.issues.push({ field: `${item.path}.step`, message: 'Bitte den Schritt angeben.', }); return; } const step: PlanStepConfig = { step: stringIn(item, 'step', '', 2, 40, STEP_NAME), offsetDays: intIn(item, 'offsetDays', 0, 0, MAX_OFFSET_DAYS), }; const windowDays = optionalIntIn(item, 'windowDays', 0, MAX_WINDOW_DAYS); if (windowDays !== undefined) { step.windowDays = windowDays; } steps.push(step); }); // One bad entry makes the whole list unreliable: back to the default plan. return scope.issues.length > before ? clonedSteps(variant) : steps; } export const splitScheduleParser = makeParser( 'SPLIT_SCHEDULE', DEFAULT_SPLIT_SCHEDULE_CONFIG, (scope) => ({ RAISE_OWN: readSteps(scope, 'RAISE_OWN'), QUEEN_CELL: readSteps(scope, 'QUEEN_CELL'), QUEEN: readSteps(scope, 'QUEEN'), notify: bool(scope, 'notify', DEFAULT_SPLIT_SCHEDULE_CONFIG.notify), }), );