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:
@@ -235,6 +235,45 @@ final class ModelTypeCategoryConversionService
|
||||
}
|
||||
}
|
||||
|
||||
// Check slot tables for actual data (post-normalization architecture)
|
||||
$filledPieceSlots = (int) $this->connection->fetchOne(
|
||||
'SELECT COUNT(*) FROM composant_piece_slots cps
|
||||
JOIN composants c ON cps.composantid = c.id
|
||||
WHERE c.typecomposantid = :id AND cps.selectedpieceid IS NOT NULL',
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
|
||||
$filledSubSlots = (int) $this->connection->fetchOne(
|
||||
'SELECT COUNT(*) FROM composant_subcomponent_slots css
|
||||
JOIN composants c ON css.composantid = c.id
|
||||
WHERE c.typecomposantid = :id AND css.selectedcomposantid IS NOT NULL',
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
|
||||
$filledProductSlots = (int) $this->connection->fetchOne(
|
||||
'SELECT COUNT(*) FROM composant_product_slots cps
|
||||
JOIN composants c ON cps.composantid = c.id
|
||||
WHERE c.typecomposantid = :id AND cps.selectedproductid IS NOT NULL',
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
|
||||
if ($filledPieceSlots > 0 || $filledSubSlots > 0 || $filledProductSlots > 0) {
|
||||
$parts = [];
|
||||
if ($filledPieceSlots > 0) {
|
||||
$parts[] = sprintf('%d slot(s) pièce', $filledPieceSlots);
|
||||
}
|
||||
if ($filledSubSlots > 0) {
|
||||
$parts[] = sprintf('%d slot(s) sous-composant', $filledSubSlots);
|
||||
}
|
||||
if ($filledProductSlots > 0) {
|
||||
$parts[] = sprintf('%d slot(s) produit', $filledProductSlots);
|
||||
}
|
||||
$blockers[] = sprintf(
|
||||
'Des composants ont des données dans leurs slots : %s.',
|
||||
implode(', ', $parts),
|
||||
);
|
||||
}
|
||||
|
||||
// Check name collision with existing pieces
|
||||
$collisions = $this->connection->fetchFirstColumn(
|
||||
'SELECT c.name FROM composants c
|
||||
@@ -383,12 +422,22 @@ final class ModelTypeCategoryConversionService
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
|
||||
// 6. Delete original composants
|
||||
// 6. Delete original composants (cascades to slot tables)
|
||||
$this->connection->executeStatement(
|
||||
'DELETE FROM composants WHERE typecomposantid = :id',
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
|
||||
// 6b. Clean up skeleton requirements that only apply to COMPONENT category
|
||||
$this->connection->executeStatement(
|
||||
'DELETE FROM skeleton_piece_requirements WHERE modeltypeid = :id',
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
$this->connection->executeStatement(
|
||||
'DELETE FROM skeleton_subcomponent_requirements WHERE modeltypeid = :id',
|
||||
['id' => $modelTypeId],
|
||||
);
|
||||
|
||||
// 7. Update ModelType
|
||||
$this->connection->executeStatement(
|
||||
'UPDATE model_types
|
||||
|
||||
Reference in New Issue
Block a user