admin-commercial-configrun.md00_intake/stub.mdThe CommercialConfigCard (vendor-percentage set, pricing-rules set, overall status) is dummy. There
is no stored commercial configuration, so the commercial stage of the journey cannot be marked ready.
Per the storage decision, this simple config lives in the existing TenantSetting key/value
collection rather than a dedicated model.
commercial category in TenantSetting.TenantSetting.commercialRulesSet from them, so name them to be reusable.touches: packages/services/src/db/models/tenant-setting.ts (keys, read/write),
apps/web/components/admin/dashboard/commercial-config-card.tsx.01_define/output/spec.mdOn the admin "Platform setup" dashboard, the CommercialConfigCard ("Vendor % set" and "Pricing
rules set" yes/no badges, plus a Complete | Partial | Missing status) is driven entirely by dummy
data in lib/mock/admin.ts — there is no stored commercial configuration, so the commercial stage
of the journey can never be marked ready from real state. This is feature 4 of the
admin-dashboard-data epic, which advances Build the Bridge / Q2 2026 Objective 3 — validate
technical infrastructure & payout flow by making each dashboard section operational from real,
per-tenant data rather than a mock. Per the epic's storage decision, this simple config lives in
the existing TenantSetting key/value collection rather than a dedicated model, mirroring the
escalation config (admin-escalation-config) that shipped before it.
commercial category in
TenantSetting, with two keys this round:commercial.vendorRevenuePercent — number, 0–100. The vendor's share of revenue. Presence of
this setting is what "vendor % set" means (so 0 is a valid configured value, distinct from
never-configured).commercial.pricingRulesSet — boolean. Whether the tenant has confirmed its pricing rules.escalation does.getCommercialConfig(tenantId) and
setCommercialConfig(tenantId, config) to TenantSettingService, following the existing
getEscalationConfig / setEscalationConfig precedent (upsert under the commercial category;
values stored independently per key). Export a CommercialConfig type
({ vendorRevenuePercent: number | null; pricingRulesSet: boolean }). getCommercialConfig
returns { vendorRevenuePercent: null, pricingRulesSet: false } when nothing is stored./admin/commercial page. A server component reads the current config;
a client form (backed by a server action, following the /admin/escalation actions pattern) lets
the admin set the vendor revenue percentage (0–100) and toggle "pricing rules set", then save.
Sentence-case copy throughout.commercial slice from
loadAdminDashboardData instead of d.commercialConfig. The card's two badges and its status are
derived from the stored values:vendorPercentSet = a commercial.vendorRevenuePercent value is stored (not null).pricingRulesSet = the stored commercial.pricingRulesSet flag./admin/commercial, and the matching "No pricing rule set" entry in the
config-gaps panel points its action at /admin/commercial (currently /admin/setup, which is
not the commercial config route)./admin/commercial, and the values persist per tenant in TenantSetting under the
commercial category (commercial.vendorRevenuePercent, commercial.pricingRulesSet).getCommercialConfig / setCommercialConfig exist on TenantSettingService and a
CommercialConfig type is exported; get returns
{ vendorRevenuePercent: null, pricingRulesSet: false } when no setting is stored./admin/commercial, and the "No pricing rule set" config-gap
action targets /admin/commercial.commercial.pricingRulesSet is a single boolean
flag this round, not a rules engine.admin-sla-config) and escalation config (admin-escalation-config, already shipped).admin-readiness-gaps
feature, which will derive commercialRulesSet from getCommercialConfig (e.g. status Complete).
This feature only fixes the single "No pricing rule set" gap's link target; it keeps the key names
reusable so the capstone can consume them unchanged.apps/dashboards — that app stays the dummy source of truth.admin-data-foundation
(already spun out), which converted /admin/dashboard to a server-fetched per-tenant page via
loadAdminDashboardData. Build adds the commercial slice to that existing server fetch rather
than re-introducing client-side fetching.02_build/output/notes.mdfeat: admin-commercial-config — commercial tenant-settings + config UIpackages/services/src/db/services/tenant-setting/index.ts: added CommercialConfig
type and getCommercialConfig / setCommercialConfig on TenantSettingService, mirroring the
escalation precedent. Two keys under the commercial category:
commercial.vendorRevenuePercent (number; presence = "set", so 0 is a valid configured value) and
commercial.pricingRulesSet (boolean). get returns { vendorRevenuePercent: null, pricingRulesSet: false }
when nothing is stored; set only writes the percent key when a number is provided (never stores null).packages/services/src/db/services/index.ts: re-export the CommercialConfig type from the
services barrel (so it's available via @sustentus/services/server).apps/web/app/(app)/admin/commercial/{page.tsx,actions.ts,_components/commercial-config-form.tsx}:
new admin config page. Server component reads the config; client form (number input for vendor %,
switch for pricing rules) saves via the saveCommercialConfig server action, which revalidates
/admin/commercial and /admin/dashboard. Mirrors the /admin/escalation pattern.apps/web/lib/admin-dashboard-data.ts: added the commercial slice (AdminDashboardCommercial)
to the server fetch — vendorPercentSet, pricingRulesSet, and a derived Complete/Partial/Missing
status (Complete when both set, Partial when one, Missing when neither).apps/web/app/(app)/admin/dashboard/page.tsx: pass the real commercial slice to
CommercialConfigCard instead of d.commercialConfig.apps/web/components/admin/dashboard/commercial-config-card.tsx: added a footer linking to
/admin/commercial (badges/status unchanged, now fed real data).apps/web/lib/mock/admin.ts: retargeted the "No pricing rule set" config-gap action from
/admin/setup to /admin/commercial./admin/commercial; values persist
per tenant under the commercial category — page + form + action + setCommercialConfig.getCommercialConfig / setCommercialConfig exist and CommercialConfig is exported; get
returns { vendorRevenuePercent: null, pricingRulesSet: false } when nothing is stored.loadAdminDashboardData, not the mock./admin/commercial; the "No pricing rule set" gap action targets /admin/commercial.gh pr checks. No check is expected to fail.setCommercialConfig intentionally skips writing the vendor-percent key when null, so a blank
input leaves the tenant "unset" (Missing) rather than persisting a null value the required-Mixed
schema would reject. Unsetting an already-stored percent is out of scope this round (capture only).commercial.vendorRevenuePercent, commercial.pricingRulesSet) are deliberately
reusable so the admin-readiness-gaps capstone can derive commercialRulesSet from them unchanged.adminData.commercialConfig in the mock is now unused by the dashboard but left in place (the
mock remains the dummy source of truth for apps/dashboards).03_ship/output/changelog.mdPersona: admin
Admins can now set up commercial terms from the platform setup dashboard:
This is part of making your platform setup dashboard reflect your organisation's real configuration.
03_ship/output/investor-update.mdWe've made commercial setup configurable per organisation — admins can set their vendor revenue share and confirm their pricing rules, and the platform setup dashboard now reflects the real, saved status instead of placeholder content. It continues turning the admin control surface into a working control panel, and brings the commercial inputs that underpin payouts under explicit, per-organisation control.
This advances the Build the Bridge initiative and our Q2 2026 objective to validate technical infrastructure & payout flow — putting the commercial inputs behind payouts under real, per-organisation control.
03_ship/output/pr.mdapps/docs/technical page enumerates admin routes or tenant-setting keys; mirrors the escalation sibling's determination)feature-role-matrix/service-journey entity covers admin platform-setup config; this is operational configuration capture, not a service-journey step)apps/help/app/changelog/page.mdx)origin/main). The change is a near-exact
mirror of admin-escalation-config, which already passed a high-effort /code-review with no
correctness bugs; the new logic (commercial status derivation, vendor-percent presence check) is
small and was checked directly.value: false (pricing-rules flag) and value: 0 (vendor %) both satisfy
the TenantSetting schema's required (escalation already exercises the false case in prod).
Status derivation (Complete/Partial/Missing) and the null-percent presence check are correct.setCommercialConfig does not write the vendor-percent key when blank,
so an already-stored percent cannot be cleared this round — documented "capture only, no unset"
scope, not a defect.main ref was stale, making git diff main...HEAD show
admin-people-data files. Confirmed against fetched origin/main that the PR is commercial-config
only (those files are already on main)./admin/commercial; persisted per
tenant under the commercial category (commercial.vendorRevenuePercent, commercial.pricingRulesSet).getCommercialConfig / setCommercialConfig exist and CommercialConfig is exported; get
returns { vendorRevenuePercent: null, pricingRulesSet: false } when unset./admin/commercial; the "No pricing rule set" config-gap action targets it./admin/commercial is reachable — registered in ROUTE_POLICIES (the
deny-by-default middleware was redirecting it to the dashboard).