# Tham chiếu: Kho

Kho, tồn kho và phiếu nhập, xuất, kiểm kho. Mọi đường dẫn dưới `https://danix.vn/api/open/v1`. Đặc tả máy đọc: [openapi.json](/openapi.json).

## Danh sách kho {#listWarehouses}

`GET /warehouses`

Các kho hàng của shop.

**Quyền cần có:** khoá có quyền `pos.inventory.read`.

**Ví dụ yêu cầu**

```bash
curl -X GET "https://danix.vn/api/open/v1/warehouses" \
  -H "Authorization: Bearer dnx_live_…"
```

**Phản hồi mẫu (200)**

```json
{
  "data": [
    {
      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
      "code": "KHO1",
      "name": "Kho chính",
      "phone": null,
      "addressLine": null,
      "provinceName": null,
      "districtName": null,
      "wardName": null,
      "isDefault": true
    }
  ]
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `data` | array<object> | có | Các kho. |
| `data[].id` | string (uuid) | có | Mã kho. |
| `data[].code` | string | có | Mã kho. |
| `data[].name` | string | có | Tên kho. |
| `data[].phone` | string hoặc null | có | Số điện thoại kho. |
| `data[].addressLine` | string hoặc null | có | Địa chỉ kho. |
| `data[].provinceName` | string hoặc null | có | Tỉnh hoặc thành phố. |
| `data[].districtName` | string hoặc null | có | Quận hoặc huyện. |
| `data[].wardName` | string hoặc null | có | Phường hoặc xã. |
| `data[].isDefault` | boolean | có | Kho mặc định của shop. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Tồn kho theo mẫu mã và kho {#listStock}

`GET /inventory/stock`

Mỗi dòng là tồn của một mẫu mã tại một kho. Không có giá vốn. Đồng bộ tăng dần bằng `updatedSince` (lật trang trong một lượt bằng `nextCursor`): có `updatedSince` thì chỉ trả dòng tồn đổi từ mốc ấy và đã qua khoảng trễ an toàn (bằng trần thời gian tua lại kho của shop cộng 15 giây, mặc định 2 phút 15 giây). Mốc của dòng tồn chỉ đổi khi số tồn hoặc `inTransit` đổi; giá, mã, mã vạch, tên và ảnh đi kèm lấy từ đồng bộ sản phẩm và danh sách kho.

**Quyền cần có:** khoá có quyền `pos.inventory.read` và quyền `pos.products.read`.

**Tham số query**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `limit` | integer | không | Số dòng mỗi trang, từ 1 đến 100. Mặc định 50. |
| `cursor` | string | không | Con trỏ lấy từ `nextCursor` của trang trước, để lật trang trong CÙNG một lượt đọc (giữ nguyên các tham số khác). Không tự dựng, không lưu để nối lượt đồng bộ sau. |
| `updatedSince` | string (date-time) | không | Chỉ lấy bản ghi có `updatedAt` từ thời điểm này (ISO 8601 UTC), tính cả mốc. Đây là cách đồng bộ tăng dần: có tham số này thì danh sách chỉ trả bản ghi đã qua khoảng trễ an toàn (trần thời gian tua lại kho của shop cộng 15 giây, mặc định 2 phút 15 giây). |
| `warehouseId` | string (uuid) | không | Chỉ lấy tồn của kho này. |
| `search` | string | không | Tìm theo tên hoặc mã mẫu mã. |

**Ví dụ yêu cầu**

```bash
curl -X GET "https://danix.vn/api/open/v1/inventory/stock" \
  -H "Authorization: Bearer dnx_live_…"
