# Profit Check v2 — agent brief

Read this file before changing the invoice desk. It is the live product contract. `ProfitCheck-Cursor-Handoff.md` is stale (it still talks about settings hiding columns and an Incl. Tax column). Ignore those.

The owner is **Maxime** (Designer). He works in code. Do not rebuild from scratch. Do not “fix” the ¥2 variance.

---

## How to get the code

### Fastest: the banner on the live page

Share this URL: https://fthat.life/profitcheck/v2/

The top banner has:

- **Handoff** → `AGENT.md` (this file)
- **Source zip** → TypeScript, scripts, and this brief
- **Copy page link**

Direct files:

- https://fthat.life/profitcheck/v2/assets/AGENT.md
- https://fthat.life/profitcheck/v2/assets/profitcheck-source.zip

### Local workspace (Cursor / Dropbox)

```
C:\Users\racef\Dropbox\Cowork-Sync\Profit Check\v2\
```

That folder **is** the app. Parent `Profit Check\` also has a v1 static page; do not edit v1 unless asked.

### Live server (built output only)

| | |
|---|---|
| App | https://fthat.life/profitcheck/v2/ |
| Host | Hetzner `178.156.175.85` (SSH `root`) |
| App dir | `/opt/profitcheck-v2/` |
| Built files | `/opt/profitcheck-v2/.output/` |
| Process | systemd `profitcheck-v2.service`, Nitro on `127.0.0.1:9122` |
| Cat token | `/opt/profitcheck-v2/cat.env` — **never overwrite** |

The VPS does **not** hold the TypeScript tree. Source lives in Dropbox (and in the Download zip). Deploy copies `.output` only.

### Run locally

```bash
cd v2
npm install
npm run dev          # http://localhost:8080/profitcheck/v2/
npm run typecheck
npm test
```

Dev must go through `npm run dev` (or `node scripts/with-app-env.mjs …`). Do not start Vite by itself.

### Refresh the Download zip

```bash
node scripts/pack-source.mjs
```

`vite build` also regenerates `public/assets/profitcheck-source.zip` and copies `AGENT.md` next to it.

### Deploy (keep `cat.env`)

```bash
node scripts/with-app-env.mjs node node_modules/vite/bin/vite.js build
# copy SSR CSS: node_modules/.nitro/vite/services/ssr/assets/styles-*.css → .output/public/assets/
tar the .output folder → scp → extract to /opt/profitcheck-v2/.output → systemctl restart profitcheck-v2
```

Do not delete `/opt/profitcheck-v2/cat.env`. Dropbox often locks `.output` (`EBUSY`); stop a local Nitro on 9122 before rebuilding.

---

## What this app is

A REV9 Japan invoice desk for **Garage Vary**, March 2026. Demo PDF drop reloads a **hardcoded extract**. It does not parse PDF bytes.

| | |
|---|---|
| Printed due | ¥471,337 |
| Extracted | ¥471,339 |
| Variance | **¥2 — keep it, show the mismatch** |
| Tax | 10% (`TAX_RATE = 1.1`) |
| Persistence | `localStorage` only |

No auth, no Postgres, no Order Placed checkbox.

---

## Files that matter

| Path | Role |
|---|---|
| `src/routes/index.tsx` | Home → `<InvoiceDesk />` |
| `src/components/profit-check/invoice-desk.tsx` | Desk, table, header, totals |
| `src/components/profit-check/brand-settings-panel.tsx` | Garage Vary Settings |
| `src/components/profit-check/edi-panel.tsx` | EDI maps |
| `src/data/garage-vary.ts` | Extract, `InvoiceLine`, storage key |
| `src/lib/columns.ts` | Column defs, hide/reorder/widths, alignment |
| `src/lib/brand-settings.ts` | Settings + `priceShow` + verification math |
| `src/lib/edi.ts` | EDI pairs + header icon status |
| `src/lib/cat/map-monitor.ts` | Which columns Cat may fill |
| `src/lib/cat/lookup.ts` | Server fns to Vigilant Cat |
| `src/styles.css` | Tokens, `.field`, `.edi-icon-flash` |
| `scripts/pack-source.mjs` | Builds the Download zip |
| `vite.config.ts` | `base: "/profitcheck/v2/"` |

---

## Product rules (still in force)

### Column ownership

**Invoice-owned — never fill from Vigilant Cat**

- Order #
- Qty
- Name (INV) (`jp`)
- Shop Price
- Domestic Shipping

**Cat may fill (when mapped)**

- SKU
- Name (ENG)
- MSRP (`list`)
- Discount

SKU prefixes in Settings are **internal**. They stay empty by default. **Never concatenate** a prefix into the SKU cell. Never show a prefix chip next to SKU.

### Columns vs Settings

Settings do **not** hide columns. Visibility is **Columns** (eye / eye-off). Actions cannot be hidden.

One-time seed (`profitcheck.columns.seededFromSettings.v1`) copied old settings hides into column prefs. Do not re-gate MSRP / discount / verify / ship on settings flags.

### Alignment

| Column | Align |
|---|---|
| Order # | left |
| Qty | center |
| SKU | left |
| MSRP | center |
| Verification | center |
| Shop Price, Discount, Domestic Shipping | right |
| Manually Checked | center |

### Tax display (`priceShow`)

Shop Price and Domestic Shipping use the **same** invoice flag: `brand.includesTax`.

- Tax-in: green amount + small `inc. tx`
- Exclusive: black amount + green `{amount × 1.1} inc. tx` underneath

MSRP uses **Cat** flag `brand.vcIncludesTax` (same visual language, different setting).

Do **not** special-case Shop Price against MSRP × discount. Shop follows shipping.

Amounts ≤ 0 show no caption.

Cat yen is stored as a plain integer. Tax is display-only; do not rewrite Cat prices to “match” invoice tax.

### Verification

When **Verify pricing** is on: expected = MSRP × discount, converted into the invoice tax basis, compared to **typed** Shop Price. Discount does **not** change Shop Price.

- Match → green check, centered
- Diff → yen delta (green if shop is lower, red if higher)
- Skip if MSRP or shop is missing

### Manually Checked

Show the checkbox when the row is flagged **or** already checked: `(Require_Manual_Check === "YES" && !manuallyChecked) || manuallyChecked`.

After a click it **stays visible** so it can be unchecked. Uncheck restores the red flag if `Require_Manual_Check === "YES"`. Do not show it on ordinary / note-only rows that were never flagged.

Demo flags: `#33307`, `#33246`.

