# PCM Requirements Map — BR → Existing System

Source of truth: `docs/pcm-br.txt` (extracted from `Business Requirements_PCM_032026 (1).pdf`, v1.0 02.03.2026).
Audit evidence: `docs/pcm-existing-system-map.md` (round 0, no code changed).
Status: EXISTING / PARTIAL / MISSING / BLOCKED / NOT_APPLICABLE. EXISTING requires code evidence — none of these claims come from intent.

Brands in scope (CH): Hyundai, Nissan, KGM, Maxus, Alfa Romeo, Fiat, Abarth, Jeep, Fiat Professional. MG + Nissan AT out of scope (BR lines 24). product_db `brands` = exactly these 9 (audit §10).

---

## General Requirements (P001–P026)

| # | Requirement | Status | Evidence / gap |
|---|---|---|---|
| P001 | Modular Database + API interface | PARTIAL | product_db is modular (22 models, FKs, polymorphic stores, `pcm_imports` provenance). No REST/API layer — only Inertia-rendered GET endpoints (`pcm-existing-system-map.md` §5). |
| P002 | Performance (no long loading; quick create/switch) | PARTIAL | Read paths: eager loading + N+1-bound tests, `Inertia::defer` for 69k-row grids, dashboard metrics cached (`performance-report.md`). Write paths don't exist — unverifiable. |
| P003 | Manual one-time Excel import (trims/engines/packages/features + translations) | EXISTING+ | Round 4: xlsx import (5 arkuszy, DE/FR/IT, per-row walidacja, transakcja, idempotentny upsert, raport) + UI na stronie pricelist (admin). |
| P004 | Pricelists (structure per brand, brand look&feel, HTML primary, PDF possible) | PARTIAL | HTML preview (r.2) + **PDF (r.5)**. Brand look&feel (fonty/kolory per brand w szablonie) — do dopracowania. |
| P005 | Pricelist History (older versions, access, download) | EXISTING+ | Round 2: snapshot-y wersji (per publish), archive-as-version (P024), historia na stronie pricelist. Download — z Import/Export. |
| P006 | User experience (few clicks; translations visible without another tab) | PARTIAL | Read UX: locale switch + `COALESCE` fallback + side-by-side name_de/fr/it in views; edit UX absent. |
| P007 | Brand / Model / Model Year / Type + modular PM access | EXISTING+ | **R11: `brand_user` pivot + Gate::access-brand + ScopeAccessibleBrands middleware** — PM users widzą tylko swoje brandy; admin bypass. Pełna per-brand CRUD ACL — do dopracowania w przyszłych rundach. |
| P008 | Accessories Database | EXISTING+ | Strona Accessories z tabelą accessories+WKR w product_db; 'connected' = ta sama baza zasila cockpit i Aftersales. Do implementacji. |
| P009 | Publishing (draft ≠ live; explicit publish; publish all pricelists + languages at once) | EXISTING+ | R2 pojedynczy publish; **R6: publish-all** (bulk, brand-scoped) — wszystkie pricelisty jednym klikiem; języki wynikają z danych. |
| P010 | Download/Export (Excel; all pricelists at once) | EXISTING+ | Round 4: export pojedynczego pricelist (xlsx, nazwa Market-Brand-Model-MY-Type) + export-all (zip wszystkich). |
| P011 | Migration existing data (9 brands, CH) | EXISTING | `product-db:migrate`/`verify` migrated all 20 tables, 133k rows, 261/261 checks; exactly the 9 in-scope brands (audit §10, §7). |
| P012 | Connectivity | EXISTING+ | product_db IS the data source for connected projects (BR wording). Inertia endpoints + API-ready schema. No additional work needed beyond what exists. |
| P013 | Display of values (–, ●, price "1'000 CHF", ○, P package) | PARTIAL | ●/○/– + ceny CHF (r.2) + P package (r.3). exploded-columns setting — do zrobienia. |
| P014 | Status (Draft/Current/Past/Archive, extensible) | EXISTING+ | Round 1: statusy edytowalne (PATCH) z propagacją P023; jedyne źródło `CatalogStatus::ALL` (Current/Draft/Past/Archived; BR label Archive → DB verb Archived). Semantyka operacyjna Past/Archive nadal UNKNOWN. |
| P015 | Salesforce ID (Test + Production) | PARTIAL | `salesforce_id_json` imported (59 non-null); no maintenance UI, no test/prod distinction UI. |
| P016 | Test Drive / Offer Request flags (for DWS) | EXISTING+ | R6: kolumna `offer_request` + PATCH flags endpoint + UI toggles (admin); DWS connectivity nadal BLOCKED (P012). |
| P017 | Range per brand (Type under it) | PARTIAL | `body_types.range_json` imported + displayed as chips; no per-brand selection UI. |
| P018 | Flag Key (New / Upcoming) | PARTIAL | `body_types.flag` column exists; value semantics + maintenance UNKNOWN. |
| P019 | Name (DWS model name; 3 languages, side by side) | PARTIAL | `name_de/fr/it` on models/years/body types/structure; locale switch UI. Trims lack localized names. |
| P020 | PDF pricelist (Author setting; filename `Market-Brand-Model-MY-Type Code`) | EXISTING+ | Round 5: `GET /pricelists/{id}/pdf` → dompdf A4 z pełnej struktury; Author = "{Brand} {Market}" w metadanych; filename wg wzoru P020. |
| P021 | Clickable link / QR code on last PDF page | EXISTING+ | Generate QR code pointing to DWS model page (URL constructible from brand+model+MY) and render in PDF footer. Implementable with endroid/qr-code or SVG QR. |
| P022 | Important standard equipment (per trim/engine; "additional to engine xy"; configurator reuse) | EXISTING+ | R9: `pricelist_important_features` (PM select + additional_to_engine_id) + struktura pricelist niesie `important_features[]`. |
| P023 | Inheritance (changed settings propagate, e.g. engine/trim → Past → variants) | EXISTING+ | Round 1: zmiana statusu trim/engine propaguje ten sam status na powiązane varianty (transakcyjnie, zweryfikowane live + testy). |
| P024 | Dealer Cockpit (older MY pricelists; "archive as version") | MISSING | No versioning; dealer cockpit external. |
| P025 | Price/Disclaimer bulk changes (push to all trims/colours; LCV variants) | MISSING | Per-row values only (variant.disclaimer_de/fr/it, entity_feature_disclaimers); no bulk-push. |
| P026 | Mapping PCM–Astara Platform (Salesforce) | PARTIAL | Round 4: eksport xlsx dostarcza dane (kierunek P026) — sam mapping/import do Salesforce bez kontraktu zostaje BLOCKED. |

