Compare commits

...

3 commits

Author SHA1 Message Date
dca5860869 Merge pull request 'feat(db): seed data (RBAC + allergens + catalogue)' (#8) from feat/p2-seed-data into dev
All checks were successful
CI / secret-scan (push) Successful in 8s
CI / php-lint (push) Successful in 18s
CI / static-tests (push) Successful in 5s
CI / auto-merge (push) Has been skipped
Reviewed-on: #8
2026-06-15 15:57:14 +02:00
Imugiii
04404dc8c5 docs: clarify manager has read-only user access (user.read), not zero access
All checks were successful
CI / secret-scan (pull_request) Successful in 7s
CI / php-lint (pull_request) Successful in 18s
CI / static-tests (pull_request) Successful in 4s
CI / auto-merge (push) Has been skipped
CI / auto-merge (pull_request) Has been skipped
CI / secret-scan (push) Successful in 10s
CI / php-lint (push) Successful in 18s
CI / static-tests (push) Successful in 5s
2026-06-15 13:47:58 +00:00
Imugiii
fcf52a0895 feat(db): seed data - RBAC matrix + INCO allergens + admin user + catalogue (9 cat / 53 products / 13 menus + composition)
All checks were successful
CI / secret-scan (push) Successful in 11s
CI / static-tests (push) Successful in 9s
CI / secret-scan (pull_request) Successful in 10s
CI / php-lint (push) Successful in 26s
CI / php-lint (pull_request) Successful in 25s
CI / static-tests (pull_request) Successful in 7s
CI / auto-merge (push) Has been skipped
CI / auto-merge (pull_request) Has been skipped
2026-06-15 13:45:14 +00:00
4 changed files with 387 additions and 2 deletions

View file

@ -0,0 +1,190 @@
-- =============================================================================
-- Wakdo — Seed 0001 : RBAC + reference data + admin user
-- =============================================================================
-- Purpose : Seed the foundational rows the back-office cannot boot without:
-- the 5 RBAC roles, the frozen catalogue of 23 permissions, the
-- default role/permission matrix, per-role visible order sources,
-- the 14 EU INCO allergens, and a single bootstrap admin user.
-- Source : docs/merise/dictionary.md (3.8 allergen, 3.15 role, 3.16
-- role_visible_source, 3.17 permission catalogue + default grants,
-- 3.18 role_permission), docs/merise/mct.md (operations 1-28),
-- docs/PROJECT_CONTEXT.md section 7 (role responsibilities) and
-- decision D5 (admin gets order.create / order.deliver ; manager
-- does NOT get order.cancel).
-- Phase : P2 — demo/reference seed, applied AFTER db/migrations/0001_init_schema.sql.
-- Target : MariaDB 11.4 LTS. Fed by db/seed.sh into the already-selected DB.
--
-- Notes:
-- - Statements are ordered so every FK resolves: role and permission first,
-- then role_permission / role_visible_source, then user (FK -> role).
-- - role_permission rows use subqueries on role.code and permission.code so
-- no surrogate ids are hardcoded (robust to AUTO_INCREMENT gaps).
-- - admin/manager get no role_visible_source rows: they have a global view of
-- all sources (the absence of rows means "no source filter applied").
-- =============================================================================
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- -----------------------------------------------------------------------------
-- 1. role (5) — dictionary.md 3.15
-- order_source: counter/drive auto-tag their own source; admin/manager NULL
-- (they may create on behalf of any channel); kitchen NULL (read-only on
-- orders, never creates one).
-- -----------------------------------------------------------------------------
INSERT INTO role (code, label, description, default_route, order_source, is_active) VALUES
('admin', 'Administrator', 'Full back-office access: complete catalogue CRUD (incl. deletes), user/role/permission (RBAC) management, stock, stats, order create/deliver/cancel.', '/admin/dashboard', NULL, 1),
('manager', 'Manager', 'Catalogue create/update, ingredient and stock management (restock + inventory), statistics. No user/RBAC administration, no order cancellation.', '/admin/stats', NULL, 1),
('kitchen', 'Kitchen Staff', 'Read-only kitchen display (KDS) of paid orders sorted by paid_at ascending, plus inventory counting. Performs no order status transition.', '/kitchen/display', NULL, 1),
('counter', 'Counter Staff', 'Takes orders at the counter, delivers them to the customer, can cancel. Inventory counting. source auto-tagged as counter.', '/counter/orders', 'counter', 1),
('drive', 'Drive Staff', 'Takes orders at the drive-thru (intercom + headset), delivers them, can cancel. Inventory counting. source auto-tagged as drive.', '/drive/orders', 'drive', 1);
-- -----------------------------------------------------------------------------
-- 2. permission (23) — frozen catalogue, dictionary.md 3.17.
-- code format <resource>.<action>. The catalogue is fixed at the seed and
-- never created through the UI (only assigned to roles via MANAGE_RBAC).
-- -----------------------------------------------------------------------------
INSERT INTO permission (code, label, description) VALUES
('product.create', 'Create product', 'Create a new catalogue product.'),
('product.read', 'Read products', 'View products in the back-office and on order screens.'),
('product.update', 'Update product', 'Edit an existing product (name, price, VAT, availability, etc.).'),
('product.delete', 'Delete product', 'Permanently delete a product when no FK references block it.'),
('menu.create', 'Create menu', 'Create a new menu with its slot configuration.'),
('menu.read', 'Read menus', 'View menus, slots and slot options.'),
('menu.update', 'Update menu', 'Edit an existing menu and its slot configuration.'),
('menu.delete', 'Delete menu', 'Permanently delete a menu when no historical order references it.'),
('category.manage', 'Manage categories', 'Create, update or deactivate product/menu categories.'),
('ingredient.manage', 'Manage ingredients', 'Manage ingredients, product composition and allergen mapping.'),
('stock.read', 'Read stock', 'View ingredient stock levels and movement history.'),
('stock.count', 'Count stock', 'Record a physical inventory count (inventory correction).'),
('stock.manage', 'Manage stock', 'Record restocks (pack deliveries) and manage stock parameters.'),
('order.read', 'Read orders', 'View orders and the preparation display.'),
('order.create', 'Create order', 'Create an order at the counter or drive-thru.'),
('order.deliver', 'Deliver order', 'Mark a paid order as delivered (single-gesture handover).'),
('order.cancel', 'Cancel order', 'Cancel a pending or paid order (restocks ingredients if paid).'),
('user.create', 'Create user', 'Create a new back-office user.'),
('user.read', 'Read users', 'View the list and details of back-office users.'),
('user.update', 'Update user', 'Edit a back-office user (incl. password reset, RGPD anonymisation).'),
('user.deactivate', 'Deactivate user', 'Deactivate a back-office user without deleting the row.'),
('role.manage', 'Manage roles and RBAC', 'Manage roles, role/permission assignments and visible sources.'),
('stats.read', 'Read statistics', 'Access the statistics / KPI dashboard.');
-- -----------------------------------------------------------------------------
-- 3. role_permission — default matrix, dictionary.md 3.17 grants + PROJECT_CONTEXT
-- section 7 + decision D5. Subqueries on role.code / permission.code avoid
-- hardcoded ids.
-- -----------------------------------------------------------------------------
-- admin: ALL 23 permissions (cross join the admin role with the whole catalogue).
INSERT INTO role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM role r
CROSS JOIN permission p
WHERE r.code = 'admin';
-- manager: catalogue create/update + category/ingredient + full stock + stats.
-- NO order.* (incl. no order.cancel per D5), NO user/role admin.
INSERT INTO role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM role r
JOIN permission p ON p.code IN (
'product.create', 'product.read', 'product.update',
'menu.create', 'menu.read', 'menu.update',
'category.manage', 'ingredient.manage',
'stock.read', 'stock.count', 'stock.manage',
'user.read',
'stats.read'
)
WHERE r.code = 'manager';
-- kitchen: read-only orders + read-only catalogue + inventory (read + count).
INSERT INTO role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM role r
JOIN permission p ON p.code IN (
'product.read', 'menu.read',
'stock.read', 'stock.count',
'order.read'
)
WHERE r.code = 'kitchen';
-- counter: read catalogue + full order lifecycle (read/create/deliver/cancel)
-- + inventory (read + count).
INSERT INTO role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM role r
JOIN permission p ON p.code IN (
'product.read', 'menu.read',
'stock.read', 'stock.count',
'order.read', 'order.create', 'order.deliver', 'order.cancel'
)
WHERE r.code = 'counter';
-- drive: identical grant set to counter (read catalogue + full order lifecycle
-- + inventory). The source differs (auto-tagged drive), not the rights.
INSERT INTO role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM role r
JOIN permission p ON p.code IN (
'product.read', 'menu.read',
'stock.read', 'stock.count',
'order.read', 'order.create', 'order.deliver', 'order.cancel'
)
WHERE r.code = 'drive';
-- -----------------------------------------------------------------------------
-- 4. role_visible_source — dictionary.md 3.16.
-- kitchen sees all 3 sources; counter sees kiosk+counter; drive sees drive.
-- admin/manager: no rows -> global view (no source filter).
-- -----------------------------------------------------------------------------
INSERT INTO role_visible_source (role_id, source)
SELECT r.id, s.source
FROM role r
JOIN (
SELECT 'kitchen' AS role_code, 'kiosk' AS source UNION ALL
SELECT 'kitchen', 'counter' UNION ALL
SELECT 'kitchen', 'drive' UNION ALL
SELECT 'counter', 'kiosk' UNION ALL
SELECT 'counter', 'counter' UNION ALL
SELECT 'drive', 'drive'
) s ON s.role_code = r.code;
-- -----------------------------------------------------------------------------
-- 5. allergen (14) — EU INCO Regulation (EU) No 1169/2011, Annex II.
-- dictionary.md 3.8. code = machine code (en), name = French display label.
-- -----------------------------------------------------------------------------
INSERT INTO allergen (code, name, description) VALUES
('gluten', 'Gluten', 'Cereales contenant du gluten (ble, seigle, orge, avoine, epeautre, kamut) et produits a base de ces cereales.'),
('crustaceans', 'Crustaces', 'Crustaces et produits a base de crustaces.'),
('eggs', 'Oeufs', 'Oeufs et produits a base d''oeufs.'),
('fish', 'Poisson', 'Poissons et produits a base de poissons.'),
('peanuts', 'Arachides', 'Arachides et produits a base d''arachides.'),
('soybeans', 'Soja', 'Soja et produits a base de soja.'),
('milk', 'Lait', 'Lait et produits a base de lait (y compris le lactose).'),
('nuts', 'Fruits a coque', 'Fruits a coque : amandes, noisettes, noix, noix de cajou, de pecan, du Bresil, pistaches, noix de Macadamia.'),
('celery', 'Celeri', 'Celeri et produits a base de celeri.'),
('mustard', 'Moutarde', 'Moutarde et produits a base de moutarde.'),
('sesame', 'Graines de sesame', 'Graines de sesame et produits a base de graines de sesame.'),
('sulphites', 'Anhydride sulfureux et sulfites', 'Anhydride sulfureux et sulfites en concentration superieure a 10 mg/kg ou 10 mg/l (exprimes en SO2).'),
('lupin', 'Lupin', 'Lupin et produits a base de lupin.'),
('molluscs', 'Mollusques', 'Mollusques et produits a base de mollusques.');
-- -----------------------------------------------------------------------------
-- 6. user (1) — bootstrap administrator. dictionary.md 3.14.
-- role_id resolved from role.code = 'admin'. pin_hash NULL (no PIN set yet).
--
-- DEV password: WakdoAdmin2026! (argon2id hash below, generated via
-- `docker exec wakdo-app php -r 'echo password_hash("WakdoAdmin2026!",
-- PASSWORD_ARGON2ID);'`). MUST be changed in production — this is a known
-- demo credential and must never reach a real deployment as-is.
-- -----------------------------------------------------------------------------
INSERT INTO user (email, password_hash, pin_hash, first_name, last_name, role_id, is_active)
SELECT
'admin@wakdo.local',
'$argon2id$v=19$m=65536,t=4,p=1$V3dVMi55cDVBYVZPMU1TRw$8iMoNyfC12t7V2CU+YgqwvEb3xNywm7PUSIoNMgRdvc',
NULL,
'Wakdo',
'Admin',
r.id,
1
FROM role r
WHERE r.code = 'admin';

195
db/seeds/0002_catalogue.sql Normal file
View file

@ -0,0 +1,195 @@
-- =============================================================================
-- Wakdo — Seed 0002 : Catalogue (reference / demo data)
-- =============================================================================
-- Purpose : Populate the Catalogue sub-domain (category, product, menu,
-- menu_slot, menu_slot_option) from the school JSON sources.
-- Sources : docs/merise/_sources/categories.json (9 categories)
-- docs/merise/_sources/produits.json (menus + 53 products)
-- src/public/borne/data/produits.json (cents prices + clean paths,
-- used for cross-check)
-- Phase : P2 — demo seed, assumes a fresh schema (0001_init_schema.sql).
--
-- Conventions:
-- - Monetary amounts are INT in CENTS (euros float x 100, rounded).
-- - vat_rate is per-mille: 100 = 10% (default), 55 = 5.5% for products in
-- resealable containers (bottled water, bottled juices) — dictionary note 9.
-- - image_path is a relative path under the public root, normalised to
-- assets/images/produits/<category>/<file>.png (dictionary note 8).
-- - Menus go to the `menu` table (NOT `product`); every other category goes
-- to `product`. The "burgers" category items are the anchor products that
-- menus reference via burger_product_id.
-- - price_maxi_cents = price_normal_cents + 150 (Maxi format, +1.50 EUR).
-- - Foreign keys are resolved by subquery on natural keys (slug / name)
-- rather than hardcoded ids.
-- - Insertion order respects FK dependencies:
-- category -> product -> menu -> menu_slot -> menu_slot_option.
-- =============================================================================
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- -----------------------------------------------------------------------------
-- 1. category (9) — root table, source order = display_order
-- -----------------------------------------------------------------------------
INSERT INTO category (name, slug, image_path, display_order, is_active) VALUES
('menus', 'menus', 'assets/images/categories/menus.png', 1, 1),
('boissons', 'boissons', 'assets/images/categories/boissons.png', 2, 1),
('burgers', 'burgers', 'assets/images/categories/burgers.png', 3, 1),
('frites', 'frites', 'assets/images/categories/frites.png', 4, 1),
('encas', 'encas', 'assets/images/categories/encas.png', 5, 1),
('wraps', 'wraps', 'assets/images/categories/wraps.png', 6, 1),
('salades', 'salades', 'assets/images/categories/salades.png', 7, 1),
('desserts', 'desserts', 'assets/images/categories/desserts.png', 8, 1),
('sauces', 'sauces', 'assets/images/categories/sauces.png', 9, 1);
-- -----------------------------------------------------------------------------
-- 2. product — every non-menu item (53 rows)
-- category_id resolved via subquery on category.slug.
-- display_order follows source order within each category.
-- vat_rate defaults to 100; 55 only for resealable-container drinks
-- (Eau, Jus d'Orange, Jus de Pommes Bio) per dictionary note 9.
-- -----------------------------------------------------------------------------
-- 2.a burgers (anchor products for menus)
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='burgers'), 'Le 280', 680, 100, 'assets/images/produits/burgers/280.png', 1, 1),
((SELECT id FROM category WHERE slug='burgers'), 'Big Tasty', 860, 100, 'assets/images/produits/burgers/big-tasty-1-viande.png', 1, 2),
((SELECT id FROM category WHERE slug='burgers'), 'Big Tasty Bacon', 890, 100, 'assets/images/produits/burgers/big-tasty-bacon-1-viande.png', 1, 3),
((SELECT id FROM category WHERE slug='burgers'), 'Big Mac', 600, 100, 'assets/images/produits/burgers/bigmac.png', 1, 4),
((SELECT id FROM category WHERE slug='burgers'), 'CBO', 890, 100, 'assets/images/produits/burgers/cbo.png', 1, 5),
((SELECT id FROM category WHERE slug='burgers'), 'MC Chicken', 730, 100, 'assets/images/produits/burgers/mcchicken.png', 1, 6),
((SELECT id FROM category WHERE slug='burgers'), 'MC Crispy', 530, 100, 'assets/images/produits/burgers/mccrispy.png', 1, 7),
((SELECT id FROM category WHERE slug='burgers'), 'MC Fish', 485, 100, 'assets/images/produits/burgers/mcfish.png', 1, 8),
((SELECT id FROM category WHERE slug='burgers'), 'Royal Bacon', 510, 100, 'assets/images/produits/burgers/royalbacon.png', 1, 9),
((SELECT id FROM category WHERE slug='burgers'), 'Royal Cheese', 440, 100, 'assets/images/produits/burgers/royalcheese.png', 1, 10),
((SELECT id FROM category WHERE slug='burgers'), 'Royal Deluxe', 540, 100, 'assets/images/produits/burgers/royaldeluxe.png', 1, 11),
((SELECT id FROM category WHERE slug='burgers'), 'Signature BBQ Beef 2 viandes', 1140, 100, 'assets/images/produits/burgers/signature-bbq-beef-2-viandes.png', 1, 12),
((SELECT id FROM category WHERE slug='burgers'), 'Signature Beef BBQ', 1030, 100, 'assets/images/produits/burgers/signature-beef-bbq-burger-1-viande.png', 1, 13);
-- 2.b boissons (Eau + the two bottled juices are resealable-container = vat_rate 55)
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='boissons'), 'Coca Cola', 190, 100, 'assets/images/produits/boissons/coca-cola.png', 1, 1),
((SELECT id FROM category WHERE slug='boissons'), 'Coca Sans Sucres', 190, 100, 'assets/images/produits/boissons/coca-sans-sucres.png', 1, 2),
((SELECT id FROM category WHERE slug='boissons'), 'Eau', 100, 55, 'assets/images/produits/boissons/eau.png', 1, 3),
((SELECT id FROM category WHERE slug='boissons'), 'Fanta Orange', 190, 100, 'assets/images/produits/boissons/fanta.png', 1, 4),
((SELECT id FROM category WHERE slug='boissons'), 'Ice Tea Peche', 190, 100, 'assets/images/produits/boissons/ice-tea-peche.png', 1, 5),
((SELECT id FROM category WHERE slug='boissons'), 'Ice Tea Citron', 190, 100, 'assets/images/produits/boissons/the-vert-citron-sans-sucres.png', 1, 6),
((SELECT id FROM category WHERE slug='boissons'), 'Jus d''Orange', 210, 55, 'assets/images/produits/boissons/jus-orange.png', 1, 7),
((SELECT id FROM category WHERE slug='boissons'), 'Jus de Pommes Bio', 230, 55, 'assets/images/produits/boissons/jus-pomme-bio.png', 1, 8);
-- 2.c frites
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='frites'), 'Petite Frite', 145, 100, 'assets/images/produits/frites/petite-frite.png', 1, 1),
((SELECT id FROM category WHERE slug='frites'), 'Moyenne Frite', 275, 100, 'assets/images/produits/frites/moyenne-frite.png', 1, 2),
((SELECT id FROM category WHERE slug='frites'), 'Grande Frite', 350, 100, 'assets/images/produits/frites/grande-frite.png', 1, 3),
((SELECT id FROM category WHERE slug='frites'), 'Potatoes', 215, 100, 'assets/images/produits/frites/potatoes.png', 1, 4),
((SELECT id FROM category WHERE slug='frites'), 'Grande Potatoes', 340, 100, 'assets/images/produits/frites/grande-potatoes.png', 1, 5);
-- 2.d encas
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='encas'), 'Cheeseburger', 260, 100, 'assets/images/produits/encas/cheeseburger.png', 1, 1),
((SELECT id FROM category WHERE slug='encas'), 'Croc MCdo', 320, 100, 'assets/images/produits/encas/croc-mc-do.png', 1, 2),
((SELECT id FROM category WHERE slug='encas'), 'Nuggets x4', 420, 100, 'assets/images/produits/encas/nuggets-4.png', 1, 3),
((SELECT id FROM category WHERE slug='encas'), 'Nuggets x20', 1300, 100, 'assets/images/produits/encas/nuggets-20.png', 1, 4);
-- 2.e wraps
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='wraps'), 'MC Wrap Chevre', 310, 100, 'assets/images/produits/wraps/mcwrap-chevre.png', 1, 1),
((SELECT id FROM category WHERE slug='wraps'), 'MC Wrap Poulet Bacon', 330, 100, 'assets/images/produits/wraps/mcwrap-poulet-bacon.png', 1, 2),
((SELECT id FROM category WHERE slug='wraps'), 'Ptit Wrap Chevre', 260, 100, 'assets/images/produits/wraps/ptit-wrap-chevre.png', 1, 3),
((SELECT id FROM category WHERE slug='wraps'), 'Ptit Wrap Ranch', 260, 100, 'assets/images/produits/wraps/ptit-wrap-ranch.png', 1, 4);
-- 2.f salades
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='salades'), 'Petite Salade', 330, 100, 'assets/images/produits/salades/petite-salade.png', 1, 1),
((SELECT id FROM category WHERE slug='salades'), 'Cesar Classic', 880, 100, 'assets/images/produits/salades/salade-classic-caesar.png', 1, 2),
((SELECT id FROM category WHERE slug='salades'), 'Italienne Mozza', 880, 100, 'assets/images/produits/salades/salade-italian-mozza.png', 1, 3);
-- 2.g desserts
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='desserts'), 'Brownie', 260, 100, 'assets/images/produits/desserts/brownies.png', 1, 1),
((SELECT id FROM category WHERE slug='desserts'), 'Cheesecake chocolat M&M''S', 310, 100, 'assets/images/produits/desserts/cheesecake-choconuts-m&m-s.png', 1, 2),
((SELECT id FROM category WHERE slug='desserts'), 'Cheesecake Fraise', 310, 100, 'assets/images/produits/desserts/cheesecake-fraise.png', 1, 3),
((SELECT id FROM category WHERE slug='desserts'), 'Cookie', 320, 100, 'assets/images/produits/desserts/cookie.png', 1, 4),
((SELECT id FROM category WHERE slug='desserts'), 'Donut', 260, 100, 'assets/images/produits/desserts/doghnut.png', 1, 5),
((SELECT id FROM category WHERE slug='desserts'), 'Macarons', 270, 100, 'assets/images/produits/desserts/macarons.png', 1, 6),
((SELECT id FROM category WHERE slug='desserts'), 'MC Fleury', 440, 100, 'assets/images/produits/desserts/mcfleury.png', 1, 7),
((SELECT id FROM category WHERE slug='desserts'), 'Muffin', 360, 100, 'assets/images/produits/desserts/muffin.png', 1, 8),
((SELECT id FROM category WHERE slug='desserts'), 'Sunday', 100, 100, 'assets/images/produits/desserts/sunday.png', 1, 9);
-- 2.h sauces
INSERT INTO product (category_id, name, price_cents, vat_rate, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='sauces'), 'Classic Barbecue', 70, 100, 'assets/images/produits/sauces/classic-barbecue.png', 1, 1),
((SELECT id FROM category WHERE slug='sauces'), 'Classic Moutarde', 70, 100, 'assets/images/produits/sauces/classic-moutarde.png', 1, 2),
((SELECT id FROM category WHERE slug='sauces'), 'Creamy Deluxe', 70, 100, 'assets/images/produits/sauces/cremy-deluxe.png', 1, 3),
((SELECT id FROM category WHERE slug='sauces'), 'Ketchup', 70, 100, 'assets/images/produits/sauces/ketchup.png', 1, 4),
((SELECT id FROM category WHERE slug='sauces'), 'Chinoise', 70, 100, 'assets/images/produits/sauces/sauce-chinoise.png', 1, 5),
((SELECT id FROM category WHERE slug='sauces'), 'Curry', 70, 100, 'assets/images/produits/sauces/sauce-curry.png', 1, 6),
((SELECT id FROM category WHERE slug='sauces'), 'Pommes Frites', 70, 100, 'assets/images/produits/sauces/sauce-pommes-frite.png', 1, 7);
-- -----------------------------------------------------------------------------
-- 3. menu (13) — the "menus" category items.
-- category_id = the menus category.
-- burger_product_id resolved by matching the anchor burger name
-- ("Menu Le 280" -> product "Le 280", etc.).
-- price_normal_cents from source; price_maxi_cents = normal + 150.
-- -----------------------------------------------------------------------------
INSERT INTO menu (category_id, burger_product_id, name, price_normal_cents, price_maxi_cents, image_path, is_available, display_order) VALUES
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Le 280'), 'Menu Le 280', 880, 1030, 'assets/images/produits/burgers/280.png', 1, 1),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Big Tasty'), 'Menu Big Tasty', 1060, 1210, 'assets/images/produits/burgers/big-tasty-1-viande.png', 1, 2),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Big Tasty Bacon'), 'Menu Big Tasty Bacon', 1090, 1240, 'assets/images/produits/burgers/big-tasty-bacon-1-viande.png', 1, 3),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Big Mac'), 'Menu Big Mac', 800, 950, 'assets/images/produits/burgers/bigmac.png', 1, 4),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='CBO'), 'Menu CBO', 1090, 1240, 'assets/images/produits/burgers/cbo.png', 1, 5),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='MC Chicken'), 'Menu MC Chicken', 930, 1080, 'assets/images/produits/burgers/mcchicken.png', 1, 6),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='MC Crispy'), 'Menu MC Crispy', 720, 870, 'assets/images/produits/burgers/mccrispy.png', 1, 7),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='MC Fish'), 'Menu MC Fish', 720, 870, 'assets/images/produits/burgers/mcfish.png', 1, 8),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Royal Bacon'), 'Menu Royal Bacon', 705, 855, 'assets/images/produits/burgers/royalbacon.png', 1, 9),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Royal Cheese'), 'Menu Royal Cheese', 640, 790, 'assets/images/produits/burgers/royalcheese.png', 1, 10),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Royal Deluxe'), 'Menu Royal Deluxe', 740, 890, 'assets/images/produits/burgers/royaldeluxe.png', 1, 11),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Signature BBQ Beef 2 viandes'), 'Menu Signature BBQ Beef 2 viandes', 1350, 1500, 'assets/images/produits/burgers/signature-bbq-beef-2-viandes.png', 1, 12),
((SELECT id FROM category WHERE slug='menus'), (SELECT id FROM product WHERE name='Signature Beef BBQ'), 'Menu Signature Beef BBQ', 1190, 1340, 'assets/images/produits/burgers/signature-beef-bbq-burger-1-viande.png', 1, 13);
-- -----------------------------------------------------------------------------
-- 4. menu_slot — three standard slots per menu:
-- drink (required), side (required), sauce (optional).
-- One INSERT per slot_type, fanning out over all 13 menus via SELECT.
-- -----------------------------------------------------------------------------
INSERT INTO menu_slot (menu_id, name, slot_type, is_required, display_order)
SELECT m.id, 'Boisson', 'drink', 1, 1
FROM menu m
JOIN category c ON c.id = m.category_id AND c.slug = 'menus';
INSERT INTO menu_slot (menu_id, name, slot_type, is_required, display_order)
SELECT m.id, 'Accompagnement', 'side', 1, 2
FROM menu m
JOIN category c ON c.id = m.category_id AND c.slug = 'menus';
INSERT INTO menu_slot (menu_id, name, slot_type, is_required, display_order)
SELECT m.id, 'Sauce', 'sauce', 0, 3
FROM menu m
JOIN category c ON c.id = m.category_id AND c.slug = 'menus';
-- -----------------------------------------------------------------------------
-- 5. menu_slot_option — eligible products per slot:
-- drink slot -> all products in category 'boissons'
-- side slot -> all products in category 'frites'
-- sauce slot -> all products in category 'sauces'
-- Composite PK (menu_slot_id, product_id) is naturally satisfied: each
-- (slot, product) pair is unique because slots are unique per menu.
-- -----------------------------------------------------------------------------
INSERT INTO menu_slot_option (menu_slot_id, product_id)
SELECT ms.id, p.id
FROM menu_slot ms
JOIN product p ON p.category_id = (SELECT id FROM category WHERE slug='boissons')
WHERE ms.slot_type = 'drink';
INSERT INTO menu_slot_option (menu_slot_id, product_id)
SELECT ms.id, p.id
FROM menu_slot ms
JOIN product p ON p.category_id = (SELECT id FROM category WHERE slug='frites')
WHERE ms.slot_type = 'side';
INSERT INTO menu_slot_option (menu_slot_id, product_id)
SELECT ms.id, p.id
FROM menu_slot ms
JOIN product p ON p.category_id = (SELECT id FROM category WHERE slug='sauces')
WHERE ms.slot_type = 'sauce';

