backendApi · capExpertApp · nothing built yet
Build & lifecycle
What each step of an offer writes once the thread is the bundle and the negotiation is per bundle, the reads that move onto the items table, and the work in the API and the front end that gets there. In a bundle of one, every step writes what it writes today, plus one item at send.
Rows each step writesper bundle
One offer row per step, bound to the bundle, whatever its size. Items are written once, at send.
| Step | Who | Offer row | Bundle and items | Other tables |
|---|---|---|---|---|
| Send | vendor | One new row: Awaiting Response, the total. | A new bundle pointing at it, and one item per listing. | Every item’s status rollup. |
| Counter | vendor | One new row: Awaiting Response. | The bundle’s current and vendor pointers move to it. | Rollups. |
| Counter | facility | One new row: Offer Received. | The bundle’s current and facility pointers move to it. | Rollups. |
| Accept | whoever did not make the current offer | The current row: Awaiting Signature, with the previous status kept. Competing offers on each item’s listing: Sold. | — | One PA row. docusign_request_id on every item’s listing. Each listing’s inspection: Sold. |
| Reject | same as accept | The current row: Offer Rejected. | — | Rollups. |
| Cancel | vendor or admin | The current row: Canceled. | — | Rollups. |
| PA declined or voided | DocuSign, import role | The row: PA Rejected. | — | docusign_request_id cleared on every item’s listing. |
| PA resent | either side | The row: Awaiting Signature. | — | A new PA row; every item’s listing points at it. |
| PA signed | completion task, sent with the bundle id | The row: Logistics Required. Competing offers on each listing: Sold. | — | One transaction; one line per item plus component lines; listings and inventory sold; the PA row gets the transaction id. |
| A listing goes elsewhere | cart, offline sale, bulk action, transfer | The current row of every bundle that has the listing as an item: Sold or Removed, which closes each of those bundles whole. | — | Those bundles’ PAs voided through their offer rows. The vendor is told which listing went. |
| Divestiture turns to auction | cron, import role | Soft-deleted, as today, for every bundle with that listing as an item. | The whole bundle and its items soft-deleted. | PAs voided; rollups for those bundles’ other listings. |
| Vendor account removed | system | All of that vendor’s bundles: Removed, as today. | — | — |
Reads that move onto items13 groups · both roles
A thread no longer names a listing, so every read that goes from a listing to its offers now goes listing → items → bundle → current offer row. Once M2 drops the old listing columns, any of these that was missed fails with an error instead of quietly finding nothing.
- G1Status rollup.
counter-offer-helper.service.ts:189-246, and the import role’s copy inmarketplace-pa.service.ts:272-318both - G2PA locks: the in-PA flag and the can-still-offer check.
marketplace-finder.service.ts:284-330,counter-offers.service.ts:455-477primary - G3The sent check at send.
vendor-offers.service.ts:90-111primary - G4The close helpers used when a listing sells or is removed.
counter-offer-helper.service.ts:25-51,counter-offers.service.ts:420-453primary - G5Counter, cancel, history and competing offers.
counter-offers.service.ts:94, 196-202, 256, 269, 282, 910-946primary - G6Completion, and its task payload.
vendor-offer-transaction.service.ts:100, 137-145, 630,request-schema.ts:287primary - G7Listing views: the offer amount per listing, offer counts, the divest lock, the vendor-offer grid and the latest-rejected subqueries.
counter-offer-helper.service.ts:136-169,market-place.service.ts:697-720, 1099-1108, 1553,marketplace.base.query.ts:154, 644-760,import/modules/marketplace/marketplace.service.ts:124both - G8My Offers and a listing’s offers.
vendor-offers-base-query.ts,vendor-offers.service.ts:487-663primary - G9The PA: the DocuSign callback and its fallback, the PA emails, resend, voiding, and the DocuSign list with its facility filter.
marketplace-pa.service.ts:55-75, 168, 187-189, 327, 351,pa-mail.service.ts:141,marketplace-mail.service.ts:825-917,counter-offers.service.ts:831,par-docusign.service.ts:443,docusign.helper.service.ts:359-501both - G10Crons and action feeds: the auction cron, feed seed and reconcile, and vendor cards.
cron.service.ts:152-238, 1223-1252,seed-action-feeds.service.ts:1432-1508,reconcile-action-feeds.service.ts:630-682,marketplace-vendor-event-handler.service.tsboth - G11The CaptAIn marketplace prompts: join through items, and count each offer once.
captain-prompts/registry/marketplace.prompts.tsprimary - G12Model links: the listing’s link to offer threads, the offer row’s link to its listing, and the PA’s link to its listing. Each include that names one is found by search, since a removed link fails only at run time.
offerAssociations,marketPlaceData,marketplaceDetailsboth - G13Listing responses keep the
offerAssociationsfield the front end reads, now built through items, so the details modal and the cards keep working.primary
API · primary rolebackendApi
- A1Shared helpers in
src/shared/modules/vendor-offers/, because both roles need them: the bundles a listing is in, a vendor’s live bundle for a listing, a bundle’s listings, and the split of the total at completion.new - A2Send, one route for one or more listings:
POST /vendor-offer/send-offerwith the listing ids, the total, and a vendor id that only an admin may pass. It locks the listing rows, runs today’s per-listing checks for each plus R1, and writes the bundle, its items and the first offer row in one transaction.Today’ssend-offer/:marketplaceIdcalls the same code with one listing until the front end has moved over (Q7).change - A3Counter, accept, reject, cancel, PA resend, offline PA upload and history keep their routes. A listing id plus the vendor finds the vendor’s bundle containing that listing. A status change is one row update; a counter adds one row and moves the bundle’s pointers.change
- A4Accept locks the bundle’s listing rows and re-checks them inside its transaction, and accepts only the bundle’s current offer, so two accepts cannot both win a listing.change
- A5PA document and envelope list every item and its components, with the bundle total as the grand total.change
- A6Completion receives the bundle id, and still accepts the old listing-and-vendor payload for one release, for tasks already queued. It writes one transaction with one line per item at its part of the total.change
- A7A listing going elsewhere: the close helpers find every bundle with that listing as an item and close each whole, voiding their PAs through their offer rows. The sent check then allows a new offer after Sold or Removed, as well as Canceled (Q2).change
- A8Emails and notifications: one per bundle step, listing every item. The equipment block is built in code, so no template migration is needed.change
API · import rolebackendApi
- I1DocuSign callback and its cron fallback reach the bundle through the PA’s offer row. A declined or voided PA is one row update, then
docusign_request_idis cleared and the rollup re-run on every item’s listing. A signed PA sends the completion task with the bundle id.change - I2Divestiture-to-auction cron finds bundles through the items and removes each whole.change
- I3Action-feed seed and reconcile write one vendor card per bundle.change
What the screens getAPI responses
- V1Vendor “My Offers” is one row per bundle — already the table’s grain — with its item count and total, and its listings in the row expansion.change
- V2Facility “My Offers” stays one row per listing, because a listing can be in several vendors’ bundles. A listing in a bundle of more than one carries a badge.badge
- V3History, a listing’s offers, and listing responses add the bundle id, the item count and the listings. A listing’s offers list compares a bundle’s total with its items’ combined FMV, not with one listing’s.change
Front endcapExpertApp
- F1One offer dialog for one or more listings. It shows the listings, which can be removed, one total, and the existing address check, and confirms “N items for $X”. The details modal’s “Send offer” opens it with one listing.new
- F2Browse Market selection for vendors: row selection in the list view and a checkbox on each card. The selection survives paging, and listings from other sellers are disabled after the first pick. A bulk bar shows “Send offer (n)”.new
- F3The offer thread modal shows the bundle’s items instead of a single item card and the total at each step, and says each action applies to all N items.change
- F4Badges and grouping: a listing in a bundle shows the bundle total and item count wherever it shows an offer; vendor My Offers rows expand to their listings.change
- F5The DocuSign notifications list links a PA to its bundle’s listings instead of one listing.change
- F6Regenerate the swagger client after the DTO changes, and restart the front end.step
Proofbefore anything is called done
- P1Unit tests: the split at completion adds up to the total; send is refused for mixed sellers, a component listing, a locked listing, or a listing already in this vendor’s live bundle; a counter moves the bundle’s pointers; accept takes the lock; a listing going elsewhere closes the whole bundle and voids its PA.specs
- P2Each read group G1–G13 exercised with a bundle of several listings, checked from every one of its listings.specs
- P3End to end on the local database: a vendor bundles three listings, the facility counters, the vendor accepts; one PA lists all three; an offline-PA upload completes it. Expect one bundle, three items, one offer row per step, one transaction with three lines, all three listings sold, and another vendor’s offer on one of them closed at accept.e2e
- P4A bundle of one run end to end behaves exactly like today’s single-listing offer.regression
- P5The migration checks on Migration all pass, and swagger for each role differs only by the new route and the new fields.data · contract
Build orderthree repos, separate commits
- dbMigrations: M1.
- backendApi: entities, the shared helpers, send, the other steps, the read groups G1–G13, then the import role.
- capExpertApp: the offer dialog, Browse Market selection, the thread modal, badges and grouping, and the DocuSign list.
- dbMigrations: M2, once the API is live.