fix(data-integrity) : prevent data loss in clone, slots, conversion and custom fields
- Clone: CustomFieldValue now references cloned CustomField, not source - Slots: validate piece type matches slot requirement + 404 on missing piece - Conversion: check slot tables before allowing category conversion + clean orphan skeleton requirements - CustomFieldValue: prevent creation of orphan CustomField without target entity Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,18 @@ class ComposantPieceSlotController extends AbstractController
|
||||
$slot->setSelectedPiece(null);
|
||||
} else {
|
||||
$piece = $this->entityManager->find(Piece::class, $payload['selectedPieceId']);
|
||||
if (!$piece) {
|
||||
return $this->json(['success' => false, 'error' => 'Pièce introuvable.'], 404);
|
||||
}
|
||||
|
||||
$slotTypePiece = $slot->getTypePiece();
|
||||
if ($slotTypePiece && $piece->getTypePiece()?->getId() !== $slotTypePiece->getId()) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => sprintf('La pièce doit être de type « %s ».', $slotTypePiece->getName()),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$slot->setSelectedPiece($piece);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,19 +196,16 @@ class CustomFieldValueController extends AbstractController
|
||||
return $this->json(['success' => false, 'error' => 'customFieldId or customFieldName is required.'], 400);
|
||||
}
|
||||
|
||||
$customField = new CustomField();
|
||||
$customField->setName($customFieldName);
|
||||
$customField->setType((string) ($payload['customFieldType'] ?? 'text'));
|
||||
$customField->setRequired((bool) ($payload['customFieldRequired'] ?? false));
|
||||
|
||||
$options = $payload['customFieldOptions'] ?? null;
|
||||
if (is_array($options)) {
|
||||
$customField->setOptions($options);
|
||||
// Try to find an existing custom field by name instead of creating an orphan
|
||||
$existing = $this->customFieldRepository->findOneBy(['name' => $customFieldName]);
|
||||
if ($existing instanceof CustomField) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$this->entityManager->persist($customField);
|
||||
|
||||
return $customField;
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => sprintf('Custom field "%s" not found. Create it explicitly first.', $customFieldName),
|
||||
], 404);
|
||||
}
|
||||
|
||||
private function resolveTarget(array $payload): array|JsonResponse
|
||||
|
||||
@@ -173,6 +173,8 @@ class MachineStructureController extends AbstractController
|
||||
|
||||
private function cloneCustomFields(Machine $source, Machine $target): void
|
||||
{
|
||||
$cfMap = [];
|
||||
|
||||
foreach ($source->getCustomFields() as $cf) {
|
||||
$newCf = new CustomField();
|
||||
$newCf->setName($cf->getName());
|
||||
@@ -183,12 +185,20 @@ class MachineStructureController extends AbstractController
|
||||
$newCf->setOrderIndex($cf->getOrderIndex());
|
||||
$newCf->setMachine($target);
|
||||
$this->entityManager->persist($newCf);
|
||||
|
||||
$cfMap[$cf->getId()] = $newCf;
|
||||
}
|
||||
|
||||
foreach ($source->getCustomFieldValues() as $cfv) {
|
||||
$originalCf = $cfv->getCustomField();
|
||||
$newCf = $cfMap[$originalCf->getId()] ?? null;
|
||||
if (!$newCf) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newValue = new CustomFieldValue();
|
||||
$newValue->setMachine($target);
|
||||
$newValue->setCustomField($cfv->getCustomField());
|
||||
$newValue->setCustomField($newCf);
|
||||
$newValue->setValue($cfv->getValue());
|
||||
$this->entityManager->persist($newValue);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user