# 仓库物料管理系统 API 文档 **基础地址**:`/api` --- ## 阅读说明 ### 认证 除 `POST /auth/login` 外,所有接口都要求携带 JWT: | 请求头 | 值 | |----------------|-------------------| | Authorization | `Bearer ` | 缺少或失效的令牌返回 `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 | 服务端错误 |