2026-04-29 on prod. Cpanel's TenantDetailView Subscriptions card now shows a live usage bar per (tenant, product). New backend endpoint GET /admin/tenants/:t/subscriptions/:productCode/usage branches on productCode: nix_cafe → COUNT cafe.orders WHERE state='paid' AND date_trunc('month', created_at) = current month; outdoor_sales → COUNT activities WHERE activity_type='visit' in current month; other products → 404 "no usage metric defined". Reads limit from subscriptions.limits_json (0/missing → null/∞). New UsageBar.vue in Cpanel auto-fetches on subscription expand and renders a green/amber/red progress bar (thresholds 70%/90%).
nix_cafe subscription rows on Supabase. The 04-28 Render→Supabase migration carried schemas but no operator data; the seed script seed-supabase-getcoffee.ts creates tenant + tenant_user + cafe.tenant_config but not the subscriptions row. Without it, the Subscriptions card never showed nix_cafe at all and PATCH-limit calls 404'd. One-shot SQL inserted both: nix_cafe / cafe_pro / active with default limits_json from the plan.prodSql(). Captured limits_json as a JSON string, tried to UPDATE … SET limits_json = ''::jsonb through the bash/node-e/knex.raw chain — Postgres rejected with "Token 'max_shops' is invalid" because cmd.exe stripped the quotes. Switched cleanup to a SELECT-into-UPDATE pattern that resolves the plan's defaults inside Postgres: UPDATE subscriptions SET limits_json = (SELECT limits_json FROM plans WHERE code='cafe_pro'). No JSON literal needs to traverse the shell. Documented in project_prodsql_escape_limits.md.{ productCode, period, metric, label, current, limit }. period in YYYY-MM, metric is monthly_orders for Cafe + monthly_visits for Outdoor.state='paid' filter holds.max_monthly_orders=10 via PATCH /limits; next GET returns limit: 10.max_monthly_orders=0; endpoint returns limit: null. UI renders ∞ from null.nix_displays on get-coffee returns 404 "Tenant has no subscription".outdoor_sales returns the visits shape (label "Field visits this month", metric monthly_visits, limit from max_monthly_visits).| test-r4-3-prod.mjs (NEW) | 8/8 |
| test-r4-2-prod.mjs | 9/9 |
| test-r4-1-prod.mjs | 9/9 |
| test-phase1-prod.mjs | 11/11 |
| test-receipt-preview-prod.mjs | 6/6 |
| test-r2-followups3-prod.mjs | 7/7 |
| test-r2-followups2-prod.mjs | 11/11 |
| test-r2-followups-prod.mjs | 19/19 |
| test-nix-os-r2-4b-prod.mjs | 12/12 |
| test-nix-os-70-2-prod.mjs | 10/10 |
| test-cafe-followups-prod.mjs | 5/5 |
nix-outdoor-sales-backend @ 09562a2:
src/admin/subscription.service.ts — getProductUsage(tenantId, productCode):
branches Cafe→cafe.orders, Outdoor→activities,
others→404 "not tracked". Reads limit from
subscriptions.limits_json; 0/missing → null.
src/admin/subscription.router.ts — GET /:productCode/usage
nix-outdoor-sales-admin @ ca38c30 (Cpanel):
src/components/UsageBar.vue — NEW. Live progress bar, auto-fetches on
mount, green/amber/red on threshold,
shows ∞ when limit is null.
src/views/TenantDetailView.vue — UsageBar mounted above the Limits block
in each expanded subscription row.
D:/NIX Software/:
test-r4-3-prod.mjs — NEW E2E (8 checks). Seeds 3 paid + 1
refunded order to verify the state filter.
Cleanup uses SELECT-into-UPDATE to avoid
JSON-literal-through-bash escape issues.
prod data fix (no commit, one-shot):
subscriptions(tenant_id=get-coffee, product_code='nix_cafe', plan_code='cafe_pro')
subscriptions(tenant_id=demo, product_code='nix_cafe', plan_code='cafe_pro')
— backfilled rows the Render→Supabase migration didn't carry.
R4.1 ─ Multi-shop activation E2E ✓ shipped (2026-04-29)
R4.x ─ Carried-forward follow-ups bundle ✓ shipped (2026-04-29)
R4.2 ─ Impersonation ✓ shipped (2026-04-29)
R4.3 ─ Per-product usage endpoint ✓ shipped (2026-04-29)
R4.4 ─ Drop legacy tenant_limits/plan_code pending (last)
Open follow-ups discovered by R4.3:
- seed-supabase-getcoffee.ts should also insert a default subscription row
(the Render→Supabase migration left tenants without subscriptions).
- Pro Cafe usage shows 0 (Pro orders live in Odoo, not cafe.orders) —
real fix needs a Pro mirror table; see project_pro_tier_odoo_latency.md.