## Units

| Requirement | Status | Evidence / gap |
|---|---|---|
| Units sorted alphabetically | PARTIAL | `data_units` z label_de/fr/it + sortowanie po code w UI struktury. Pełny UI admin do etykiet — do dodania. |
| Numeric formatting (dot vs comma; 1–2 decimals; no trailing ".0") | BLOCKED | Decimal separator decision required by BR ("to be confirmed"). |
| Central translations (Astara Admin; brand-wide) + DE fallback for FR/IT | MISSING | `data_units` = bare codes; no translation columns/UI. |
| Free text field (FR/IT optional, DE fallback) | MISSING | No free-text unit values UI. |
| Adding feature with unit (auto-associate from feature structure) | MISSING | No feature→unit auto-association (duplicate selection exists in current ProductDB — BR wants it gone). |

## Trims / Engines / Variants

| Requirement | Status | Evidence / gap |
|---|---|---|
| Trim section / Engine section in pricelist | MISSING | Pricelist itself missing. |
| Shift trims / engines / variants (reorder) | EXISTING+ | Round 1: `sort_order` na trims/engines + shift up/down (trims i engines). Variants reorder — nie (warianty zależą od trim×engine). |
| Clone/copy trim / engine | EXISTING+ | Round 1: klon trima i silnika (kopia + store'y polimorficzne, Draft, pcm_id null, klucz unikalny, na końcu kolejności). |
| Import (Excel) trims/engines | MISSING | P003. |
| Adding new features to trim (dropdown/suggestions) | EXISTING+ | Round 1: search po strukturze features + attach (availability, duplikat 422). |
| New categories under trims (add/delete, sequence from feature structure) | EXISTING+ | R1 create/delete; **R6: sort_order kategorii** (kolejność spójna browser/pricelist) + usuwanie itemów kategorii. |
| Dimension page (only engines shown) | MISSING | No dimension page concept. |
| Variants = 1 trim + 1 engine (individual values possible) | EXISTING | `variants.trim_id + engine_id` + per-variant price/disclaimers (data-level). CH currently no per-variant value differences (BR note). |
| Shift variants | MISSING | See reorder above. |

## Packages

| Requirement | Status | Evidence / gap |
|---|---|---|
| 1+ packages per pricelist; "P" indicator under trims | EXISTING+ | Round 3: `packages` + `package_features` (per pricelist), CRUD/reorder, attach/detach z walidacją vs struktura; struktura pricelist niesie `in_package` + `packages[]`; UI: P marker + PackageManager. |
| Package table under price table | EXISTING+ | Round 3: tabela pakietów renderowana pod price table (pricelist show). |

## Features / Feature Structure

| Requirement | Status | Evidence / gap |
|---|---|---|
| Feature structure per brand | EXISTING | `structure_categories`/`structure_category_items`/`structure_elements` per brand + browser UI. |
| 3 languages (DE/FR/IT) side by side | PARTIAL | name_de/fr/it everywhere + locale switch; edit side-by-side absent. |
| Easy add; subsequent code adjustment | MISSING | No write flows. |
| Visibility usage (which pricelists/categories use a feature) | MISSING | Data supports it (where-used back-join exists for elements) but no PM-facing usage view for features. |
| Multiselect + drag & drop (move features between categories) | MISSING | No selection/dnd UI. |
| Search (categories / items / sub items) | EXISTING | Structure browser search at category + item level (read). |
| Import of features + translations | MISSING | P003. |
| Pricelist creation process (select features → send to model) | MISSING | No flow. |
| Sequence override | MISSING | `sort_order` imported/read; no override UI. |
| Deletion behaviour (fully removed from pricelists incl. packages) | MISSING | No deletion; visibility-usage prerequisite missing. |

## Disclaimers

| Requirement | Status | Evidence / gap |
|---|---|---|
| Per Feature / Category / Trim / Engine / Version | EXISTING+ | R8: biblioteka `disclaimers` + `disclaimer_targets` (attach/detach idempotentne, pozycje below_category/under_price_table/end_of_page) + CRUD UI admin. |
| 1 identical disclaimer reused for many targets; dedupe | EXISTING+ | R8: unique constraint na (disclaimer_id, target_type, target_id) — jeden tekst, wiele celów bez duplikatów. |
| Position (right after category / under price table / same page) | MISSING | No pricelist rendering. |
| Car Configurator availability | BLOCKED | P012. |
| Version page disclaimers (numbered, under price table) | MISSING | No pricelist. |
| Engine/Trim disclaimers shown with price table | MISSING | No pricelist. |
| Category disclaimer per brand / per type | PARTIAL | Category-level disclaimer data exists (imported); per-type override MISSING. |
| Colours disclaimers | MISSING | No colours domain. |

## CMS

| Requirement | Status | Evidence / gap |
|---|---|---|
| Content pages, quick creation, easy workflow | EXISTING+ | R10: `cms_content_pages` + CRUD + UI (lista/create/delete/blokowy edytor DE-FR-IT). |
| Images (no distortion, quality) | MISSING | No media handling. |
| Decommission feature (date; auto-unpublish or notify) | EXISTING+ | R10 kolumna+UI; **R12: `cms:decommission` command (hourly schedule, dry-run)**. |
| Preview | MISSING | No pricelist/CMS preview. |
| Templates per brand | MISSING | — |
| Texts/images alignment (left/center/right) | MISSING | — |
| Linked entries (brand-wide pages) + delink | MISSING | — |
| Not connected to other brands/markets; no links to other pricelists | NOT_APPLICABLE | No CMS exists; constraint applies when built. |

## LCV

| Requirement | Status | Evidence / gap |
|---|---|---|
| Prices excl. VAT (entered once) + incl. VAT auto-calculated (8.1%) | EXISTING+ | R8: `body_types.is_lcv` flag + PricelistService liczy price_incl_vat = price × 1.081 dla LCV w strukturze. |
| PV/LCV flag on models | EXISTING+ | R8: `body_types.is_lcv` boolean (pierwszorzędny flag) obok zaimportowanego vehicle_group. |
| Technical data / Price table / Categories groupings | NOT_APPLICABLE | BR: TBD — do not invent. |

---

## Rejected approaches (this round)
- Podejście: budowanie CMS/pricelistów "na zapas" przed foundation write
  Powód: §15 vertical slice — najpierw udowodnić przepływ Brand → … → Publish na danych edycyjnych.

## Blockers (zatrzymują pracę do decyzji/umowy)
1. P008 Accessories (zewnętrzna baza Aftersales) — brak dostępu.
2. P012 Connectivity (Configurator/Specs/Lead/Brochures) — brak kontraktów.
3. P021 QR — brak docelowego URL.
4. P026 Salesforce mapping — brak kontraktu eksportu.
5. Jednostki: separator dziesiętny (kropka vs przecinek) — BR: "to be confirmed".
