Files
shanxia2.0/openspec/changes/pl-contract-cert-quota-control/design.md
T

23 KiB
Raw Blame History

完整改造方案

1. 已确认的业务规则

取消“证书齐备”的业务概念。安排运输的依据是合同上适用证件能否覆盖本次数量以及证件使用条件是否满足,不再有人工点一下即可放行的状态。不以图片是否存在作为放行条件,不再把动态判断结果固化为可编辑布尔值。

山峡与A签销售合同,实际可送到A的客户B;采购也可由供应商直接送到山峡客户。合同交易方和实际装送方分别记录;管理山峡自身交易证件,不要求录入A-B下游合同。合同产品发生量继续按原合同口径统计,证件发生量由明确的用证分配明细统计。

2. 档案与编辑

手工录入可无图片;图片解析先预填可编辑表单,用户对照原图修改后保存。识别失败或缺字段可手填,不能猜测数量、单位和证号。所有证面字段可在确认前编辑;再次解析不得覆盖人工数据或自动启用、扣量。补图不新建档案、不重置余额。

购买证9组:购买方(名称/姓名、法定代表人、住所/地址、电话);销售方(名称、法定代表人、住所、电话);购买物品;用途;数量;有效次数;有效期限;编号;核发日期。

运输证9组:货主(名称/姓名、法定代表人、住所/地址、电话);运输物品(品名、数量、包装、购买许可/备案/出口许可证号);运输单位(名称、运输方式、住所/电话、车号次、运输路线);收货单位名称;有效期限;有效次数;申请单位名称;编号;核发日期。

数量与单位分别存储,多个车牌和引用证号完整保存。系统字段包括建档方式、附件、状态、确认信息及余额,不改变证面9组范围。用户确认的档案才可用于额度计算,保留已有复核信息,但不额外创造“齐备审批”。已有使用记录时禁止直接修改单位、证号等业务身份;核定量修订需依据与流水,不低于已用加预占,不自动重算历史折算量。

3. 合同、产品、证件关系

合同绑定证件,每条绑定可勾选多个合同产品。同一产品可以适用于多张购买证、运输证,多个规格如50%和98%硫酸可以共用证面“硫酸”的证。人工明确适用范围,不依赖品名相似匹配、不按浓度折纯。

证件只有一份总额度,不能因多个产品、合同绑定或车辆而复制。绑定仅表示可用范围,实际安排才分配和预占。子集合差量保存,遗漏不变、显式空集合须先校验引用;已有未结清用量的范围不能直接移除。历史明细和原ID保留。

4. 自动匹配、跨证分配和预占

可安排余量 = total_qty - used_qty - reserved_qty。两证独立逐证校验,各自覆盖相应交易来源量,不能把购买证和运输证数量相加作一份需求。

候选条件:当前租户与数据权限、合同绑定、适用产品、有效状态和期限/次数、实际参与方与装送条件。运输证还要匹配本次车辆及路线,引用购买证号须覆盖其所承担的相应购买证分配。自由文本路线不能靠模糊字符串匹配宣称自动核验,无法可靠判断时返回需确认原因。

默认策略建议:先满足可选证少的产品,在可行组合中优先较早到期证件,其次同单位下可用量较少者,最后证ID稳定排序。日期空值不得当作长期有效。此排序为方案推荐,不改变用户已确认的跨证分配规则。

算法须联合考虑全部产品、购买证与运输证引用关系。简单逐行贪心失败后须尝试重新分配(同单位可用容量网络/回溯验证),不能仅凭余额合计足够即通过,也不能因先抢占稀缺证而错误报缺口。不同单位不在同一数值排序池直接比较。

同单位自动分配;第一张余20吨,本次30吨,则第一张20、第二张10。两类证都适用,必要时继续第三张。每个来源的同种证分配之和必须等于需求,不能漏分、超分或重复分配。不同单位的分配由用户填写来源分配量与本证单位量,系统不推算密度、浓度、吨千克换算或历史系数。

预览仅计算不占用。点击安排后,服务端重读计划、证件及范围版本,在一个事务中重新计算/验证分配并预占;陈旧人工折算须重确认。计划、所有证件、绑定、usage按统一顺序加锁,逐证校验后写计划和流水,任一失败全部回滚。客户端重试用同operation_id和请求摘要,重复提交不重复扣;同幂等号不同载荷拒绝。

次数按实际使用事件独立控制,一次有效证被其他计划占用或使用后不可再选,不因本次有多个产品计为多次。未执行取消释放本次次数占用;已执行不能靠取消重置次数。多次有效仍受数量上限约束。

