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

**Sheet:** https://pricing.pina-brand-006-rj-laravel-admin.pages.dev/ · **artboards:** /canvas/ · **one image:** /contact-sheet.png · **round 2:** /r2/ and /r2/canvas/ · **round 1:** /r1/ and /r1/canvas/
**Status:** round 3, 2026-10-18. Design only. No code, no ledger, no commits.
**Boards:** 14 + 1 summary (Q04b is new). Same pipeline: burned-in band · 1440×900 screen in the locked 4b·R shell · annotation panel.
**Source:** `r3/src/r_pages.py` (screens; reuses r2's `q_pages.py` for the shell, retailer header, prong tabs and design editor) · `r3/src/r_build.py` (shots, sheet, canvas, dist with r2 at `/r2/` and r1 at `/r1/`) · `r3/src/pricing-r3.css`. Output: `r3/shots/`, `r3/canvas/`, `r3/contact-sheet.png`.

## Frank's decision (2026-10-18, chat, direct question)

> Q: "Fabric combination pricing in the new admin: export/import only, or keep an on-screen grid?"
> A: **"Export/import only."** "We don't allow for a view of the file, we just allow export and import — you're showing me a mockup we don't support."

And on width: "collection and code columns are way too wide — make them narrower to use space better."

The old Blade page (`retailer_combos.blade.php`) does load an editable grid (`resources/assets/panel/page_dependencies/retailer-hypergrid.js` + per-cell `admin/pricing/retailer/update`). The new admin does not carry it over. Recorded as **decided** in `PROPOSALS.md` (appended) and on the `/admin/pricing/create` row of `page-map.seed.json` / `PAGE-MAP.md` (M02 matrix editor dropped; file-uploader, stepper, review-step, M05 diff, M04 download link and M01 jobs + bell kept or added).

## What changed

**Prong 1 · Fabric combination pricing has no grid.** It is two equal cards under the same two prong tabs (P-a stays recommended):

| Card | On screen | Real or not |
|---|---|---|
| Export | **Export (queued)** · the real Blade line "Exports are queued — you'll receive a notification with a download link when the file is ready (typically 30-60 seconds)." · recent files for this retailer with **Download** · "Files are kept 7 days" | **Real.** `GET admin/pricing/export/{retailer}` queues the batch; `GET admin/pricing/exports?retailer=` lists the files (file, size, created, newest first); `POST admin/pricing/export-download-url` returns a signed link; `pricing:prune-exports --days=7` |
| Import | **Choose CSV** + a drop zone · **Recent imports** | Upload is real (`POST admin/pricing/import/{retailer}` → 202 + batch id). **Recent imports is a needs-backend ghost**: import batches are stored with the user, not the retailer, so there is no per-retailer import list today |

**The prong status "9 / 10 combinations priced" stays.** It is derivable from a real read (`GET admin/pricing/retailer/{retailer}`, paged by `dataset_count`); it is a count, not a view of the file. One summary endpoint would make it one call (NB-5, optional).

**Export queued → notification (Q03).** Click → toast with the real line → a row in the shell Jobs tray → the card follows the batch (`GET admin/pricing/exports/{batch}`: Queued → Running → Ready) → the bell gets the real "Your recent pricing export has been completed · Download File" → the new file lands on top of Recent exports with a primary Download. A lagging queue prefixes the real "Queue worker may be lagging (oldest job is N min old)."

**Import is a step flow inside prong 1 (Q04, Q04b).** Vertical steps on the left (done steps keep a one-line summary), the current step on the right:
1. Upload · 2. Check (Username + Currency must match; an unknown combination name is mapped first) · 3. **Preview as a diff summary**, never a grid: Changed / New / Cleared / Unchanged, then the same counts for the combinations the file touches ("6 other combinations · no change") · 4. Confirm: type the retailer's username, **Import 93 changes** · 5. Run: batch progress, also in the Jobs tray · 6. Result: the real bell text "Your recent import has been completed", counts sent, Errors (both real whole-job error texts), **Export to check**.

The clearing rule is stated **once**, in the preview: "0 or an empty cell clears a price."

**Prong 2 · Design pricing: structure unchanged, columns narrowed.** Option 34ch, Days 11ch, Wholesale / Retail 15ch each, numbers right-aligned and tabular; the width freed goes to the row-message column (errors, "cleared → not priced").

**Narrowing elsewhere.** Entry table (Q01): retailer name flexible, username 19ch, account 12ch, currencies 7–9ch, each prong 26ch (+ cell padding). Export files and the preview table use ch widths too: file name flexible, created 12ch, size 6ch, counts 5–8ch.

**Phone /m/pricing (Q13).** The fabric prong is **Export (queued)** + recent files with Download; no prices on screen; "Import on desktop". Design prong unchanged (read-only prices).

## Boards

| # | Board | r3 |
|---|---|---|
| S00 | Summary | new text |
| Q01 | Entry: retailers with both prong statuses | narrowed |
| Q02 ★ | Fabric combination pricing = Export \| Import cards | **replaced** |
| Q03 | Export queued → notification with the download link | **replaced** |
| Q04 | Import: preview as a diff summary + typed confirm | **replaced** |
| Q04b | Import: run → result with counts and errors | **new** |
| Q05 ★ | Design pricing | narrowed |
| Q06 | Design pricing editing | narrowed |
| Q07, Q08 | P-b overview / P-b editor (not recommended) | Q08 narrowed; Q07 annotation only |
| Q09 | Switch retailer | unchanged (design table behind it narrowed) |
| Q10 | Empty fabric prong: no Export / Import cards, title + Open account | updated |
| Q11, Q12 | Empty design prong · permission denied | unchanged |
| Q13 | Phone | fabric screen **replaced** |

Retired from r2: Q02 grid, Q03 per-cell editing (saved + Undo, "0 clears this price", "Entry not saved!"), Q04 import sheet over the grid. r1's P08/P09 grid boards also no longer apply. Both stay viewable at `/r2/` and `/r1/` for history.

## Needs backend

| # | Item | Where |
|---|---|---|
| new | Per-retailer import history (job_batches has `user_id`, no retailer id) | Q02 ghost |
| NB-1 (optional) | Server dry run for the import preview. Without it the preview diffs the file in the browser against a fresh retailer read (r1 option 7b) | Q04 |
| NB-2 | Counts written by the import job in its result | Q04b ghost |
| NB-5 (optional) | One pricing summary per retailer | Q01, Q02, Q05, Q07, Q09, Q13 |
| P-G4 | Import maps combinations by English name; by id would remove the mapping step | Q04 |

## Found while checking the code (for Frank / backend)

- **The import's clearing rule is narrower than the grid's.** The grid stored ≤ 0 as empty (`PricingController.php:750`). The import stores empty only for an empty cell or exactly `0` (`ProcessRetailerImportChunk.php:128`, a PHP truthiness check); a negative number or `0.00` is written as typed. The preview line therefore says "0 or an empty cell clears a price", and the annotation proposes flagging negatives / `0.00` in the preview before Confirm (UI-only). Frank's brief said "0 or less"; the code says otherwise.
- **An unknown combination name fails the whole import with a raw PHP error** (`$combo->id` on null, `ProcessRetailerImport :88-93`), surfaced as "Import Validation Error : Attempt to read property "id" on null". The Check step's mapping exists to stop that before the job runs.
- The admin API's `admin.pricing.retailer.update` (per-cell PATCH) is still live; the new UI simply never calls it. Retiring the route is not part of this decision.

## Page map

`/admin/pricing/create` row (the page that renders `type=combinations`): overrides rewritten to export / import only; components drop **M02**, add **file-uploader, stepper, review-step, M05, M04**; API ops drop `admin.pricing.retailer.update`, add `admin.pricing.exports.index`, `.exports.show`, `.export-download-url`, `admin.pricing.import`; PR-070 removed from that row (moot). `check_page_map.py`: OK (325/325). `PAGE-MAP.md` was patched for that one row in the generator's own format, **not** regenerated: `build_page_map.py` rebuilds `page-map.seed.json`, `PAGE-MAP.md` and `PROPOSALS.md` from the pre-phase-3 snapshot plus `proposals_data.py` and would drop this row edit and the appended PROPOSALS entry. Carry both into the generator inputs before the next rebuild. The seed's header totals (`counts.*`) were already out of step with the rows before this change and were not touched.

## Could not verify

- Where a retailer's fabrics (the export's rows) are assigned; Q10's "Open account" target is still unverified.
- Whether the new admin can poll an import batch by id: the export has `GET admin/pricing/exports/{batch}`; for import the tray uses the shared job-batches list (`admin.dashboard.job-batches`).
