Files
ERP-core/docs/14-BACKEND-SALES-API.md
T

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 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:

{
  "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:

{
  "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:

{
  "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:

[
  {
    "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-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.