核心 API 与工作流
本页聚焦“对象 CRUD”和“跨步骤工作流”之间的分工。前者负责稳定的资源读写,后者负责审批、入库、完成等状态推进。理解这层分界后,新增接口时更容易判断应该放在哪个文件里。
路由分层
后端并不是把所有业务塞进一个大路由文件,而是按职责拆分:
- app/api/users.py 负责登录、用户 CRUD、头像与密码。
- app/api/user_sessions.py 负责设备与会话。
- app/api/inventory.py 负责库存核心列表与详情,再通过扩展注册挂载库存补充能力。
- app/api/reagent_orders.py 负责试剂订单的创建、列表与编辑。
- app/api/reagent_orders_workflow.py 负责审批、到货、入库和删除。
- app/api/consumable_orders.py 同时承担耗材订单 CRUD 与状态流转。
- app/api/announcements.py、app/api/error_logs.py、app/api/user_logs.py、app/api/cart_sync.py、app/api/events.py 则处理外围能力。
- app/api/common_shelf.py、app/api/chemical_name_map.py 负责常用货架分组与 CAS 主数据维护。
试剂工作流
试剂链路是系统里最重的业务流:
POST /api/reagent-orders/创建订单。GET /api/reagent-orders/cas-overview/{cas_number}在创建或编辑时提供同 CAS 的库存与订单提示。- 管理员通过
POST /api/reagent-orders/{id}/approve或reject进入审批分支。 - 申请人或管理员在到货后调用
POST /api/reagent-orders/{id}/confirm-arrival。 - 需要转库存的订单再调用
POST /api/reagent-orders/{id}/stock-in,把订单数据复制到inventory。
这里的关键约束不是状态名字,而是“订单转库存是 copy,不是 move”。订单记录必须保留,用于审计、回溯和统计。
耗材工作流
耗材链路刻意更短:
- 创建耗材订单。
- 管理员审批通过或驳回。
- 审批通过后直接
complete,不进入瓶级库存管理。
因此 app/api/consumable_orders.py 同时承担查询、修改、审批与完成逻辑。它的重点是表单校验、导出与状态过滤,而不是库存生成。
库存工作流
库存接口是一组复合能力,而不是单一 CRUD:
- 列表查询同时承担分页、排序、短 TTL 缓存、CAS 搜索、文本搜索、FTS 搜索和拼音排序。
manual-add支持管理员绕过订单链路直接入库。borrow和return会修改状态、写借还历史,并通过 SSE 通知前端。dashboard/my-borrows和dashboard/pending-stockin为首页和仪表盘聚合数据。import/template与import组成 Excel 导入链路。- 常用货架由独立的
common_shelf表(CommonShelf模型)维护,按CAS + 品牌 + 规格形成分组键group_key,并由/api/common-shelf/*提供分组级与瓶级操作。 - 手动加瓶前会校验 CAS 主数据;若缺失主数据,需要先走
/api/chemical-name-map完成补录,避免常用货架出现无法稳定展示名称的脏数据。
事件驱动补充层
路由不是唯一的更新出口。很多接口在数据库提交后还会广播 SSE:
- 库存创建、编辑、删除、借用、归还
- 常用货架创建、编辑、删除、加瓶、扣减 1 瓶
- 试剂订单与耗材订单的创建、更新、删除
- 仪表盘聚合数据更新
这意味着前端页面不是“请求一次然后静态展示”,而是以 HTTP 快照为基线,再通过 SSE 做增量修正或 stale 提示。
购物车同步
cart_sync 不是“扩展直接发请求”这么简单,而是分成两步:
POST /api/cart-sync根据扩展采集到的商品,对已有试剂订单做匹配分析。POST /api/cart-sync/import把选中的商品导入系统,生成试剂订单或耗材订单。
扩展、前端导入页和后端路由之间通过批次数据衔接,详见 购物车同步扩展。
日志、错误与公告
user_logs用短期令牌保护用户操作日志查询,避免直接暴露日志文件。error_logs给管理员提供错误观测入口。announcements负责首页公告流,同时承担图片上传、可见性控制与置顶规则。
状态机与边界
试剂订单
- 状态枚举:
PENDING/APPROVED/REJECTED/ARRIVED/STOCKED/DELETED。 - 审批:
/{id}/approve与/{id}/reject仅管理员可用,更新状态并写入操作日志。 - 到货确认:
/{id}/confirm-arrival可直接进入暂存或常用货架,状态置为ARRIVED。 - 一键入库:
/{id}/stock-in将订单复制到inventory,保留source_order_id,支持写入常用货架或普通库存,入库后状态变为STOCKED。 - SSE 与缓存:审批、到货、入库都会通过
sse_manager推送reagent_orders房间并清理列表缓存。
耗材订单
- 状态:
PENDING/APPROVED/REJECTED/COMPLETED。 - 审批/完成:
/{id}/approve、/{id}/reject、/{id}/complete,完成后直接status=COMPLETED,不进入库存。 - SSE 与缓存:同样推送
consumable_orders房间并清理缓存。
库存与借用
- 入库来源:手动新增、批量导入、试剂订单复制;都会预计算拼音与 internal_code,并可写入常用货架标记。
- 借用/归还:校验当前
borrower_id和数量,借用会记录borrow_log,公用账号会弹出选择借用人;归还/消费会更新数量与last_borrower_id。 - 删除保护:已借出状态禁止编辑/删除,需先归还。
- FTS 与缓存:库存写操作会重建缓存并通过 SSE 推送
inventory/common_shelf房间。
购物车同步
- 阶段 1 匹配:
POST /api/cart-sync/sync按 CAS / 名称匹配已有库存或订单,返回匹配结果。 - 阶段 2 导入:
POST /api/cart-sync/import将匹配结果落地为试剂/耗材订单,失败条目逐条记录日志。 - SSE:导入完成后广播
cart_sync/reagent_orders/consumable_orders更新。
导入导出
- Excel 导入:
/api/inventory/import支持.csv/.xlsx/.xls,单文件 2MB,逐行错误返回;成功后推送 SSE 并刷新缓存。 - 导出:库存与订单都提供导出接口,走后台生成文件再下载。
边界与风险
- 订单复制入库必须保留
source_order_id,否则审计链会断裂。 - 常用货架与普通库存字段不一致时,前端列表和 SSE 房间要同步更新,否则会出现数据不一致。
- 批量导入需校验文件大小与行数,超限会返回 413/400;模板变更也要同步前端下载链接。
- 新增命名路由时,要注意它不能被
/{id}路由吞掉。
验证建议
- 试剂流程:新建 -> 审批 -> 到货 -> 入库,确认库存生成且
source_order_id回填。 - 耗材流程:新建 -> 审批 -> 完成,确认不会生成库存记录。
- 借用流程:借用后状态应锁定编辑,归还后可恢复;日志应记录 borrower/returner。
- 购物车导入:模拟扩展提交批次,确认能返回匹配结果并在导入后生成订单与 SSE 推送。
二次开发建议
- 新增业务接口时,先判断它是“对象 CRUD”还是“工作流动作”,后者通常更适合单独放在 workflow 路由中。
- 任何会改动列表结果的接口,都要同时考虑缓存失效和 SSE 广播。
- 库存相关新路由若是命名路由,必须在
/{inventory_id}之前注册。 - 面向前端或扩展的正式接口,最好同时在 API 参考 中登记。
参考代码
- app/api/announcements.py
- app/api/cart_sync.py
- app/api/consumable_orders.py
- app/api/error_logs.py
- app/api/events.py
- app/api/inventory.py
- app/api/reagent_orders.py
- app/api/reagent_orders_workflow.py
- app/api/user_logs.py
- app/api/user_sessions.py
- app/api/users.py
- app/models/consumable_order.py
- app/models/reagent_order.py