f9fec3e908
Auto Tag Develop / tag (push) Successful in 12s
## ERP-149 — Récupération des codes IDTF (transport routier) > ⚠️ MR **empilée** sur `feat/erp-39-qualimat-sync` (PR #99), elle-même sur la PR #97. Ordre de merge : **#97 → #99 → celle-ci**. Les bases se recibleront automatiquement. Commande console `app:idtf:sync` : récupère l'export Excel des codes IDTF (régimes de nettoyage transport) depuis icrt-idtf.com, le parse et synchronise une table référentielle. Scope **road** ; discriminant `schema` road/water conservé pour un futur fluvial. ### Contenu - **Migration** `Version20260612160000` (namespace racine) : `idtf_product` + `idtf_sync_log`, `COMMENT ON COLUMN` sur chaque colonne, unique `(schema, idtf_number)`, `cas_numbers` JSONB, soft-delete. - **`IdtfSheetParser`** : parsing **pur** d'une matrice (sans dépendance PhpSpreadsheet) — détection **dynamique** de la ligne d'en-tête, mapping par libellé normalisé (résiste au réordonnancement), CAS split sur `;`, date `dd-mm-yyyy` → ISO + `checkdate`, skip des lignes non numériques. - **`SyncIdtfCommand`** : options `--schema` (road|water) / `--file` / `--dry-run`. POST avec les **10 `fields[]` explicites** (le piège `fields[]=all` ne sort que 6 colonnes) → export 11 colonnes ; garde-fou content-type/signature ZIP. Upsert DBAL transactionnel + soft-delete + journal. - Cible `make idtf-sync`. ### Tests - Unitaires (`IdtfSheetParser` : en-tête dynamique, mapping, CAS, date, skip, ordre de colonnes). - Fonctionnels de la commande via un `.xlsx` **généré** par PhpSpreadsheet (parsing → upsert → journal → soft-delete + schéma invalide rejeté). - Suite complète **608** verte (hors flaky JWT connu). `ColumnsHaveSqlCommentTest` ✅. - Bout-en-bout réel : sync de **687 codes IDTF** (road). ### Décisions - Migration **namespace racine** (convention réelle ; pas de FK cross-module). - **Aucun changement Composer** : `phpoffice/phpspreadsheet` était déjà une dépendance (^5.7) — le bump initial vers ^5.8 a été reverté. - Réutilise `framework.http_client` activé par la PR QUALIMAT (raison de l'empilement sur #99). --------- Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #101 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
167 lines
8.8 KiB
YAML
167 lines
8.8 KiB
YAML
doctrine:
|
|
dbal:
|
|
# Deux connexions pointant sur le meme DSN : l'ORM utilise `default`,
|
|
# l'AuditLogWriter utilise `audit` pour ecrire hors de la transaction
|
|
# Doctrine et eviter tout entanglement transactionnel en batch.
|
|
default_connection: default
|
|
connections:
|
|
default:
|
|
url: '%env(resolve:DATABASE_URL)%'
|
|
profiling_collect_backtrace: '%kernel.debug%'
|
|
# Exclut certaines tables de toute operation de comparaison de
|
|
# schema (doctrine:schema:update, schema:validate, diff de
|
|
# migrations...). Ces tables n'ont volontairement aucune entite
|
|
# mappee :
|
|
# - `audit_log` : append-only via DBAL brut (AuditLogWriter) pour
|
|
# eviter la recursion du listener Doctrine.
|
|
# - `qualimat_carrier` / `qualimat_sync_log` : referentiel
|
|
# transporteurs synchronise en DBAL brut (upsert `ON CONFLICT`)
|
|
# par `app:qualimat:sync`, hors ORM.
|
|
# - `idtf_product` / `idtf_sync_log` : referentiel codes IDTF
|
|
# synchronise en DBAL brut par `app:idtf:sync`, hors ORM.
|
|
# Sans ce filtre, schema:update les considere comme "orphelines" et
|
|
# genere un `DROP TABLE` qui casse la base de test apres chaque
|
|
# `make test-db-setup` (la migration les a creees, schema:update les
|
|
# supprime juste apres). Creation / suppression restent pilotees par
|
|
# les migrations (audit_log : Version20260420202749 ; qualimat :
|
|
# Version20260612150000 ; idtf : Version20260612160000).
|
|
schema_filter: '~^(?!(?:audit_log|qualimat_carrier|qualimat_sync_log|idtf_product|idtf_sync_log)$).+~'
|
|
audit:
|
|
url: '%env(resolve:DATABASE_URL)%'
|
|
orm:
|
|
validate_xml_mapping: true
|
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
|
identity_generation_preferences:
|
|
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
|
auto_mapping: true
|
|
# Mapping contrat DDD → classe concrete. Permet au module Core de
|
|
# referencer `SiteInterface` dans ses ORM mappings (User) sans importer
|
|
# la classe concrete du module Sites. Pattern officiel Doctrine pour
|
|
# les bounded contexts, remplace l'ancien import direct
|
|
# `App\Module\Sites\Domain\Entity\Site` dans User.php.
|
|
resolve_target_entities:
|
|
App\Shared\Domain\Contract\SiteInterface: App\Module\Sites\Domain\Entity\Site
|
|
# Cible des ManyToOne created_by / updated_by du TimestampableBlamableTrait.
|
|
# Permet a Shared de referencer UserInterface dans ses ORM mappings sans
|
|
# importer la classe concrete du module Core (cf. spec-back M0 § 2.8).
|
|
Symfony\Component\Security\Core\User\UserInterface: App\Module\Core\Domain\Entity\User
|
|
# Cible des ManyToMany Client.categories / ClientAddress.categories (M1).
|
|
# Permet au module Commercial de referencer une Category via le contrat
|
|
# Shared sans importer la classe concrete du module Catalog (regle n°1).
|
|
App\Shared\Domain\Contract\CategoryInterface: App\Module\Catalog\Domain\Entity\Category
|
|
mappings:
|
|
# Mapping des entites techniques partagees (src/Shared/Domain/Entity).
|
|
# Premier occupant : UploadedDocument (infra upload generique ERP-154).
|
|
# Necessaire car les entites Shared ne sont pas couvertes par
|
|
# l'auto_mapping (qui ne cible que les bundles).
|
|
Shared:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/src/Shared/Domain/Entity'
|
|
prefix: 'App\Shared\Domain\Entity'
|
|
alias: Shared
|
|
Core:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/src/Module/Core/Domain/Entity'
|
|
prefix: 'App\Module\Core\Domain\Entity'
|
|
alias: Core
|
|
# Mapping inconditionnelle du module Sites : la structure DB
|
|
# existe meme si SitesModule::class est retire de config/modules.php.
|
|
# L'activation fonctionnelle (ex: exposition des permissions, futurs
|
|
# endpoints API) passe exclusivement par config/modules.php.
|
|
Sites:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/src/Module/Sites/Domain/Entity'
|
|
prefix: 'App\Module\Sites\Domain\Entity'
|
|
alias: Sites
|
|
# Mapping inconditionnel du module Catalog (meme logique que Sites) :
|
|
# la structure DB (category, category_type) existe meme si
|
|
# CatalogModule::class n'est pas encore wire dans config/modules.php
|
|
# (declaration du module = ticket 0.5 / ERP-47). L'ORM doit connaitre
|
|
# les entites pour que le schema soit en phase ; l'activation
|
|
# fonctionnelle passe exclusivement par config/modules.php.
|
|
Catalog:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/src/Module/Catalog/Domain/Entity'
|
|
prefix: 'App\Module\Catalog\Domain\Entity'
|
|
alias: Catalog
|
|
# Mapping inconditionnel du module Commercial (meme logique que Catalog) :
|
|
# les tables (client, sous-collections, referentiels comptables) creees
|
|
# par la migration M1 (Version20260601000000) doivent etre connues de
|
|
# l'ORM. L'activation fonctionnelle passe par config/modules.php.
|
|
Commercial:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/src/Module/Commercial/Domain/Entity'
|
|
prefix: 'App\Module\Commercial\Domain\Entity'
|
|
alias: Commercial
|
|
# Mapping inconditionnel du module Technique (meme logique que Commercial) :
|
|
# les tables prestataires (provider + sous-collections + jointures M2M)
|
|
# creees par la migration M3 (Version20260612100000) doivent etre connues
|
|
# de l'ORM. L'activation fonctionnelle passe par config/modules.php.
|
|
Technique:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/src/Module/Technique/Domain/Entity'
|
|
prefix: 'App\Module\Technique\Domain\Entity'
|
|
alias: Technique
|
|
controller_resolver:
|
|
auto_mapping: false
|
|
|
|
when@test:
|
|
doctrine:
|
|
dbal:
|
|
# Le suffixe "_test" doit etre propage aux deux connexions : l'ORM
|
|
# l'herite via `default`, l'AuditLogWriter via `audit`. Sans cela,
|
|
# la connexion `audit` ecrirait dans la base dev pendant que l'ORM
|
|
# ecrit dans la base test — divergence invisible en apparence mais
|
|
# fatale pour les tests du journal d'audit.
|
|
#
|
|
# `idle_connection_ttl: 1` (au lieu du defaut 600s) : en test on
|
|
# reboote le kernel a chaque test. Sans TTL court, les connexions
|
|
# orphelines s'accumulent dans PG et on finit par saturer le pool
|
|
# (max_connections=100) sur une suite de 200+ tests qui utilisent
|
|
# 2 connexions chacun (default + audit).
|
|
connections:
|
|
default:
|
|
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
|
idle_connection_ttl: 1
|
|
audit:
|
|
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
|
idle_connection_ttl: 1
|
|
orm:
|
|
mappings:
|
|
# Entite fictive SiteAware utilisee uniquement en tests du
|
|
# module Sites (ticket 4). Le mapping n'est charge qu'en
|
|
# environnement test, donc aucun impact sur les schemas
|
|
# dev/prod. La table est creee a la volee par les tests
|
|
# d'integration (via `SchemaTool::createSchema`) dans le
|
|
# setUp de SiteScopedQueryExtensionTest.
|
|
TestFixtures:
|
|
type: attribute
|
|
is_bundle: false
|
|
dir: '%kernel.project_dir%/tests/Fixtures/SiteAware'
|
|
prefix: 'App\Tests\Fixtures\SiteAware'
|
|
alias: TestFixtures
|
|
|
|
when@prod:
|
|
doctrine:
|
|
orm:
|
|
query_cache_driver:
|
|
type: pool
|
|
pool: doctrine.system_cache_pool
|
|
result_cache_driver:
|
|
type: pool
|
|
pool: doctrine.result_cache_pool
|
|
|
|
framework:
|
|
cache:
|
|
pools:
|
|
doctrine.result_cache_pool:
|
|
adapter: cache.app
|
|
doctrine.system_cache_pool:
|
|
adapter: cache.system
|