# Dam Fixes Impact Report — Layout, AutoNumber, Label

**Date**: 2026-03-31
**Method**: Issue-by-issue verification against `ywang-official/MetaDrivenPlatform4AI` issues + code-level root cause tracing in `pingcap-inc/MetaDrivenPlatform4AI`

---

## Dam 1: Layout 种子数据是死代码

### 缺陷描述

NexusCRM 的页面布局系统有 3 层解析机制（`metadata_layouts.go:28-126`）：

- **Tier 1**: 按用户 Profile 查找分配的布局 → 从未种过数据，永远为空
- **Tier 2**: 查找对象的默认布局 → 数据在每次启动时被删除
- **Tier 3**: `generateSmartLayout()` 从 live schema 自动生成 → 所有对象都走到这里

`standard_layouts.json` 文件包含 9 个 Salesforce 级别的精确布局（Lead, Case, Campaign, Order, Contract, Account, Contact, Opportunity, Quote），每个都有正确的 section 名称和字段分配。但这个文件是**死代码**——没有任何 Go 代码读取它（无 `//go:embed`，无 `json.Unmarshal`，无 `os.ReadFile`）。

同时，`CleanupHardcodedLayouts()` (`layouts.go:42-53`) 在每次服务启动时执行：

```go
rawDB.Exec("DELETE FROM `_System_PageLayout` WHERE `Id` LIKE 'std_layout_%%'")
```

被 `main.go:157` 无条件调用。注释解释了删除原因（`layouts.go:36-39`）：

> *"Those rows contained only 8-27 standard fields, hiding 200+ custom fields."*

Tier 3 的 `generateSmartLayout()` (`metadata_layouts.go:442-588`) 用字段名模式匹配生成 section：
- 含 Street/City/State/Postal/Country → "Address Information"
- 含 Description/Notes/Comments → "Description"
- CreatedDate/LastModified 等 → "System Information"
- 其他所有字段 → "Additional Details"

它生成的是泛化 section（"Information"、"Additional Details"），而 Salesforce 的 Account 有 "Account Information"、"TiDB Cloud"、"Intent Insights"、"Account Plan Strategy"、"SLA" 等业务专属 section。自动生成算法不可能猜到这些业务命名。

### 代码证据

| 文件 | 行号 | 内容 |
|------|------|------|
| `backend/internal/bootstrap/standard_layouts.json` | 全文件 | 9 个精确布局，ID 为 `std_layout_Account` 等。死代码。 |
| `backend/internal/bootstrap/layouts.go` | 42-53 | `CleanupHardcodedLayouts()` — DELETE 所有 `std_layout_*` 行 |
| `backend/cmd/server/main.go` | 157 | `bootstrap.CleanupHardcodedLayouts(db.DB())` 无条件调用 |
| `backend/internal/features/metadata/metadata_layouts.go` | 28-49 | Tier 1/2 查找路径——代码正确，但 DB 中无数据 |
| `backend/internal/features/metadata/metadata_layouts.go` | 79-106 | Tier 3 `generateSmartLayout()` 回退——所有对象都走到这里 |
| `backend/internal/features/metadata/metadata_layouts.go` | 442-588 | Smart layout 生成算法——5 个泛化 section |
| `backend/internal/features/metadata/metadata_layouts.go` | 69-76 | Version-bump 删除机制——`IsCustomized=0` 的布局在版本号升级时被删除重建 |

### 影响面：逐 Issue 验证

#### 自动解决（9 个 OPEN issues）

