material_manage_system/docs/api.md

1038 lines
29 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` | 仅管理员 |
| 业务伙伴 | `/partners` | 仅管理员 |
| 仓库与库位 | `/warehouses`、`/warehouses/:id/locations` | 仅管理员 |
| 物料申请 | `/applications`、`/applications/my` | 需登录 |
| 操作日志 | `/logs`、`/logs/materials/:materialId` | 需登录 |
| 图片上传 | `POST /upload` | 需登录 |
---
## 1. 物料接口
物料是主数据,**不承载任何库存信息**。库存数量和预警阈值分别由库存接口和入库流程维护。
### GET /materials
分页查询物料列表。
**查询参数:**
| 参数 | 类型 | 必填 | 说明 |
|----------|---------|------|----------------------------|
| search | string | 否 | 按物料编码或名称模糊搜索 |
| category | string | 否 | **当前未生效**,服务端接收但未用于过滤 |
| limit | integer | 否 | 每页条数,默认 20,上限 100 |
| offset | integer | 否 | 偏移量,默认 0 |
> 该接口**不按物料状态过滤**,已作废的物料同样会出现在结果里,调用方需要自己判断 `status` 字段。
**响应**(`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` 字段。
**路径参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|---------|------|----------|
| id | integer | 是 | 物料 ID |
**请求体:** 无。
**响应:**
```json
{
"code": 0,
"message": "Material obsoleted successfully",
"data": null
}
```
**状态码:**
- `200 OK` — 作废成功
- `400 Bad Request` — 物料 ID 非法
- `404 Not Found` — 物料不存在
- `409 Conflict` — **仍有库存(在库、预留、锁定或在途数量不为 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 钢材",
"items": [
{ "material_id": 1, "quantity": 500, "warning_stock": 100 },
{ "material_id": 2, "quantity": 200 }
]
}
```
| 字段 | 类型 | 必填 | 说明 |
|----------|--------|------|--------------------|
| handler | string | 是 | 经手人 |
| supplier | string | 是 | 供应商 |
| items | array | 是 | 明细,至少 1 行 |
**明细字段:**
| 字段 | 类型 | 必填 | 说明 |
|---------------|---------|------|--------------------------------------------------------|
| material_id | integer | 是 | 物料 ID |
| quantity | integer | 是 | 入库数量,必须大于 0 |
| warning_stock | number | 否 | 为该物料设置的预警阈值;**不传表示不改动已存的阈值** |
> `warning_stock` 与库存更新在同一个事务里落库,保证物料的第一个阈值和第一行库存同时生效。编辑旧入库单时**不传**这个字段(而不是传 `0`),才不会把别处刚调整好的阈值覆盖回去。
>
> 本接口**不支持备注字段**。前端即便提交了 `remark`,服务端也会忽略。
**响应:** `201 Created`,`data` 为创建后的入库单,结构同 `GET /inbound/:id`。
**状态码:**
- `201 Created` — 创建成功
- `400 Bad Request` — 请求体非法、物料不存在,或 `warning_stock` 为负数
---
### PUT /inbound/:id
更新入库单。服务端会先冲销原明细的库存,再按新明细重新入库,两步在同一事务内完成。
**路径参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|---------|------|----------|
| id | integer | 是 | 入库单 ID |
**请求体:** 字段同 `POST /inbound`。`warning_stock` 同样遵循「不传即不改动」的语义。
**响应:** `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",
"handler": "李四",
"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": "装配车间",
"items": [
{ "material_id": 1, "quantity": 100 },
{ "material_id": 2, "quantity": 50 }
]
}
```
| 字段 | 类型 | 必填 | 说明 |
|----------|--------|------|------------------|
| handler | string | 是 | 经手人 |
| receiver | string | 是 | 领用人 |
| items | array | 是 | 明细,至少 1 行 |
**明细字段:**
| 字段 | 类型 | 必填 | 说明 |
|-------------|---------|------|----------------------|
| material_id | integer | 是 | 物料 ID |
| quantity | integer | 是 | 出库数量,必须大于 0 |
**响应:** `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 阈值 > 0
报警 = 参与预警 AND 现有库存 <= 阈值
```
展开到接口行为上:
- **库存查询(`GET /stock`)只列「入过库」的物料**。判断依据是存在库存记录行,而不是库存数量大于 0——所以**已被领用到 0 的物料仍然会列出**,而只存在于主数据、从未入库的物料不会出现。已作废的物料也不列出。
- **阈值没设过(为 0)的物料不会报警**,即使库存已经是 0。没有设定期望值,就不替使用者判断"该补货了"。
- **阈值被停用的物料不会报警**,阈值本身保留,随时可以重新启用。
| 是否入过库 | 现有库存 | 阈值 | 启用 | 状态 |
|------------|----------|------|------|----------|
| 否 | 0 | 0 | — | 不在列表中 |
| 是 | 0 | 0 | 默认 | 正常 |
| 是 | 5 | 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,
"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"` |
| warning_enabled | 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,
"stock_status": "normal"
}
}
```
**状态码:**
- `200 OK` — 成功
- `400 Bad Request` — 物料 ID 非法
- `404 Not Found` — 物料不存在
---
### GET /stock/warning
分页查询需要关注的物料:配置了启用状态且大于 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,
"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 的阈值才能触发预警 |
| 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` 区分),与 `GET /stock/logs` 的带符号约定不一致。这是当前实现的实际行为,使用前请留意。
**状态码:**
- `200 OK` — 成功
- `404 Not Found` — 物料不存在
---
## 状态码汇总
| 状态码 | 说明 |
|--------|--------------------------------------------------|
| 200 | 请求成功 |
| 201 | 资源创建成功 |
| 400 | 请求体或参数非法、物料不存在、库存不足 |
| 401 | 未登录或令牌失效 |
| 403 | 已登录但权限不足(如非管理员访问管理员接口) |
| 404 | 资源不存在 |
| 409 | 状态冲突,目前仅用于「物料仍有库存时作废」 |
| 500 | 服务端错误 |