123 lines
9.8 KiB
SQL
123 lines
9.8 KiB
SQL
-- MySQL 8.0. Run in the target database before upgrading, with application writes stopped.
|
|
-- Every row in the final result is a BLOCKER: repair with business confirmation.
|
|
-- This check works on legacy and upgraded schemas; inferred dimensions never use company 1.
|
|
-- Canonical source: after edits run pwsh -NoProfile -File scripts/sync-company-integrity.ps1
|
|
-- Verify the self-contained migration with the same command plus -Check.
|
|
-- BEGIN COMPANY INTEGRITY CORE
|
|
DROP TEMPORARY TABLE IF EXISTS tmp_company_application_dimensions;
|
|
CREATE TEMPORARY TABLE tmp_company_application_dimensions AS
|
|
SELECT a.id, COUNT(ai.id) item_count, COUNT(DISTINCT m.organization_id) organization_count,
|
|
MIN(m.organization_id) organization_id, COUNT(m.id) material_count
|
|
FROM material_applications a LEFT JOIN application_items ai ON ai.application_id=a.id
|
|
LEFT JOIN materials m ON m.id=ai.material_id GROUP BY a.id;
|
|
|
|
DROP TEMPORARY TABLE IF EXISTS tmp_company_log_dimensions;
|
|
CREATE TEMPORARY TABLE tmp_company_log_dimensions AS
|
|
SELECT s.id, o.organization_id, o.warehouse_id, o.location_id
|
|
FROM stock_logs s JOIN inbound_orders o ON s.related_order_type='inbound' AND s.related_order_id=o.id
|
|
JOIN inbound_items i ON i.inbound_order_id=o.id AND i.material_id=s.material_id
|
|
WHERE s.type='in' AND s.quantity=i.quantity
|
|
UNION
|
|
SELECT s.id, o.organization_id, o.warehouse_id, o.location_id
|
|
FROM stock_logs s JOIN outbound_orders o ON s.related_order_type='outbound' AND s.related_order_id=o.id
|
|
JOIN outbound_items i ON i.outbound_order_id=o.id AND i.material_id=s.material_id
|
|
WHERE s.type='out' AND s.quantity=i.quantity
|
|
UNION
|
|
SELECT s.id, d.organization_id, b.warehouse_id, b.location_id
|
|
FROM stock_logs s JOIN material_applications a ON s.related_order_type='application' AND s.related_order_id=a.id
|
|
JOIN tmp_company_application_dimensions d ON d.id=a.id AND d.item_count>0 AND d.organization_count=1 AND d.material_count=d.item_count
|
|
JOIN application_items i ON i.application_id=a.id AND i.material_id=s.material_id
|
|
JOIN inventory_balances b ON b.organization_id=d.organization_id AND b.material_id=s.material_id
|
|
JOIN warehouses w ON w.id=b.warehouse_id AND w.organization_id=d.organization_id
|
|
JOIN warehouse_locations l ON l.id=b.location_id AND l.warehouse_id=w.id
|
|
WHERE s.type='out' AND a.status='approved' AND s.quantity=i.quantity;
|
|
|
|
DROP TEMPORARY TABLE IF EXISTS tmp_company_integrity_anomalies;
|
|
CREATE TEMPORARY TABLE tmp_company_integrity_anomalies (
|
|
issue VARCHAR(100) NOT NULL, entity_id BIGINT NOT NULL, detail VARCHAR(500) NOT NULL
|
|
);
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'category_parent',c.id,'Parent category belongs to another company or is missing'
|
|
FROM material_categories c LEFT JOIN material_categories p ON p.id=c.parent_id
|
|
WHERE c.parent_id IS NOT NULL AND (p.id IS NULL OR p.organization_id<>c.organization_id);
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'material_category_unit_type',m.id,'Material category, base unit or type belongs to another company or is missing'
|
|
FROM materials m LEFT JOIN material_categories c ON c.id=m.category_id
|
|
LEFT JOIN units u ON u.id=m.base_unit_id LEFT JOIN material_types mt ON mt.id=m.material_type_id
|
|
WHERE c.id IS NULL OR c.organization_id<>m.organization_id
|
|
OR (m.base_unit_id IS NOT NULL AND (u.id IS NULL OR u.organization_id<>m.organization_id))
|
|
OR (m.material_type_id IS NOT NULL AND (mt.id IS NULL OR mt.organization_id<>m.organization_id));
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'rule_category',r.id,'Code rule category belongs to another company or is missing'
|
|
FROM code_rules r LEFT JOIN material_categories c ON c.id=r.category_id
|
|
WHERE c.id IS NULL OR c.organization_id<>r.organization_id;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'duplicate_default',user_id,'User has more than one default organization'
|
|
FROM user_organizations WHERE is_default<>0 GROUP BY user_id HAVING COUNT(*)>1;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'user_without_access',u.id,'User has no enabled company grant and no enabled super_admin role'
|
|
FROM users u WHERE NOT EXISTS(SELECT 1 FROM user_organizations g JOIN organizations o ON o.id=g.organization_id AND o.status='active' WHERE g.user_id=u.id)
|
|
AND NOT EXISTS(SELECT 1 FROM user_roles ur JOIN roles r ON r.id=ur.role_id AND r.code='super_admin' AND r.enabled=1 WHERE ur.user_id=u.id);
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'multiple_warehouses',organization_id,'Company has multiple warehouses (including inactive ones)'
|
|
FROM warehouses GROUP BY organization_id HAVING COUNT(*)>1;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'multiple_locations',warehouse_id,'Warehouse has multiple locations (including inactive ones)'
|
|
FROM warehouse_locations GROUP BY warehouse_id HAVING COUNT(*)>1;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'inventory_dimensions',b.id,'Inventory material, company, warehouse or location do not agree'
|
|
FROM inventory_balances b LEFT JOIN materials m ON m.id=b.material_id LEFT JOIN warehouses w ON w.id=b.warehouse_id
|
|
LEFT JOIN warehouse_locations l ON l.id=b.location_id LEFT JOIN organizations o ON o.id=b.organization_id
|
|
WHERE o.id IS NULL OR m.id IS NULL OR w.id IS NULL OR l.id IS NULL OR m.organization_id<>b.organization_id OR w.organization_id<>b.organization_id OR l.warehouse_id<>b.warehouse_id;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'warning_dimensions',r.id,'Warning material, company or warehouse do not agree'
|
|
FROM stock_warning_rules r LEFT JOIN materials m ON m.id=r.material_id LEFT JOIN warehouses w ON w.id=r.warehouse_id
|
|
WHERE m.id IS NULL OR w.id IS NULL OR m.organization_id<>r.organization_id OR w.organization_id<>r.organization_id;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'inbound_dimensions',o.id,'Inbound company, warehouse, location, materials or actual inventory dimensions do not agree'
|
|
FROM inbound_orders o LEFT JOIN warehouses w ON w.id=o.warehouse_id LEFT JOIN warehouse_locations l ON l.id=o.location_id
|
|
WHERE w.id IS NULL OR l.id IS NULL OR w.organization_id<>o.organization_id OR l.warehouse_id<>o.warehouse_id
|
|
OR NOT EXISTS(SELECT 1 FROM inbound_items i WHERE i.inbound_order_id=o.id)
|
|
OR EXISTS(SELECT 1 FROM inbound_items i LEFT JOIN materials m ON m.id=i.material_id
|
|
WHERE i.inbound_order_id=o.id AND (m.id IS NULL OR m.organization_id<>o.organization_id
|
|
OR NOT EXISTS(SELECT 1 FROM inventory_balances b WHERE b.material_id=i.material_id AND b.organization_id=o.organization_id AND b.warehouse_id=o.warehouse_id AND b.location_id=o.location_id)));
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'outbound_dimensions',o.id,'Outbound company, warehouse, location, materials or actual inventory dimensions do not agree'
|
|
FROM outbound_orders o LEFT JOIN warehouses w ON w.id=o.warehouse_id LEFT JOIN warehouse_locations l ON l.id=o.location_id
|
|
WHERE w.id IS NULL OR l.id IS NULL OR w.organization_id<>o.organization_id OR l.warehouse_id<>o.warehouse_id
|
|
OR NOT EXISTS(SELECT 1 FROM outbound_items i WHERE i.outbound_order_id=o.id)
|
|
OR EXISTS(SELECT 1 FROM outbound_items i LEFT JOIN materials m ON m.id=i.material_id
|
|
WHERE i.outbound_order_id=o.id AND (m.id IS NULL OR m.organization_id<>o.organization_id
|
|
OR NOT EXISTS(SELECT 1 FROM inventory_balances b WHERE b.material_id=i.material_id AND b.organization_id=o.organization_id AND b.warehouse_id=o.warehouse_id AND b.location_id=o.location_id)));
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'application_dimensions',id,'Application has empty, missing or mixed-company material details'
|
|
FROM tmp_company_application_dimensions WHERE item_count=0 OR organization_count<>1 OR material_count<>item_count;
|
|
INSERT INTO tmp_company_integrity_anomalies
|
|
SELECT 'unattributable_stock_log',s.id,'Log has no unique verified material/document/company/warehouse/location association'
|
|
FROM stock_logs s LEFT JOIN tmp_company_log_dimensions d ON d.id=s.id
|
|
LEFT JOIN materials m ON m.id=s.material_id LEFT JOIN warehouses w ON w.id=d.warehouse_id LEFT JOIN warehouse_locations l ON l.id=d.location_id
|
|
GROUP BY s.id,m.id,m.organization_id
|
|
HAVING COUNT(d.id)<>1 OR COUNT(m.id)=0 OR SUM(d.organization_id<>m.organization_id OR w.organization_id<>d.organization_id OR l.warehouse_id<>d.warehouse_id)>0;
|
|
|
|
-- The same check file must work before the columns exist and after backfill.
|
|
SET @company_check_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='stock_logs' AND column_name IN ('organization_id','warehouse_id','location_id'))=3,
|
|
'INSERT INTO tmp_company_integrity_anomalies SELECT ''stored_stock_log_dimensions'',s.id,''Stored log dimensions differ from verified source document'' FROM stock_logs s LEFT JOIN tmp_company_log_dimensions d ON d.id=s.id WHERE d.id IS NULL OR s.organization_id IS NULL OR s.warehouse_id IS NULL OR s.location_id IS NULL OR s.organization_id<>d.organization_id OR s.warehouse_id<>d.warehouse_id OR s.location_id<>d.location_id',
|
|
'DO 0');
|
|
PREPARE company_check FROM @company_check_sql;
|
|
EXECUTE company_check;
|
|
DEALLOCATE PREPARE company_check;
|
|
SET @company_check_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='material_applications' AND column_name='organization_id')=1,
|
|
'INSERT INTO tmp_company_integrity_anomalies SELECT ''stored_application_dimensions'',a.id,''Stored application company differs from verified detail materials'' FROM material_applications a JOIN tmp_company_application_dimensions d ON d.id=a.id WHERE a.organization_id IS NULL OR a.organization_id<>d.organization_id',
|
|
'DO 0');
|
|
PREPARE company_check FROM @company_check_sql;
|
|
EXECUTE company_check;
|
|
DEALLOCATE PREPARE company_check;
|
|
|
|
-- END COMPANY INTEGRITY CORE
|
|
SELECT issue, entity_id, detail FROM tmp_company_integrity_anomalies ORDER BY issue,entity_id;
|
|
-- Counts are also review evidence, even when zero or one per dimension.
|
|
SELECT organization_id,COUNT(*) warehouse_count FROM warehouses GROUP BY organization_id;
|
|
SELECT warehouse_id,COUNT(*) location_count FROM warehouse_locations GROUP BY warehouse_id;
|