1107 lines
33 KiB
Markdown
1107 lines
33 KiB
Markdown
# 仓库物料管理系统 API 文档
|
||
|
||
**基础地址**:`/api`
|
||
|
||
---
|
||
|
||
## 阅读说明
|
||
|
||
### 认证
|
||
|
||
除 `POST /auth/login` 外,所有接口都要求携带 JWT:
|
||
|
||
| 请求头 | 值 |
|
||
|----------------|-------------------|
|
||
| Authorization | `Bearer <token>` |
|
||
|
||
缺少或失效的令牌返回 `401 Unauthorized`。标为「仅管理员」的接口由 `AdminOnly` 中间件拦截,非管理员返回 `403 Forbidden`。
|
||
|
||
### 统一响应结构
|
||
|
||
所有接口共用同一个外层结构:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---------|---------|--------------------------------------------|
|
||
| code | integer | `0` 表示成功;失败时等于 HTTP 状态码 |
|
||
| message | string | 提示信息(后端硬编码,目前为英文) |
|
||
| data | object | 业务数据;无返回值的接口为 `null` |
|
||
|
||
### 分页结构
|
||
|
||
列表接口的 `data` **不是数组**,而是一个分页对象:
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 0,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|----------|---------|------------------------------------------|
|
||
| items | array | 当前页数据 |
|
||
| limit | integer | 每页条数,默认 20,**上限 100** |
|
||
| offset | integer | 偏移量,默认 0 |
|
||
| total | integer | 符合条件的总条数 |
|
||
| has_more | boolean | 是否还有下一页 |
|
||
|
||
`limit` 传超过 100 的值会被服务端截断为 100,而不是报错。
|
||
|
||
### 本文档覆盖范围
|
||
|
||
本文档详述**物料、入库、出库、库存**四个资源域。以下分组已实现但未在本文档展开,需要时请直接查阅 `internal/handler/api.go` 的路由注册:
|
||
|
||
| 分组 | 路径 | 权限 |
|
||
|-------------------------|---------------------------------------------|--------|
|
||
| 认证 | `POST /auth/login`、`GET /auth/me`、`POST /auth/logout` | 登录接口公开,其余需登录 |
|
||
| 用户 | `/users` | 仅管理员 |
|
||
| 组织 | `/organizations` | 仅管理员 |
|
||
| 角色 / 权限 | `/roles`、`/permissions` | 仅管理员 |
|
||
| 主数据:物料分类 | `/material-categories` | 仅管理员 |
|
||
| 主数据:计量单位 | `/units` | 仅管理员 |
|
||
| 主数据:编码规则 | `/code-rules`、`GET /code-rules/next` | 仅管理员;`/next` 需登录 |
|
||
| 业务伙伴 | `/partners` | 仅管理员 |
|
||
| 仓库与库位 | `/warehouses`、`/warehouses/:id/locations` | 仅管理员 |
|
||
| 物料申请 | `/applications`、`/applications/my` | 需登录 |
|
||
| 操作日志 | `/logs`、`/logs/materials/:materialId` | 需登录 |
|
||
| 图片上传 | `POST /upload` | 需登录 |
|
||
|
||
---
|
||
|
||
## 1. 物料接口
|
||
|
||
物料是主数据,**不承载任何库存信息**。库存数量和预警阈值分别由库存接口和入库流程维护。
|
||
|
||
### GET /materials
|
||
|
||
分页查询物料列表。
|
||
|
||
**查询参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|----------|---------|------|----------------------------|
|
||
| search | string | 否 | 按物料编码或名称模糊搜索 |
|
||
| status | string | 否 | `active`(默认)、`obsolete` 或 `all`;业务下拉应使用默认值,物料管理页使用 `all` |
|
||
| limit | integer | 否 | 每页条数,默认 20,上限 100 |
|
||
| offset | integer | 否 | 偏移量,默认 0 |
|
||
|
||
> `status` 默认 `active`,所以该接口默认只返回有效物料,已作废的物料需要显式传 `status=all` 或 `status=obsolete` 才能查到。业务单据的物料下拉正是靠这个默认值自然排除了作废物料;库存流水的检索框则要显式传 `status=all`,才能查到已作废物料的历史。
|
||
|
||
**响应**(`data` 为分页对象,此处只展示 `items` 内元素):
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"organization_id": 1,
|
||
"material_code": "A01-0001",
|
||
"name": "内六角螺栓",
|
||
"material_image": "/uploads/bolt.png",
|
||
"spec": "M8x30",
|
||
"manufacturer_model": "GB/T 70.1",
|
||
"category_id": 12,
|
||
"base_unit_id": 3,
|
||
"unit": "个",
|
||
"status": "active",
|
||
"version": 1,
|
||
"created_at": "2026-01-01T08:00:00Z",
|
||
"updated_at": "2026-01-01T08:00:00Z"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 1,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
`material_image`、`spec`、`manufacturer_model`、`category_id`、`base_unit_id`、`unit` 为空时**不会出现在响应里**(后端 `omitempty`),前端取值时需要做空值兜底。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `401 Unauthorized` — 未登录
|
||
|
||
---
|
||
|
||
### GET /materials/:id
|
||
|
||
查询单个物料。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|----------|
|
||
| id | integer | 是 | 物料 ID |
|
||
|
||
**响应:** `data` 为单个物料对象,字段同 `GET /materials`。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `400 Bad Request` — 物料 ID 非法
|
||
- `404 Not Found` — 物料不存在
|
||
|
||
---
|
||
|
||
### POST /materials
|
||
|
||
创建物料。
|
||
|
||
**请求体:**
|
||
|
||
```json
|
||
{
|
||
"material_code": "A01-0001",
|
||
"name": "内六角螺栓",
|
||
"spec": "M8x30",
|
||
"manufacturer_model": "GB/T 70.1",
|
||
"category_id": 12,
|
||
"base_unit_id": 3,
|
||
"unit": "个",
|
||
"material_image": "/uploads/bolt.png"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|-------------------|---------|------|--------------------------------------|
|
||
| material_code | string | 是 | 物料编码,组织内唯一 |
|
||
| name | string | 是 | 物料名称 |
|
||
| category_id | integer | 是 | 小类分类 ID,必须大于 0 且真实存在 |
|
||
| spec | string | 否 | 规格 |
|
||
| manufacturer_model| string | 否 | 厂家型号 |
|
||
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
||
| unit | string | 否 | 单位名称(冗余展示字段) |
|
||
| material_image | string | 否 | 图片地址 |
|
||
| organization_id | integer | 否 | 传入会被忽略,服务端取当前用户的组织 |
|
||
|
||
**响应:** `201 Created`,`data` 为创建后的物料对象。
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "Material created successfully",
|
||
"data": { "id": 1, "material_code": "A01-0001", "name": "内六角螺栓", "status": "active" }
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `201 Created` — 创建成功
|
||
- `400 Bad Request` — 请求体非法、物料编码已存在,或 `category_id` 不是有效的小类类型
|
||
|
||
---
|
||
|
||
### PUT /materials/:id
|
||
|
||
更新物料。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|----------|
|
||
| id | integer | 是 | 物料 ID |
|
||
|
||
**请求体:**
|
||
|
||
```json
|
||
{
|
||
"material_code": "A01-0001",
|
||
"name": "内六角螺栓(镀锌)",
|
||
"spec": "M8x35",
|
||
"category_id": 12,
|
||
"unit": "个"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|-------------------|---------|------|--------------------------|
|
||
| category_id | integer | **是** | 即使不修改分类也必须传 |
|
||
| material_code | string | 否 | 物料编码 |
|
||
| name | string | 否 | 物料名称 |
|
||
| spec | string | 否 | 规格 |
|
||
| manufacturer_model| string | 否 | 厂家型号 |
|
||
| base_unit_id | integer | 否 | 基本计量单位 ID |
|
||
| unit | string | 否 | 单位名称 |
|
||
| material_image | string | 否 | 图片地址 |
|
||
| status | string | 否 | 物料状态 |
|
||
|
||
> `category_id` 在更新接口里同样是必填项。只改名称而不传分类会返回 `400`。
|
||
|
||
**响应:** `200 OK`,`data` 为更新后的物料对象。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 更新成功
|
||
- `400 Bad Request` — 请求体非法或缺少 `category_id`
|
||
- `404 Not Found` — 物料不存在
|
||
|
||
---
|
||
|
||
### POST /materials/:id/obsolete
|
||
|
||
作废物料。这是**软删除**:物料从库存视图(`GET /stock`、`GET /stock/warning`)中消失,但记录保留,可以恢复。
|
||
|
||
> `GET /materials` 默认仅返回有效物料;物料管理页应传 `status=all` 才会同时看到作废项并执行撤销。入库、出库和申领的服务端也会拒绝使用已作废物料。历史单据仍可读取作废物料的名称与编码。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|----------|
|
||
| id | integer | 是 | 物料 ID |
|
||
|
||
**请求体:** 无。
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "Material obsoleted successfully",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 作废成功
|
||
- `400 Bad Request` — 物料 ID 非法
|
||
- `404 Not Found` — 物料不存在
|
||
- `409 Conflict` — **仍有库存(在库、预留、锁定或在途数量不为 0)**,需要先出库清零再作废
|
||
|
||
> 不再补货的物料走作废退役。作废要求库存为零,所以典型流程是:出库清零 → 作废。
|
||
>
|
||
> 注意区分「作废」和「关掉预警」:作废是物料退出库存视野,不再出现在任何库存接口里;关掉预警(`warning_enabled: false`)只是不再提醒,物料和阈值都还在。另外,把阈值设成 0 不等于停用预警——0 是"库存降到 0 时预警",仍然会报警。
|
||
|
||
---
|
||
|
||
### POST /materials/:id/restore
|
||
|
||
撤销售废,把物料恢复为可用状态。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|----------|
|
||
| id | integer | 是 | 物料 ID |
|
||
|
||
**请求体:** 无。
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "Material restored successfully",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 恢复成功
|
||
- `400 Bad Request` — 物料 ID 非法
|
||
- `404 Not Found` — 物料不存在
|
||
|
||
---
|
||
|
||
## 2. 入库接口
|
||
|
||
入库是物料**进入库存视野**的唯一入口:物料的第一行库存记录和第一个预警阈值都在这里产生。入库成功后,服务端会通过 WebSocket 广播库存变更。
|
||
|
||
### GET /inbound
|
||
|
||
分页查询入库单列表。
|
||
|
||
**查询参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|--------|---------|------|----------------------------|
|
||
| search | string | 否 | 按经手人或供应商模糊搜索 |
|
||
| limit | integer | 否 | 每页条数,默认 20,上限 100 |
|
||
| offset | integer | 否 | 偏移量,默认 0 |
|
||
|
||
**响应**(`data` 为分页对象,此处只展示 `items` 内元素):
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"inbound_date": "2026-01-01T08:00:00Z",
|
||
"handler": "张三",
|
||
"supplier": "ABC 钢材",
|
||
"status": "completed",
|
||
"created_at": "2026-01-01T08:00:00Z",
|
||
"updated_at": "2026-01-01T08:00:00Z"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 1,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
> 单据的 `status` 字段目前是服务端**硬编码的固定值 `"completed"`**(数据库里没有对应的状态列),所有入库单都返回同一个值,不能用于区分单据阶段。出库单同理。
|
||
|
||
**状态码:** `200 OK`、`401 Unauthorized`
|
||
|
||
---
|
||
|
||
### GET /inbound/:id
|
||
|
||
查询单个入库单,**包含明细行**。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|------------|
|
||
| id | integer | 是 | 入库单 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"inbound_date": "2026-01-01T08:00:00Z",
|
||
"handler": "张三",
|
||
"supplier": "ABC 钢材",
|
||
"status": "completed",
|
||
"created_at": "2026-01-01T08:00:00Z",
|
||
"updated_at": "2026-01-01T08:00:00Z",
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"inbound_order_id": 1,
|
||
"material_id": 1,
|
||
"material_code": "A01-0001",
|
||
"material_name": "内六角螺栓",
|
||
"quantity": 500
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `400 Bad Request` — 入库单 ID 非法
|
||
- `404 Not Found` — 入库单不存在
|
||
|
||
---
|
||
|
||
### POST /inbound
|
||
|
||
创建入库单。创建时库存立即增加,同一个事务内写入预警阈值。
|
||
|
||
**请求体:**
|
||
|
||
```json
|
||
{
|
||
"handler": "张三",
|
||
"supplier": "ABC 钢材",
|
||
"remark": "到货验收通过",
|
||
"items": [
|
||
{ "material_id": 1, "quantity": 500, "warning_stock": 100, "warning_enabled": true },
|
||
{ "material_id": 2, "quantity": 200, "warning_stock": 0, "warning_enabled": false },
|
||
{ "material_id": 3, "quantity": 80 }
|
||
]
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|----------|--------|------|--------------------|
|
||
| handler | string | 是 | 经手人 |
|
||
| supplier | string | 是 | 供应商 |
|
||
| remark | string | 否 | 备注 |
|
||
| items | array | 是 | 明细,至少 1 行 |
|
||
|
||
**明细字段:**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|-----------------|---------|------|------------------------------------------------------------|
|
||
| material_id | integer | 是 | 物料 ID |
|
||
| quantity | integer | 是 | 入库数量,必须大于 0 |
|
||
| warning_stock | number | 否 | 为该物料设置的预警阈值,`0` 表示"库存降到 0 时预警";**不传表示不改动已存的阈值** |
|
||
| warning_enabled | boolean | 否 | 预警是否启用;**不传默认启用**,传 `false` 关闭预警。仅在同时传了 `warning_stock` 时生效 |
|
||
|
||
> `warning_stock` 与库存更新在同一个事务里落库,保证物料的第一个阈值和第一行库存同时生效。编辑旧入库单时**不传**这两个字段(而不是传 `0` 或 `false`),才不会把别处刚调整好的配置覆盖回去。
|
||
>
|
||
> 关闭预警而想保留原阈值时,把该物料当前的阈值连同 `warning_enabled: false` 一起传回即可(前端表单正是这么做的)。
|
||
>
|
||
`inbound_no` 由服务端按 `IN-` 加 8 位订单 ID 自动生成;`operator` 为创建该单据的当前登录用户。二者均由服务端维护,不能由请求体指定。
|
||
|
||
**响应:** `201 Created`,`data` 为创建后的入库单,结构同 `GET /inbound/:id`。
|
||
|
||
**状态码:**
|
||
|
||
- `201 Created` — 创建成功
|
||
- `400 Bad Request` — 请求体非法、物料不存在,或 `warning_stock` 为负数
|
||
|
||
---
|
||
|
||
### PUT /inbound/:id
|
||
|
||
更新入库单。服务端会先冲销原明细的库存,再按新明细重新入库,两步在同一事务内完成。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|----------|
|
||
| id | integer | 是 | 入库单 ID |
|
||
|
||
**请求体:** 字段同 `POST /inbound`。`warning_stock` 和 `warning_enabled` 同样遵循「不传即不改动」的语义。
|
||
|
||
**响应:** `200 OK`,`data` 为更新后的入库单。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 更新成功
|
||
- `400 Bad Request` — 请求体非法、物料不存在,或 `warning_stock` 为负数
|
||
- `500 Internal Server Error` — 入库单不存在等错误
|
||
|
||
---
|
||
|
||
### DELETE /inbound/:id
|
||
|
||
删除入库单。同时**冲销该单带来的库存增加**并删除对应的库存流水。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|----------|
|
||
| id | integer | 是 | 入库单 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "Inbound order deleted successfully",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 删除成功
|
||
- `400 Bad Request` — 入库单 ID 非法
|
||
- `500 Internal Server Error` — 入库单不存在等错误
|
||
|
||
---
|
||
|
||
### GET /inbound/materials/:materialId
|
||
|
||
查询某个物料的入库记录。**该接口不分页**,`data` 直接是数组。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------------|---------|------|----------|
|
||
| materialId | integer | 是 | 物料 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"inbound_id": 1,
|
||
"handler": "张三",
|
||
"supplier": "ABC 钢材",
|
||
"quantity": 500,
|
||
"created_at": "2026-01-01T08:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**状态码:** `200 OK`、`400 Bad Request`
|
||
|
||
---
|
||
|
||
## 3. 出库接口
|
||
|
||
出库会校验可用库存,**库存不足时整单失败**。出库成功后同样会广播 WebSocket 库存变更。
|
||
|
||
### GET /outbound
|
||
|
||
分页查询出库单列表。
|
||
|
||
**查询参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|--------|---------|------|----------------------------|
|
||
| search | string | 否 | 按经手人或领用人模糊搜索 |
|
||
| limit | integer | 否 | 每页条数,默认 20,上限 100 |
|
||
| offset | integer | 否 | 偏移量,默认 0 |
|
||
|
||
**响应**(`data` 为分页对象,此处只展示 `items` 内元素):
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"outbound_date": "2026-01-01T08:00:00Z",
|
||
"receiver": "装配车间",
|
||
"status": "completed",
|
||
"created_at": "2026-01-01T08:00:00Z",
|
||
"updated_at": "2026-01-01T08:00:00Z"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 1,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
**状态码:** `200 OK`、`401 Unauthorized`
|
||
|
||
---
|
||
|
||
### GET /outbound/:id
|
||
|
||
查询单个出库单,**包含明细行**。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|------------|
|
||
| id | integer | 是 | 出库单 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"id": 1,
|
||
"outbound_date": "2026-01-01T08:00:00Z",
|
||
"handler": "李四",
|
||
"receiver": "装配车间",
|
||
"status": "completed",
|
||
"created_at": "2026-01-01T08:00:00Z",
|
||
"updated_at": "2026-01-01T08:00:00Z",
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"outbound_order_id": 1,
|
||
"material_id": 1,
|
||
"material_code": "A01-0001",
|
||
"material_name": "内六角螺栓",
|
||
"quantity": 100
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `400 Bad Request` — 出库单 ID 非法
|
||
- `404 Not Found` — 出库单不存在
|
||
|
||
---
|
||
|
||
### POST /outbound
|
||
|
||
创建出库单。库存立即扣减,库存不足时整单失败。
|
||
|
||
**请求体:**
|
||
|
||
```json
|
||
{
|
||
"handler": "李四",
|
||
"receiver": "装配车间",
|
||
"remark": "急用",
|
||
"items": [
|
||
{ "material_id": 1, "quantity": 100 },
|
||
{ "material_id": 2, "quantity": 50 }
|
||
]
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|----------|--------|------|------------------|
|
||
| receiver | string | 是 | 领用人 |
|
||
| remark | string | 否 | 备注 |
|
||
| items | array | 是 | 明细,至少 1 行 |
|
||
|
||
**明细字段:**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|-------------|---------|------|----------------------|
|
||
| material_id | integer | 是 | 物料 ID |
|
||
| quantity | integer | 是 | 出库数量,必须大于 0 |
|
||
|
||
`outbound_no` 由服务端按 `OUT-` 加 8 位订单 ID 自动生成;`operator` 与 `handler` 均由服务端设置为当前登录用户名,不能由请求体指定。
|
||
|
||
**响应:** `201 Created`,`data` 为创建后的出库单,结构同 `GET /outbound/:id`。
|
||
|
||
**状态码:**
|
||
|
||
- `201 Created` — 创建成功
|
||
- `400 Bad Request` — 请求体非法、物料不存在,或**库存不足**
|
||
|
||
---
|
||
|
||
### PUT /outbound/:id
|
||
|
||
更新出库单。服务端先冲销原明细的库存扣减,再按新明细重新扣减,两步在同一事务内完成。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|------------|
|
||
| id | integer | 是 | 出库单 ID |
|
||
|
||
**请求体:** 字段同 `POST /outbound`。
|
||
|
||
**响应:** `200 OK`,`data` 为更新后的出库单。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 更新成功
|
||
- `400 Bad Request` — 请求体非法、物料不存在,或库存不足
|
||
- `500 Internal Server Error` — 出库单不存在等错误
|
||
|
||
---
|
||
|
||
### DELETE /outbound/:id
|
||
|
||
删除出库单。同时**把已扣减的库存加回来**并删除对应的库存流水。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|---------|------|------------|
|
||
| id | integer | 是 | 出库单 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "Outbound order deleted successfully",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 删除成功
|
||
- `400 Bad Request` — 出库单 ID 非法
|
||
- `500 Internal Server Error` — 出库单不存在等错误
|
||
|
||
---
|
||
|
||
### GET /outbound/materials/:materialId
|
||
|
||
查询某个物料的出库记录。**该接口不分页**,`data` 直接是数组。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------------|---------|------|----------|
|
||
| materialId | integer | 是 | 物料 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"outbound_id": 1,
|
||
"handler": "李四",
|
||
"receiver": "装配车间",
|
||
"quantity": 100,
|
||
"created_at": "2026-01-01T08:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**状态码:** `200 OK`、`400 Bad Request`
|
||
|
||
---
|
||
|
||
## 4. 库存接口
|
||
|
||
### 库存状态的判定规则
|
||
|
||
整套预警逻辑只有一句话:
|
||
|
||
```
|
||
参与预警 = 启用
|
||
报警 = 参与预警 AND 现有库存 <= 阈值
|
||
```
|
||
|
||
展开到接口行为上:
|
||
|
||
- **库存查询(`GET /stock`)只列「入过库」的物料**。判断依据是存在库存记录行,而不是库存数量大于 0——所以**已被领用到 0 的物料仍然会列出**,而只存在于主数据、从未入库的物料不会出现。已作废的物料也不列出。
|
||
- **阈值为 0 是合法配置**,含义是"库存降到 0 时预警"。
|
||
- **「不设置预警」等同于「不启用预警」**,两者是同一个状态。从没配过规则和手动关闭预警的物料,接口统一返回 `warning_enabled: false`,都不报警。需要区分这两者做表单预填时看 `warning_configured`(它只表示"配没配过规则",不参与判定)。
|
||
- **关闭预警时阈值本身保留**,随时可以重新启用。
|
||
|
||
| 是否入过库 | 是否配过规则 | 现有库存 | 阈值 | 启用 | 状态 |
|
||
|------------|--------------|----------|------|------|----------|
|
||
| 否 | — | 0 | — | — | 不在列表中 |
|
||
| 是 | 否 | 0 | — | false | 正常 |
|
||
| 是 | 是 | 5 | 0 | 是 | 正常 |
|
||
| 是 | 是 | 0 | 0 | 是 | 库存不足 |
|
||
| 是 | 是 | 0 | 10 | 是 | 库存不足 |
|
||
| 是 | 是 | 10 | 10 | 是 | 库存不足 |
|
||
| 是 | 是 | 15 | 10 | 是 | 正常 |
|
||
| 是 | 是 | 0 | 10 | 否 | 正常 |
|
||
|
||
### GET /stock
|
||
|
||
分页查询库存,只包含入过库且未作废的物料。
|
||
|
||
**查询参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|--------|---------|------|----------------------------|
|
||
| search | string | 否 | 按物料编码或名称模糊搜索 |
|
||
| limit | integer | 否 | 每页条数,默认 20,上限 100 |
|
||
| offset | integer | 否 | 偏移量,默认 0 |
|
||
|
||
**响应**(`data` 为分页对象,此处只展示 `items` 内元素):
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"material_id": 1,
|
||
"material_code": "A01-0001",
|
||
"name": "内六角螺栓",
|
||
"spec": "M8x30",
|
||
"unit": "个",
|
||
"major_category": "A01",
|
||
"minor_category": "A01-01",
|
||
"total_on_hand": "500",
|
||
"warning_stock": "100",
|
||
"warning_enabled": true,
|
||
"warning_configured": true,
|
||
"stock_status": "normal"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 1,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|--------------------|---------|------------------------------------------------------------|
|
||
| major_category | string | 大类分类编码 |
|
||
| minor_category | string | 小类分类编码 |
|
||
| total_on_hand | string | 现有库存合计,以字符串返回以免精度丢失 |
|
||
| warning_stock | string | 预警阈值,`"0"` 表示"库存降到 0 时预警" |
|
||
| warning_enabled | boolean | 预警是否启用。没配过规则和手动关闭都是 `false` |
|
||
| warning_configured | boolean | 是否配过预警规则。仅供表单预填区分"从没配过"和"已关闭",不参与判定 |
|
||
| stock_status | string | `low`(库存不足)或 `normal`(正常) |
|
||
|
||
**状态码:** `200 OK`、`401 Unauthorized`
|
||
|
||
---
|
||
|
||
### GET /stock/:materialId
|
||
|
||
查询单个物料的库存。与 `GET /stock` 不同,**这个接口对任何物料都会给出答案**,包括从未入库的物料(返回 `total_on_hand` 为 0)。物料申请和出库表单依赖这个特性逐个查询物料。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------------|---------|------|----------|
|
||
| materialId | integer | 是 | 物料 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"material_id": 1,
|
||
"material_code": "A01-0001",
|
||
"name": "内六角螺栓",
|
||
"spec": "M8x30",
|
||
"unit": "个",
|
||
"major_category": "A01",
|
||
"minor_category": "A01-01",
|
||
"total_on_hand": "500",
|
||
"warning_stock": "100",
|
||
"warning_enabled": true,
|
||
"warning_configured": true,
|
||
"stock_status": "normal"
|
||
}
|
||
}
|
||
```
|
||
|
||
> `PUT /stock/:materialId/warning` 的入库表单预填依赖 `warning_configured`:没配过规则的物料要按"默认启用 + 阈值 0"预填,配过但被关闭的物料必须保持关闭,否则入库会把别人刚关掉的预警悄悄重新打开。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `400 Bad Request` — 物料 ID 非法
|
||
- `404 Not Found` — 物料不存在
|
||
|
||
---
|
||
|
||
### GET /stock/:materialId/details
|
||
|
||
查询某个物料在各库存维度(组织 / 仓库 / 库位)上的明细。**该接口不分页**,`data` 直接是数组;物料存在但从没有过库存记录时返回空数组。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------------|---------|------|----------|
|
||
| materialId | integer | 是 | 物料 ID |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"organization_id": 1,
|
||
"organization": "默认组织",
|
||
"warehouse_id": 1,
|
||
"warehouse": "默认仓库",
|
||
"location_id": 1,
|
||
"location": "主库位",
|
||
"material_id": 1,
|
||
"on_hand": "500",
|
||
"reserved": "0",
|
||
"locked": "0",
|
||
"in_transit": "0",
|
||
"updated_at": "2026-01-01T08:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------------|---------|------------------------|
|
||
| on_hand | string | 现有库存 |
|
||
| reserved | string | 预留数量 |
|
||
| locked | string | 锁定数量 |
|
||
| in_transit | string | 在途数量 |
|
||
|
||
> `reserved`、`locked`、`in_transit` 三个维度当前没有任何业务写入,恒为 `0`;只有 `on_hand` 会随出入库变动。作废校验会把这四项一起纳入判断。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `400 Bad Request` — 物料 ID 非法
|
||
- `404 Not Found` — 物料不存在
|
||
|
||
---
|
||
|
||
### GET /stock/warning
|
||
|
||
分页查询需要关注的物料:预警处于启用状态,并且现有库存已触达阈值。阈值为 0 的物料在库存降到 0 时也会出现在这里。
|
||
|
||
**查询参数:** `limit`(默认 20,上限 100)、`offset`(默认 0)。
|
||
|
||
**响应**(`data` 为分页对象,此处只展示 `items` 内元素):
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"material_id": 2,
|
||
"material_code": "A02-0003",
|
||
"name": "铝棒",
|
||
"spec": "20mm x 3000mm",
|
||
"unit": "根",
|
||
"major_category": "A02",
|
||
"minor_category": "A02-01",
|
||
"total_on_hand": "30",
|
||
"warning_stock": "100",
|
||
"warning_enabled": true,
|
||
"warning_configured": true,
|
||
"stock_status": "low"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 1,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
**状态码:** `200 OK`、`401 Unauthorized`
|
||
|
||
---
|
||
|
||
### PUT /stock/:materialId/warning
|
||
|
||
设置、调整或停用某个物料的预警阈值。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------------|---------|------|----------|
|
||
| materialId | integer | 是 | 物料 ID |
|
||
|
||
**请求体:**
|
||
|
||
```json
|
||
{
|
||
"warning_stock": 100,
|
||
"enabled": true
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---------------|---------|------|------------------------------------------------------------|
|
||
| warning_stock | number | 是 | 阈值,必须大于等于 0。`0` 是合法值,表示"库存降到 0 时预警" |
|
||
| enabled | boolean | 否 | 默认 `true`;传 `false` 停用该物料的预警,阈值本身保留 |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "Stock warning updated successfully",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `400 Bad Request` — 物料 ID 非法,或 `warning_stock` 为负数
|
||
|
||
---
|
||
|
||
### GET /stock/logs
|
||
|
||
分页查询全部库存流水,按时间倒序。
|
||
|
||
**查询参数:** `limit`(默认 20,上限 100)、`offset`(默认 0)。
|
||
|
||
**响应**(`data` 为分页对象,此处只展示 `items` 内元素):
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [
|
||
{
|
||
"id": 2,
|
||
"material_id": 1,
|
||
"change_type": "outbound",
|
||
"change_quantity": "-100",
|
||
"before_quantity": "500",
|
||
"after_quantity": "400",
|
||
"reference_id": 1,
|
||
"reference_type": "outbound",
|
||
"created_at": "2026-01-01T10:00:00Z"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0,
|
||
"total": 1,
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|-----------------|--------|------------------------------------------------|
|
||
| change_type | string | `inbound`(入库)或 `outbound`(出库) |
|
||
| change_quantity | string | **带符号**的变动数量,出库为负 |
|
||
| before_quantity | string | 变动前库存 |
|
||
| after_quantity | string | 变动后库存 |
|
||
| reference_type | string | 来源单据类型,如 `inbound`、`outbound` |
|
||
| reference_id | integer| 来源单据 ID |
|
||
|
||
**状态码:** `200 OK`、`401 Unauthorized`
|
||
|
||
---
|
||
|
||
### GET /stock/logs/:materialId
|
||
|
||
分页查询某个物料的库存流水。
|
||
|
||
**路径参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------------|---------|------|----------|
|
||
| materialId | integer | 是 | 物料 ID |
|
||
|
||
**查询参数:** `limit`(默认 20,上限 100)、`offset`(默认 0)。
|
||
|
||
**响应**:结构与 `GET /stock/logs` 相同。`change_quantity` 采用带符号约定:入库为正数、出库为负数;`change_type` 仍用于展示入库/出库文字、样式和筛选。
|
||
|
||
**状态码:**
|
||
|
||
- `200 OK` — 成功
|
||
- `404 Not Found` — 物料不存在
|
||
|
||
---
|
||
|
||
## 状态码汇总
|
||
|
||
| 状态码 | 说明 |
|
||
|--------|--------------------------------------------------|
|
||
| 200 | 请求成功 |
|
||
| 201 | 资源创建成功 |
|
||
| 400 | 请求体或参数非法、物料不存在、库存不足 |
|
||
| 401 | 未登录或令牌失效 |
|
||
| 403 | 已登录但权限不足(如非管理员访问管理员接口) |
|
||
| 404 | 资源不存在 |
|
||
| 409 | 状态冲突,目前仅用于「物料仍有库存时作废」 |
|
||
| 500 | 服务端错误 |
|