From 034c193e4b7e8e2cba222d740e7afdde4de202ce Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 25 Jan 2026 21:19:42 +0100 Subject: [PATCH] feat(audit): add history tracking and bump version to 1.1.2 --- VERSION | 2 +- config/services.yaml | 12 + migrations/Version20260125143939.php | 932 +++++++++++++++--- migrations/Version20260125170000.php | 41 + src/Controller/ComposantHistoryController.php | 80 ++ src/Controller/PieceHistoryController.php | 80 ++ src/Controller/ProductHistoryController.php | 80 ++ src/Entity/AuditLog.php | 117 +++ .../ComposantAuditSubscriber.php | 300 ++++++ src/EventSubscriber/PieceAuditSubscriber.php | 300 ++++++ .../ProductAuditSubscriber.php | 298 ++++++ src/Repository/AuditLogRepository.php | 37 + 12 files changed, 2157 insertions(+), 122 deletions(-) create mode 100644 migrations/Version20260125170000.php create mode 100644 src/Controller/ComposantHistoryController.php create mode 100644 src/Controller/PieceHistoryController.php create mode 100644 src/Controller/ProductHistoryController.php create mode 100644 src/Entity/AuditLog.php create mode 100644 src/EventSubscriber/ComposantAuditSubscriber.php create mode 100644 src/EventSubscriber/PieceAuditSubscriber.php create mode 100644 src/EventSubscriber/ProductAuditSubscriber.php create mode 100644 src/Repository/AuditLogRepository.php diff --git a/VERSION b/VERSION index 524cb55..45a1b3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.1 +1.1.2 diff --git a/config/services.yaml b/config/services.yaml index 79b8ce2..fe134d2 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -21,3 +21,15 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + + App\EventSubscriber\ProductAuditSubscriber: + tags: + - { name: doctrine.event_subscriber } + + App\EventSubscriber\PieceAuditSubscriber: + tags: + - { name: doctrine.event_subscriber } + + App\EventSubscriber\ComposantAuditSubscriber: + tags: + - { name: doctrine.event_subscriber } diff --git a/migrations/Version20260125143939.php b/migrations/Version20260125143939.php index 804876b..909e84d 100644 --- a/migrations/Version20260125143939.php +++ b/migrations/Version20260125143939.php @@ -20,165 +20,792 @@ final class Version20260125143939 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER INDEX idx_f95a3199df92e79b RENAME TO IDX_F95A3199CC8A4CEE'); - $this->addSql('ALTER INDEX idx_f95a3199a3fdb2a7 RENAME TO IDX_F95A319936799605'); - $this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT "_ComposantConstructeurs_A_fkey"'); - $this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT "_ComposantConstructeurs_B_fkey"'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f95a3199df92e79b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f95a3199df92e79b RENAME TO IDX_F95A3199CC8A4CEE'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f95a3199a3fdb2a7') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f95a3199a3fdb2a7 RENAME TO IDX_F95A319936799605'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT IF EXISTS "_ComposantConstructeurs_A_fkey"'); + $this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT IF EXISTS "_ComposantConstructeurs_B_fkey"'); $this->addSql('ALTER TABLE _composantconstructeurs ALTER A TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _composantconstructeurs ALTER B TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _composantconstructeurs ADD CONSTRAINT FK_60760125D3D99E8B FOREIGN KEY (A) REFERENCES composants (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _composantconstructeurs ADD CONSTRAINT FK_607601254AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _composantconstructeurs ADD PRIMARY KEY (A, B)'); - $this->addSql('ALTER INDEX idx_5b97d813e8b7be43 RENAME TO IDX_60760125D3D99E8B'); - $this->addSql('ALTER INDEX _composantconstructeurs_b_index RENAME TO IDX_607601254AD0CF31'); - $this->addSql('ALTER INDEX idx_6b64d7ff6736d61 RENAME TO IDX_6B64D7FF5C4A705F'); - $this->addSql('ALTER INDEX idx_6b64d7fff6bae05f RENAME TO IDX_6B64D7FF633EC4FD'); - $this->addSql('ALTER INDEX idx_6b64d7ffa1dac1c6 RENAME TO IDX_6B64D7FF345EE564'); - $this->addSql('ALTER INDEX idx_6b64d7ff96428d73 RENAME TO IDX_6B64D7FF3C6A9D1'); - $this->addSql('ALTER INDEX idx_6b64d7ffa3fdb2a7 RENAME TO IDX_6B64D7FF36799605'); - $this->addSql('ALTER INDEX idx_4a48378c158582c3 RENAME TO IDX_4A48378C2F024C2'); - $this->addSql('ALTER INDEX idx_4a48378cdf92e79b RENAME TO IDX_4A48378CCC8A4CEE'); - $this->addSql('ALTER INDEX idx_4a48378c4ca601c8 RENAME TO IDX_4A48378C169F1CF6'); - $this->addSql('ALTER INDEX idx_4a48378c40c2d03b RENAME TO IDX_4A48378C57B7763A'); - $this->addSql('ALTER INDEX idx_a2b07288f6bae05f RENAME TO IDX_A2B07288633EC4FD'); - $this->addSql('ALTER INDEX idx_a2b07288a1dac1c6 RENAME TO IDX_A2B07288345EE564'); - $this->addSql('ALTER INDEX idx_a2b0728896428d73 RENAME TO IDX_A2B072883C6A9D1'); - $this->addSql('ALTER INDEX idx_a2b07288a3fdb2a7 RENAME TO IDX_A2B0728836799605'); - $this->addSql('ALTER INDEX idx_a2b07288fcf7805f RENAME TO IDX_A2B072886973A4FD'); - $this->addSql('ALTER INDEX idx_528efe19f6bae05f RENAME TO IDX_528EFE19633EC4FD'); - $this->addSql('ALTER INDEX idx_528efe19a1dac1c6 RENAME TO IDX_528EFE19345EE564'); - $this->addSql('ALTER INDEX idx_528efe197d44d2df RENAME TO IDX_528EFE19EF6CF34B'); - $this->addSql('ALTER INDEX idx_528efe19bcced9e3 RENAME TO IDX_528EFE19C44B383C'); - $this->addSql('ALTER INDEX idx_62941615f6bae05f RENAME TO IDX_62941615633EC4FD'); - $this->addSql('ALTER INDEX idx_6294161596428d73 RENAME TO IDX_629416153C6A9D1'); - $this->addSql('ALTER INDEX idx_629416157d44d2df RENAME TO IDX_62941615EF6CF34B'); - $this->addSql('ALTER INDEX idx_6294161532c54aaf RENAME TO IDX_62941615F957D314'); - $this->addSql('ALTER INDEX machine_product_links_machineid_idx RENAME TO IDX_8CC32259633EC4FD'); - $this->addSql('ALTER INDEX machine_product_links_productid_idx RENAME TO IDX_8CC3225936799605'); - $this->addSql('ALTER INDEX idx_8cc32259357fdbff RENAME TO IDX_8CC32259B590B209'); - $this->addSql('ALTER INDEX idx_8cc322597d44d2df RENAME TO IDX_8CC32259EF6CF34B'); - $this->addSql('ALTER INDEX idx_8cc32259bcd7dad6 RENAME TO IDX_8CC32259A63AC5DC'); - $this->addSql('ALTER INDEX idx_8cc3225987ceb33f RENAME TO IDX_8CC32259937A1D7C'); - $this->addSql('ALTER INDEX idx_f1ce8dedfcf7805f RENAME TO IDX_F1CE8DED6973A4FD'); - $this->addSql('ALTER INDEX idx_f1ce8ded158582c3 RENAME TO IDX_F1CE8DED2F024C2'); - $this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT "_MachineConstructeurs_B_fkey"'); - $this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT "_MachineConstructeurs_A_fkey"'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_5b97d813e8b7be43') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_5b97d813e8b7be43 RENAME TO IDX_60760125D3D99E8B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('_composantconstructeurs_b_index') IS NOT NULL THEN + EXECUTE 'ALTER INDEX _composantconstructeurs_b_index RENAME TO IDX_607601254AD0CF31'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff6736d61') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff6736d61 RENAME TO IDX_6B64D7FF5C4A705F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7fff6bae05f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7fff6bae05f RENAME TO IDX_6B64D7FF633EC4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ffa1dac1c6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ffa1dac1c6 RENAME TO IDX_6B64D7FF345EE564'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff96428d73') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff96428d73 RENAME TO IDX_6B64D7FF3C6A9D1'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ffa3fdb2a7') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ffa3fdb2a7 RENAME TO IDX_6B64D7FF36799605'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378c158582c3') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378c158582c3 RENAME TO IDX_4A48378C2F024C2'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378cdf92e79b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378cdf92e79b RENAME TO IDX_4A48378CCC8A4CEE'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378c4ca601c8') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378c4ca601c8 RENAME TO IDX_4A48378C169F1CF6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378c40c2d03b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378c40c2d03b RENAME TO IDX_4A48378C57B7763A'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b07288f6bae05f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b07288f6bae05f RENAME TO IDX_A2B07288633EC4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b07288a1dac1c6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b07288a1dac1c6 RENAME TO IDX_A2B07288345EE564'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b0728896428d73') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b0728896428d73 RENAME TO IDX_A2B072883C6A9D1'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b07288a3fdb2a7') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b07288a3fdb2a7 RENAME TO IDX_A2B0728836799605'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b07288fcf7805f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b07288fcf7805f RENAME TO IDX_A2B072886973A4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19f6bae05f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19f6bae05f RENAME TO IDX_528EFE19633EC4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19a1dac1c6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19a1dac1c6 RENAME TO IDX_528EFE19345EE564'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe197d44d2df') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe197d44d2df RENAME TO IDX_528EFE19EF6CF34B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19bcced9e3') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19bcced9e3 RENAME TO IDX_528EFE19C44B383C'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_62941615f6bae05f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_62941615f6bae05f RENAME TO IDX_62941615633EC4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6294161596428d73') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6294161596428d73 RENAME TO IDX_629416153C6A9D1'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_629416157d44d2df') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_629416157d44d2df RENAME TO IDX_62941615EF6CF34B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6294161532c54aaf') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6294161532c54aaf RENAME TO IDX_62941615F957D314'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('machine_product_links_machineid_idx') IS NOT NULL THEN + EXECUTE 'ALTER INDEX machine_product_links_machineid_idx RENAME TO IDX_8CC32259633EC4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('machine_product_links_productid_idx') IS NOT NULL THEN + EXECUTE 'ALTER INDEX machine_product_links_productid_idx RENAME TO IDX_8CC3225936799605'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259357fdbff') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259357fdbff RENAME TO IDX_8CC32259B590B209'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc322597d44d2df') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc322597d44d2df RENAME TO IDX_8CC32259EF6CF34B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259bcd7dad6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259bcd7dad6 RENAME TO IDX_8CC32259A63AC5DC'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc3225987ceb33f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc3225987ceb33f RENAME TO IDX_8CC32259937A1D7C'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f1ce8dedfcf7805f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f1ce8dedfcf7805f RENAME TO IDX_F1CE8DED6973A4FD'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f1ce8ded158582c3') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f1ce8ded158582c3 RENAME TO IDX_F1CE8DED2F024C2'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT IF EXISTS "_MachineConstructeurs_B_fkey"'); + $this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT IF EXISTS "_MachineConstructeurs_A_fkey"'); $this->addSql('ALTER TABLE _machineconstructeurs ALTER A TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _machineconstructeurs ALTER B TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _machineconstructeurs ADD CONSTRAINT FK_E6A040CCD3D99E8B FOREIGN KEY (A) REFERENCES machines (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _machineconstructeurs ADD CONSTRAINT FK_E6A040CC4AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _machineconstructeurs ADD PRIMARY KEY (A, B)'); - $this->addSql('ALTER INDEX idx_4f225b32e8b7be43 RENAME TO IDX_E6A040CCD3D99E8B'); - $this->addSql('ALTER INDEX _machineconstructeurs_b_index RENAME TO IDX_E6A040CC4AD0CF31'); - $this->addSql('DROP INDEX "ModelType_category_name_key"'); - $this->addSql('DROP INDEX "ModelType_code_key"'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4f225b32e8b7be43') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4f225b32e8b7be43 RENAME TO IDX_E6A040CCD3D99E8B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('_machineconstructeurs_b_index') IS NOT NULL THEN + EXECUTE 'ALTER INDEX _machineconstructeurs_b_index RENAME TO IDX_E6A040CC4AD0CF31'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE model_types DROP CONSTRAINT IF EXISTS "ModelType_category_name_key"'); + $this->addSql('ALTER TABLE model_types DROP CONSTRAINT IF EXISTS "ModelType_code_key"'); $this->addSql('ALTER TABLE model_types ALTER id TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE model_types ALTER category TYPE VARCHAR(255)'); $this->addSql('ALTER TABLE model_types ALTER createdAt DROP DEFAULT'); $this->addSql('ALTER TABLE model_types ALTER componentSkeleton TYPE JSON'); $this->addSql('ALTER TABLE model_types ALTER pieceSkeleton TYPE JSON'); $this->addSql('ALTER TABLE model_types ALTER productSkeleton TYPE JSON'); - $this->addSql('ALTER INDEX idx_b92d74724ca601c8 RENAME TO IDX_B92D7472169F1CF6'); - $this->addSql('ALTER INDEX idx_b92d7472a3fdb2a7 RENAME TO IDX_B92D747236799605'); - $this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT "_PieceConstructeurs_A_fkey"'); - $this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT "_PieceConstructeurs_B_fkey"'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_b92d74724ca601c8') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_b92d74724ca601c8 RENAME TO IDX_B92D7472169F1CF6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_b92d7472a3fdb2a7') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_b92d7472a3fdb2a7 RENAME TO IDX_B92D747236799605'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT IF EXISTS "_PieceConstructeurs_A_fkey"'); + $this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT IF EXISTS "_PieceConstructeurs_B_fkey"'); $this->addSql('ALTER TABLE _piececonstructeurs ALTER A TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _piececonstructeurs ALTER B TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _piececonstructeurs ADD CONSTRAINT FK_E94732E5D3D99E8B FOREIGN KEY (A) REFERENCES pieces (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _piececonstructeurs ADD CONSTRAINT FK_E94732E54AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _piececonstructeurs ADD PRIMARY KEY (A, B)'); - $this->addSql('ALTER INDEX idx_77fc120e8b7be43 RENAME TO IDX_E94732E5D3D99E8B'); - $this->addSql('ALTER INDEX _piececonstructeurs_b_index RENAME TO IDX_E94732E54AD0CF31'); - $this->addSql('ALTER INDEX idx_b3ba5a5a40c2d03b RENAME TO IDX_B3BA5A5A57B7763A'); - $this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT "_ProductConstructeurs_B_fkey"'); - $this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT "_ProductConstructeurs_A_fkey"'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_77fc120e8b7be43') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_77fc120e8b7be43 RENAME TO IDX_E94732E5D3D99E8B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('_piececonstructeurs_b_index') IS NOT NULL THEN + EXECUTE 'ALTER INDEX _piececonstructeurs_b_index RENAME TO IDX_E94732E54AD0CF31'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_b3ba5a5a40c2d03b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_b3ba5a5a40c2d03b RENAME TO IDX_B3BA5A5A57B7763A'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT IF EXISTS "_ProductConstructeurs_B_fkey"'); + $this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT IF EXISTS "_ProductConstructeurs_A_fkey"'); $this->addSql('ALTER TABLE _productconstructeurs ALTER A TYPE VARCHAR(36)'); $this->addSql('ALTER TABLE _productconstructeurs ALTER B TYPE VARCHAR(36)'); + // Clean orphaned relations before re-adding foreign keys. + $this->addSql('DELETE FROM _productconstructeurs WHERE A IS NULL OR B IS NULL'); + $this->addSql('DELETE FROM _productconstructeurs pc WHERE NOT EXISTS (SELECT 1 FROM products p WHERE p.id = pc.A)'); + $this->addSql('DELETE FROM _productconstructeurs pc WHERE NOT EXISTS (SELECT 1 FROM constructeurs c WHERE c.id = pc.B)'); $this->addSql('ALTER TABLE _productconstructeurs ADD CONSTRAINT FK_CF7403FCD3D99E8B FOREIGN KEY (A) REFERENCES products (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _productconstructeurs ADD CONSTRAINT FK_CF7403FC4AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE'); $this->addSql('ALTER TABLE _productconstructeurs ADD PRIMARY KEY (A, B)'); - $this->addSql('ALTER INDEX idx_66f61802e8b7be43 RENAME TO IDX_CF7403FCD3D99E8B'); - $this->addSql('ALTER INDEX _productconstructeurs_b_index RENAME TO IDX_CF7403FC4AD0CF31'); - $this->addSql('DROP INDEX uniq_profiles_email'); - $this->addSql('ALTER INDEX idx_96958790158582c3 RENAME TO IDX_969587902F024C2'); - $this->addSql('ALTER INDEX idx_96958790df92e79b RENAME TO IDX_96958790CC8A4CEE'); - $this->addSql('ALTER INDEX idx_f609e59e158582c3 RENAME TO IDX_F609E59E2F024C2'); - $this->addSql('ALTER INDEX idx_f609e59e4ca601c8 RENAME TO IDX_F609E59E169F1CF6'); - $this->addSql('ALTER INDEX idx_29a51f98158582c3 RENAME TO IDX_29A51F982F024C2'); - $this->addSql('ALTER INDEX idx_29a51f9840c2d03b RENAME TO IDX_29A51F9857B7763A'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_66f61802e8b7be43') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_66f61802e8b7be43 RENAME TO IDX_CF7403FCD3D99E8B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('_productconstructeurs_b_index') IS NOT NULL THEN + EXECUTE 'ALTER INDEX _productconstructeurs_b_index RENAME TO IDX_CF7403FC4AD0CF31'; + END IF; +END $$; +SQL +); + $this->addSql('DROP INDEX IF EXISTS uniq_profiles_email'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_96958790158582c3') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_96958790158582c3 RENAME TO IDX_969587902F024C2'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_96958790df92e79b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_96958790df92e79b RENAME TO IDX_96958790CC8A4CEE'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f609e59e158582c3') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f609e59e158582c3 RENAME TO IDX_F609E59E2F024C2'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f609e59e4ca601c8') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f609e59e4ca601c8 RENAME TO IDX_F609E59E169F1CF6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_29a51f98158582c3') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_29a51f98158582c3 RENAME TO IDX_29A51F982F024C2'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_29a51f9840c2d03b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_29a51f9840c2d03b RENAME TO IDX_29A51F9857B7763A'; + END IF; +END $$; +SQL +); } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT FK_60760125D3D99E8B'); - $this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT FK_607601254AD0CF31'); - $this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT _ComposantConstructeurs_pkey'); + $this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT IF EXISTS FK_60760125D3D99E8B'); + $this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT IF EXISTS FK_607601254AD0CF31'); + $this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT IF EXISTS _ComposantConstructeurs_pkey'); $this->addSql('ALTER TABLE _ComposantConstructeurs ALTER a TYPE TEXT'); $this->addSql('ALTER TABLE _ComposantConstructeurs ALTER b TYPE TEXT'); $this->addSql('ALTER TABLE _ComposantConstructeurs ADD CONSTRAINT "_ComposantConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES composants (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE _ComposantConstructeurs ADD CONSTRAINT "_ComposantConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER INDEX idx_607601254ad0cf31 RENAME TO "_ComposantConstructeurs_B_index"'); - $this->addSql('ALTER INDEX idx_60760125d3d99e8b RENAME TO IDX_5B97D813E8B7BE43'); - $this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT FK_E6A040CCD3D99E8B'); - $this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT FK_E6A040CC4AD0CF31'); - $this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT _MachineConstructeurs_pkey'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_607601254ad0cf31') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_607601254ad0cf31 RENAME TO "_ComposantConstructeurs_B_index"'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_60760125d3d99e8b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_60760125d3d99e8b RENAME TO IDX_5B97D813E8B7BE43'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT IF EXISTS FK_E6A040CCD3D99E8B'); + $this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT IF EXISTS FK_E6A040CC4AD0CF31'); + $this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT IF EXISTS _MachineConstructeurs_pkey'); $this->addSql('ALTER TABLE _MachineConstructeurs ALTER a TYPE TEXT'); $this->addSql('ALTER TABLE _MachineConstructeurs ALTER b TYPE TEXT'); $this->addSql('ALTER TABLE _MachineConstructeurs ADD CONSTRAINT "_MachineConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE _MachineConstructeurs ADD CONSTRAINT "_MachineConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES machines (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER INDEX idx_e6a040cc4ad0cf31 RENAME TO "_MachineConstructeurs_B_index"'); - $this->addSql('ALTER INDEX idx_e6a040ccd3d99e8b RENAME TO IDX_4F225B32E8B7BE43'); - $this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT FK_E94732E5D3D99E8B'); - $this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT FK_E94732E54AD0CF31'); - $this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT _PieceConstructeurs_pkey'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_e6a040cc4ad0cf31') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_e6a040cc4ad0cf31 RENAME TO "_MachineConstructeurs_B_index"'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_e6a040ccd3d99e8b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_e6a040ccd3d99e8b RENAME TO IDX_4F225B32E8B7BE43'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT IF EXISTS FK_E94732E5D3D99E8B'); + $this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT IF EXISTS FK_E94732E54AD0CF31'); + $this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT IF EXISTS _PieceConstructeurs_pkey'); $this->addSql('ALTER TABLE _PieceConstructeurs ALTER a TYPE TEXT'); $this->addSql('ALTER TABLE _PieceConstructeurs ALTER b TYPE TEXT'); $this->addSql('ALTER TABLE _PieceConstructeurs ADD CONSTRAINT "_PieceConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES pieces (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE _PieceConstructeurs ADD CONSTRAINT "_PieceConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER INDEX idx_e94732e54ad0cf31 RENAME TO "_PieceConstructeurs_B_index"'); - $this->addSql('ALTER INDEX idx_e94732e5d3d99e8b RENAME TO IDX_77FC120E8B7BE43'); - $this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT FK_CF7403FCD3D99E8B'); - $this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT FK_CF7403FC4AD0CF31'); - $this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT _ProductConstructeurs_pkey'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_e94732e54ad0cf31') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_e94732e54ad0cf31 RENAME TO "_PieceConstructeurs_B_index"'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_e94732e5d3d99e8b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_e94732e5d3d99e8b RENAME TO IDX_77FC120E8B7BE43'; + END IF; +END $$; +SQL +); + $this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT IF EXISTS FK_CF7403FCD3D99E8B'); + $this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT IF EXISTS FK_CF7403FC4AD0CF31'); + $this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT IF EXISTS _ProductConstructeurs_pkey'); $this->addSql('ALTER TABLE _ProductConstructeurs ALTER a TYPE TEXT'); $this->addSql('ALTER TABLE _ProductConstructeurs ALTER b TYPE TEXT'); $this->addSql('ALTER TABLE _ProductConstructeurs ADD CONSTRAINT "_ProductConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES products (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE _ProductConstructeurs ADD CONSTRAINT "_ProductConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER INDEX idx_cf7403fc4ad0cf31 RENAME TO "_ProductConstructeurs_B_index"'); - $this->addSql('ALTER INDEX idx_cf7403fcd3d99e8b RENAME TO IDX_66F61802E8B7BE43'); - $this->addSql('ALTER INDEX idx_f95a319936799605 RENAME TO IDX_F95A3199A3FDB2A7'); - $this->addSql('ALTER INDEX idx_f95a3199cc8a4cee RENAME TO IDX_F95A3199DF92E79B'); - $this->addSql('ALTER INDEX idx_6b64d7ff345ee564 RENAME TO IDX_6B64D7FFA1DAC1C6'); - $this->addSql('ALTER INDEX idx_6b64d7ff5c4a705f RENAME TO IDX_6B64D7FF6736D61'); - $this->addSql('ALTER INDEX idx_6b64d7ff633ec4fd RENAME TO IDX_6B64D7FFF6BAE05F'); - $this->addSql('ALTER INDEX idx_6b64d7ff3c6a9d1 RENAME TO IDX_6B64D7FF96428D73'); - $this->addSql('ALTER INDEX idx_6b64d7ff36799605 RENAME TO IDX_6B64D7FFA3FDB2A7'); - $this->addSql('ALTER INDEX idx_4a48378c57b7763a RENAME TO IDX_4A48378C40C2D03B'); - $this->addSql('ALTER INDEX idx_4a48378c2f024c2 RENAME TO IDX_4A48378C158582C3'); - $this->addSql('ALTER INDEX idx_4a48378c169f1cf6 RENAME TO IDX_4A48378C4CA601C8'); - $this->addSql('ALTER INDEX idx_4a48378ccc8a4cee RENAME TO IDX_4A48378CDF92E79B'); - $this->addSql('ALTER INDEX idx_a2b07288345ee564 RENAME TO IDX_A2B07288A1DAC1C6'); - $this->addSql('ALTER INDEX idx_a2b07288633ec4fd RENAME TO IDX_A2B07288F6BAE05F'); - $this->addSql('ALTER INDEX idx_a2b072886973a4fd RENAME TO IDX_A2B07288FCF7805F'); - $this->addSql('ALTER INDEX idx_a2b072883c6a9d1 RENAME TO IDX_A2B0728896428D73'); - $this->addSql('ALTER INDEX idx_a2b0728836799605 RENAME TO IDX_A2B07288A3FDB2A7'); - $this->addSql('ALTER INDEX idx_528efe19345ee564 RENAME TO IDX_528EFE19A1DAC1C6'); - $this->addSql('ALTER INDEX idx_528efe19633ec4fd RENAME TO IDX_528EFE19F6BAE05F'); - $this->addSql('ALTER INDEX idx_528efe19ef6cf34b RENAME TO IDX_528EFE197D44D2DF'); - $this->addSql('ALTER INDEX idx_528efe19c44b383c RENAME TO IDX_528EFE19BCCED9E3'); - $this->addSql('ALTER INDEX idx_62941615ef6cf34b RENAME TO IDX_629416157D44D2DF'); - $this->addSql('ALTER INDEX idx_62941615633ec4fd RENAME TO IDX_62941615F6BAE05F'); - $this->addSql('ALTER INDEX idx_629416153c6a9d1 RENAME TO IDX_6294161596428D73'); - $this->addSql('ALTER INDEX idx_62941615f957d314 RENAME TO IDX_6294161532C54AAF'); - $this->addSql('ALTER INDEX idx_8cc32259633ec4fd RENAME TO "machine_product_links_machineId_idx"'); - $this->addSql('ALTER INDEX idx_8cc3225936799605 RENAME TO "machine_product_links_productId_idx"'); - $this->addSql('ALTER INDEX idx_8cc32259ef6cf34b RENAME TO IDX_8CC322597D44D2DF'); - $this->addSql('ALTER INDEX idx_8cc32259b590b209 RENAME TO IDX_8CC32259357FDBFF'); - $this->addSql('ALTER INDEX idx_8cc32259a63ac5dc RENAME TO IDX_8CC32259BCD7DAD6'); - $this->addSql('ALTER INDEX idx_8cc32259937a1d7c RENAME TO IDX_8CC3225987CEB33F'); - $this->addSql('ALTER INDEX idx_f1ce8ded2f024c2 RENAME TO IDX_F1CE8DED158582C3'); - $this->addSql('ALTER INDEX idx_f1ce8ded6973a4fd RENAME TO IDX_F1CE8DEDFCF7805F'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_cf7403fc4ad0cf31') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_cf7403fc4ad0cf31 RENAME TO "_ProductConstructeurs_B_index"'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_cf7403fcd3d99e8b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_cf7403fcd3d99e8b RENAME TO IDX_66F61802E8B7BE43'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f95a319936799605') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f95a319936799605 RENAME TO IDX_F95A3199A3FDB2A7'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f95a3199cc8a4cee') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f95a3199cc8a4cee RENAME TO IDX_F95A3199DF92E79B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff345ee564') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff345ee564 RENAME TO IDX_6B64D7FFA1DAC1C6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff5c4a705f') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff5c4a705f RENAME TO IDX_6B64D7FF6736D61'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff633ec4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff633ec4fd RENAME TO IDX_6B64D7FFF6BAE05F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff3c6a9d1') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff3c6a9d1 RENAME TO IDX_6B64D7FF96428D73'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_6b64d7ff36799605') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_6b64d7ff36799605 RENAME TO IDX_6B64D7FFA3FDB2A7'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378c57b7763a') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378c57b7763a RENAME TO IDX_4A48378C40C2D03B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378c2f024c2') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378c2f024c2 RENAME TO IDX_4A48378C158582C3'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378c169f1cf6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378c169f1cf6 RENAME TO IDX_4A48378C4CA601C8'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_4a48378ccc8a4cee') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_4a48378ccc8a4cee RENAME TO IDX_4A48378CDF92E79B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b07288345ee564') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b07288345ee564 RENAME TO IDX_A2B07288A1DAC1C6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b07288633ec4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b07288633ec4fd RENAME TO IDX_A2B07288F6BAE05F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b072886973a4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b072886973a4fd RENAME TO IDX_A2B07288FCF7805F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b072883c6a9d1') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b072883c6a9d1 RENAME TO IDX_A2B0728896428D73'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_a2b0728836799605') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_a2b0728836799605 RENAME TO IDX_A2B07288A3FDB2A7'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19345ee564') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19345ee564 RENAME TO IDX_528EFE19A1DAC1C6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19633ec4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19633ec4fd RENAME TO IDX_528EFE19F6BAE05F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19ef6cf34b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19ef6cf34b RENAME TO IDX_528EFE197D44D2DF'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_528efe19c44b383c') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_528efe19c44b383c RENAME TO IDX_528EFE19BCCED9E3'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_62941615ef6cf34b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_62941615ef6cf34b RENAME TO IDX_629416157D44D2DF'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_62941615633ec4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_62941615633ec4fd RENAME TO IDX_62941615F6BAE05F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_629416153c6a9d1') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_629416153c6a9d1 RENAME TO IDX_6294161596428D73'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_62941615f957d314') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_62941615f957d314 RENAME TO IDX_6294161532C54AAF'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259633ec4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259633ec4fd RENAME TO "machine_product_links_machineId_idx"'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc3225936799605') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc3225936799605 RENAME TO "machine_product_links_productId_idx"'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259ef6cf34b') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259ef6cf34b RENAME TO IDX_8CC322597D44D2DF'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259b590b209') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259b590b209 RENAME TO IDX_8CC32259357FDBFF'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259a63ac5dc') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259a63ac5dc RENAME TO IDX_8CC32259BCD7DAD6'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_8cc32259937a1d7c') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_8cc32259937a1d7c RENAME TO IDX_8CC3225987CEB33F'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f1ce8ded2f024c2') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f1ce8ded2f024c2 RENAME TO IDX_F1CE8DED158582C3'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f1ce8ded6973a4fd') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f1ce8ded6973a4fd RENAME TO IDX_F1CE8DEDFCF7805F'; + END IF; +END $$; +SQL +); $this->addSql('ALTER TABLE model_types ALTER id TYPE TEXT'); $this->addSql('ALTER TABLE model_types ALTER category TYPE VARCHAR'); $this->addSql('ALTER TABLE model_types ALTER componentskeleton TYPE JSONB'); @@ -187,15 +814,78 @@ final class Version20260125143939 extends AbstractMigration $this->addSql('ALTER TABLE model_types ALTER createdat SET DEFAULT CURRENT_TIMESTAMP'); $this->addSql('CREATE UNIQUE INDEX "ModelType_category_name_key" ON model_types (category, name)'); $this->addSql('CREATE UNIQUE INDEX "ModelType_code_key" ON model_types (code)'); - $this->addSql('ALTER INDEX idx_b92d7472169f1cf6 RENAME TO IDX_B92D74724CA601C8'); - $this->addSql('ALTER INDEX idx_b92d747236799605 RENAME TO IDX_B92D7472A3FDB2A7'); - $this->addSql('ALTER INDEX idx_b3ba5a5a57b7763a RENAME TO IDX_B3BA5A5A40C2D03B'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_b92d7472169f1cf6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_b92d7472169f1cf6 RENAME TO IDX_B92D74724CA601C8'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_b92d747236799605') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_b92d747236799605 RENAME TO IDX_B92D7472A3FDB2A7'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_b3ba5a5a57b7763a') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_b3ba5a5a57b7763a RENAME TO IDX_B3BA5A5A40C2D03B'; + END IF; +END $$; +SQL +); $this->addSql('CREATE UNIQUE INDEX uniq_profiles_email ON profiles (email)'); - $this->addSql('ALTER INDEX idx_969587902f024c2 RENAME TO IDX_96958790158582C3'); - $this->addSql('ALTER INDEX idx_96958790cc8a4cee RENAME TO IDX_96958790DF92E79B'); - $this->addSql('ALTER INDEX idx_f609e59e169f1cf6 RENAME TO IDX_F609E59E4CA601C8'); - $this->addSql('ALTER INDEX idx_f609e59e2f024c2 RENAME TO IDX_F609E59E158582C3'); - $this->addSql('ALTER INDEX idx_29a51f9857b7763a RENAME TO IDX_29A51F9840C2D03B'); - $this->addSql('ALTER INDEX idx_29a51f982f024c2 RENAME TO IDX_29A51F98158582C3'); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_969587902f024c2') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_969587902f024c2 RENAME TO IDX_96958790158582C3'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_96958790cc8a4cee') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_96958790cc8a4cee RENAME TO IDX_96958790DF92E79B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f609e59e169f1cf6') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f609e59e169f1cf6 RENAME TO IDX_F609E59E4CA601C8'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_f609e59e2f024c2') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_f609e59e2f024c2 RENAME TO IDX_F609E59E158582C3'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_29a51f9857b7763a') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_29a51f9857b7763a RENAME TO IDX_29A51F9840C2D03B'; + END IF; +END $$; +SQL +); + $this->addSql(<<<'SQL' +DO $$ BEGIN + IF to_regclass('idx_29a51f982f024c2') IS NOT NULL THEN + EXECUTE 'ALTER INDEX idx_29a51f982f024c2 RENAME TO IDX_29A51F98158582C3'; + END IF; +END $$; +SQL +); } } diff --git a/migrations/Version20260125170000.php b/migrations/Version20260125170000.php new file mode 100644 index 0000000..eaa728e --- /dev/null +++ b/migrations/Version20260125170000.php @@ -0,0 +1,41 @@ +addSql(<<<'SQL' + CREATE TABLE audit_logs ( + id VARCHAR(36) NOT NULL, + entityType VARCHAR(50) NOT NULL, + entityId VARCHAR(36) NOT NULL, + action VARCHAR(20) NOT NULL, + diff JSON DEFAULT NULL, + snapshot JSON DEFAULT NULL, + actorProfileId VARCHAR(36) DEFAULT NULL, + createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + PRIMARY KEY(id) + ) + SQL); + + $this->addSql('CREATE INDEX idx_audit_entity ON audit_logs (entityType, entityId)'); + $this->addSql('CREATE INDEX idx_audit_created_at ON audit_logs (createdAt)'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE audit_logs'); + } +} diff --git a/src/Controller/ComposantHistoryController.php b/src/Controller/ComposantHistoryController.php new file mode 100644 index 0000000..c221198 --- /dev/null +++ b/src/Controller/ComposantHistoryController.php @@ -0,0 +1,80 @@ +components->find($id); + if (!$component) { + return new JsonResponse( + ['message' => 'Composant introuvable.'], + Response::HTTP_NOT_FOUND, + ); + } + + $logs = $this->auditLogs->findEntityHistory('composant', $id, 200); + + $actorIds = array_values(array_unique(array_filter(array_map( + static fn ($log) => $log->getActorProfileId(), + $logs, + )))); + + $actorMap = []; + if ($actorIds !== []) { + $profiles = $this->profiles->findBy(['id' => $actorIds]); + foreach ($profiles as $profile) { + $label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName())); + if ($label === '') { + $label = $profile->getEmail() ?? $profile->getId(); + } + $actorMap[$profile->getId()] = $label; + } + } + + $items = array_map( + static function ($log) use ($actorMap) { + $actorId = $log->getActorProfileId(); + + return [ + 'id' => $log->getId(), + 'action' => $log->getAction(), + 'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM), + 'actor' => $actorId + ? [ + 'id' => $actorId, + 'label' => $actorMap[$actorId] ?? $actorId, + ] + : null, + 'diff' => $log->getDiff(), + 'snapshot' => $log->getSnapshot(), + ]; + }, + $logs, + ); + + return new JsonResponse([ + 'items' => array_values($items), + 'total' => count($items), + ]); + } +} + diff --git a/src/Controller/PieceHistoryController.php b/src/Controller/PieceHistoryController.php new file mode 100644 index 0000000..423be7f --- /dev/null +++ b/src/Controller/PieceHistoryController.php @@ -0,0 +1,80 @@ +pieces->find($id); + if (!$piece) { + return new JsonResponse( + ['message' => 'Pièce introuvable.'], + Response::HTTP_NOT_FOUND, + ); + } + + $logs = $this->auditLogs->findEntityHistory('piece', $id, 200); + + $actorIds = array_values(array_unique(array_filter(array_map( + static fn ($log) => $log->getActorProfileId(), + $logs, + )))); + + $actorMap = []; + if ($actorIds !== []) { + $profiles = $this->profiles->findBy(['id' => $actorIds]); + foreach ($profiles as $profile) { + $label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName())); + if ($label === '') { + $label = $profile->getEmail() ?? $profile->getId(); + } + $actorMap[$profile->getId()] = $label; + } + } + + $items = array_map( + static function ($log) use ($actorMap) { + $actorId = $log->getActorProfileId(); + + return [ + 'id' => $log->getId(), + 'action' => $log->getAction(), + 'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM), + 'actor' => $actorId + ? [ + 'id' => $actorId, + 'label' => $actorMap[$actorId] ?? $actorId, + ] + : null, + 'diff' => $log->getDiff(), + 'snapshot' => $log->getSnapshot(), + ]; + }, + $logs, + ); + + return new JsonResponse([ + 'items' => array_values($items), + 'total' => count($items), + ]); + } +} + diff --git a/src/Controller/ProductHistoryController.php b/src/Controller/ProductHistoryController.php new file mode 100644 index 0000000..7af455a --- /dev/null +++ b/src/Controller/ProductHistoryController.php @@ -0,0 +1,80 @@ +products->find($id); + if (!$product) { + return new JsonResponse( + ['message' => 'Produit introuvable.'], + Response::HTTP_NOT_FOUND, + ); + } + + $logs = $this->auditLogs->findEntityHistory('product', $id, 200); + + $actorIds = array_values(array_unique(array_filter(array_map( + static fn ($log) => $log->getActorProfileId(), + $logs, + )))); + + $actorMap = []; + if ($actorIds !== []) { + $profiles = $this->profiles->findBy(['id' => $actorIds]); + foreach ($profiles as $profile) { + $label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName())); + if ($label === '') { + $label = $profile->getEmail() ?? $profile->getId(); + } + $actorMap[$profile->getId()] = $label; + } + } + + $items = array_map( + static function ($log) use ($actorMap) { + $actorId = $log->getActorProfileId(); + + return [ + 'id' => $log->getId(), + 'action' => $log->getAction(), + 'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM), + 'actor' => $actorId + ? [ + 'id' => $actorId, + 'label' => $actorMap[$actorId] ?? $actorId, + ] + : null, + 'diff' => $log->getDiff(), + 'snapshot' => $log->getSnapshot(), + ]; + }, + $logs, + ); + + return new JsonResponse([ + 'items' => array_values($items), + 'total' => count($items), + ]); + } +} + diff --git a/src/Entity/AuditLog.php b/src/Entity/AuditLog.php new file mode 100644 index 0000000..de5d5e5 --- /dev/null +++ b/src/Entity/AuditLog.php @@ -0,0 +1,117 @@ +entityType = $entityType; + $this->entityId = $entityId; + $this->action = $action; + $this->diff = $diff; + $this->snapshot = $snapshot; + $this->actorProfileId = $actorProfileId; + } + + #[ORM\PrePersist] + public function initializeAuditLog(): void + { + if (!isset($this->createdAt)) { + $this->createdAt = new DateTimeImmutable(); + } + + if ($this->id === null) { + $this->id = $this->generateCuid(); + } + } + + public function getId(): ?string + { + return $this->id; + } + + public function getEntityType(): string + { + return $this->entityType; + } + + public function getEntityId(): string + { + return $this->entityId; + } + + public function getAction(): string + { + return $this->action; + } + + public function getDiff(): ?array + { + return $this->diff; + } + + public function getSnapshot(): ?array + { + return $this->snapshot; + } + + public function getActorProfileId(): ?string + { + return $this->actorProfileId; + } + + public function getCreatedAt(): DateTimeImmutable + { + return $this->createdAt; + } + + private function generateCuid(): string + { + // Keep the same lightweight CUID-like strategy used across the project. + return 'cl'.substr(strtolower(base_convert(bin2hex(random_bytes(12)), 16, 36)), 0, 24); + } +} diff --git a/src/EventSubscriber/ComposantAuditSubscriber.php b/src/EventSubscriber/ComposantAuditSubscriber.php new file mode 100644 index 0000000..100bcdb --- /dev/null +++ b/src/EventSubscriber/ComposantAuditSubscriber.php @@ -0,0 +1,300 @@ +getObjectManager(); + if (!$em instanceof EntityManagerInterface) { + return; + } + + $uow = $em->getUnitOfWork(); + $actorProfileId = $this->resolveActorProfileId(); + $pendingUpdates = []; + $pendingSnapshots = []; + $pendingComponents = []; + + foreach ($uow->getScheduledEntityInsertions() as $entity) { + if (!$entity instanceof Composant) { + continue; + } + + $diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity)); + $snapshot = $this->snapshotComposant($entity); + $this->persistAuditLog($em, new AuditLog('composant', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId)); + } + + foreach ($uow->getScheduledEntityUpdates() as $entity) { + if (!$entity instanceof Composant) { + continue; + } + + $componentId = (string) $entity->getId(); + if ($componentId === '') { + continue; + } + + $diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity)); + if ($diff !== []) { + $pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff); + $pendingSnapshots[$componentId] = $this->snapshotComposant($entity); + $pendingComponents[$componentId] = $entity; + } + } + + foreach ($uow->getScheduledEntityDeletions() as $entity) { + if (!$entity instanceof Composant) { + continue; + } + + $snapshot = $this->snapshotComposant($entity); + $this->persistAuditLog($em, new AuditLog('composant', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId)); + } + + foreach ($uow->getScheduledCollectionUpdates() as $collection) { + $this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents); + } + foreach ($uow->getScheduledCollectionDeletions() as $collection) { + $this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents); + } + + foreach ($pendingUpdates as $componentId => $diff) { + if ($diff === []) { + continue; + } + + $component = $pendingComponents[$componentId] ?? null; + if (!$component instanceof Composant) { + continue; + } + + $snapshot = $pendingSnapshots[$componentId] ?? $this->snapshotComposant($component); + $this->persistAuditLog($em, new AuditLog('composant', $componentId, 'update', $diff, $snapshot, $actorProfileId)); + } + } + + /** + * @param array> $pendingUpdates + * @param array> $pendingSnapshots + * @param array $pendingComponents + */ + private function collectCollectionUpdate( + object $collection, + array &$pendingUpdates, + array &$pendingSnapshots, + array &$pendingComponents, + ): void { + if (!$collection instanceof PersistentCollection) { + return; + } + + $owner = $collection->getOwner(); + if (!$owner instanceof Composant) { + return; + } + + $componentId = (string) $owner->getId(); + if ($componentId === '') { + return; + } + + $mapping = $collection->getMapping(); + $fieldName = $mapping['fieldName'] ?? null; + if ($fieldName !== 'constructeurs') { + return; + } + + $before = $this->normalizeCollection($collection->getSnapshot()); + $after = $this->normalizeCollection($collection->toArray()); + + if ($before === $after) { + return; + } + + $diff = [ + 'constructeurIds' => [ + 'from' => $before, + 'to' => $after, + ], + ]; + + $pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff); + $pendingSnapshots[$componentId] = $this->snapshotComposant($owner); + $pendingComponents[$componentId] = $owner; + } + + private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void + { + $uow = $em->getUnitOfWork(); + $log->initializeAuditLog(); + $em->persist($log); + + $meta = $em->getClassMetadata(AuditLog::class); + $uow->computeChangeSet($meta, $log); + } + + /** + * @param array $changeSet + * @return array + */ + private function buildDiffFromChangeSet(array $changeSet): array + { + $diff = []; + foreach ($changeSet as $field => [$oldValue, $newValue]) { + if ($field === 'updatedAt' || $field === 'createdAt') { + continue; + } + + $normalizedOld = $this->normalizeValue($oldValue); + $normalizedNew = $this->normalizeValue($newValue); + + if ($normalizedOld === $normalizedNew) { + continue; + } + + $diff[$field] = [ + 'from' => $normalizedOld, + 'to' => $normalizedNew, + ]; + } + + return $diff; + } + + private function snapshotComposant(Composant $component): array + { + return [ + 'id' => $component->getId(), + 'name' => $component->getName(), + 'reference' => $component->getReference(), + 'prix' => $component->getPrix(), + 'structure' => $component->getStructure(), + 'typeComposant' => $this->normalizeValue($component->getTypeComposant()), + 'product' => $this->normalizeValue($component->getProduct()), + 'constructeurIds' => $this->normalizeCollection($component->getConstructeurs()), + ]; + } + + /** + * @param iterable $items + * @return list + */ + private function normalizeCollection(iterable $items): array + { + $ids = []; + foreach ($items as $item) { + if (\is_object($item) && \method_exists($item, 'getId')) { + $id = $item->getId(); + if ($id !== null && $id !== '') { + $ids[] = (string) $id; + } + } + } + + sort($ids); + + return array_values(array_unique($ids)); + } + + private function normalizeValue(mixed $value): mixed + { + if ($value === null || \is_scalar($value)) { + return $value; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format(\DateTimeInterface::ATOM); + } + + if ($value instanceof ModelType) { + return [ + 'id' => $value->getId(), + 'name' => $value->getName(), + 'code' => $value->getCode(), + ]; + } + + if ($value instanceof Product) { + return [ + 'id' => $value->getId(), + 'name' => $value->getName(), + 'reference' => $value->getReference(), + ]; + } + + if ($value instanceof Collection) { + return $this->normalizeCollection($value); + } + + if (\is_object($value) && \method_exists($value, 'getId')) { + return (string) $value->getId(); + } + + if (\is_array($value)) { + return $value; + } + + return (string) $value; + } + + /** + * @param array $base + * @param array $extra + * @return array + */ + private function mergeDiffs(array $base, array $extra): array + { + foreach ($extra as $field => $change) { + $base[$field] = $change; + } + + return $base; + } + + private function resolveActorProfileId(): ?string + { + $session = $this->requestStack->getSession(); + if (!$session instanceof SessionInterface) { + return null; + } + + $profileId = $session->get('profileId'); + if (!$profileId) { + return null; + } + + return (string) $profileId; + } +} + diff --git a/src/EventSubscriber/PieceAuditSubscriber.php b/src/EventSubscriber/PieceAuditSubscriber.php new file mode 100644 index 0000000..c84bb37 --- /dev/null +++ b/src/EventSubscriber/PieceAuditSubscriber.php @@ -0,0 +1,300 @@ +getObjectManager(); + if (!$em instanceof EntityManagerInterface) { + return; + } + + $uow = $em->getUnitOfWork(); + $actorProfileId = $this->resolveActorProfileId(); + $pendingUpdates = []; + $pendingSnapshots = []; + $pendingPieces = []; + + foreach ($uow->getScheduledEntityInsertions() as $entity) { + if (!$entity instanceof Piece) { + continue; + } + + $diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity)); + $snapshot = $this->snapshotPiece($entity); + $this->persistAuditLog($em, new AuditLog('piece', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId)); + } + + foreach ($uow->getScheduledEntityUpdates() as $entity) { + if (!$entity instanceof Piece) { + continue; + } + + $pieceId = (string) $entity->getId(); + if ($pieceId === '') { + continue; + } + + $diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity)); + if ($diff !== []) { + $pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff); + $pendingSnapshots[$pieceId] = $this->snapshotPiece($entity); + $pendingPieces[$pieceId] = $entity; + } + } + + foreach ($uow->getScheduledEntityDeletions() as $entity) { + if (!$entity instanceof Piece) { + continue; + } + + $snapshot = $this->snapshotPiece($entity); + $this->persistAuditLog($em, new AuditLog('piece', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId)); + } + + foreach ($uow->getScheduledCollectionUpdates() as $collection) { + $this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces); + } + foreach ($uow->getScheduledCollectionDeletions() as $collection) { + $this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces); + } + + foreach ($pendingUpdates as $pieceId => $diff) { + if ($diff === []) { + continue; + } + + $piece = $pendingPieces[$pieceId] ?? null; + if (!$piece instanceof Piece) { + continue; + } + + $snapshot = $pendingSnapshots[$pieceId] ?? $this->snapshotPiece($piece); + $this->persistAuditLog($em, new AuditLog('piece', $pieceId, 'update', $diff, $snapshot, $actorProfileId)); + } + } + + /** + * @param array> $pendingUpdates + * @param array> $pendingSnapshots + * @param array $pendingPieces + */ + private function collectCollectionUpdate( + object $collection, + array &$pendingUpdates, + array &$pendingSnapshots, + array &$pendingPieces, + ): void { + if (!$collection instanceof PersistentCollection) { + return; + } + + $owner = $collection->getOwner(); + if (!$owner instanceof Piece) { + return; + } + + $pieceId = (string) $owner->getId(); + if ($pieceId === '') { + return; + } + + $mapping = $collection->getMapping(); + $fieldName = $mapping['fieldName'] ?? null; + if ($fieldName !== 'constructeurs') { + return; + } + + $before = $this->normalizeCollection($collection->getSnapshot()); + $after = $this->normalizeCollection($collection->toArray()); + + if ($before === $after) { + return; + } + + $diff = [ + 'constructeurIds' => [ + 'from' => $before, + 'to' => $after, + ], + ]; + + $pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff); + $pendingSnapshots[$pieceId] = $this->snapshotPiece($owner); + $pendingPieces[$pieceId] = $owner; + } + + private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void + { + $uow = $em->getUnitOfWork(); + $log->initializeAuditLog(); + $em->persist($log); + + $meta = $em->getClassMetadata(AuditLog::class); + $uow->computeChangeSet($meta, $log); + } + + /** + * @param array $changeSet + * @return array + */ + private function buildDiffFromChangeSet(array $changeSet): array + { + $diff = []; + foreach ($changeSet as $field => [$oldValue, $newValue]) { + if ($field === 'updatedAt' || $field === 'createdAt') { + continue; + } + + $normalizedOld = $this->normalizeValue($oldValue); + $normalizedNew = $this->normalizeValue($newValue); + + if ($normalizedOld === $normalizedNew) { + continue; + } + + $diff[$field] = [ + 'from' => $normalizedOld, + 'to' => $normalizedNew, + ]; + } + + return $diff; + } + + private function snapshotPiece(Piece $piece): array + { + return [ + 'id' => $piece->getId(), + 'name' => $piece->getName(), + 'reference' => $piece->getReference(), + 'prix' => $piece->getPrix(), + 'typePiece' => $this->normalizeValue($piece->getTypePiece()), + 'product' => $this->normalizeValue($piece->getProduct()), + 'productIds' => $piece->getProductIds(), + 'constructeurIds' => $this->normalizeCollection($piece->getConstructeurs()), + ]; + } + + /** + * @param iterable $items + * @return list + */ + private function normalizeCollection(iterable $items): array + { + $ids = []; + foreach ($items as $item) { + if (\is_object($item) && \method_exists($item, 'getId')) { + $id = $item->getId(); + if ($id !== null && $id !== '') { + $ids[] = (string) $id; + } + } + } + + sort($ids); + + return array_values(array_unique($ids)); + } + + private function normalizeValue(mixed $value): mixed + { + if ($value === null || \is_scalar($value)) { + return $value; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format(\DateTimeInterface::ATOM); + } + + if ($value instanceof ModelType) { + return [ + 'id' => $value->getId(), + 'name' => $value->getName(), + 'code' => $value->getCode(), + ]; + } + + if ($value instanceof Product) { + return [ + 'id' => $value->getId(), + 'name' => $value->getName(), + 'reference' => $value->getReference(), + ]; + } + + if ($value instanceof Collection) { + return $this->normalizeCollection($value); + } + + if (\is_object($value) && \method_exists($value, 'getId')) { + return (string) $value->getId(); + } + + if (\is_array($value)) { + return $value; + } + + return (string) $value; + } + + /** + * @param array $base + * @param array $extra + * @return array + */ + private function mergeDiffs(array $base, array $extra): array + { + foreach ($extra as $field => $change) { + $base[$field] = $change; + } + + return $base; + } + + private function resolveActorProfileId(): ?string + { + $session = $this->requestStack->getSession(); + if (!$session instanceof SessionInterface) { + return null; + } + + $profileId = $session->get('profileId'); + if (!$profileId) { + return null; + } + + return (string) $profileId; + } +} + diff --git a/src/EventSubscriber/ProductAuditSubscriber.php b/src/EventSubscriber/ProductAuditSubscriber.php new file mode 100644 index 0000000..76e2aca --- /dev/null +++ b/src/EventSubscriber/ProductAuditSubscriber.php @@ -0,0 +1,298 @@ +getObjectManager(); + if (!$em instanceof EntityManagerInterface) { + return; + } + + $uow = $em->getUnitOfWork(); + $actorProfileId = $this->resolveActorProfileId(); + $pendingUpdates = []; + $pendingSnapshots = []; + $pendingProducts = []; + + foreach ($uow->getScheduledEntityInsertions() as $entity) { + if (!$entity instanceof Product) { + continue; + } + + $diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity)); + $snapshot = $this->snapshotProduct($entity); + $this->persistAuditLog($em, new AuditLog('product', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId)); + } + + foreach ($uow->getScheduledEntityUpdates() as $entity) { + if (!$entity instanceof Product) { + continue; + } + + $productId = (string) $entity->getId(); + if ($productId === '') { + continue; + } + + $diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity)); + if ($diff !== []) { + $pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff); + $pendingSnapshots[$productId] = $this->snapshotProduct($entity); + $pendingProducts[$productId] = $entity; + } + } + + foreach ($uow->getScheduledEntityDeletions() as $entity) { + if (!$entity instanceof Product) { + continue; + } + + $snapshot = $this->snapshotProduct($entity); + $this->persistAuditLog($em, new AuditLog('product', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId)); + } + + // Capture constructeur collection updates, which are not included in the change set. + foreach ($uow->getScheduledCollectionUpdates() as $collection) { + $this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts); + } + foreach ($uow->getScheduledCollectionDeletions() as $collection) { + $this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts); + } + + foreach ($pendingUpdates as $productId => $diff) { + if ($diff === []) { + continue; + } + + $product = $pendingProducts[$productId] ?? null; + if (!$product instanceof Product) { + continue; + } + + $snapshot = $pendingSnapshots[$productId] ?? $this->snapshotProduct($product); + $this->persistAuditLog($em, new AuditLog('product', $productId, 'update', $diff, $snapshot, $actorProfileId)); + } + } + + /** + * @param array> $pendingUpdates + * @param array> $pendingSnapshots + * @param array $pendingProducts + */ + private function collectCollectionUpdate( + object $collection, + array &$pendingUpdates, + array &$pendingSnapshots, + array &$pendingProducts, + ): void { + if (!$collection instanceof PersistentCollection) { + return; + } + + $owner = $collection->getOwner(); + if (!$owner instanceof Product) { + return; + } + + $productId = (string) $owner->getId(); + if ($productId === '') { + return; + } + + $mapping = $collection->getMapping(); + $fieldName = $mapping['fieldName'] ?? null; + if ($fieldName !== 'constructeurs') { + return; + } + + $before = $this->normalizeCollection($collection->getSnapshot()); + $after = $this->normalizeCollection($collection->toArray()); + + if ($before === $after) { + return; + } + + $diff = [ + 'constructeurIds' => [ + 'from' => $before, + 'to' => $after, + ], + ]; + + $pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff); + $pendingSnapshots[$productId] = $this->snapshotProduct($owner); + $pendingProducts[$productId] = $owner; + } + + private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void + { + $uow = $em->getUnitOfWork(); + // Ensure identifiers and timestamps are set even when persisting during onFlush. + $log->initializeAuditLog(); + $em->persist($log); + + $meta = $em->getClassMetadata(AuditLog::class); + $uow->computeChangeSet($meta, $log); + } + + /** + * @param array $changeSet + * @return array + */ + private function buildDiffFromChangeSet(array $changeSet): array + { + $diff = []; + foreach ($changeSet as $field => [$oldValue, $newValue]) { + // Skip noisy timestamps managed automatically. + if ($field === 'updatedAt' || $field === 'createdAt') { + continue; + } + + $normalizedOld = $this->normalizeValue($oldValue); + $normalizedNew = $this->normalizeValue($newValue); + + if ($normalizedOld === $normalizedNew) { + continue; + } + + $diff[$field] = [ + 'from' => $normalizedOld, + 'to' => $normalizedNew, + ]; + } + + return $diff; + } + + private function snapshotProduct(Product $product): array + { + return [ + 'id' => $product->getId(), + 'name' => $product->getName(), + 'reference' => $product->getReference(), + 'supplierPrice' => $product->getSupplierPrice(), + 'typeProduct' => $this->normalizeValue($product->getTypeProduct()), + 'constructeurIds' => $this->normalizeCollection($product->getConstructeurs()), + ]; + } + + /** + * @param array $base + * @param array $extra + * @return array + */ + private function mergeDiffs(array $base, array $extra): array + { + foreach ($extra as $field => $change) { + $base[$field] = $change; + } + + return $base; + } + + /** + * @param iterable $items + * @return list + */ + private function normalizeCollection(iterable $items): array + { + $ids = []; + foreach ($items as $item) { + if (\is_object($item) && \method_exists($item, 'getId')) { + $id = $item->getId(); + if ($id !== null && $id !== '') { + $ids[] = (string) $id; + } + } + } + + sort($ids); + + return array_values(array_unique($ids)); + } + + private function normalizeValue(mixed $value): mixed + { + if ($value === null || \is_scalar($value)) { + return $value; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format(\DateTimeInterface::ATOM); + } + + if ($value instanceof ModelType) { + return [ + 'id' => $value->getId(), + 'name' => $value->getName(), + 'code' => $value->getCode(), + ]; + } + + if ($value instanceof Collection) { + return $this->normalizeCollection($value); + } + + if (\is_object($value) && \method_exists($value, 'getId')) { + return (string) $value->getId(); + } + + if (\is_array($value)) { + return $value; + } + + return (string) $value; + } + + private function resolveActorProfileId(): ?string + { + $session = $this->requestStack->getSession(); + if (!$session instanceof SessionInterface) { + return null; + } + + $profileId = $session->get('profileId'); + if (!$profileId) { + return null; + } + + return (string) $profileId; + } +} diff --git a/src/Repository/AuditLogRepository.php b/src/Repository/AuditLogRepository.php new file mode 100644 index 0000000..dfdb101 --- /dev/null +++ b/src/Repository/AuditLogRepository.php @@ -0,0 +1,37 @@ + + */ +final class AuditLogRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, AuditLog::class); + } + + /** + * @return list + */ + public function findEntityHistory(string $entityType, string $entityId, int $limit = 100): array + { + return $this->createQueryBuilder('a') + ->andWhere('a.entityType = :entityType') + ->andWhere('a.entityId = :entityId') + ->setParameter('entityType', $entityType) + ->setParameter('entityId', $entityId) + ->orderBy('a.createdAt', 'DESC') + ->setMaxResults($limit) + ->getQuery() + ->getResult(); + } +} +