Multi-item vendor offers · plan · 29 September 2026 · nothing built yet

Every offer becomes a bundle

Today a vendor offer covers exactly one marketplace listing. This plan lets one offer cover several listings from the same facility, for one total price, negotiated once and accepted or turned down as a whole. The offer thread becomes the bundle, a new items table lists its listings, and each offer or counter-offer is one row for the whole bundle. A single-listing offer is a bundle of one, and every existing offer already is one.

New table
1marketplace_offer_items
Columns added
2item_count on bundles, the bundle link on offer rows
Columns dropped
3the listing on threads, offer rows and PAs
Open decisions
8each with a default
the direction

One flow, one negotiation, no lead listing. Each row of marketplace_offer_associations becomes a bundle: the vendor, the seller, its item count, and the pointers to its current and last offers. The new marketplace_offer_items lists its listings. The offer rows, the PA and the completion task all point at the bundle, so a status change is one update and nothing is stored under one chosen listing.

Decisions takenD1–D7

  1. D1One total price for the whole bundle. Both sides send and counter one number.decided
  2. D2All or nothing. Counter, accept, reject and cancel act on the whole bundle, and it ends in one PA and one transaction.decided
  3. D3One seller facility per bundle. Every listing in it belongs to the same facility account.decided
  4. D4Vendors send bundles from the marketplace.Admins sending on a vendor’s behalf is Q5 below.decided
  5. D5One flow for every offer. A single-item offer is a bundle whose item_count is 1, and every existing offer becomes one.decided
  6. D6The negotiation is per bundle: each offer or counter-offer is one row for the whole bundle.decided
  7. D7The offer thread is the bundle, and a new items table holds its listings. There is no lead listing: the offer rows, the PA and the completion task all point at the bundle.decided

The modelthree tables

Two tables exist today and change meaning; the items table is new. Column-by-column detail is on Tables & changes.

LevelTableOne row perHolds
Bundlemarketplace_offer_associationsofferThe vendor, the seller facility, how many listings it covers (item_count), who acted last, and the pointers to the current offer row and to each side’s last offer.
Itemmarketplace_offer_itemslisting in a bundleThe bundle and the listing. Nothing else: the listing’s details stay on the listing.
Offer rowmarketplace_counter_offersbundle, per negotiation stepStatus, who acted and the total, for the whole bundle, and the bundle it belongs to.

Why this structurethree options compared

The thread table already holds exactly what a bundle needs, and every existing thread is already a bundle of one. What it cannot hold is a list of listings, which is what the items table is for.

Thread as the bundle, plus items — chosenBrand-new tables, old data movedA bundles table beside the threads
New tables1 — items3 — offers, items, steps1 — bundles
Existing dataStays where it is. One item per thread is added.Copied, then the old tables dropped. Every stored id is remapped: PAs point at offer rows, action feeds at threads.Stays. One bundle per thread is added.
DuplicationNone.None.The same pointers on every listing’s thread row.
Code that changesEvery read from a listing to its offers — 13 groups.Every read and write of offers.Fewer reads, but a lead listing to store rows under.
A read that is missedFails loudly once the old listing columns are dropped.Fails loudly.Quietly finds nothing for all but one listing.
what it costs

A thread no longer names a listing, so every read that goes from a listing to its offers moves onto the items table: the status rollup, the PA locks, the sent check, the close helpers, completion, the crons and action feeds, the listing views, My Offers and the PA screens. They are listed in 13 groups on Build & lifecycle.

How the total is storedone number

The offer row’s amount is the one total both sides negotiate, as it is for a single offer today. No per-listing price is stored while the offer is negotiated. The total is split into per-listing prices once, at completion, for the transaction’s lines — the split rule is Q1. A screen that shows an offer on one listing shows the bundle total and its item count.

The lifecycleper bundle

The rows each step writes are on Build & lifecycle.

  1. Send. The vendor picks one or more listings from one facility and one total. One bundle, one item per listing, one offer row.
  2. Counter. Either side answers with a new total, as often as needed. One new offer row each time; the bundle’s pointers move to it.
  3. Accept. The bundle’s offer row moves to Awaiting Signature, competing offers on its listings close, and one PA goes out listing every item.
  4. PA signed. One transaction covers every item and its components, and all of them are marked sold.
  5. Or it ends early. Reject, cancel, a declined or voided PA, or one of its listings going to someone else closes the bundle’s offer row.

Out of scopenon-goals

Still to decideQ1–Q8 · default first

  1. Q1How the total splits into the transaction’s line prices at completion: by FMV, equal when FMV is missing (default), or the whole amount on one line, the way components work today.price
  2. Q2When one listing in a live bundle goes to someone else, the bundle closes as Sold or Removed, exactly as a single offer does today, and the vendor may offer again on the listings still available (default).Today a new offer is allowed only after Canceled, so the sent check also accepts Sold and Removed.status
  3. Q3At most 50 listings in one bundle (default). It bounds the PA document and the transaction.limit
  4. Q4Divestiture and regular listings may share a bundle when each passes its own checks (default).rule
  5. Q5Admins sending on a vendor’s behalf send one listing, as today (default), or bundles too.sender
  6. Q6A new offer after a bundle ends is a new bundle, and the ended one stays in My Offers as its own row (default). Today a re-offer after a cancel reuses the old thread.history
  7. Q7The one-listing send route stays for one release while the front end moves to the new route, then goes (default).route
  8. Q8A vendor’s own My Offers keeps one list, one row per offer with its item count (default), or gets the same two tabs as the facility and admin pages (Offer tabs).vendors