43 KiB
仓库物料管理系统 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 返回当前用户可访问的启用公司及其仓库摘要;普通用户按显式授权过滤,超级管理员得到所有启用公司:
{
"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,仅投递同公司订阅者。前端切换/登出关闭旧连接,重新取票建连,消息同时校验公司与上下文版本。
数据检查、升级顺序、验证证据和回退限制见 公司隔离验收与交付。
统一响应结构
所有接口共用同一个外层结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| code | integer | 0 表示成功;失败时等于 HTTP 状态码 |
| message | string | 提示信息(后端硬编码,目前为英文) |
| data | object | 业务数据;无返回值的接口为 null |
分页结构
列表接口的 data 不是数组,而是一个分页对象:
{
"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,而不是报错。
本文档覆盖范围
本文档详述物料、入库、出库、库存四个资源域,物料批量导入(/material-import)已并入 §1。以下分组已实现但未在本文档展开,需要时请直接查阅 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 内元素):
{
"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
创建物料。
请求体:
{
"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 为创建后的物料对象。
{
"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 |
请求体:
{
"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_id404 Not Found— 物料不存在
POST /materials/:id/obsolete
作废物料。这是软删除:物料从库存视图(GET /stock、GET /stock/warning)中消失,但记录保留,可以恢复。
GET /materials默认仅返回有效物料;物料管理页应传status=all才会同时看到作废项并执行撤销。入库、出库和申领的服务端也会拒绝使用已作废物料。历史单据仍可读取作废物料的名称与编码。
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 物料 ID |
请求体: 无。
响应:
{
"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 |
请求体: 无。
响应:
{
"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:
{
"code": 0,
"message": "成功导入 200 条物料",
"data": { "total_rows": 200, "success_rows": 200, "failed_rows": 0, "errors": [] }
}
校验失败响应 400 Bad Request(HTTP 状态是 400,但 data 里带完整明细):
{
"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 内元素):
{
"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 |
响应:
{
"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
创建入库单。创建时库存立即增加,同一个事务内写入预警阈值。
请求体:
{
"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 |
响应:
{
"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 |
响应:
{
"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 内元素):
{
"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 |
响应:
{
"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
创建出库单。库存立即扣减,库存不足时整单失败。
请求体:
{
"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 |
响应:
{
"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 |
响应:
{
"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 内元素):
{
"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 |
响应:
{
"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 |
响应:
{
"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 内元素):
{
"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 |
请求体:
{
"warning_stock": 100,
"enabled": true
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| warning_stock | number | 是 | 阈值,必须大于等于 0。0 是合法值,表示"库存降到 0 时预警" |
| enabled | boolean | 否 | 默认 true;传 false 停用该物料的预警,阈值本身保留 |
响应:
{
"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 内元素):
{
"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 | 服务端错误 |