Persona 04 — Tier 1 Manufacturer
Confidence markers and citation shorthand per README.md.
Who they are
The final manufacturer (cut-make-trim; demo: ACME Apparel, Vietnam) — the company that receives a brand’s nomination, physically handles the traced fibre, scans it at its facilities, and declares how much traced fibre went into the order. Malcolm’s key allocation rule lives here: the manufacturer, not the brand, sets the claimed tonnage, because only they know actual fibre used; the PO itself is a money/product figure. STATED (BL:wt2:38; flow-explained rule 5).
Tier 2 in the demo (ACME TIER2) renders the identical experience — Tier 1 and Tier 2 share one manufacturer persona, differing only by data. VERIFIED observation (BL:gui-walkthrough README case 9).
A good day: scans from the floor appear in Scan History, the partner brand’s nomination arrives, they link sessions, allocate per line, propose the claim, and the brand confirms without variance questions.
What makes them this persona
isManufacturer = hasIncomingPoShares — the company has at least one non-archived purchase order addressed to it (purchase_orders.partner_company_id = companyId AND archived = false). VERIFIED (L:src/hooks/usePersonaContext.ts:32,40-41). This is worth pausing on: manufacturer-ness is not a company type, a tier, or a registration — it is having been sent a nomination. A factory with no incoming nominations falls to the generic “scanner” persona (see persona-06-additional-roles.md). VERIFIED (usePersona fallback).
Every screen they touch
| Route | What they see / do |
|---|---|
| / (HomeManufacturer) | Manufacturer dashboard: linking/scan-coverage KPI tiles (facilities, scanner coverage, verification sessions), next-actions inbox; empty state all-clear card. VERIFIED (L:src/pages/HomeManufacturer.tsx:396-406; port-plan.md unit 12) |
| /facilities, /facilities/add | Facility list and licence-gated creation (“No licenses available. Buy more licenses first.” at zero). VERIFIED (L:src/components/CreateFacility.tsx:49-53) |
| /scanners | Scanner list per facility. The “request a scanner” CTA is a mailto to support — no in-app request object exists, so admin assignment cannot be triggered by the request itself (QC M-02 divergence). VERIFIED (L:src/pages/Scanners.tsx:154-165) |
| /verifications (Scan History) | Sessions at their facilities: date, facility, scanner, volume, direction (inbound/outbound), shipping fields; session IDs obfuscated with Inert connection chips until claim-linked. VERIFIED (L:src/hooks/useVerifications.ts; ObfuscatedId/ConnectionStatusChip) |
| /link-to-po (Linking Production) | Their proposal workbench, grouped: awaiting evidence pack / awaiting brand confirmation / confirmed packs; the 3-step LinkVerificationsToPoModal (select sessions, per-line allocation + claim amount, review and notify). VERIFIED (L:src/components/LinkVerificationsToPoModal.tsx; BL:gui-walkthrough README) |
| /partners | Invite the brand (or be invited) by 3-letter company code, case-insensitive; partnership is the precondition for nominations and for declaring shipments to/from a company. VERIFIED (L:src/pages/settings/SettingsPartners.tsx:160-177; BL:demo:72) |
| /collections, /team, /notifications, /settings/* (incl. Certificates) | Shared surfaces; company certificates (Settings > Certificates) attachable during proposal. VERIFIED routes; certificates module is BE-blocked in monet |
The linking rules that bind them (the heart of this persona)
- Selectable sessions: only sessions at their own facilities OR shared to them via
session_shares, minus any session already claim-bound (production_claim_sessions) or already PO-linked (purchase_order_verifications). VERIFIED (L:src/components/LinkVerificationsToPoModal.tsx:134-193). - A session can only ever back one PO link —
purchase_order_verifications.session_idis UNIQUE at the DB. VERIFIED (SQL 20260608132010:53). Same-company double-claiming is blocked by UNIQUE(company_id, session_id) onproduction_claim_sessions. VERIFIED (SQL 20260511103814:9-10). - The DB-side link trigger additionally requires the session’s facility to belong to the PO partner, or the session to be shared with them — the cross-company facility guard. VERIFIED (validate_po_verification, SQL 20260608132010). Note the UI mostly prevents this by filtering candidates, so QC M-06’s expected explicit error message may in practice never render; the guard manifests as absence from the picker. INFERRED from both mechanisms existing.
- Deliberate looseness Malcolm insists on: any scan at their own facility is attachable even if it was addressed to another company — the sub-tier import case. “You can send a scan to somebody else and put that on a claim certificate”. A counterparty display on the picker is planned mitigation, not a restriction. STATED (BL:wt2:21-22).
- Allocation: per PO line (quantity, blend %), allocated CU computed live; over-allocating a line beyond its remaining quantity blocks Next. Claim MT is capped at min(buyer’s available capacity, programme remaining) with named errors (exceedsProgramme, exceedsBuyerCapacity, programmeExhausted). VERIFIED (LinkVerificationsToPoModal:236-280,373-378).
- Proposing writes, atomically-ish: purchase_order_verifications, a
production_claimsrow with status proposed scoped to the RETAILER’s company (the reservation lives on the brand),production_claim_linessnapshot,production_claim_sessionsbindings, PO status -> proposed. VERIFIED (L:src/hooks/usePurchaseOrders.ts:373-560,437-441). - A claim cannot reach proposed with zero linked verifications — DB trigger. VERIFIED (validate_claim_has_verifications, origin/main SQL 20260814070749).
- After confirmation their linked sessions are locked/retired (is_locked = PO-linked AND pack exists; IDs unmask, chip flips to “Linked & Verified by {brand}”); rejection deletes the PO’s links and frees the sessions to re-pick. VERIFIED (L:src/hooks/useVerifications.ts:209-234; useRejectEvidenceClaim:773-834).
- Supplier close: they can flag “no more shipments” (supplier_closed_at/note) — notifies the brand, does not close the PO. VERIFIED (usePurchaseOrders.ts:1063-1090).
CRUD abilities and gates
Create: facilities (licence-gated), scans (SAT/simulator), session shares, partner invites, claim proposals (with certificate attach), team invites. Update: allocations before propose; own facility data; supplier-close flag. Cannot: confirm or reject claims (brand-only), edit a claim after confirm (locked for both parties), reduce a PO line below its claimed quantity, create nominations (brand-side), see another company’s scans unless shared. VERIFIED (lifecycle hooks; L:src/hooks/usePurchaseOrderLines.ts:105-110; BL:wt2:30).
Dependencies
Upstream: the brand must exist as a partner and share a nomination before /link-to-po has anything actionable (the QC script’s own ordering breaks here — M-05 needs R-01 from a later section); FT Admin supplies facility licences and scanners; producers upstream supply the marked fibre their scans detect. Downstream: the brand’s Ready-to-Claim queue is fed entirely by their proposals; the programme owner’s uptake and unlinked-CU signals reflect their linking pace. VERIFIED ordering issue (.local/qc-test-script/analysis.md e12).
First-login / empty state
A manufacturer company with no incoming nominations is not yet this persona at all (falls to generic Home). Once a nomination arrives: HomeManufacturer with the all-clear card until sessions/nominations demand action; unassigned facilities show an empty state with the request CTA (mailto). VERIFIED (HomeManufacturer.tsx:396-406; Scanners.tsx:154-165).
Likely failure modes and confusions
- Expecting an in-app scanner request that routes to admin — it is an email. VERIFIED.
- Being unable to find a scan in the picker and reading it as a bug — usually it is already linked elsewhere (one-PO-per-session rule) or belongs to a non-shared facility. INFERRED from the three candidate filters.
- Variance disputes: their allocation differing from PO line quantities is surfaced to the brand as “changed” rows needing review — expect questions rather than errors. VERIFIED (usePoProposalReview.ts:55-80).
- Confusion between “the PO is fully claimed” and “the PO is closed”: closing is a separate retailer action; their supplier-close only signals intent. VERIFIED (useCloseNomination vs useSupplierCloseNomination).
Implementation state
Lovable: fully working including the Aug HIG rewrites (ReadyToClaim/Nominations counterparts). Monet: /verifications, /link-to-po are routed empty shells (sessions, purchase_orders, purchase_order_verifications, production_claims all no-op stubs; writes throw or toast “backend pending”); /partners exists only at the old /settings/partners path until the nav refactor is ported; facilities and scanners are the two genuinely wired surfaces (real /api/monet/v1 CRUD). VERIFIED (M:src/lib/apiAdapter.ts mappers; .local/qc-test-script/analysis.md route matrix; port-plan.md units 3,10-12). The whole propose/confirm domain has zero real endpoints — see 05-implementation-state.md.