pliceclogs-og/docs/superpowers/specs/2026-04-12-inventory-sku-lookup-design.md
type-two e5b8504bc6 Import pliceclogs as-is — the original Discogs seller extension
Snapshot of the working tree exactly as it stood, no edits. This is the predecessor
PRICEGOD was rewritten from ("kept intact, untouched" per pricegod/README.md), and it
is still the only place the DYMO scale is actually implemented —
rfid-daemon/index.js:1068-1265: HID discovery, parseScaleReport, one-shot read, and a
streaming /weight + /scale/start|stop session whose JSON shape PRICEGOD's daemon.js
already speaks.

Preserved verbatim on purpose (hence -og), including the known bug: DYMO_PIDS at
index.js:1082 is [0x8003, 0x8004], so it cannot see the bench M25 (0x8009). Fix that
in whatever daemon inherits the scale, not here.

node_modules stays ignored (22M of the 24M tree). No credentials in the import: the
two PEM markers in utils.js/sheets.js only strip headers off a key read from settings,
and mrpadmin / johnking are an SSH and a Postgres username, both key/trust auth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 13:47:09 +10:00

168 lines
5.9 KiB
Markdown

# Inventory SKU Lookup — Design Spec
**Date:** 2026-04-12
## Overview
When the popup opens on a Discogs release page and the feature is enabled, it SSHes (via Tailscale) to the inventory MariaDB, looks up SKUs for the current `release_id`, and auto-fills the SKU custom collection field(s) on the Discogs page. If there is a row/box count mismatch, a picker UI lets the user manually assign each SKU to the correct collection box.
---
## Database Schema
Table: `wp_rmp_disc_inventory` (on MariaDB at `100.123.123.64`)
| Column | Type | Notes |
|---|---|---|
| `sku` | varchar(255) PK | Timestamp-format integer e.g. `20250515100402` — lower = older |
| `release_id` | int(11) INDEX | Discogs release ID |
| `media_condition` | text nullable | |
| `sleeve_condition` | text nullable | |
| `price` | decimal(10,2) | May drift vs Discogs — display only, never written to Discogs |
Query:
```sql
SELECT sku, price, media_condition, sleeve_condition
FROM wp_rmp_disc_inventory
WHERE release_id = ?
ORDER BY sku ASC
```
---
## Architecture
```
Discogs page (content.js)
↕ sendMessage
Popup (popup.js)
↕ fetch localhost:7790
rfid-daemon (index.js) ──ssh2──▶ mrpadmin@100.123.123.64
──mysql2─▶ MariaDB 127.0.0.1:3306 (via tunnel)
```
The existing `updateDiscogsSku` message (`chrome.tabs.sendMessage`) already handles writing a SKU value to a specific collection box index — no changes needed to `content.js`.
`state.collectionBoxCount` and `state.collectionBoxSkus` are already tracked in popup.js and populated when the popup loads on a Discogs release page.
**Distinct from existing MONSTERWIKI inventory import (`handleImportInventory` / INV button):** that feature talks to the Python server on `100.91.239.7:5002` (PostgreSQL, different dataset) and is triggered manually. This new feature is auto-triggered on popup open, talks to the WordPress MariaDB at `100.123.123.64`, and only writes SKU (not price).
---
## 1. Settings UI (Connect section, popup.html)
Add a new collapsible sub-section "Inventory SKU Lookup" inside the existing Connect settings block.
**Fields:**
| Field | Element ID | Default |
|---|---|---|
| Enable toggle | `inventorySkuEnabled` (checkbox) | unchecked |
| DB Host | `inventory-db-host` | `100.123.123.64` |
| DB Name | `inventory-db-name` | `wp_rmp_disc_` |
| DB User | `inventory-db-user` | *(empty)* |
| DB Password | `inventory-db-pass` (type=password) | *(empty)* |
| SSH Key Path | `inventory-ssh-key` | `~/.ssh/id_rsa` |
Saved and loaded by the existing "Save Connect Settings" button alongside Discogs token, Google settings, etc. Values stored in `chrome.storage.local` under key `inventorySettings`.
---
## 2. rfid-daemon: `/inventory-lookup` endpoint
### New npm dependencies
- `ssh2` — SSH tunnel
- `mysql2` — MariaDB client (promise API)
### Endpoint
```
GET /inventory-lookup?release_id=&dbHost=&dbName=&dbUser=&dbPass=&sshKeyPath=
```
### Behaviour
1. Read SSH private key from `sshKeyPath` on disk (resolved from `~` to `os.homedir()`)
2. Open SSH connection to `mrpadmin@dbHost` using the private key
3. Forward a random local port → `127.0.0.1:3306` on the remote host via `ssh2` `forwardOut`
4. Connect `mysql2` to `127.0.0.1:<localPort>` with `dbUser`, `dbPass`, `dbName`
5. Execute query with `release_id` as the bound parameter
6. Close tunnel and DB connection
7. Return:
```json
{ "rows": [ { "sku": "20250515100402", "price": "12.00", "media_condition": "VG+", "sleeve_condition": "VG" } ] }
```
Errors return `{ "error": "<message>" }` with HTTP 500.
The tunnel is opened fresh per request (no persistent connection) — requests are infrequent (one per popup open).
---
## 3. Popup logic (popup.js)
### Trigger
Runs automatically when the popup finishes loading on a Discogs release page (`state.discogsTabId` is set and `state.releaseData.id` exists) and `inventorySettings.enabled` is `true`.
### Flow
```
load inventorySettings from chrome.storage.local
if not enabled → stop
GET /inventory-lookup?release_id=<id>&...creds...
if error → show error message in popup, stop
count rows = result.rows.length
count boxes = number of collection boxes on page (state.collectionBoxCount or equivalent)
if rows == 0 → silent, stop
if rows == 1 → writeSkuToBox(rows[0].sku, state.collectionBoxCount - 1) [silent, last box]
if rows == boxes → for each i: writeSkuToBox(rows[i].sku, i) [silent, earliest SKU → first box]
else → show mismatch picker UI
```
### writeSkuToBox(sku, boxIndex)
Calls `chrome.tabs.sendMessage(state.discogsTabId, { action: 'updateDiscogsSku', sku, boxIndex })` — existing content.js handler, no changes needed.
---
## 4. Mismatch Picker UI
Rendered inside the popup when `rows ≠ boxes` (and not the 1-row case).
**Layout:**
```
⚠ SKU mismatch: 2 DB rows, 3 collection boxes
[ 20250515100402 · $12.00 · VG+ · VG ] ← clickable card
[ 20260101090000 · $18.50 · NM · NM ] ← clickable card
Write selected to box: [1] [2] [3]
```
**Interaction:**
- Clicking a card selects it (highlighted border/background), deselects others
- The box number buttons are always visible (one per collection box, labelled 1-based)
- Clicking a box number button calls `writeSkuToBox(selectedSku, boxIndex)` for the selected card
- After writing, that card gets a small checkmark indicator; user can continue selecting and writing other rows
- No card selected + box button click → no-op (button visually disabled until a card is selected)
---
## 5. Error / Status Display
A small status line is added to the Discogs tab in the popup (below existing content) to show:
- Nothing (default)
- `"SKU mismatch: N rows, M boxes"` — triggers picker
- `"Inventory lookup failed: <reason>"` — SSH/DB error
- `"SKUs filled"` — after successful auto-fill (fades after 3s)
---
## Out of Scope
- Writing price to Discogs (price may drift — display only in picker)
- Caching DB results between popup opens
- Handling multiple Discogs tabs simultaneously