View file

@ -229,7 +229,7 @@ Reseaux :
- Authentification sessions securisees (hash bcrypt/argon2, protection CSRF, fixation session) — duree de session adaptee a un poste complet d'equipier (idle timeout 4h, absolute timeout 10h)
- 5 roles RBAC seed : `admin`, `manager`, `kitchen`, `counter`, `drive` (RBAC permission-driven, 23 permissions figees au seed ; roles personnalises possibles)
- **Admin** : CRUD complet catalogue (+ suppressions), gestion utilisateurs, roles et permissions (RBAC), stats
- **Manager** : catalogue (create/update), stock (reappro + inventaire), statistiques ; pas d'acces utilisateurs ni RBAC
- **Manager** : catalogue (create/update), stock (reappro + inventaire), statistiques ; utilisateurs en **lecture seule** (`user.read`, pas de creation/modification/desactivation), pas d'acces RBAC
- **Kitchen** : file des commandes `paid` triee par `paid_at` croissant, en **lecture seule** (KDS visuel) ; inventaire
- **Counter** / **Drive** : saisir une commande (comptoir / drive-thru via casque/intercom), bouton "declarer livree" (geste unique `paid -> delivered`), annuler ; `source` auto-tague depuis `role.order_source` ; inventaire
- Upload images produits (validation type MIME + taille + stockage dans volume `wakdo_uploads`)

