Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5409c79d1d | |||
| e9f8b0bc45 |
@@ -134,6 +134,16 @@ return [
|
|||||||
'module' => 'transport',
|
'module' => 'transport',
|
||||||
'permission' => 'transport.carriers.view',
|
'permission' => 'transport.carriers.view',
|
||||||
],
|
],
|
||||||
|
// Catalogue produit (M6, ERP-197). Place juste sous le repertoire
|
||||||
|
// transporteurs (DECISION Matthieu 24/06). Admin-only : gate par
|
||||||
|
// `catalog.products.view` et son module owner `catalog`.
|
||||||
|
[
|
||||||
|
'label' => 'sidebar.catalog.products',
|
||||||
|
'to' => '/admin/products',
|
||||||
|
'icon' => 'mdi:package-variant-closed',
|
||||||
|
'module' => 'catalog',
|
||||||
|
'permission' => 'catalog.products.view',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'label' => 'sidebar.core.roles',
|
'label' => 'sidebar.core.roles',
|
||||||
'to' => '/admin/roles',
|
'to' => '/admin/roles',
|
||||||
|
|||||||
@@ -52,7 +52,8 @@
|
|||||||
"admin": "Sites"
|
"admin": "Sites"
|
||||||
},
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"categories": "Gestion des catégories"
|
"categories": "Gestion des catégories",
|
||||||
|
"products": "Catalogue produit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export interface Persona {
|
|||||||
// sidebar-visibility pour driver la matrice. Les valeurs correspondent
|
// sidebar-visibility pour driver la matrice. Les valeurs correspondent
|
||||||
// aux slugs de route (`/admin/<slug>`), volontairement stables quand
|
// aux slugs de route (`/admin/<slug>`), volontairement stables quand
|
||||||
// la copie/i18n change.
|
// la copie/i18n change.
|
||||||
expectedAdminLinks: Array<'users' | 'roles' | 'sites' | 'audit-log' | 'categories'>
|
expectedAdminLinks: Array<'users' | 'roles' | 'sites' | 'audit-log' | 'categories' | 'products'>
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHARED_PASSWORD = 'e2e-secret'
|
const SHARED_PASSWORD = 'e2e-secret'
|
||||||
@@ -47,7 +47,7 @@ export const personas: Record<PersonaKey, Persona> = {
|
|||||||
password: SHARED_PASSWORD,
|
password: SHARED_PASSWORD,
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'],
|
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'products', 'audit-log'],
|
||||||
},
|
},
|
||||||
'user-full': {
|
'user-full': {
|
||||||
key: 'user-full',
|
key: 'user-full',
|
||||||
@@ -65,6 +65,12 @@ export const personas: Record<PersonaKey, Persona> = {
|
|||||||
'sites.bypass_scope',
|
'sites.bypass_scope',
|
||||||
'catalog.categories.view',
|
'catalog.categories.view',
|
||||||
'catalog.categories.manage',
|
'catalog.categories.manage',
|
||||||
|
// Catalogue produit (M6, ERP-197). Admin-only (matrice docx p.3) :
|
||||||
|
// mappe sur le persona "tout", pas de nouveau persona (regle ABSOLUE
|
||||||
|
// n°7). L'item vit dans la section Administration sur la route
|
||||||
|
// `/admin/products` -> ajoute le lien `products` a expectedAdminLinks.
|
||||||
|
'catalog.products.view',
|
||||||
|
'catalog.products.manage',
|
||||||
// Commercial — Repertoire clients (M1). Mappe ici sur le persona
|
// Commercial — Repertoire clients (M1). Mappe ici sur le persona
|
||||||
// "tout" en attendant les vrais roles metier (bureau/compta/
|
// "tout" en attendant les vrais roles metier (bureau/compta/
|
||||||
// commerciale/usine) seedes par ERP-74. Pas de nouveau persona
|
// commerciale/usine) seedes par ERP-74. Pas de nouveau persona
|
||||||
@@ -110,7 +116,7 @@ export const personas: Record<PersonaKey, Persona> = {
|
|||||||
'logistique.weighing_tickets.view',
|
'logistique.weighing_tickets.view',
|
||||||
'logistique.weighing_tickets.manage',
|
'logistique.weighing_tickets.manage',
|
||||||
],
|
],
|
||||||
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'],
|
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'products', 'audit-log'],
|
||||||
},
|
},
|
||||||
'user-readonly': {
|
'user-readonly': {
|
||||||
key: 'user-readonly',
|
key: 'user-readonly',
|
||||||
@@ -155,4 +161,4 @@ export function getPersona(key: PersonaKey): Persona {
|
|||||||
return personas[key]
|
return personas[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALL_ADMIN_LINKS = ['users', 'roles', 'sites', 'categories', 'audit-log'] as const
|
export const ALL_ADMIN_LINKS = ['users', 'roles', 'sites', 'categories', 'products', 'audit-log'] as const
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use App\Shared\Infrastructure\Database\ColumnCommentsCatalog;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M6 — Catalogue produit (ERP-198) : creation du schema BDD du module.
|
||||||
|
*
|
||||||
|
* Objets crees (spec-back § 3.2) :
|
||||||
|
* - storage_type : referentiel PROVISOIRE des types de stockage (en attente de la
|
||||||
|
* liste definitive d'Aurore — § 2.4 / RG-6.06). Lecture seule au M6.
|
||||||
|
* - storage_type_site : jonction M2M storage_type <-> site (sur quels sites un type
|
||||||
|
* de stockage est disponible — alimente le filtrage du multi-select par site).
|
||||||
|
* - product : table principale (code unique global parmi les actifs, etats
|
||||||
|
* multi-valeur JSONB, champs conditionnels SALE, categorie de type PRODUIT,
|
||||||
|
* soft-delete prepare + Timestampable/Blamable).
|
||||||
|
* - product_site : jonction M2M product <-> site (sites de disponibilite, RG-6.04).
|
||||||
|
* - product_storage_type : jonction M2M product <-> storage_type (RG-6.06).
|
||||||
|
*
|
||||||
|
* Seed : ajout du `category_type` PRODUIT (miroir CategoryTypeFixtures, comme
|
||||||
|
* CLIENT/FOURNISSEUR/PRESTATAIRE/ADRESSE — § 2.5). Les `Category` de type PRODUIT et
|
||||||
|
* le seed Figma du referentiel storage_type suivent au ticket ERP-201.
|
||||||
|
*
|
||||||
|
* Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) et NON modulaire :
|
||||||
|
* la table product porte des FK cross-module (user, site, category). 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` (cf.
|
||||||
|
* Version20260617150000 pour le M5).
|
||||||
|
*
|
||||||
|
* 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 n°12).
|
||||||
|
*
|
||||||
|
* NB schema:update (test-db-setup) : product / storage_type et leurs jonctions seront
|
||||||
|
* mappes en ORM au ticket suivant (entites Product + StorageType, ERP-199). D'ici la,
|
||||||
|
* `schema:update --force` les drope sur la base de TEST uniquement (sans impact :
|
||||||
|
* aucun test ne les reference encore, et dev/prod ne lancent jamais schema:update).
|
||||||
|
* Leurs descriptions seront ajoutees a ColumnCommentsCatalog au ticket entites (comme
|
||||||
|
* weighing_ticket : migration ERP-182, catalogue ERP-183).
|
||||||
|
*/
|
||||||
|
final class Version20260625110000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'ERP-198 (M6) : storage_type (+ jonction site) + product (+ jonctions site/stockage) + seed category_type PRODUIT.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->createStorageType();
|
||||||
|
$this->createStorageTypeSite();
|
||||||
|
$this->createProduct();
|
||||||
|
$this->createProductSite();
|
||||||
|
$this->createProductStorageType();
|
||||||
|
$this->seedCategoryTypeProduit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// Ordre inverse des dependances FK.
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS product_storage_type');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS product_site');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS product');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS storage_type_site');
|
||||||
|
$this->addSql('DROP TABLE IF EXISTS storage_type');
|
||||||
|
// Retrait du type seede (best-effort : echoue si des categories le referencent
|
||||||
|
// encore — attendu, le down sert au dev sur base saine).
|
||||||
|
$this->addSql("DELETE FROM category_type WHERE code = 'PRODUIT'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Referentiel des types de stockage (PROVISOIRE) — § 2.4 / RG-6.06
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function createStorageType(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE storage_type (
|
||||||
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
code VARCHAR(40) NOT NULL,
|
||||||
|
label VARCHAR(120) NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX uq_storage_type_code ON storage_type (code)');
|
||||||
|
|
||||||
|
$this->comment('storage_type', '_table', 'Referentiel des types de stockage (PROVISOIRE, en attente liste Aurore) — Boisseau, Cellule, Tas, Cuve melasse… (RG-6.06). Lecture seule au M6.');
|
||||||
|
$this->comment('storage_type', 'id', 'Identifiant interne auto-incremente.');
|
||||||
|
$this->comment('storage_type', 'code', 'Code stable MAJUSCULE du type de stockage (ex. TAS, CUVE_MELASSE). Unique (uq_storage_type_code).');
|
||||||
|
$this->comment('storage_type', 'label', 'Libelle FR affiche du type de stockage (ex. « Cuve melasse »).');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createStorageTypeSite(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE storage_type_site (
|
||||||
|
storage_type_id INT NOT NULL,
|
||||||
|
site_id INT NOT NULL,
|
||||||
|
PRIMARY KEY (storage_type_id, site_id),
|
||||||
|
CONSTRAINT fk_storage_type_site_type
|
||||||
|
FOREIGN KEY (storage_type_id) REFERENCES storage_type (id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_storage_type_site_site
|
||||||
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX idx_storage_type_site_site ON storage_type_site (site_id)');
|
||||||
|
|
||||||
|
$this->comment('storage_type_site', '_table', 'Jointure M2M storage_type <-> site (Sites) — sites sur lesquels un type de stockage est disponible (alimente le filtrage du multi-select par site, RG-6.06).');
|
||||||
|
$this->comment('storage_type_site', 'storage_type_id', 'FK -> storage_type.id, ON DELETE CASCADE — type de stockage disponible.');
|
||||||
|
$this->comment('storage_type_site', 'site_id', 'FK -> site.id, ON DELETE CASCADE — site ou le type de stockage est disponible.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Table principale `product`
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function createProduct(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE product (
|
||||||
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
code VARCHAR(50) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
states JSONB DEFAULT '[]'::jsonb NOT NULL,
|
||||||
|
manufactured BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
contains_molasses BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
category_id INT NOT 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_product_states_not_empty
|
||||||
|
CHECK (jsonb_array_length(states) >= 1),
|
||||||
|
CONSTRAINT fk_product_category
|
||||||
|
FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT,
|
||||||
|
CONSTRAINT fk_product_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
|
||||||
|
CONSTRAINT fk_product_updated_by
|
||||||
|
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
// Unicite GLOBALE du code parmi les actifs (soft-delete tolere) — index partiel.
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX uq_product_code_active ON product (code) WHERE deleted_at IS NULL');
|
||||||
|
$this->addSql('CREATE INDEX idx_product_category ON product (category_id)');
|
||||||
|
$this->addSql('CREATE INDEX idx_product_deleted_at ON product (deleted_at)');
|
||||||
|
$this->addSql('CREATE INDEX idx_product_created_by ON product (created_by)');
|
||||||
|
$this->addSql('CREATE INDEX idx_product_updated_by ON product (updated_by)');
|
||||||
|
|
||||||
|
$this->comment('product', '_table', 'Produits du catalogue (M6 Catalog) — etat Achat/Vendu/Autre, sites de disponibilite, categorie produit, types de stockage.');
|
||||||
|
$this->comment('product', 'id', 'Identifiant interne auto-incremente.');
|
||||||
|
$this->comment('product', 'code', 'Code produit (= « Numero » de la liste), saisi, unique global parmi les actifs (RG-6.01). Index partiel uq_product_code_active. Normalise serveur (trim/UPPER).');
|
||||||
|
$this->comment('product', 'name', 'Nom du produit (≤ 255). Normalise serveur (trim).');
|
||||||
|
$this->comment('product', 'states', 'Etats du produit (JSON) : sous-ensemble non vide de PURCHASE|SALE|OTHER, multi-select (RG-6.02, chk_product_states_not_empty). Pilote les champs conditionnels.');
|
||||||
|
$this->comment('product', 'manufactured', '« Fabrique » : saisi uniquement si states contient SALE, sinon force false serveur (RG-6.03).');
|
||||||
|
$this->comment('product', 'contains_molasses', '« Contient de la melasse » : saisi uniquement si states contient SALE, sinon force false serveur (RG-6.03).');
|
||||||
|
$this->comment('product', 'category_id', 'Categorie produit (FK -> category.id, ON DELETE RESTRICT) — type PRODUIT, obligatoire, validee applicativement (RG-6.05).');
|
||||||
|
$this->comment('product', 'deleted_at', 'Horodatage du soft-delete technique — non expose au M6 ; la liste exclut les produits supprimes (§ 2.7). Null = ligne active.');
|
||||||
|
$this->addTimestampableBlamableComments('product');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createProductSite(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE product_site (
|
||||||
|
product_id INT NOT NULL,
|
||||||
|
site_id INT NOT NULL,
|
||||||
|
PRIMARY KEY (product_id, site_id),
|
||||||
|
CONSTRAINT fk_product_site_product
|
||||||
|
FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_product_site_site
|
||||||
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX idx_product_site_site ON product_site (site_id)');
|
||||||
|
|
||||||
|
$this->comment('product_site', '_table', 'Jointure M2M product <-> site (Sites) — sites de disponibilite du produit (>= 1 obligatoire, RG-6.04).');
|
||||||
|
$this->comment('product_site', 'product_id', 'FK -> product.id, ON DELETE CASCADE — produit concerne.');
|
||||||
|
$this->comment('product_site', 'site_id', 'FK -> site.id, ON DELETE RESTRICT — site de disponibilite rattache au produit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createProductStorageType(): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE product_storage_type (
|
||||||
|
product_id INT NOT NULL,
|
||||||
|
storage_type_id INT NOT NULL,
|
||||||
|
PRIMARY KEY (product_id, storage_type_id),
|
||||||
|
CONSTRAINT fk_product_storage_type_product
|
||||||
|
FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_product_storage_type_type
|
||||||
|
FOREIGN KEY (storage_type_id) REFERENCES storage_type (id) ON DELETE RESTRICT
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX idx_product_storage_type_type ON product_storage_type (storage_type_id)');
|
||||||
|
|
||||||
|
$this->comment('product_storage_type', '_table', 'Jointure M2M product <-> storage_type — types de stockage du produit (>= 1 obligatoire, filtres par les sites selectionnes, RG-6.06).');
|
||||||
|
$this->comment('product_storage_type', 'product_id', 'FK -> product.id, ON DELETE CASCADE — produit concerne.');
|
||||||
|
$this->comment('product_storage_type', 'storage_type_id', 'FK -> storage_type.id, ON DELETE RESTRICT — type de stockage rattache au produit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Seed du type de categorie PRODUIT (§ 2.5) — miroir CategoryTypeFixtures
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
private function seedCategoryTypeProduit(): void
|
||||||
|
{
|
||||||
|
// Idempotent via l'index unique uq_category_type_code (comme CLIENT/FOURNISSEUR/
|
||||||
|
// PRESTATAIRE/ADRESSE). Les Category de type PRODUIT suivent en ERP-201.
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
INSERT INTO category_type (code, label) VALUES ('PRODUIT', 'Produit')
|
||||||
|
ON CONFLICT (code) DO NOTHING
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Helpers (identiques au M5 Version20260617150000)
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,10 @@ final class CatalogModule
|
|||||||
// sans donner l'acces d'administration `.view` (qui ouvre la page Catalogue
|
// sans donner l'acces d'administration `.view` (qui ouvre la page Catalogue
|
||||||
// dans la sidebar). Accordee aux roles metier via la matrice RBAC § 2.7.
|
// dans la sidebar). Accordee aux roles metier via la matrice RBAC § 2.7.
|
||||||
['code' => 'catalog.categories.read_ref', 'label' => 'Lire le referentiel categories (transverse, lecture seule)'],
|
['code' => 'catalog.categories.read_ref', 'label' => 'Lire le referentiel categories (transverse, lecture seule)'],
|
||||||
|
// Catalogue produit (M6, ERP-197) : admin-only (matrice docx p.3, C7).
|
||||||
|
// Item sidebar dans la section Administration, sous « Repertoire transporteurs ».
|
||||||
|
['code' => 'catalog.products.view', 'label' => 'Voir les produits'],
|
||||||
|
['code' => 'catalog.products.manage', 'label' => 'Gérer les produits (créer, éditer)'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,10 @@ final class SeedE2ECommand extends Command
|
|||||||
'sites.bypass_scope',
|
'sites.bypass_scope',
|
||||||
'catalog.categories.view',
|
'catalog.categories.view',
|
||||||
'catalog.categories.manage',
|
'catalog.categories.manage',
|
||||||
|
// Catalogue produit (M6, ERP-197). Admin-only (matrice docx
|
||||||
|
// p.3) : mappe sur le persona "tout". Miroir de personas.ts.
|
||||||
|
'catalog.products.view',
|
||||||
|
'catalog.products.manage',
|
||||||
// Commercial — Repertoire clients (M1). Mappe ici sur le
|
// Commercial — Repertoire clients (M1). Mappe ici sur le
|
||||||
// persona "tout" en attendant les vrais roles metier
|
// persona "tout" en attendant les vrais roles metier
|
||||||
// (bureau/compta/commerciale/usine) seedes par ERP-74.
|
// (bureau/compta/commerciale/usine) seedes par ERP-74.
|
||||||
|
|||||||
Reference in New Issue
Block a user