5. 多车共享与归档结算

运输证Y核定100吨,列车辆A/B/C。A已用20,B预占30,C预占10,则全证余40。各车都从同一余额池取量,其他未列车辆不能使用。并发跨车安排同样锁Y,不能每车各获100吨。

同单位归档按每个合同业务来源实际量、该来源的原分配顺序(allocation_seq)结转,不按比例平均,不使用其他产品未用额度补本产品归档。逐项实际量是上限,总计量按证汇总。保留证件、计划、产品、车辆及来源快照。

例:本次计划30吨,购买证G1预占20、G2预占10;运输证Y1预占20、Y2预占10,两运输证均允许本次车辆且引用关系匹配。实际29吨时:

证件 释放预占 增加已用 恢复可安排余量
G1 20 20 0
G2 10 9 1
Y1 20 20 0
Y2 10 9 1

四张证本次预占均变0。实际少于第一张占用,如18吨,则第一张用18、第二张用0,分别释放未用2和10。实际超过30吨时,多出部分必须补分配并重新校验容量与匹配;不足则记录真实差异进入异常处理,保留原账与处理依据,不能伪造实际量或无条件放行。

归档后改量按差额调整并保留历史流水;重复归档幂等。异单位归档必须重新确认实际折算量,不沿用计划比例。同一物理运输的装送两侧不能在同证上相加;来源唯一标识与另一侧追溯关联要保留。采购和销售独立合同统计不改变此去重要求。

未执行取消释放占用;已有执行证据不能直接释放当作未用。改派/改产品/改合同/改数量/改车辆应整体重算新旧证,成功一起替换,失败保留原占用。不得全删全增重建使用明细。

6. 彻底退出“证书齐备”

移除确认证书齐备、取消齐备、批量齐备、人工强制齐备、上传完整自动齐备。停止读写hazard_cert_ready/by/time作为任何业务门槛,旧值仅可在历史审计保留。不要把开关改名为“额度已核验”后继续手动放行。

前端展示动态结果:无需本次两证控制、待绑定证件、待人工折算、额度不足(注明证种/需求/可用/缺口/单位)、条件不匹配、可安排;已安排显示逐证已预占及用量明细,归档显示已核销。结果由后端返回,不能客户端直接编辑。查看时结果仅供参考,提交仍重校验。已安排计划复核时应计入自身已持有占用,不能把自己预占导致的余额减少误报为不足。

筛选、菜单角标、待办通知以“需要处理用证问题”的动态结果为依据,已完成预占的计划不误计缺口;可缓存展示但安排不能信任缓存。移除旧导出列、按钮权限和API调用。新旧客户端切换期间旧hazard-cert-ready写接口明确返回410迁移提示,不静默成功;旧筛选参数拒绝或提示更新,不能继续作为真实齐备查询。

当前代码关联点:

范围 已核实入口
齐备判断与自动标记 backend/app/services/pl_plan_service.py:clear_hazard_cert_ready、maybe_auto_set_hazard_cert_ready、validate_hazard_cert_ready_for_transport、set_hazard_cert_ready
安排与归档调用 backend/app/api/pl/transport.py、backend/app/api/pl/plan.py;以及plan_service内直接创建运输入口
列表与角标 backend/app/services/pl/plan_list_filters.py、backend/app/services/pl_menu_badge_service.py
模型/schema backend/app/models/pl_models.py、backend/app/schemas/pl/plan.py
Web检查与上传弹窗 web-admin/src/composables/usePlToxicProductCertCheck.ts、src/components/business/pl-plan-cert-upload-dialog/index.vue
Web界面 web-admin/src/views/pl/plan-cert/index.vue、columnFilterDefs.ts、src/views/pl/plan-archive/index.vue
H5界面 web-admin/src/views/m/pl/plan-cert/index.vue、detail.vue、CertDetail.vue、PlanCard.vue
API类型/展示 web-admin/src/api/pl/index.ts、src/utils/pl-display.ts
小程序回归 miniprogram/scripts/mp_api_smoke.py;实施时继续追踪原生/嵌入H5调用及通知入口

以上是本次只读定位,不代表已修改代码。

7. SQL、API与权限

DDL保留8表:pl_purchase_cert、pl_transport_cert、pl_transport_cert_purchase_ref、pl_contract_cert_link、pl_contract_cert_product、pl_plan_cert_usage、pl_plan_cert_usage_item、pl_cert_qty_log。

