From d06c0b22fb546bd829d97c33af2d3ec5b7710220 Mon Sep 17 00:00:00 2001 From: Imugiii Date: Mon, 15 Jun 2026 10:39:12 +0000 Subject: [PATCH] docs(merise): add relational schema diagrams to MLD (4 sub-domains, Mermaid + SVG) --- docs/merise/_diagrams/mld-catalogue.mmd | 46 +++ docs/merise/_diagrams/mld-catalogue.svg | 1 + .../_diagrams/mld-ingredients-stock.mmd | 59 ++++ .../_diagrams/mld-ingredients-stock.svg | 1 + docs/merise/_diagrams/mld-order.mmd | 72 +++++ docs/merise/_diagrams/mld-order.svg | 1 + docs/merise/_diagrams/mld-rbac.mmd | 61 ++++ docs/merise/_diagrams/mld-rbac.svg | 1 + docs/merise/mld.md | 273 ++++++++++++++++++ 9 files changed, 515 insertions(+) create mode 100644 docs/merise/_diagrams/mld-catalogue.mmd create mode 100644 docs/merise/_diagrams/mld-catalogue.svg create mode 100644 docs/merise/_diagrams/mld-ingredients-stock.mmd create mode 100644 docs/merise/_diagrams/mld-ingredients-stock.svg create mode 100644 docs/merise/_diagrams/mld-order.mmd create mode 100644 docs/merise/_diagrams/mld-order.svg create mode 100644 docs/merise/_diagrams/mld-rbac.mmd create mode 100644 docs/merise/_diagrams/mld-rbac.svg diff --git a/docs/merise/_diagrams/mld-catalogue.mmd b/docs/merise/_diagrams/mld-catalogue.mmd new file mode 100644 index 0000000..7519fdb --- /dev/null +++ b/docs/merise/_diagrams/mld-catalogue.mmd @@ -0,0 +1,46 @@ +erDiagram + category { + int id PK + varchar name UK + varchar slug UK + smallint display_order + tinyint is_active + } + product { + int id PK + int category_id FK + varchar name + int price_cents + smallint vat_rate + tinyint is_available + smallint display_order + } + menu { + int id PK + int category_id FK + int burger_product_id FK + varchar name + int price_normal_cents + int price_maxi_cents + tinyint is_available + smallint display_order + } + menu_slot { + int id PK + int menu_id FK + varchar name + enum slot_type + tinyint is_required + smallint display_order + } + menu_slot_option { + int menu_slot_id PK,FK + int product_id PK,FK + } + + category ||--o{ product : "category_id (RESTRICT)" + category ||--o{ menu : "category_id (RESTRICT)" + product ||--o{ menu : "burger_product_id (RESTRICT)" + menu ||--o{ menu_slot : "menu_id (CASCADE)" + menu_slot ||--o{ menu_slot_option : "menu_slot_id (CASCADE)" + product ||--o{ menu_slot_option : "product_id (RESTRICT)" diff --git a/docs/merise/_diagrams/mld-catalogue.svg b/docs/merise/_diagrams/mld-catalogue.svg new file mode 100644 index 0000000..7d501b3 --- /dev/null +++ b/docs/merise/_diagrams/mld-catalogue.svg @@ -0,0 +1 @@ +

category_id (RESTRICT)

category_id (RESTRICT)

burger_product_id (RESTRICT)

menu_id (CASCADE)

menu_slot_id (CASCADE)

product_id (RESTRICT)

category

int

id

PK

varchar

name

UK

varchar

slug

UK

smallint

display_order

tinyint

is_active

product

int

id

PK

int

category_id

FK

varchar

name

int

price_cents

smallint

vat_rate

tinyint

is_available

smallint

display_order

menu

int

id

PK

int

category_id

FK

int

burger_product_id

FK

varchar

name

int

price_normal_cents

int

price_maxi_cents

tinyint

is_available

smallint

display_order

menu_slot

int

id

PK

int

menu_id

FK

varchar

name

enum

slot_type

tinyint

is_required

smallint

display_order

menu_slot_option

int

menu_slot_id

PK,FK

int

product_id

PK,FK

\ No newline at end of file diff --git a/docs/merise/_diagrams/mld-ingredients-stock.mmd b/docs/merise/_diagrams/mld-ingredients-stock.mmd new file mode 100644 index 0000000..556f22f --- /dev/null +++ b/docs/merise/_diagrams/mld-ingredients-stock.mmd @@ -0,0 +1,59 @@ +erDiagram + ingredient { + int id PK + varchar name UK + varchar unit + int stock_quantity + int stock_capacity + smallint pack_size + smallint low_stock_pct + smallint critical_stock_pct + tinyint is_active + } + product_ingredient { + int product_id PK,FK + int ingredient_id PK,FK + smallint quantity_normal + smallint quantity_maxi + tinyint is_removable + tinyint is_addable + int extra_price_cents + } + allergen { + int id PK + varchar code UK + varchar name + } + ingredient_allergen { + int ingredient_id PK,FK + int allergen_id PK,FK + } + stock_movement { + int id PK + int ingredient_id FK + enum movement_type + int delta + int order_id FK + int user_id FK + varchar note + } + product { + int id PK + varchar name + } + customer_order { + int id PK + varchar order_number + } + user { + int id PK + varchar email + } + + product ||--o{ product_ingredient : "product_id (CASCADE)" + ingredient ||--o{ product_ingredient : "ingredient_id (RESTRICT)" + ingredient ||--o{ ingredient_allergen : "ingredient_id (CASCADE)" + allergen ||--o{ ingredient_allergen : "allergen_id (RESTRICT)" + ingredient ||--o{ stock_movement : "ingredient_id (RESTRICT)" + customer_order ||--o{ stock_movement : "order_id (SET NULL, nullable)" + user ||--o{ stock_movement : "user_id (SET NULL, nullable)" diff --git a/docs/merise/_diagrams/mld-ingredients-stock.svg b/docs/merise/_diagrams/mld-ingredients-stock.svg new file mode 100644 index 0000000..22a377f --- /dev/null +++ b/docs/merise/_diagrams/mld-ingredients-stock.svg @@ -0,0 +1 @@ +

product_id (CASCADE)

ingredient_id (RESTRICT)

ingredient_id (CASCADE)

allergen_id (RESTRICT)

ingredient_id (RESTRICT)

order_id (SET NULL, nullable)

user_id (SET NULL, nullable)

ingredient

int

id

PK

varchar

name

UK

varchar

unit

int

stock_quantity

int

stock_capacity

smallint

pack_size

smallint

low_stock_pct

smallint

critical_stock_pct

tinyint

is_active

product_ingredient

int

product_id

PK,FK

int

ingredient_id

PK,FK

smallint

quantity_normal

smallint

quantity_maxi

tinyint

is_removable

tinyint

is_addable

int

extra_price_cents

allergen

int

id

PK

varchar

code

UK

varchar

name

ingredient_allergen

int

ingredient_id

PK,FK

int

allergen_id

PK,FK

stock_movement

int

id

PK

int

ingredient_id

FK

enum

movement_type

int

delta

int

order_id

FK

int

user_id

FK

varchar

note

product

int

id

PK

varchar

name

customer_order

int

id

PK

varchar

order_number

user

int

id

PK

varchar

email

\ No newline at end of file diff --git a/docs/merise/_diagrams/mld-order.mmd b/docs/merise/_diagrams/mld-order.mmd new file mode 100644 index 0000000..f6ec1e3 --- /dev/null +++ b/docs/merise/_diagrams/mld-order.mmd @@ -0,0 +1,72 @@ +erDiagram + customer_order { + int id PK + varchar order_number UK + varchar idempotency_key UK + enum source + int acting_user_id FK + enum service_mode + enum status + int total_ht_cents + int total_vat_cents + int total_ttc_cents + datetime paid_at + datetime delivered_at + datetime cancelled_at + } + order_item { + int id PK + int order_id FK + enum item_type + int product_id FK + int menu_id FK + enum format + varchar label_snapshot + int unit_price_cents_snapshot + smallint vat_rate_snapshot + smallint quantity + } + order_item_selection { + int id PK + int order_item_id FK + int menu_slot_id FK + int product_id FK + varchar label_snapshot + } + order_item_modifier { + int id PK + int order_item_id FK + int ingredient_id FK + enum action + int extra_price_cents + } + user { + int id PK + varchar email + } + product { + int id PK + varchar name + } + menu { + int id PK + varchar name + } + menu_slot { + int id PK + varchar name + } + ingredient { + int id PK + varchar name + } + + user ||--o{ customer_order : "acting_user_id (SET NULL, nullable)" + customer_order ||--o{ order_item : "order_id (CASCADE)" + product ||--o{ order_item : "product_id (RESTRICT, polymorphic)" + menu ||--o{ order_item : "menu_id (RESTRICT, polymorphic)" + order_item ||--o{ order_item_selection : "order_item_id (CASCADE)" + menu_slot ||--o{ order_item_selection : "menu_slot_id (RESTRICT)" + product ||--o{ order_item_selection : "product_id (RESTRICT)" + order_item ||--o{ order_item_modifier : "order_item_id (CASCADE)" + ingredient ||--o{ order_item_modifier : "ingredient_id (RESTRICT)" diff --git a/docs/merise/_diagrams/mld-order.svg b/docs/merise/_diagrams/mld-order.svg new file mode 100644 index 0000000..3cbc2f8 --- /dev/null +++ b/docs/merise/_diagrams/mld-order.svg @@ -0,0 +1 @@ +

acting_user_id (SET NULL, nullable)

order_id (CASCADE)

product_id (RESTRICT, polymorphic)

menu_id (RESTRICT, polymorphic)

order_item_id (CASCADE)

menu_slot_id (RESTRICT)

product_id (RESTRICT)

order_item_id (CASCADE)

ingredient_id (RESTRICT)

customer_order

int

id

PK

varchar

order_number

UK

varchar

idempotency_key

UK

enum

source

int

acting_user_id

FK

enum

service_mode

enum

status

int

total_ht_cents

int

total_vat_cents

int

total_ttc_cents

datetime

paid_at

datetime

delivered_at

datetime

cancelled_at

order_item

int

id

PK

int

order_id

FK

enum

item_type

int

product_id

FK

int

menu_id

FK

enum

format

varchar

label_snapshot

int

unit_price_cents_snapshot

smallint

vat_rate_snapshot

smallint

quantity

order_item_selection

int

id

PK

int

order_item_id

FK

int

menu_slot_id

FK

int

product_id

FK

varchar

label_snapshot

order_item_modifier

int

id

PK

int

order_item_id

FK

int

ingredient_id

FK

enum

action

int

extra_price_cents

user

int

id

PK

varchar

email

product

int

id

PK

varchar

name

menu

int

id

PK

varchar

name

menu_slot

int

id

PK

varchar

name

ingredient

int

id

PK

varchar

name

\ No newline at end of file diff --git a/docs/merise/_diagrams/mld-rbac.mmd b/docs/merise/_diagrams/mld-rbac.mmd new file mode 100644 index 0000000..2bc76b8 --- /dev/null +++ b/docs/merise/_diagrams/mld-rbac.mmd @@ -0,0 +1,61 @@ +erDiagram + role { + int id PK + varchar code UK + varchar label + varchar default_route + enum order_source + tinyint is_active + } + user { + int id PK + varchar email UK + varchar password_hash + varchar pin_hash + varchar first_name + varchar last_name + int role_id FK + tinyint is_active + smallint failed_login_attempts + datetime lockout_until + datetime anonymized_at + } + role_visible_source { + int role_id PK,FK + enum source PK + } + permission { + int id PK + varchar code UK + varchar label + } + role_permission { + int role_id PK,FK + int permission_id PK,FK + } + audit_log { + int id PK + int actor_user_id FK + int actor_role_id FK + varchar action_code + varchar entity_type + int entity_id + varchar summary + json details + datetime created_at + } + login_throttle { + int id PK + varchar ip_address UK + smallint failed_attempts + datetime window_started_at + datetime lockout_until + datetime last_attempt_at + } + + role ||--o{ user : "role_id (RESTRICT)" + role ||--o{ role_visible_source : "role_id (CASCADE)" + role ||--o{ role_permission : "role_id (CASCADE)" + permission ||--o{ role_permission : "permission_id (CASCADE)" + user ||--o{ audit_log : "actor_user_id (SET NULL, nullable)" + role ||--o{ audit_log : "actor_role_id (SET NULL, nullable)" diff --git a/docs/merise/_diagrams/mld-rbac.svg b/docs/merise/_diagrams/mld-rbac.svg new file mode 100644 index 0000000..ad68a9f --- /dev/null +++ b/docs/merise/_diagrams/mld-rbac.svg @@ -0,0 +1 @@ +

role_id (RESTRICT)

role_id (CASCADE)

role_id (CASCADE)

permission_id (CASCADE)

actor_user_id (SET NULL, nullable)

actor_role_id (SET NULL, nullable)

role

int

id

PK

varchar

code

UK

varchar

label

varchar

default_route

enum

order_source

tinyint

is_active

user

int

id

PK

varchar

email

UK

varchar

password_hash

varchar

pin_hash

varchar

first_name

varchar

last_name

int

role_id

FK

tinyint

is_active

smallint

failed_login_attempts

datetime

lockout_until

datetime

anonymized_at

role_visible_source

int

role_id

PK,FK

enum

source

PK

permission

int

id

PK

varchar

code

UK

varchar

label

role_permission

int

role_id

PK,FK

int

permission_id

PK,FK

audit_log

int

id

PK

int

actor_user_id

FK

int

actor_role_id

FK

varchar

action_code

varchar

entity_type

int

entity_id

varchar

summary

json

details

datetime

created_at

login_throttle

int

id

PK

varchar

ip_address

UK

smallint

failed_attempts

datetime

window_started_at

datetime

lockout_until

datetime

last_attempt_at

\ No newline at end of file diff --git a/docs/merise/mld.md b/docs/merise/mld.md index 5cd6563..c701f63 100644 --- a/docs/merise/mld.md +++ b/docs/merise/mld.md @@ -97,6 +97,279 @@ in addition to the composite FK PK. Applied to `product_ingredient`. Tables are ordered by dependency (no-FK tables first, then tables that depend on them). +### Relational diagrams (by sub-domain) + +The relational schema is shown as four Mermaid `erDiagram` views, one per sub-domain (same +decomposition as the MCD; a single 21-table diagram would not lay out cleanly). These differ +from the MCD: associative entities are resolved into join tables with composite PKs, the +`order_item` polymorphism appears as two nullable FKs (`product_id` / `menu_id`), and every +foreign key is explicit. Audit timestamps (`created_at` / `updated_at`) are present on most +tables (see the per-table sections below) but omitted from the diagrams to keep them readable. +Relationship labels carry the FK column and its `ON DELETE` behaviour. Cross-sub-domain FK +targets are shown as stub tables (id + name). Portable SVG renders live in `_diagrams/` +(`mld-catalogue.svg`, `mld-ingredients-stock.svg`, `mld-order.svg`, `mld-rbac.svg`). + +#### Catalogue + +```mermaid +erDiagram + category { + int id PK + varchar name UK + varchar slug UK + smallint display_order + tinyint is_active + } + product { + int id PK + int category_id FK + varchar name + int price_cents + smallint vat_rate + tinyint is_available + smallint display_order + } + menu { + int id PK + int category_id FK + int burger_product_id FK + varchar name + int price_normal_cents + int price_maxi_cents + tinyint is_available + smallint display_order + } + menu_slot { + int id PK + int menu_id FK + varchar name + enum slot_type + tinyint is_required + smallint display_order + } + menu_slot_option { + int menu_slot_id PK,FK + int product_id PK,FK + } + + category ||--o{ product : "category_id (RESTRICT)" + category ||--o{ menu : "category_id (RESTRICT)" + product ||--o{ menu : "burger_product_id (RESTRICT)" + menu ||--o{ menu_slot : "menu_id (CASCADE)" + menu_slot ||--o{ menu_slot_option : "menu_slot_id (CASCADE)" + product ||--o{ menu_slot_option : "product_id (RESTRICT)" +``` + +#### Ingredients & Stock + +```mermaid +erDiagram + ingredient { + int id PK + varchar name UK + varchar unit + int stock_quantity + int stock_capacity + smallint pack_size + smallint low_stock_pct + smallint critical_stock_pct + tinyint is_active + } + product_ingredient { + int product_id PK,FK + int ingredient_id PK,FK + smallint quantity_normal + smallint quantity_maxi + tinyint is_removable + tinyint is_addable + int extra_price_cents + } + allergen { + int id PK + varchar code UK + varchar name + } + ingredient_allergen { + int ingredient_id PK,FK + int allergen_id PK,FK + } + stock_movement { + int id PK + int ingredient_id FK + enum movement_type + int delta + int order_id FK + int user_id FK + varchar note + } + product { + int id PK + varchar name + } + customer_order { + int id PK + varchar order_number + } + user { + int id PK + varchar email + } + + product ||--o{ product_ingredient : "product_id (CASCADE)" + ingredient ||--o{ product_ingredient : "ingredient_id (RESTRICT)" + ingredient ||--o{ ingredient_allergen : "ingredient_id (CASCADE)" + allergen ||--o{ ingredient_allergen : "allergen_id (RESTRICT)" + ingredient ||--o{ stock_movement : "ingredient_id (RESTRICT)" + customer_order ||--o{ stock_movement : "order_id (SET NULL, nullable)" + user ||--o{ stock_movement : "user_id (SET NULL, nullable)" +``` + +#### Order + +```mermaid +erDiagram + customer_order { + int id PK + varchar order_number UK + varchar idempotency_key UK + enum source + int acting_user_id FK + enum service_mode + enum status + int total_ht_cents + int total_vat_cents + int total_ttc_cents + datetime paid_at + datetime delivered_at + datetime cancelled_at + } + order_item { + int id PK + int order_id FK + enum item_type + int product_id FK + int menu_id FK + enum format + varchar label_snapshot + int unit_price_cents_snapshot + smallint vat_rate_snapshot + smallint quantity + } + order_item_selection { + int id PK + int order_item_id FK + int menu_slot_id FK + int product_id FK + varchar label_snapshot + } + order_item_modifier { + int id PK + int order_item_id FK + int ingredient_id FK + enum action + int extra_price_cents + } + user { + int id PK + varchar email + } + product { + int id PK + varchar name + } + menu { + int id PK + varchar name + } + menu_slot { + int id PK + varchar name + } + ingredient { + int id PK + varchar name + } + + user ||--o{ customer_order : "acting_user_id (SET NULL, nullable)" + customer_order ||--o{ order_item : "order_id (CASCADE)" + product ||--o{ order_item : "product_id (RESTRICT, polymorphic)" + menu ||--o{ order_item : "menu_id (RESTRICT, polymorphic)" + order_item ||--o{ order_item_selection : "order_item_id (CASCADE)" + menu_slot ||--o{ order_item_selection : "menu_slot_id (RESTRICT)" + product ||--o{ order_item_selection : "product_id (RESTRICT)" + order_item ||--o{ order_item_modifier : "order_item_id (CASCADE)" + ingredient ||--o{ order_item_modifier : "ingredient_id (RESTRICT)" +``` + +#### RBAC & security + +```mermaid +erDiagram + role { + int id PK + varchar code UK + varchar label + varchar default_route + enum order_source + tinyint is_active + } + user { + int id PK + varchar email UK + varchar password_hash + varchar pin_hash + varchar first_name + varchar last_name + int role_id FK + tinyint is_active + smallint failed_login_attempts + datetime lockout_until + datetime anonymized_at + } + role_visible_source { + int role_id PK,FK + enum source PK + } + permission { + int id PK + varchar code UK + varchar label + } + role_permission { + int role_id PK,FK + int permission_id PK,FK + } + audit_log { + int id PK + int actor_user_id FK + int actor_role_id FK + varchar action_code + varchar entity_type + int entity_id + varchar summary + json details + datetime created_at + } + login_throttle { + int id PK + varchar ip_address UK + smallint failed_attempts + datetime window_started_at + datetime lockout_until + datetime last_attempt_at + } + + role ||--o{ user : "role_id (RESTRICT)" + role ||--o{ role_visible_source : "role_id (CASCADE)" + role ||--o{ role_permission : "role_id (CASCADE)" + permission ||--o{ role_permission : "permission_id (CASCADE)" + user ||--o{ audit_log : "actor_user_id (SET NULL, nullable)" + role ||--o{ audit_log : "actor_role_id (SET NULL, nullable)" +``` + +> `login_throttle` has no FK (an IP is not a modelled entity); it stands alone, keyed by +> `ip_address`. + --- ### 4.1 `category` -- 2.45.3