Files
nick-doc/02 - Data Models/SellerOffer.md
Siavash Sameni dceaf82934 audit: 2026-05-30 full-codebase audit — report, issues, docs, runbooks
Full-codebase-audit 2026-05-30 outputs:
- Audit report: 09 - Audits/Full Codebase Audit - 2026-05-30.md
- 81 issue files ISSUE-055..135 (decisions + 1 skipped no-brainer).
- Scanner docs from scratch (was zero): architecture, data model, API ref, payment
  flow, operations runbook + repo README.
- Doc-sync updates across API reference, data models, flows, design system.
- Secret Rotation Runbook (08 - Operations) for the exposed credentials.
- Reusable workflow guide (07 - Development) + .claude/workflows/full-codebase-audit.js.

Issues remain status:open intentionally — the code fixes are uncommitted-then-committed
working-tree changes per repo and aren't "resolved" until merged/deployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 18:48:04 +04:00

4.6 KiB

title, tags, aliases
title tags aliases
SellerOffer
data-model
mongoose
Seller Offer
Bid
ISellerOffer

SellerOffer

Last updated: 2026-05-30 — added AML fields (requireAmlCheck, amlBlockOnFailure)

A seller's bid against a PurchaseRequest. Stores the proposed price, the delivery time commitment, optional notes/attachments, and a small status machine (pending / accepted / rejected / withdrawn). The parent PurchaseRequest keeps the array of offer ids in offers[] and the chosen one in selectedOfferId.

[!note] Source backend/src/models/SellerOffer.ts:24 — schema definition backend/src/models/SellerOffer.ts:100 — model export

Schema

Field Type Required Default Validation Index Description
sellerId ObjectId → User yes yes Seller submitting the bid.
purchaseRequestId ObjectId → PurchaseRequest yes yes Parent request.
title String yes trim, maxlength 200 Offer headline.
description String yes trim, maxlength 1000 Pitch and details.
price.amount Number yes min 0 Quoted amount.
price.currency String yes USDT enum: USD / EUR / IRR / USDT / USDC Quote currency.
deliveryTime.amount Number yes min 1 Numeric ETA.
deliveryTime.unit String yes enum: hours / days / weeks ETA unit.
status String no pending enum: pending / accepted / rejected / withdrawn yes Offer status.
attachments[] String[] no URLs of supporting files.
notes String no trim Internal/private notes.
validUntil Date no Expiration.
requireAmlCheck Boolean no If true, AML screening must pass before the offer is presented to the buyer.
amlBlockOnFailure Boolean no If true and AML screening fails, the offer is blocked. Otherwise it is flagged for manual review.
createdAt Date auto yes (desc) Mongoose timestamp.
updatedAt Date auto Mongoose timestamp.

Status enum note: Valid values are pending | accepted | rejected | withdrawn only. 'active' is not a valid status and would throw a Mongoose ValidationError if passed.

Virtuals

None defined.

Indexes

Defined at backend/src/models/SellerOffer.ts:95-98:

  • { sellerId: 1 }
  • { purchaseRequestId: 1 }
  • { status: 1 }
  • { createdAt: -1 }

Pre/Post Hooks

None declared.

Instance Methods

None defined.

Static Methods

None defined.

Service notes

createOffer — eligible parent request statuses

createOffer in SellerOfferService permits offers against a PurchaseRequest whose status is pending, received_offers, or active. Attempts against any other status are rejected.

withdrawOffer() — frontend action available

SellerOfferService.withdrawOffer() is not a dedicated HTTP route. The correct API path is PUT /api/marketplace/offers/:id/status with { status: 'withdrawn' }.

The frontend exposes this via the withdrawOffer(offerId) action in src/actions/marketplace.ts (added commit 240a668). It is called from:

  • step-2-waiting-for-payment.tsx (edit/cancel controls while requestDetails.status === 'received_offers')
  • frontend/src/app/dashboard/seller/marketplace/offers/page.tsx (Offer Management page, bulk view)

Relationships

State Transitions

stateDiagram-v2
    [*] --> pending
    pending --> accepted : buyer accepts
    pending --> rejected : buyer rejects
    pending --> withdrawn : seller cancels
    accepted --> [*]
    rejected --> [*]
    withdrawn --> [*]

Common Queries

// Offers for a request
SellerOffer.find({ purchaseRequestId }).sort({ createdAt: -1 });

// Seller's active offers
SellerOffer.find({ sellerId, status: 'pending' });

// Reject siblings on accept
SellerOffer.updateMany(
  { purchaseRequestId, _id: { $ne: acceptedId }, status: 'pending' },
  { status: 'rejected' }
);

// Cleanup expired offers
SellerOffer.find({ validUntil: { $lt: new Date() }, status: 'pending' });

Related: PurchaseRequest, Payment, User.