主档:证号、完整证面字段、entry_mode、可空attachment_urls、total_qty/quantity_unit、reserved_qty/used_qty及版本。绑定表支持两类证多对多。usage按计划+证种+证ID唯一;item保留来源完整量快照、来源分配量、本证单位计划/实际量、合同范围版本、allocation_seq及折算留痕。跨证原始完整量快照不能求和。

allocation_seq在同计划、同合同来源、同证种范围内确定结转顺序,事务内检查正数及不重复;与公共order_num分开,后者不作业务结算顺序。两种证的来源分配独立覆盖完整业务量。运输计划车辆在confirmation_snapshot/source_snapshot保存本次快照。

计划用证接口返回purchaseAllocations[]与transportAllocations[],每行包含usageItemId(已有时)、certId、contractLinkId、scopeProductId、allocationSeq、allocatedSourceQty、certQty、单位及版本、转换说明。车辆由当前安排上下文读取,单位由后端证件取得。预览返回逐证缺口、原因和人工输入项;安排接口内完成提交,无需前端先占证再创建运输。归档接口同事务提交实际量和异单位确认信息。

租户与权限使用当前用户上下文,数据权限多角色并集;增量子数据保存遵守专题规则。所有金额外数量用Decimal,禁止float;MySQL5.7不依赖CHECK约束,服务层验证非负、总额度和历史引用。

8. 迁移与上线

先建档、确认历史已用与在途预占,再切换安排门槛;旧图片/齐备标记不能直接推定核定量或余额。旧附件可作为建档来源,不能自动生成无限额度。历史已归档不自动回算重复扣,明确期初截止点;在途计划逐条关联有效证和占用并核对未包含在期初已用内。

准备期新能力不参与正式放行,切换后所有入口统一使用新规则,不双扣、不允许旧齐备备用放行。未完成历史映射的受影响计划进入明确的数据补齐流程,不虚构分配。旧字段暂留用于兼容审计,不物理删除业务记录;客户端切换完成后再单独评估列清理。

启用新账后回滚不得恢复旧齐备绕过;暂停相关新安排、保留新账、修复后继续,必要的业务纠错走可追溯调整。

9. 易制爆合法使用证明(用户已确认)

2026-11-01用户明确:合法使用证明针对易制爆产品,独立提供界面,用户后续上传,不影响运输安排。此决定替代此前“保留合法用途证明独立阻断校验”的候选建议,不再等待确认。

业务展示名称统一为“合法使用证明”;现有技术字段legal_use_cert_url可继续复用,保留历史附件,不能仅为改名清空或迁移丢失数据。

  • 仅含易制爆、无需两证控制的产品:未上传合法使用证明也可安排运输,其他正常运输业务校验照常执行。不得因易制爆标志本身强制要求购买证和运输证额度。
  • 同时涉及易制毒/易制爆的计划或产品:购买证、运输证按实际需证产品范围检查条件并预占;合法使用证明未上传不构成阻断。不能因允许后补证明而放宽两证额度校验。
  • 合法使用证明不预占、不扣量、不影响购买证与运输证余额;其附件状态不得重新包装为“证书齐备”。
  • 单独提供“合法使用证明”界面,按计划及易制爆产品明细列示计划号、合同、产品、实际相关客户、已有附件和上传情况。允许筛选待上传/已上传,上传、预览、下载及按权限维护附件;状态由附件派生,仅供补资料跟进,不能回写计划放行开关。
  • 允许运输安排之后继续补传;附件保存使用独立资料维护入口,不能复用会因计划已安排而禁止全部编辑的通用计划保存,也不能触发运输重建、额度重算或库存变化。若计划已归档,补充资料仍保留原业务明细ID和结算数据,不通过重开归档实现补图。
  • 沿用现有计划产品legal_use_cert_url保存关系,同字段多附件按MinIO URL逗号分隔,不新增证件额度表。服务端校验当前租户、数据权限、目标产品明细归属和附件访问;更新只涉及本次提交附件,遗漏不变、显式清空按权限及引用规则处理,保留操作审计。
  • Web提供独立菜单/页面;移动H5及小程序提供对应可达入口。实施前按当前规范确认页面选型、菜单归属和权限。原危化品上传页的历史合法使用证明继续可见,不与购买证/运输证档案混在齐备操作中。
  • 待上传资料计数与运输额度缺口计数分开,前者不阻断安排,也不混入“不可安排”的数量。旧公共齐备判断必须拆除对合法使用证明的运输前置校验,不能仅删除按钮而保留后端拒绝。