View file

@ -42,7 +42,7 @@ multi-canal. Chaque acteur candidat est confronte au perimetre reel.
| **Client (borne kiosk)** | Retenu (acteur `CUSTOMER`) | Acteur central du Bloc 1. Compose et valide une commande sur la borne tactile autonome (canal `kiosk`). **Non authentifie**. |
| **Accueil** | **Scinde** en `counter` et `drive` | Le besoin "Accueil" recouvre deux canaux operationnels distincts : le comptoir (`counter`) et le drive (`drive`). Le v0.2 les separe car le tag `source` de la commande et le filtre de dashboard (`role_visible_source`) different. Tous deux saisissent des commandes, les remettent et les annulent. |
| **Preparation** | Retenu, renomme `kitchen` | Role RBAC `kitchen`. Voit la file des commandes `paid` triees par `paid_at` croissant. **Lecture seule** : ne declenche aucune transition de statut (le KDS est un dispositif visuel ; la remise revient a `counter`/`drive`). |
| **Administration** | **Scinde** en `admin` et `manager` | Le v0.1 fusionnait "Manager/Admin". Le v0.2 distingue : `admin` (gestion des utilisateurs, des roles et permissions, suppressions catalogue) et `manager` (catalogue create/update, stock/reappro, stats), sans acces aux utilisateurs ni au RBAC. Resout le point ouvert v0.1 "Manager vs Admin". |
| **Administration** | **Scinde** en `admin` et `manager` | Le v0.1 fusionnait "Manager/Admin". Le v0.2 distingue : `admin` (gestion des utilisateurs, des roles et permissions, suppressions catalogue) et `manager` (catalogue create/update, stock/reappro, stats), utilisateurs en lecture seule (`user.read`) et sans acces au RBAC. Resout le point ouvert v0.1 "Manager vs Admin". |
| **Caisse** | Ecarte (recouvert par `counter`/`drive`) | Aucun role `caisse` n'existe. L'encaissement est atomique a la creation de commande (saisie du numero = substitut de paiement) ; il est realise par le Client (kiosk) ou par `counter`/`drive` (back-office). Resout le point ouvert v0.1 "Caisse absente du RBAC". |
| **Systeme** | Retenu (acteur `SYS`) | Logique interne (generation du numero, reponse API de confirmation). Apparait dans le MCT (3.4 `DISPLAY_CONFIRMATION`) ; non represente comme acteur humain au diagramme. |