Three independent feature tracks shipped together: marketplace deals get a real buyer approve / request-revision / reject workflow with a 7-day auto-release, agencies get multi-client (Tier 1) so a single agency workspace can transact on behalf of distinct clients, and sources can now subcontract slices of a marketplace deal to an approved vendor end-to-end.
Why this matters
Marketplace deals previously had no buyer signoff path — the seller hit "complete" and money moved. Buyers had no recourse. Now every delivery enters a PENDING_REVIEW state with a 7-day window for the buyer to approve, request a revision (max 3 rounds), or reject (escalates to dispute). Silence equals approval — implemented as a lazy auto-release sweep, no Celery worker required.
Agencies have always been treated as a single tenant. In reality an agency runs many client brands, each with its own restrictions, billing, and reporting. Tier 1 gives every bid and deal a client_id tag, exposes a /agency/clients CRUD page, and adds a per-bid client picker. Tiers 2-5 (per-client dashboards, margin tracking, multi-tier approvals, per-client restriction enforcement) build on this foundation.
Vendors were sellable on marketplace per the backend role table, but the UI never surfaced the path. Phase 1 of the vendor↔marketplace integration takes the realistic shape: a Source (publisher/influencer) accepts a marketplace deal, then delegates a slice — "edit this 60-second short", "shoot the product photography" — to an approved Vendor. Vendor delivers, Source approves, Source rolls the result into the parent deal's delivery to the Advertiser.
1. Buyer-Review Workflow (max 3 revisions, 7-day auto-release)
#### Schema (migration 1a2b3c4d5e6f)
- New
DealStatusenum values:pending_review,revision_requested. - New columns on
marketplace_deals:delivery_submitted_at,review_deadline_at,buyer_approved_at,revision_count,max_revisions(default 3),revision_history(JSONB).
#### Endpoints | Method | Path | Purpose | |---|---|---| | POST | /marketplace/deals/{id}/submit-delivery | Seller submits → flips to PENDING_REVIEW, stamps 7-day deadline | | POST | /marketplace/deals/{id}/approve-delivery | Buyer approves → COMPLETED | | POST | /marketplace/deals/{id}/request-revision | Buyer asks for changes (≥20-char notes, capped at 3 rounds) | | POST | /marketplace/deals/{id}/reject-delivery | Buyer rejects (≥20-char reason) → DISPUTED |
#### Auto-release
_auto_release_stale_deals()helper called at the top ofGET /dealsandGET /deals/{id}.- Any
PENDING_REVIEWdeal whosereview_deadline_at < now()is auto-flipped toCOMPLETED. - No Celery worker — lazy expiry via the read path.
#### Frontend
- New buyer review banner on
/marketplace/deals/[id]showing countdown + Approve / Request revision / Reject buttons. - Revision form (≥20 char) with rolling round count, max 3.
- Reject form (≥20 char reason) escalates to dispute.
- Revision history pane shows every round with seller proof URL + buyer notes.
2. Agency Multi-Client (Tier 1)
#### Schema (migration 2b3c4d5e6f7a)
- New
client_brandstable:agency_workspace_id,name, contact + billing fields,restrictionsJSONB,requires_approval(Tier 4 hook),is_active. Uniqueness on(agency, name). - New nullable columns:
marketplace_bids.client_id,marketplace_deals.client_id(FK toclient_brandsON DELETE SET NULL).
#### Endpoints
GET/POST /agency/clients— list (with?include_inactive) + create.GET/PATCH/DELETE /agency/clients/{id}— fetch / update / soft delete.validate_client_for_agency()helper called from bid create + deal create + accept-bid.
#### Frontend
- New
/agency/clientspage: grid layout with logo, contact, search, include-inactive toggle, create/edit modal. - Bid modal on
/marketplace/listings/[id]now has a Client (on whose behalf?) picker forAGENCY_ADMINusers. client_idpropagates: bid → deal on accept.- Sidebar adds Clients entry under AGENCY mode.
3. Vendor↔Marketplace — Subcontracts (Phase 1)
#### Schema (migration 5e6f7a8b9c0d)
- New
marketplace_deal_subcontractstable with own state machine:ASSIGNED → SUBMITTED → APPROVED | REJECTED | REVISION_REQUESTED → SUBMITTED. - Independent of
vendor_deliverables(which stays campaign-scoped). - Tracks:
deal_id,vendor_id,assigned_by_workspace_id,title,description,due_date,agreed_amount, file/notes/attachments, full revision history.
#### Endpoints (7 total under /marketplace) | Method | Path | Actor | Purpose | |---|---|---|---| | POST | /deals/{deal_id}/subcontracts | Source | Assign vendor | | GET | /deals/{deal_id}/subcontracts | Seller / Buyer / SA | List | | PATCH | /deals/{deal_id}/subcontracts/{id} | Source | Update (only while ASSIGNED/REJECTED) | | DELETE | /deals/{deal_id}/subcontracts/{id} | Source | Cancel | | POST | /deals/{deal_id}/subcontracts/{id}/submit | Vendor | Upload proof | | POST | /deals/{deal_id}/subcontracts/{id}/approve | Source | Mark APPROVED | | POST | /deals/{deal_id}/subcontracts/{id}/reject | Source | Reject | | POST | /deals/{deal_id}/subcontracts/{id}/request-revision | Source | Ask for changes | | GET | /marketplace/vendors/my/subcontracts | Vendor | Inbox of assigned subcontracts |
/vendors/search now allows SOURCE role (previously locked to advertiser/agency) so the picker works for publishers too.
#### Frontend
SubcontractPanelon Source-side deal page: vendor picker (search), title/description/due-date/amount form, status timeline per subcontract, Approve/Revise/Reject buttons.- New
/vendors/my/marketplacepage (Vendor inbox) with Submit/Resubmit forms keyed offASSIGNEDandREVISION_REQUESTEDstates. - Sidebar adds Marketplace work entry under VENDOR mode.
4. Line Item Assignee + Brief Workflow
Campaign line items get a real assignee + brief lifecycle (migrations 3c4d5e6f7a8b, 4d5e6f7a8b9c, 6f7a8b9c0d1e):
- New columns on
campaign_line_items:assignee_workspace_id(the source/influencer doing the work),brief_*fields (deliverables, posting timeline, brand mentions),creative_url,submitted_at,approved_at,rejection_reason. - New status values normalized to UPPERCASE matching the rest of the codebase.
- Source dashboard surfaces assigned line items via a new
/source/assigned-line-itemsendpoint. - Frontend
/campaigns/[id]/line-itemspage rebuilt (~550 lines) with assignee picker, per-item brief expander, status-aware action buttons (assign, submit, approve/reject).
5. Misc polish
- Marketplace deal page accepts
client_idon bid creation and surfaces it on deal/bid details. - Marketplace
MarketplaceNavsimplified. - Influencer "My Deliverables" page added under
/influencer/deliverables(links to assigned line items).
Migrations to apply
``bash cd backend && source venv/bin/activate && alembic upgrade head ``
Order applied (each safe, additive only): 1. 1a2b3c4d5e6f — marketplace deal review state 2. 3c4d5e6f7a8b — line item assignee workflow 3. 4d5e6f7a8b9c — line item status uppercase 4. 2b3c4d5e6f7a — agency client brands 5. 6f7a8b9c0d1e — line item brief fields 6. 5e6f7a8b9c0d — marketplace deal subcontracts
Known limits (intentional, future tiers)
- Vendor subcontracts: No notifications when assigned. No auto-rollup of approved subcontract output into parent deal's delivery. No Source→Vendor payment flow (
agreed_amountrecorded only). - Agency multi-client: Tier 1 only. Per-client dashboards (Tier 2), margin tracking (Tier 3), multi-tier approvals (Tier 4), per-client restriction enforcement (Tier 5) all deferred.
- Buyer review: Auto-release uses lazy sweep —
PENDING_REVIEWdeals expire only when someone hitsGET /dealsorGET /deals/{id}. Acceptable while Celery is parked.
---

