Buyer-Review Workflow + Agency Tier 1 + Vendor Subcontracts

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.

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 DealStatus enum 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 of GET /deals and GET /deals/{id}.
  • Any PENDING_REVIEW deal whose review_deadline_at < now() is auto-flipped to COMPLETED.
  • 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_brands table: agency_workspace_id, name, contact + billing fields, restrictions JSONB, requires_approval (Tier 4 hook), is_active. Uniqueness on (agency, name).
  • New nullable columns: marketplace_bids.client_id, marketplace_deals.client_id (FK to client_brands ON 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/clients page: 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 for AGENCY_ADMIN users.
  • client_id propagates: bid → deal on accept.
  • Sidebar adds Clients entry under AGENCY mode.

3. Vendor↔Marketplace — Subcontracts (Phase 1)

#### Schema (migration 5e6f7a8b9c0d)

  • New marketplace_deal_subcontracts table 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

  • SubcontractPanel on Source-side deal page: vendor picker (search), title/description/due-date/amount form, status timeline per subcontract, Approve/Revise/Reject buttons.
  • New /vendors/my/marketplace page (Vendor inbox) with Submit/Resubmit forms keyed off ASSIGNED and REVISION_REQUESTED states.
  • 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-items endpoint.
  • Frontend /campaigns/[id]/line-items page 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_id on bid creation and surfaces it on deal/bid details.
  • Marketplace MarketplaceNav simplified.
  • 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_amount recorded 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_REVIEW deals expire only when someone hits GET /deals or GET /deals/{id}. Acceptable while Celery is parked.

---

Want to see it in your own workspace?

Bring planning, discovery, delivery, and measurement into one platform.