| Issue # | 标题 | 为什么 layout 修复能解决 |
|---------|------|-------------------------|
| **#118** | Campaign: Name field missing from New/Edit Campaign form | 自动生成的 layout 遗漏了 Name 字段或放错位置。精确 Campaign layout 的 Information section 第一个字段就是 Name。 |
| **#170** | TestMetadataService_GetLayout_FromRepo returns wrong layout ID and label | 测试期望 `layout_1` / `Default`，实际得到 `default_Account` / `Default Layout`（自动生成的 ID 和名字）。加载精确 layout 后测试期望与数据一致。 |
| **#198** | Remove or wire up dead standard_layouts.json bootstrap file | 这个 issue 就是要求加载这个死代码文件。修复直接关闭此 issue。 |
| **#201** | Account Layout: Missing custom sections (TiDB Cloud, Intent Insights, Account Plan Strategy, SLA) | 自动生成只有 5 个泛化 section。`standard_layouts.json` 中的 Account layout 有正确的业务 section。 |
| **#205** | Account Layout: Custom fields misclassified into wrong sections (SLA fields in Description, TiDB fields in Additional Details) | `classifyField()` 模式匹配把 SLA 字段放进了 Description section。精确 layout 有明确的字段-to-section 分配，不依赖模式匹配。 |
| **#228** | Opportunity Layout: Duplicate fields displayed (Forecast Category x2, Opportunity Number x2, Quantity x2) | `generateSmartLayout` 包含所有 schema 字段无去重。精确 Opportunity layout 每个字段只出现一次。 |
| **#237** | Lead Page Layouts API returns null — Setup UI broken, edit form renders all 292 fields | Layout endpoint 返回 null 因为 DB 中无 Lead layout（被 `CleanupHardcodedLayouts` 删了）。加载 Lead layout 后 API 返回有效数据。 |
| **#295** | Campaign Detail: Missing 'PLANNING' and 'CAMPAIGN STATISTICS' sections — fields collapsed into generic 'ADDITIONAL DETAILS' | 自动生成无法产生 "PLANNING" 和 "CAMPAIGN STATISTICS" 这样的业务 section 名。精确 Campaign layout 包含这些 section。 |
| **#300** | New Campaign form ignores configured page layout — shows auto-generated flat form with wrong sections and extra fields | 表单显示自动生成 layout 因为没有存储的 layout 能存活 bootstrap。加载 Campaign layout 后表单使用精确 section。 |

#### 部分改善（6 个 OPEN issues）

| Issue # | 标题 | Layout 修复能解决什么 | 剩余需要修的 |
|---------|------|---------------------|-------------|
| **#61** | Campaign Edit Form layout does not match Salesforce | Layout 存在后 detail view 的 section 正确。 | 前端 bug：`ObjectView.tsx:302` 在 edit mode 传 `layout={undefined}`，需要修前端代码让 edit mode 也传 layout。 |
| **#134** | Filter auto-generated layouts in addFieldToLayout | 精确 layout 存在后 `layouts[0]` 更可能是正确的 layout。 | 代码 bug 仍在：`addFieldToLayout` 不过滤 auto-generated layout，新增自定义字段时仍可能选错。 |
| **#171** | TestMetadataService_GetLayout_FallbackToGenerated includes system field CreatedDate | 精确 layout 存在后大部分对象不走 fallback，测试失败频率降低。 | Tier 3 `generateSmartLayout` 仍然包含 CreatedDate 系统字段，任何没有精确 layout 的对象仍会触发此问题。 |
| **#282** | Quote Edit form missing page layout — renders all 100+ fields alphabetically with raw camelCase labels | 精确 Quote layout 提供 5 section 27 字段的结构。 | raw camelCase labels 是 label 系统问题（Dam 3），需要独立修复。edit mode 的 `layout={undefined}` bug（同 #61）也需要修前端。 |
| **#296** | New Campaign form: Name field missing — nameless campaigns can be created with no validation | Layout 修复让 Name 字段出现在表单中。 | 后端缺少 required field 验证——即使 Name 出现在表单中，用户留空提交也不会被拦截。需要修 `persistence_insert.go` 的 required check。 |
| **#126** | Map record types to distinct page layouts in DescribeLayouts | DB 中有精确 layout 是实现 RT-to-Layout 映射的前提。 | 映射代码还没写——`sobject_describe.go:330` 仍然硬编码所有 RT 指向同一 layout ID。 |

#### 无效（19 个 issues — 根因不是 layout）

