Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6d39cb187 | |||
| 864bc7e8e8 |
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
use App\Module\Catalog\CatalogModule;
|
use App\Module\Catalog\CatalogModule;
|
||||||
use App\Module\Commercial\CommercialModule;
|
use App\Module\Commercial\CommercialModule;
|
||||||
use App\Module\Core\CoreModule;
|
use App\Module\Core\CoreModule;
|
||||||
|
use App\Module\Logistique\LogistiqueModule;
|
||||||
use App\Module\Sites\SitesModule;
|
use App\Module\Sites\SitesModule;
|
||||||
use App\Module\Technique\TechniqueModule;
|
use App\Module\Technique\TechniqueModule;
|
||||||
use App\Module\Transport\TransportModule;
|
use App\Module\Transport\TransportModule;
|
||||||
@@ -15,4 +16,5 @@ return [
|
|||||||
CatalogModule::class,
|
CatalogModule::class,
|
||||||
TechniqueModule::class,
|
TechniqueModule::class,
|
||||||
TransportModule::class,
|
TransportModule::class,
|
||||||
|
LogistiqueModule::class,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -24,13 +24,18 @@ doctrine:
|
|||||||
# is_active, TIMESTAMP(6)) -> schema:update reste un no-op.
|
# is_active, TIMESTAMP(6)) -> schema:update reste un no-op.
|
||||||
# - `idtf_product` / `idtf_sync_log` : referentiel codes IDTF
|
# - `idtf_product` / `idtf_sync_log` : referentiel codes IDTF
|
||||||
# synchronise en DBAL brut par `app:idtf:sync`, hors ORM.
|
# synchronise en DBAL brut par `app:idtf:sync`, hors ORM.
|
||||||
|
# - `weighing_ticket_counter` / `weighbridge_dsd_counter` : compteurs
|
||||||
|
# par site (numero de ticket de pesee RG-5.02 / DSD du pont RG-5.04,
|
||||||
|
# M5 Logistique), incrementes en DBAL brut sous verrou `FOR UPDATE`
|
||||||
|
# par l'allocateur — jamais mappes en ORM (cf. spec M5 § 2.5 / § 2.7).
|
||||||
# Sans ce filtre, schema:update les considere comme "orphelines" et
|
# Sans ce filtre, schema:update les considere comme "orphelines" et
|
||||||
# genere un `DROP TABLE` qui casse la base de test apres chaque
|
# genere un `DROP TABLE` qui casse la base de test apres chaque
|
||||||
# `make test-db-setup` (la migration les a creees, schema:update les
|
# `make test-db-setup` (la migration les a creees, schema:update les
|
||||||
# supprime juste apres). Creation / suppression restent pilotees par
|
# supprime juste apres). Creation / suppression restent pilotees par
|
||||||
# les migrations (audit_log : Version20260420202749 ; qualimat :
|
# les migrations (audit_log : Version20260420202749 ; qualimat :
|
||||||
# Version20260612150000 ; idtf : Version20260612160000).
|
# Version20260612150000 ; idtf : Version20260612160000 ; compteurs M5 :
|
||||||
schema_filter: '~^(?!(?:audit_log|qualimat_sync_log|idtf_product|idtf_sync_log)$).+~'
|
# Version20260617150000).
|
||||||
|
schema_filter: '~^(?!(?:audit_log|qualimat_sync_log|idtf_product|idtf_sync_log|weighing_ticket_counter|weighbridge_dsd_counter)$).+~'
|
||||||
audit:
|
audit:
|
||||||
url: '%env(resolve:DATABASE_URL)%'
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
orm:
|
orm:
|
||||||
|
|||||||
@@ -95,6 +95,24 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
// Section "Logistique" (M5, ERP-181) : nouveau pole "operations physiques sur
|
||||||
|
// site", distinct du repertoire Transport (M4). Porte le ticket de pesee au
|
||||||
|
// pont bascule. L'item est gate par `logistique.weighing_tickets.view` ; la
|
||||||
|
// section disparait automatiquement (SidebarProvider) si le module `logistique`
|
||||||
|
// est desactive ou si l'user n'a pas la permission (Compta / Commerciale).
|
||||||
|
[
|
||||||
|
'label' => 'sidebar.logistique.section',
|
||||||
|
'icon' => 'mdi:scale',
|
||||||
|
'items' => [
|
||||||
|
[
|
||||||
|
'label' => 'sidebar.logistique.weighing_tickets',
|
||||||
|
'to' => '/weighing-tickets',
|
||||||
|
'icon' => 'mdi:scale',
|
||||||
|
'module' => 'logistique',
|
||||||
|
'permission' => 'logistique.weighing_tickets.view',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
// Section "Administration" : regroupe toutes les pages de configuration
|
// Section "Administration" : regroupe toutes les pages de configuration
|
||||||
// applicative (RBAC, users, sites, audit log).
|
// applicative (RBAC, users, sites, audit log).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -39,6 +39,10 @@
|
|||||||
"section": "Transport",
|
"section": "Transport",
|
||||||
"carriers": "Répertoire transporteurs"
|
"carriers": "Répertoire transporteurs"
|
||||||
},
|
},
|
||||||
|
"logistique": {
|
||||||
|
"section": "Logistique",
|
||||||
|
"weighing_tickets": "Tickets de pesée"
|
||||||
|
},
|
||||||
"core": {
|
"core": {
|
||||||
"roles": "Gestion des rôles",
|
"roles": "Gestion des rôles",
|
||||||
"users": "Utilisateurs",
|
"users": "Utilisateurs",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export default defineNuxtConfig({})
|
||||||
@@ -102,6 +102,12 @@ export const personas: Record<PersonaKey, Persona> = {
|
|||||||
'transport.carriers.view',
|
'transport.carriers.view',
|
||||||
'transport.carriers.manage',
|
'transport.carriers.manage',
|
||||||
'transport.carriers.archive',
|
'transport.carriers.archive',
|
||||||
|
// Logistique — Tickets de pesee (M5, ERP-181). Meme logique : mappe sur
|
||||||
|
// le persona "tout", pas de nouveau persona (regle ABSOLUE n°7).
|
||||||
|
// logistique.weighing_tickets.view n'ajoute pas de lien dans la section
|
||||||
|
// Administration, donc expectedAdminLinks reste inchange.
|
||||||
|
'logistique.weighing_tickets.view',
|
||||||
|
'logistique.weighing_tickets.manage',
|
||||||
],
|
],
|
||||||
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'],
|
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use App\Shared\Infrastructure\Database\ColumnCommentsCatalog;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M5 — Tickets de pesee (ERP-182) : creation du schema BDD du module Logistique.
|
||||||
|
*
|
||||||
|
* Objets crees :
|
||||||
|
* - site.code : code court du site (86/17/82), prefixe de numerotation des
|
||||||
|
* tickets (RG-5.02). Backfill depuis les 2 premiers chiffres du code postal
|
||||||
|
* + index unique uq_site_code (§ 2.5). NULLABLE a ce ticket (l'entite Site ne
|
||||||
|
* mappe pas encore `code`) ; le mapping ORM + peuplement + SET NOT NULL sont
|
||||||
|
* portes par le ticket entite (WeighingTicket).
|
||||||
|
* - weighing_ticket_counter : sequence du numero de ticket par site (RG-5.02).
|
||||||
|
* - weighbridge_dsd_counter : compteur DSD du pont bascule par site (RG-5.04).
|
||||||
|
* - weighing_ticket : table principale (contrepartie Client/Fournisseur/Autre,
|
||||||
|
* immatriculation partagee, pesees a vide + a plein en colonnes plates,
|
||||||
|
* poids net derive, soft-delete prepare + Timestampable/Blamable).
|
||||||
|
*
|
||||||
|
* Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) et NON modulaire :
|
||||||
|
* la table porte des FK cross-module (user, client, supplier, site). Le tri par
|
||||||
|
* timestamp au sein du namespace racine garantit l'ordre apres la creation de
|
||||||
|
* ces tables sur base vide ; un namespace modulaire casserait `make db-reset`.
|
||||||
|
*
|
||||||
|
* Convention IDs (spec § 2.2) : `INT GENERATED BY DEFAULT AS IDENTITY`,
|
||||||
|
* horodatages `TIMESTAMP(0) WITHOUT TIME ZONE` (le TimestampableBlamableTrait
|
||||||
|
* mappe `datetime_immutable`).
|
||||||
|
*
|
||||||
|
* Chaque colonne porte son `COMMENT ON COLUMN` (regle ABSOLUE n°12).
|
||||||
|
*
|
||||||
|
* NB schema:update (test-db-setup) :
|
||||||
|
* - weighing_ticket_counter / weighbridge_dsd_counter ne sont JAMAIS mappees en
|
||||||
|
* ORM (DBAL brut sous verrou FOR UPDATE, § 2.5 / § 2.7) -> exclues du
|
||||||
|
* `schema_filter` (config/packages/doctrine.yaml) pour que schema:update ne
|
||||||
|
* les drope pas. Leurs descriptions sont aussi catalogue-es dans
|
||||||
|
* ColumnCommentsCatalog (rejeu par `app:apply-column-comments`).
|
||||||
|
* - weighing_ticket et la colonne site.code seront mappes en ORM au ticket
|
||||||
|
* suivant (entite WeighingTicket + propriete Site::code) ; d'ici la,
|
||||||
|
* schema:update les drope sur la base de TEST uniquement (sans impact : aucun
|
||||||
|
* test ne les reference encore, et dev/prod ne lancent jamais schema:update).
|
||||||
|
*/
|
||||||
|
final class Version20260617150000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'ERP-182 (M5) : site.code + compteurs (numero ticket / DSD) + table weighing_ticket (tickets de pesee).';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSiteCode();
|
||||||
|
$this->createWeighingTicketCounter();
|
||||||
|
$this->createWeighbridgeDsdCounter();
|
||||||
|
$this->createWeighingTicket();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// Ordre inverse : table principale puis compteurs, enfin la colonne site.code.
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS weighing_ticket');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS weighbridge_dsd_counter');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS weighing_ticket_counter');
|
||||||
|
$this->addSql('DROP INDEX IF EXISTS uq_site_code');
|
||||||
|
$this->addSql('ALTER TABLE site DROP COLUMN IF EXISTS code');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// site.code — prefixe de numerotation des tickets (§ 2.5)
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function addSiteCode(): void
|
||||||
|
{
|
||||||
|
// Colonne NULLABLE a ce ticket : l'entite Site ne mappe pas encore `code`,
|
||||||
|
// donc tout persist ORM (fixtures, tests) l'omettrait -> un NOT NULL casserait
|
||||||
|
// `make db-reset`. Le mapping ORM Site::code, son peuplement (86/17/82) et le
|
||||||
|
// passage `SET NOT NULL` sont portes par le ticket suivant (entite WeighingTicket
|
||||||
|
// + Site::code), via une 2e migration. L'index unique est pose des maintenant
|
||||||
|
// (Postgres tolere plusieurs NULL) : il garantit l'unicite des qu'ils seront peuples.
|
||||||
|
$this->addSql('ALTER TABLE site ADD COLUMN code VARCHAR(8) DEFAULT NULL');
|
||||||
|
// Backfill : 2 premiers chiffres du code postal (departement) par defaut,
|
||||||
|
// editable ensuite cote admin Sites. No-op sur base fraiche (aucun site encore).
|
||||||
|
$this->addSql('UPDATE site SET code = LEFT(postal_code, 2) WHERE code IS NULL');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX uq_site_code ON site (code)');
|
||||||
|
|
||||||
|
$this->comment('site', 'code', 'Code court du site (ex. 86/17/82) — prefixe de numerotation des tickets de pesee (RG-5.02). Unique (uq_site_code). Backfill = 2 premiers chiffres du CP. NOT NULL pose au ticket entite.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Compteur du numero de ticket (sequence par site) — RG-5.02
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function createWeighingTicketCounter(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE weighing_ticket_counter (
|
||||||
|
site_id INT NOT NULL,
|
||||||
|
last_value INT DEFAULT 0 NOT NULL,
|
||||||
|
PRIMARY KEY (site_id),
|
||||||
|
CONSTRAINT fk_wt_counter_site
|
||||||
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->comment('weighing_ticket_counter', '_table', 'Sequence du numero de ticket de pesee par site (RG-5.02, M5 Logistique) — incrementee en DBAL brut sous verrou FOR UPDATE, hors ORM.');
|
||||||
|
$this->comment('weighing_ticket_counter', 'site_id', 'Site proprietaire de la sequence (1 ligne par site). PK + FK -> site.id, ON DELETE CASCADE.');
|
||||||
|
$this->comment('weighing_ticket_counter', 'last_value', 'Dernier numero de ticket attribue pour le site. Increment verrouille FOR UPDATE (RG-5.02).');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Compteur DSD (pesee du pont, par site) — RG-5.04
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function createWeighbridgeDsdCounter(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE weighbridge_dsd_counter (
|
||||||
|
site_id INT NOT NULL,
|
||||||
|
last_value INT DEFAULT 0 NOT NULL,
|
||||||
|
PRIMARY KEY (site_id),
|
||||||
|
CONSTRAINT fk_dsd_counter_site
|
||||||
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->comment('weighbridge_dsd_counter', '_table', 'Compteur DSD du pont bascule par site (RG-5.04, M5 Logistique) — chaque pesee consomme une valeur. Incremente en DBAL brut sous verrou FOR UPDATE, hors ORM.');
|
||||||
|
$this->comment('weighbridge_dsd_counter', 'site_id', 'Site proprietaire du compteur (1 pont par site). PK + FK -> site.id, ON DELETE CASCADE.');
|
||||||
|
$this->comment('weighbridge_dsd_counter', 'last_value', 'Derniere valeur DSD attribuee pour le site (pont bascule). Increment verrouille FOR UPDATE (RG-5.04).');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Table principale `weighing_ticket`
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function createWeighingTicket(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE weighing_ticket (
|
||||||
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
site_id INT NOT NULL,
|
||||||
|
number VARCHAR(20) NOT NULL,
|
||||||
|
counterparty_type VARCHAR(12) NOT NULL,
|
||||||
|
client_id INT DEFAULT NULL,
|
||||||
|
supplier_id INT DEFAULT NULL,
|
||||||
|
other_label VARCHAR(255) DEFAULT NULL,
|
||||||
|
immatriculation VARCHAR(20) NOT NULL,
|
||||||
|
plate_free_format BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
empty_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||||
|
empty_weight INT DEFAULT NULL,
|
||||||
|
empty_dsd INT DEFAULT NULL,
|
||||||
|
empty_mode VARCHAR(8) DEFAULT NULL,
|
||||||
|
empty_manual_number VARCHAR(50) DEFAULT NULL,
|
||||||
|
full_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||||
|
full_weight INT DEFAULT NULL,
|
||||||
|
full_dsd INT DEFAULT NULL,
|
||||||
|
full_mode VARCHAR(8) DEFAULT NULL,
|
||||||
|
full_manual_number VARCHAR(50) DEFAULT NULL,
|
||||||
|
net_weight INT DEFAULT NULL,
|
||||||
|
deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||||
|
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||||
|
created_by INT DEFAULT NULL,
|
||||||
|
updated_by INT DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
CONSTRAINT chk_wt_counterparty_type
|
||||||
|
CHECK (counterparty_type IN ('CLIENT', 'FOURNISSEUR', 'AUTRE')),
|
||||||
|
CONSTRAINT chk_wt_empty_mode
|
||||||
|
CHECK (empty_mode IS NULL OR empty_mode IN ('AUTO', 'MANUAL')),
|
||||||
|
CONSTRAINT chk_wt_full_mode
|
||||||
|
CHECK (full_mode IS NULL OR full_mode IN ('AUTO', 'MANUAL')),
|
||||||
|
CONSTRAINT chk_wt_client_branch
|
||||||
|
CHECK (counterparty_type <> 'CLIENT' OR (client_id IS NOT NULL AND supplier_id IS NULL AND other_label IS NULL)),
|
||||||
|
CONSTRAINT chk_wt_supplier_branch
|
||||||
|
CHECK (counterparty_type <> 'FOURNISSEUR' OR (supplier_id IS NOT NULL AND client_id IS NULL AND other_label IS NULL)),
|
||||||
|
CONSTRAINT chk_wt_other_branch
|
||||||
|
CHECK (counterparty_type <> 'AUTRE' OR (other_label IS NOT NULL AND client_id IS NULL AND supplier_id IS NULL)),
|
||||||
|
CONSTRAINT fk_wt_site
|
||||||
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT,
|
||||||
|
CONSTRAINT fk_wt_client
|
||||||
|
FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE RESTRICT,
|
||||||
|
CONSTRAINT fk_wt_supplier
|
||||||
|
FOREIGN KEY (supplier_id) REFERENCES supplier (id) ON DELETE RESTRICT,
|
||||||
|
CONSTRAINT fk_wt_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
|
||||||
|
CONSTRAINT fk_wt_updated_by
|
||||||
|
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX uq_weighing_ticket_number ON weighing_ticket (site_id, number)');
|
||||||
|
$this->addSql('CREATE INDEX idx_wt_site ON weighing_ticket (site_id)');
|
||||||
|
$this->addSql('CREATE INDEX idx_wt_client ON weighing_ticket (client_id)');
|
||||||
|
$this->addSql('CREATE INDEX idx_wt_supplier ON weighing_ticket (supplier_id)');
|
||||||
|
$this->addSql('CREATE INDEX idx_wt_deleted_at ON weighing_ticket (deleted_at)');
|
||||||
|
$this->addSql('CREATE INDEX idx_wt_created_by ON weighing_ticket (created_by)');
|
||||||
|
$this->addSql('CREATE INDEX idx_wt_updated_by ON weighing_ticket (updated_by)');
|
||||||
|
|
||||||
|
$this->comment('weighing_ticket', '_table', 'Tickets de pesee (M5 Logistique) — pesee a vide + a plein au pont bascule, contrepartie Client/Fournisseur/Autre. Cloisonne par site courant.');
|
||||||
|
$this->comment('weighing_ticket', 'id', 'Identifiant interne auto-incremente.');
|
||||||
|
$this->comment('weighing_ticket', 'site_id', 'Site du pont bascule (cloisonnement § 2.3). FK -> site.id, ON DELETE RESTRICT. Renseigne serveur depuis le site courant, immuable (RG-5.09).');
|
||||||
|
$this->comment('weighing_ticket', 'number', 'Numero {siteCode}-TP-{NNNN}, unique par site (uq_weighing_ticket_number), immuable. Sequence weighing_ticket_counter (RG-5.02).');
|
||||||
|
$this->comment('weighing_ticket', 'counterparty_type', 'Contrepartie : CLIENT, FOURNISSEUR ou AUTRE (chk_wt_counterparty_type, RG-5.03). Pilote l obligation client_id / supplier_id / other_label.');
|
||||||
|
$this->comment('weighing_ticket', 'client_id', 'Branche CLIENT (RG-5.03) : client concerne. FK -> client.id, ON DELETE RESTRICT. Requis ssi counterparty_type = CLIENT, nul sinon (chk_wt_client_branch).');
|
||||||
|
$this->comment('weighing_ticket', 'supplier_id', 'Branche FOURNISSEUR (RG-5.03) : fournisseur concerne. FK -> supplier.id, ON DELETE RESTRICT. Requis ssi counterparty_type = FOURNISSEUR (chk_wt_supplier_branch).');
|
||||||
|
$this->comment('weighing_ticket', 'other_label', 'Branche AUTRE (RG-5.03) : libelle libre de la contrepartie. Requis ssi counterparty_type = AUTRE, nul sinon (chk_wt_other_branch).');
|
||||||
|
$this->comment('weighing_ticket', 'immatriculation', 'Plaque du vehicule, partagee entre pesee vide et plein. Masque XX-000-XX sauf si plate_free_format (RG-5.01). Normalisee serveur (trim/UPPER).');
|
||||||
|
$this->comment('weighing_ticket', 'plate_free_format', '« Tout format » : desactive le masque XX-000-XX de l immatriculation (RG-5.01). Partage entre les 2 formulaires. Faux par defaut.');
|
||||||
|
$this->comment('weighing_ticket', 'empty_date', 'Date/heure de la pesee a vide (tare). Defaut jour courant cote front (RG-5.07). Null tant que la pesee vide n est pas faite.');
|
||||||
|
$this->comment('weighing_ticket', 'empty_weight', 'Poids a vide (tare) en kg — readonly UI, rempli par la pesee (RG-5.07).');
|
||||||
|
$this->comment('weighing_ticket', 'empty_dsd', 'Compteur DSD du pont a la pesee a vide. AUTO = valeur du pont ; MANUAL = dernier dsd du site + 1 (RG-5.04).');
|
||||||
|
$this->comment('weighing_ticket', 'empty_mode', 'Mode de la pesee a vide : AUTO (pont bascule) ou MANUAL (saisie) — chk_wt_empty_mode (RG-5.06).');
|
||||||
|
$this->comment('weighing_ticket', 'empty_manual_number', 'Numero de pesee saisi en pesee manuelle (distinct du DSD) — formulaire a vide (RG-5.04).');
|
||||||
|
$this->comment('weighing_ticket', 'full_date', 'Date/heure de la pesee a plein (brut). Null tant que la pesee plein n est pas faite.');
|
||||||
|
$this->comment('weighing_ticket', 'full_weight', 'Poids a plein (brut) en kg — readonly UI, rempli par la pesee (RG-5.07).');
|
||||||
|
$this->comment('weighing_ticket', 'full_dsd', 'Compteur DSD du pont a la pesee a plein. AUTO = valeur du pont ; MANUAL = dernier dsd du site + 1 (RG-5.04).');
|
||||||
|
$this->comment('weighing_ticket', 'full_mode', 'Mode de la pesee a plein : AUTO (pont bascule) ou MANUAL (saisie) — chk_wt_full_mode (RG-5.06).');
|
||||||
|
$this->comment('weighing_ticket', 'full_manual_number', 'Numero de pesee saisi en pesee manuelle (distinct du DSD) — formulaire a plein (RG-5.04).');
|
||||||
|
$this->comment('weighing_ticket', 'net_weight', 'Poids net = full_weight - empty_weight (kg), calcule serveur (RG-5.05). Null si une pesee manque. Colonne Poids de la liste.');
|
||||||
|
$this->comment('weighing_ticket', 'deleted_at', 'Horodatage du soft-delete technique — prepare mais non expose par l API au M5 (§ 2.13). Null = ligne active.');
|
||||||
|
$this->addTimestampableBlamableComments('weighing_ticket');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Helpers (identiques au M4 Version20260615150000)
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pose les 4 commentaires standardises Timestampable/Blamable sur une table,
|
||||||
|
* en reutilisant le catalogue partage (source unique, ERP-67).
|
||||||
|
*/
|
||||||
|
private function addTimestampableBlamableComments(string $table): void
|
||||||
|
{
|
||||||
|
foreach (ColumnCommentsCatalog::timestampableBlamableComments() as $column => $description) {
|
||||||
|
$this->comment($table, $column, $description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emet un `COMMENT ON TABLE` (colonne speciale `_table`) ou `COMMENT ON COLUMN`
|
||||||
|
* en dollar-quoting Postgres ($_$...$_$) pour eviter tout echappement d apostrophe.
|
||||||
|
*/
|
||||||
|
private function comment(string $table, string $column, string $description): void
|
||||||
|
{
|
||||||
|
$quotedTable = '"'.str_replace('"', '""', $table).'"';
|
||||||
|
|
||||||
|
if ('_table' === $column) {
|
||||||
|
$this->addSql(sprintf('COMMENT ON TABLE %s IS $_$%s$_$', $quotedTable, $description));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addSql(sprintf(
|
||||||
|
'COMMENT ON COLUMN %s.%s IS $_$%s$_$',
|
||||||
|
$quotedTable,
|
||||||
|
'"'.str_replace('"', '""', $column).'"',
|
||||||
|
$description,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,6 +80,9 @@ final class RbacSeeder
|
|||||||
// Transporteurs (M4 § 5.2, ERP-153) : view + manage (PAS archive -> admin seul).
|
// Transporteurs (M4 § 5.2, ERP-153) : view + manage (PAS archive -> admin seul).
|
||||||
'transport.carriers.view',
|
'transport.carriers.view',
|
||||||
'transport.carriers.manage',
|
'transport.carriers.manage',
|
||||||
|
// Tickets de pesee (M5 § 5.2, ERP-181) : view + manage (« Tout »).
|
||||||
|
'logistique.weighing_tickets.view',
|
||||||
|
'logistique.weighing_tickets.manage',
|
||||||
// Visibilite multi-site des prestataires (M3 § 2.13) : voit tous les sites.
|
// Visibilite multi-site des prestataires (M3 § 2.13) : voit tous les sites.
|
||||||
'sites.bypass_scope',
|
'sites.bypass_scope',
|
||||||
// Lecture des referentiels transverses pour les selects client (ERP-102).
|
// Lecture des referentiels transverses pour les selects client (ERP-102).
|
||||||
@@ -137,9 +140,14 @@ final class RbacSeeder
|
|||||||
'label' => 'Usine',
|
'label' => 'Usine',
|
||||||
// Prestataires (M3 § 2.9 + § 2.13, ERP-138) : view en lecture seule,
|
// Prestataires (M3 § 2.9 + § 2.13, ERP-138) : view en lecture seule,
|
||||||
// SANS `sites.bypass_scope` -> cloisonne aux prestataires de son site
|
// SANS `sites.bypass_scope` -> cloisonne aux prestataires de son site
|
||||||
// courant. Aucun autre acces metier.
|
// courant.
|
||||||
'permissions' => [
|
'permissions' => [
|
||||||
'technique.providers.view',
|
'technique.providers.view',
|
||||||
|
// Tickets de pesee (M5 § 5.2, ERP-181) : view + manage. L'Usine
|
||||||
|
// pese sur site -> reste cloisonnee a son site courant (pas de
|
||||||
|
// bypass_scope ; les tickets sont filtres par SiteScopedQueryExtension).
|
||||||
|
'logistique.weighing_tickets.view',
|
||||||
|
'logistique.weighing_tickets.manage',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ final class SeedE2ECommand extends Command
|
|||||||
'transport.carriers.view',
|
'transport.carriers.view',
|
||||||
'transport.carriers.manage',
|
'transport.carriers.manage',
|
||||||
'transport.carriers.archive',
|
'transport.carriers.archive',
|
||||||
|
// Logistique — Tickets de pesee (M5, ERP-181). Meme logique :
|
||||||
|
// mappe sur le persona "tout". Miroir de personas.ts.
|
||||||
|
'logistique.weighing_tickets.view',
|
||||||
|
'logistique.weighing_tickets.manage',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Module\Logistique;
|
||||||
|
|
||||||
|
final class LogistiqueModule
|
||||||
|
{
|
||||||
|
public const string ID = 'logistique';
|
||||||
|
public const string LABEL = 'Logistique';
|
||||||
|
public const bool REQUIRED = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste declarative des permissions RBAC exposees par le module Logistique.
|
||||||
|
*
|
||||||
|
* Socle des tickets de pesee (M5 § 5.1, ERP-181) :
|
||||||
|
* - `view` : consultation de la liste / fiche d'un ticket de pesee ;
|
||||||
|
* - `manage` : creation / modification d'un ticket de pesee.
|
||||||
|
*
|
||||||
|
* Consommee par `app:sync-permissions`. Matrice role -> permissions dans
|
||||||
|
* `RbacSeeder::MATRIX` (§ 5.2 : Bureau / Usine = view + manage ; Compta /
|
||||||
|
* Commerciale = aucun acces).
|
||||||
|
*
|
||||||
|
* @return array<int, array{code: string, label: string}>
|
||||||
|
*/
|
||||||
|
public static function permissions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['code' => 'logistique.weighing_tickets.view', 'label' => 'Voir les tickets de pesée'],
|
||||||
|
['code' => 'logistique.weighing_tickets.manage', 'label' => 'Créer / modifier les tickets de pesée'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -538,6 +538,21 @@ final class ColumnCommentsCatalog
|
|||||||
'price_state' => 'Etat du prix : EN_COURS, VALIDE ou NON_VALIDE (chk_carrier_price_state). Affiche dans le tableau Prix.',
|
'price_state' => 'Etat du prix : EN_COURS, VALIDE ou NON_VALIDE (chk_carrier_price_state). Affiche dans le tableau Prix.',
|
||||||
'position' => 'Ordre d affichage du prix dans la liste du transporteur (croissant).',
|
'position' => 'Ordre d affichage du prix dans la liste du transporteur (croissant).',
|
||||||
] + self::timestampableBlamableComments(),
|
] + self::timestampableBlamableComments(),
|
||||||
|
|
||||||
|
// M5 Logistique (ERP-182) — compteurs par site, hors ORM (DBAL brut
|
||||||
|
// FOR UPDATE) donc exclus du schema_filter ; catalogues ici pour que
|
||||||
|
// `app:apply-column-comments` rejoue leurs descriptions au besoin.
|
||||||
|
'weighing_ticket_counter' => [
|
||||||
|
'_table' => 'Sequence du numero de ticket de pesee par site (RG-5.02, M5 Logistique) — incrementee en DBAL brut sous verrou FOR UPDATE, hors ORM.',
|
||||||
|
'site_id' => 'Site proprietaire de la sequence (1 ligne par site). PK + FK -> site.id, ON DELETE CASCADE.',
|
||||||
|
'last_value' => 'Dernier numero de ticket attribue pour le site. Increment verrouille FOR UPDATE (RG-5.02).',
|
||||||
|
],
|
||||||
|
|
||||||
|
'weighbridge_dsd_counter' => [
|
||||||
|
'_table' => 'Compteur DSD du pont bascule par site (RG-5.04, M5 Logistique) — chaque pesee consomme une valeur. Incremente en DBAL brut sous verrou FOR UPDATE, hors ORM.',
|
||||||
|
'site_id' => 'Site proprietaire du compteur (1 pont par site). PK + FK -> site.id, ON DELETE CASCADE.',
|
||||||
|
'last_value' => 'Derniere valeur DSD attribuee pour le site (pont bascule). Increment verrouille FOR UPDATE (RG-5.04).',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user