Complete all for Items
This commit is contained in:
@@ -81,7 +81,7 @@ Domain errors add a stable `code` (catalog §7):
|
||||
| `POST /api/v1/auth/lock` | LockUserAccount | Required + CSRF |
|
||||
| `POST /api/v1/auth/change-password` | ChangeUserPassword | Required + CSRF |
|
||||
| `POST /api/v1/auth/verify-password` | VerifyPassword | Required |
|
||||
| `POST /api/v1/auth/logout` | LogoutUser | Anonymous* |
|
||||
| `POST /api/v1/auth/logout` | LogoutUser | Anonymous* — `userId` **optional** |
|
||||
| `PUT /api/v1/auth/me` | UpdateUser | Required + CSRF |
|
||||
| `POST /api/v1/auth/2fa/initiate` | initiateTwoFASetup | Required + CSRF |
|
||||
| `POST /api/v1/auth/2fa/complete` | completeTwoFASetup | Required + CSRF |
|
||||
@@ -100,6 +100,13 @@ Domain errors add a stable `code` (catalog §7):
|
||||
than the bearer token — carried over from AuthHex's own design, not introduced by this proxy. Tracked as an accepted risk
|
||||
in docs/02-SECURITY.md Part A.
|
||||
|
||||
> **`POST /auth/logout` — `userId` is optional (2026-07-17).** AuthHex returns `user.userId: null` in its own
|
||||
> login/register response, so a browser never learns the id it would need to send. When omitted, ERPCore resolves it from
|
||||
> the session token's `UserId` claim. The `erp_at`/`erp_rt`/`XSRF-TOKEN` cookies are cleared **regardless** of whether the
|
||||
> upstream revoke succeeds — a logout that leaves the caller holding a live session cookie is worse than one that leaves a
|
||||
> stale session server-side (which lapses on its own). Previously the required `userId` made a browser logout impossible:
|
||||
> the cookies survived and "logging out" was cosmetic.
|
||||
|
||||
Request/response field shapes match AuthHex's own payloads one-for-one (project-root `API_REFERENCE.md` §3–§5), except
|
||||
session-issuing responses omit `AccessToken`/`RefreshToken` (cookie-delivered instead) and `refreshToken` is read from the
|
||||
`erp_rt` cookie rather than the request body.
|
||||
@@ -124,8 +131,10 @@ Query: `q`, `status` (`Active|Inactive`), `categoryId`, `subCategoryId`, `brandI
|
||||
"brandId": 2, "baseUomId": 1,
|
||||
"defaultVendorId": 5, "stockNature": "Stocked", "trackingMode": "Batch", "taxClass": "STD",
|
||||
"status": "Active", "reorder": [ { "warehouseId": 1, "reorderPoint": 500, "reorderQty": 2000 } ],
|
||||
"conversions": [ { "conversionId": 33, "fromUom": 7, "toUom": 1, "factor": 12 } ],
|
||||
"createdAt": "2026-06-01T08:00:00Z", "updatedAt": "2026-07-01T10:15:00Z" }
|
||||
```
|
||||
`conversions` is inlined (added 2026-07-17) because they are otherwise unreadable: `PUT /items/{id}/uom-conversions` returns them but nothing reads them back, so a detail screen could never show current state before editing.
|
||||
|
||||
#### `POST /items`
|
||||
The `sku` is **generated by the client** (it encodes the chosen item-type values, e.g. `BL-100-0003`); the server only enforces uniqueness. `subCategoryId`/`brandId` are optional.
|
||||
@@ -359,7 +368,18 @@ Requires `If-Match`. All three flags are **required** — a partial body is a `4
|
||||
```
|
||||
`POST /requisitions/{id}/submit` → **200 OK** `status: "Submitted"`.
|
||||
|
||||
`GET /requisitions?status=Submitted` → list envelope of `RequisitionSummaryDto` (`{requisitionId, docNo, status, requestedBy, createdAt, lineCount}`).
|
||||
|
||||
### 3.2 RFQs & Quotations
|
||||
#### `GET /rfqs`
|
||||
Query: `q`, `status` (`Open|Closed`), + paging. → list envelope of
|
||||
`{ rfqId, docNo, requisitionId, status, lineCount, quotationCount }`.
|
||||
|
||||
> **`vendorIds` is not persisted.** `POST /rfqs` validates the invited vendors but stores no
|
||||
> RFQ↔vendor link, so neither `GET /rfqs` nor `GET /rfqs/{id}` returns them. Quotations
|
||||
> reference vendors directly — `quotationCount` (and the comparison's `vendorIds`) are the
|
||||
> facts that survive. A UI cannot show "invited but not yet quoted".
|
||||
|
||||
#### `POST /rfqs`
|
||||
```json
|
||||
{ "requisitionId": 210, "vendorIds": [5, 8, 11],
|
||||
@@ -417,6 +437,12 @@ Edit while open (not FullyReceived/Closed/Cancelled); requires `If-Match`. → *
|
||||
**200 OK** `status: "Cancelled"`; `409` if any receipt exists.
|
||||
|
||||
### 3.4 Purchase Returns
|
||||
#### `GET /purchase-returns`
|
||||
Query: `q`, `vendorId`, `warehouseId`, + paging. → list envelope of
|
||||
`{ returnId, docNo, vendorId, warehouseId, reasonCodeId, status, createdBy, createdAt, lineCount }`.
|
||||
|
||||
#### `GET /purchase-returns/{returnId}` → the full return incl. `lines` and `ledgerRefs`; `404` if absent.
|
||||
|
||||
#### `POST /purchase-returns`
|
||||
```json
|
||||
{ "vendorId": 5, "warehouseId": 1, "reasonCodeId": 22,
|
||||
@@ -435,6 +461,12 @@ Edit while open (not FullyReceived/Closed/Cancelled); requires `If-Match`. → *
|
||||
|
||||
## 4. Goods Receipt (GRN)
|
||||
> On **confirm**, each line creates a **FIFO cost layer** and posts an **inbound ledger** entry (FR-GRN-06). Goods may land `holdStatus: "OnHold"` (not issuable) until released.
|
||||
> **No `PUT` or `DELETE` exists for a GRN** — a receipt is corrected with a reversing document, never edited or erased (FR-X-05).
|
||||
> **Gap vs FR-GRN-04:** `CreateGrnLineInput` carries `batch` but has **no serial-number field**, so serials cannot be captured on receipt as the requirement mandates. Tracked in `Backend/PROGRESS.md`.
|
||||
|
||||
### 4.0 `GET /grns`
|
||||
Query: `q`, `status` (`Draft|Confirmed|Closed`), `poId`, `vendorId`, `warehouseId`, + paging. → list envelope of
|
||||
`{ grnId, docNo, poId, vendorId, warehouseId, status, createdBy, createdAt, postedAt, lineCount }`.
|
||||
|
||||
### 4.1 `POST /grns`
|
||||
Against a PO (lines default from open PO lines) or direct (`poId: null`, by permission).
|
||||
@@ -479,9 +511,18 @@ Received `OnHold` → the layer is **not** available until released.
|
||||
{ "itemId": 1001, "warehouseId": 1, "onHand": 5000, "available": 0, "onHold": 5000,
|
||||
"inTransit": 0, "reserved": 0, "asOf": "2026-07-07T10:06:00Z" }
|
||||
```
|
||||
`available = onHand − onHold − reserved − inTransit(out)`. `reserved` always `0` in Phase 1 (stub, FR-STK-11).
|
||||
`available = onHand − onHold − reserved`. `inTransit` is **reported, not subtracted again**: dispatch has already consumed the source layers, so removing it a second time would double-count (docs/10 C.9). `reserved` always `0` in Phase 1 (stub, FR-STK-11).
|
||||
|
||||
#### `GET /stock/on-hand/list?warehouseId=1&itemId=1001`
|
||||
Both filters optional; + paging. On-hand for every (item, warehouse) pair that holds stock — backs the Stock Enquiry list. Pairs come from `STOCK_LAYER`, so an item that never had a receipt in a warehouse does not appear.
|
||||
```json
|
||||
{ "items": [ { "itemId": 1001, "warehouseId": 1, "onHand": 5000, "available": 0, "onHold": 5000,
|
||||
"inTransit": 0, "reserved": 0, "asOf": "2026-07-17T10:06:00Z" } ],
|
||||
"pagination": { "page": 1, "pageSize": 20, "totalItems": 1, "totalPages": 1 } }
|
||||
```
|
||||
|
||||
### 5.2 `GET /stock/ledger?itemId=1001&warehouseId=1&from=2026-07-01&to=2026-07-07`
|
||||
Also accepts **`sourceDocType`** + **`sourceDocId`** — the only way to ask "what movements did this document post?", since the ledger's document reference is polymorphic with no FK to follow (docs/10 C.9). `sourceDocType` is the document-type prefix as stored: `GRN`, `ADJ`, `TRF`, `PRET`, `CNT` (`Domain/DocumentTypes.cs`), not the friendly name.
|
||||
```json
|
||||
{ "items": [ { "ledgerId": 55010, "itemId": 1001, "warehouseId": 1, "binId": 45, "batchId": 410,
|
||||
"serialId": null, "direction": "In", "qtyBase": 5000, "unitCost": 12.50, "value": 62500.00,
|
||||
@@ -501,6 +542,10 @@ Received `OnHold` → the layer is **not** available until released.
|
||||
### 5.4 Transfers (in-transit)
|
||||
> create → dispatch → receive. Dispatch consumes source FIFO layers into in-transit; receive creates the destination layer at inherited cost (cost-preserving, FR-STK-06).
|
||||
|
||||
#### `GET /stock-transfers`
|
||||
Query: `q`, `status` (`Draft|InTransit|Received|Closed`), `srcWarehouseId`, `destWarehouseId`, + paging. → list envelope of
|
||||
`{ transferId, docNo, srcWarehouseId, destWarehouseId, status, createdBy, createdAt, lineCount }`.
|
||||
|
||||
#### `POST /stock-transfers`
|
||||
```json
|
||||
{ "srcWarehouseId": 1, "destWarehouseId": 2,
|
||||
@@ -535,6 +580,12 @@ Received `OnHold` → the layer is **not** available until released.
|
||||
### 5.5 Adjustments (auto-post)
|
||||
> Reason code mandatory. Decrease consumes FIFO layers; increase creates a layer at supplied/last cost (FR-STK-07).
|
||||
|
||||
#### `GET /stock-adjustments`
|
||||
Query: `q`, `warehouseId`, `reasonCodeId`, + paging. Newest first. → list envelope of
|
||||
`{ adjustmentId, docNo, warehouseId, reasonCodeId, status, createdBy, createdAt, lineCount }`.
|
||||
|
||||
#### `GET /stock-adjustments/{adjustmentId}` → the full adjustment incl. `lines` and `ledgerRefs`; `404` if absent.
|
||||
|
||||
#### `POST /stock-adjustments`
|
||||
```json
|
||||
{ "warehouseId": 1, "reasonCodeId": 4,
|
||||
@@ -550,6 +601,10 @@ Received `OnHold` → the layer is **not** available until released.
|
||||
`400 REASON_CODE_REQUIRED` if `reasonCodeId` omitted.
|
||||
|
||||
### 5.6 Counts
|
||||
#### `GET /stock-counts`
|
||||
Query: `q`, `status` (`Draft|Counted|Posted`), `warehouseId`, + paging. → list envelope of
|
||||
`{ countId, docNo, warehouseId, countType, status, createdBy, createdAt, lineCount }`.
|
||||
|
||||
#### `POST /stock-counts`
|
||||
```json
|
||||
{ "warehouseId": 1, "countType": "Cycle", "itemIds": [1001, 1002] }
|
||||
@@ -564,12 +619,13 @@ Received `OnHold` → the layer is **not** available until released.
|
||||
```json
|
||||
{ "lines": [ { "countLineId": 400, "countedQty": 4980 } ] }
|
||||
```
|
||||
**200 OK** → `{ "lines": [ { "countLineId": 400, "systemQty": 4985, "countedQty": 4980, "variance": -5 } ] }`
|
||||
**200 OK** → the whole `CountDto` (with server-computed `variance`), not just the lines.
|
||||
|
||||
#### `POST /stock-counts/{id}/post` → **200 OK** (posts variance adjustment, closes count)
|
||||
```json
|
||||
{ "countId": 30, "status": "Posted", "adjustmentId": 78, "ledgerRefs": [ 55060 ] }
|
||||
```
|
||||
`adjustmentId` is **null** when the count had no variance to post.
|
||||
|
||||
### 5.7 Reorder alerts
|
||||
#### `GET /stock/reorder-alerts?warehouseId=1`
|
||||
|
||||
Reference in New Issue
Block a user