Reports Module — Vendor
Vendor-scoped, registry-driven operational reports (orders sub-orders) with date-ranged paginated views, KPI/trend, and async CSV/XLSX exports — all locked to the authenticated vendor.
The vendor-facing mirror of the admin reporting surface: date-ranged, row-level operational reports scoped to the authenticated vendor, with an on-screen paginated view and async CSV/XLSX file exports. Today it exposes one report — Orders (one row per sub-order belonging to the vendor).
Source:
api-modules/reports/src/controllers/vendor-report.controller.ts+vendor-report-export.controller.ts.Optional plugin. Removing
ReportsModule.forRoot({ … })fromapp.module.tsdisables every report + export route (admin and vendor), stops the export worker and its retention cron, and leaves thereport_exporttable inert.
How it works
Vendor reports use the same registry-driven machinery as the admin surface (a ReportDefinition provides key, title, columns, filters, filtersSchema, run(), plus optional kpis/summary() and trendSeries/trend()), registered via ReportsModule.forRoot({ vendorReports: [...] }). The only difference: every run is locked to the caller's vendor — the report's queries filter on req.vendor.id, so a vendor only ever sees its own sub-orders and its own money columns (its allocated subtotal / discount / shipping / tax / total), never another supplier's figures or whole-order totals.
- View is synchronous + paginated, filtered to a
[from, to)window. The orders report also exposes summary KPI cards (GET .../summary) and a trend chart (GET .../trend). - Export is asynchronous:
POST .../exportenqueues a BullMQ job; a worker runs the report (scoped to the vendor) in batches, writes a CSV/XLSX file to object storage, and flips thereport_exportrow toready. The vendor polls the exports list and downloads the file. Files auto-expire after 7 days (daily retention sweep); the row remains asexpired.
A date range is required for exports and capped at 366 days. Export files are capped at 200,000 rows — beyond that the file is marked truncated (never silently cut).
Built-in reports
| Key | Row granularity | Date field | Filters |
|---|---|---|---|
orders | one sub-order (the vendor's slice of an order) | order placed date | fulfillmentStatus |
fulfillmentStatus is one of pending | fulfilled | delivered | cancelled | returned. Columns: orderNumber, placedAt, customerName, fulfillmentStatus, paymentStatus, and the vendor's subtotal, discountAllocated, shippingCost, taxAmount, total (all money is integer subunits). KPIs: orders, grossRevenue, avgOrderValue, discountTotal, shippingTotal. Trend series: orders, revenue.
Conventions
Authentication
All endpoints require a Better-Auth vendor session. There is no permission/RBAC gating — scoping is implicit to the active vendor (resolveActiveVendorId(session)). An export id belonging to another vendor (or to a store-wide admin export) resolves to 404, never 403.
Response envelope
Successful responses are wrapped by ResponseInterceptor into { data, message, statusCode, metadata? }. List/run endpoints use the canonical metadata: { total, limit, offset, hasMore }. The download endpoint streams the raw file (Content-Disposition: attachment) and bypasses the envelope.
Endpoints
GET /vendor/reports
List registered vendor reports with their metadata.
Response data is an array of:
{
"key": "orders",
"title": "Orders",
"description": "Your sub-orders in the range with your totals, fulfillment, and payment status.",
"dateField": "Order placed date",
"columns": [{ "key": "orderNumber", "label": "Order #", "type": "string" }, /* … */],
"filters": [{ "key": "fulfillmentStatus", "label": "Fulfillment status", "type": "enum", "options": [/* … */] }],
"kpis": [{ "key": "orders", "label": "Orders", "type": "number" }, /* … */],
"trendSeries": [{ "key": "orders", "label": "Orders", "type": "number" }, /* … */]
}Column type is one of string | number | money | date | datetime | boolean (money is integer subunits).
GET /vendor/reports/:key/meta
Metadata for a single report (same shape as one element above).
GET /vendor/reports/:key/summary
Summary KPI values for the range. Same query params as the run endpoint (from, to, filters). Returns:
{ "kpis": [{ "key": "orders", "label": "Orders", "type": "number", "value": 312 }, /* … */] }value is null when not computable; empty kpis when the report has no summary.
GET /vendor/reports/:key/trend
Time-bucketed series for the range (bucketed by day, or by month for ranges over ~62 days). Returns:
{
"granularity": "day",
"series": [{ "key": "orders", "label": "Orders", "type": "number" }, /* … */],
"points": [{ "bucket": "<ISO>", "orders": 12, "revenue": 458900 }, /* … */]
}GET /vendor/reports/:key
Run a report (paginated, on-screen).
Query params:
| Param | Type | Notes |
|---|---|---|
from | ISO datetime | inclusive; omitted → last 30 days |
to | ISO datetime | exclusive; omitted → now |
filters | JSON string | report-specific, validated against filtersSchema |
limit | int (1–500) | default 100 |
offset | int | default 0 |
Response: paginated array of column-keyed row objects (metadata carries totals).
POST /vendor/reports/:key/export
Queue an async export. Body:
{ "format": "csv" | "xlsx", "from": "<ISO>", "to": "<ISO>", "filters": { /* optional */ } }from/to are required. Returns the created report_export row (status: "pending").
GET /vendor/report-exports
List the vendor's export history (newest first). Query: reportKey?, status?, limit (default 50), offset. Each row:
{
"id": "…",
"reportKey": "orders",
"format": "csv",
"status": "pending | processing | ready | failed | expired",
"params": { "from": "<ISO>", "to": "<ISO>", "filters": {} },
"rowCount": 312,
"truncated": false,
"fileName": "orders-20260101-20260201.csv",
"fileSize": 21884,
"error": null,
"createdAt": "<ISO>",
"completedAt": "<ISO>",
"expiresAt": "<ISO>",
"downloadUrl": "/vendor/report-exports/…/download" // only when ready
}GET /vendor/report-exports/:id
Status of a single export — poll this (or the list) until status: "ready". 404 if the export belongs to another vendor.
GET /vendor/report-exports/:id/download
Stream a ready export file as an attachment. 404 if not ready / expired / unknown / not owned by the caller.
Profile Customization — Vendor
Vendor self-service of the seller page and account details: logo, banner, bio, business identity/contact/address, the cover-image carousel, and the payout bank account.
Reviews Module — Vendor surface
Vendor-facing HTTP surface for moderating reviews on the vendor's own products — listing, editing content, approving/rejecting, marking/unmarking spam, and soft-deleting. List is…