feat(version): bump to 0.8.103 and enhance planning method profile integration
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 59s
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 59s
- Updated app version to 0.8.103, reflecting recent enhancements in training planning. - Incremented database schema version to 20260512057, ensuring compatibility with new features. - Introduced optional `planning_method_profile` for combination exercises, allowing for detailed planning and coaching support. - Enhanced frontend components to manage and display planning method profiles effectively in the Training Unit Sections Editor and ExerciseFullContent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
12fd3926b2
commit
4e654e50c0
|
|
@ -0,0 +1,8 @@
|
|||
-- 057: Terminspezifisches Ablaufprofil fuer Kombinationsuebungen in der Planung
|
||||
-- NULL = method_profile vom Katalog (exercises) verwenden; sonst dieser JSONB-Stand gilt fuer diese Platzierung.
|
||||
|
||||
ALTER TABLE training_unit_section_items
|
||||
ADD COLUMN IF NOT EXISTS planning_method_profile JSONB NULL;
|
||||
|
||||
COMMENT ON COLUMN training_unit_section_items.planning_method_profile IS
|
||||
'Snapshots des Ablaufprofils fuer diese Einheit/Zeile; NULL = exercises.method_profile.';
|
||||
|
|
@ -407,6 +407,18 @@ def _normalize_assistant_trainer_profile_ids(
|
|||
)
|
||||
return uniq
|
||||
|
||||
def _normalize_planning_method_profile_payload(raw) -> Optional[Dict[str, Any]]:
|
||||
"""None = Katalog wirksam; Dict = Snapshot fuer diese Platzierung."""
|
||||
if raw is None:
|
||||
return None
|
||||
if isinstance(raw, dict):
|
||||
return dict(raw)
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="planning_method_profile muss ein JSON-Objekt oder null sein",
|
||||
)
|
||||
|
||||
|
||||
# Nachverfolgbarkeit: Übernahmen aus Rahmenprogramm über origin_framework_slot_id
|
||||
_ORIGIN_LINEAGE_JOIN = """
|
||||
LEFT JOIN training_framework_slots origin_slot ON origin_slot.id = tu.origin_framework_slot_id
|
||||
|
|
@ -452,6 +464,8 @@ def _fetch_sections(cur, unit_id: int) -> List[Dict[str, Any]]:
|
|||
e.title AS exercise_title,
|
||||
e.exercise_kind AS exercise_kind,
|
||||
e.summary AS exercise_summary,
|
||||
e.method_archetype AS catalog_method_archetype,
|
||||
e.method_profile AS catalog_method_profile,
|
||||
(
|
||||
SELECT fa.name FROM exercise_focus_areas efa
|
||||
JOIN focus_areas fa ON fa.id = efa.focus_area_id
|
||||
|
|
@ -471,6 +485,14 @@ def _fetch_sections(cur, unit_id: int) -> List[Dict[str, Any]]:
|
|||
(sec["id"],),
|
||||
)
|
||||
sec["items"] = [r2d(r) for r in cur.fetchall()]
|
||||
for it in sec["items"]:
|
||||
if it.get("item_type") != "exercise":
|
||||
continue
|
||||
cmp_raw = it.get("catalog_method_profile")
|
||||
if not isinstance(cmp_raw, dict):
|
||||
it["catalog_method_profile"] = {}
|
||||
else:
|
||||
it["catalog_method_profile"] = dict(cmp_raw)
|
||||
secs.append(sec)
|
||||
return secs
|
||||
|
||||
|
|
@ -506,6 +528,7 @@ def _sections_clone_payload(cur, unit_id: int) -> List[Dict[str, Any]]:
|
|||
"actual_duration_min": it.get("actual_duration_min"),
|
||||
"notes": it.get("notes"),
|
||||
"modifications": it.get("modifications"),
|
||||
"planning_method_profile": it.get("planning_method_profile"),
|
||||
}
|
||||
sm = _optional_source_training_module_id_payload(it.get("source_training_module_id"))
|
||||
if sm is not None:
|
||||
|
|
@ -676,9 +699,10 @@ def _append_copied_module_items_to_section(
|
|||
section_id, order_index, item_type,
|
||||
exercise_id, exercise_variant_id,
|
||||
planned_duration_min, actual_duration_min,
|
||||
notes, modifications, note_body, source_training_module_id
|
||||
notes, modifications, note_body,
|
||||
source_training_module_id, planning_method_profile
|
||||
) VALUES (%s, %s, 'exercise',
|
||||
%s, %s, %s, NULL, %s, NULL, NULL, %s)
|
||||
%s, %s, %s, NULL, %s, NULL, NULL, %s, NULL)
|
||||
""",
|
||||
(
|
||||
section_id,
|
||||
|
|
@ -728,6 +752,15 @@ def _insert_section_items(cur, section_id: int, items_in: Optional[List[Any]], s
|
|||
eid = int(eid)
|
||||
vid = _optional_positive_int(raw.get("exercise_variant_id"), "exercise_variant_id")
|
||||
_validate_variant_for_exercise(cur, eid, vid)
|
||||
cur.execute(
|
||||
"""SELECT COALESCE(exercise_kind, 'simple') AS k FROM exercises WHERE id = %s""",
|
||||
(eid,),
|
||||
)
|
||||
er = cur.fetchone()
|
||||
ek = str(er["k"] if er and er.get("k") is not None else "simple").strip().lower()
|
||||
planning_mp = _normalize_planning_method_profile_payload(raw.get("planning_method_profile"))
|
||||
if ek != "combination":
|
||||
planning_mp = None
|
||||
src_mod = _optional_source_training_module_id_payload(raw.get("source_training_module_id"))
|
||||
cur.execute(
|
||||
"""
|
||||
|
|
@ -735,10 +768,10 @@ def _insert_section_items(cur, section_id: int, items_in: Optional[List[Any]], s
|
|||
section_id, order_index, item_type,
|
||||
exercise_id, exercise_variant_id,
|
||||
planned_duration_min, actual_duration_min,
|
||||
notes, modifications, note_body, source_training_module_id
|
||||
notes, modifications, note_body,
|
||||
source_training_module_id, planning_method_profile
|
||||
) VALUES (%s, %s, 'exercise',
|
||||
%s, %s, %s, %s, %s, %s, NULL, %s
|
||||
)
|
||||
%s, %s, %s, %s, %s, %s, NULL, %s, %s)
|
||||
""",
|
||||
(
|
||||
section_id,
|
||||
|
|
@ -750,6 +783,7 @@ def _insert_section_items(cur, section_id: int, items_in: Optional[List[Any]], s
|
|||
raw.get("notes"),
|
||||
raw.get("modifications"),
|
||||
src_mod,
|
||||
planning_mp,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Shinkan Jinkendo Version Information
|
||||
|
||||
APP_VERSION = "0.8.102"
|
||||
APP_VERSION = "0.8.103"
|
||||
BUILD_DATE = "2026-05-12"
|
||||
DB_SCHEMA_VERSION = "20260512056"
|
||||
DB_SCHEMA_VERSION = "20260512057"
|
||||
|
||||
MODULE_VERSIONS = {
|
||||
"legal_documents": "1.4.0", # Admin: Live-Vorschau pro Abschnitt + modale Vollvorschau (Editor + Dokumentenliste)
|
||||
|
|
@ -24,7 +24,7 @@ MODULE_VERSIONS = {
|
|||
"exercises": "2.24.2", # Kombi: geführtes method_profile im Übungsformular nach Archetyp + Coach zeigt Profil als Key/Wert
|
||||
"training_units": "0.2.0",
|
||||
"training_programs": "0.1.0",
|
||||
"planning": "0.9.1", # Kombinationsübungen: Sektionen PATCH/validator + exercise_kind GET; Frontend KEINE Varianten bei combination
|
||||
"planning": "0.9.2", # Kombi: planning_method_profile auf Sektions-Items (Migration 057); Form-Payload + Coach-PUT
|
||||
"training_modules": "1.0.0",
|
||||
"import_wiki": "1.0.0",
|
||||
"admin": "1.0.0",
|
||||
|
|
@ -35,6 +35,13 @@ MODULE_VERSIONS = {
|
|||
}
|
||||
|
||||
CHANGELOG = [
|
||||
{
|
||||
"version": "0.8.103",
|
||||
"date": "2026-05-12",
|
||||
"changes": [
|
||||
"Trainingsplanung: bei Kombinationszeilen optionales `planning_method_profile` (Migration 057); Planungs-Editor mit Ablaufprofil-Details, „wie Katalog“ / „aus Katalog kopieren“; Payload/Coach-PUT übernehmen Snapshot.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"version": "0.8.102",
|
||||
"date": "2026-05-12",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import React from 'react'
|
|||
import { Link } from 'react-router-dom'
|
||||
import ExerciseRichTextBlock from './ExerciseRichTextBlock'
|
||||
import CombinationCoachSlots from './CombinationCoachSlots'
|
||||
import { effectiveComboMethodProfile } from '../utils/comboPlanningMethodProfile'
|
||||
|
||||
function TagRow({ exercise }) {
|
||||
const tags = []
|
||||
|
|
@ -53,9 +54,9 @@ function metaParts(exercise) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {{ exercise?: object|null, loading?: boolean, error?: string|null, exerciseId?: number, variantId?: number|string|null }} props
|
||||
* @param {{ exercise?: object|null, loading?: boolean, error?: string|null, exerciseId?: number, variantId?: number|string|null, planningComboMethodProfile?: object|null }} props
|
||||
*/
|
||||
export default function ExerciseFullContent({ exercise, loading, error, exerciseId, variantId }) {
|
||||
export default function ExerciseFullContent({ exercise, loading, error, exerciseId, variantId, planningComboMethodProfile }) {
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '1rem' }}>
|
||||
|
|
@ -80,6 +81,10 @@ export default function ExerciseFullContent({ exercise, loading, error, exercise
|
|||
const isCombination =
|
||||
String(exercise.exercise_kind || 'simple').toLowerCase().trim() === 'combination'
|
||||
|
||||
const coachComboProfile = isCombination
|
||||
? effectiveComboMethodProfile(exercise.method_profile, planningComboMethodProfile)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className="exercise-coach-catalog" style={{ fontSize: '0.93rem', lineHeight: 1.5 }}>
|
||||
{variant ? (
|
||||
|
|
@ -114,7 +119,7 @@ export default function ExerciseFullContent({ exercise, loading, error, exercise
|
|||
<CombinationCoachSlots
|
||||
combinationSlots={exercise.combination_slots}
|
||||
methodArchetype={exercise.method_archetype}
|
||||
methodProfile={exercise.method_profile}
|
||||
methodProfile={coachComboProfile}
|
||||
/>
|
||||
) : null}
|
||||
<h2 style={{ margin: '0 0 8px', fontSize: '1.2rem', lineHeight: 1.35 }}>{exercise.title}</h2>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import React, { Fragment, useCallback, useEffect, useState } from 'react'
|
||||
import { GripVertical, Pencil } from 'lucide-react'
|
||||
import CombinationMethodProfileEditor from './CombinationMethodProfileEditor'
|
||||
import { comboPlanningProfileJsonForEditor } from '../utils/comboPlanningMethodProfile'
|
||||
import {
|
||||
defaultSection,
|
||||
exerciseRow,
|
||||
|
|
@ -1019,6 +1021,72 @@ export default function TrainingUnitSectionsEditor({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{isCombination && it.exercise_id ? (
|
||||
<div
|
||||
className="tu-combo-planning-profile"
|
||||
style={{
|
||||
padding: '4px 12px 4px',
|
||||
paddingLeft: enableItemDragReorder ? 44 : 12,
|
||||
borderTop: '1px solid var(--border)',
|
||||
}}
|
||||
>
|
||||
<details className="card" style={{ padding: '12px 14px', background: 'var(--surface2)' }}>
|
||||
<summary
|
||||
style={{ cursor: 'pointer', fontSize: '0.88rem', color: 'var(--text2)', fontWeight: 600 }}
|
||||
>
|
||||
Ablaufprofil für diese Planung (Kombination)
|
||||
<span style={{ marginLeft: 10, fontWeight: 400, fontSize: '0.82rem' }}>
|
||||
{it.planning_method_profile != null &&
|
||||
typeof it.planning_method_profile === 'object' &&
|
||||
!Array.isArray(it.planning_method_profile)
|
||||
? '— Anpassung aktiv'
|
||||
: '— wie im Katalog'}
|
||||
</span>
|
||||
</summary>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 10 }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary framework-ctrl framework-ctrl--xs"
|
||||
onClick={() => updateItem(sIdx, iIdx, 'planning_method_profile', null)}
|
||||
>
|
||||
Planung wie Katalog
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary framework-ctrl framework-ctrl--xs"
|
||||
title="Bearbeitbare Kopie der Katalog-Vorgaben setzen"
|
||||
onClick={() =>
|
||||
updateItem(sIdx, iIdx, 'planning_method_profile', {
|
||||
...(it.catalog_method_profile || {}),
|
||||
})
|
||||
}
|
||||
>
|
||||
Aus Katalog kopieren …
|
||||
</button>
|
||||
</div>
|
||||
<CombinationMethodProfileEditor
|
||||
methodArchetype={(it.catalog_method_archetype || '').trim()}
|
||||
methodProfileJson={comboPlanningProfileJsonForEditor(
|
||||
it.catalog_method_profile || {},
|
||||
it.planning_method_profile
|
||||
)}
|
||||
onChangeMethodProfileJson={(json) => {
|
||||
try {
|
||||
const obj = JSON.parse(json || '{}')
|
||||
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
||||
updateItem(sIdx, iIdx, 'planning_method_profile', obj)
|
||||
}
|
||||
} catch {
|
||||
/* Ungültiges JSON — Hinweis im Editor */
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showExecutionExtras ? (
|
||||
<div className="tu-ex-debrief">
|
||||
<div className="tu-ex-debrief__grow">
|
||||
|
|
|
|||
|
|
@ -740,6 +740,11 @@ export default function TrainingCoachPage() {
|
|||
exercise={catalogExercise}
|
||||
exerciseId={currentEntry?.item?.exercise_id ?? null}
|
||||
variantId={currentEntry?.item?.exercise_variant_id ?? null}
|
||||
planningComboMethodProfile={
|
||||
String(currentEntry?.item?.exercise_kind || 'simple').toLowerCase().trim() === 'combination'
|
||||
? currentEntry?.item?.planning_method_profile ?? null
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
26
frontend/src/utils/comboPlanningMethodProfile.js
Normal file
26
frontend/src/utils/comboPlanningMethodProfile.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/** Effektives Ablaufprofil für Kombination im Coach/in der Planung */
|
||||
|
||||
export function effectiveComboMethodProfile(catalogDict, planningSnapshot) {
|
||||
const cat =
|
||||
catalogDict && typeof catalogDict === 'object' && !Array.isArray(catalogDict)
|
||||
? catalogDict
|
||||
: {}
|
||||
if (
|
||||
planningSnapshot !== null &&
|
||||
planningSnapshot !== undefined &&
|
||||
typeof planningSnapshot === 'object' &&
|
||||
!Array.isArray(planningSnapshot)
|
||||
) {
|
||||
return { ...planningSnapshot }
|
||||
}
|
||||
return { ...cat }
|
||||
}
|
||||
|
||||
export function comboPlanningProfileJsonForEditor(catalogDict, planningSnapshot) {
|
||||
const o = effectiveComboMethodProfile(catalogDict, planningSnapshot)
|
||||
try {
|
||||
return JSON.stringify(Object.keys(o).length ? o : {})
|
||||
} catch {
|
||||
return '{}'
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ export function sectionsToPutPayload(unit, durationOverridesByItemId = {}) {
|
|||
else actual = typeof actual === 'number' ? actual : parseInt(String(actual), 10)
|
||||
if (actual !== null && !Number.isFinite(actual)) actual = null
|
||||
|
||||
return {
|
||||
const row = {
|
||||
item_type: 'exercise',
|
||||
order_index: it.order_index ?? ii,
|
||||
exercise_id: parseInt(String(eid), 10),
|
||||
|
|
@ -92,6 +92,13 @@ export function sectionsToPutPayload(unit, durationOverridesByItemId = {}) {
|
|||
notes: trimOrNull(it.notes),
|
||||
modifications: trimOrNull(it.modifications),
|
||||
}
|
||||
if (isCombo) {
|
||||
const pmp = it.planning_method_profile
|
||||
if (pmp != null && typeof pmp === 'object' && !Array.isArray(pmp)) {
|
||||
row.planning_method_profile = { ...pmp }
|
||||
}
|
||||
}
|
||||
return row
|
||||
})
|
||||
.filter(Boolean),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,18 @@ export function defaultSection(title = 'Hauptteil') {
|
|||
return { title, guidance_notes: '', items: [] }
|
||||
}
|
||||
|
||||
function normalizeCatalogMethodProfile(cp) {
|
||||
if (cp && typeof cp === 'object' && !Array.isArray(cp)) return { ...cp }
|
||||
return {}
|
||||
}
|
||||
|
||||
/** NULL = Planung folgt Katalogprofil der Übung */
|
||||
function normalizePlanningMethodProfile(pm) {
|
||||
if (pm == null) return null
|
||||
if (typeof pm === 'object' && !Array.isArray(pm)) return { ...pm }
|
||||
return null
|
||||
}
|
||||
|
||||
export function exerciseRow() {
|
||||
return {
|
||||
item_type: 'exercise',
|
||||
|
|
@ -18,6 +30,9 @@ export function exerciseRow() {
|
|||
modifications: '',
|
||||
source_training_module_id: '',
|
||||
source_module_title: '',
|
||||
catalog_method_archetype: '',
|
||||
catalog_method_profile: {},
|
||||
planning_method_profile: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -29,16 +44,20 @@ export async function hydrateExercisePlanningRow(exercise) {
|
|||
if (!id) return null
|
||||
let meta = {}
|
||||
|
||||
async function fetchFull() {
|
||||
let full
|
||||
|
||||
async function ensureFull() {
|
||||
if (full !== undefined) return full
|
||||
try {
|
||||
return await api.getExercise(id)
|
||||
full = await api.getExercise(id)
|
||||
} catch {
|
||||
return null
|
||||
full = null
|
||||
}
|
||||
return full
|
||||
}
|
||||
|
||||
if (!variants.length) {
|
||||
const full = await fetchFull()
|
||||
await ensureFull()
|
||||
if (full) {
|
||||
variants = Array.isArray(full.variants) ? full.variants : []
|
||||
title = full.title || title
|
||||
|
|
@ -48,6 +67,8 @@ export async function hydrateExercisePlanningRow(exercise) {
|
|||
exercise_club_id: full.club_id ?? null,
|
||||
exercise_created_by: full.created_by ?? null,
|
||||
exercise_status: full.status || 'draft',
|
||||
catalog_method_archetype: typeof full.method_archetype === 'string' ? full.method_archetype.trim() : '',
|
||||
catalog_method_profile: normalizeCatalogMethodProfile(full.method_profile),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -62,7 +83,7 @@ export async function hydrateExercisePlanningRow(exercise) {
|
|||
meta.exercise_created_by == null ||
|
||||
exerciseKind == null
|
||||
) {
|
||||
const full = await fetchFull()
|
||||
await ensureFull()
|
||||
if (full) {
|
||||
if (meta.exercise_visibility == null) meta.exercise_visibility = full.visibility || 'private'
|
||||
if (meta.exercise_club_id == null) meta.exercise_club_id = full.club_id ?? null
|
||||
|
|
@ -89,6 +110,15 @@ export async function hydrateExercisePlanningRow(exercise) {
|
|||
row.variants = variants
|
||||
}
|
||||
Object.assign(row, meta)
|
||||
if (row.exercise_kind === 'combination') {
|
||||
if (full === undefined) await ensureFull()
|
||||
if (full) {
|
||||
row.catalog_method_archetype =
|
||||
typeof full.method_archetype === 'string' ? full.method_archetype.trim() : ''
|
||||
row.catalog_method_profile = normalizeCatalogMethodProfile(full.method_profile)
|
||||
}
|
||||
}
|
||||
row.planning_method_profile = null
|
||||
return row
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +177,9 @@ export function normalizeUnitToForm(fullUnit) {
|
|||
: '',
|
||||
notes: it.notes ?? '',
|
||||
modifications: it.modifications ?? '',
|
||||
catalog_method_archetype: String(it.catalog_method_archetype ?? '').trim(),
|
||||
catalog_method_profile: normalizeCatalogMethodProfile(it.catalog_method_profile),
|
||||
planning_method_profile: normalizePlanningMethodProfile(it.planning_method_profile),
|
||||
...(smEx != null
|
||||
? {
|
||||
source_training_module_id: smEx,
|
||||
|
|
@ -186,6 +219,9 @@ export function normalizeUnitToForm(fullUnit) {
|
|||
: '',
|
||||
notes: ex.notes ?? '',
|
||||
modifications: ex.modifications ?? '',
|
||||
catalog_method_archetype: String(ex.catalog_method_archetype ?? '').trim(),
|
||||
catalog_method_profile: normalizeCatalogMethodProfile(ex.catalog_method_profile),
|
||||
planning_method_profile: normalizePlanningMethodProfile(ex.planning_method_profile),
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
|
@ -217,6 +253,8 @@ export async function enrichSectionsWithVariants(sections) {
|
|||
club_id: ex.club_id ?? null,
|
||||
created_by: ex.created_by ?? null,
|
||||
status: ex.status || 'draft',
|
||||
method_archetype: typeof ex.method_archetype === 'string' ? ex.method_archetype.trim() : '',
|
||||
method_profile: normalizeCatalogMethodProfile(ex.method_profile),
|
||||
})
|
||||
} catch {
|
||||
cache.set(id, {
|
||||
|
|
@ -227,6 +265,8 @@ export async function enrichSectionsWithVariants(sections) {
|
|||
club_id: null,
|
||||
created_by: null,
|
||||
status: 'draft',
|
||||
method_archetype: '',
|
||||
method_profile: {},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -240,8 +280,18 @@ export async function enrichSectionsWithVariants(sections) {
|
|||
if (!c) return it
|
||||
const ek = String(c.exercise_kind || 'simple').toLowerCase().trim()
|
||||
const isCombo = ek === 'combination'
|
||||
const itemCatalog = normalizeCatalogMethodProfile(it.catalog_method_profile)
|
||||
const catalog_method_profile =
|
||||
Object.keys(itemCatalog).length > 0
|
||||
? itemCatalog
|
||||
: normalizeCatalogMethodProfile(c.method_profile)
|
||||
const rowArche = String(it.catalog_method_archetype ?? '').trim()
|
||||
const catalog_method_archetype = rowArche || String(c.method_archetype ?? '').trim()
|
||||
return {
|
||||
...it,
|
||||
catalog_method_archetype,
|
||||
catalog_method_profile,
|
||||
planning_method_profile: normalizePlanningMethodProfile(it.planning_method_profile),
|
||||
exercise_kind: isCombo ? 'combination' : 'simple',
|
||||
exercise_title: it.exercise_title || c.title,
|
||||
exercise_variant_id: isCombo ? '' : it.exercise_variant_id,
|
||||
|
|
@ -296,6 +346,12 @@ export function buildSectionsPayload(sections) {
|
|||
notes: it.notes?.trim() ? it.notes.trim() : null,
|
||||
modifications: it.modifications?.trim() ? it.modifications.trim() : null,
|
||||
}
|
||||
if (isCombo) {
|
||||
const pmp = it.planning_method_profile
|
||||
if (pmp != null && typeof pmp === 'object' && !Array.isArray(pmp)) {
|
||||
rowEx.planning_method_profile = { ...pmp }
|
||||
}
|
||||
}
|
||||
if (smEx != null) rowEx.source_training_module_id = smEx
|
||||
return rowEx
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user