| Issue # | 标题 | 真实根因 |
|---------|------|---------|
| **#47** | Lead Status path/progress bar missing | 缺少前端 StatusPath 组件，不是 layout 数据问题 |
| **#84** | Contract detail missing status progress bar and Upload buttons | 同 #47——缺少前端组件 + 缺少 action button 实现 |
| **#169** | Quote detail page missing status progress bar | 同 #47——缺少前端组件 |
| **#183** | Opportunity/Contact missing 'Activity' tab | 缺少完整的 Activity tab 实现（Task/Event 查询 + Timeline 组件） |
| **#194** | Missing Activity tab on Opportunity/Contact | 同 #183 |
| **#215** | Fix rows field in layout sections: set to len(layoutRows) not total field count | `sobject_describe.go` 中的算术 bug，layout 数据不影响 |
| **#250** | Quote Number column appears twice in list view | List view 列配置（`_System_Object.ListFields`），不是 page layout |
| **#289** | Campaign Detail: Highlights panel not rendered | Compact layout 渲染是独立前端组件，不是 page layout section |
| **#301** | New Campaign: Status defaults to blank instead of 'Planned' | Picklist 默认值由 `_System_Field.DefaultValue` 控制，不是 layout |
| **#303** | New Campaign: Owner field not pre-populated with current user | Owner 预填是记录创建逻辑，不是 layout |
| **#114** | Update DescribeLayouts to return real record types | `sobject_describe.go` 代码硬编码 Master RT，需要改代码不是加数据 |
| **#120** | Campaign: Duplicate Edit/Delete buttons and Owner shows UUID | 前端渲染 bug（按钮重复）+ lookup 字段解析 bug（UUID 未转用户名） |
| **#202** | Account Layout: ~70 custom fields from PDF not in system_tables.json | 字段元数据定义缺失，需要加到 `system_tables.json`，不是 layout |
| **#256** | 'Upload Archived OF' button missing from Order detail | 缺少前端 action button 实现 |
| **#283** | Quote Columns button produces no dialog | 前端列选择器组件 bug |
| **#284** | Quote Record Type "Next" button has no effect | Record Type 选择弹窗的前端事件处理 bug |
| **#288** | Quote 'Upload Files' button missing | 缺少文件上传功能实现 |
| **#298** | Campaign Detail: Clone action missing from header | 缺少 Clone action 实现 |
| **#302** | New Campaign: Owner field not pre-populated | 同 #303——记录创建逻辑，不是 layout |

---

## Dam 2: AutoNumber 字段被标记为可编辑

### 缺陷描述

在 Salesforce 中，AutoNumber 类型字段（如 QuoteNumber、CaseNumber、OrderNumber、ContractNumber）是**固有只读**的——不管用户权限如何，`updateable` 始终为 `false`。这和 Formula 字段一样，是字段类型本身的属性，不是权限控制。

NexusCRM 的 UI API 和 Describe API 在计算 `updateable` 时遗漏了 AutoNumber 检查：

**UI API** (`primitives.go:64-65`):
```go
createable := !isSystemAuditField(f.APIName) && !f.IsSystem && f.Formula == nil
updateable := !isSystemAuditField(f.APIName) && !f.IsSystem && f.Formula == nil
```

三个条件：不是系统审计字段、不是系统字段、不是 Formula。**没有检查 AutoNumber。**

QuoteNumber 是 AutoNumber 类型，不是 SystemAuditField，不是 IsSystem，也没有 Formula → `updateable = true`。

**Describe API** (`sobject_describe.go:155`) 有同样的遗漏：
```go
Updateable: canEdit && fieldEditable,
```
`fieldEditable` 只检查 `!isSystemAuditField && !f.IsSystem && f.Formula == nil`。

**结果链**:

