Consolidate create and edit pages into single create pages with edit mode support. Remove obsolete catalog pages, history composables, and fix remaining code review issues. Include migration to relink orphaned custom fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
157 lines
6.3 KiB
PHP
157 lines
6.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace DoctrineMigrations;
|
||
|
||
use Doctrine\DBAL\Schema\Schema;
|
||
use Doctrine\Migrations\AbstractMigration;
|
||
|
||
/**
|
||
* Re-link orphaned CustomField records to their ModelType.
|
||
*
|
||
* Legacy data created one CustomField definition per composant/piece instead
|
||
* of sharing a single definition on the ModelType. This migration:
|
||
* 1. Finds orphaned CustomField used by composants → elects one canonical per (ModelType, name)
|
||
* 2. Same for pieces
|
||
* 3. Re-points CustomFieldValue to the canonical
|
||
* 4. Sets typecomposantid / typepieceid on the canonical
|
||
* 5. Deletes now-unused duplicate CustomField rows
|
||
*/
|
||
final class Version20260404_RelinkOrphanedCustomFields extends AbstractMigration
|
||
{
|
||
public function getDescription(): string
|
||
{
|
||
return 'Re-link orphaned custom_fields to their model_types via composants and pieces';
|
||
}
|
||
|
||
public function up(Schema $schema): void
|
||
{
|
||
// ── COMPOSANTS ──────────────────────────────────────────────────
|
||
// For each (model_type, field_name) group of orphaned fields used by composants,
|
||
// pick the one with the smallest id as canonical, set its typecomposantid,
|
||
// re-point all values to it, then delete the duplicates.
|
||
$this->addSql(<<<'SQL'
|
||
-- Step 1: Create temp table with canonical mapping for COMPOSANT orphans
|
||
CREATE TEMP TABLE _cf_composant_canonical AS
|
||
SELECT DISTINCT ON (c.typecomposantid, cf.name)
|
||
cf.id AS canonical_id,
|
||
c.typecomposantid AS mt_id,
|
||
cf.name AS cf_name
|
||
FROM custom_fields cf
|
||
INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
|
||
INNER JOIN composants c ON c.id = cfv.composantid
|
||
WHERE cf.typecomposantid IS NULL
|
||
AND cf.typepieceid IS NULL
|
||
AND cf.typeproductid IS NULL
|
||
AND cf.machineid IS NULL
|
||
AND c.typecomposantid IS NOT NULL
|
||
ORDER BY c.typecomposantid, cf.name, cf.id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
-- Step 2: Create temp table listing ALL orphaned composant field ids with their canonical
|
||
CREATE TEMP TABLE _cf_composant_all AS
|
||
SELECT cf.id AS orphan_id, canon.canonical_id
|
||
FROM custom_fields cf
|
||
INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
|
||
INNER JOIN composants c ON c.id = cfv.composantid
|
||
INNER JOIN _cf_composant_canonical canon
|
||
ON canon.mt_id = c.typecomposantid AND canon.cf_name = cf.name
|
||
WHERE cf.typecomposantid IS NULL
|
||
AND cf.typepieceid IS NULL
|
||
AND cf.typeproductid IS NULL
|
||
AND cf.machineid IS NULL
|
||
AND c.typecomposantid IS NOT NULL
|
||
AND cf.id != canon.canonical_id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
-- Step 3: Re-point custom_field_values from duplicates to canonical
|
||
UPDATE custom_field_values cfv
|
||
SET customfieldid = dup.canonical_id
|
||
FROM _cf_composant_all dup
|
||
WHERE cfv.customfieldid = dup.orphan_id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
-- Step 4: Set typecomposantid on canonical fields
|
||
UPDATE custom_fields cf
|
||
SET typecomposantid = canon.mt_id
|
||
FROM _cf_composant_canonical canon
|
||
WHERE cf.id = canon.canonical_id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
-- Step 5: Delete duplicate (non-canonical) custom_fields for composants
|
||
DELETE FROM custom_fields
|
||
WHERE id IN (SELECT orphan_id FROM _cf_composant_all);
|
||
SQL);
|
||
|
||
$this->addSql('DROP TABLE IF EXISTS _cf_composant_all');
|
||
$this->addSql('DROP TABLE IF EXISTS _cf_composant_canonical');
|
||
|
||
// ── PIECES ───────────<E29480><E29480><EFBFBD>────────────────────────────<E29480><E29480><EFBFBD>─────────────
|
||
$this->addSql(<<<'SQL'
|
||
CREATE TEMP TABLE _cf_piece_canonical AS
|
||
SELECT DISTINCT ON (p.typepieceid, cf.name)
|
||
cf.id AS canonical_id,
|
||
p.typepieceid AS mt_id,
|
||
cf.name AS cf_name
|
||
FROM custom_fields cf
|
||
INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
|
||
INNER JOIN pieces p ON p.id = cfv.pieceid
|
||
WHERE cf.typecomposantid IS NULL
|
||
AND cf.typepieceid IS NULL
|
||
AND cf.typeproductid IS NULL
|
||
AND cf.machineid IS NULL
|
||
AND p.typepieceid IS NOT NULL
|
||
ORDER BY p.typepieceid, cf.name, cf.id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
CREATE TEMP TABLE _cf_piece_all AS
|
||
SELECT cf.id AS orphan_id, canon.canonical_id
|
||
FROM custom_fields cf
|
||
INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
|
||
INNER JOIN pieces p ON p.id = cfv.pieceid
|
||
INNER JOIN _cf_piece_canonical canon
|
||
ON canon.mt_id = p.typepieceid AND canon.cf_name = cf.name
|
||
WHERE cf.typecomposantid IS NULL
|
||
AND cf.typepieceid IS NULL
|
||
AND cf.typeproductid IS NULL
|
||
AND cf.machineid IS NULL
|
||
AND p.typepieceid IS NOT NULL
|
||
AND cf.id != canon.canonical_id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
UPDATE custom_field_values cfv
|
||
SET customfieldid = dup.canonical_id
|
||
FROM _cf_piece_all dup
|
||
WHERE cfv.customfieldid = dup.orphan_id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
UPDATE custom_fields cf
|
||
SET typepieceid = canon.mt_id
|
||
FROM _cf_piece_canonical canon
|
||
WHERE cf.id = canon.canonical_id;
|
||
SQL);
|
||
|
||
$this->addSql(<<<'SQL'
|
||
DELETE FROM custom_fields
|
||
WHERE id IN (SELECT orphan_id FROM _cf_piece_all);
|
||
SQL);
|
||
|
||
$this->addSql('DROP TABLE IF EXISTS _cf_piece_all');
|
||
$this->addSql('DROP TABLE IF EXISTS _cf_piece_canonical');
|
||
}
|
||
|
||
public function down(Schema $schema): void
|
||
{
|
||
// Data migration — not reversible. The old per-entity duplicates are gone.
|
||
$this->addSql('SELECT 1');
|
||
}
|
||
}
|