6.8 KiB
14 · BACKEND — Sales API Reference
Authoritative for: sales API contracts for invoices, slips, bundle sales, free issues, and sales reports. Navigation: start from
00-CORE.md. Sales business rules live indocs/SALES_MODULE_PLAN.mdand the sales-related backend progress is tracked inBackend/PROGRESS.md. Scope: this document covers the sales endpoints currently implemented in ERPCore. Free issue is modeled as a sales-slip alias, not a separate table.
1. Conventions
- Base URL:
https://{host}/api/v1 - Authentication: same as the rest of the v1 API
- Concurrency:
ETag/If-Matchon mutable resources - Validation:
ProblemDetails/ValidationProblemDetails
2. Sales Invoices
GET /api/v1/sales-invoices
Query:
pagepageSizeqstatuscustomerIdwarehouseId
Returns a paged list of SalesInvoiceSummaryDto.
The current frontend register uses this endpoint directly for the invoice hub view.
GET /api/v1/sales-invoices/{salesInvoiceId}
Returns SalesInvoiceDto.
The detail page shows the document template and only exposes edit actions when the invoice is Draft.
POST /api/v1/sales-invoices
Creates a draft sales invoice.
Request:
{
"customerId": 2,
"warehouseId": 1,
"invoiceType": "B2C",
"lines": [
{
"itemId": 1,
"uomId": 1,
"warehouseId": 1,
"qty": 1,
"freeQty": 0,
"unitPrice": 100,
"allowManualPriceOverride": false,
"discountMode": "Percentage",
"discountPct": 0,
"discountAmount": 0,
"discountValue": 0,
"taxPct": 0,
"isFreeIssue": false,
"parentLineId": null
}
]
}
PUT /api/v1/sales-invoices/{salesInvoiceId}
Updates a draft invoice. Requires If-Match.
POST /api/v1/sales-invoices/{salesInvoiceId}/post
Posts stock out and marks the invoice as Posted.
Before posting, the UI calls GET /api/v1/sales-invoices/{salesInvoiceId}/posting-check to show shortages.
POST /api/v1/sales-invoices/{salesInvoiceId}/cancel
Cancels a draft invoice.
3. Sales Slips
GET /api/v1/sales-slips
Query:
pagepageSizeqstatuscustomerIdwarehouseId
Returns a paged list of SalesSlipSummaryDto.
GET /api/v1/sales-slips/{salesSlipId}
Returns SalesSlipDto.
The detail page follows the same document-style layout and draft-only edit behavior as invoices.
POST /api/v1/sales-slips
Creates a draft sales slip.
Request:
{
"customerId": 2,
"warehouseId": 1,
"cashierUserId": 1,
"lines": [
{
"itemId": 1,
"uomId": 1,
"warehouseId": 1,
"qty": 1,
"freeQty": 0,
"unitPrice": 50,
"allowManualPriceOverride": false,
"discountMode": "Percentage",
"discountPct": 0,
"discountAmount": 0,
"discountValue": 0,
"taxPct": 0,
"isFreeIssue": false,
"parentLineId": null
}
]
}
PUT /api/v1/sales-slips/{salesSlipId}
Updates a draft slip. Requires If-Match.
POST /api/v1/sales-slips/{salesSlipId}/post
Posts stock out and marks the slip as Posted.
Before posting, the UI calls GET /api/v1/sales-slips/{salesSlipId}/posting-check.
POST /api/v1/sales-slips/{salesSlipId}/cancel
Cancels a draft slip.
4. Bundle Sales
Bundle sales are a separate sales document family for fixed bundle compositions.
GET /api/v1/bundle-sales
Query:
pagepageSizeqcustomerIdwarehouseId
Returns a paged list of BundleSaleSummaryDto.
GET /api/v1/bundle-sales/{bundleSaleId}
Returns BundleSaleDto.
GET /api/v1/bundle-sales/{bundleSaleId}/posting-check
Validates component stock before posting.
POST /api/v1/bundle-sales
Creates a draft bundle sale from a fixed template.
Request:
{
"customerId": 2,
"warehouseId": 1,
"cashierUserId": 1,
"bundleSaleTemplateId": 1,
"bundleName": "Summer Promo Pack",
"bundleCode": "BND-001",
"bundlePrice": 2500,
"allowPriceOverride": true
}
PUT /api/v1/bundle-sales/{bundleSaleId}
Updates a draft bundle sale. Requires If-Match.
POST /api/v1/bundle-sales/{bundleSaleId}/post
Posts the bundle and consumes stock from the included component lines.
POST /api/v1/bundle-sales/{bundleSaleId}/cancel
Cancels a draft bundle sale.
Business rule:
- bundle composition is fixed by template lines
- posting consumes component stock, not a synthetic bundle stock item
- print pages should show both the bundle summary and the component breakdown
5. Free Issues
Free issue is a business alias over sales slips. There is no separate free-issue table in the current schema.
GET /api/v1/free-issues
Same contract as GET /api/v1/sales-slips.
GET /api/v1/free-issues/{freeIssueId}
Same contract as GET /api/v1/sales-slips/{salesSlipId}.
POST /api/v1/free-issues
Same contract as POST /api/v1/sales-slips.
PUT /api/v1/free-issues/{freeIssueId}
Same contract as PUT /api/v1/sales-slips/{salesSlipId}.
POST /api/v1/free-issues/{freeIssueId}/post
Same contract as POST /api/v1/sales-slips/{salesSlipId}/post.
POST /api/v1/free-issues/{freeIssueId}/cancel
Same contract as POST /api/v1/sales-slips/{salesSlipId}/cancel.
Business rule:
- free issue is represented by
IsFreeIssue=trueand/orFreeQty>0 - it still posts stock out through the normal slip posting flow
- the frontend free-issue screen is the main working page for create/list/edit/cancel
- cancelled free-issue records are hidden from the working list view
6. Sales Reports
GET /api/v1/reports/sales
Returns the report catalog.
Response:
[
{
"id": "daily-summary",
"name": "Daily Summary",
"description": "Aggregated sales by day across posted invoices and slips.",
"supportedFilters": ["from", "to"]
}
]
GET /api/v1/reports/sales/{reportId}
Returns a single report definition by id.
POST /api/v1/reports/sales/query
Returns the actual report data.
Request:
{
"reportType": "item-summary",
"from": "2026-07-01",
"to": "2026-07-28",
"itemId": 1,
"customerId": null,
"warehouseId": 1
}
Supported report types:
daily-summaryitem-summarycustomer-summarywarehouse-summarydiscount-summaryfree-issue-summary
Validation rule:
- invalid filters for a given report type are rejected
- unsupported report types are rejected
- the frontend report page uses the same endpoint for all report types
daily-summaryauto-loads with a default date range in the UI- the report results table is rendered from the backend response rows, not hardcoded data
7. Notes
- The sales report service reads both invoices and slips where relevant.
- Free issue reporting is derived from the same sales document lines.
- Bundle sales are treated as a separate fixed-composition document family.
- There is no separate free-issue table in the current schema.