Products tab and Nodes link
Epic ask (verbatim): "file an epic to create Customers section where operator can see all customers and fully manage their parameters (move all from Directory/Org) and fully manage all of their billing and products. Operator should be able to add/change/remove any products (without or with billing), see all history, etc. UI must be compliant with best practices and Jaah Standard (feed-like, tabbed pages, no prose on page, etc.)"
Settled inputs: PRD = Task 6049, Architecture = Task 6050 (`task_read` both; §3.4 products API (built by C6a + C6b) and §3.8 Nodes are your contracts). This is child **C7**. Replace C3's `pages/customers/ProductsTab.tsx` slot body (`export function ProductsTab({ org }: { org: CustomerOrg })`) over the `/api/customers/{org_id}/products*` API. You run after C1 (history recording) is merged and deployed, so the live deletes you perform on dev ARE recorded in History (no backfill exists — D4); C8 verifies them.
Requirement (verbatim, Req 4): "Products: the operator can add, change and remove ANY product for a customer (hostings/machines, plan features, add-ons, etc.). Each action can be done with billing (charged/prorated/refunded through Stripe) or without it (comped/manual, clearly marked as such)."
Requirement (verbatim, Req 6): "The Nodes → Delete \"This machine is hosted. Delete it from the Billing page.\" dead end for operators routes into this customer's product management instead."
Decision D8 (binding): a billed product is removed only with money (Credit unused days = default, or Refund to card); "Without billing" remove is offered only for Comped/Included products. D11: billed add has "Provision now" ON by default.
Acceptance (verbatim from PRD, AC4.6 reshaped by D8):
- AC4.1 Each product row shows: name, size/details, state, how it is paid — **Billed** (price/period from Stripe), **Included** (from the plan), or **Comped** (operator, with its end) — and, for a comp, the end date or "Never". Oracle: Wealthcharts' Products tab on dev lists its hosted machine with these fields.
- AC4.2 (UI) Until paid a not-yet-provisioned billed add shows "Awaiting payment" with the invoice link.
- AC4.3 (UI) a comped add reads "Comped · ends <date>" (or Never).
- AC4.4 Change: resize (billed → quote/confirm; comped → comp line), change a comp's end, Comped → Billed, Billed → Comped, toggle feature switches.
- AC4.5 Remove with billing: the dialog defaults to "Credit unused days on next invoice" and offers "Refund unused days to card now". Stripe computes both (removal-quote shows Stripe's figures).
- AC4.7 Every add/change/remove dialog states the mode (With billing / Without billing) explicitly, and the destructive confirm names the customer, the product and the money outcome. No mode is preselected silently for a money-moving remove. Oracle: screenshot of the remove dialog at 390px — readable, every option tappable.
- AC6.1 For an operator, deleting a hosted machine on Nodes/Hosts no longer dead-ends: one tap lands on `/customers/<wealthcharts>?tab=products` with the hosting highlighted. The tenant's message stays as today.
- AC6.2 Operators see a hosted machine's size/customer on Nodes/Hosts ("<customer> · <size>"; today skipped at `Nodes.tsx:741`).
- AC7.1/7.2 feed-like rows, no help prose, usable at 1440 and 390, dialogs full-height sheets at 390, tap targets ≥44px.
- AC8.1 `by-node` route operator + `finance:view`; tenant 403/404.
HOW (pinned): `ProductsTab.tsx` = feed of `GET .../products` items (Billed/Included/Comped chip, ends, invoice link, actions from `actions[]`); Add dialog (catalog from `GET .../catalog`, mode With billing → provision now/on payment; Without billing → end never/date/plan); Change dialogs for each PATCH op; Remove dialog per D8 with `removal-quote` figures; honour `?product=` by scrolling to and highlighting the row. Nodes (§3.8): `routes/nodes.py` `delete_node` — for an operator caller the 409 `detail` becomes `{"message": "This machine is hosted.", "customer": {"id","name"}, "rental_id", "href": "/customers/<org>?tab=products&product=<rental>"}`; tenant keeps `HOSTED_MACHINE` string unchanged. New `GET /api/customers/hostings/by-node?ids=` → `{"<node_id>": {"org":{"id","name"},"rental_id","size"}}` from `hostings.for_machines`; `Nodes.tsx` uses it for operators.
Credentials & reach:
- Operator browser oracles + PRD §5 step 1 on Wealthcharts: `jaah/debug/main/operator-login` on `https://app.dev.jaah.ai` — verified.
- Stripe test confirmation of the billed delete: `jaah/debug/stripe-dev` — verified. Do NOT use `jaah/system/stripe-jaah-dev-sk`.
- API tests, screenshots, `./dc check full`: worktree only — verified.
Out of scope: Do NOT change the products API semantics built by C6a/C6b (only consume it; a bug in it may be fixed minimally and named); tenant Nodes message (`HOSTED_MACHINE`) and tenant Billing pages unchanged; do NOT build the History tab (C8); no new pricing models or Stripe Products/Prices; no money arithmetic in web or API (show Stripe's figures only); the operator never enters card numbers; must not require act-as (Task 6047); no new permission keys for tenant roles.
Done means:
- PRD §5 step 1 on dev after Jaah Deploy: as operator open Customers → Wealthcharts → Products; delete its hosted machine **with billing** (credit or refund) and then a comped one (add a comped Shared hosting first if needed) **without billing**. Report: the Stripe effect observed via `jaah/debug/stripe-dev` (object ids only) for the billed delete, that nothing changed in Stripe for the comped one, and that `GET /api/customers/<wealthcharts>/history` shows both with actor and mode.
- AC4.1/4.2/4.3 — screenshot of Wealthcharts' Products tab showing Billed/Comped rows with paid-by, state, ends.
- AC4.4 — on a dev test customer perform resize (comp), change comp end, Comped→Billed, Billed→Comped, toggle one switch; each succeeds and the feed updates after reload.
- AC4.7 — Playwright screenshot of the remove dialog at 390 (readable, every option ≥44px, names customer + product + money outcome, no preselected money mode).
- AC6.1 — on dev: Nodes → Delete on a hosted machine as operator → one tap lands on `/customers/<org>?tab=products&product=<rental>` with the row highlighted; pytest: tenant still gets the `HOSTED_MACHINE` string.
- AC6.2 — operator Hosts tab shows "<customer> · <size>" for hosted machines (screenshot); pytest: `by-node` as tenant Owner → 403/404.
- AC7.1/7.2 — Playwright screenshots of the Products tab and each dialog at 1440 and 390 attached to the PR, each judged usable (product names readable, no horizontal scroll, zero help paragraphs).
- `./dc check full` green; the PR's Jaah Deploy to dev succeeds.
Outcome
Products tab (feed, Add/Resize/End/convert/Remove, D8 money) + Nodes 409 link + by-node sizes. PR #5284 merged, Jaah Deploy OK, all dev oracles passed incl. Stripe. Gate green but 3 smoke specs failing on develop too. 6 UX follow-ups in report.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)