这是本系统已确认的业务设计,验收依据为用户本次说明;本次仅更新方案,未实施界面或接口。

10. 工单 #4 实施前核对与候选补充(2026-11-01,待方案确认)

来源:工单 #4,远端更新时间2026-11-01 10:09:14(北京时间)。正文及全部评论已读取,评论0条,无图片附件。以下为当前代码核对及候选落地方案,不修改前述已确认业务规则,不代表业务实现、数据库验证或用户验收完成。

10.1 当前实现缺口

位置 核实结果 实施要求
backend/app/services/pl_plan_service.py 的 validate_hazard_cert_ready_for_transport ready为1或附件完整即返回;纯易制爆也进入公共校验 所有安排入口改接额度服务;合法使用证明独立于需证判断
同文件 update_plan_product_certs 已归档拒绝修改;三个附件字段均赋值,遗漏可能清空;末尾自动标记齐备并commit 独立后补接口按显式字段更新,预校验整批明细后写入,不复用现有自动齐备流程
backend/app/api/pl/transport.py、api/pl/plan.py、pl_plan_service.py 均存在旧放行校验调用 分别覆盖已有计划安排、直接创建运输及对应业务入口,不能只改某一路由
web-admin/src/views/m/pl/plan-cert/model.ts及其测试、src/utils/mp-pl-plan-card-fields.ts及其测试 仍依赖hazardCertReady筛选、卡片字段或批量操作 同步替换客户端模型及旧测试断言,不能仅隐藏按钮
miniprogram/scripts/mp_api_smoke.py 仍调用旧齐备写接口并恢复旧值 改为旧接口拒绝且业务状态不变的断言,不能保留旧成功期望
8表SQL草案 已有operation_id/request_hash及逐证唯一键;无独立请求结果记录;用证次数未有明确结构字段 在正式DDL前落实请求级幂等及次数模型,不把逐证唯一键等同于完整操作幂等

本次仅核实仓库模型与SQL草案,未连接数据库;不能据此认定运行数据库已具备或缺少任何表。

10.2 推荐实施顺序与替代方案

推荐在同一工单内分阶段开发、统一切换:A档案/绑定/迁移 → B分配/预占/核销核心及隔离数据库回归 → C全部放行入口与各端界面替换 → D历史对账及切换演练。合法使用证明独立接口可以在核心开发期间完成,但最终验收仍覆盖所有端。阶段完成不等于整单交付,准备期不提前停用旧入口或启用半套新账。

备选为全部模块一次实现再集中验证,优点是中间状态少,缺点是定位事务与跨端问题困难。仅隐藏齐备按钮无法达到工单要求,不作为可交付方案。

10.3 请求级幂等与次数(候选技术方案)

保留8张领域表;推荐另增请求操作记录,用tenant_no + operation_id唯一约束记录动作、plan_id、规范化request_hash及结果引用。在同一事务内创建操作记录、完成全部安排/调整/核销及流水;同号同载荷返回原结果,同号不同动作/计划/载荷拒绝。请求记录不能提前commit,失败整体回滚;数据库死锁或超时只允许有界重试整个事务。

原因:现有uk_cert_operation(tenant_no, operation_id, cert_type, cert_id)只能阻止同号同证重复流水,不能阻止同号改选另一组证、跨计划复用,也不能记录无需两证的成功安排。新表是对8表草案的候选补充,正式迁移前需确认,不在本次执行DDL。

证面有效次数保留原文;服务端派生一次/多次/待确认使用策略,不猜测无法识别的文字。一次有效证在证件行锁内检查有效占用和已执行事件,同次安排的多个产品共享一次事件。期初须独立记录是否已使用一次,不能仅凭used_qty是否为零推断。零实运量但有执行证据时不得自动恢复一次使用资格。最终字段与期初流水结构列为正式DDL前置任务。

10.4 联合分配、去重与异常边界

分配器输入为经过权限过滤的来源行、证件容量、合同适用边、车辆条件、购买证与运输证引用边及人工折算记录。先固定经确认的异单位分配,再对同单位剩余需求进行有容量约束的联合可行性求解;购买证分配结果不能先独立固定而忽略运输证引用限制。排序只是可行解偏好,搜索达到资源限额时返回明确的计算未完成状态,不能误称业务额度不足。

需增加反例:产品A可用G1/G2,产品B只能G1;以及购买证有余量但可用运输证仅引用G2的情形。都应找到完整可行解,而非因先使用G1误拒绝。

