增加项目侧重构文档

This commit is contained in:
strepsiades
2026-03-12 14:54:48 +08:00
parent 758b1c4f77
commit 84b3b7652a
16 changed files with 1485 additions and 458 deletions
+72
View File
@@ -184,3 +184,75 @@ construction 领域后续梳理时,至少要回答下面这些问题:
5. `construction_node_config` 中哪些数据只是节点补充信息,哪些其实已经在承担字段候选项职责。
如果发现某字段既参与初始化又参与手动创建,就应优先考虑迁到字段级配置。
## 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` 可以创建,非法值明确报错。
@@ -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”和“审批状态”并存的混乱状态。
+76
View File
@@ -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 明确报错。
+52
View File
@@ -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` 跟随合同主表时,不出现与合同主表不一致的脏数据。
@@ -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. 明确结算明细审批状态与通用审批体系的正式映射。
+76 -19
View File
@@ -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
+757
View File
@@ -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/创建校验与配置同源,配置来源本身相对已经收敛。
+74
View File
@@ -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` 明确报错,不再依赖默认兜底。
-109
View File
@@ -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` 与通用审批状态定义彻底对齐。
+62
View File
@@ -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 校验使用同源常量。
-29
View File
@@ -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。
+151 -12
View File
@@ -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
- 初始化流程和手动创建校验可以逐步收敛到同一来源
- 前端下拉、初始化、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/`
+44
View File
@@ -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`
+40
View File
@@ -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`
+79 -32
View File
@@ -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`,因为它依赖正式状态集合先收敛。
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`
-69
View File
@@ -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`