```
后端 describe/object-info API 返回 QuoteNumber.updateable = true
        ↓
前端 MetadataRecordForm.tsx:380
  const isEditable = isEdit ? f.updateable : f.createable
  → QuoteNumber 出现在编辑表单中
        ↓
前端 MetadataRecordForm.tsx:102
  if (fieldMeta.updateable) cleanData[key] = data[key]
  → QuoteNumber 被包含在 PATCH payload 中
        ↓
后端 SchemaValidator schema_validator.go:57-60
  if normalizedType == FieldTypeAutonumber && isUpdate →
  返回 "Field 'QuoteNumber' is an AutoNumber and cannot be updated."
  → 整个 PATCH 请求被 400 拒绝
        ↓
前端无 error toast（独立问题）→ 用户看到的是 Save 后什么都没发生
```

特别注意：前端在 create mode 有一个 partial workaround（`MetadataRecordForm.tsx:383`）：
```tsx
const isAutoNumber = f.autoNumber && !isEdit;
```
这只在 create mode 隐藏 AutoNumber 字段，edit mode 不隐藏——因为前端信任后端的 `updateable` flag。

### 代码证据

| 文件 | 行号 | 内容 |
|------|------|------|
| `backend/internal/features/uiapi/primitives.go` | 64-65 | UI API `updateable` 计算——缺少 AutoNumber 检查 |
| `backend/internal/interfaces/rest/sobject_describe.go` | 155 | Describe API `Updateable` 计算——同样缺少 AutoNumber 检查 |
| `backend/internal/interfaces/rest/sobject_describe.go` | 113 | `AutoNumber: f.Type == constants.FieldTypeAutonumber` — AutoNumber flag 本身是正确设置的 |
| `backend/pkg/validation/schema_validator.go` | 57-61 | `"Field '%s' is an AutoNumber and cannot be updated."` — 后端正确拒绝 |
| `frontend/src/features/record/components/MetadataRecordForm.tsx` | 102 | `fieldMeta.updateable` 控制 PATCH payload 包含哪些字段 |
| `frontend/src/features/record/components/MetadataRecordForm.tsx` | 380 | `f.updateable` 控制 edit mode 显示哪些字段 |
| `frontend/src/features/record/components/MetadataRecordForm.tsx` | 383 | `f.autoNumber && !isEdit` — 只在 create mode 隐藏 AutoNumber |
| `backend/internal/bootstrap/schema.go` | 561-566 | 定义了 4 个 AutoNumber 字段 |
| `shared/pkg/constants/enums.go` | 31 | `FieldTypeAutonumber` 常量定义 |

### 受影响的 AutoNumber 字段

| 对象 | 字段 | 格式 |
|------|------|------|
| Quote | QuoteNumber | {00000000} |
| Case | CaseNumber | {00000000} |
| Order | OrderNumber | {00000000} |
| Contract | ContractNumber | {00000000} |

### 影响面：逐 Issue 验证

#### 直接解决（1 个 P0）

| Issue # | 标题 | State | 为什么是直接根因 |
|---------|------|-------|-----------------|
| **#286** | bug(quote): Edit form Save fails — AutoNumber__c field included in update payload | OPEN P0 | Issue 描述完全匹配 bug 机制：`updateable=true` 导致 AutoNumber 进入 PATCH payload，后端 SchemaValidator 拒绝。修复 `updateable=false` 后前端自动排除该字段，Save 成功。 |

#### 部分改善（2 个）

| Issue # | 标题 | AutoNumber 修复能解决什么 | 剩余需要修的 |
|---------|------|--------------------------|-------------|
| **#289** | bug(quote): Edit form missing page layout — renders all 100+ fields alphabetically with raw camelCase labels | AutoNumber 字段从 edit form 中消失（因为 `updateable=false`），减少可编辑字段数量。 | 主要根因是 layout 问题（Dam 1）：100+ 字段平铺是因为没有布局 section。raw camelCase labels 是 label 问题（Dam 3）。 |
| **#311** | [Edit Campaign] Campaign Statistics fields shown as editable inputs instead of read-only display | 同类 bug——calculated/rollup 字段也应标为 `updateable=false`。修复 AutoNumber 时应同时加 calculated/rollup 检查。 | 需要确认 Campaign Statistics 字段的具体类型是 rollup 还是其他。 |

