fix(import): validate/normalize imported data, guard Excel I/O (#3)
- loadFromJSON and the localStorage startup load now run employees through normalizeEmployees(): fills missing constraints, drops unusable rows, de-duplicates ids, and discards invalid vacation ranges — preventing later crashes (e.g. c.neverDays on undefined) and same-day double-books. - Excel import is wrapped in try/catch with a typeof XLSX guard and user feedback; the Excel export button gets the same guard. - createEmployee reuses the shared defaultConstraints() factory. Adds normalizeEmployees tests (defaults, dropping, dedup, vacations) and a loadFromJSON normalization test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
e851bb2833
commit
065876ced4
5 files changed
+118
-18
No files matched your search
+48
-9
@@ -19,6 +19,52 @@ function parseSettings(raw) {
|
||||
return { moderatorsPerDay: valid ? n : 1 };
|
||||
}
|
||||
|
||||
function defaultConstraints() {
|
||||
return {
|
||||
neverDays: [],
|
||||
lowPriorityDays: [],
|
||||
onlyDays: [],
|
||||
maxPerMonth: null,
|
||||
minGapDays: 14,
|
||||
vacations: []
|
||||
};
|
||||
}
|
||||
|
||||
// Coerce an arbitrary constraints object into a valid, fully-populated one.
|
||||
function normalizeConstraints(c) {
|
||||
const raw = (c && typeof c === 'object') ? c : {};
|
||||
const arr = v => (Array.isArray(v) ? v : []);
|
||||
return {
|
||||
neverDays: arr(raw.neverDays),
|
||||
lowPriorityDays: arr(raw.lowPriorityDays),
|
||||
onlyDays: arr(raw.onlyDays),
|
||||
maxPerMonth: Number.isFinite(raw.maxPerMonth) ? raw.maxPerMonth : null,
|
||||
minGapDays: Number.isFinite(raw.minGapDays) ? raw.minGapDays : 14,
|
||||
vacations: arr(raw.vacations).filter(v =>
|
||||
v && typeof v.from === 'string' && typeof v.to === 'string' && v.from <= v.to
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
// Validate/repair an imported employee list: drop unusable rows, fill missing
|
||||
// constraints, and deduplicate ids so downstream code never crashes or
|
||||
// double-books. Returns a fresh, safe array.
|
||||
export function normalizeEmployees(rawList) {
|
||||
if (!Array.isArray(rawList)) return [];
|
||||
const seen = new Set();
|
||||
const out = [];
|
||||
for (const e of rawList) {
|
||||
if (!e || typeof e !== 'object') continue;
|
||||
const name = String(e.name ?? '').trim();
|
||||
if (!name) continue; // unusable without a name
|
||||
let id = (typeof e.id === 'string' && e.id) ? e.id : generateId();
|
||||
if (seen.has(id)) id = generateId(); // resolve duplicate ids
|
||||
seen.add(id);
|
||||
out.push({ id, name, constraints: normalizeConstraints(e.constraints) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function loadFromJSON(json) {
|
||||
let parsed;
|
||||
try {
|
||||
@@ -30,7 +76,7 @@ export function loadFromJSON(json) {
|
||||
throw new Error('Ungültiges Dateiformat: Objekt erwartet');
|
||||
}
|
||||
return {
|
||||
employees: Array.isArray(parsed.employees) ? parsed.employees : [],
|
||||
employees: normalizeEmployees(parsed.employees),
|
||||
calendar: (parsed.calendar && typeof parsed.calendar === 'object')
|
||||
? {
|
||||
holidays: Array.isArray(parsed.calendar.holidays) ? parsed.calendar.holidays : [],
|
||||
@@ -57,14 +103,7 @@ export function createEmployee(name) {
|
||||
return {
|
||||
id: generateId(),
|
||||
name: trimmed,
|
||||
constraints: {
|
||||
neverDays: [],
|
||||
lowPriorityDays: [],
|
||||
onlyDays: [],
|
||||
maxPerMonth: null,
|
||||
minGapDays: 14,
|
||||
vacations: []
|
||||
}
|
||||
constraints: defaultConstraints()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user