```

**Phản hồi mẫu (200)**

```json
{
  "data": [
    {
      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
      "productId": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
      "productName": "Áo thun cổ tròn",
      "variantName": "Áo thun - M",
      "code": "AO-THUN-M",
      "barcode": null,
      "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
      "warehouseName": "Kho chính",
      "quantity": "18",
      "inTransit": "0",
      "price": "225000"
    }
  ],
  "nextCursor": null
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `data` | array<object> | có | Các bản ghi của trang này. |
| `data[].variantId` | string (uuid) | có | Mã mẫu mã. |
| `data[].productId` | string (uuid) | có | Mã sản phẩm. |
| `data[].productName` | string | có | Tên sản phẩm. |
| `data[].variantName` | string | có | Tên mẫu mã. |
| `data[].code` | string | có | Mã mẫu mã (SKU). |
| `data[].barcode` | string hoặc null | có | Mã vạch. |
| `data[].warehouseId` | string (uuid) | có | Mã kho. |
| `data[].warehouseName` | string | có | Tên kho. |
| `data[].quantity` | string | có | Tồn thực trong kho. Chuỗi thập phân, tối đa ba chữ số lẻ. |
| `data[].inTransit` | string | có | Số lượng đang trên đường về kho. Chuỗi thập phân, tối đa ba chữ số lẻ. |
| `data[].price` | string | có | Giá bán. Chuỗi thập phân, ví dụ "150000". |
| `nextCursor` | string hoặc null | có | Con trỏ của trang kế; `null` khi đã hết dữ liệu. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 400 | [`invalid-cursor`](/developers/errors#invalid-cursor) | Con trỏ phân trang không hợp lệ |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Tạo phiếu kho nháp {#createInventoryDocument}

`POST /inventory/documents`

Tạo phiếu nhập, xuất hoặc kiểm kho ở trạng thái `draft`. Tồn kho chỉ đổi khi ghi sổ bằng `POST /inventory/documents/{id}/post`.

**Quyền cần có:** khoá có ít nhất một trong các quyền `pos.inventory.receipt`, `pos.inventory.adjust` và đủ các quyền `pos.inventory.read`, `pos.products.read`.

**Thân yêu cầu (JSON)**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `kind` | "receipt" \| "issue" \| "stocktake" | có | Loại phiếu: nhập, xuất hoặc kiểm kho. |
| `warehouseId` | string (uuid) | có | Kho của phiếu. |
| `supplierId` | string (uuid) hoặc null | không | Nhà cung cấp (phiếu nhập). |
| `documentDate` | string | không | Ngày của phiếu, dạng YYYY-MM-DD. |
| `note` | string hoặc null | không | Ghi chú. |
| `lines` | array<object> | không | Các dòng của phiếu, tối đa 500. |
| `lines[].variantId` | string (uuid) | có | Mã mẫu mã. |
| `lines[].quantity` | string | không | Số lượng nhập hoặc xuất. |
| `lines[].countedQuantity` | string | không | Số đếm thực tế (phiếu kiểm kho). |
| `lines[].unitCost` | string | không | Giá nhập (phiếu nhập). |
| `lines[].note` | string hoặc null | không | Ghi chú dòng. |

**Ví dụ yêu cầu**

```bash
curl -X POST "https://danix.vn/api/open/v1/inventory/documents" \
  -H "Authorization: Bearer dnx_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "kind": "receipt",
  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
  "lines": [
    {
      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
      "quantity": "10",
      "unitCost": "120000"
    }
  ]
}'
```

**Phản hồi mẫu (201)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
  "kind": "receipt",
  "code": "PN0001",
  "status": "draft",
  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
  "supplierId": null,
  "occurredAt": "2026-10-02T03:15:00.000Z",
  "postedAt": null,
  "costingMethod": null,
  "note": null,
  "lines": []
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã phiếu. |
| `kind` | string | có | Loại phiếu: `receipt`, `issue`, `stocktake`… |
| `code` | string | có | Mã phiếu. |
| `status` | "draft" \| "ordered" \| "posted" \| "in_transit" \| "completed" \| "cancelled" | có | Trạng thái phiếu: `draft` là nháp, `posted` đã ghi sổ. |
| `warehouseId` | string (uuid) | có | Kho của phiếu. |
| `supplierId` | string (uuid) hoặc null | có | Nhà cung cấp. |
| `occurredAt` | string (date-time) | có | Ngày của phiếu. ISO 8601, múi giờ UTC. |
| `postedAt` | string (date-time) hoặc null | có | Thời điểm ghi sổ. ISO 8601 UTC hoặc `null`. |
| `costingMethod` | "average" \| "fifo" hoặc null | có | Phương pháp tính giá vốn khi ghi sổ. |
| `note` | string hoặc null | có | Ghi chú phiếu. |
| `lines` | array<object> | có | Các dòng của phiếu. |
| `lines[].id` | string (uuid) | có | Mã dòng phiếu. |
| `lines[].variantId` | string (uuid) | có | Mã mẫu mã. |
| `lines[].variantCode` | string | có | Mã mẫu mã. |
| `lines[].quantity` | string hoặc null | có | Số lượng nhập hoặc xuất. Chuỗi thập phân. |
| `lines[].countedQuantity` | string hoặc null | có | Số đếm thực tế (phiếu kiểm kho). |
| `lines[].unitCost` | string hoặc null | không | Giá nhập. Chỉ có khi khoá được xem giá vốn. |
| `lines[].note` | string hoặc null | có | Ghi chú dòng. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 422 | [`variant-removed`](/developers/errors#variant-removed) | Mẫu mã đã bị gỡ khỏi sản phẩm |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Ghi sổ phiếu kho {#postInventoryDocument}

`POST /inventory/documents/{id}/post`

Ghi sổ phiếu nháp: tồn kho đổi ngay. Ghi sổ lần hai trả `document-already-posted`. Quyền theo LOẠI phiếu: phiếu kiểm kho cần `pos.inventory.adjust`, phiếu nhập hay xuất cần `pos.inventory.receipt` — có quyền còn lại thôi thì `insufficient-permission`.

**Quyền cần có:** khoá có ít nhất một trong các quyền `pos.inventory.receipt`, `pos.inventory.adjust` và đủ các quyền `pos.inventory.read`, `pos.products.read`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |

**Ví dụ yêu cầu**

```bash
curl -X POST "https://danix.vn/api/open/v1/inventory/documents/{id}/post" \
  -H "Authorization: Bearer dnx_live_…"
```

Thay `{id}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (200)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
  "kind": "receipt",
  "code": "PN0001",
  "status": "posted",
  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
  "supplierId": null,
  "occurredAt": "2026-10-02T03:15:00.000Z",
  "postedAt": "2026-10-02T03:16:00.000Z",
  "costingMethod": "average",
  "note": null,
  "lines": []
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã phiếu. |
| `kind` | string | có | Loại phiếu: `receipt`, `issue`, `stocktake`… |
| `code` | string | có | Mã phiếu. |
| `status` | "draft" \| "ordered" \| "posted" \| "in_transit" \| "completed" \| "cancelled" | có | Trạng thái phiếu: `draft` là nháp, `posted` đã ghi sổ. |
| `warehouseId` | string (uuid) | có | Kho của phiếu. |
| `supplierId` | string (uuid) hoặc null | có | Nhà cung cấp. |
| `occurredAt` | string (date-time) | có | Ngày của phiếu. ISO 8601, múi giờ UTC. |
| `postedAt` | string (date-time) hoặc null | có | Thời điểm ghi sổ. ISO 8601 UTC hoặc `null`. |
| `costingMethod` | "average" \| "fifo" hoặc null | có | Phương pháp tính giá vốn khi ghi sổ. |
| `note` | string hoặc null | có | Ghi chú phiếu. |
| `lines` | array<object> | có | Các dòng của phiếu. |
| `lines[].id` | string (uuid) | có | Mã dòng phiếu. |
| `lines[].variantId` | string (uuid) | có | Mã mẫu mã. |
| `lines[].variantCode` | string | có | Mã mẫu mã. |
| `lines[].quantity` | string hoặc null | có | Số lượng nhập hoặc xuất. Chuỗi thập phân. |
| `lines[].countedQuantity` | string hoặc null | có | Số đếm thực tế (phiếu kiểm kho). |
| `lines[].unitCost` | string hoặc null | không | Giá nhập. Chỉ có khi khoá được xem giá vốn. |
| `lines[].note` | string hoặc null | có | Ghi chú dòng. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |
| 409 | [`conflict`](/developers/errors#conflict) | Thao tác xung đột với trạng thái hiện tại |
| 409 | [`document-already-posted`](/developers/errors#document-already-posted) | Phiếu kho đã ghi sổ |
| 422 | [`insufficient-stock`](/developers/errors#insufficient-stock) | Không đủ tồn kho cho thao tác này |
| 422 | [`variant-removed`](/developers/errors#variant-removed) | Mẫu mã đã bị gỡ khỏi sản phẩm |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).
