fix(custom-fields) : prevent data loss on ModelType save + restoration scripts

Backend: match existing CustomField by name as fallback when ID is not provided,
preventing deletion and recreation of field definitions (which cascade-deletes values).

Includes restoration/migration scripts for prod:
- restore-custom-field-values.php: restores piece values from audit logs
- migrate-orphaned-custom-fields.php: migrates values from orphaned CFs
- fix-prod-all.php: combined fix (migrate + restore + cleanup)
- fix-prod-recreate-and-migrate.php: full fix (recreate missing CFs + migrate + restore)
- check-prod-*.php: diagnostic scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-17 20:10:48 +01:00
parent add3a9a21f
commit 38777b7de0
11 changed files with 1217 additions and 5 deletions

View File

@@ -192,10 +192,12 @@ class SkeletonStructureService
['orderIndex' => 'ASC']
);
// Index existing by ID for matching
$existingById = [];
// Index existing by ID and by name for matching
$existingById = [];
$existingByName = [];
foreach ($existingFields as $cf) {
$existingById[$cf->getId()] = $cf;
$existingById[$cf->getId()] = $cf;
$existingByName[$cf->getName()] = $cf;
}
$processedIds = [];
@@ -204,11 +206,13 @@ class SkeletonStructureService
// Normalize both formats to a common shape
$normalized = $this->normalizeCustomFieldData($fieldData, $i);
// Try to match an existing field by ID
// Try to match an existing field by ID first, then by name as fallback
$existingField = null;
$fieldId = $fieldData['customFieldId'] ?? $fieldData['id'] ?? null;
if ($fieldId && isset($existingById[$fieldId])) {
$existingField = $existingById[$fieldId];
} elseif (isset($existingByName[$normalized['name']]) && !isset($processedIds[$existingByName[$normalized['name']]->getId()])) {
$existingField = $existingByName[$normalized['name']];
}
if ($existingField) {