feat : cycle de vie brouillon/validé du ticket de pesée (ERP-193)

Une pesée (bascule ou manuelle) s'enregistre désormais dès la validation de sa
modale, sans exiger la contrepartie ni l'immatriculation : le ticket naît
« brouillon » (status DRAFT, sans numéro). Le bouton « Valider » finalise quand
les 3 champs du haut (contrepartie + champ associé + immatriculation) ET les 2
pesées sont renseignés : attribution du numéro {siteCode}-TP-{NNNN} et passage
en VALIDATED, puis ouverture du bon de pesée PDF.

Back : counterparty_type/immatriculation/number nullables + colonne status
(migration racine), contraintes strictes déplacées en groupe de validation
finalize, opération PATCH /weighing_tickets/{id}/validate, numéro attribué à la
validation. Front : 4 champs en haut hors blocs, persistance immédiate des
pesées, écrans Ajouter/Modifier refondus, colonne Statut dans la liste, form à
plat pleine largeur. Tests back (lifecycle brouillon/validate) + front à jour.
This commit is contained in:
2026-06-24 15:13:12 +02:00
parent d5d7d2e2aa
commit 819ac5e608
20 changed files with 794 additions and 389 deletions
+91
View File
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* M5 — Tickets de pesee (ERP-193) : cycle de vie brouillon -> valide.
*
* Le metier peut desormais enregistrer une pesee (bascule ou manuelle) SANS avoir
* rempli la contrepartie ni l'immatriculation : le ticket est cree « brouillon »
* des la 1ere pesee, puis « valide » (numero attribue, status VALIDATED) quand les
* 3 champs requis (type + champ contrepartie + immatriculation) ET les 2 pesees
* sont renseignes.
*
* Schema impacte :
* - `counterparty_type`, `immatriculation`, `number` passent NULLABLE (un brouillon
* n'a encore ni contrepartie, ni immat, ni numero — le numero n'est attribue
* qu'a la validation pour eviter les trous de sequence). Les CHECK de branche
* chk_wt_*_branch tolerent deja un counterparty_type NULL (NULL <> 'X' = NULL,
* donc CHECK non viole).
* - nouvelle colonne `status` (DRAFT|VALIDATED). Les tickets EXISTANTS (crees sous
* l'ancien flux, donc complets) sont retro-marques VALIDATED ; le defaut des
* nouvelles lignes est DRAFT.
*
* Namespace racine `DoctrineMigrations` (et non modulaire) : la migration ALTER une
* table creee par la migration racine Version20260617150000. Doctrine Migrations
* 3.x trie par FQCN alphabetique entre namespaces -> une migration modulaire
* `App\Module\...` passerait AVANT la racine sur base vide (make db-reset) et
* tenterait l'ALTER avant le CREATE. Le namespace racine garantit le tri par
* timestamp (regle ABSOLUE n°11, cf. Version20260617170000 pour site.code).
*/
final class Version20260624100000 extends AbstractMigration
{
public function getDescription(): string
{
return 'ERP-193 : weighing_ticket brouillon/valide (counterparty_type/immatriculation/number nullable + colonne status).';
}
public function up(Schema $schema): void
{
// Brouillon : ni contrepartie, ni immat, ni numero tant que non valide.
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN counterparty_type DROP NOT NULL');
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN immatriculation DROP NOT NULL');
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN number DROP NOT NULL');
// Statut du cycle de vie. Colonne ajoutee nullable, retro-remplie a VALIDATED
// pour les tickets existants (complets), puis figee NOT NULL DEFAULT DRAFT.
$this->addSql('ALTER TABLE weighing_ticket ADD COLUMN status VARCHAR(12)');
$this->addSql("UPDATE weighing_ticket SET status = 'VALIDATED'");
$this->addSql("ALTER TABLE weighing_ticket ALTER COLUMN status SET DEFAULT 'DRAFT'");
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN status SET NOT NULL');
$this->addSql("ALTER TABLE weighing_ticket ADD CONSTRAINT chk_wt_status CHECK (status IN ('DRAFT','VALIDATED'))");
// Commentaires (regle ABSOLUE n°12).
$this->comment('weighing_ticket', 'status', "Cycle de vie : DRAFT (En attente, pesee enregistree sans contrepartie/immat) ou VALIDATED (Terminee, valide avec numero). Defaut DRAFT.");
$this->comment('weighing_ticket', 'number', "Numero {siteCode}-TP-{NNNN}, unique par site, immuable. NULL tant que le ticket est brouillon : attribue a la validation (RG-5.02, ERP-193).");
$this->comment('weighing_ticket', 'counterparty_type', "Contrepartie : CLIENT, FOURNISSEUR ou AUTRE (RG-5.03). NULL tant que brouillon ; requise a la validation. Pilote l'obligation client_id / supplier_id / other_label.");
$this->comment('weighing_ticket', 'immatriculation', "Plaque du vehicule, partagee entre pesee vide et plein (RG-5.01). NULL tant que brouillon ; requise a la validation. Masque XX-000-XX sauf plate_free_format.");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE weighing_ticket DROP CONSTRAINT IF EXISTS chk_wt_status');
$this->addSql('ALTER TABLE weighing_ticket DROP COLUMN IF EXISTS status');
// Restauration NOT NULL : echoue s'il subsiste des brouillons (number /
// counterparty_type / immatriculation NULL) — irreversible en presence de
// donnees brouillon, ce qui est attendu (le down sert au dev sur base saine).
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN number SET NOT NULL');
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN immatriculation SET NOT NULL');
$this->addSql('ALTER TABLE weighing_ticket ALTER COLUMN counterparty_type SET NOT NULL');
}
/**
* Pose un COMMENT ON COLUMN en dollar-quoting Postgres ($_$...$_$) pour eviter
* tout echappement d'apostrophes dans les descriptions.
*/
private function comment(string $table, string $column, string $description): void
{
$this->addSql(sprintf(
'COMMENT ON COLUMN %s.%s IS $_$%s$_$',
'"'.str_replace('"', '""', $table).'"',
'"'.str_replace('"', '""', $column).'"',
$description,
));
}
}