- 覆盖范围表补一行分组入口,并注明已在 §1 展开(该表标题写的是「未展开」) - 「1. 物料接口」末尾补 GET /material-import/template 与 POST /material-import: 模板的两张工作表与 5 列定义、按列名解析的规则、逐行校验表、成功与失败的响应 示例、row 是 Excel 真实行号、5MB 与 20000 行限制、任意一行失败整批不入库、 导入记录只留汇总不落逐行明细 - 状态码旁列出真实的提示文案(含后端返回的原文),便于与响应逐字对照
1264 lines
43 KiB
Markdown
1264 lines
43 KiB
Markdown
# 仓库物料管理系统 API 文档
|
||
|
||
**基础地址**:`/api`
|
||
|
||
---
|
||
|
||
## 阅读说明
|
||
|
||
### 认证
|
||
|
||
除 `POST /auth/login` 外,所有接口都要求携带 JWT:
|
||
|
||
| 请求头 | 值 |
|
||
|----------------|-------------------|
|
||
| Authorization | `Bearer <token>` |
|
||
|
||
缺少或失效的令牌返回 `401 Unauthorized`。标为「仅管理员」的接口由 `AdminOnly` 中间件拦截,非管理员返回 `403 Forbidden`。有效角色在每次请求时从数据库重载,JWT 中的旧角色快照不授予当前权限。
|
||
|
||
### 公司上下文(2026-10-09)
|
||
|
||
组织级接口必须同时携带 `Authorization: Bearer <token>` 和 `X-Organization-ID: <正整数公司ID>`。适用范围为物料、分类、单位、编码规则、伙伴、仓库/库位、库存/预警/流水、入出库、申请/审批和物料日志。超级管理员也必须选择公司;没有跨公司汇总或默认公司兜底。
|
||
|
||
登录、当前用户、用户管理、组织管理、角色/权限管理、上传及下面的公司初始化/切换接口不要求组织头。公司授权只限制数据范围,功能角色权限仍单独校验。
|
||
|
||
| HTTP 状态 | 公司隔离语义 |
|
||
| --- | --- |
|
||
| 400 | 组织头缺失、不是纯数字正整数、请求参数非法,或引用另一公司的分类/单位/物料等 |
|
||
| 403 | 当前公司不存在、停用或未授权,统一提示“当前公司不可访问”;功能权限不足也返回 403 |
|
||
| 404 | 在已授权的公司上下文内查询、更新或删除另一公司的记录,或记录不存在 |
|
||
| 409 | 仓库/库位缺失或停用导致库存写操作不可执行,或已有业务状态冲突 |
|
||
|
||
新建记录的归属取服务端验证的上下文,忽略请求体伪造的 `organization_id`。普通编辑不能迁移公司。列表、分页总数、搜索、编码查重、日志和状态变更均限制当前公司;不同公司允许相同物料编码。
|
||
|
||
### 公司授权、初始化与切换
|
||
|
||
`GET /auth/organizations` 返回当前用户可访问的启用公司及其仓库摘要;普通用户按显式授权过滤,超级管理员得到所有启用公司:
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"organizations": [{
|
||
"id": 201, "code": "BEIJING", "name": "北京公司", "status": "active",
|
||
"warehouse": {"id": 201, "name": "北京仓库", "status": "active",
|
||
"location": {"id": 201, "name": "北京库位", "status": "active"}}
|
||
}],
|
||
"default_organization_id": 201
|
||
}
|
||
}
|
||
```
|
||
|
||
`warehouse` 或 `location` 未配置时为 `null`;普通用户可读取摘要,无需仓库维护权限。`default_organization_id` 可为 `null`,不能据此隐式使用 ID 1。
|
||
|
||
`POST /auth/organization-context` 请求 `{"organization_id":202,"previous_organization_id":201}`,返回 `data: {organization_id, organization, warehouse}`。服务端独立验证目标公司并写入切换审计;审计失败则切换失败。该调用不改变用户的默认公司,也不保存全局会话公司,请求仍须携带组织头。
|
||
|
||
`POST /users` 和 `PUT /users/:id` 支持 `organization_ids` 与 `default_organization_id`。普通用户的默认公司必须属于启用授权列表且唯一;更新时省略授权字段表示保留原设置,显式空授权列表配合 `null` 默认公司表示清空。授权变更与审计在同一事务中提交。超级管理员无需逐公司授权。
|
||
|
||
首次登录选择有效默认公司;没有有效默认但有授权公司时要求选择,没有授权时禁止组织级请求。前端以每账号 `sessionStorage` 保存标签页选择,刷新重新校验。切换清理列表、详情、筛选、分页、弹窗及缓存,并以公司 ID 和上下文版本屏蔽旧响应/错误;深链接回对应列表。脏表单先确认放弃,保存期间禁止切换,登出/换账号清空状态。
|
||
|
||
### 仓库、库存与单据事务
|
||
|
||
每个公司最多一个仓库,每个仓库最多一个库位,数据库唯一约束包括停用记录。页面只显示公司对应的仓库/库位摘要;服务端按公司解析库存维度,不信任客户端的仓库/库位归属。
|
||
|
||
| 配置状态 | 库存写操作提示(409) |
|
||
| --- | --- |
|
||
| 无仓库 | 请先配置本公司仓库 |
|
||
| 仓库停用 | 本公司仓库已停用 |
|
||
| 无库位 | 请先配置本公司仓库的库位 |
|
||
| 库位停用 | 本公司库位已停用 |
|
||
|
||
上述状态允许维护主数据、查询本公司历史库存/单据/流水,以及创建申请、驳回、取消等不扣库存操作。入出库创建/修改/删除、预警修改及自动出库审批要求启用维度,禁止借用另一公司的资源。所有启用且授权公司均可使用已有业务模块。
|
||
|
||
入出库表头及库存流水保存 `organization_id/warehouse_id/location_id`。修改/删除在事务内锁定当前公司原单据,先按原库存维度恢复原单据造成的库存变化,再处理新的本公司明细;失败整体回滚。库存余额锁定完整维度与物料 ID,条件扣减防止透支,多物料按 ID 排序锁定。流水结存只在本公司内计算,提交成功后才发布本公司事件。
|
||
|
||
### 申请与审批
|
||
|
||
`POST /applications` 在创建时绑定当前公司,明细只允许本公司有效物料;`GET /applications/my` 按公司和本人过滤,`GET /applications` 按公司及审批功能权限查询。审批、驳回和取消使用 `/applications/:id/approve`、`/reject`、`/cancel`;取消仍须本人,状态更新带公司、ID 和原状态条件。
|
||
|
||
审批请求支持 `{"auto_outbound":true}`。自动出库审批在同一事务中锁定本公司的 pending 申请,解析本公司启用仓库/库位,锁定并扣减库存,建立同公司出库单和流水,最后变更申请状态。重复审批不会重复出库;库存不足或中途故障回滚全部业务记录。非自动出库审批仍沿用现有库存检查规则。
|
||
|
||
### WebSocket 公司订阅
|
||
|
||
`POST /auth/websocket-ticket` 必须携带令牌及组织头,返回 `data: {ticket, expires_at}`(到期 Unix 秒)。随机票据一次性、30 秒有效,绑定用户、公司和当前会话。使用 `/ws?ticket=<票据>` 握手,不在 URL 放长期 JWT。
|
||
|
||
握手和每次投递重新检查有效用户、公司授权及会话;撤销授权/角色、停用公司或登出后,失效连接不再接收事件。库存事件含 `type: "stock_update"`、`organization_id`、`action`、`order_id`,仅投递同公司订阅者。前端切换/登出关闭旧连接,重新取票建连,消息同时校验公司与上下文版本。
|
||
|
||
数据检查、升级顺序、验证证据和回退限制见 [公司隔离验收与交付](company-master-data-isolation-acceptance.md)。
|
||
|
||
### 统一响应结构
|
||
|
||
所有接口共用同一个外层结构:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---------|---------|--------------------------------------------|
|
||
| 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` | 需登录 |
|
||
| 物料批量导入 | `GET /material-import/template`、`POST /material-import` | 需登录,见 §1 |
|
||
|
||
---
|
||
|
||
## 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` — 物料不存在
|
||
|
||
---
|
||
|
||
### GET /material-import/template
|
||
|
||
下载批量导入用的 xlsx 模板。模板随代码实时生成、不落盘,所以下载到的列定义永远与当前校验规则一致。
|
||
|
||
**请求:** 无参数。
|
||
|
||
**响应:** xlsx 文件流,`Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,文件名为 `物料导入模板.xlsx`。
|
||
|
||
模板含两张工作表:
|
||
|
||
| 工作表 | 内容 |
|
||
|--------|------|
|
||
| 填写说明 | 字段、是否必填、示例、填写规则 |
|
||
| 物料数据 | 第 1 行是表头,数据从第 2 行开始 |
|
||
|
||
`物料数据` 的表头是 5 列:`物料名称`、`规格`、`单位`、`大类`、`小类`。解析时**按列名定位,不按列位置**:
|
||
|
||
- 列顺序可以随意调整;表头允许带首尾空格和末尾的 `*` 必填标记;不认识的列忽略;
|
||
- 必填列 `物料名称`、`大类`、`小类` 缺任何一列,判为模板不合法并返回「模板格式不正确,请重新下载最新模板」;
|
||
- 选填列 `规格`、`单位` 可以整列删掉,取值按空处理。
|
||
|
||
分类**按名称**填写,后端按「大类名 + 小类名」在本公司内组合匹配(小类名在一个公司内会重名,例如多个大类下都有「其他」,所以必须两级组合)。单位按名称匹配,同时兼容按编码匹配(如 `kg` 与 `千克`)。
|
||
|
||
### POST /material-import
|
||
|
||
上传填好的 xlsx,**校验通过即在同一个请求内入库**——没有「预览确认」环节,也没有暂存任务 ID。
|
||
|
||
**请求:** `multipart/form-data`,文件字段名 `file`。仅支持 `.xlsx`,单文件不超过 5 MB,数据区不超过 20,000 行(不含表头)。
|
||
|
||
**逐行校验规则:**
|
||
|
||
| 列 | 规则 |
|
||
|----|------|
|
||
| 物料名称 | 必填,去首尾空格后非空,≤ 200 字 |
|
||
| 规格 | 选填,≤ 200 字 |
|
||
| 单位 | 选填;留空时默认「个」。非空时必须在当前公司已启用单位中按名称或编码匹配到 |
|
||
| 大类 | 必填,必须是当前公司已启用的顶层分类名称 |
|
||
| 小类 | 必填,必须是该大类下已启用的小类名称 |
|
||
| 编码规则 | 该小类必须存在启用的编码规则(`code_rules.status = 'active'`) |
|
||
|
||
物料编码由系统按小类编码规则生成,**不接受模板里的编码列**。同一行可能产生多条原因,用中文分号连接。
|
||
|
||
**成功响应 `201 Created`:**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "成功导入 200 条物料",
|
||
"data": { "total_rows": 200, "success_rows": 200, "failed_rows": 0, "errors": [] }
|
||
}
|
||
```
|
||
|
||
**校验失败响应 `400 Bad Request`**(HTTP 状态是 400,但 `data` 里带完整明细):
|
||
|
||
```json
|
||
{
|
||
"code": 400,
|
||
"message": "有 3 行校验未通过,本次未导入任何数据",
|
||
"data": {
|
||
"total_rows": 200, "success_rows": 0, "failed_rows": 3,
|
||
"errors": [
|
||
{ "row": 3, "data": { "name": "", "spec": "M8x30", "unit": "个", "major": "机械结构类", "minor": "标准件" }, "reason": "物料名称不能为空" }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
`row` 是 **Excel 里的真实行号**(从 1 开始、含表头行),用户可以直接对着 Excel 定位;`data` 是该行的原始内容。
|
||
|
||
**状态码:**
|
||
|
||
- `201 Created` — 全部行通过并入库
|
||
- `400 Bad Request` — 存在校验未通过的行(整批不入库);模板不合法(「模板格式不正确,请重新下载最新模板」)、行数超限(「导入行数超过 20000 行上限」)、未选文件(「请选择要导入的 xlsx 文件」)、文件超过 5 MB(「文件超过 5MB 上限」)
|
||
- `500 Internal Server Error` — 入库失败,事务已回滚,整批未落库
|
||
|
||
**行为说明:**
|
||
|
||
- **任意一行校验失败,整批不入库**,响应中返回全部逐行错误;重新上传前请先修正后重试。
|
||
- 整批在**一个事务**内完成:按小类锁编码规则 → 分配编码 → 分批插入物料(每批 500 行)→ 写审计日志;任一步失败整体回滚。
|
||
- 每次导入(无论成功或失败)都会写一条 `material_import_logs` 记录:操作人、公司、时间、文件名、总行数、成功数、失败数、状态、失败原因摘要(前 5 行行号)。**逐行错误明细只随响应返回,不落库。**
|
||
- 首版只支持**新增**物料:不做更新,也不自动创建分类或单位。
|
||
- **不做重复检测**:同一份文件重复上传会真的导入两遍(生成不同的编码)。
|
||
|
||
---
|
||
|
||
## 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 | 服务端错误 |
|