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>
181 lines
10 KiB
Markdown
181 lines
10 KiB
Markdown
---
|
|
title: PurchaseRequest
|
|
tags: [data-model, mongoose]
|
|
aliases: [Purchase Request, Buy Request, IPurchaseRequest]
|
|
---
|
|
|
|
# PurchaseRequest
|
|
|
|
> **Last updated:** 2026-05-30 — `budget.currency` locked to USDT; `categoryId` added to `IRequestTableItem`
|
|
|
|
The central buyer-side document. A `PurchaseRequest` captures what a buyer wants to acquire (physical product, digital product, service, or consultation), the budget envelope, urgency, delivery details, and the entire lifecycle from creation through payment, delivery, and completion. Sellers respond by attaching [[SellerOffer]] documents; the buyer accepts one, a [[Payment]] is opened, and delivery is verified by a 6-digit code.
|
|
|
|
> [!note] Source
|
|
> `backend/src/models/PurchaseRequest.ts:95` — schema definition
|
|
> `backend/src/models/PurchaseRequest.ts:387` — model export
|
|
|
|
## Schema
|
|
|
|
| Field | Type | Required | Default | Validation | Index | Description |
|
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
| `buyerId` | ObjectId → [[User]] | yes | — | — | yes | Buyer that owns the request. |
|
|
| `title` | String | yes | — | trim, maxlength 200 | — | Short headline. |
|
|
| `description` | String | yes | — | trim, minlength 5 (frontend), maxlength 2000 | — | Long form description. Frontend enforces a 5-character minimum; the field is optional in the raw schema but the form will reject shorter values. |
|
|
| `categoryId` | ObjectId → [[Category]] | yes | — | — | yes | Category the request belongs to. |
|
|
| `productType` | String | no | `physical_product` | enum: `physical_product` / `digital_product` / `service` / `consultation` | yes | What kind of fulfilment is expected. |
|
|
| `productLink` | String | no | — | trim, must match `/^https?:\/\/.+/` | — | Reference URL for the desired product. |
|
|
| `size` | String | no | — | trim, maxlength 100 | — | Product size. |
|
|
| `color` | String | no | — | trim, maxlength 100 | — | Product color. |
|
|
| `brand` | String | no | — | trim, maxlength 100 | — | Brand preference. |
|
|
| `preferredSellerIds[]` | ObjectId → [[User]] | no | `[]` | — | — | Targeted sellers for a private request. |
|
|
| `quantity` | Number | no | `1` | min 1 | — | Unit count. |
|
|
| `budget.min` | Number | no | — | min 0 | — | Lower bound. |
|
|
| `budget.max` | Number | no | — | min 0 | — | Upper bound. |
|
|
| `budget.currency` | String | no | `USDT` | enum: `USDT` (escrow MVP — USD/EUR/IRR removed in commit 3aaa2fe) | — | Budget currency. |
|
|
| `urgency` | String | no | `medium` | enum: `low` / `medium` / `high` / `urgent` | yes | Buyer urgency. |
|
|
| `status` | String | no | `pending` | enum (13 values — see State Transitions below) | yes | Lifecycle state. |
|
|
| `isPublic` | Boolean | no | `true` | — | — | Public marketplace listing vs. private request. |
|
|
| `tags[]` | String[] | no | — | trim | — | Free-form tags. |
|
|
| `specifications[].key` | String | yes | — | trim | — | Spec key. |
|
|
| `specifications[].value` | String | yes | — | trim | — | Spec value. |
|
|
| `specifications[].label` | String | no | — | trim | — | Human label. |
|
|
| `deliveryInfo.deliveryType` | String | yes | `physical` | enum: `physical` / `online` | — | Delivery channel. |
|
|
| `deliveryInfo.address` | String | no | — | — | — | Physical address. |
|
|
| `deliveryInfo.preferredDate` | Date | no | — | — | — | Buyer's target date. |
|
|
| `deliveryInfo.notes` | String | no | — | — | — | Free-form notes. |
|
|
| `deliveryInfo.deliveryAddress.name` | String | no | — | — | — | Recipient name. |
|
|
| `deliveryInfo.deliveryAddress.phoneNumber` | String | no | — | — | — | Recipient phone. |
|
|
| `deliveryInfo.deliveryAddress.fullAddress` | String | no | — | — | — | Full address string. |
|
|
| `deliveryInfo.deliveryAddress.addressType` | String | no | — | — | — | e.g. Home / Office. |
|
|
| `deliveryInfo.email` | String | no | — | email regex | — | For digital delivery. |
|
|
| `deliveryInfo.sellerDeliveryInfo.estimatedDeliveryDate` | Date | no | — | — | — | Seller's ETA date. |
|
|
| `deliveryInfo.sellerDeliveryInfo.estimatedDeliveryTime` | String | no | — | — | — | Seller's ETA time. |
|
|
| `deliveryInfo.sellerDeliveryInfo.trackingNumber` | String | no | — | — | — | Carrier tracking. |
|
|
| `deliveryInfo.sellerDeliveryInfo.deliveryNotes` | String | no | — | — | — | Notes from seller. |
|
|
| `deliveryInfo.sellerDeliveryInfo.shippingMethod` | String | no | — | — | — | Method label. |
|
|
| `deliveryInfo.sellerDeliveryInfo.downloadLink` | String | no | — | — | — | Download URL for digital products. |
|
|
| `deliveryInfo.sellerDeliveryInfo.digitalFiles[]` | String[] | no | — | — | — | Digital file URLs. |
|
|
| `deliveryInfo.deliveryDateTime` | Date | no | — | — | — | Confirmed delivery datetime. |
|
|
| `deliveryInfo.deliveryDate` | Date | no | — | — | — | Confirmed delivery date. |
|
|
| `deliveryInfo.shippedAt` | Date | no | — | — | — | Timestamp of shipment. |
|
|
| `deliveryInfo.deliveryCode` | String | no | — | trim, length 6 | — | 6-digit handoff code. |
|
|
| `deliveryInfo.deliveryCodeGeneratedAt` | Date | no | — | — | — | When code was issued. |
|
|
| `deliveryInfo.deliveryCodeExpiresAt` | Date | no | — | — | — | When code expires. |
|
|
| `deliveryInfo.deliveryCodeUsed` | Boolean | no | `false` | — | — | Whether the code has been redeemed. |
|
|
| `deliveryInfo.deliveryCodeUsedAt` | Date | no | — | — | — | When it was redeemed. |
|
|
| `deliveryInfo.deliveryCodeUsedBy` | ObjectId → [[User]] | no | — | — | — | Seller that redeemed. |
|
|
| `deliveryInfo.deliveredAt` | Date | no | — | — | — | Final delivery timestamp. |
|
|
| `deliveryInfo.deliveryAttempts[].sellerId` | ObjectId → [[User]] | yes | — | — | — | Seller making the attempt. |
|
|
| `deliveryInfo.deliveryAttempts[].attemptedAt` | Date | no | `Date.now` | — | — | When attempted. |
|
|
| `deliveryInfo.deliveryAttempts[].success` | Boolean | yes | — | — | — | Whether it succeeded. |
|
|
| `deliveryInfo.deliveryAttempts[].code` | String | no | — | — | — | Code entered (only stored on success). |
|
|
| `serviceInfo.duration` | Number | no | — | min 0.5 | — | Hours, only for service/consultation. |
|
|
| `serviceInfo.sessionType` | String | no | — | enum: `online` / `in_person` / `hybrid` | — | Service session type. |
|
|
| `serviceInfo.location` | String | no | — | trim, maxlength 200 | — | Service location. |
|
|
| `serviceInfo.requirements[]` | String[] | no | — | trim | — | Pre-requisites. |
|
|
| `attachments[]` | String[] | no | — | — | — | Attached file URLs. |
|
|
| `offers[]` | ObjectId → [[SellerOffer]] | no | — | — | — | Offers received. |
|
|
| `selectedOfferId` | ObjectId → [[SellerOffer]] | no | `null` | — | — | Accepted offer. |
|
|
| `rating` | Number | no | `null` | min 1, max 5 | — | Buyer's post-delivery rating. |
|
|
| `feedback` | String | no | `null` | maxlength 1000 | — | Buyer's feedback text. |
|
|
| `deliveryConfirmed` | Boolean | no | `false` | — | — | Buyer confirmation flag. |
|
|
| `deliveryConfirmedAt` | Date | no | `null` | — | — | Confirmation timestamp. |
|
|
| `metadata.source` | String | no | `manual` | enum: `manual` / `template` / `api` | — | Where the request came from. |
|
|
| `metadata.templateId` | String | no | — | trim | — | Originating [[RequestTemplate]] id. |
|
|
| `metadata.version` | String | no | — | trim | — | Schema version. |
|
|
| `createdAt` | Date | auto | — | — | yes (desc) | Mongoose timestamp. |
|
|
| `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. |
|
|
|
|
### Status enum — all valid values
|
|
|
|
`pending_payment` · `pending` · `active` · `received_offers` · `in_negotiation` · `payment` · `processing` · `delivery` · `delivered` · `confirming` · `completed` · `seller_paid` · `cancelled`
|
|
|
|
**Note:** `finalized` and `archived` are **not** valid status values and do not appear in the `IPurchaseRequest` frontend type or the Mongoose schema enum. Using either would cause a validation error.
|
|
|
|
## Virtuals
|
|
|
|
None defined.
|
|
|
|
## Indexes
|
|
|
|
Single-field — `backend/src/models/PurchaseRequest.ts:376-381`:
|
|
|
|
- `{ buyerId: 1 }`
|
|
- `{ categoryId: 1 }`
|
|
- `{ productType: 1 }`
|
|
- `{ status: 1 }`
|
|
- `{ createdAt: -1 }`
|
|
- `{ urgency: 1 }`
|
|
|
|
Compound — `backend/src/models/PurchaseRequest.ts:384-385`:
|
|
|
|
- `{ productType: 1, status: 1 }`
|
|
- `{ categoryId: 1, productType: 1 }`
|
|
|
|
## Pre/Post Hooks
|
|
|
|
None declared at the schema level.
|
|
|
|
## Instance Methods
|
|
|
|
None defined.
|
|
|
|
## Static Methods
|
|
|
|
None defined.
|
|
|
|
## Relationships
|
|
|
|
- **References**: [[User]] (`buyerId`, `preferredSellerIds[]`, `deliveryInfo.deliveryCodeUsedBy`, `deliveryInfo.deliveryAttempts[].sellerId`), [[Category]] (`categoryId`), [[SellerOffer]] (`offers[]`, `selectedOfferId`).
|
|
- **Referenced by**: [[SellerOffer]] (`purchaseRequestId`), [[Payment]] (`purchaseRequestId`), [[Dispute]] (`purchaseRequestId`), [[Chat]] (`relatedTo.id` when `relatedTo.type === 'PurchaseRequest'`), [[Review]] (`purchaseRequestId`).
|
|
|
|
## State Transitions
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> pending_payment
|
|
[*] --> pending
|
|
pending_payment --> pending : payment confirmed
|
|
pending --> active : published
|
|
active --> received_offers : first offer
|
|
received_offers --> in_negotiation : buyer engages
|
|
in_negotiation --> payment : offer accepted
|
|
payment --> processing : payment captured
|
|
processing --> delivery : shipped
|
|
delivery --> delivered : handed over
|
|
delivered --> confirming : code redeemed
|
|
confirming --> completed : buyer confirms
|
|
completed --> seller_paid : payout released
|
|
pending --> cancelled
|
|
active --> cancelled
|
|
received_offers --> cancelled
|
|
in_negotiation --> cancelled
|
|
completed --> [*]
|
|
seller_paid --> [*]
|
|
cancelled --> [*]
|
|
```
|
|
|
|
## Common Queries
|
|
|
|
```ts
|
|
// Buyer's open requests
|
|
PurchaseRequest.find({ buyerId, status: { $in: ['pending', 'active', 'received_offers'] } });
|
|
|
|
// Public marketplace feed
|
|
PurchaseRequest.find({ isPublic: true, status: 'active' }).sort({ createdAt: -1 });
|
|
|
|
// Sellers' eligible queue
|
|
PurchaseRequest.find({ productType, status: 'active', categoryId });
|
|
|
|
// Populate offers
|
|
PurchaseRequest.findById(id).populate('offers').populate('selectedOfferId');
|
|
|
|
// Redeem delivery code
|
|
PurchaseRequest.findOneAndUpdate(
|
|
{ _id: id, 'deliveryInfo.deliveryCode': code, 'deliveryInfo.deliveryCodeUsed': false },
|
|
{ $set: { 'deliveryInfo.deliveryCodeUsed': true, 'deliveryInfo.deliveryCodeUsedAt': new Date() } }
|
|
);
|
|
```
|
|
|
|
Related: [[SellerOffer]], [[Payment]], [[Chat]], [[Dispute]], [[Review]], [[RequestTemplate]], [[Category]].
|