01摘要与三源合并
一句话:这是在既有销售合同单表上新建的一个只读分组查询——按「框架合同 → 其下订单合同」成组展示,7 个筛选、16 个展示字段、4 条处理规则、2 个权限点;数据面无新表,全部靠新增字段与自关联表达。
本文档怎么用
开发人员
第 4 章是唯一实现依据;第 5 章给任务与文件落点;第 6 章给验收口径。第 2 章可不读。
业务 / 产品
只需读第 7 章(4 项确认单)+ 第 2.2 节(分歧影响面)。
评委 / 架构
读第 2 章(三路异同)与第 8 章(工程约束),验证合并过程是否可复核。
02三路规范异同
三路在互不可见的前提下独立分析同一份需求。结果是:需求要素 26/26 三路全覆盖(覆盖完整性不是差异点);真正的差异出现在「需求没写、但实现必须拍板」的地方。
2.1 共识清单(直接采纳)
以下 6 项是三路在互不知情的情况下给出同一答案的决策。这类结论可信度最高——它不依赖任何单一路径的判断,直接进入规范正文。
| # | 议题 | 三路一致的结论 | 来源 |
|---|---|---|---|
| 1 | 状态可见口径 | 只展示合同审核状态为「已确认」(applyStatus='yqr')的合同;框架合同自身亦须已确认——沿用老系统台账口径,保证两系统数字可比 | A · B · C |
| 2 | 分页单位 | 以框架合同为分页单位:先分页框架,再按本页框架批量取订单组装成组;totalRows 只用于展示、不参与分页计算 | A · B · C |
| 3 | 关联键与唯一性 | 关联键 = 框架合同的销售合同编号(订单侧存该编号);因库中无唯一索引,须「写入侧前置唯一性校验 + 查询侧去重兜底」双保险 | A · B · C |
| 4 | 期限交集端点 | 闭区间相交,含端点(查询区间端点恰等于框架期限起/止当天即命中) | A · B · C |
| 5 | 「合同类型2」列 | 新增独立列,枚举 framework / order,不得复用既有「合同类型(C)」(老库已有 CONTRACTTYPE NVARCHAR2(1),撞名风险) | A · B · C |
| 6 | 序号语义 | 仅框架行显示序号,订单行不显示;序号按结果集跨页全局连续,不按页重排 | A · B · C |
2.1.1 三路一致认定的老系统差异(同一份事实)
三路各自独立取证,对老系统现状的 7 项判断完全一致(细节与出处见第 3 章)。这说明事实层的分歧风险很低——合并时不需要重新裁决现状。
| # | 项 | 老系统现状 | 需求写法 |
|---|---|---|---|
| D1 | 数据可见口径 | 主 SQL 硬过滤 applyStatus='yqr'(ContractSellQueryMapper.xml:124) | 需求未提 → 采纳「只出已确认」 |
| D2 | 排序键 | order by a.oid desc(:205)——写入主键倒序,与签订时间无关 | 按框架签订时间倒序、空值靠后 → 必改 |
| D3 | 部门权限判定字段 | 执行部门 OR 签订部门(ContractSellQueryController.java:104-105;SQL :144-146) | 仅「签订部门」→ 见 §2.2 裁决 1 |
| D4 | 模糊搜索范围 | 3 列:name / contractId / economicsLawNumber(:126) | 4 列(+ 分公司合同编号)→ 需扩列 |
| D5 | 框架/订单与预估额字段 | 销售侧完全不存在(无表、无列、无 domain、无代码) | 需求大量引用 → 需新建列 + 强依赖导入侧 |
| D6 | 关联键唯一性 | ECONOMICSLAWNUMBER 无唯一索引(唯一索引仅 OID、CONTRACTID) | 以销售合同编号关联 → 行放大风险 |
| D7 | 功能位置 | 【合同台账查询(分公司)】菜单名在仓库中未找到(菜单配置在数据库) | 「在其下方新增」→ 落点需业务确认 |
2.2 分歧与裁决
4 项分歧 + 1 项遗漏。每项都给出裁决、理由、以及「如果你不同意会怎样」的影响面。争议项强制标注为待业务确认,但不阻塞开发——开发按裁决值先行。
需求原文:「部门级可查看『签订部门』为本部门的框架合同及其下属订单合同」——只提签订部门。老系统台账查询用的是「执行部门 OR 签订部门」双字段。
理由:① 这是一个全新功能,不是改造既有台账查询——不存在「可见数据回退」的问题,A 路的投诉论据在这里不成立;② 需求是已由营销中心与分公司签批的确认版,字面效力最高;③ 「签订部门」是业务上定义框架合同归属的字段,与「本部门的框架合同」这一表述自洽。
影响面:部门级用户看到的框架范围比老系统台账口径窄。若业务实际期望「执行部门也进范围」,改动点集中在一处权限条件(
OR 追加一项),成本可控。
需求未定义。这是「组级权限」与「行级权限」的取舍。
理由:① 需求的可见对象是「本部门的框架合同及其下属订单合同」,是组级实体;② B 路的行级裁剪会造成「框架显示为 3 个订单、实际只露出 1 个」的静默缺失,直接破坏本功能最核心的「一屏看完整组」价值;③ 与「分页以框架为单位、组不撕裂」的共识(§2.1 第 2 项)保持同一套分组语义。
影响面:部门级用户可能看到他部门的订单行(作为本部门框架的子行)。若业务不接受,须改为行级过滤,并同步重新定义分组呈现与分页语义——这是本裁决唯一的实质代价。
需求未定义。三路给出三种不同态度,其中 A 路完全未意识到该场景。
理由:列表的唯一分组单位是框架合同;无父的订单既无法归组,也会让「分页以框架为单位」的计数失真。但「静默丢弃」是不可接受的——所以补齐 C 路未强调的两层:可观测告警日志(运维可发现)+ 导入侧父子完整性校验(从源头拦住)。
影响面:异常数据不出现在业务视图,靠日志与导入校验闭环。若业务希望「在界面上就能看到未归属订单」,则改采 B 路方案:在列表末尾追加「未归属订单」伪分组,需同步调整分页与序号口径。
三路一致同意「新增独立列、不复用创建时间」,但对历史数据的取值出现细微分歧。
createTime。理由:回填
createTime 会伪造语义——手工录入的合同同样有 createTime,回填后会被当作「被导入过」的合同,使「导入日期」这个筛选条件从第一天起就撒谎。留空 + 明确排除,是唯一不会污染口径的选择。影响面:历史合同按导入日期筛选时查不到(符合事实:它们确实不是导入进来的)。若业务确实需要区分「导入 / 手工录入」,可另用
dataType='import' 标记做辅助判断,不冲突。
A 路用「设计树 + 两轮共 16 个拷问」产出最多未定义点,却漏掉了「订单指向的框架不存在」这一边界。B、C 两路都识别到了,并给出了相反的处理策略。这提示:流程本身不保证想到边界情况——本规范在 §4.5 把该场景写成显式规则(R-05),并要求配套告警与导入校验。
2.3 三路方法论差异(与规范内容无关,供选型参考)
| 维度 | A · mattpocock | B · OpenSpec | C · spec-kit |
|---|---|---|---|
| 心智模型 | 一组可组合的小技能 | specs/=现状,changes/=提案(delta) | 规格即源码,阶段流水线 |
| 流程刚性 | 最松 | 中 | 最紧(七步 + 宪法门禁) |
| 产物主形态 | 拷问决策台账 + tracer 票据 | Requirement / Scenario | FR + plan + tasks + 契约 |
| 独有能力 | 设计树拷问、每问带推荐答案、阻塞边票据图 | validate --strict 机检护栏 | 宪法门禁 + spec↔plan↔tasks 三方交叉检查 |
| 本次实测短板 | 漏了孤儿订单场景;技能假设有真实 tracker 与在线用户 | 表单类需求上未新增结构化信息密度 | 对高密度单页表单需求,user story 粒度偏粗(被硬拆成 4 个故事) |
| 证据穷尽性 | ⭐⭐⭐ 找到完整 DDL,并纠正既有分析的两处计数错误 | ⭐⭐⭐ 同左,并发现被引用的文件实际不存在 | ⭐⭐ 未定位到 DDL 来源,把可查证事实记为「未找到」 |
注:证据穷尽性是本次三路唯一出现“客观正确性差异”的维度——A、B 两路都挖到了 onesystem/analysis/ddl/*.txt(由 old-src/oracle.dmp 解析出的权威 DDL),C 路停在「源码里没有 → 未找到」。
03老系统现状取证
三路一致认定的事实,逐条带 文件:行号。查不到依据的一律显式写「未找到」,不用常识填充。路径缩写:old-src/ = onesystem/old-src/;analysis/ = onesystem/analysis/(由 old-src/oracle.dmp 解析出的 DDL)。
3.1 现有功能与调用链
| 层 | 位置 | 要点 |
|---|---|---|
| Controller | old-src/contract/src/main/java/r1/projectmanage/controller/ContractSellQueryController.java:57 | /contract/contractSellQuery,类注释「合同台账查询」:51 |
| Service | .../service/impl/ContractSellQueryServiceImpl.java:53 | 类注释「销售合同台账查询」:47 |
| 列表 SQL | .../mapper/ContractSellQueryMapper.xml:7-206 | selectContractListPage;表 ESS_CONTRACT 见 :85 |
| VO | .../model/ContractSellVO.java:8 | @GaTableName("ESS_Contract") |
| 权限工具 | .../util/UserPermissionUtils.java:30-39,88-97 | IsCompanyExist() / isDeptPermission() |
3.2 列表主 SQL 的关键写法(实现时须逐条对照)
| 行为 | 出处 | 现行写法 |
|---|---|---|
| 数据范围硬过滤 | ContractSellQueryMapper.xml:124 | co.applyStatus = 'yqr'(只出「发展已确认」) |
| 编号/名称模糊 | :126 | (name like ? or contractId like ? or economicsLawNumber like ?)——仅 3 列 |
| 分公司合同编号 | :168 | bccontractId like ?——独立参数,未并入上面的模糊条件 |
| 合同甲方 | :130;join :86-87 | cuo.name like ?(cuo = ess_customorganization) |
| 日期区间 | :134-139 | 只有「经法审批时间」区间 auditTimeStart/End,无 signedDate 区间 |
| 部门权限 | :144-146 | and (co.executionDepartment = ? or co.signedDepartment = ?)——双字段 OR |
| 排序 | :205 | order by a.oid desc |
| 去重 | :9,:11 | 内外层 select distinct(一对多 join 会放大行) |
| 派生列先例 | :40-48,:68 | 收入金额 / 已开收据金额 / 匹配金额,全部是子查询聚合 + nvl(...,0) |
| 分页 | ContractSellQueryServiceImpl.java:98;Controller:111-112 | 框架 CountableRowBounds 按行分页 |
3.3 关键否定结论(三路一致)
ESS_CONTRACT不含任何框架 / 订单 / 预估额 / 框架期限列(全表 136 列扫描,analysis/ddl/create_table.txt)。- 销售侧零命中「框架合同 / 订单合同」概念——命中项全部属于采购 / 外包域。
- 易混淆项:
QUALITYSTARTTIME / QUALITYENDTIME是质保起止(对应导出表头「质保周期/月」),不是框架合同期限。 ESS_CONTRACT已有CONTRACTTYPE NVARCHAR2(1),但销售侧 VO 未映射它(VO 的typeC来自TYPEC)——新增「合同类型2」不得复用该列。- 「导入日期」独立列不存在——老系统只有
DATATYPE='import'标记 +CREATETIME。
3.4 术语撞车(必须在代码与词表里显式区分)
「框架合同」在采购 / 项目域是既有概念,与销售侧的「销售框架合同」同名不同义。全库含 FRAME 的列分布:
| 表 | 列 |
|---|---|
R1_PURCHASECONTRACT | FRAMENUMBER · FRAMEINDATESTART · FRAMEINDATEEND |
R1_GOODSPLANDETAIL | ISFRAME · FRAMENO |
R1_PROJECTPLANDETAIL | ISFRAME · FRAMENO |
R1_PROJECTPROCESSRESULT | FRAMENUMBER |
| ESS_CONTRACT(销售侧) | 无任何 FRAME 列——所以本功能要新增,且命名须避开 FRAME* 前缀带来的歧义 |
ContractSellVO.java:20)。新系统一律用「销售合同编号」;仅在迁移映射与需求对照中保留旧名。老系统的列名(economicsLawNumber)、附件名(contract_f 经法合同审批单)与报错文案里旧词仍在,属历史包袱,不迁移。
3.5 导入链路(本功能的前置依赖)
| 事项 | 出处 | 事实 |
|---|---|---|
| 两套导入模板 | ContractSellImportServiceImpl.java:87-93,95-101 | 逐项核对:均不含框架类型 / 框架预估金额 / 框架期限 / 各类预估额 / 订单→框架关联 / 分公司合同编号 |
| 落库状态 | :864,:1536;ProjectConstant.java:74 | 导入写入的 applyStatus = wtj(未提交),而列表硬过滤 yqr → 导入数据默认查不到,须走确认流程 |
| 导入标记 | :860,:1532;ProjectConstant.java:926 | dataType = 'import' |
| 判重 | :395-399,409-410 | 按 economicsLawNumber / 名称判重,文案用「内部合同编号」 |
| 未写入的列 | ContractSellMapper.xml:214-275 | insertContractVo 不写 bccontractId、signCompany → 佐证「分公司合同编号」由另一篇(缺失的)导入需求承担 |
| 导出先例 | ContractSellQueryServiceImpl.java:68-79 | 表头 54 列,含「分公司合同编号」「分公司合同归属单位」 |
old-src/oracle.dmp 是 1 GB 二进制文件,不可直读,仓库里也没有 oracle_dmp_表结构.sql 这个文件。权威表结构与索引只有一处来源:onesystem/analysis/ddl/create_table.txt 与 create_index.txt。注意 analysis/schema.json 只镜像了部分列(例:ESS_CONTRACT 的 DDL 有 136 列完整段 + 38 列在用子集两段),只看它会漏列。
04合并规范正文
本章是唯一实现依据。规则编号 FR-xx 用于需求追溯,R-xx 为边界规则,A-xx 为已定假设。每条都有唯一编号,评审意见请按编号提。
4.1 范围与目标
要做(Goals)
- 在【合同台账查询(分公司)】下方新增只读的【框架合同信息查询】页面
- 以「框架合同」为分组单位,成组展示其全部下属订单合同
- 7 个筛选条件,其中 2 个具备穿透能力
- 16 个展示字段,含 1 个查询期派生列
- 2 个权限点 + 公司级 / 部门级数据范围
- 支持搜索与导出,导出与列表同口径
不做(Non-Goals)
- 不改造既有合同台账查询(
/contract/contractSellQuery)的行为 - 不新增第二张合同表——框架与订单靠单表自关联表达
- 不实现通用行级数据权限引擎(切片期只留角色骨架)
- 不承担框架/订单字段的写入路径(属姊妹需求,本功能只读)
- 不处理采购侧框架合同——同名不同域,明确排除
- 不做老系统改造,不存在代码级复用
4.2 数据模型
单表 sales_contract,镜像老系统 ESS_Contract 的在用字段(新系统字段名翻译为规范英文、语义不变),并按需求新增 8 列。不拆表:老系统销售合同(含分公司)本就是同表按字段区分,拆表会破坏迁移路径与编号规则。
contractKind2 区分、靠 frameContractNo 建立父子关系的两类记录。| 新系统字段 | 需求列名 | 老系统列 | 类型 | 说明与出处 |
|---|---|---|---|---|
oid | — | OID | bigint PK | 主键,唯一索引 I100068 |
salesContractNo | 销售合同编号 | ECONOMICSLAWNUMBER | text | NVARCHAR2(100)。ContractSellVO.java:20 注释「销售合同编号(2023-12月之前叫:经法编号)」——即需求中的「经法号」。框架—订单关联键 |
internalContractNo | 内部合同编号 | CONTRACTID | text | NVARCHAR2(50),唯一索引 UNIQUE_CONTRACT_CONTRACTID。ContractSellVO.java:14「2023-12月之前叫:合同编号」 |
branchContractNo | 分公司合同编号 | BCCONTRACTID | text | NVARCHAR2(50)。ContractSellVO.java:171「分公司合同编码」 |
contractName | 合同名称 | NAME | text | — |
partyId / partyName | 合同甲方 | CONTRACTPARTYID → join ess_customorganization.name | bigint / text | 甲方名称来自关联组织,见列表 SQL :86-87,130 |
signedDepartment | 签订部门 | SIGNEDDEPARTMENT | bigint | 域 ESS_Contract_SignDept(ProjectConstant.java:311)。既是筛选下拉的数据对象,也是部门级权限的判定字段 |
executionDepartment | — | EXECUTIONDEPARTMENT | bigint | 域 ESS_Contract_ExecutionDept(:313)。本功能不作为权限判定依据(见 §4.7) |
signedDate | 签订日期 | SIGNEDDATE | date | 框架分组的排序键,可空 |
totalAmount | 合同金额/元 | TOTALAMOUNT | decimal(30,4) | 订单金额之和构成「已执行金额」 |
applyStatus | — | APPLYSTATUS | text | 域 ESS_Contract_ApplyStatus;yqr=已确认(ProjectConstant.java:73)。可见性过滤依据 |
contractType | — | TYPEC | text | 域 ESS_Contract_TypeC(:321)。保持不变,与新增的 contractKind2 并列 |
dataType | — | DATATYPE | text | import=导入(ProjectConstant.java:926) |
createTime | — | CREATETIME | datetime | — |
| 新系统字段 | 需求列名 | 类型 | 来源 | 约束与说明 |
|---|---|---|---|---|
contractKind2 | 合同类型2 | text | 新增 | 枚举 framework=框架合同 / order=订单合同。不得复用 CONTRACTTYPE(老库已有 NVARCHAR2(1),占用情况未知) |
frameContractNo | 其框架合同经法号 | text | 新增 | 订单行存其所属框架的 salesContractNo;框架行此字段为空。需建索引 |
frameEstimatedAmount | 框架预估金额/元 | decimal(30,4) | 新增 | 可空 |
framePeriodStart | 框架合同期限(起) | date | 新增 | 与下一列组成期限区间,是交集筛选的依据 |
framePeriodEnd | 框架合同期限(止) | date | 新增 | 若两者皆非空,须满足 start ≤ end(写入侧校验) |
estimateYear1 | 当年预估额/元 | decimal(30,4) | 新增 | 可空 |
estimateYear2 | 第二年预估额/元 | decimal(30,4) | 新增 | 可空 |
importDate | 导入日期 | date | 新增 | 语义=「该合同数据被导入系统的日期」,由导入动作写入。历史非导入数据留空(见 §2.2 裁决 4) |
executedAmount | 已执行金额/元 | decimal | 派生 | 不落库,查询期聚合。= 该框架下 applyStatus='yqr' 的订单 totalAmount 之和;无订单为 0。口径照抄老 SQL 同类子查询(ContractSellQueryMapper.xml:40-48,68) |
contractKind2 ∈ {framework, order};② 订单行的 frameContractNo 必填,且必须能匹配到一条框架合同的 salesContractNo;③ 框架行 frameContractNo 为空、framePeriodStart ≤ framePeriodEnd;④ 在「框架合同」范围内,salesContractNo 强制唯一,重复则报错、不静默入库。
4.3 筛选字段(7 个)
| 编号 | 筛选条件 | 交互 | 规则 | 穿透 |
|---|---|---|---|---|
FR-01 | 合同编号/名称 | 模糊输入 | 命中 4 列任一即算中:销售合同编号、内部合同编号、分公司合同编号、合同名称(老系统仅 3 列,须补 BCCONTRACTID) | 是 |
FR-02 | 合同类型2 | 下拉 | 选项:框架合同 / 订单合同。未选则不加过滤条件 | 否 |
FR-03 | 签订部门 | 下拉 | 覆盖 8 个事业部(含分公司)+ 营销中心。数据源=组织主数据,不复制老系统硬编码清单(ProjectConstant.java:78-96 的 SIGDEPT1–9) | 否 |
FR-04 | 签订日期 | 日期区间 | 按 signedDate 过滤,端点计入;单侧为空按开区间处理,不报错 | 否 |
FR-05 | 合同甲方 | 模糊输入 | 按甲方名称(关联组织名)模糊匹配 | 否 |
FR-06 | 框架合同期限 | 日期区间 | 交集语义:查询区间与框架期限区间相交即命中,含端点;任一端为空视为该端无限(不因缺值漏筛) | 是 |
FR-07 | 导入日期 | 日期区间 | 按 importDate 过滤;该列为空的记录被排除 | 否 |
FR-08 | 穿透规则:当使用 FR-01 或 FR-06 时,若命中的是订单合同,系统先上溯其框架合同,再带出该框架下全部订单合同,结果集 = 「框架合同 + 其下全部订单合同」 | — | ||
FR-09 | 除 FR-01/FR-06 外,其余筛选条件(FR-02–FR-05、FR-07)按实际匹配记录返回,不做整组展开 | — | ||
4.4 展示字段(16 个)
| # | 需求列名 | 来源 | 取值来源 | 实现要点 |
|---|---|---|---|---|
| 1 | 序号 | 前端 | 服务端计算(全局 seq) | FR-11:仅框架行显示;跨页全局连续(第 2 页接着第 1 页计数),订单行留空 |
| 2 | 合同类型2 | 新增 | contractKind2 | FR-12:展示为中文「框架合同」/「订单合同」,不暴露枚举键 |
| 3 | 销售合同编号 | 复用 | salesContractNo | FR-13:需求文档中的「经法号」即此列 |
| 4 | 内部合同编号 | 复用 | internalContractNo | FR-14:唯一索引列 |
| 5 | 分公司合同编号 | 复用 | branchContractNo | FR-15:同时是 FR-01 的模糊列之一 |
| 6 | 合同名称 | 复用 | contractName | FR-16 |
| 7 | 合同甲方 | 复用 | partyName(关联组织名) | FR-17 |
| 8 | 签订部门 | 复用 | signedDepartment + 域翻译 | FR-18:须做 domain 翻译后展示部门名(参照 ContractSellQueryServiceImpl.java:156-160) |
| 9 | 签订日期 | 复用 | signedDate | FR-19:同时也是框架分组的排序键 |
| 10 | 合同金额/元 | 复用 | totalAmount | FR-20:框架与订单两类行都展示自身金额 |
| 11 | 框架预估金额/元 | 新增 | frameEstimatedAmount | FR-21:订单行此列为空是正常状态,不要显示 0 |
| 12 | 已执行金额/元 | 派生 | 查询期聚合 | FR-22:仅框架行有值 = 其下 applyStatus='yqr' 的订单 totalAmount 之和;无已确认订单为 0;不受当前筛选影响(按该框架全量已确认订单汇总,见 R-04) |
| 13 | 当年预估额/元 | 新增 | estimateYear1 | FR-23 |
| 14 | 第二年预估额/元 | 新增 | estimateYear2 | FR-24 |
| 15 | 框架合同期限 | 新增 | framePeriodStart ~ framePeriodEnd | FR-25:以日期时间段形式展示(起止),不是两个独立列 |
| 16 | 导入日期 | 待确认 | importDate | FR-26:新增独立列(不复用创建时间)。历史数据留空 → 见确认项 C-03 |
金额格式统一:元、两位小数。新增列为空时展示为空,不显示 0 或占位符(否则会与真实 0 混淆)。
4.5 处理规则(4 条)与边界规则
| 编号 | 规则 | 内容 |
|---|---|---|
FR-27 | 列表排序 | 按框架合同的签订日期倒序;签订日期为空的框架排在最后;同一框架下的订单紧随其框架,不参与全局排序。须显式表达空值次序,不依赖数据库方言默认行为 |
FR-28 | 框架—订单关系 | 以订单行的「其框架合同经法号」(frameContractNo)作为父子关系依据,其值等于所属框架的销售合同编号 |
FR-29 | 行样式 | 框架合同行显示浅绿色;订单合同行正常显示 |
FR-30 | 导出 | 按当前筛选结果导出,字段与顺序与界面 16 列一致,导出为全量(不受分页与折叠限制) |
FR-31 | 搜索 | 按当前全部筛选条件(含穿透类)重新查询并刷新列表 |
| 编号 | 边界场景 | 处理 |
|---|---|---|
R-01 | 框架下无订单 | 仍展示该框架行(不产生空行),已执行金额显示 0 |
R-02 | 框架期限任一端为空 | 该端视为无限,不因缺值漏筛;两端皆空时在任何期限区间下均命中(需在验收中确认业务是否接受) |
R-03 | 期限与查询区间仅端点相接 | 判为命中(闭区间,含端点) |
R-04 | 已执行金额 vs 当前筛选 | 按该框架下全部已确认订单汇总,不受当前筛选导致的部分订单未展示所影响(保证同一框架的金额稳定可复核) |
R-05 | 孤儿订单(frameContractNo 为空或指向不存在的框架) | 三层处置:① 分组列表中不展示(无父分组,强行展示会破坏分组语义与分页计数);② 产生可观测告警日志供运维发现;③ 导入侧前置校验拦截,使其不产生。→ 见确认项 C-02 |
R-06 | 同一销售合同编号对应多条记录 | 查询侧去重兜底(按 oid),不静默放大重复行;写入侧对「框架合同」范围内的编号强制唯一,重复导入直接报错 |
R-07 | 单个框架下订单数极大 | 组内可折叠(默认收起,按需展开)并设单组行数告警阈值;导出始终为全量,不受折叠影响 |
R-08 | 筛选值为不存在的编号 | 返回空结果集,不报错 |
R-09 | 签订部门下拉出现不在主数据中的历史值 | 筛选与展示均不丢失、不报错(原值透传,不静默丢弃) |
R-10 | 区间起止颠倒(start > end) | 自动交换后执行,不报错(参照老系统 ContractSellQueryServiceImpl.java:92-96 对金额区间的处理) |
4.6 查询语义(实现难点集中在这里)
① 分组分页:分页单位是「框架合同」,不是「行」
这是本次合并中三路完全一致的关键决策,也是实现上最容易做错的一点:如果沿用老系统按行分页,会出现「框架行在第 1 页、它的订单行在第 2 页」的撕裂。
LIMIT/OFFSET),再按本页框架的编号批量取订单,服务端组装成组。实现要点(对应 FR-27 与 §2.1 第 2 项共识)
- 两段式查询:先
SELECT ... FROM sales_contract WHERE contract_kind2='framework' [筛选] ORDER BY ... LIMIT ? OFFSET ?;再按本页框架的salesContractNo批量取订单。 - 不能用单条分页 SQL 直接产出分组结果——老系统
CountableRowBounds按行分页的做法必须改。 - 空值排序显式化:
ORDER BY signed_date IS NULL, signed_date DESC。不要依赖方言默认:SQLite 里NULL恰好排最后,但 PostgreSQL 语义不同,显式写死才安全。 totalRows与totalGroups分开:前者只用于界面展示行数,后者才是分页计算依据。
② 穿透筛选:只有两个条件会「整组展开」
③ 排序与派生列
| 项 | 规则 |
|---|---|
| 组间排序 | 框架合同按 signedDate 倒序;signedDate 为空的组排在最后(FR-27 / R-02) |
| 组内排序 | 订单行紧随其框架行,不单独按自身签订日期参与全局排序 |
| 已执行金额 | 框架行 = 其下 applyStatus='yqr' 订单的 totalAmount 之和;无已确认订单为 0;订单行该列留空(FR-22 / R-04) |
| 口径可比性 | 金额聚合照抄老系统同类子查询风格(nvl(...,0) → COALESCE(...,0)),保证同一框架在两系统中的数字可比 |
4.7 权限与数据范围
| 编号 | 权限点 | 分配角色 | 可见范围 |
|---|---|---|---|
FR-32 | 框架合同信息查询-公司-查看 ESS_SALECONTRACT_FRAMECONTRACTQUERY_VIEW_COMPANY | 公司销售合同管理员 | 总、分公司全部框架合同及其订单,不做部门过滤 |
FR-33 | 框架合同信息查询-部门-查看 ESS_SALECONTRACT_FRAMECONTRACTQUERY_VIEW_DEPT | 事业部销售负责人(含分公司) | 仅 signedDepartment = 用户所属一级部门的框架合同及其下属订单 |
FR-34 | 两者皆无 | 拒绝访问,不返回任何数据(对应老系统 NULLPERMISSION 语义,ContractSellQueryController.java:107-110) | |
FR-35 | 角色—权限绑定 | 公司级 → 公司销售合同管理员;部门级 → 事业部销售负责人(含分公司)。角色定义在数据库中,仓库内未找到,须运维侧确认 | |
判定顺序
- 命中公司级权限 → 不加任何范围条件(全量)
- 否则命中部门级权限 → 加条件
signedDepartment = 用户一级部门(取当前用户一级部门,参照ContractSellQueryController.java:259-268) - 两者皆无 → 抛无权限错误
组级权限(FR-36,见 §2.2 裁决 2)
权限判定落到框架行:一旦某框架行命中本部门,其下全部订单一并展示,组内不裁剪。穿透筛选命中的他部门框架,按同一规则裁剪——即不展示。
切片期只落上述骨架与权限码映射,不实现通用行级数据权限引擎。
4.8 查询契约(接口语义)
只定义语义,不绑定传输细节。入参 10 个筛选 + 2 个分页;出参为分组结构。
| 参数 | 类型 | 必填 | 语义 | 对应 |
|---|---|---|---|---|
keyword | string | 否 | 编号/名称模糊,命中 4 列任一;触发穿透 | FR-01 |
contractKind2 | enum | 否 | framework / order | FR-02 |
signedDepartment | string(id) | 否 | 签订部门(组织主数据 ID) | FR-03 |
signedDateStart / signedDateEnd | date | 否 | 签订日期闭区间 | FR-04 |
partyName | string | 否 | 合同甲方模糊 | FR-05 |
framePeriodStart / framePeriodEnd | date | 否 | 框架期限查询区间;交集含端点,触发穿透 | FR-06 |
importDateStart / importDateEnd | date | 否 | 导入日期区间;空值记录被排除 | FR-07 |
page | int ≥ 1 | 否 | 页码,以框架合同组为单位 | §4.6 |
pageSize | int ≥ 1 | 否 | 每页组数 | §4.6 |
| 字段 | 语义 |
|---|---|
groups[] | 分组数组,每页 pageSize 组 |
groups[].seq | 序号,框架合同在结果集全局的顺序(跨页连续) |
groups[].framework | 框架合同行(含 16 个展示字段) |
groups[].orders[] | 该框架下全部订单合同行(可为空数组) |
totalGroups | 框架组总数——分页计算依据 |
totalRows | 展示总行数(框架行 + 订单行)——仅用于展示,不参与分页 |
page / pageSize | 回显 |
| 场景 | 行为 |
|---|---|
| 无任何查看权限 | 返回无权限错误,不返回数据 |
入参类型/区间非法(start > end) | 校验层拦截;区间颠倒时按老系统做法自动交换(R-10) |
| 无数据 | 返回空 groups、totalGroups = 0,不报错(R-08) |
| 存在孤儿订单 | 不进入分组结果;写入可观测告警日志(R-05) |
4.9 导出与搜索
导出(FR-30)
- 入参 = 列表的同一套筛选参数(不含
page/pageSize) - 复用同一套筛选 / 权限 / 排序 / 分组逻辑——不允许另写一套查询
- 输出列为界面 16 列,顺序一致
- 全量导出,不受分页与组内折叠影响
- 行区分(框架 / 订单)在导出中保留可读的分组表达
搜索(FR-31)
- 按当前全部筛选条件(含穿透类)重新查询并刷新列表
- 清空条件恢复默认视图(无筛选、第 1 页)
- 搜索须重置到第 1 页,避免页码越界
4.10 前端要求
| 项 | 要求 |
|---|---|
| 菜单入口 | 位置在现有【合同台账查询(分公司)】下方(FR-10 的功能位置要求) |
| 筛选区 | 7 个控件 + 「搜索」「导出」按钮;签订部门为下拉(组织主数据)、签订日期/框架期限/导入日期为区间控件 |
| 分组渲染 | 框架行(浅绿 + 序号)→ 其下订单行(常规、无序号、相对缩进),组与组之间有视觉间隔 |
| 序号列 | 仅框架行有值;跨页连续 |
| 超大组 | 默认折叠,按需展开(R-07) |
| 无权限态 | 明确的权限不足提示页,不显示空列表(避免被误读为「没有数据」) |
| 列宽与右侧对齐 | 金额列右对齐、千分位;日期列为空显示为空而非 null |
05任务拆分
三路各给了任务拆分(A:8 张 tracer 票据;B:7 组 24 项;C:7 阶段 44 项)。本章合并为一套:沿用 A 的垂直切片结构(每片可独立演示、独立验收),吸收 C 的文件落点,吸收 B 的「每项自带验证方式」。
| 编号 | 任务 | 落点 | 验证方式 |
|---|---|---|---|
| S1 · 前置:字段落库与写入路径 硬阻塞,无依赖,可立即开始 | |||
S1.1 | 数据模型新增 8 列(见 §4.2 表 4-2),均允许为空 | server/sales-contract/schema.ts | 执行迁移后逐列存在且类型正确 |
S1.2 | 枚举约束 contractKind2 ∈ {framework, order};contractKind2、frameContractNo 建索引 | schema.ts / constants.ts | 非法枚举写入被拒;按 frameContractNo 查询命中索引 |
S1.3 | 导入模板扩列 + 落库写入新字段;importDate 于导入动作写入 | 姊妹需求(导入模块) | 导入含框架信息的样例文件后,各列非空且 importDate = 导入当日 |
S1.4 | 写入侧唯一性校验:框架范围内 salesContractNo 重复则报错,不静默入库 | 姊妹需求(导入模块) | 重复编号导入被拒绝 |
S1.5 | 写入侧父子完整性校验:订单 frameContractNo 必须匹配到框架 | 姊妹需求(导入模块) | 悬空订单导入被拒绝(R-05 第 ③ 层) |
| S2 · 分组列表骨架(MVP) 依赖 S1 | |||
S2.1 | 页面路由 + 菜单入口(【合同台账查询(分公司)】下方) | app/(contract)/framework-contract-query/page.tsx | 菜单层级正确、点击可进入 |
S2.2 | 框架分页查询(page/pageSize 以组为单位) | server/sales-contract/queries.ts | 构造 2 组数据、pageSize=1,断言翻页后组完整 |
S2.3 | 按本页框架编号批量取订单 | queries.ts | 1 个框架 + 3 订单 → 3 条订单随框架同页返回 |
S2.4 | 组装分组:注入全局 seq、装配 orders、计算 totalGroups / totalRows | queries.ts | 序号跨页连续;totalGroups ≠ totalRows 时以组数为分页依据 |
S2.5 | 排序显式空值:ORDER BY signed_date IS NULL, signed_date DESC | queries.ts / rules.ts | 用例覆盖「倒序」「空值靠后」「订单不脱离框架」 |
S2.6 | 分组渲染组件:框架行浅绿 + 序号;订单行常规、无序号、缩进 | components/FrameworkGroupList.tsx | 页面断言框架行背景浅绿、订单行序号列为空 |
| S3 · 常规筛选(6 个非穿透) 依赖 S2 | |||
S3.1 | keyword 4 列模糊(补齐 branchContractNo) | queries.ts | 以分公司合同编号片段查询能命中 |
S3.2 | contractKind2 下拉筛选 | queries.ts / FilterForm.tsx | 选「订单合同」结果只含订单;未选不加条件 |
S3.3 | 签订部门下拉,数据源=组织主数据(不硬编码) | FilterForm.tsx | 下拉项与需求「8 事业部 + 营销中心」逐项一致 |
S3.4 | 签订日期区间筛选(端点计入,单侧为空按开区间) | queries.ts | 区间内命中、边界当天命中 |
S3.5 | 合同甲方模糊筛选 | queries.ts | 甲方名称片段命中 |
S3.6 | 导入日期区间筛选(空值记录被排除) | queries.ts | 历史无 importDate 的记录不出现 |
S3.7 | 搜索按钮 / 条件清空(重置到第 1 页) | FilterForm.tsx | 多条件搜索结果与直接调用查询一致 |
| S4 · 框架期限交集筛选 依赖 S2 | |||
S4.1 | 交集判定(含端点):框架起 ≤ 查询止 AND 框架止 ≥ 查询起 | rules.ts / queries.ts | 用例:完全包含 / 部分重叠 / 仅端点相接 / 完全不相交 |
S4.2 | 空端处理:任一端为空视为该端无限 | rules.ts | 单端为空的框架在该条件下不漏筛(R-02) |
| S5 · 穿透筛选 依赖 S3 + S4 | |||
S5.1 | keyword 命中订单 → 上溯框架 → 带出该框架全部订单 | queries.ts | 按某订单编号查询,结果 = 其框架 + 该框架全部订单 |
S5.2 | 期限交集命中 → 带出该框架全部订单 | queries.ts | 按区间查询,命中框架及其全部订单同组返回 |
S5.3 | 非穿透条件不做整组展开 | queries.ts | 仅用「合同甲方」筛选,结果为实际匹配、不展开 |
S5.4 | 同编号多记录时去重兜底(按 oid),不放大重复行 | queries.ts | 构造重复编号数据,断言组数与行数不翻倍(R-06) |
| S6 · 完整展示字段与派生列 依赖 S2 | |||
S6.1 | 16 列全部映射,含签订部门的 domain 翻译 | queries.ts / dto.ts | 样例记录逐列取值与来源一致 |
S6.2 | executedAmount 派生聚合(仅 yqr 订单;无订单为 0) | queries.ts | 框架 3 订单(2 确认 1 未确认)→ 金额 = 2 条之和;无订单 → 0 |
S6.3 | 新增列为空时展示为空(不显示 0 或占位符) | dto.ts / 组件 | 空值渲染为空字符串 |
S6.4 | 订单行 executedAmount 留空 | 组件 | 订单行该格为空 |
S6.5 | 金额格式统一(元、两位小数、右对齐、千分位) | 组件 | 视觉核对 |
| S7 · 权限与数据范围 依赖 S2 | |||
S7.1 | 两个权限码常量 | constants.ts | 常量存在且被判定逻辑引用 |
S7.2 | 判定:公司级全量 / 部门级 signedDepartment = 一级部门 / 皆无拒绝 | permissions.ts | 三类账号用例:全量 / 子集 / 拒绝 |
S7.3 | 组级权限:框架命中则其全部订单可见(组内不裁剪) | queries.ts | 框架属 A 部门、其订单属 B 部门 → 订单仍可见 |
S7.4 | 穿透与权限叠加:命中他部门框架 → 按同一规则裁剪,不展示 | queries.ts | 部门级用户以他部门订单编号穿透,结果为空 |
S7.5 | 角色骨架映射 + 无权限拒绝态页面 | permissions.ts / components/NoPermission.tsx | 权限不足显示明确提示,不显示空列表 |
| S8 · 导出 依赖 S5 + S6 + S7 | |||
S8.1 | 导出服务复用同一筛选 / 权限 / 排序 / 分组逻辑,全量输出 | server/sales-contract/export.ts | 导出内容与列表逐行一致 |
S8.2 | 导出列 = 16 展示字段,且顺序一致 | export.ts | 列头比对 |
S8.3 | 导出受同一数据范围约束 | export.ts | 部门级用户导出不含越权数据 |
| 跨切面(不阻塞主链路) | |||
X1 | 端到端验收 V1–V11(见第 6 章) | tests/e2e/framework-query.spec.ts | 全部通过 |
X2 | 与老系统口径比对:合同金额 / 已执行金额 | 验证记录 | 同等筛选下两系统数字一致 |
X3 | 性能:常规数据量下首屏 < 3 秒 | 验证记录 | 实测 |
X4 | 术语与 ADR 提案输出(CONTEXT.md / docs/adr 只读,以提案形式交付) | 提案文件 | 可直接粘贴合入 |
X5 | 孤儿订单可观测告警日志(配合 R-05) | queries.ts | 存在孤儿数据时产生告警 |
queries.ts 是热点文件——多个任务改同一文件时串行提交,避免写冲突。
06验收标准
先备好这份最小数据集,再逐条走 V1–V11。这套数据专门覆盖了空值排序、未确认订单、无订单框架、跨部门四个边界。
| 编号 | 类型 | 签订部门 | 签订日期 | 审核状态 | 框架期限 | 说明 |
|---|---|---|---|---|---|---|
F1 | 框架合同 | A 事业部 | 2026-03-01 | 已确认 | 2026-01-01 ~ 2026-12-31 | 下挂 O1、O2 |
F2 | 框架合同 | A 事业部 | (空) | 已确认 | 2025-06-01 ~ 2026-06-30 | 下挂 O3;用于空值排序与交集筛选 |
F3 | 框架合同 | B 事业部 | 2026-02-01 | 已确认 | 2026-05-01 ~ 2027-04-30 | 无订单;用于「无订单按 0」与部门权限 |
O1 | 订单合同 | A 事业部 | 2026-03-10 | 已确认 | — | frameContractNo = F1 编号;金额 100 |
O2 | 订单合同 | A 事业部 | 2026-04-01 | 未确认 | — | 金额 200 —— 不应计入 F1 已执行金额 |
O3 | 订单合同 | A 事业部 | 2025-06-15 | 已确认 | — | frameContractNo = F2 编号;金额 50 |
| # | 操作 | 期望结果 | 覆盖 |
|---|---|---|---|
| V1 | 公司级账号进入页面,不做筛选 | 出现 F1 / F2 / F3 三个分组;F1 下 O1、O2,F2 下 O3,F3 无订单;框架行浅绿 + 序号,订单行无序号 | FR-10~12, FR-29 |
| V2 | 观察排序 | F1(2026-03-01)→ F3(2026-02-01)→ F2(空,排最后) | FR-27, R-02 |
| V3 | 「合同编号/名称」输入 O1 的编号 | 结果 = F1 框架 + F1 下全部订单(O1、O2) | FR-08 |
| V4 | 「框架合同期限」选 2026-06-01 ~ 2026-06-10 | 命中 F2(与 F2 期限有交集)并带出 O3 | FR-06, FR-08 |
| V5 | 仅用「合同甲方」筛选 | 结果为实际匹配记录,不展开为框架 + 全部订单 | FR-09 |
| V6 | 查看 F1 的「已执行金额」 | = 100(仅计 O1;O2 未确认,不计入) | FR-22, R-04 |
| V7 | 查看 F3 的「已执行金额」 | = 0(无订单) | FR-22, R-01 |
| V8 | 部门级账号(A 事业部)查询 | 只见 F1、F2 及其订单,看不到 F3(B 事业部) | FR-33, FR-36 |
| V9 | 无权限账号访问 | 被拒绝(无权限错误),不显示空列表 | FR-34 |
| V10 | 导出当前查询结果 | 文件列 = 界面 16 列且顺序一致;行数 = 查询结果行数 | FR-30 |
| V11 | 「合同编号/名称」输入不存在的值 | 空结果,不报错 | R-08 |
| 编号 | 标准 |
|---|---|
SC-01 | 16 个展示字段 100% 可见,且逐字段与需求文字对应 |
SC-02 | 以任一订单编号做编号/名称筛选,结果 100% 包含其所属框架及该框架下全部订单(穿透正确率 100%) |
SC-03 | 以非穿透条件筛选时,穿透展开率 0 |
SC-04 | 抽查 20 组,排序 100% 满足「按签订日期倒序 + 空值在末尾」,且框架行与其订单行同组、框架行在前 |
SC-05 | 部门级用户结果中,签订部门非本部门的框架行数为 0(越权框架 0 条) |
SC-06 | 抽查若干框架,「已执行金额」100% 等于其下「已确认」订单金额之和(与手工汇总偏差 0) |
SC-07 | 导出列与界面 16 列一致,行数等于当前查询结果行数 |
SC-08 | 常规数据量下,查询首屏 3 秒内呈现 |
SC-09)。这既是数字正确性问题,也是迁移期的对账基础。
07待业务确认清单
这 4 项是不规范阻塞开发的口径类问题:开发按本文裁决先行,业务确认后如需调整,改动点都已定位到具体位置。建议由营销中心与分公司一并确认。本章可直接打印签字。
| 编号 | 议题 | 本文裁决(开发按此实现) | 若业务选择另一方案的代价 | 业务确认 |
|---|---|---|---|---|
C-01 |
部门级权限按哪个字段判定 | 只认「签订部门」(照需求字面) | 若需包含「执行部门」:权限条件加一个 OR,改动局部;但可见范围会变大 |
|
C-02 |
框架属本部门、订单属他部门时的可见性 | 组级权限:框架可见 → 其全部订单可见 | 若改为行级过滤:需重新定义分组呈现与分页语义(框架下订单会静默变少) | |
C-03 |
「导入日期」历史数据是否回填创建时间 | 不回填,历史数据留空;按导入日期筛选时被排除 | 若回填:手工录入的合同会被误判为「被导入过」,从第一天起污染该筛选口径 | |
C-04 |
孤儿订单(指向的框架不存在)呈现方式 | 不展示 + 告警日志 + 导入侧拦截 | 若需界面可见:在列表末尾追加「未归属订单」伪分组,需同步调整分页与序号口径 |
| 编号 | 缺失项 | 为什么必须问 |
|---|---|---|
C-05 | 《1.销售合同管理-分公司合同信息导入》需求文档 | 本次输入未提供。决定新字段由谁写入、导入模板扩列清单,是 S1 切片的输入。三路一致标注为最大缺口 |
C-06 | 角色「公司销售合同管理员」「事业部销售负责人」的定义 | 角色配置在数据库,源码中未找到。需运维侧确认角色存在并完成权限绑定 |
C-07 | 菜单【合同台账查询(分公司)】的实际落点 | 该菜单名在仓库中未找到(菜单在数据库)。需确认新功能的挂点与既有功能的关系 |
C-08 | 框架期限两端皆空时的筛选行为 | 本文按「两端皆空 = 无限」处理(R-02),意味着它在任何期限区间下都会命中。需确认业务是否接受 |
C-09 | 企业数据量级与单框架最大订单数 | 决定 R-07 折叠阈值与分页大小,以及是否需要预聚合优化 |
08工程约束
以下 5 条是从 onesystem/docs/adr/0001–0004 与 CONTEXT.md 提炼的项目级原则。它们不是本功能的专属规定,但本功能必须遵守;与之冲突的设计须在评审中论证。
| # | 原则 | 对本功能的具体约束 |
|---|---|---|
| I | 镜像老系统领域模型与编号规则 不可协商 | 数据模型原样镜像 ESS_Contract 在用字段,字段名翻译为规范英文但语义不变 → 直接决定了「单表自关联、不拆两张表」这条设计 |
| II | 显式状态机,禁止工作流引擎 | 本功能为纯查询、无审批流;只读引用既有合同审核状态枚举(applyStatus),不引入任何流程引擎 |
| III | TypeScript 全栈 + ORM;切片 SQLite、生产 PostgreSQL | Next.js + Drizzle + Zod;Vitest + Playwright。禁止书写 SQLite 特有方言的裸 SQL → 直接决定了空值排序必须显式写成 ORDER BY signed_date IS NULL, signed_date DESC,而不是依赖方言默认 |
| IV | 证据可追溯 + 只读事实源 | 一切技术断言须带 文件:行号;onesystem/ 只读,app/** 不得作为事实来源。本文档第 3 章即是该原则的产物 |
| V | 简单优先(YAGNI),但合规/金额/权限边界必须显式 | 不实现通用行级数据权限引擎、不做多表拆分、不做超出需求的抽象;但权限边界、金额口径(已执行金额)、审批相关规则必须显式建模并可测试 |
技术落点(建议,须与仓库现有布局对齐)
onesystem/app/
├── src/server/sales-contract/
│ ├── schema.ts 表定义(镜像 ESS_Contract 在用字段 + 8 个新增列)
│ ├── constants.ts contractKind2 枚举、applyStatus、2 个权限码
│ ├── queries.ts 分组查询(框架分页 + 穿透 + 派生列)+ 导出查询
│ ├── rules.ts 期限交集、空值排序、区间交换
│ ├── permissions.ts 公司级 / 部门级判定
│ ├── export.ts 导出(列与展示字段对齐)
│ └── dto.ts Zod:查询入参 / 行出参
└── src/app/(contract)/framework-contract-query/
├── page.tsx 查询页(筛选区 + 分组列表 + 导出)
└── components/ FilterForm · FrameworkGroupList · ExportButton · NoPermission
tests/{unit,integration,e2e}/ Vitest 覆盖查询与规则;Playwright 覆盖 V1–V11
09附录
9.1 术语表(建议合入 CONTEXT.md 新增「合同侧」区)
| 词条 | 释义 | 禁用 / 易混(_Avoid_) |
|---|---|---|
| 销售框架合同 | 销售合同中的一类,代表在某一期限内对某客户的框架性约定,其下可挂多个订单合同。判定依据是「合同类型2 = 框架合同」 | 框架合同(歧义:采购侧已有同名的「采购框架合同」,属不同域);大合同 |
| 销售订单合同 | 挂在某个销售框架合同下的具体执行合同,通过「其框架合同经法号」指向所属框架 | 子合同;订单(易与采购/投标语境混用) |
| 销售合同编号 | 框架↔订单自关联所用的业务编号;需求文档里历史称谓「经法号」在 2023-12 更名后的现行叫法。该编号在数据中无唯一性保证,作关联键时须辅以写入侧校验与查询侧去重 | 经法号 / 经法编号(历史名,仅见于老数据与老文案);经济学法编号 |
| 其框架合同经法号 | 需求文档中描述「订单合同指向其框架合同」的字段说法,其取值即所属框架的销售合同编号。新实现统一用「销售合同编号」表达 | 框架编号(易与采购 FRAMENUMBER 混) |
| 合同类型2 | 合同在「框架—订单」两层结构中的角色枚举:框架合同 / 订单合同。与既有「合同类型(C)」是不同概念,不得混用或复用同一列 | 合同类型(会与既有 C 型混淆) |
| 框架合同期限 | 框架合同的有效起止时间区间(起、止两个日期) | 质保周期(老系统 QUALITYSTARTTIME / QUALITYENDTIME 是质保起止,不是框架期限) |
另有两条值得留档的架构决策(A 路提案,满足「难逆 / 非直觉 / 真取舍」):ADR 0005「销售框架与订单的关联键用销售合同编号,并加写入侧唯一性前置校验」;ADR 0006「框架合同查询以框架合同为分页单位」。理由是这两条看起来「本该用唯一列」「本该按行分页」,后来者容易改回去。
9.2 三路产物索引(可追溯合并来源)
| 来源 | 产物 |
|---|---|
| A · mattpocock/skills | sdd-eval/A-mattpocock-skills/.scratch/sales-contract-framework-query/:research.md(证据链)· grilling.md(16 问决策台账)· spec.md(30 user story / 7 段模板)· context-proposal.md(术语与 ADR 提案)· issues/01–08-*.md(8 张 tracer 票据)· PROCESS.md |
| B · OpenSpec | sdd-eval/B-openspec/openspec/changes/sales-contract-framework-query/:proposal.md · specs/sales-contract/framework-contract-query/spec.md(26 Requirement / 48 Scenario,validate --strict 通过)· design.md(9 个未定义点 + 决策 D-1~D-8)· tasks.md · PROCESS.md |
| C · spec-kit | sdd-eval/C-spec-kit/:.specify/memory/constitution.md(5 条原则)· specs/001-sales-contract-framework-query/:spec.md(35 FR / 8 SC)· clarifications.md(16 个未定义点)· plan.md · research.md · data-model.md · contracts/query-contract.md · quickstart.md · tasks.md(44 任务)· analysis-report.md · PROCESS.md |
| 评测总报告 | sdd-eval/README.md:三路覆盖矩阵、8 维度评分、隔离与红线核查记录 |
9.3 证据索引(关键结论 → 一手来源)
| 结论 | 一手来源 |
|---|---|
| 台账查询入口与权限判定 | old-src/contract/src/main/java/r1/projectmanage/controller/ContractSellQueryController.java:51,57,92-117,259-268 |
| 列表 SQL(过滤 / 模糊 / 部门 / 排序 / 派生) | .../mapper/ContractSellQueryMapper.xml:7-206(:124 过滤、:126 模糊、:144-146 部门、:205 排序) |
| VO 字段与术语演进 | .../model/ContractSellVO.java:8,14,20,38,111,162,171 |
| 权限码常量 | .../model/ProjectConstant.java:73,311,313,321,494,496;ContractBackToArticleConrtoller.java:58 |
| 事业部硬编码 9 项 | .../model/ProjectConstant.java:78-96 |
| 导入模板与落库 | .../service/impl/ContractSellImportServiceImpl.java:64,87-101,864,1536;.../mapper/ContractSellMapper.xml:214-275 |
| 导出表头 54 列 | .../service/impl/ContractSellQueryServiceImpl.java:68-79 |
ESS_CONTRACT 完整 136 列与全库 FRAME 列分布 | onesystem/analysis/ddl/create_table.txt(由 old-src/oracle.dmp 解析) |
唯一索引仅 OID、CONTRACTID | onesystem/analysis/ddl/create_index.txt |
| 新系统架构决策 | onesystem/docs/adr/0001–0004;onesystem/CONTEXT.md |
10评审者检查清单
评审会按此清单逐项过。任何一项不通过,请指出对应编号(FR-xx / R-xx / C-xx / Sx.x),便于直接定位改动。
开发 / 技术评审
- 数据模型是否严格镜像老系统在用字段?8 个新增列的类型与约束是否认可?
- 分组分页的两段式实现是否清晰?(
FR-27、§4.6 ①)这是最容易做错的点 - 空值排序的显式写法是否被接受?(
ORDER BY signed_date IS NULL, signed_date DESC) - 穿透的两段式(上溯 + 下钻)是否有性能隐患?
R-06去重兜底是否够? - 已执行金额「不受当前筛选影响」(
R-04)是否与产品预期一致? R-05孤儿订单的三层处置是否认可?告警日志的落点在哪?- 导出复用同一查询逻辑(
FR-30)在现有代码结构下是否可行? - 任务切片的依赖图(图 6)与并行建议是否符合团队实际?
业务 / 产品评审
C-01部门级权限字段:只认签订部门,是否与业务预期一致?C-02组级权限:部门级用户可能看到他部门的订单行,是否接受?C-03导入日期历史数据留空,导致按导入日期筛选查不到历史合同,是否接受?C-04孤儿订单不在界面展示,是否接受?C-05姊妹需求《分公司合同信息导入》文档何时能提供?(这是唯一硬阻塞)C-08框架期限两端皆空时在任何区间下都命中,是否符合业务口径?- 验收种子数据(表 6-1)与 V1–V11 是否覆盖了真实业务场景?