#### 无关（8 个）

| Issue # | 标题 | 真实根因 |
|---------|------|---------|
| **#287** | Quote Name blank and Account not linked after creation | 创建时 Name 字段未从 Opportunity 继承，是记录创建逻辑问题 |
| **#81** | Contract record title displays raw UUID | AutoNumber 生成未在创建时触发，是 auto-number generation 问题 |
| **#252** | Order Edit form fails - boolean float64 | JSON 类型 coercion 问题（float64 vs bool），完全无关 |
| **#41** | Quote record title shows raw UUID | 同 #81——auto-number 生成问题，已 CLOSED |
| **#83** | New Contract saves with all fields empty | 缺少 required field 验证，与 updateable 无关 |
| **#282** | Quote Number column appears twice in list view | List view 列配置问题 |
| **#283** | Columns button produces no dialog | 前端 UI 组件 bug |
| **#284** | Record Type "Next" button has no effect | Record Type 弹窗事件处理 bug |

---

## Dam 3: 字段 Label 生成和保护机制

### 缺陷描述

字段 Label 经历三个阶段：

**阶段 1：初始种入**

`system_tables.json` 定义标准字段时可以包含 `"label"` 属性。Bootstrap 用 `INSERT IGNORE` 写入 `_System_Field`——如果行已存在则跳过。PR #481 已为 417 个字段补充了 label。

**阶段 2：自动发现**

`discover_tables.go:20` 扫描 TiDB 的 `INFORMATION_SCHEMA.COLUMNS`，发现不在 `system_tables.json` 中的物理列。对每个新列，调用 `fieldNameToLabel(apiName)` 生成 label，然后 `INSERT IGNORE` 写入。

`fieldNameToLabel()` (`discover_tables.go:315-336`) 的处理流程：

```
输入 → 去 __c 后缀 → 去尾部 Id → splitCamelCase → 下划线转空格 → 首字母大写 → 输出
```

`splitCamelCase` 的正则是 `([a-z])([A-Z])`——**只在小写字母后跟大写字母时插入空格**。

对不同输入的实际行为：

| 输入 | 输出 | 正确吗？ | 问题 |
|------|------|---------|------|
| `CreatedDate` | `Created Date` | ✓ | |
| `AccountId` | `Account` | ✓ | 去掉了 Id |
| `*AnnualRevenue` | `*Annual Revenue` | ✗ | **星号没去掉** |
| `HTMLBody` | `HTMLBody` | ✗ | **连续大写没有分割**（无 小写→大写 边界） |
| `CurrencyIsoCode` | `Currency Iso Code` | △ | 分割正确但 ISO 应保持大写 |
| `OriginalOpportunity` | `Original Opportunity` | ✓ | `l→O` 边界正确分割 |
| `IsActive` | `Is Active` | ✓ | |
| `EvaluatioDwellTime` | `Evaluatio Dwell Time` | ✓ | 分割正确（但原始名拼错了） |

**核心 bug**：正则 `([a-z])([A-Z])` 无法处理：
- 星号/特殊字符前缀（如 `*AnnualRevenue`）
- 连续大写字母（如 `HTMLBody`、`SFDCId`）

**阶段 3：修复环节**

`schema.go:776-806` 在 bootstrap 时查找 `Label == ApiName AND (IsCustomized = 0 OR IsCustomized IS NULL)` 的字段，用 `fieldNameToLabel()` 重新计算 label。

**关键限制**：`IsCustomized=1` 的字段被跳过。如果一个字段曾通过 admin API 被编辑（设了 `IsCustomized=1`），即使它的 Label 仍然等于 ApiName（说明 label 从未被真正定制），修复环节也不会处理它。

**前端 fallback**：

`schemas.ts:141` 有一个 fallback：
```typescript
label: decodeHtmlEntities(f.label || "") || humanizeFieldName(f.name || ""),
```