PlPlanLoadProduct和PlPlanDeliveryProduct都有合同/合同产品关联。现有usage item唯一键只覆盖同usage同来源侧行,不能单独证明装送去重;实施前需明确物理货物对应关系与唯一计量来源。推荐保留来源配对映射,不以产品名、SKU相同或两侧数量相同猜配。无法唯一对应时阻断受影响分配并要求补齐来源关系,采购/销售合同统计仍各按原口径计算。具体缺省计量侧属于待确认业务口径。

超量实际发生且补分配失败时,候选方案为保留实际回传及异常原因,原证账与预占不变,正式归档结转不成功;后续补证或调整后重试。异常证据与业务实际量必须持久化,不能在抛错回滚时一同丢失。需要区分“保存事实/异常”与“成功归档结转”两个结果,禁止假报归档成功。

10.5 当前阶段与下一步

当前阶段:工单已拉取,方案差距已记录;A—D均未开发。开始业务实现前确认第10.3节请求记录方案、第10.4节唯一计量来源及异常归档口径,随后在从V3.0建立的独立任务worktree落实实施清单。保留当前目录已有修改,不复制Agent凭据到任务分支。

验证缺口:未执行应用测试、隔离MySQL5.7事务验证、外部图片解析或UI验证;未执行生产迁移、push、合并、部署或远端工单修改。

11. 开发接入记录(2026-11-01)

用户在本会话回复“好的”确认第10节候选方案,进入开发。任务分支codex/gitea-issue-4;原工作目录已有修改保留。

  • 模块命名修正:现有pl_cert_service.py已服务车辆/人员证件;新模块统一使用pl_quota_*,不覆盖原服务。
  • 新发现的旁路:api/pl/transport.py:create_transport接受plan_ids并关联计划,却未调用旧hazard校验;纳入原子用证接入。archive_plan的无payload早提交分支也须覆盖。
  • create_transport_for_plan当前仅有定义未找到调用;不能把计划车辆预填当正式安排预占。
  • Figma流程原型v1:根节点4:2。档案4:5、购买证4:43、运输证4:111、范围4:168、预览4:197、资料后补4:240、H5资料卡4:271。已检查渲染并修正横向容器行高;282个可编辑子节点,无完整界面位图,Inter字体,无横向溢出。当前为流程/字段布局稿,待用户确认,不能视为最终视觉验收。
  • 视觉依据:档案P01参考web-admin/src/views/demo/unit/index.vue;多状态用证P02参考views/demo/order/index.vue;建档P12沿用顶部标签与分区表单;资料后补P13复用components/business/attachment-upload-field/index.vue。Web/H5沿用Fresh Mint,小程序按三墙规范;原型不是新建公共设计系统的授权。
  • 新环境缺口:仅发现阿里云人员证件OCR,无购买证/运输证大模型解析配置。已询问服务选择,等待期间不上传真实图片。Docker可访问且有MySQL5.7镜像,因工程记录未授权服务启停,已请求独立测试容器授权;尚未创建容器。

11.1 本轮用户反馈与环境确认

  • 用户明确“暂不启动容器,保留事务验证待办”:不创建MySQL测试容器,SQLite/纯逻辑测试继续,真实MySQL并发及事务验收保留未完成。
  • 用户指定DeepSeek:使用官方视觉接口deepseek-flash候选,官方说明https://api-docs.deepseek.com/guides/vision/。运行密钥仅保存任务worktree被忽略的backend/.env(0600),不写入文档、Git、Agent凭据文件或日志。GET /models认证成功,返回支持的模型名称;尚未执行真实证件图片解析。
  • 用户指出原型缺少如何关联合同及合同产品的操作过程;v1不视为获批。补充v2关联流程节点11:2:选择山峡合同 → 勾选合同产品行 → 分别配置购买证/运输证及各自适用行 → 保存后矩阵维护;另加证件详情反向查看关联及关联另一合同。等待修订稿确认,不先实施依赖界面。

实施确认补记(2026-11-01)

用户已确认合同关联 v2:“符合,按 v2 实现(推荐)”。原型节点为 ImDcikQnCX5GiJQLwUHwDC / 11:2:选择山峡自身合同 → 勾选合同产品行 → 分别选择购买证、运输证及各自适用产品 → 保存;合同与证件两侧均可查看维护。绑定不扣量,产品和车辆共享证件余额。沿用 Fresh Mint 和现有组件实现。

用户明确暂不启动 MySQL 测试容器,实际 InnoDB 事务/并发锁验证保留待办。图片候选解析选择 DeepSeek,凭据仅保存在忽略的本地配置,不进入代码或文档。