## ADDED Requirements ### Requirement: 作废须持久化独立原因字段 系统 SHALL 为下列实体提供独立作废/取消原因三元组,且不得仅将原因拼入通用 `remark` 作为唯一存储: - `crm_quote`:`invalid_reason`、`invalid_time`、`invalid_by` - `pl_plan`、`pl_vehicle_fuel_register`、`pl_vehicle_recharge_apply`、`pl_vehicle_maintenance_apply`、`pl_vehicle_urea_apply`、`pl_freight_settlement`:`cancel_reason`、`cancel_time`、`cancel_by` - `db_weighing_slip`:`void_reason`、`void_time`、`void_by` 原因字段长度 SHALL 为 `varchar(500)` 且允许 NULL。合同与调价函既有 `invalid_*` 行为 SHALL 保持不变。 #### Scenario: Schema includes dedicated columns - **WHEN** 查询上述表结构 - **THEN** 对应三元组列存在且允许 NULL ### Requirement: 作废接口支持可选原因 对报价单 invalid、运输计划 cancel、加油/充值/维保/尿素申请 cancel、运费结算 cancel、地磅 void,系统 SHALL 接受可选原因(请求体字段可缺省或为空)。原因 trim 后为空时 SHALL 将原因列置为 NULL,且 SHALL NOT 因此拒绝作废。无论原因是否填写,作废成功时 SHALL 写入作废时间与操作人,并按既有规则更新作废/取消状态。 #### Scenario: Allow void without reason - **WHEN** 调用上述作废/取消接口且未提供原因或原因为空白 - **THEN** 单据仍进入已作废/已取消(或地磅 `is_voided=1`),原因列为 NULL,时间与操作人已落库 #### Scenario: Persist reason when provided - **WHEN** 调用上述接口且提供非空原因 - **THEN** 单据作废成功,且原因/时间/操作人已落库 ### Requirement: 报价单不再写入备注拼接原因 报价单作废若提供原因,SHALL 写入 `invalid_reason`(及时间、操作人);SHALL NOT 再向 `remark` 追加「【作废原因】」前缀文本作为主存储。未提供原因时 SHALL 仍写入 `invalid_time` 与 `invalid_by`,`invalid_reason` 为 NULL。 #### Scenario: Quote void uses invalid fields - **WHEN** 用户作废报价单并提交非空原因 - **THEN** `invalid_reason` 等于提交原因,且不以「【作废原因】」形式追加为 `remark` 的唯一来源 #### Scenario: Quote void without reason - **WHEN** 用户作废报价单且不填原因 - **THEN** 报价单状态为已作废,`invalid_reason` 为空,`invalid_time` 与 `invalid_by` 已写入 ### Requirement: 加油登记作废恢复清空原因 加油登记从已作废恢复为正常时,系统 SHALL 清空 `cancel_reason`、`cancel_time`、`cancel_by`。 #### Scenario: Restore clears cancel triad - **WHEN** 用户对已作废加油登记执行作废恢复 - **THEN** 状态为正常且三个 cancel 字段均为空 ### Requirement: Web 与小程序作废可采集原因 web-admin 中上述业务的作废操作 SHALL 提供作废原因输入(选填),允许不填直接确认作废。miniprogram 中充值申请、加油登记、地磅单的作废操作 SHALL 同样允许选填原因。已作废单据的查看态:若有已保存原因则 SHALL 展示;有作废时间/操作人时一并展示。 #### Scenario: Web allows submit without reason - **WHEN** 用户在 Web 打开作废对话框且未填原因点击确认 - **THEN** 仍可调用作废 API 并完成作废 #### Scenario: Miniprogram optional reason - **WHEN** 用户在小程序对充值申请、加油登记或地磅单作废且不填原因 - **THEN** 仍可调用对应 cancel/void API 并完成作废