但 `humanizeFieldName` (`labelUtils.ts:9-16`) 用的是同样的 `([a-z])([A-Z])` 正则，有同样的限制。所以即使后端返回空 label，前端的 fallback 也会对连续大写和星号前缀产生错误输出。

### 代码证据

| 文件 | 行号 | 内容 |
|------|------|------|
| `backend/internal/bootstrap/discover_tables.go` | 315-336 | `fieldNameToLabel()` 函数定义 |
| `backend/internal/bootstrap/discover_tables.go` | 629 | `camelCaseRe = regexp.MustCompile("([a-z])([A-Z])")` |
| `backend/internal/bootstrap/discover_tables.go` | 633-634 | `splitCamelCase` 实现 |
| `backend/internal/bootstrap/schema.go` | 776-806 | Label 修复环节——WHERE 含 `IsCustomized = 0` 条件 |
| `backend/internal/infrastructure/persistence/schema_metadata_persist.go` | 144 | Admin API 保存时设 `IsCustomized = 1` |
| `frontend/src/infrastructure/api/schemas.ts` | 141 | 前端 fallback：`humanizeFieldName(f.name)` |
| `frontend/src/core/utils/labelUtils.ts` | 9-16 | `humanizeFieldName` 实现（同样的正则限制） |

### 影响面：逐 Issue 验证

#### 直接根因 — OPEN（6 个）

| Issue # | 标题 | State | 具体根因 |
|---------|------|-------|---------|
| **#90** | Frontend has no fallback when field label is empty — renders blank or API name | OPEN | 前端在 `field.label` 为空时直接渲染空白。虽然 `schemas.ts:141` 有 `humanizeFieldName` fallback，但其他消费路径（如 `LayoutRenderer.tsx:243` 的 `item.label \|\| field.label`）可能绕过了这个 fallback。 |
| **#206** | Account Layout: Field label '*AnnualRevenue' displays with asterisk prefix | OPEN | `fieldNameToLabel()` 未去除星号前缀。`*` 来自物理 DB 列名（TiDB `INFORMATION_SCHEMA.COLUMNS`），在 `discover_tables.go:283` 未经清洗直接传入。 |
| **#235** | Opportunity Layout: Asterisk-prefixed field labels (*EvaluatioDwellTime, *Stage Duration) | OPEN | 同 #206 的星号问题 + `EvaluatioDwellTime` 中连续大写未分割。 |
| **#236** | Opportunity Layout: Non-humanized field labels (OriginalOpportunity, InThisFiscalYear, RecordTypeName) | OPEN | `fieldNameToLabel("OriginalOpportunity")` 应输出 `"Original Opportunity"`（正则能处理），但 label 仍显示原始值。说明 `IsCustomized=1` 阻止了修复环节更新此字段。 |
| **#293** | Campaign Detail: Field labels show raw API names (Name / Owner / Currency ISO Code) | OPEN | Issue 明确说"DB fix applied on 2026-03-19 but not taking effect"。这正是 `IsCustomized=1` 阻止 `schema.go:782` 修复环节生效的症状。 |
| **#304** | New Campaign form: field labels 'Owner' and 'Currency ISO Code' do not match SFDC labels | OPEN | 同 #293 的根因——Campaign 字段的 `IsCustomized=1` 阻止 label 更新。 |

#### 部分相关（5 个）

| Issue # | 标题 | State | 关系 |
|---------|------|-------|------|
| **#121** | Campaign Member Status: Form title shows object description instead of friendly name | OPEN | 这是**对象级** label fallback bug（`registerSystemObjectMetadata` 用 `description` 而不是 `tableName` 作 fallback），不是字段级 `fieldNameToLabel` 问题。相关但根因不同。 |
| **#207** | Account Layout: 'S1/S2/S3/S4 Response in' labels truncated — missing 'Hours' | OPEN | `fieldNameToLabel("S1ResponseInHours")` 实际输出 `"S1 Response In Hours"` 是正确的。label 可能在 `system_tables.json` 中被设为不完整值，或 DB 中存的是截断版本。需要检查 DB 实际存储值。 |
| **#130** | Fix naive PluralLabel computation in registerSystemObjectMetadata | OPEN | 这是 `pluralize()` 函数（`schema.go:363`）的问题，不是 `fieldNameToLabel`。"CampaignMemberStatuss" 是 naive `+ "s"` 的结果。 |
| **#147** | Fix naive pluralization in buildObjectMetadata | OPEN | 同 #130——pluralization 函数问题，不同代码路径。 |
| **#70** | Sidebar nav shows API names instead of object labels | CLOSED | 对象级 label 问题（sidebar 用 `PluralLabel`）。已修复。 |