### EDI

When EDI is on, every distinct non-empty **From** value on the invoice must have a map.

Header **Garage Vary EDI** arrows: red flash if incomplete, green if complete, default if off.

### Totals

Totals always use the **full table**, never the filtered subset.

- Products = Σ shop (× 1.1 if invoice is tax-exclusive)
- Shipping = per-line ship, or once per Order # when flat shipping is on, or 0 if shipping is off
- Variance = round(grand) − 471337

### Filters

Stores / status / days only hide rows. **Days does not filter yet.** Do not compute totals from what is visible.

### Demo / persistence

- PDF drop reloads the Garage Vary extract. Not a real parser.
- Keep the ¥2 mismatch banner.
- `localStorage` keys:
  - `profitcheck.garagevary.20260424.v3` — rows
  - `profitcheck.columns.v1` — columns
  - `profitcheck.brand.garagevary.v8` — settings
  - `profitcheck.edi.garagevary.v1` — EDI
  - `profitcheck.theme` — light / dark

### Do not

- Pre-fill SKU prefixes (`GV-`, `86-`, `MJ-`, …)
- Write prefixes into SKU
- Bring back Order Placed
- Hide or “fix” the ¥2 variance
- Add login or a database unless asked
- Overwrite `/opt/profitcheck-v2/cat.env`
- Let Settings hide columns again
- Let Shop Price use a different tax rule than Domestic Shipping
- Hide the manually-checked box after it is ticked
- Fill Order # / Qty / Name (INV) / Shop Price / Shipping from Cat

---

## Vigilant Cat

Live Cat API is on the VPS (`CAT_API_BASE=http://127.0.0.1:8000` + `cat.env`). Pass `competitor_id` on monitor lookups when a supplier is saved.

Save a supplier before listing that supplier’s maps. Auto-map: pick an invoice column + Cat field; tab out (or save a VC URL) to search.
