2026-05-13 07:21:33 +02:00
|
|
|
|
/** Effektives Ablaufprofil für Kombination im Coach/in der Planung */
|
|
|
|
|
|
|
2026-05-13 15:49:14 +02:00
|
|
|
|
import { inferAdvanceModeFromStoredSlotRow } from './combinationMethodProfileUi'
|
|
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
/** Top-Level: null/undefined aus Planungs-Snapshot löschen keine Katalog-Felder (API liefert oft JSON-null). */
|
|
|
|
|
|
function omitNullUndefinedTop(planObj) {
|
|
|
|
|
|
const out = {}
|
|
|
|
|
|
for (const [k, v] of Object.entries(planObj)) {
|
|
|
|
|
|
if (v === null || v === undefined) continue
|
|
|
|
|
|
out[k] = v
|
|
|
|
|
|
}
|
|
|
|
|
|
return out
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Je Slot-Zeile: null/undefined aus Planung überschreiben keine Katalog-Werte (z. B. consecutive_reps). */
|
2026-05-13 15:49:14 +02:00
|
|
|
|
function patchHasExplicitAdvanceMode(patch) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
Object.prototype.hasOwnProperty.call(patch, 'advance_mode') &&
|
|
|
|
|
|
patch.advance_mode !== null &&
|
|
|
|
|
|
patch.advance_mode !== undefined &&
|
|
|
|
|
|
String(patch.advance_mode).trim() !== ''
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Nach Merge: Rep/Manual ohne Sekunden-Arbeit; Zeit ohne advance_mode-Schlüssel. */
|
|
|
|
|
|
function coerceMergedSlotProfileRow(row) {
|
|
|
|
|
|
if (!row || typeof row !== 'object') return row
|
|
|
|
|
|
const inferred = inferAdvanceModeFromStoredSlotRow(row)
|
|
|
|
|
|
if (inferred === 'rep' || inferred === 'manual') {
|
|
|
|
|
|
delete row.load_sec
|
|
|
|
|
|
row.advance_mode = inferred
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delete row.advance_mode
|
|
|
|
|
|
}
|
|
|
|
|
|
return row
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
function mergeSlotProfileFields(prev, patch) {
|
|
|
|
|
|
const base = prev && typeof prev === 'object' ? { ...prev } : {}
|
2026-05-13 15:49:14 +02:00
|
|
|
|
if (!patch || typeof patch !== 'object') return coerceMergedSlotProfileRow(base)
|
|
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
for (const [k, v] of Object.entries(patch)) {
|
|
|
|
|
|
if (v === null || v === undefined) continue
|
|
|
|
|
|
base[k] = v
|
|
|
|
|
|
}
|
2026-05-13 15:49:14 +02:00
|
|
|
|
|
|
|
|
|
|
const loadExplicit =
|
|
|
|
|
|
Object.prototype.hasOwnProperty.call(patch, 'load_sec') &&
|
|
|
|
|
|
patch.load_sec !== null &&
|
|
|
|
|
|
patch.load_sec !== undefined &&
|
|
|
|
|
|
String(patch.load_sec).trim() !== ''
|
|
|
|
|
|
|
|
|
|
|
|
// Nur Arbeitsssekunden aus Planung → Zeitmodus soll Katalog‑advance_mode rep nicht „festhalten“
|
|
|
|
|
|
if (loadExplicit && !patchHasExplicitAdvanceMode(patch)) {
|
|
|
|
|
|
delete base.advance_mode
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
const ix = Number(base.slot_index)
|
|
|
|
|
|
if (Number.isFinite(ix)) base.slot_index = ix
|
2026-05-13 15:49:14 +02:00
|
|
|
|
|
|
|
|
|
|
return coerceMergedSlotProfileRow(base)
|
2026-05-13 15:28:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 14:42:01 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Vereinigt slot_profiles_v1 aus Katalog und Planungs-Overlay (je slot_index).
|
|
|
|
|
|
* @param {unknown} catArr
|
|
|
|
|
|
* @param {unknown} planArr
|
|
|
|
|
|
*/
|
|
|
|
|
|
function mergeSlotProfilesV1(catArr, planArr) {
|
|
|
|
|
|
const c = Array.isArray(catArr) ? catArr : []
|
|
|
|
|
|
const p = Array.isArray(planArr) ? planArr : []
|
|
|
|
|
|
const byIx = new Map()
|
|
|
|
|
|
for (const r of c) {
|
|
|
|
|
|
if (!r || typeof r !== 'object') continue
|
|
|
|
|
|
const ix = Number(r.slot_index)
|
|
|
|
|
|
if (!Number.isFinite(ix)) continue
|
|
|
|
|
|
byIx.set(ix, { ...r })
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const r of p) {
|
|
|
|
|
|
if (!r || typeof r !== 'object') continue
|
|
|
|
|
|
const ix = Number(r.slot_index)
|
|
|
|
|
|
if (!Number.isFinite(ix)) continue
|
|
|
|
|
|
const prev = byIx.get(ix) || {}
|
2026-05-13 15:28:37 +02:00
|
|
|
|
byIx.set(ix, mergeSlotProfileFields(prev, r))
|
2026-05-13 14:42:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
return [...byIx.entries()].sort((a, b) => a[0] - b[0]).map(([, row]) => row)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Katalog-Basis + optionaler Planungs-Snapshot.
|
|
|
|
|
|
* Wichtig: `planning_method_profile` darf nur **Überschreibungen** sein — nie den Katalog komplett ersetzen
|
|
|
|
|
|
* (sonst verschwinden Zeiten/Runden bei leerem Objekt oder Teil-Payload).
|
|
|
|
|
|
*/
|
2026-05-13 07:21:33 +02:00
|
|
|
|
export function effectiveComboMethodProfile(catalogDict, planningSnapshot) {
|
|
|
|
|
|
const cat =
|
2026-05-13 14:42:01 +02:00
|
|
|
|
catalogDict && typeof catalogDict === 'object' && !Array.isArray(catalogDict) ? { ...catalogDict } : {}
|
|
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
if (planningSnapshot === null || planningSnapshot === undefined) {
|
|
|
|
|
|
return { ...cat }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof planningSnapshot === 'string') {
|
|
|
|
|
|
const t = planningSnapshot.trim()
|
|
|
|
|
|
if (!t || t === 'null') return { ...cat }
|
|
|
|
|
|
try {
|
|
|
|
|
|
const p = JSON.parse(t)
|
|
|
|
|
|
return effectiveComboMethodProfile(catalogDict, p)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return { ...cat }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof planningSnapshot !== 'object' || Array.isArray(planningSnapshot)) {
|
2026-05-13 14:42:01 +02:00
|
|
|
|
return { ...cat }
|
2026-05-13 07:21:33 +02:00
|
|
|
|
}
|
2026-05-13 14:42:01 +02:00
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
const planRaw = planningSnapshot
|
|
|
|
|
|
const merged = { ...cat, ...omitNullUndefinedTop(planRaw) }
|
2026-05-13 14:42:01 +02:00
|
|
|
|
|
2026-05-13 15:28:37 +02:00
|
|
|
|
if (Object.prototype.hasOwnProperty.call(planRaw, 'slot_profiles_v1')) {
|
|
|
|
|
|
merged.slot_profiles_v1 = mergeSlotProfilesV1(cat.slot_profiles_v1, planRaw.slot_profiles_v1)
|
2026-05-13 14:42:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return merged
|
2026-05-13 07:21:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function comboPlanningProfileJsonForEditor(catalogDict, planningSnapshot) {
|
|
|
|
|
|
const o = effectiveComboMethodProfile(catalogDict, planningSnapshot)
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.stringify(Object.keys(o).length ? o : {})
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return '{}'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|