← automation
sherpa-portal#263 Done · In review ⏱ 1h 31m
Per-outlet documents & templates within a single product
⎇ auto/portal-263-per-outlet-documents-template
⤷ based on #456
Portal localhost:3015Embed localhost:3115API localhost:4015
🔍 iterative-review ✓ (main-pass) 🧪 test:api failed 241 passed · 5 failed · 0 flaky · 12 skipped · report 🧑‍🔬 QA ✅ PASS

Feedback

Pull requests

loading…

QA evaluation

✅ PASS

PASS. Both acceptance criteria were verified live against the running apps, not just claims. Criterion 1 was proven on the actual customer acceptance screen in the embed (port 3115): on ONE product, the Emil-Frey-Oslo draft renders its outlet logo + 'EMIL FREY OSLO' T&Cs while the Independent-Trondheim draft (no override) renders no outlet logo + the 'PRODUCT DEFAULT' T&Cs — and the portal config UI shows Oslo/Bergen 'Overridden' (distinct accents #c81e1e/#1e40af + distinct editor content) vs Trondheim 'Inherits product default'. Criterion 2 was proven by editing the product default and confirming the outlet overrides stayed intact while the no-override outlet inherited the updated default. test:api is 'failed' with 5 failures, but all 5 are pre-existing shared-DB-pollution/known-baseline cases (02-bulk renewals, 04 ledger counts, 05 active-count, 06-policy-suspension, 06-product-translations) — none touch per-outlet templates, and the ticket's own spec (10-per-outlet-templates) passes; so tests_ok=true. The feature is genuinely usable in both the portal and embed UIs.

2/2 acceptance criteria passed
  • Two outlets on one product render different T&Cs + logo on the customer acceptance screen; a third outlet with no overrides renders the product defaultVerified end-to-end in the REAL embed customer acceptance screen (http://localhost:3115/accept/<policyId>?token=...) for two draft policies on ONE product (extended_warranty_…, id 324): the Emil-Frey-Oslo policy renders the outlet logo in the header + 'EMIL FREY OSLO terms.'; the Independent-Trondheim policy (no override) renders NO outlet logo + 'PRODUCT DEFAULT terms.' (eval-6, eval-7). The acceptance-view payload the embed consumes confirms it: Oslo branding {logoUrl: red dummyimage, brandColor:#c81e1e}, termsTemplateId 451; Trondheim branding {logoUrl:'', brandColor:''}, termsTemplateId 450 (the product default). Also verified in the portal config UI: Oslo + Bergen each show 'Overridden' with their own accent chips (#c81e1e / #1e40af) and distinct T&Cs content in the editor preview (EMIL FREY OSLO / TOYOTA BERGEN), while Trondheim shows 'Inherits product default' (eval-1..eval-4).
  • Editing the product default does not clobber outlet overridesIn the portal, after editing the product-default terms to 'PRODUCT DEFAULT V2', the Emil-Frey-Oslo override editor still showed its own 'EMIL FREY OSLO' content and the 'Overridden' state (eval-5). The live render endpoint the embed/email consume confirmed resolution: oslo→EMIL FREY OSLO, bergen→TOYOTA BERGEN (overrides intact), trondheim→PRODUCT DEFAULT V2 and no-outlet→PRODUCT DEFAULT V2 (the third outlet correctly inherits the UPDATED default). Override rows were untouched by the default edit.
tests_ok: true · quality: Override/inheritance UX is best-in-class (scope selector + explicit Inherits/Overridden state + Customize/Edit-override/Reset-to-default + a no-clobber banner), matching Contentful/Shopify-Markets/Stripe conventions. API resolution is clean: outlet→product-default fallback is most-specific-wins per locale (productTemplateHelpers.findActiveProductTemplate), branding resolves outlet→org (outletBrandingService) and is injected into the acceptance view by the policy's outletId; outlet lookups are org-scoped (no IDOR). The #263 commits are surgical (portal 9 files, embed 3 files, api ~17 files). Minor non-blocking notes: (1) the scope selector is a native <select> listing every outlet by name — with many/duplicate-named outlets a searchable combobox would scale better; (2) outlet logos are external URLs (blocked in headless, so screenshots show alt text/monogram — the <img> + branding tokens are correct). The unrelated lens/login churn in the origin/development..HEAD range comes from the #456 base branch, not #263.