diff --git a/docs/enum_refactor/construction.md b/docs/enum_refactor/construction.md index 0d0f075..d20f0dd 100644 --- a/docs/enum_refactor/construction.md +++ b/docs/enum_refactor/construction.md @@ -183,4 +183,76 @@ construction 领域后续梳理时,至少要回答下面这些问题: 4. 哪些逻辑仍在使用硬编码白名单。 5. `construction_node_config` 中哪些数据只是节点补充信息,哪些其实已经在承担字段候选项职责。 -如果发现某字段既参与初始化又参与手动创建,就应优先考虑迁到字段级配置。 \ No newline at end of file +如果发现某字段既参与初始化又参与手动创建,就应优先考虑迁到字段级配置。 + +## 9. 按本轮要求的改造落点 + +construction 领域这轮不是泛泛地“改配置”,而是明确围绕 3 条链路收口: + +- 初始化 +- create 校验 +- 下拉列表 + +### 9.1 初始化 + +当前施工初始化本来就不是 dict 驱动,而是: + +- `construction_section_config` +- `ConstructionTypeTree` +- `ConstructionTypeMapping` + +因此本轮初始化改造的重点不是“把 dict 搬到 config”,而是: + +- 把 `section_type_code` 的候选项读取统一收口到新的 config 工具 +- 保留类型树和 root node 等结构化数据,但不再让 section 候选项散落在私有 helper 里 + +### 9.2 Create 校验 + +当前 `ConstructionService.create()` / `create_for_scdw()` 已经在 service 开头做了较强校验: + +- `section_type_code` 来自 `construction_section_config` +- `section_sub_type_code` 来自 section config 和类型树映射 +- `type_id` 来自施工类型树或 SCDW 专用配置 + +本轮目标是: + +- `section_type_code` 统一改成通过 `ConfigService.validate_field_value(project_type, "construction.section_type_code", value)` 校验 +- `section_sub_type_code`、`type_id` 继续走配置/类型树校验,不做降级 + +### 9.3 下拉列表 + +当前施工下拉接口已经基本同源: + +- `/construction/section/type/list` +- `/construction/section/sub/type/list` +- `/construction/task/type/list` + +它们当前都来自 `construction_section_config + 类型树`。 + +本轮目标不是重做接口,而是: + +- 让 section 级选项由新的 config 工具统一返回 +- 保证下拉、初始化、create 校验继续同源 + +### 9.4 清理项 + +construction 领域这轮优先清理的是“section 候选项读取分散”问题,不是一次性重写全部施工结构。 + +完成后应清理: + +- 只负责 section 候选项翻译的旧 helper 直接解析逻辑 +- 不再适合作为 section 真源的旧硬编码映射 + +但下面这些不属于本轮删除目标: + +- `ConstructionTypeTree` +- `ConstructionTypeMapping` +- 与节点树生成强绑定的结构化配置 + +## 10. 测试关注点 + +construction 领域 service 层集成测试至少覆盖: + +1. 初始化:项目初始化后生成的 section / root node / 主施工任务是否来自目标 config。 +2. 下拉:section / sub type / task type 列表是否和初始化使用同源配置。 +3. Create:合法 `section_type_code`、`section_sub_type_code`、`type_id` 可以创建,非法值明确报错。 diff --git a/docs/enum_refactor/construction_task_detail.md b/docs/enum_refactor/construction_task_detail.md deleted file mode 100644 index 5a5b7a6..0000000 --- a/docs/enum_refactor/construction_task_detail.md +++ /dev/null @@ -1,89 +0,0 @@ -# Construction Task Detail 领域重构指南 - -## 1. 范围 - -本文档对应 backend 的 `construction_detail` 明细表。 - -当前优先关注: - -- `construction_detail.status` -- `construction_detail.approval_status` -- `construction_detail.is_complete` - -## 2. 字段盘点 - -### 2.1 `status` - -当前实现: - -- 模型里是 `SmallInteger` -- 代码注释已经说明该字段废弃 -- 同步 schema 里仍有 `Optional[int] status` - -项目侧取值范围: - -- 当前无法列出稳定正式范围 -- 只能确认它仍是 int 状态字段,且与进度状态相关 - -集团侧差异: - -- 当前没看到集团侧 `enum_config` 为该明细领域单独列值 -- 同步 schema 仍保留该字段,说明推送侧还没有完全去掉它 - -结论: - -- 应优先决定是否彻底下线该字段 - -### 2.2 `approval_status` - -当前实现: - -- backend 依赖 `ApprovalMixin`,数据库侧是审批状态数值字段 -- 同步 schema `construction_detail.py` 用的是 `ApprovalStatus` enum - -项目侧取值范围: - -- 当前项目侧没有单独的 construction_detail 审批 enum 文件 -- 实际范围跟随通用审批体系,不在 construction 模块内独立定义 - -集团侧差异: - -- 同步 schema 已经收紧成 `ApprovalStatus` -- 项目侧 construction_detail 自身还没有独立、清晰的 enum 入口 - -结论: - -- 应统一接入通用审批状态定义 - -### 2.3 `is_complete` - -当前实现: - -- 模型里是 `Boolean` -- backend 和 sync schema 都存在 - -项目侧取值范围: - -- `true` -- `false` - -集团侧差异: - -- 当前没有明显差异 - -结论: - -- 保持布尔字段即可 - -## 3. 重点结论 - -- `construction_detail.status` 现在的核心问题不是值域不清,而是字段本身是否还应继续存在。 -- `approval_status` 应直接并到通用审批体系,不要在 construction 模块单独再造一套。 -- `is_complete` 语义明确,不需要做枚举化改造。 - -## 4. 待修改项清单 - -1. 决定 `construction_detail.status` 是否正式废弃。 -2. 如果不废弃,补上正式状态定义源。 -3. 将 `approval_status` 明确统一到通用审批 enum 体系。 -4. 清理明细层里“旧 status”和“审批状态”并存的混乱状态。 diff --git a/docs/enum_refactor/contract.md b/docs/enum_refactor/contract.md index 6b94687..96308db 100644 --- a/docs/enum_refactor/contract.md +++ b/docs/enum_refactor/contract.md @@ -114,3 +114,79 @@ 4. 把 `contract.code` 从“自由字符串”改成统一配置来源。 5. 评估 `contract.code` 是否直接迁到 `field_config`。 6. 区分清楚 `contract.code` 与“合同编号/contract_sn”的职责,避免一个字段同时承担两层语义。 + +## 5. 按本轮要求的改造落点 + +contract 领域本轮要直接收口 3 条链路: + +- 初始化 +- create 校验 +- 下拉列表 + +### 5.1 初始化 + +当前合同初始化已经走 `contract_config`。 + +因此这轮初始化改造的核心不是重做合同初始化,而是: + +- 把 `contract.code` 明确成正式字段级配置来源 +- 后续让合同初始化、物资初始化、前端下拉都围绕这套配置工作 + +`contract_type` 继续保留为代码 enum,不迁到 `field_config`。 + +### 5.2 Create 校验 + +当前 `ContractService.create()` 的校验主要依赖: + +- `contarct_type_dict` +- `contract_code_dict` +- service / product 的硬编码白名单 + +这和初始化使用的 `contract_config` 不是同一真源。 + +本轮目标是: + +- `contract_type` 继续按代码 enum 校验 +- `contract.code` 改成通过新的 config 工具校验 +- 避免继续新增 `if contract_type == ... and code in [...]` 这种硬编码白名单 + +### 5.3 下拉列表 + +当前合同下拉接口: + +- `/contract/type/list` +- `/contract/sub-type/list` + +其中: + +- `type/list` 直接读代码字典 +- `sub-type/list` 先写死过滤列表,再读代码字典 + +这和初始化 `contract_config` 不一致。 + +本轮目标是: + +- 合同类型列表继续由代码 enum 提供 +- 合同子类型列表改为动态读取 config +- 保证前端看到的 code 列表和初始化、create 校验同源 + +### 5.4 清理项 + +本轮 contract 领域改造完成后,应清理: + +- 只用于 `contract.code` 下拉和校验的旧硬编码 code 白名单 +- 只承担旧合同子类型翻译的代码字典入口 + +但下面这些不应误删: + +- `ContractType` enum +- 其它真正稳定的固定语义 enum + +## 6. 测试关注点 + +contract 领域 service 层集成测试至少覆盖: + +1. 初始化:`contract_config` 能正确初始化合同,并继续驱动下游结算/物资链路。 +2. 下拉:合同子类型下拉是否来自目标 config,且与初始化 code 集一致。 +3. Create:合法 `contract_type + code` 创建成功,非法 code 明确报错。 + diff --git a/docs/enum_refactor/contract_settlement.md b/docs/enum_refactor/contract_settlement.md index 4fae941..8a1ab58 100644 --- a/docs/enum_refactor/contract_settlement.md +++ b/docs/enum_refactor/contract_settlement.md @@ -95,3 +95,55 @@ 2. 为 `status` 补正式常量或 enum 定义。 3. 将 `payment_method` 的合法值校验统一沉到正式 schema/enum,而不是只在个别 schema 手动校验。 4. 明确结算主表与合同主表在 `contract_type` 上是否必须同源。 + +## 5. 按本轮要求的改造落点 + +contract_settlement 领域不属于项目初始化第一批重点,但它直接涉及“写死下拉改动态”这件事。 + +### 5.1 初始化 + +本领域当前没有独立的项目初始化候选项链路。 + +`contract_type` 主要跟随合同主表,`payment_method` 主要出现在结算业务选择和后续分支处理中。 + +### 5.2 Create / 业务校验 + +当前 `payment_method` 的合法值主要靠: + +- `PaymentMethod` enum +- `SettlePaymentMethodsSelect` schema 里的手动 validator + +而 service `payment_methods_select()` 本身没有再做额外校验。 + +本轮目标是: + +- 保留 `payment_method` 的固定语义 enum 属性 +- 但在业务层入口也要能通过统一 config 工具或统一 enum 工具做显式校验 +- 不把校验只留在 schema 里 + +### 5.3 下拉列表 + +当前支付方式只有选择接口,没有正式的动态下拉接口。 + +因此这轮要补的不是“替换已有动态接口”,而是: + +- 把现有写死支付方式候选项明确收口 +- 如果前端需要单独下拉接口,应改成动态返回,而不是继续依赖前端或 schema 写死 + +同理,`payment_type`、`advance_type` 这类当前靠本地 enum 固定的候选项,也应按同样思路处理。 + +### 5.4 清理项 + +本轮 contract_settlement 领域完成后,应避免继续保留: + +- 只存在于 schema validator、但没有统一出口的支付方式候选项定义 +- 前端和后端各自写死一份支付方式列表 + +## 6. 测试关注点 + +contract_settlement 领域 service 层集成测试至少覆盖: + +1. `payment_method` 合法值更新成功,非法值明确报错。 +2. 如果补了下拉接口,下拉返回是否与 service 校验同源。 +3. `contract_type` 跟随合同主表时,不出现与合同主表不一致的脏数据。 + diff --git a/docs/enum_refactor/contract_settlement_detail.md b/docs/enum_refactor/contract_settlement_detail.md deleted file mode 100644 index e13c5e8..0000000 --- a/docs/enum_refactor/contract_settlement_detail.md +++ /dev/null @@ -1,97 +0,0 @@ -# Contract Settlement Detail 领域重构指南 - -## 1. 范围 - -本文档对应 backend 的 `contract_settlement_detail` 明细表。 - -当前优先关注: - -- `contract_settlement_detail.payment_type` -- `contract_settlement_detail.advance_type` -- `contract_settlement_detail.approval_status` - -## 2. 字段盘点 - -### 2.1 `payment_type` - -当前实现: - -- 本地 `PaymentType` enum 定义在 `app/modules/contract/enums.py` -- 取值:`advance`、`progress`、`settlement` -- 模型里是 `String` -- `PostDetailData` schema 里已使用 `PaymentType | None` - -当前使用: - -- 结算服务会按 `payment_type` 分支处理,例如预付款与进度款逻辑不同 - -项目侧取值范围: - -- `advance` -- `progress` -- `settlement` - -集团侧差异: - -- 当前没看到集团侧 `enum_config` 为结算明细单独列出该字段 -- 当前仓库也没有 standalone 的同步 schema 对应 `contract_settlement_detail` - -结论: - -- 保留为代码 enum -- 这是少数已经在 schema 层显式使用 enum 的字段,应该延续这种做法 - -### 2.2 `advance_type` - -当前实现: - -- 本地 `AdvanceType` enum 取值:`project`、`product`、`service` -- 模型里是 `String` -- `PostDetailData` schema 里已使用 `AdvanceType | None` - -项目侧取值范围: - -- `project` -- `product` -- `service` - -集团侧差异: - -- 当前没看到集团侧 `enum_config` 为该字段给出正式候选项 -- 当前仓库也没有 standalone 的同步 schema 对照该字段 - -结论: - -- 保留为代码 enum - -### 2.3 `approval_status` - -当前实现: - -- 模型依赖 `ApprovalMixin` -- 结算明细服务强依赖审批流 - -项目侧取值范围: - -- 当前跟随通用审批状态体系 -- 本领域自身没有单独 enum 文件 - -集团侧差异: - -- 当前没看到 standalone 的同步 schema 对这个明细领域建模 - -结论: - -- 接入通用审批定义即可 - -## 3. 重点结论 - -- `payment_type`、`advance_type` 已经是本地相对成熟的代码 enum,用法比结算主表更收敛。 -- 这一层不需要优先迁 `field_config`,重点是把模型层和 schema 层收口一致。 -- `approval_status` 继续走通用审批定义即可。 - -## 4. 待修改项清单 - -1. 将 `payment_type`、`advance_type` 保持为正式 enum,不要退回自由字符串。 -2. 评估模型层是否也要收紧,而不只是 schema 层使用 enum。 -3. 明确结算明细审批状态与通用审批体系的正式映射。 diff --git a/docs/enum_refactor/field_config_draft.md b/docs/enum_refactor/field_config_draft.md index 4b3be32..98bf1e7 100644 --- a/docs/enum_refactor/field_config_draft.md +++ b/docs/enum_refactor/field_config_draft.md @@ -23,42 +23,31 @@ | project | `project_type` | 代码 enum + 初始化字典 | 保留代码 enum | 核心路由字段 | | project | `project_subtype` | 自由字符串 | 迁到 `field_config` | 建议 key:`project.project_subtype` | | project | `syxs` | 自由字符串 | 迁到 `field_config` | 建议 key:`project.syxs` | -| project | `status` | 代码 enum + 服务推导 | 暂缓收口 | 先确认是否保留 `early` | +| project | `status` | 代码 enum + 服务推导 | 保留代码 enum | 正式集合含 `early`,并清理历史 `NULL` | | project | `progress_type` | 代码 enum + DB enum | 保留代码 enum | 已与 sync schema 对齐 | -| project | `key_constraints` | 代码 enum 分叉 + DB enum | 暂缓收口 | 先统一 `OTHER/QT` 差异 | +| project | `key_constraints` | 代码 enum 分叉 + DB enum | 保留代码 enum | 项目侧保留 `OTHER`,备注集团侧使用 `QT` | | contract | `contract_type` | 代码 enum | 保留代码 enum | 先确认是否保留 `project` | | contract | `code` | 初始化字典 / config / 自由字符串 | 迁到 `field_config` | 建议 key:`contract.code` | | contract | `currency` | 自由字符串 | 暂不处理 | 当前仅见 `CNY` | | construction_task | `section_type_code` | 硬编码 dict + config | 迁到 `field_config` | 建议 key:`construction.section_type_code` | | construction_task | `task_type` | 类型树 + 字符串字段 | 暂缓收口 | 先拆 category 与真实任务类型 | | construction_task | `status` | 数值字段 + dict | 保留代码常量 | 先统一常量定义源 | -| construction_task_detail | `status` | 旧数值字段 | 暂缓收口 | 先决定是否废弃 | -| construction_task_detail | `approval_status` | 通用审批体系 | 接入通用审批定义 | 不单独建 `field_config` | | material | `material_type` | 本地列表 + 自由字符串 | 迁到 `field_config` | 建议 key:`material.material_type` | | material | `status` | 数值字段 | 暂缓收口 | 先和 `supply_status` 划边界 | | material | `supply_status` | 代码 enum | 保留代码常量 | 先确认是否为正式推送字段 | | material | `approval_party` | 本地候选项 | 暂不处理 | 当前无集团侧对应 | -| material_detail | `kit` | 本地配置字典 | 暂缓收口 | 先确认是否进入推送口径 | -| material_detail | `part` | 自由字符串 | 暂不处理 | 当前更像说明字段 | -| material_detail | `status` | sync schema 有,backend 缺正式来源 | 暂缓收口 | 先补字段来源映射 | -| material_detail | `approval_status` | 通用审批体系 | 接入通用审批定义 | 不单独建 `field_config` | | contract_settlement | `contract_type` | 跟随合同主表 | 暂缓收口 | 先确认是否允许 `project` | | contract_settlement | `status` | 数值字段 | 保留代码常量 | 需补统一定义源 | | contract_settlement | `payment_method` | 代码 enum | 保留代码 enum | 不建议迁 `field_config` | -| contract_settlement_detail | `payment_type` | 代码 enum | 保留代码 enum | schema 已在使用 | -| contract_settlement_detail | `advance_type` | 代码 enum | 保留代码 enum | schema 已在使用 | -| contract_settlement_detail | `approval_status` | 通用审批体系 | 接入通用审批定义 | 不单独建 `field_config` | | personnel | `personnel_type` | 本地代码常量 | 保留代码 enum | 本地真实领域是人员资质 | | personnel | `task_type` | 本地候选项常量 | 保留代码常量 | 先补 schema 白名单 | | personnel | `is_duty` | 数值常量 | 保留代码常量 | 与集团侧 `status` 不是同一字段 | -| personnel_detail | 全领域 | 当前无独立模型 | 暂缓收口 | 先确认领域是否存在 | | preparation | `code` | 初始化字典 + 自由字符串 | 迁到 `field_config` | 建议 key:`preparation.code` | | preparation | `approval_status` | 通用审批体系 | 接入通用审批定义 | 不单独建 `field_config` | | preparation | `is_exist` | 布尔字段 | 暂不处理 | 不属于候选项字段 | | production | `code` | 初始化字典 + 自由字符串 | 迁到 `field_config` | 建议 key:`production.code` | | production | `approval_status` | 通用审批体系 | 接入通用审批定义 | 不单独建 `field_config` | | production | `is_exist` | 布尔字段 | 暂不处理 | 不属于候选项字段 | -| project_detail_data | `key` | 开放字符串集合 | 暂缓收口 | 先统一 backend key 与 sync key 命名 | ## 4. 第一批建议落地项 @@ -75,21 +64,17 @@ 优先保留并收紧 schema 的代码 enum 字段: - `project.project_type` +- `project.status` - `project.progress_type` +- `project.key_constraints` - `contract.contract_type` - `contract_settlement.payment_method` -- `contract_settlement_detail.payment_type` -- `contract_settlement_detail.advance_type` 优先先做边界确认、暂不直接配置化的字段: -- `project.status` -- `project.key_constraints` - `construction.task_type` - `material.status` - `contract_settlement.contract_type` -- `project_detail_data.key` -- `personnel_detail` 全领域 ## 5. 使用方式 @@ -98,3 +83,75 @@ 1. 先处理“迁到 `field_config`”的一组字段。 2. 再处理“保留代码 enum,但 schema 还没收口”的一组字段。 3. 最后处理“暂缓收口”的结构性问题字段。 + +## 6. 按本轮目标拆出的第一批字段 + +这次不是单纯做字段分类,而是要直接服务于 3 条链路: + +- 项目初始化 +- create 业务校验 +- 前端下拉列表 + +因此第一批字段不能只看“适不适合进 `field_config`”,还要看它能不能尽快把这 3 条链路收口到同一真源。 + +### 6.1 第一批优先收口字段 + +建议第一批优先处理: + +- `contract.code` +- `material.material_type` +- `construction.section_type_code` +- `preparation.code` +- `production.code` +- `power.code` + +原因: + +- 都属于 code 类字段 +- 都直接影响初始化、创建或下拉其中至少两条链路 +- 当前都存在 config / dict / 代码常量混用问题,或者已具备明显收口价值 + +### 6.2 每个字段要同时回答的 3 个问题 + +对上面每个字段,后续文档和代码改造都应同时回答: + +1. 初始化时从哪里拿候选项。 +2. create service 开头用哪个 config key 做校验。 +3. 前端下拉接口用哪个 config key 返回选项。 + +如果这 3 个问题的答案不是同一个 key,就说明还没有真正收口。 + +### 6.3 本轮不优先收口但要避免误迁的字段 + +下面这些字段虽然也和 enum 有关,但本轮不应抢先塞进字段级配置: + +- `project.status` +- `project.progress_type` +- `project.key_constraints` +- `contract.contract_type` +- `contract_settlement.payment_method` + +原因是: + +- 这些字段语义更固定 +- 业务分支已经直接依赖 +- 本轮重点是先把“按项目类型变化的 code 类字段”收口,而不是把固定状态类枚举全部配置化 + +### 6.4 对物资字段的特别说明 + +`material.material_type` 虽然在表里只是一行,但它实际上牵扯 4 套来源: + +- 初始化合同候选项:`contract_config` +- 物资初始化补充:`material_contract_*` +- 前端下拉:`material_types` +- create 内部默认展示/单位:`contract_product_type_list` + +所以 `material.material_type` 不是简单迁一个 key,而是本轮最需要先统一真源的一项。 + +### 6.5 本轮完成后的字段判定标准 + +一个字段只有同时满足下面 3 条,才算本轮真正完成收口: + +- 初始化使用它对应的 field config +- create 校验使用它对应的 field config +- 下拉列表返回也使用它对应的 field config diff --git a/docs/enum_refactor/init_project_data.md b/docs/enum_refactor/init_project_data.md new file mode 100644 index 0000000..64cb103 --- /dev/null +++ b/docs/enum_refactor/init_project_data.md @@ -0,0 +1,757 @@ +# init_project_data 现状分析 + +## 1. 范围 + +本文档只分析 backend 里的 `ProjectService.init_project_data)` 当前到底做了什么。 + +重点回答 4 个问题: + +- 这个函数当前创建了哪些业务数据。 +- 它直接读取了哪些 `config` 和 `dict`。 +- 哪些 `dict` 是真的在负责初始化,哪些只是历史残留。 +- 如果把这里 `dict` 负责的部分迁到 `config`,复杂度到底高不高。 + +## 2. 触发时机 + +`init_project_data)` 在项目审批通过后执行。 + +调用链是: + +- `ProjectService.audit_project()` +- `ProjectService._audit_approval()` +- `ProjectService.init_project_data)` + +也就是说,这不是“创建项目主表时立即执行”的逻辑,而是“审批通过后批量灌初始化数据”的逻辑。 + +## 3. 函数当前做的 6 件事 + +按执行顺序,当前函数会做下面 6 件事: + +1. 读取当前 `project_type` 整个 category 下的全部 config。 +2. 读取 6 组 dict 数据。 +3. 初始化 `preparation` 里程碑节点。 +4. 初始化 `production` 投产节点。 +5. 初始化 `contract` 合同,并在合同初始化内部继续初始化 `contract_settlement` 和 `material`。 +6. 初始化 `power` 力能配置、`construction` 施工任务、`archive` 电子档案。 + +注意: + +- 这个函数表面上不长,但它本质上是一个“项目初始化编排器”。 +- 真正复杂的地方不只在它自己,而在它调用出去的各个 `init_from_*`。 + +## 4. 直接读取了什么 + +### 4.1 直接读取的 config + +函数一开始会调用: + +- `config_service.get_category_config(category=project.project_type)` + +这一步不是只读某一个 key,而是把当前 `project_type` 这个 category 下的全部 config 都读出来,再做: + +- `configs = {config_key: decoded_json}` + +但在 `init_project_data)` 自己内部,真正直接用到的 config 只有 1 个: + +- `contract_config` + +也就是说: + +- 直接读取:整类 config +- 直接消费:只有 `contract_config` +- 其余 config 在这个函数里只是被一并装进了 `configs`,并没有直接使用 + +### 4.2 直接读取的 dict + +函数会一次性读取 6 个 dict key: + +- `preparation_{project_type}` +- `production_{project_type}` +- `construction_contract_{project_type}` +- `service_contract_{project_type}` +- `material_contract_{project_type}` +- `power_{project_type}` + +但这 6 个 key 不是都真正被当前函数消费了。 + +## 5. 哪些数据是怎么创建出来的 + +### 5.1 Preparation + +创建来源: + +- 直接使用 dict:`preparation_{project_type}` + +创建方式: + +- 读取 dict 项的 `code`、`name` +- 先删项目下旧的 `Preparation` +- 再批量插入新的 `Preparation` + +涉及配置数量: + +- `config`: 0 +- `dict`: 1 组 + +当前种子数据条目数: + +- `preparation_photovoltaic`: 12 条 +- `preparation_onshore_wind`: 13 条 +- `preparation_offshore_wind`: 14 条 + +结论: + +- 这是最典型的“dict 直接承担初始化模板”场景。 +- 结构非常简单,迁到 `config` 不复杂。 + +### 5.2 Production + +创建来源: + +- 直接使用 dict:`production_{project_type}` + +创建方式: + +- 读取 dict 项的 `code`、`name` +- 先删项目下旧的 `Production` +- 再批量插入新的 `Production` + +涉及配置数量: + +- `config`: 0 +- `dict`: 1 组 + +当前种子数据条目数: + +- `production_photovoltaic`: 9 条 +- `production_onshore_wind`: 9 条 +- `production_offshore_wind`: 9 条 + +结论: + +- 和 `preparation` 一样,是平铺型 code/name 列表。 +- 迁到 `config` 也不复杂。 + +### 5.3 Contract + +创建来源: + +- 直接使用 config:`contract_config` + +创建方式: + +- `init_project_data)` 从 `configs["contract_config"]` 取出列表 +- `ContractService.init_from_project()` 根据每项的 `code`、`name`、`contract_type` 创建合同 +- 先删项目下旧合同,再批量插入新合同 + +后续联动: + +- 初始化 `contract_settlement` +- 初始化 `material` + +涉及配置数量: + +- `config`: 1 个 key +- `dict`: 0 组直接参与合同创建 + +当前 `contract_config` 条目数: + +- `photovoltaic`: 21 条 +- `onshore_wind`: 20 条 +- `offshore_wind`: 20 条 + +结论: + +- 合同初始化本体已经是 config 驱动,不是 dict 驱动。 +- 所以这里真正要迁移的不是“合同本身”,而是合同初始化后还依赖的那些配套 dict。 + +### 5.4 Material + +创建来源: + +- 不是 `init_project_data)` 直接创建。 +- 它是 `contract.init_from_project()` 内部继续调用 `material.init_from_contract()` 创建。 + +当前来源拆分: + +- 合同列表来源:`contract_config` +- 物资单位来源:`material_contract_{project_type}` dict + +也就是说,当前 `material` 初始化其实是两段拼起来的: + +- 先由 `contract_config` 决定会创建哪些货物类合同 +- 再由 `material_contract_*` dict 给这些物资补 `unit` + +涉及配置数量: + +- `config`: 1 个上游 key `contract_config` +- `dict`: 1 组 `material_contract_{project_type}` + +当前种子数据条目数: + +- `material_contract_photovoltaic`: 14 条 +- `material_contract_onshore_wind`: 12 条 +- `material_contract_offshore_wind`: 7 条 + +这里有一个关键现状: + +- `material` 实际创建多少条,取决于 `contract_config` 里有多少 `product` 合同。 +- `material_contract_*` dict 只负责补充单位和名称,不负责决定是否创建。 +- 因此这两套来源当前不是同一个真源。 + +已看到的分叉: + +- 光伏 `contract_config` 里的货物类候选项和 `material_contract_photovoltaic` 并不完全一致。 +- 陆风也存在类似分叉。 +- 海风更明显:当前能看到 `contract_config` 已有较完整货物类合同,但 `dict` 侧条目明显更少。 + +结论: + +- `material` 不是简单的“把一个 dict 搬到 config”。 +- 真正要做的是先合并“合同货物候选项”和“物资单位/展示信息”两套来源。 +- 这块复杂度中等,不是最高,但已经不是平铺搬运。 + +### 5.5 Power + +创建来源: + +- 直接使用 dict:`power_{project_type}` + +创建方式: + +- 读取 dict 项的 `code`、`name` +- 统一绑定到 `contract_data.get("EPC")` +- 先删项目下旧 `Power` +- 再批量插入新 `Power` + +涉及配置数量: + +- `config`: 0 +- `dict`: 1 组 + +当前种子数据条目数: + +- `power_photovoltaic`: 1 条 +- `power_onshore_wind`: 2 条 +- `power_offshore_wind`: 2 条 + +结论: + +- 也是平铺型初始化模板,迁到 `config` 不复杂。 +- 但它和合同存在一条强耦合:默认绑定 `EPC` 合同。 + +### 5.6 Construction + +创建来源: + +- `init_project_data)` 自己没有直接给施工传 dict 或 config 数据 +- 只调用:`create_constructions_for_project(tenant_id, project_id)` + +但施工初始化内部会继续读取: + +- `construction_section_config` + +并依赖: + +- `BuildMethod` +- `ConstructionTypeTree` +- `ConstructionTypeMapping` + +实际创建内容: + +- 按 `construction_section_config.sections[*].project_root_nodes` 创建项目根单位工程 +- 基于这些根节点再创建主施工任务 +- 对质量验收类施工继续创建施工树 +- 对主施工继续自动补次级施工 + +涉及配置数量: + +- `config`: 至少 1 个 key `construction_section_config` +- `dict`: 0 +- 另外强依赖数据库里的施工类型树,不是简单 JSON 配置 + +结论: + +- 施工初始化本来就不是 dict 驱动,已经是 config + 类型树驱动。 +- 这里真正复杂的不是“dict 要不要迁 config”,而是施工本身就已经是结构化初始化。 + +### 5.7 Archive + +创建来源: + +- 不依赖 dict +- 也不依赖 `init_project_data)` 读出来的 config + +当前方式: + +- 按 `template/{project_type}_archive.xlsx` 模板文件初始化电子档案树 + +涉及配置数量: + +- `config`: 0(对 `_init_project_data` 而言) +- `dict`: 0 +- 外部模板文件: 1 份 Excel + +结论: + +- 电子档案初始化不属于这次 dict/config 收敛的核心问题。 + +## 6. 这 6 组 dict 里,哪些真的在负责事情 + +### 6.1 直接被 `init_project_data)` 消费的 dict + +- `preparation_{project_type}` +- `production_{project_type}` +- `power_{project_type}` + +这 3 组 dict 是真的在当前函数里直接承担初始化模板职责。 + +### 6.2 被 `init_project_data)` 取了,但当前函数里没有直接消费的 dict + +- `construction_contract_{project_type}` +- `service_contract_{project_type}` +- `material_contract_{project_type}` + +其中: + +- `construction_contract_*`:当前只看到被 `init_project_data)` 取出,但没有实际使用。 +- `service_contract_*`:当前也只看到被 `init_project_data)` 取出,但没有实际使用。 +- `material_contract_*`:当前函数本身没用,但会在 `MaterialService.init_from_contract()` 里被重新读取一次,用于补单位。 + +这说明当前 `init_project_data)` 至少存在两类历史包袱: + +- 取了但没用的 dict key +- 同一套语义在上游合同 config 和下游 material dict 之间分叉 + +## 7. 把 dict 迁到 config,会不会太复杂 + +### 7.1 不复杂的部分 + +下面 3 组迁移到 config,复杂度不高: + +- `preparation_{project_type}` +- `production_{project_type}` +- `power_{project_type}` + +原因很简单: + +- 它们当前就是平铺的 `code/name` 列表 +- 服务接口已经接受 `list[dict]` +- 初始化逻辑只做“删旧 + 批量重建” +- 不依赖复杂树结构,也不依赖额外业务联动 + +如果迁移: + +- `preparation`、`production` 很适合直接改成 `field_config` +- `power` 更像一个小型初始化模板,也可以直接放到 config,但不一定要走 `field_config` 这个名字 + +### 7.2 中等复杂的部分 + +`material_contract_{project_type}` 迁移到 `config`,复杂度中等。 + +原因不是数据量大,而是来源分叉: + +- 哪些货物类记录会被创建,取决于 `contract_config` +- 每种物资的显示名、单位,又取决于 `material_contract_*` dict + +所以这块不是“dict 搬家”,而是“先统一真源”。 + +更稳妥的做法是: + +- 先把 `contract_config` 和 `material_contract_*` 合并成一套正式结构 +- 再决定是否把单位、显示名、parts 一并并到 `material.field_config` 或其它 config key + +### 7.3 高复杂的部分其实不在 dict + +真正复杂的初始化不是 dict,而是施工: + +- 它已经依赖 `construction_section_config` +- 又依赖 `ConstructionTypeTree` +- 又依赖 `ConstructionTypeMapping` +- 还会继续生成施工树和次级施工 + +所以如果目标是“降低 `init_project_data` 初始化复杂度”,优先清理 dict 只能解决一部分表层问题,不能解决施工初始化本身的结构复杂度。 + +## 8. 额外现状问题 + +### 8.1 `init_project_data)` 读了整类 config,但只直接用了 `contract_config` + +这会导致: + +- 函数看起来像“config 驱动” +- 实际上只有合同初始化是直接 config 驱动 + +### 8.2 有 3 个 dict key 在当前函数里不是实际输入 + +- `construction_contract_*` +- `service_contract_*` +- `material_contract_*` + +其中前 2 个当前更像历史残留。 + +### 8.3 当前只明显覆盖了 3 类项目类型的初始化种子 + +从现有 `dict.csv` / `config.csv` 看,当前明确有初始化种子的主要是: + +- `photovoltaic` +- `onshore_wind` +- `offshore_wind` + +而 `project_type` 枚举里其实还有: + +- `pumped_storage` +- `hydropower` +- `thermal_power` +- `gas_turbine` + +也就是说,`project_type` 枚举范围和初始化种子覆盖范围并不一致。 + +### 8.4 海风配置目前看起来不完整 + +当前能直接看到: + +- `offshore_wind` 有 `contract_config` +- 也有 `preparation_*`、`production_*`、`power_*`、`material_contract_*` dict + +但当前 `config.csv` 里没看到海风的: + +- `construction_section_config` +- `construction_node_config` +- `material_types` +- `supply_chain_group` + +这意味着海风初始化链路当前很可能并不完整,至少配置层不齐。 + +## 9. 结论 + +可以把 `init_project_data)` 当前的配置来源,拆成 4 类理解: + +1. 纯 dict 初始化:`preparation`、`production`、`power` +2. 纯 config 初始化:`contract` +3. config + dict 混合初始化:`material` +4. config + 类型树 + 模板文件初始化:`construction`、`archive` + +所以“把这里 dict 负责的东西迁到 config 会不会太复杂”的答案不是一个统一结论: + +- `preparation`、`production`、`power`:不复杂,可以迁。 +- `material_contract`:中等复杂,先统一真源再迁。 +- `construction`:复杂点不在 dict,本来就不是 dict 驱动。 + +## 10. 建议落地顺序 + +1. 先把 `init_project_data)` 里“取了但没直接用”的 `construction_contract_*`、`service_contract_*` 从函数里清出来,确认是否还能删掉。 +2. 再把 `preparation_{project_type}`、`production_{project_type}`、`power_{project_type}` 迁到 config。 +3. 再处理 `material_contract_*`,但前提是先统一 `contract_config` 和 material 初始化所需的单位/展示信息来源。 +4. 施工初始化不要和这批 dict 迁移混做,单独处理。 + +## 10.1 按本轮改造目标,初始化链路具体要改什么 + +如果把这次目标收敛成“初始化统一走配置工具”,那么 `init_project_data)` 不应该再只是分析对象,而是明确要往下面的形态改。 + +### A. 保留并整理的 config + +初始化链路应继续保留并整理这几类 config: + +- `contract_config`:合同初始化候选项 +- `construction_section_config`:施工区域 / 单位工程 / 任务入口配置 +- `material_types`:物资类型、展示名、单位、parts 等字段配置 +- 后续新增的字段级配置 key,例如: + - `preparation.code` + - `production.code` + - `power.code` + +这里的关键不是 config 数量多少,而是把“初始化真的要用的候选项”收口到 config 工具统一读取。 + +### B. 应从 dict 迁出的初始化候选项 + +下面这几类当前属于平铺型初始化模板,适合迁出 dict: + +- `preparation_{project_type}` +- `production_{project_type}` +- `power_{project_type}` + +迁移后的目标形态是: + +- `init_project_data)` 不再直接读这些 dict key +- 改由 `ConfigService.get_field_options()` 或等价工具读取字段候选项 +- service 仍只负责“删旧 + 批量重建”,不再关心候选项来自 dict 还是 config + +### C. 应去掉的不适当 dict 用法 + +本轮最典型的不适当 dict 用法是: + +- `material_contract_{project_type}` 用 `remark` 存物资单位 + +这类做法的问题不是“它现在不能工作”,而是: + +- 含义不显式 +- 初始化、创建、下拉三条链路都没法自然共用 +- 后续导出 CSV / 校验时也不容易发现结构错误 + +迁移方向应是: + +- 把 `unit`、`label`、`parts` 等字段并回正式 config 结构 +- 不再让单位这类业务字段挂在 dict `remark` 上 + +### D. `init_project_data)` 迁移后的读取原则 + +迁移后,`init_project_data)` 本身应只做 2 件事: + +- 找到当前项目类型下所需的候选项配置 +- 把候选项列表传给各领域初始化 service + +它不应该继续承担: + +- 现场拼 JSON 结构 +- 自己手工区分 dict / config / remark +- 同时取一堆历史 key 再由下游各自二次分叉读取 + +### E. 初始化改造完成后的判定标准 + +初始化侧改造完成后,至少应满足: + +- `preparation`、`production`、`power` 初始化不再依赖 dict +- `material` 初始化不再依赖 dict `remark` 补单位 +- 初始化候选项与前端下拉、create 校验使用同一套 config 真源 +- `init_project_data)` 中不再保留只读取不用的 dict key + +## 11. 从接口创建视角反看这些字段是不是已经“主要 config 化” + +只看 `init_project_data)`,容易得出“系统已经比较 config 驱动”的印象。 + +但如果再抽样看接口创建路径,会发现当前真实情况是: + +- 初始化层里,确实有一部分已经走 `config` +- 但创建接口里,很多 enum-like 字段仍然主要靠代码常量或 service 层校验 +- schema 层大多还是普通 `str`,并没有把这些候选值正式收口到统一配置来源 + +### 11.1 Contract 创建 + +接口入参: + +- `ContractCreate` 里的 `contract_type`、`code` 都只是普通字符串 + +创建时字段来源: + +- `contract_type`、`code` 直接来自接口传参 +- 合同名称不是前端传,而是 service 根据 `contract_code_dict` 动态拼出来 + +当前校验方式: + +- schema 层:只有长度约束,没有枚举白名单 +- service 层: + - `code` 必须在 `contract_code_dict` + - `contract_type` 必须在 `contarct_type_dict` + - `service` 合同再限制为 `JL`、`KY`、`E`、`SCYW` + - `product` 合同再限制为一组硬编码产品 code + +结论: + +- 合同初始化是 `contract_config` 驱动 +- 但合同接口创建仍然主要依赖代码字典和硬编码白名单 +- 这说明“初始化层已 config 化”并不等于“合同域已经完成 config 收口” + +### 11.2 Material 创建 + +接口入参: + +- `MaterialCreate.material_type`、`approval_party` 也都是普通字符串 + +创建时字段来源: + +- `material_type` 直接来自接口传参 +- `show_name`、`unit` 不是前端传,而是 `MaterialService.get_material_info_by_code()` 从 `contract_product_type_list` 里反查 +- 创建物资时还会同步创建一个 `product` 合同,合同 code 直接复用 `material_type` + +当前校验方式: + +- schema 层: + - `material_type` 只是示例值来自 `contract_product_type_list` + - `approval_party` 只有示例,没有正式白名单 +- service 层: + - 强校验的是 `contract_quantity > 0`、项目存在、tenant 存在 + - `material_type` 本身没有显式抛错白名单 + - 如果 code 不在 `contract_product_type_list`,`get_material_info_by_code()` 会退回默认值 `支架/组` + - 随后又会去调用合同创建,而合同创建对 `code` 有一层更严格的白名单校验 + +结论: + +- material 创建不是 config 驱动,而是代码常量驱动 +- 而且 `material_type` 的直接校验位置并不清晰,存在“本服务默认值兜底,合同服务再拦截”的现状 +- 这正是还需要整理 config/真源的证据,不适合直接认为已经收口完成 + +### 11.3 Construction 创建 + +接口入参: + +- `ConstructionCreate.section_type_code`、`section_sub_type_code`、`type_id` 都是普通字符串 + +创建时字段来源: + +- `section_type_code`、`section_sub_type_code`、`type_id` 直接来自接口传参 +- `section_type_name`、`section_sub_type_name`、`task_code`、`task_type` 则由 service 根据配置和类型树反查生成 + +当前校验方式: + +- schema 层:只有字符串长度和数值范围,没有枚举白名单 +- service 层: + - `section_type_code` 必须存在于 `construction_section_config` + - `section_sub_type_code` 必须能映射到统一类型节点 + - `type_id` 必须能映射到施工类型树节点 + - `SCDW` 分支还会单独校验专用任务编码列表 + +结论: + +- 施工创建是这 3 个抽样域里最接近“配置/类型树驱动”的 +- 但即便如此,接口 schema 本身仍未把候选值正式声明出来,真正校验仍落在 service 里 + +### 11.4 总判断 + +如果只问“初始化层面能不能看出 enum_config 类字段怎么来的”,答案是: + +- 能看出一部分。 +- `preparation`、`production`、`power` 明显还来自 dict。 +- `contract` 初始化候选项明显来自 `contract_config`。 +- `material` 初始化候选项来自 `contract_config`,单位补充来自 `material_contract_*` dict。 +- `construction` 初始化候选项来自 config + 类型树。 + +但如果进一步问“那是不是已经主要用 config 写了,所以没必要再整理 config”,答案是否定的。 + +原因是当前系统仍然存在 3 个没有收口的问题: + +- 初始化来源和接口创建来源不是同一套真源。 +- schema 层没有把这些字段正式建模成受控候选值。 +- service 层仍保留大量代码字典、硬编码白名单和默认兜底。 + +所以现在做 config 整理仍然有必要,而且必要性不在“把所有东西都搬到 config”,而在: + +- 明确每个字段的唯一真源 +- 消除 dict/config/代码常量三头并存 +- 让初始化和接口创建共用同一套候选值与校验逻辑 + +## 12. 前端下拉选项当前是通过什么路径返回的 + +除了创建接口本身,前端通常会先调一些 list 接口拿可选项。 + +这部分如果和初始化来源不一致,就会出现: + +- 初始化能创建出来的值,不一定和前端可选项完全一致 +- 前端下拉看到的值,也不一定就是创建 service 实际校验使用的那套真源 + +下面按合同、物资、施工 3 类抽样。 + +### 12.1 Contract 的前端下拉路径 + +当前相关接口主要有: + +- `/contract/type/list` +- `/contract/sub-type/list` + +返回路径: + +- `router -> ContractService.get_contract_type_list()` +- `router -> ContractService.get_sub_type_list(contract_type)` + +数据来源: + +- `get_contract_type_list()` 直接读代码里的 `contarct_type_dict` +- `get_sub_type_list()` 先根据传入 `contract_type` 选一组硬编码 `filter_key_list` +- 再去代码里的 `contract_code_dict` 组装返回值 + +和初始化是否一致: + +- 不一致。 +- 初始化合同候选项来自 `contract_config`。 +- 但前端拿到的合同类型/子类型下拉,来自代码字典和硬编码列表。 + +这意味着: + +- 前端创建合同时看到的 code/type 候选,不是直接从初始化同源配置拿出来的。 +- 如果未来 `contract_config` 改了,但代码字典没改,就会分叉。 + +### 12.2 Material 的前端下拉路径 + +当前至少有两条相关路径: + +- `/material/material-types` +- `/material-detail/contract-product-type/list` + +第一条路径:`/material/material-types` + +- `router` 先根据 `project_id` 查项目类型 +- 再调用 `config_service.get_config_json(project.project_type, "material_types")` +- 返回 `material_types` 里的 `label/value` + +它的数据来源是: + +- config:`material_types` + +第二条路径:`/material-detail/contract-product-type/list` + +- `router -> MaterialDetailService.get_material_part_options()` +- service 内部同样会先读项目的 `material_types` config +- 再按当前 material 的 `material_type` 找到对应 kit 和 parts + +它的数据来源也是: + +- config:`material_types` + +但同时,物资模块里还保留了老的代码常量路径: + +- `MaterialService.get_material_info_by_code()` 直接读 `contract_product_type_list` +- `MaterialDetailService.get_kit_options()` 也直接读 `contract_product_type_list` + +和初始化是否一致: + +- 也不一致。 +- 前端下拉主要已经走 `material_types` config。 +- 但初始化物资时,候选项来源还是 `contract_config`,单位来源还是 `material_contract_*` dict。 +- 而创建 service 内部补 `show_name/unit` 时,又还在读代码常量 `contract_product_type_list`。 + +这是当前分叉最明显的一类: + +- 前端选项:config +- 初始化来源:config + dict +- 创建补充逻辑:代码常量 + +### 12.3 Construction 的前端下拉路径 + +当前相关接口主要有: + +- `/construction/section/type/list` +- `/construction/section/sub/type/list` +- `/construction/task/type/list` + +返回路径: + +- `router -> ConstructionService.get_section_type_list(project_type)` +- `router -> ConstructionService.get_section_sub_type_list(section_type_code, project_type)` +- `router -> ConstructionService.get_task_type_list(section_type_code, section_sub_type_code, project_type)` + +数据来源: + +- `section_type_list` 通过 `config_helper.section_type_id_dict()` 读取 `construction_section_config` +- `section_sub_type_list` 通过 `config_helper.section_type_project_root_node_mapping()` 读取 `construction_section_config.project_root_nodes` +- `task_type_list` 则继续基于 `construction_section_config` 和 `ConstructionTypeTree` 生成 + +和初始化是否一致: + +- 基本一致。 +- 施工初始化本来就是 `construction_section_config + 类型树` 驱动。 +- 前端下拉接口也是同一条配置/类型树路径。 + +所以施工是目前 3 类里最接近“初始化与前端下拉同源”的。 + +### 12.4 总结 + +从前端下拉接口角度看,当前 3 类字段的真实状态可以概括成: + +1. Contract:前端下拉走代码字典,和初始化 `contract_config` 不一致。 +2. Material:前端下拉主要走 `material_types` config,但初始化和创建内部仍分别依赖 `contract_config`、`material_contract_*` dict、`contract_product_type_list` 代码常量,分叉最严重。 +3. Construction:前端下拉和初始化都主要走 `construction_section_config + 类型树`,相对一致。 + +所以如果后续要做收口,优先级也比较明确: + +- 合同:先统一 `contract_config` 与前端 `type/sub-type list`。 +- 物资:先统一 `material_types`、`contract_config`、`material_contract_*`、`contract_product_type_list` 四套来源。 +- 施工:优先补 schema/创建校验与配置同源,配置来源本身相对已经收敛。 diff --git a/docs/enum_refactor/material.md b/docs/enum_refactor/material.md index e0f02d0..35cd7be 100644 --- a/docs/enum_refactor/material.md +++ b/docs/enum_refactor/material.md @@ -125,3 +125,77 @@ 3. 统一 `35KV/XT`、`CYJD/CBJDB`、`SVG/SX`、`ECPG/ECXT` 这类命名差异。 4. 明确 `status` 与 `supply_status` 的职责边界。 5. 为 `material_type` 在 schema 层增加统一校验入口。 + +## 5. 按本轮要求的改造落点 + +material 是本轮最需要针对性改造的领域之一,因为它当前同时存在: + +- 初始化来源分叉 +- create 校验分叉 +- 下拉来源分叉 + +### 5.1 初始化 + +当前 material 初始化不是单源: + +- 候选项来自 `contract_config` 里的 product 合同 +- 单位和部分展示信息来自 `material_contract_{project_type}` dict + +而且 `unit` 现在还挂在 dict `remark` 上。 + +本轮目标是: + +- 明确 `material.material_type` 的正式配置来源 +- 把 `label`、`unit`、`parts` 等附属字段纳入正式 config 结构 +- 去掉“从 dict remark 补单位”这种不适当用法 + +### 5.2 Create 校验 + +当前 create 路径里: + +- `material_type` 入参本身没有正式 config 校验 +- `show_name`、`unit` 由 `contract_product_type_list` 反查 +- 随后又会调用合同创建,由合同 service 再做一层 code 白名单校验 + +这说明 material create 现在不是单一入口校验。 + +本轮目标是: + +- 在 `MaterialService.create()` 开头基于 config 校验 `material_type` +- 用 `get_field_option_map()` 之类的方法直接拿 `label`、`unit`、`parts` +- 不再依赖 `contract_product_type_list` 和默认兜底值 + +### 5.3 下拉列表 + +当前物资下拉已经部分走 config: + +- `/material/material-types` 读 `material_types` +- `/material-detail/contract-product-type/list` 也读 `material_types` + +但模块里仍保留了旧的硬编码路径: + +- `get_material_info_by_code()` +- `get_kit_options()` +- `contract_product_type_list` + +本轮目标是: + +- 让物资类型和部件列表统一从新的 config 工具返回 +- 下拉、初始化、create 校验全部围绕同一套 `material_type` 配置 + +### 5.4 清理项 + +material 领域这轮改造完成后,应优先清理: + +- `material_contract_*` 中用 `remark` 承载单位的旧逻辑 +- `contract_product_type_list` 里只服务于 `material_type` 真源的旧定义 +- `get_material_info_by_code()` 这类旧反查入口 + +## 6. 测试关注点 + +material 领域 service 层集成测试至少覆盖: + +1. 初始化:项目初始化后 material 的 `material_type`、`show_name`、`unit` 是否来自目标 config。 +2. 下拉:物资类型和部件列表是否来自同一 config,且与初始化同源。 +3. Create:合法 `material_type` 能创建,非法 `material_type` 明确报错,不再依赖默认兜底。 + diff --git a/docs/enum_refactor/material_detail.md b/docs/enum_refactor/material_detail.md deleted file mode 100644 index 913d406..0000000 --- a/docs/enum_refactor/material_detail.md +++ /dev/null @@ -1,109 +0,0 @@ -# Material Detail 领域重构指南 - -## 1. 范围 - -本文档对应 backend 的 `material_detail` 明细表。 - -当前优先关注: - -- `material_detail.kit` -- `material_detail.part` -- `material_detail.status` -- `material_detail.approval_status` - -## 2. 字段盘点 - -### 2.1 `kit` - -当前实现: - -- 模型里是 `String` -- 本地候选项来自 `kit_part_dict` -- 没有独立 enum 类 - -项目侧取值范围: - -- 当前本地明确值包括:`TY`、`ZJ`、`LZ`、`XC`、`ZT`、`NB`、`XB`、`ZB`、`GS`、`CN`、`PCS`、`DCC`、`35KV`、`CYJD`、`SVG`、`DL`、`35-DL`、`10-DL`、`400DL`、`two-DL`、`TXDL`、`GX`、`WX`、`ECPG`、`QT` - -集团侧差异: - -- 集团侧没有独立的 `MaterialDetail` 顶层段 -- 同步 schema `material_detail.py` 里也没有 `kit` 字段 - -结论: - -- 当前是本地专有字段 -- 先保留配置字典来源 - -### 2.2 `part` - -当前实现: - -- 模型里是 `String` -- 没有统一 enum 或配置表 - -项目侧取值范围: - -- 当前无法列出稳定范围 -- 更像是自由文本/部件说明字段 - -集团侧差异: - -- 当前没看到同步 schema 或集团侧 `enum_config` 给出对应候选项 - -结论: - -- 暂不枚举化 - -### 2.3 `status` - -当前实现: - -- backend 明细模型没有显式业务 status 字段 -- 同步 schema `material_detail.py` 有 `status: Optional[int]` - -项目侧取值范围: - -- 当前无法从 backend `material_detail` 模型中列出正式取值范围 - -集团侧差异: - -- 同步 schema 仍保留该字段 -- backend 当前没有同名正式字段,存在领域映射缺口 - -结论: - -- 需要先确认推送时该字段来源到底是 material 主表还是 detail 明细计算值 - -### 2.4 `approval_status` - -当前实现: - -- backend 依赖 `ApprovalMixin` -- 同步 schema 使用 `ApprovalStatus` enum - -项目侧取值范围: - -- 当前 material_detail 自身没有独立审批 enum 文件 -- 依赖通用审批体系 - -集团侧差异: - -- 同步 schema 已经显式收紧为审批 enum - -结论: - -- 统一接入通用审批 enum 即可 - -## 3. 重点结论 - -- `material_detail` 当前不是一个干净的“可直接枚举化”领域,最大的缺口是 backend 与 sync schema 的字段映射不完整。 -- `kit` 可以继续保留为本地候选项来源,但是否外提到 `field_config` 取决于后续是否进入推送口径。 -- `status` 必须先确认正式来源,否则不能直接做字段级配置。 - -## 4. 待修改项清单 - -1. 明确 `kit` 是否需要单独迁成字段配置。 -2. 明确 `part` 是否继续保留自由文本。 -3. 明确 `material_detail.status` 在 backend 的正式来源。 -4. 将 `approval_status` 与通用审批状态定义彻底对齐。 diff --git a/docs/enum_refactor/personnel.md b/docs/enum_refactor/personnel.md index 8973006..ff2dda9 100644 --- a/docs/enum_refactor/personnel.md +++ b/docs/enum_refactor/personnel.md @@ -103,3 +103,65 @@ 2. 为 `personnel_type` 补正式 enum/schema 校验。 3. 为 `task_type` 补统一白名单校验。 4. 不要把集团侧 `Personnel.code/status` 直接映射到本地 `personnel_type/is_duty`。 + +## 5. 按本轮要求的改造落点 + +personnel 领域不是这轮“项目初始化 + 项目类型 field_config”第一批重点,但仍需要按统一口径写清楚 create、下拉和清理边界。 + +### 5.1 初始化 + +本地人员资质领域当前没有项目初始化批量灌数链路。 + +因此本轮不把它纳入 `init_project_data)` 第一批改造范围。 + +### 5.2 Create 校验 + +当前 `PersonnelQualificationService.create()` 已直接在 service 层校验: + +- `personnel_type` +- `task_type` + +校验来源是本地模型类常量: + +- `PERSONNEL_TYPE_SAFE` +- `PERSONNEL_TYPE_SPECIAL` +- `PERSONNEL_TASK_TYPE` + +这类字段当前更像固定语义本地枚举,不是按 `project_type` 变化的字段级配置候选项。 + +本轮目标是: + +- 继续把它们视为本地固定候选项 +- 统一 create / update / list 使用同一套定义源 +- 不误迁到项目类型 field_config + +### 5.3 下拉列表 + +当前已有: + +- `/safety/personnel/task_type/list` + +它直接返回 `PERSONNEL_TASK_TYPE`。 + +本轮这里不要求强行改成 `project_type` 相关 config,而是要求: + +- 明确它是本地固定候选项 +- 前端下拉和 create 校验继续同源 + +### 5.4 清理项 + +personnel 领域本轮的清理重点不是删 enum,而是避免错误地把集团侧 `Personnel` 语义强行映射进来。 + +应避免: + +- 把本地 `personnel_type`、`task_type` 误改造成和集团侧 `code/status` 一一对应 +- 同一字段在 service 和下拉各自维护两套值 + +## 6. 测试关注点 + +personnel 领域 service 层集成测试至少覆盖: + +1. `personnel_type` 合法值和非法值创建校验。 +2. `special` 人员必须校验 `task_type`。 +3. `task_type/list` 返回值与 create 校验使用同源常量。 + diff --git a/docs/enum_refactor/personnel_detail.md b/docs/enum_refactor/personnel_detail.md deleted file mode 100644 index 1988110..0000000 --- a/docs/enum_refactor/personnel_detail.md +++ /dev/null @@ -1,29 +0,0 @@ -# Personnel Detail 领域重构指南 - -## 1. 范围 - -本文档对应用户要求中的 `personnel_detail` 领域。 - -## 2. 当前状态 - -当前仓库里没有独立的 `personnel_detail` backend 模型,也没有对应的 sync schema。 - -本地目前只有: - -- `safety_personnel_qualification` 单表实现 - -也就是说,当前系统没有“人员主表 + 人员明细表”的正式拆分。 - -## 3. 重点结论 - -- 现阶段不建议凭空定义 `personnel_detail` 的取值域重构方案。 -- 如果后续业务真的需要这一层,应先明确它到底对应: - - 人员证书明细 - - 进退场记录 - - 还是资源配置明细 - -## 4. 待修改项清单 - -1. 先确认 `personnel_detail` 是否真的是一个独立领域。 -2. 如果是,先补正式模型与 sync schema,再讨论 enum/field_config。 - diff --git a/docs/enum_refactor/plan.md b/docs/enum_refactor/plan.md index 225689b..b7c5633 100644 --- a/docs/enum_refactor/plan.md +++ b/docs/enum_refactor/plan.md @@ -22,6 +22,60 @@ - enum 字段:继续留在代码中,由 enum 表达固定约束。 - field_config 字段:迁到配置中,由字段级配置表达候选项。 +## 1.1 本轮实际要改的内容 + +这次文档和后续代码改造,不再停留在“抽象上应该统一”的层面,而是直接围绕下面 5 件事展开。 + +### 0. 通用能力 + +要补两类公共能力: + +- 增强现有配置类,在 `ConfigService` 上新增字段级配置读取、映射、校验工具。 +- 增强基于 JSON 导出 CSV 的工具,使字段级配置和现有 config 维护流程能直接联动。 + +### 1. 优化项目初始化过程 + +目标不是只分析 `init_project_data)`,而是把初始化链路改成统一从配置工具取候选项: + +- 整理项目初始化当前实际使用到的 config。 +- 把适合迁移的初始化候选项改为通过新的 config 工具获取后创建。 +- 去掉不适当的 dict 用法,例如把单位塞在 dict `remark` 里。 + +### 2. 优化创建数据的验证 + +这次不改 schema 目录类型系统,但要改 service 层入口。 + +要求是: + +- 在 create 类接口对应的 service 业务入口开头,基于 config 工具校验 code 类字段。 +- 不再继续新增代码字典白名单、硬编码 if/else 白名单。 + +### 3. 统一下拉列表来源 + +这次不仅要梳理下拉列表,还要把它们收口到新的 config 工具: + +- 梳理所有 code 类下拉列表当前返回路径。 +- 能迁移的下拉统一改成通过 config 工具返回。 +- 少量现有写死的下拉列表,例如合同结算类型列表,也要改成动态获取。 + +### 4. 删除废弃 enum / 硬编码字典 + +完成上述迁移后,要清掉已经不再适合作为真源的: + +- 废弃 enum +- 废弃硬编码 dict +- 只用于旧下拉或旧创建校验的代码常量 + +### 5. 自动化测试脚本 + +这次不是只改文档和代码,还要补自动化测试,覆盖至少 3 类集成场景: + +- 初始化脚本 / 初始化 service 集成测试 +- 各业务下拉列表获取集成测试 +- 各业务 create service 集成测试 + +测试脚本写在 `tests` 下,不写到项目根目录。 + ## 2. 重构范围 本次重构只覆盖一类字段: @@ -50,6 +104,12 @@ 本次要做的不是一次性清理所有来源,而是为这些字段补一个统一的字段级配置出口。 +对本轮来说,这里的“统一出口”不是泛指,而是明确服务于 3 条业务链路: + +- 初始化:通过统一配置工具取候选项并创建数据 +- 创建:通过统一配置工具校验 code 类字段 +- 下拉:通过统一配置工具返回选项列表 + ## 3. 不在本次范围内的内容 以下内容明确不在本次重构范围: @@ -61,6 +121,11 @@ - schema 目录下的类型系统改造 - 运行时直接读取 JSON 文件作为配置源 +这里补充两条边界: + +- 不允许为了兼容旧逻辑继续新增 dict `remark` 之类的隐式字段承载方式。 +- 不允许为了减少改动,在新工具落地后继续保留“前端下拉走 config、初始化走 dict、create 校验走代码字典”的三头并存状态。 + 因此,本次允许字段级配置中的 `value / label` 与现有 dict 翻译短期重复。后续如有需要,可再通过脚本生成 dict 或做统一翻译收敛。 ## 4. 核心设计 @@ -225,6 +290,17 @@ async def validate_field_value( ... ``` +如果后续需要覆盖“批量校验多个字段”或“直接返回下拉结构”的场景,可以继续补: + +```python +async def get_field_options_for_frontend(project_type: str, key: str, tenant_id: str | None = None) -> list[dict[str, Any]]: + ... + + +async def validate_field_values(project_type: str, items: list[tuple[str, str]], tenant_id: str | None = None) -> None: + ... +``` + ### 6.2 各接口职责 `get_field_config()`: @@ -255,6 +331,12 @@ async def validate_field_value( - 校验给定值是否在当前项目类型该字段的候选项中 - 校验失败时抛出业务异常 +另外,本轮会直接用到两种典型调用方式: + +- 初始化时:`get_field_options()` / `get_field_option_map()` +- 创建和编辑时:`validate_field_value()` +- 下拉接口时:`get_field_options_for_frontend()` 或 `get_field_options()` + 这里特别强调: - `validate` 是手动调用的 @@ -325,6 +407,12 @@ async def validate_field_value( - JSON 文件仍然采用 config 行列表格式 - 工具负责 JSON 和 `config.csv`、数据库之间的转换 +这块和本轮目标直接对应: + +- 字段级配置调整后,可以直接导出为 `config.csv` +- 初始化改造前后,可以快速对比 config 差异 +- 下拉列表和创建校验依赖的 config,可以单独导出检查,不再手工翻长 CSV + 建议目录: - `scripts/initdata/json_config/` @@ -413,6 +501,12 @@ async def validate_field_value( - 手动创建时从硬编码 dict 判断合法值,改为调用 `validate_field_value()` - 展示名称时从字段选项里直接拿 `label` +对本轮来说,每个领域文档都至少要把下面 3 条链路写清楚: + +- 初始化:这个字段初始化时从哪里取候选项,迁移后改成哪个 config key +- 创建:这个字段 create service 当前怎么校验,迁移后改成哪个 `validate_field_value()` 调用 +- 下拉:这个字段前端当前通过哪个接口取值,迁移后改成哪个 config key 统一返回 + ### 8.4 必须完整对齐集团侧 `enum_config` 领域梳理时,不能只挑 depm 里已经处理过的字段,也不能只挑“看起来最容易改”的字段。 @@ -432,7 +526,7 @@ async def validate_field_value( ## 9. 实施步骤 -### 第一阶段:定义公共 schema 和 `ConfigService` 接口 +### 第一阶段:公共能力 - 明确字段级配置固定 JSON 结构 - 新增 `FieldOptionItem` @@ -440,21 +534,33 @@ async def validate_field_value( - 给关键读取方法加缓存 - 补齐 JSON 序列化 / 反序列化统一处理 - 明确错误处理规则:配置结构不合法时直接报错 +- 增强 JSON / CSV 工具,支持字段级配置导入导出 -### 第二阶段:重整 JSON / CSV 工具 +### 第二阶段:初始化改造 -- 建立 `scripts/initdata/json_config/` 目录规范 -- 支持一 key 一文件 -- 支持 `json_config` 批量生成 `config.csv` -- 支持 `json_config` 批量导入数据库 -- 支持从 `config.csv` / 数据库反向导出 JSON +- 逐项整理 `init_project_data)` 当前真实依赖的 config / dict +- 把 `preparation`、`production`、`power` 这类平铺型初始化模板迁到 config +- 整理 `material` 初始化,把单位等附属属性从不适当的 dict 备注迁出 +- 清理 `init_project_data)` 中取了但不该继续作为真源的 dict 依赖 -### 第三阶段:按领域逐个梳理并迁移 +### 第三阶段:创建校验改造 -- 按领域编写独立重构文档 -- 明确该领域哪些字段迁到 `field_config` -- 改造初始化流程和手动创建校验流程 -- 下线对应的硬编码白名单和不再适合承担初始化来源的 dict 使用方式 +- 为 contract / material / construction 等 create service 入口补基于 config 的 code 校验 +- 把当前分散在 service 内的硬编码白名单逐步迁到 config 工具 +- 保留真正应该继续存在的代码 enum,不把固定语义状态误迁到 config + +### 第四阶段:下拉列表改造 + +- 梳理 code 类下拉接口 +- 下拉统一改成通过 config 工具返回 +- 少量仍写死的下拉改为动态获取 +- 保证前端下拉、初始化候选项、create 校验三者同源 + +### 第五阶段:清理和测试 + +- 删除废弃 enum 和废弃代码字典 +- 删除不再适合作为真源的 dict 依赖 +- 补 service 层集成测试和初始化测试 ## 10. 产出要求 @@ -465,9 +571,42 @@ async def validate_field_value( - 一套明确的缓存与缓存失效规则 - 一套 JSON / CSV / DB 三者之间的转换与维护规范 - 一组领域级重构文档 +- 一组初始化 / 下拉 / create 的 service 层集成测试 判断本次重构是否成功的标准不是“历史实现全部统一”,而是: - 后续新增一个与项目类型初始化相关的字段时,知道应优先写入字段级配置总表 - 业务代码可以通过 `ConfigService` 统一获取 options / map / label / validate -- 初始化流程和手动创建校验可以逐步收敛到同一来源 \ No newline at end of file +- 初始化流程和手动创建校验可以逐步收敛到同一来源 +- 前端下拉、初始化、create 校验不再各自读不同真源 + +## 11. 自动化测试要求 + +这次测试不是只补 unit test,而是以 service 层集成测试为主。 + +至少覆盖下面 3 组: + +### 11.1 初始化测试 + +- 覆盖项目初始化入口 +- 校验初始化创建出的 preparation / production / contract / material / power / construction 数据是否来自目标 config +- 校验错误配置时直接报错,不做静默降级 + +### 11.2 下拉列表测试 + +- 覆盖 contract / material / construction 等核心下拉接口或对应 service +- 校验返回项是否来自预期 config key +- 校验下拉返回和初始化候选项是否一致 + +### 11.3 Create 校验测试 + +- 直接走 service 层 create +- 对合法 code 校验通过 +- 对非法 code 明确抛错 +- 校验不再依赖旧硬编码字典或 dict `remark` + +### 11.4 测试组织方式 + +- 测试代码写在 `tests/` 下 +- 优先写 service 层集成测试,不在项目根目录放临时脚本 +- 如果需要初始化测试数据,应复用现有测试体系,不改 `schemas/`、不改 `filtered_datasource/` \ No newline at end of file diff --git a/docs/enum_refactor/preparation.md b/docs/enum_refactor/preparation.md index 9966f71..6a24b3d 100644 --- a/docs/enum_refactor/preparation.md +++ b/docs/enum_refactor/preparation.md @@ -87,3 +87,47 @@ 1. 将 `preparation.code` 正式迁到 `field_config`。 2. 区分“节点编码范围”与“审批状态/存在性”这两类字段,不要混在一个配置源里。 3. 将 `approval_status` 接入通用审批状态定义。 + +## 5. 按本轮要求的改造落点 + +preparation 属于本轮最适合先收口的一类: + +- 初始化平铺 +- code 字段明确 +- 旧代码里已经出现多份硬编码映射 + +### 5.1 初始化 + +当前 preparation 初始化直接来自 `preparation_{project_type}` dict。 + +本轮目标是: + +- 把 `preparation.code` 迁到 field config +- 初始化改成通过 config 工具读取候选项再批量创建 +- 不再依赖 dict 作为初始化真源 + +### 5.2 Create 校验 + +当前 `PreparationCreate` / `PreparationService` 没有统一的 code 白名单校验入口。 + +本轮目标是: + +- 在 create / update 对应的 service 入口开头,基于 config 校验 `preparation.code` +- 不再新增本地 code 白名单或名称反查表来兜底 + +### 5.3 下拉列表 + +preparation 当前没有单独的“节点编码下拉接口”,前端更多拿的是项目实例列表 `/preparation/list`。 + +这轮文档上的要求是: + +- 如果后续需要独立 code 候选项下拉,应直接从 `preparation.code` field config 返回 + +## 6. 测试关注点 + +preparation 领域 service 层集成测试至少覆盖: + +1. 初始化:项目初始化时生成的节点是否来自目标 field config。 +2. Create / Update:合法 `code` 可以写入,非法 `code` 明确报错。 +3. 列表:项目实例列表顺序是否来自配置顺序,而不是硬编码 `sort_code`。 + diff --git a/docs/enum_refactor/production.md b/docs/enum_refactor/production.md index 4b0472c..fb5e250 100644 --- a/docs/enum_refactor/production.md +++ b/docs/enum_refactor/production.md @@ -86,3 +86,43 @@ 1. 将 `production.code` 正式迁到 `field_config`。 2. 将项目初始化里对 `production_{project_type}` 的依赖逐步收敛到统一字段配置入口。 3. 将 `approval_status` 接入通用审批状态定义。 + +## 5. 按本轮要求的改造落点 + +production 和 preparation 一样,属于本轮适合优先收口的平铺型节点编码领域。 + +### 5.1 初始化 + +当前 production 初始化直接来自 `production_{project_type}` dict。 + +本轮目标是: + +- 把 `production.code` 迁到 field config +- 初始化改成通过 config 工具读取候选项后批量创建 +- 不再依赖 dict 作为初始化真源 +∏ +### 5.2 Create 校验 + +当前 production 领域也没有统一的 code 候选项校验入口。 + +本轮目标是: + +- 在 create / update 的 service 入口开头,基于 config 校验 `production.code` +- 不再继续用本地硬编码列表承担合法值白名单职责 + +### 5.3 下拉列表 + +production 当前也没有单独的 code 候选项下拉接口,更多是返回项目实例列表 `/production/list`。 + +本轮要求是: + +- 如果需要单独 code 候选项下拉,应直接从 `production.code` field config 返回 + +## 6. 测试关注点 + +production 领域 service 层集成测试至少覆盖: + +1. 初始化:项目初始化时生成的投产节点是否来自目标 field config。 +2. Create / Update:合法 `code` 可以写入,非法 `code` 明确报错。 +3. 列表:项目实例列表顺序是否来自配置顺序,而不是硬编码 `sort_code`。 + diff --git a/docs/enum_refactor/project.md b/docs/enum_refactor/project.md index cf6d652..250d227 100644 --- a/docs/enum_refactor/project.md +++ b/docs/enum_refactor/project.md @@ -29,7 +29,7 @@ 当前使用: -- `ProjectService._init_project_data()` 用它作为初始化路由键,去取 `preparation_{project_type}`、`production_{project_type}`、`power_{project_type}` 等配置 +- `ProjectService.init_project_data)` 用它作为初始化路由键,去取 `preparation_{project_type}`、`production_{project_type}`、`power_{project_type}` 等配置 - construction 模块的部分接口和工具也直接复用 `ProjectType` 当前问题: @@ -164,23 +164,26 @@ - 定义重复 - 模型层不是 enum - 当前状态值由服务逻辑推导,但接口层没有统一约束 -- `early` 是否正式存在还没定 +- 当前正式状态集合必须补上 `early` +- 当前库里还存在 `NULL`,这说明状态机和历史数据都还没收口干净 项目侧取值范围: -- `app/common/enums.py` 和 `app/modules/project/enums.py` 当前都只有 4 个正式值:`preparation`、`started`、`partially_connected`、`completed` -- `ProjectService.change_project_status()` 当前实际也只会写入这 4 个值,或者写入 `None` +- 目标正式值应为:`early`、`preparation`、`started`、`partially_connected`、`completed` +- 当前代码定义和服务推导还没有完整覆盖这 5 个值 +- 当前实际数据里还存在 `NULL`,这应视为待清理异常值,而不是正式取值 集团侧差异: -- 集团侧比项目侧多一个值:`early` -- 其余 `preparation`、`started`、`partially_connected`、`completed` 两边都能对上 +- 项目侧最终也要纳入 `early` +- 因此这个字段的目标差异不是“是否要 early”,而是“项目侧何时补齐 early 并清掉 NULL” - 同步 schema 里的 `status` 当前也是 `Optional[str]`,没有正式 enum 类型 结论: -- 暂不拍板 -- 先确认正式状态集合,再决定保留代码 enum 还是转 `field_config` +- 保留为代码 enum +- 正式状态集合按 `early`、`preparation`、`started`、`partially_connected`、`completed` 收口 +- `NULL` 不是正式值,必须通过数据修复和状态回填消除 ### 2.5 `progress_type` @@ -244,33 +247,27 @@ - 定义分叉 - 主表是 enum,但部分接口入参仍是字符串 +- 项目侧正式口径应保留 `OTHER` 项目侧取值范围: -- `app/modules/project/enums.py` 当前是 4 个值:`NONE`、`LAND`、`TRANSMISSION`、`BOTH` -- `app/common/enums.py` 和初始化 `dict_data.csv` 当前是 5 个值:`NONE`、`LAND`、`TRANSMISSION`、`BOTH`、`OTHER` +- 项目侧正式值应为:`NONE`、`LAND`、`TRANSMISSION`、`BOTH`、`OTHER` +- 当前 `app/common/enums.py` 和初始化 `dict_data.csv` 已包含这 5 个值 +- 当前 `app/modules/project/enums.py` 还少 `OTHER` 集团侧差异: - 集团侧同步 schema 中,`key_constraints` 也是正式 enum - 同步 schema 的取值项是:`NONE`、`LAND`、`TRANSMISSION`、`BOTH`、`QT` -- 项目侧 `app/common/enums.py` + 初始化数据当前是:`NONE`、`LAND`、`TRANSMISSION`、`BOTH`、`OTHER` -- 项目侧 `app/modules/project/enums.py` 当前是:`NONE`、`LAND`、`TRANSMISSION`、`BOTH` - -可见当前不是“无法对照”,而是至少存在两层明确差异: - -- 集团侧同步 schema 用的是 `QT` -- 项目侧本地一处用 `OTHER`,一处甚至没有第五个值 - -困难: - -- 项目侧自己就已经分叉,当前无法直接写出唯一正式范围 -- 在先统一本地定义前,任何“正式取值范围”都只能算候选,不算最终结论 +- 项目侧正式口径保留 `OTHER` +- 这里需要备注的差异只有一条:集团侧用 `QT`,项目侧用 `OTHER` 结论: - 保留为代码 enum -- 先统一正式定义,再补 schema 校验 +- 项目侧按 `OTHER` 收口 +- 在同步和推送链路上单独备注 `OTHER/QT` 差异 +- 同时补齐 project 模块那套缺失的 `OTHER` ### 2.7 `build_methods` @@ -325,28 +322,78 @@ - `project_type` 是初始化路由键,必须保留为代码 enum。 - `project_subtype`、`syxs` 是典型的按项目类型变化字段,应迁到 `field_config`。 - `progress_type`、`key_constraints`、`build_methods` 都已经在项目侧真实使用,不是可忽略字段;它们应保留为代码 enum,但当前实现没有收紧干净。 -- `status` 当前最不稳定,因为它一边像固定状态,一边又还没把正式状态集合收敛清楚。 +- `status` 也应保留为代码 enum,正式集合必须包含 `early`,并且要清掉历史 `NULL`。 +- `key_constraints` 项目侧正式口径保留 `OTHER`,只需要在文档和同步链路上备注集团侧使用 `QT`。 ## 4. 待修改项清单 1. 统一 `ProjectType` 定义,只保留一套正式 enum。 -2. 统一 `ProjectStatus` 定义,并先确认是否正式支持 `early`。 +2. 统一 `ProjectStatus` 定义,并把 `early` 纳入正式状态集合。 3. 统一 `ProgressType` 定义,只保留一套正式 enum。 -4. 统一 `KeyConstraints` 定义,先确定 `OTHER` 是否保留。 +4. 统一 `KeyConstraints` 定义,项目侧正式保留 `OTHER`。 5. 将 `project.project_subtype` 改为字段级配置,放入 `field_config`。 6. 将 `project.syxs` 改为字段级配置,放入 `field_config`。 7. 为 `project_type` 在 schema 层增加正式 enum 校验,不再使用裸 `str`。 8. 为 `progress_type` 在创建入口增加正式 enum 校验。 9. 为 `build_methods` 在 schema 层增加正式 enum 校验。 10. 为 `key_constraints` 在仍使用字符串入参的接口上补 enum 校验。 -11. 评估 `Project` 模型中的 `project_type`、`status`、`build_methods` 是否要收紧为数据库 enum;如果暂不收紧,也要先统一 schema 校验。 -12. 为 `project_subtype`、`syxs` 增加统一读取入口:`get_field_options()` / `validate_field_value()`。 -13. 明确 `status` 的唯一来源:是继续由 `change_project_status()` 推导,还是允许手动写入。 -14. 明确 `progress_type` / `key_constraints` 在 `project_detail_data` 链路中的正式定义源,避免主表和详情链路再各用一套。 +11. 清理 `Project.status` 的历史 `NULL` 数据,并补状态回填逻辑,确保 `NULL` 不再出现。 +12. 评估 `Project` 模型中的 `project_type`、`status`、`build_methods` 是否要收紧为数据库 enum;如果暂不收紧,也要先统一 schema 校验。 +13. 为 `project_subtype`、`syxs` 增加统一读取入口:`get_field_options()` / `validate_field_value()`。 +14. 明确 `status` 的唯一来源:是继续由 `change_project_status()` 推导,还是允许手动写入。 ## 5. 推荐落地顺序 1. 先统一 `ProjectType`、`ProgressType`、`KeyConstraints`、`ProjectStatus` 的定义源。 2. 再补 schema 层校验,把 `project_type`、`progress_type`、`build_methods`、`key_constraints` 的裸字符串入口收紧。 -3. 再把 `project_subtype`、`syxs` 迁到 `field_config`。 -4. 最后处理 `status`,因为它依赖正式状态集合先收敛。 \ No newline at end of file +3. 先补 `status=early` 和历史 `NULL` 清理,再把 `project_subtype`、`syxs` 迁到 `field_config`。 + +## 6. 按本轮要求的改造落点 + +project 领域这轮要区分两类字段: + +- 固定语义枚举:继续保留代码 enum +- 按项目类型变化的候选项:迁到 field config + +### 6.1 初始化 + +Project 本身不直接批量初始化子表候选项,但 `project_type` 是整个初始化流程的路由键。 + +因此本轮在 project 领域的初始化要求是: + +- `project_type` 继续保留代码 enum,作为 config 路由键 +- `project_subtype`、`syxs` 如果进入初始化或默认值链路,应直接从 field config 读取 + +### 6.2 Create / Update 校验 + +这轮 project 领域要分开处理: + +- `project_type`、`status`、`progress_type`、`key_constraints`、`build_methods`:继续按代码 enum 收紧 +- `project_subtype`、`syxs`:通过新的 config 工具校验 + +也就是说,project 领域不是“全部配置化”,而是“固定 enum 和 field config 分流后分别收紧”。 + +### 6.3 下拉列表 + +当前 project 领域里: + +- `project_type`、`progress_type`、`key_constraints`、`build_methods` 更适合继续由代码 enum 提供候选项 +- `project_subtype`、`syxs` 后续如果提供前端下拉,应直接走 field config + +本轮目标是明确边界,避免把本地固定枚举误迁到 config,也避免把按项目类型变化字段继续留成自由字符串。 + +### 6.4 清理项 + +project 领域本轮改造完成后,应清理: + +- 重复的 enum 定义源 +- 仍以裸字符串接收固定 enum 字段的入口 +- `status` 历史 `NULL` 值 + +## 7. 测试关注点 + +project 领域 service 层集成测试至少覆盖: + +1. `project_type`、`progress_type`、`key_constraints`、`build_methods` 的合法/非法值校验。 +2. `project_subtype`、`syxs` 的 field config 读取与校验。 +3. `status` 状态推导补齐 `early` 且不再产生 `NULL`。 diff --git a/docs/enum_refactor/project_detail_data.md b/docs/enum_refactor/project_detail_data.md deleted file mode 100644 index 44ed266..0000000 --- a/docs/enum_refactor/project_detail_data.md +++ /dev/null @@ -1,69 +0,0 @@ -# Project Detail Data 领域重构指南 - -## 1. 范围 - -本文档对应 backend 的 `project_detail_data` 聚合明细表。 - -当前优先关注: - -- `project_detail_data.key` - -## 2. 字段盘点 - -### 2.1 `key` - -当前实现: - -- backend 模型里是 `String` -- backend schema 里也是 `str` -- 没有本地正式 enum -- 实际取值由 `ProjectDetailDataService` 和业务逻辑共同决定 - -项目侧取值范围: - -- 当前代码里明确出现的 key 至少包括: - - `authorized_capacity` - - `investment_decision_capacity` - - `plan_construction_capacity` - - `actual_construction_capacity` - - `key_constraints` - - `constructing_capacity` - - `production_capacity` - - `ensure_production_capacity` - - `climb_production_capacity` - - `challenge_production_capacity` - - `dynamic_total_investment_price` - - `complete_investment_price` - - `complete_investment_rate` - - `qualified_production_score` - -集团侧差异: - -- 同步 schema `project_extensions/project_detail.py` 已定义了 `ProjectDetailKey` enum -- 同步 schema 当前只有 7 个 key: - - `plan_construction` - - `actual_construction` - - `production` - - `ensure_production` - - `climb_production` - - `challenge_production` - - `complete_investment` -- 项目侧和同步侧当前不仅数量不一致,命名也不一致 - -结论: - -- 这是当前差异最大的领域之一 -- 不能直接把 backend `key` 当成已经对齐的枚举字段 - -## 3. 重点结论 - -- backend `project_detail_data.key` 当前是开放字符串集合。 -- 同步侧已经有一套更收敛的 `ProjectDetailKey` enum。 -- 两边差异不只是“少几个值”,而是命名体系都还没统一。 - -## 4. 待修改项清单 - -1. 为 `project_detail_data.key` 建立正式定义表,不再继续开放字符串。 -2. 明确 backend key 与同步 `ProjectDetailKey` 的映射关系。 -3. 区分哪些 key 应继续保留在 `project_detail_data`,哪些应回归 Project 主表或扩展表。 -4. 先统一命名,再决定是否直接迁成代码 enum 或 `field_config`。