279 lines
6.8 KiB
Markdown
279 lines
6.8 KiB
Markdown
# 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 in `docs/SALES_MODULE_PLAN.md` and the sales-related backend progress is tracked in `Backend/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-Match` on mutable resources
|
|
- Validation: `ProblemDetails` / `ValidationProblemDetails`
|
|
|
|
---
|
|
|
|
## 2. Sales Invoices
|
|
|
|
### `GET /api/v1/sales-invoices`
|
|
Query:
|
|
- `page`
|
|
- `pageSize`
|
|
- `q`
|
|
- `status`
|
|
- `customerId`
|
|
- `warehouseId`
|
|
|
|
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:
|
|
```json
|
|
{
|
|
"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:
|
|
- `page`
|
|
- `pageSize`
|
|
- `q`
|
|
- `status`
|
|
- `customerId`
|
|
- `warehouseId`
|
|
|
|
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:
|
|
```json
|
|
{
|
|
"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:
|
|
- `page`
|
|
- `pageSize`
|
|
- `q`
|
|
- `customerId`
|
|
- `warehouseId`
|
|
|
|
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:
|
|
```json
|
|
{
|
|
"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=true` and/or `FreeQty>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:
|
|
```json
|
|
[
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"reportType": "item-summary",
|
|
"from": "2026-07-01",
|
|
"to": "2026-07-28",
|
|
"itemId": 1,
|
|
"customerId": null,
|
|
"warehouseId": 1
|
|
}
|
|
```
|
|
|
|
Supported report types:
|
|
- `daily-summary`
|
|
- `item-summary`
|
|
- `customer-summary`
|
|
- `warehouse-summary`
|
|
- `discount-summary`
|
|
- `free-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-summary` auto-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.
|