#### 直接根因 — CLOSED（4 个，确认是此根因但已修复）

| Issue # | 标题 | 状态 |
|---------|------|------|
| **#88** | Field discovery sets Label = API Name | CLOSED | **这就是 `fieldNameToLabel` bug 的原始报告。** |
| **#89** | 416 fields in system_tables.json missing 'label' property | CLOSED | **数据层面的根因。** PR #481 补充了 417 个 label。 |
| **#42** | Quote form displays raw API field names | CLOSED | #88 的直接症状——Quote 字段显示下划线原始名。 |
| **#128** | Extract duplicated splitCamelCase to shared utility | OPEN | 重构任务——`splitCamelCase` 在 `discover_tables.go` 和 `schema_column_ops.go` 中重复。 |

#### 无关（2 个）

| Issue # | 标题 | 真实根因 |
|---------|------|---------|
| **#60** | Flow Label column displays API Name instead of MasterLabel | Flow Tooling API 的 `MasterLabel` 映射问题，完全不同的代码路径 |
| **#149** | Support consonant doubling (Quiz -> Quizzes) | Pluralization 函数的边缘情况，不是 `fieldNameToLabel` |

### 额外发现

Issue 搜索还发现 **30+ 额外 label 相关 issues** 未在原始 14 个列表中，包括：
- **#289** — Quote edit form 100+ fields with raw camelCase labels（也受 Dam 1 layout 影响）
- **#150** — Campaign Name field wrong label
- **#110** — Picklist label vs raw value display
- **#313/#315** — Edit Campaign mislabeled fields
- **#55** — Approver picker shows raw username

这说明 label 系统的实际影响面可能大于验证的 6 个直接 issue。

---

## 综合影响矩阵

| Dam | 修复量 | 直接解决 (OPEN) | 部分改善 | 无效 | P0 | 额外潜在影响 |
|-----|--------|----------------|---------|------|----|----|
| **1: Layout** | 2-3h | **9** | 6 | 19 | — | 未来新对象自动受益 |
| **2: AutoNumber** | 30min | **1** | 2 | 8 | **#286** | 4 个 AutoNumber 字段 × 所有对象 |
| **3: Label** | 2h | **6** | 5 | 2 | — | 30+ 额外 label issues 可能受益 |
| **合计** | ~5h | **16** | 13 | — | 1 | |

### 三个 Dam 之间的关系

```
#282 (Quote 100+ fields + raw labels)
  ├── Dam 1 修复 → 100+ 字段变为 27 字段 in 5 sections
  ├── Dam 2 修复 → AutoNumber 字段从 edit form 消失
  └── Dam 3 修复 → raw camelCase labels 变为人类可读

#311 (Campaign Statistics editable)
  ├── Dam 2 修复 → calculated 字段也标为 updateable=false（如果同时加 calculated check）
  └── Dam 1 修复 → 精确 layout 可以把 Statistics 字段标为 read-only section

#296 (Campaign 缺 PLANNING section) + #293 (Campaign raw labels)
  ├── Dam 1 修复 → PLANNING section 出现
  └── Dam 3 修复 → section 内字段 label 正确
```

**有 5 个 issues 同时受 2-3 个 Dam 影响**（#282, #289, #311, #61, #296+#293 组合）。这些 issue 需要多个 Dam 修复才能完全解决，但每个 Dam 修复都消除了问题的一个维度。
