# PRICING · flagship round 1 · brand-006 RJ Laravel Admin

**Sheet:** https://pricing.pina-brand-006-rj-laravel-admin.pages.dev/ · **artboards:** /canvas/ (3 pages: Design pricing · Fabric pricing + import/export · Ladder, adjacent screens, phone) · **one image:** /contact-sheet.png
**Status:** round 1 for Frank's review, 2026-10-18. Design only. No code, no ledger, no commits.
**Boards:** 22 + 1 summary. Each board has a burned-in label chip, the screen at 1440×900 in the locked 4b·R shell (the same shell as the Orders flagship), and an annotation panel with decisions, sources and tags.
**Source:** `src/p_data.py` (fixtures, every item cited) · `src/p_pages.py` (screens; it imports the Orders flagship helpers) · `src/p_build.py` (composites, shots, sheet, canvas, dist) · `src/pricing.css`.

## Truth rules on every board

- **No Chinese anywhere.** The pricing pages render English only, and the tables hold numbers, not names (TRUTH-PRICING §0; FEEDBACK-B-r1 §A12).
- **Currency per column.** Wholesale is in the retailer's **settlement** currency (retailer ↔ RJ). Retail is in its **consumer** currency (retailer ↔ customer). The badge sits in the column header. Navy = settlement, green = consumer. Amounts are never converted (`designs.blade.php:45-63`, `PricingService.php:72`). The board retailer uses a live **EUR → USD** pair. P20 shows the live **CNY → USD** pair.
- **Real scope:** retailer × main product × product (`PricingController.php:139-141`). The Product filter is the Jacket | Trousers segment.
- **Real names:** 2-Piece, 3-Piece, Blazer… (16 live main products). The Jacket design list is the real 101 rows: 22 categories and 79 options (rj-admin `get_pricing_grid kind=designs`, retailer 4 · main product 2 · product 1).
- **Save models are the real ones.** Design pricing is one form with **one Submit** (`designs.blade.php:9-81`). Fabric pricing (the combinations grid) saves **per cell** (`PricingController::updateRetailerPricing :718-830`).
- **Import / Export belong to fabric pricing only.** They are per retailer (`retailer_combos.blade.php:6-18`). Design pricing has no CSV.
- **Master pricing is retired (403)** and is never drawn (`PricingController.php:68`, `web.php:1482-1486`).
- Retailer names are fictional. Their currency pairs and counts are the live ones (13 online: 9 USD/USD · 2 EUR/EUR · 1 EUR→USD · 1 CNY→USD). A dotted underline marks an example value. The one real stored design price shown is **Hand: 20.00 USD · 30.00 EUR · 1 day**.
- Tags: purple dashed = **proposal**, amber dashed = **needs backend**, blue = **API only**.

## Boards

