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>
84 lines
2.9 KiB
PHP
84 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__.'/../vendor/autoload.php';
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
|
|
$conn = DriverManager::getConnection([
|
|
'driver' => 'pdo_pgsql',
|
|
'host' => 'localhost',
|
|
'port' => 5432,
|
|
'dbname' => 'inventory',
|
|
'user' => 'ferme_user',
|
|
'password' => 'fermerecette',
|
|
]);
|
|
|
|
// Show a sample of orphaned CFValues for pieces
|
|
echo "--- Sample orphaned piece CFValues ---\n";
|
|
$rows = $conn->fetchAllAssociative("
|
|
SELECT cfv.id as cfv_id, cfv.value, cfv.pieceid,
|
|
cf.id as cf_id, cf.name as cf_name,
|
|
cf.typecomposantid, cf.typepieceid, cf.typeproductid,
|
|
p.name as piece_name, p.typepieceid as piece_modeltype
|
|
FROM custom_field_values cfv
|
|
JOIN custom_fields cf ON cf.id = cfv.customfieldid
|
|
JOIN pieces p ON p.id = cfv.pieceid
|
|
WHERE cfv.pieceid IS NOT NULL
|
|
AND cf.typepieceid IS NULL
|
|
ORDER BY p.name
|
|
LIMIT 10
|
|
");
|
|
echo sprintf("Found %d (limited to 10)\n\n", count($rows));
|
|
foreach ($rows as $r) {
|
|
echo sprintf(" Piece '%s' | field '%s' = '%s' | CF FK: composant=%s piece=%s product=%s\n",
|
|
$r['piece_name'], $r['cf_name'], $r['value'],
|
|
$r['typecomposantid'] ?? 'NULL',
|
|
$r['typepieceid'] ?? 'NULL',
|
|
$r['typeproductid'] ?? 'NULL'
|
|
);
|
|
}
|
|
|
|
// Show a sample of orphaned CFValues for composants
|
|
echo "\n--- Sample orphaned composant CFValues ---\n";
|
|
$rows = $conn->fetchAllAssociative("
|
|
SELECT cfv.id as cfv_id, cfv.value, cfv.composantid,
|
|
cf.id as cf_id, cf.name as cf_name,
|
|
cf.typecomposantid, cf.typepieceid, cf.typeproductid,
|
|
c.name as composant_name, c.typecomposantid as composant_modeltype
|
|
FROM custom_field_values cfv
|
|
JOIN custom_fields cf ON cf.id = cfv.customfieldid
|
|
JOIN composants c ON c.id = cfv.composantid
|
|
WHERE cfv.composantid IS NOT NULL
|
|
AND cf.typecomposantid IS NULL
|
|
ORDER BY c.name
|
|
LIMIT 10
|
|
");
|
|
echo sprintf("Found %d (limited to 10)\n\n", count($rows));
|
|
foreach ($rows as $r) {
|
|
echo sprintf(" Composant '%s' | field '%s' = '%s' | CF FK: composant=%s piece=%s product=%s\n",
|
|
$r['composant_name'], $r['cf_name'], $r['value'],
|
|
$r['typecomposantid'] ?? 'NULL',
|
|
$r['typepieceid'] ?? 'NULL',
|
|
$r['typeproductid'] ?? 'NULL'
|
|
);
|
|
}
|
|
|
|
// Check: are there CFs with ONLY typepieceid NULL but other FKs set?
|
|
echo "\n--- Orphaned CF FK patterns ---\n";
|
|
$rows = $conn->fetchAllAssociative("
|
|
SELECT
|
|
CASE WHEN typecomposantid IS NULL THEN 'NULL' ELSE 'SET' END as composant_fk,
|
|
CASE WHEN typepieceid IS NULL THEN 'NULL' ELSE 'SET' END as piece_fk,
|
|
CASE WHEN typeproductid IS NULL THEN 'NULL' ELSE 'SET' END as product_fk,
|
|
COUNT(*) as cnt
|
|
FROM custom_fields
|
|
GROUP BY composant_fk, piece_fk, product_fk
|
|
ORDER BY cnt DESC
|
|
");
|
|
foreach ($rows as $r) {
|
|
echo sprintf(" composant=%s piece=%s product=%s : %d CFs\n",
|
|
$r['composant_fk'], $r['piece_fk'], $r['product_fk'], $r['cnt']);
|
|
}
|