This commit is contained in:
Marshal
2026-07-15 14:27:00 +00:00
parent 19c9da28ae
commit d7d9db9c3a
10 changed files with 554 additions and 25 deletions

View File

@@ -0,0 +1,60 @@
/**
* Client mirror of the backend's contiguous-range rules for priority configs
* (see PriorityConfigsService.assertNoRangeCollision): ranges per type — per
* currency for CURRENCY — run 1..cap with no gaps and no overlaps, so the next
* range always starts at the lowest uncovered wagon count. The backend
* re-validates on submit AND on approval; this only drives the form prefill.
*/
export type PriorityRuleType = "WAGON" | "CURRENCY" | "CUSTOMS";
/** Hard ceiling of each type's chain — keep in sync with the API's RANGE_CAPS. */
export const PRIORITY_RANGE_CAPS: Record<PriorityRuleType, number> = {
WAGON: 50,
CURRENCY: 35,
CUSTOMS: 15,
};
export interface PriorityRangeRule {
id?: unknown;
type?: unknown;
currency?: unknown;
minWagonCount?: unknown;
maxWagonCount?: unknown;
}
/**
* Where the next range for `type` (+`currency`) must start, excluding
* `excludeId` (the rule being edited). Null when the chain already covers
* 1..cap — no further rule fits.
*/
export function nextPriorityRangeStart(
rules: PriorityRangeRule[],
type: string,
currency: string | null | undefined,
excludeId?: string,
): number | null {
const cap = PRIORITY_RANGE_CAPS[type as PriorityRuleType];
if (!cap) return null;
const scoped = rules
.filter(
(r) =>
String(r.type ?? "") === type &&
(excludeId === undefined || String(r.id ?? "") !== excludeId) &&
(type !== "CURRENCY" ||
String(r.currency ?? "") === String(currency ?? "")),
)
.map((r) => ({
min: Number(r.minWagonCount ?? 0),
max: Number(r.maxWagonCount ?? 0),
}))
.sort((a, b) => a.min - b.min);
let next = 1;
for (const r of scoped) {
if (r.min > next) break; // gap before this rule — fill it first
next = Math.max(next, r.max + 1);
}
return next > cap ? null : next;
}