material_manage_system/docs/api.md

1107 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 仓库物料管理系统 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 | 服务端错误 |