记录显式 status 提交流程,并标注旧出入库设计中现场自动完成规则已被覆盖。 Co-authored-by: Cursor <cursoragent@cursor.com>
6.6 KiB
出入库现场链接 · 单据状态可选设计
日期: 2026-07-19
状态: 已确认
范围: web-admin 公共现场页 PublicIoOrderPage + backend public_io_order / apply_site_feedback
关联: 修正 2026-07-19-pl-inbound-outbound-order-design.md 中 D5「现场首次提交 → 自动已完成」规则
界面模式: 公共页对齐既有 public/pl/inbound|outbound(Vant)
1. 背景与问题
安排运输后生成出入库单现场链接。当前「提交」只保存实际数量、备注、附件;状态变更依赖服务端隐式规则:
- 首次提交且
status == in_progress→ 自动改为completed - 再次提交不改状态
- 页面状态只读展示,无法选手动状态(含作废)
现场需要明确选择单据状态(含作废),且默认展示当前真实状态。
2. 已确认决策
| # | 决策 | 选择 |
|---|---|---|
| D1 | 实现方案 | 提交体携带 status,服务端按所选写入(方案 1) |
| D2 | 可选状态 | in_progress / completed / voided(进行中 / 已完成 / 已作废) |
| D3 | 选择器默认值 | 进入页时展示 当前单据真实状态(多为进行中) |
| D4 | 作废后链接 | 仍可打开 只读查看,不可再提交 / 上传 |
| D5 | 作废影响范围 | 仅本出入库单 status = voided;不联动作废运输计划、运输单、地磅、司机链接 |
| D6 | 隐式完成 | 删除「首次提交自动已完成」 |
| D7 | 入库 / 出库 | 对称;共用 PublicIoOrderPage 与 apply_site_feedback |
3. 前端(公共页)
文件:web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue
3.1 状态选择
- 在「现场备注」区块上方增加「单据状态」选择,使用
van-radio-group横向三项(与现有 Vant 公共页一致) - 选项文案:
进行中/已完成/已作废,值分别为in_progress/completed/voided loadPage后:selectedStatus = page.status- 可编辑态:顶部信息卡「状态」改为只读展示当前已保存状态;下方选择器为本次拟提交状态
- 只读态:仅顶部信息卡展示状态标签,不展示选择器
3.2 提交
saveFeedbackbody 增加status: selectedStatus(必填)- 当用户选择
voided并点提交:先showConfirmDialog(或等价)确认文案大致为「确认作废本出入库单?作废后不可再提交。」;取消则不请求 - 提交成功后
loadPage()刷新
3.3 只读态(page.voided === true)
判定保持后端现有:header.status == "voided" 或 计划 follow_status == "cancelled"。
- 不再仅用
van-empty「单据已作废」遮住整页 - 展示与可编辑态相同的信息结构(业务信息、取送货、产品数量、回执、备注、状态)
- 数量输入、上传、备注、状态选择、提交按钮全部禁用或隐藏
- 顶部增加只读提示条:「单据已作废,仅可查看」
3.4 类型
web-admin/src/api/pl/public-io-order.ts:saveFeedback参数增加status
4. 后端
4.1 Schema
PublicIoOrderFeedback(backend/app/schemas/pl/io_order.py)增加:
status: str # 必填;alias 可选;合法值 in_progress | completed | voided
非法值 → 业务 400「无效状态」。
4.2 apply_site_feedback
文件:backend/app/services/pl_io_order_service.py
- 若
header.status == "voided"→ 仍抛ValueError("单据已作废,无法提交")(调用方在 commit 前已用page["voided"]拦计划取消场景) - 校验
status ∈ {in_progress, completed, voided} - 写入
header.status = status(显式,不再隐式升级) - 照旧写明细
actual_qty、site_feedback_*、site_feedback_time site_first_submitted_at:若为空则写入当前时间(与本次所选状态无关)- 删除以下逻辑:
if not header.site_first_submitted_at:
...
if header.status == "in_progress":
header.status = "completed"
(首次提交时间与状态写入顺序:先写 feedback 字段与 status,再按需补 site_first_submitted_at。)
4.3 公共 API
backend/app/api/pl/public_io_order.py 的 save_feedback:把 payload.status 传入 apply_site_feedback。
page["voided"] 时拒绝提交 / 上传的既有逻辑不变。
4.4 非目标
- 不修改管理端
PATCH出入库单改状态逻辑 - 不调用计划作废(
void_plan)及其级联(运输取消、地磅、现场旧链 revoke 等) - 不改司机公共页、旧
pl_transport_site_link现场页 - 无 DDL 变更
5. 数据流
现场页 load → GET /public/pl/{inbound|outbound}/{token}
→ selectedStatus = page.status
用户编辑数量/备注/附件/状态 → 提交
→(若 status=voided)确认弹窗
→ POST .../feedback { status, items, remark, images }
→ apply_site_feedback 写 status + 回执字段
→ 若写为 voided:下次 GET voided=true → 只读
6. 错误处理
| 情况 | 行为 |
|---|---|
| 链接无效 | 404 / 前端空态(既有) |
| 已 voided 或计划已取消仍提交 | 400「单据已作废,无法提交」 |
status 缺失或非法 |
400「无效状态」 |
| 作废确认取消 | 不发请求 |
7. 验收
| # | 场景 | 期望 |
|---|---|---|
| 1 | 安排后首次打开现场链接 | 状态选择器为「进行中」 |
| 2 | 选「已完成」提交 | 单据 completed;管理端列表一致;可再次进入继续改数量/状态 |
| 3 | 选「进行中」提交(曾已完成) | 单据回到 in_progress |
| 4 | 选「已作废」并确认提交 | 仅本单 voided;运输计划跟进状态不变 |
| 5 | 作废后再打开 | 可查看历史回执与数量;不可提交/上传 |
| 6 | 管理端作废计划后打开现场链接 | 只读(既有 voided) |
| 7 | 回归 | 无 status 的旧客户端若仍存在会失败;本期仅本仓库公共页,同步改前端即可 |
8. 改动文件清单
| 层 | 文件 |
|---|---|
| Schema | backend/app/schemas/pl/io_order.py |
| Service | backend/app/services/pl_io_order_service.py |
| API | backend/app/api/pl/public_io_order.py |
| 前端 API | web-admin/src/api/pl/public-io-order.ts |
| 公共页 | web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue |
| 文档 | 本文件;实现后可在关联设计 D5 旁注「已被本设计覆盖」 |
9. 测试建议
- 后端:对
apply_site_feedback单测或手工:首次提交completed/voided/in_progress;已 voided 再调应失败 - 前端:入库与出库各走一遍上述验收表 1–5