shinkan-jinkendo/frontend/src/utils/comboPlanningMethodProfile.js

135 lines
4.2 KiB
JavaScript
Raw Normal View History

/** Effektives Ablaufprofil für Kombination im Coach/in der Planung */
import { inferAdvanceModeFromStoredSlotRow } from './combinationMethodProfileUi'
/** 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). */
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
}
function mergeSlotProfileFields(prev, patch) {
const base = prev && typeof prev === 'object' ? { ...prev } : {}
if (!patch || typeof patch !== 'object') return coerceMergedSlotProfileRow(base)
for (const [k, v] of Object.entries(patch)) {
if (v === null || v === undefined) continue
base[k] = v
}
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
}
const ix = Number(base.slot_index)
if (Number.isFinite(ix)) base.slot_index = ix
return coerceMergedSlotProfileRow(base)
}
/**
* 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) || {}
byIx.set(ix, mergeSlotProfileFields(prev, r))
}
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).
*/
export function effectiveComboMethodProfile(catalogDict, planningSnapshot) {
const cat =
catalogDict && typeof catalogDict === 'object' && !Array.isArray(catalogDict) ? { ...catalogDict } : {}
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)) {
return { ...cat }
}
const planRaw = planningSnapshot
const merged = { ...cat, ...omitNullUndefinedTop(planRaw) }
if (Object.prototype.hasOwnProperty.call(planRaw, 'slot_profiles_v1')) {
merged.slot_profiles_v1 = mergeSlotProfilesV1(cat.slot_profiles_v1, planRaw.slot_profiles_v1)
}
return merged
}
export function comboPlanningProfileJsonForEditor(catalogDict, planningSnapshot) {
const o = effectiveComboMethodProfile(catalogDict, planningSnapshot)
try {
return JSON.stringify(Object.keys(o).length ? o : {})
} catch {
return '{}'
}
}