| # | Board | What it decides | Main sources |
|---|---|---|---|
| S00 | Summary | the rules above | — |
| P01 ★ | Pricing entry: retailer → main product → product | Replaces the 4-select form. Retailer list with both currencies; main-product cards with product chips; one Fabric pricing card that carries Export (queued) and Import | `pricing/index.blade.php:6-127`; find_retailer; get_product_structure |
| P02 | Switch retailer in place | Scope-bar selects are search popovers; the scope is kept when the new retailer carries it; recent lists | `PricingController::index :73-78` |
| P03 ★ | Design pricing at real density | 101 rows · sticky header + first column · Days 0–30 · Wholesale USD · Retail EUR · — = not priced, 0.00 = a real price · category jump rail · Priced / Not priced filter | `designs.blade.php:9-81`; MCP designs read |
| P04 | Editing | Changed, cleared and error cells (1.5 = whole days only, 31 = above 0–30, −5 = below min 0) · save bar with change and error counts · Submit is blocked while any cell is red · Unsaved changes rail | `PricingService::updateDesignPricingAdmin :240-335`; POWERUPS #19 |
| P05 | Impact preview before Submit | *Proposal, UI-only.* Shows counts, then Stored → New per cell with its currency. A cleared 0.00 is spelled out. Says what the save reaches. No margin check across currencies | ADVANCED #13; `OrderDetail.php:15`; set_retailer_design_pricing description |
| P06 | Unsaved guard | Leaving a scope with changes offers: Stay · Discard and switch · Submit, then switch | POWERUPS #19 |
| P07 | Saved | Real flash "Design pricing updated successfully!" · Priced-only view · the real refusal text · Undo *needs backend* | `PricingController.php:180`; `PricingService.php:409` |
| P17 | Empty | A product with no designs points to Accounts → Configurations (the old page said "No record found") | `designs.blade.php:71` |
| P18 | Permission denied | Pricing is super-admin only | FEEDBACK-B-r1 §A12; `AdminPermissionsSeeder.php:393-397` |
| P20 | Long names + CNY | Truncation rules; numbers never truncate | MCP designs read; find_retailer |
| P08 ★ | Fabric pricing grid | 28 codes × 10 combinations × 5 columns. Collection + Code are pinned. Group title is "main product › combination". **Excess shown as Plus Size**. Main-product chips; Plus Size and Days toggles | `PricingService::prepareRetailerColumns :59-83`; MCP retailer grid; `OrderController.php:1791-1792` |
| P09 | Grid cell states | Saved + Undo · **0 clears the price here** (≤ 0 is stored as NULL) · the real "Entry not saved!" · fill and paste *proposal* | `PricingController.php:750`; `retailer-hypergrid.js:428, 469-470` |
| P10 ★ | Import: upload → check + map | Header check for Username and Currency (the job's own rule) · Settlement Currency is not checked today · an unknown combination name is caught and mapped before the job runs | `import.blade.php:7-21`; `PricingExportStreamer :121-141`; `ProcessRetailerImport :75-77, :88-93, :128-156` |
| P11 | Import: preview + typed confirm | Counts, then old → new per cell · the confirm asks you to type the retailer's username · preview *needs backend* (dry run) | `ProcessRetailerImport :112-121`; POWERUPS #18 |
| P12 | Import run + result · Export queued | The Jobs list shows the batch · the result uses the real bell texts · the real export flash and queue-lag warning | `PricingController.php:866-875, :951-990` |
| P13 ★ | Confirmation ladder | 1 one cell · 2 a few design prices · 3 review first · 4 whole list (typed username) · table mapping each action to its level | POWERUPS #18; ADVANCED #13 |
| P14 | Billing → Surcharges | Card / Bank · Surcharge Percentage 0–50 · Submit · empty clears | `billing/surcharges.blade.php`; `UpdateSurchargesRequest` |
| P15 | Pattern surcharges (cm) + express cost | Plain · Check · PinStripe · Corduroy · Velvet in CM (live 0 · 10 · 0 · 0 · 0) · express cost per combination (EUR + USD), set on Combinations | `pattern/index.blade.php:5-37`; MCP pattern_charges; MCP combinations id=2 |
| P16 | Formulas (meterage, not price) | Regular / Plus Size formula per product + Total Meterage + Jacket Lining 940 · Test Calculator · Roll-width add-ons (7 groups, cm) · Save add-ons | `formulas/create.blade.php:51-296`; MCP formulas main_product_id=2 |
| P19 | Loading | Skeleton in the real grid shape; "window 1 · 10,000 codes per request" | `retailer-hypergrid.js:197-208` |
| P21 | Phone /m/pricing | Read-mostly lookup: retailers → design price cards → one fabric code across all combinations. Editing stays on desktop | STACK-AND-PACKAGES §2 (A12 phone = read-only) |
| P22 | How you'd use it | 6 numbered pins: pick the list → narrow the scope → jump → type prices → check the money → submit once | — |

## What the real data changed in the design

- **Most design rows are not priced.** For a live EUR→USD retailer, 1 of the 79 Jacket options carries a price. That is why the page has the Priced filter, the "priced / options" counts per category, and a quiet "—".
- **Fabric pricing is small today.** Every live retailer grid has one collection ("Essentials", 28 codes), and the price is the same down the collection. The grid fits on one screen, and "Fill all" (P09, proposal) is the natural bulk action. The 10,000-row windows are legacy capacity.
- **"Excess" means Plus Size.** Orders use `excess_*` when `is_excess = 1`. Formulas head the same split "Regular" and "Plus Size".
- **0 means different things on the two grids.** On design pricing, 0.00 is a real price. On fabric pricing, ≤ 0 stores NULL. Both boards say so in words.
- **Retail goes live.** Shop sites read retail prices live (MCP `set_retailer_design_pricing`), so the review dialog states what a save reaches.

## Open questions for Frank (labelled options, source per element)

1. **Who can open Pricing?** · **1a** super admin only (drawn, P18) · 1b anyone with `update pricing` (the API gate). *Source:* FEEDBACK-B-r1 §A12; `AdminPermissionsSeeder.php:393-397`; `routes/api/v1.php` pricing writes `update pricing|admin`.
2. **Design pricing save model** · **2a** one Submit + save bar (drawn; true to the web page) · 2b a per-cell save with Undo (needs the change log, P-G2). *Source:* `designs.blade.php:9-81`; `PricingService::updateDesignPricingAdmin :240-335`.
3. **Decimals in prices** · **3a** allow 2 decimals, as the web does (`step any`) (drawn) · 3b whole amounts only (the MCP door refuses fractions). *Source:* `designs.blade.php:45-63`; rj-admin `set_retailer_design_pricing` description.
4. **0 on fabric pricing** · **4a** keep "≤ 0 = not priced" and warn on 0 (drawn, P09) · 4b make 0 a real price, as on design pricing (backend change at `PricingController.php:750`).
5. **Ladder thresholds** · **5a** review dialog above 10 cells, on any clear, or on a move over 50 % (drawn, P13) · 5b review on every Submit · 5c review only when something is cleared.
6. **Bulk on the fabric grid** · **6a** add Fill and Paste from a sheet, both at ladder level 4 (proposal, P09) · 6b keep one cell at a time + CSV only.
7. **Import preview** · 7a server dry run (needs backend, same pattern as H16/H21) · **7b** client-side diff against a fresh grid read (no backend; 28 × 50 cells today) · 7c header check only (UI-only; that part is drawn in P10).
8. **Settlement currency in the import file** · **8a** refuse a mismatch (backend change at `ProcessRetailerImport :75`) · 8b leave it unchecked, as today.
9. **Fabric-grid column titles** · **9a** "main product › combination" (drawn) · 9b keep the old "{main_product_id}-{combination}". *Source:* `PricingService.php:70`.
10. **Label for excess columns** · **10a** "Plus Size wholesale / retail" (drawn) · 10b keep "Excess". *Source:* `OrderController.php:1791-1792`; formulas headings.
11. **Design pricing CSV** · 11a add import/export per retailer × main product (P-G3) · **11b** no; fabric pricing keeps the only CSV (drawn).
12. **Pattern Design Add-Ons for super admins** · **12a** link it from the Pricing entry (proposal) · 12b change the menu rule so super admins see it. *Source:* `phase-1/menu/src/menu_data.py:72`.
13. **Phone** · **13a** read-only lookup (drawn, P21) · 13b no phone view (send to `/m/not-on-phone`).

## Needs backend

| # | Item | Where it shows |
|---|---|---|
| P-G1 | Gate pricing writes on `update pricing` (or super admin); the web writes are ungated today | P18 |
| P-G2 | Pricing change log (before/after, actor) for Undo and history on design and fabric pricing | P07, P09, P13 |
| P-G3 | Design pricing CSV per retailer × main product (only if 11a) | — |
| P-G4 | Fabric-cell write and import mapping by id (`fabric_code_id`, `combination_id`), not by display name in the app locale | P09, P10 |
| NB-1 | Import dry run: counts + old → new before confirm (only if 7a) | P11 |
| NB-2 | Import result counts (cells, codes, combinations) in the bell result | P12 |
| NB-3 | Settlement Currency check in `ProcessRetailerImport` (only if 8a) | P10 |
| NB-4 | 0 as a real price on fabric pricing (only if 4b) | P09 |
| H01 | Per-person prefs: recent price lists | P02 |

Everything else drawn is UI-only on existing endpoints: `GET/POST /api/v1/admin/pricing/designs`, `PATCH /pricing/retailer`, `GET /pricing/retailer/{retailer}`, `POST /pricing/import/{retailer}`, `GET /pricing/export/{retailer}`, plus Billing surcharges, pattern and formulas.

## Could not verify

- The live **support / orders** role permissions for Pricing beyond the sidebar gate.
- Whether an order edit re-prices from the current grid. The boards only claim that placed order lines store their prices.
- TRUTH §5 P5 lists "Non-Functional 974" under Buttonhole Finish. For this retailer the live list has only Machine and Hand, and row 974 is Peak. The boards follow the live read.
- Real names of formula design variables 10 and 613. Only Jacket Lining 940 is drawn.
