Blame view

项目相关文档/2026-07-24代码优化.md 20.8 KB
acf259d5   李曜臣   2026-07-24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
  # 2026-07-24 Team Member 范围 ALL 哨兵(美国版后端)
  
  ## 背景
  
  New User / Edit User 弹窗中 Locations、Region 支持勾选 **ALL**。前端可传 `locationIds: ["ALL"]`(或 `regionIds: ["ALL"]`),后端需按当前 Company(`partnerId` / `partnerIds`)展开并落库到 `userlocation`;列表 **Assigned Locations** 覆盖全部门店时展示 **`All Location`**,Region 覆盖全部时展示 **`All Region`**
  
  本次**仅改美国版后端**,不改 Web 前端。
  
  ---
  
  ## 接口
  
  | 方法 | 路径 | 说明 |
  |------|------|------|
  | POST | `/api/app/team-member` | 新增成员 |
  | PUT | `/api/app/team-member/{id}` | 更新成员 |
  
  批量编辑 `PUT /api/app/team-member/team-members-bulk` 单行字段与单条 PUT 相同,同样支持 ALL。
  
  ---
  
  ## ALL 传参约定
  
  ### 哨兵值
  
  - 字符串 **`ALL`**(大小写不敏感:`ALL` / `all` / `All`
  - 适用字段:`locationIds`、`regionIds`、`groupIds`(与 `regionIds` 等价)、`locations`(与 `locationIds` 合并解析)
  
  ### 与具体 Guid 同传
  
  若数组中同时含 `ALL` 与具体门店/Region Guid,**以 ALL 为准**(视为全选,忽略具体 Id)。
  
  ### 优先级(Team Member 落库)
  
  **不再做 partner + region + location 并集**;按下列顺序取**单一**规则展开:
  
  1. **`locationIds` 含 `ALL`** → 按 `partnerId` / `partnerIds` 展开该公司**全部门店**
  2. **有具体 `locationIds`,且 `regionIds` 为空或为 `ALL`****只绑这些门店**(Region=ALL 下再收窄到指定门店;避免 `regionIds:["ALL"]` 盖掉单店)
  3. **`regionIds` 含 `ALL`**(且无具体门店)→ 按 Company 展开该公司**全部 Region** 下门店
  4. **`regionIds` 有具体 Guid****按这些 Region 展开门店落库****忽略同传的 `locationIds`**,保证多区域能落库回显)
  5. **仅有具体 `locationIds`** → 只绑定指定门店
  6. **仅有 `partnerId` / `partnerIds`**(Company Admin 等)→ 绑定该公司全部门店
  
  | 请求示例 | 落库规则 |
  |----------|----------|
  | `partnerId` + `locationIds: ["ALL"]` | 展开该公司**全部**门店写入 `userlocation` |
  | `partnerId` + `regionIds: ["ALL"]` + `locationIds: ["{loc}"]` | **只绑 `{loc}`**(Region ALL + 具体门店时门店优先) |
  | `partnerId` + `regionIds: ["ALL"]`(无 location) | 展开该公司**全部 Region** 下门店 |
  | `partnerId` + `regionIds: ["{groupGuid1}", "{groupGuid2}"]` | 展开两 Region 下门店并集 |
  | `partnerId` + `regionIds: ["{g1}","{g2}"]` + `locationIds: ["{loc}"]` | **按 Region 展开**(忽略 `{loc}`),`userlocation` 为两 Region 门店并集;Get 回显 `regionIds` 含 2 个 |
  | `partnerId` + `locationIds: ["{guid1}", "{guid2}"]`(无 region) | 仅绑定指定门店 |
  | Company Admin 仅传 `partnerId`(无 region/location) | 仍自动绑定该公司全部门店(原逻辑) |
  
  **注意**`locationIds` / `regionIds` 含 ALL 时须传 **`partnerId` 或 `partnerIds`**,否则返回 400。
  
  ### 编辑回显(GET 折叠为 ALL)
  
  库表 `userlocation` **仍存展开后的门店 Guid**。`GET /api/app/team-member/{id}`(及列表同名字段)在判断为「已覆盖该公司全部」时,将 Id 数组折叠为哨兵,与新增传 ALL 对称:
  
  | 条件 | 出参 |
  |------|------|
  | 绑定门店 = 该公司全部门店 | `locationIds: ["ALL"]`,`assignedLocations` 展示 All Location |
  | 反推 Region = 该公司全部 Region | `regionIds` / `groupIds: ["ALL"]` |
  | 未全选 | 仍返回具体 Guid 数组 |
  
  示例(新增时传 `regionIds/locationIds: ["ALL"]` 后,再 GET):
  
  ```json
  {
    "partnerIds": ["3a22a48c-9758-9a26-e4ee-390b0f042835"],
    "regionIds": ["ALL"],
    "groupIds": ["ALL"],
    "locationIds": ["ALL"]
  }
  ```
  
  ---
  
  ## 请求 / 响应示例
  
  ### 1. Locations 全选(ALL)
  
  **请求**
  
  ```json
  POST /api/app/team-member
  {
    "fullName": "Test All Loc",
    "userName": "test.all.loc@example.com",
    "password": "Pass123!",
    "email": "test.all.loc@example.com",
    "roleId": "ROLE_GUID",
    "partnerId": "3a222876-7c49-6f01-c232-2d21dde8f27e",
    "locationIds": ["ALL"],
    "state": true
  }
  ```
  
  **落库**`userlocation` 写入 Partner `Subway USA` 下全部未删除门店 Id。
  
  **列表/详情 `assignedLocations`**(覆盖全部门店时):
  
  ```json
  "assignedLocations": [
    { "locationName": "All Location" }
  ]
  ```
  
  ### 2. 仅指定门店 Guid(对比)
  
  **请求**
  
  ```json
  "partnerId": "3a222876-7c49-6f01-c232-2d21dde8f27e",
  "locationIds": [
    "3a222878-b4c1-d27b-d437-93da14886350",
    "3a22287c-d353-da47-869f-0bb5dacced51"
  ]
  ```
  
  **列表 `assignedLocations`**:展示具体名称,如 `Subway LAX Store`、`Subway UNCC Store`,**不会**出现 `All Location`
  
  ### 3. Region 全选(ALL)
  
  **请求**
  
  ```json
  "partnerId": "3a222876-7c49-6f01-c232-2d21dde8f27e",
  "regionIds": ["ALL"]
  ```
  
  **落库**:该公司下所有 Region 对应门店写入 `userlocation`
  
  **Region 展示**:绑定覆盖该公司全部门店时,列表 Region 文案为 **`All Region`**`FoodLabelingDisplayConsts.AllRegion`)。
  
  ### 4. 编辑多 Region + 同传 location(按 Region 展开)
  
  **场景**:UI 勾选 2 个 Region,请求体可能仍带 1 个 `locationIds`(历史选中项)。后端**以 Region 为准**展开,保证多区域能落库并回显。
  
  **请求**
  
  ```json
  PUT /api/app/team-member/{id}
  {
    "fullName": "张三",
    "userName": "123",
    "email": "123@qq.com",
    "roleId": "3a21836a-cc04-1783-b054-f2ff3c45d4bf",
    "partnerId": "3a22a48c-9758-9a26-e4ee-390b0f042835",
    "regionIds": [
      "3a22a48d-98be-7d54-7b9d-6062afb176be",
      "3a22a48d-5356-8754-d839-a6b7aaad02f9"
    ],
    "groupIds": [
      "3a22a48d-98be-7d54-7b9d-6062afb176be",
      "3a22a48d-5356-8754-d839-a6b7aaad02f9"
    ],
    "locationIds": ["3a22a48f-4114-ad64-11bf-9adb09f999b9"],
    "state": true
  }
  ```
  
  **落库**`userlocation` 写入两 Region 下门店并集(**不是**仅 `{locationIds}` 那一店)。
  
  **Get 回显**`regionIds` 含上述 2 个 Region(由 `userlocation` → 门店 → `fl_group` 反推)。
  
  ---
  
  ## 列表展示规则
  
  | 条件 | Assigned Locations | Region(列表/PDF) |
  |------|-------------------|-------------------|
  | `userlocation` 覆盖该成员 Company 下**全部门店** | `All Location` | 全部 Region 时 `All Region` |
  | 仅部分门店 | 门店 Code/Name,分号拼接 | 具体 Region 名称 |
  
  实现位置:
  
  - 落库展开:`LocationScopeBindingHelper.ResolveTeamMemberLocationIdsForSaveAsync`
  - 展示折叠:`TeamMemberScopeDisplayHelper.FormatAssignedLocationsForListAsync` / `FormatRegionTextForListAsync`
  - 全量回显:`TeamMemberAppService.ApplyCompanyAdminDisplayScopeAsync`(绑定覆盖全部门店时展开 Region)
  
  ---
  
  ## curl 示例
  
  ### 获取 Token
  
  ```bash
  curl -X POST "http://localhost:19001/api/oauth/Login" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "userName=admin&password=123456"
  ```
  
  将响应中 `data.token`(已含 `Bearer ` 前缀)用于后续 `Authorization` 头。
  
  ### 新增 — Locations ALL
  
  ```bash
  curl -X POST "http://localhost:19001/api/app/team-member" \
    -H "Authorization: <data.token>" \
    -H "Content-Type: application/json" \
    -d "{
      \"fullName\": \"All Loc User\",
      \"userName\": \"all.loc.user@example.com\",
      \"password\": \"Pass123!\",
      \"email\": \"all.loc.user@example.com\",
      \"roleId\": \"<ROLE_GUID>\",
      \"partnerId\": \"3a222876-7c49-6f01-c232-2d21dde8f27e\",
      \"locationIds\": [\"ALL\"],
      \"state\": true
    }"
  ```
  
  ### 更新 — Region ALL
  
  ```bash
  curl -X PUT "http://localhost:19001/api/app/team-member/<USER_GUID>" \
    -H "Authorization: <data.token>" \
    -H "Content-Type: application/json" \
    -d "{
      \"fullName\": \"All Region User\",
      \"userName\": \"all.region.user@example.com\",
      \"roleId\": \"<ROLE_GUID>\",
      \"partnerId\": \"3a222876-7c49-6f01-c232-2d21dde8f27e\",
      \"regionIds\": [\"ALL\"],
      \"state\": true
    }"
  ```
  
  ### 查询列表验证 Assigned Locations
  
  ```bash
  curl -X GET "http://localhost:19001/api/app/team-member?SkipCount=0&MaxResultCount=20" \
    -H "Authorization: <data.token>"
  ```
  
  检查 `items[].assignedLocations` 与 `items[].locationIds`
  
  ---
  
  ## 改动文件
  
  | 文件 | 变更 |
  |------|------|
  | `FoodLabeling.Application/Helpers/LocationScopeBindingHelper.cs` | ALL 哨兵识别;`ResolveTeamMemberLocationIdsForSaveAsync` 展开逻辑 |
  | `FoodLabeling.Application/Services/TeamMemberAppService.cs` | 合并 `locations`;Create/Update XML 注释;全量绑定展示 |
  | `FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberCreateInputVo.cs` | 字段注释 + `Locations` |
  | `FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberUpdateInputVo.cs` | 字段注释 + `Locations` |
  | `FoodLabeling.Application.Contracts/IServices/ITeamMemberAppService.cs` | Create/Update 接口说明 |
  
  ---
  
  ## 验证
  
  - `dotnet build` 编译通过
  - 可选:MCP `user-antis-foodlabeling-us-mysql` 查 `userlocation` 确认 ALL 请求后行数等于该公司 `location` 未删除门店数
  
  ---
  
  ## label-template 多公司 / 多 Region / 多门店传参与落库(美国版)
  
  ### 背景
  
  `POST/PUT /api/app/label-template` 在 **Company / Region / Location** 三维均为 `SPECIFIED` 且各传多个 Id 时,若所选 Id 恰好等于「当前上下文全部可选项」,旧逻辑会把维度折叠为 `ALL`**清空关联表**,详情再按门店反推 Company,常表现为 **只回显 1 个公司**
  
  ### 根因
  
  `AllScopeBindingHelper.ResolveDimensionType` 在 `isFullSelection == true` 时一律返回 `ALL`,**未尊重入参显式 `appliedPartnerType` / `appliedRegionType` / `appliedLocation` = `SPECIFIED`**
  
  ### 修复约定
  
  | 入参 | 落库行为 |
  |------|----------|
  | `appliedPartnerType: "SPECIFIED"` + `partnerIds` / `companyIds` 数组 | **始终**写 `fl_label_template_partner`(每个 Id 一行),主表 `AppliedPartnerType = SPECIFIED` |
  | `appliedRegionType: "SPECIFIED"` + `regionIds` / `groupIds` | **始终**写 `fl_label_template_region` |
  | `appliedLocation: "SPECIFIED"` + `locationIds` / `appliedLocationIds` | **始终**写 `fl_label_template_location` |
  | 各维度 `ALL` 或未显式 `SPECIFIED` 且 Id 覆盖当前上下文全集 | 仍归档为 `ALL`,不写该维度关联快照(后续新增主数据动态适用) |
  
  **字段别名(合并去重)**
  
  - Company:`partnerIds` ≡ `companyIds`(`fl_partner.Id`
  - Region:`regionIds` ≡ `groupIds`(`fl_group.Id`
  - Location:`locationIds` ≡ `appliedLocationIds`(`location.Id`
  
  ### 请求示例(2 个 Company + 2 个 Region + 2 个门店)
  
  ```json
  PUT /api/app/label-template/tpl_xxx
  {
    "appliedPartnerType": "SPECIFIED",
    "companyIds": ["{partnerId1}", "{partnerId2}"],
    "partnerIds": ["{partnerId1}", "{partnerId2}"],
    "appliedRegionType": "SPECIFIED",
    "regionIds": ["{groupId1}", "{groupId2}"],
    "groupIds": ["{groupId1}", "{groupId2}"],
    "appliedLocation": "SPECIFIED",
    "locationIds": ["{locId1}", "{locId2}"],
    "appliedLocationIds": ["{locId1}", "{locId2}"]
  }
  ```
  
  ### 验证(MCP / curl)
  
  1. 保存后查库:
  
  ```sql
  SELECT AppliedPartnerType, AppliedRegionType, AppliedLocationType
  FROM fl_label_template WHERE TemplateCode = 'tpl_xxx';
  
  SELECT PartnerId FROM fl_label_template_partner
  WHERE TemplateId = (SELECT Id FROM fl_label_template WHERE TemplateCode = 'tpl_xxx');
  
  SELECT GroupId FROM fl_label_template_region
  WHERE TemplateId = (SELECT Id FROM fl_label_template WHERE TemplateCode = 'tpl_xxx');
  
  SELECT LocationId FROM fl_label_template_location
  WHERE TemplateId = (SELECT Id FROM fl_label_template WHERE TemplateCode = 'tpl_xxx');
  ```
  
  2. `GET /api/app/label-template/tpl_xxx` 出参 `companyIds` / `partnerIds` / `regionIds` / `locationIds` 数组长度应与入参一致。
  
  ### 改动文件
  
  | 文件 | 变更 |
  |------|------|
  | `Helpers/AllScopeBindingHelper.cs` | 显式 `SPECIFIED` 时不因全选折叠为 `ALL` |
  | `Helpers/LabelTemplateScopeHelper.cs` | 保存传参对齐 Label 实体;详情/列表读取主表 scope 类型,避免门店反推覆盖 |
  | `Helpers/LabelTemplateScopeSchemaHelper.cs` | 批量读取 `AppliedPartnerType` / `AppliedRegionType` |
30543ac5   李曜臣   泰鄂版同步代码
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
  
  ---
  
  ## 七类实体编辑回显 ALL 哨兵(2026-07-24 续)
  
  ### 背景
  
  Team Member 已实现「全选回显 `["ALL"]`」规则。本次将同一约定推广至 Product、Product Category、Label、Label Category、Label Type、Label Template、Multiple Options Set 的 **GET 详情**(及列表若返回 scope Id 数组)。
  
  - **落库不变**:仍可存展开 Guid 或 `Applied*Type=ALL` / `AvailabilityType=ALL`;显式 `SPECIFIED` 多 Id 落库逻辑不受影响。
  - **回显变更**:不再把 `Type=ALL` 展开为当前可见全量 Guid;改为返回 `["ALL"]` 哨兵,供编辑弹窗勾选 ALL。
  - **公共实现**`ScopeAllEchoHelper.CollapseScopeIdsToAllSentinelAsync`(对齐 `TeamMemberScopeDisplayHelper.CollapseScopeIdsToAllSentinelForEditAsync` + `AllScopeBindingHelper.IsFullIdSelection`)。
  
  ### 折叠规则(与 Team Member 一致)
  
  | 条件 | 出参字段 | 值 |
  |------|----------|-----|
  | 覆盖全部 Company(系统级) | `partnerIds` / `companyIds` | `["ALL"]` |
  | 覆盖该公司下全部 Region | `regionIds` / `groupIds` | `["ALL"]` |
  | 覆盖该公司下全部门店 | `locationIds` / `appliedLocationIds` | `["ALL"]` |
  | 主表 `Applied*Type=ALL` 或 `AvailabilityType=ALL` | 对应维度 Id 数组 | `["ALL"]`(不展开 Guid) |
  | 未全选 | 各 Id 数组 | 具体 Guid 列表 |
  
  **说明**:Company 维度为 ALL 时 `partnerIds` 为 `["ALL"]`;Company 为 SPECIFIED 且 Region/Location 全选时,`partnerIds` 仍保留具体 Company Guid(与 Team Member 一致)。
  
  ### 字段对照表
  
  | 实体 | 详情/列表接口 | Company | Region | Location | 类型字段 |
  |------|---------------|---------|--------|----------|----------|
  | Product | `GET /api/app/product/{id}`、列表 `locationIds` | `partnerIds` / `partnerId` | `groupIds` | `locationIds` | `availabilityType` |
  | Product Category | `GET /api/app/product-category/{id}`、列表 | — | `regionIds` / `groupIds` | `locationIds` | `availabilityType` |
  | Label | `GET /api/app/label/{id}`、列表 | `partnerIds` / `partnerId` | `regionIds` / `groupIds` | `locationIds` | `appliedRegionType` |
  | Label Category | `GET /api/app/label-category/{id}`、列表 | `partnerIds` / `companyIds` | `regionIds` / `groupIds` | `locationIds` | `appliedPartnerType` + `availabilityType` |
  | Label Type | `GET /api/app/label-type/{id}`、列表 | `partnerIds` / `companyIds` | `regionIds` / `groupIds` | `locationIds` | `appliedPartnerType` + `availabilityType` |
  | Label Template | `GET /api/app/label-template/{code}`、列表 | `partnerIds` / `companyIds` | `regionIds` / `groupIds` | `locationIds` / `appliedLocationIds` | `appliedPartnerType` / `appliedRegionType` / `appliedLocationType` |
  | Multiple Options Set | `GET /api/app/label-multiple-option/{id}`、列表 | `partnerIds` / `companyIds` | `regionIds` / `groupIds` | `locationIds` | `appliedPartnerType` + `availabilityType` |
  
  列表 **Region / Location 展示文案**`All Region` / `All Location` / `All Companies`)保持原逻辑,仅 Id 数组改为哨兵。
  
  ### 改动文件
  
  | 文件 | 变更 |
  |------|------|
  | `Helpers/ScopeAllEchoHelper.cs` | **新增** 公共 ALL 回显折叠 |
  | `Helpers/LabelRegionScopeHelper.cs` | Label Region/Location 展示与 Id 折叠 |
  | `Helpers/LabelTemplateScopeHelper.cs` | 模板列表/详情 scope Id 折叠 |
  | `Helpers/LabelEntityPartnerScopeHelper.cs` | 标签实体 Company Id 折叠 |
  | `Services/ProductAppService.cs` | 产品详情/列表 `locationIds`、`groupIds` |
  | `Services/ProductCategoryAppService.cs` | 产品分类详情/列表 |
  | `Services/LabelAppService.cs` | 标签详情/列表 |
  | `Services/LabelCategoryAppService.cs` | 标签分类详情/列表 |
  | `Services/LabelTypeAppService.cs` | 标签类型详情/列表;移除 `ExpandAllScopeIdsToDtoAsync` |
  | `Services/LabelTemplateAppService.cs` | 模板详情;移除全量 Guid 展开 |
  | `Services/LabelMultipleOptionAppService.cs` | 多选项详情/列表;移除 `ExpandAllScopeIdsToDtoAsync` |
  
  ---
  
  ## 保存时 Id 数组含 ALL 哨兵(2026-07-24 续)
  
  ### 背景
  
  编辑弹窗回显 `regionIds` / `groupIds` / `locationIds: ["ALL"]` 后再次保存时,若后端把字面量 `"ALL"` 当作 Guid 做存在性校验,会报错(如 `门店Id格式不正确`、`存在无效的 Region`),并产生 `fl_group.Id = 'ALL'` 等无效 SQL。
  
  ### 约定(与 Team Member 对齐)
  
  | 入参 | 保存行为 |
  |------|----------|
  | `locationIds`(或 `appliedLocationIds`)含 `ALL` | **不归档 Guid 校验**;维度 Type 归档为 `ALL`,关联表不写快照 |
  | `regionIds` / `groupIds` 含 `ALL` 且无具体门店 | 同上,Region/Availability 归档 `ALL` |
  | `regionIds` 含 `ALL` + 具体 `locationIds` | Region 归档 `ALL`,仅落具体门店快照(Team Member 同语义) |
  | `applied*Type: "SPECIFIED"` + Id 数组仅 `["ALL"]` | **识别为全选**,Type 归档为 `ALL`(Normalize 阶段转换,非 Guid 校验) |
  | 显式 `SPECIFIED` + 多个具体 Guid | **不变**:全部落库,编辑仍回显具体 Id |
  
  **实现要点**
  
  - `AllScopeBindingHelper.Normalize*ScopeAsync` / `ShouldTreat*AsAllAsync`:先识别 `ALL` 哨兵,再决定是否展开或归档 ALL。
  - `LocationScopeBindingHelper.MergeToLocationIdsAsync` / `FilterConcreteScopeIds`:合并与校验前剥离 `ALL`,禁止传入 `Validate*ExistAsync`
  - `LabelTemplateScopeHelper.ResolveScopeForSaveAsync`:经 Normalize 后 Region/Location 为 ALL 时跳过 Guid 存在性校验。
  
  ### 涉及接口(示例)
  
  - `POST/PUT /api/app/label-multiple-option` — **新增与编辑均支持** `regionIds` / `groupIds` / `locationIds` 传 `["ALL"]`;`availabilityType: SPECIFIED` + Id 数组 `["ALL"]` 仍归档为 `ALL`
  - `POST/PUT /api/app/label-template` — **新增与编辑均支持** `appliedRegionType` / `appliedLocation: SPECIFIED` + `regionIds` / `groupIds` / `locationIds` / `appliedLocationIds: ["ALL"]`,Region/Location 维度归档 `ALL`;Partner 仍可 `SPECIFIED` + 多 `companyIds`
  - `POST/PUT /api/app/label-type` — **新增与编辑均支持**(与 multiple-option 相同):`availabilityType: SPECIFIED` + `regionIds`/`groupIds`/`locationIds: ["ALL"]` → `AvailabilityType=ALL`
  - `POST/PUT /api/app/label-category` — **新增与编辑均支持**(同上):`availabilityType: SPECIFIED` + `regionIds`/`groupIds`/`locationIds: ["ALL"]` → `AvailabilityType=ALL`
  - `POST/PUT /api/app/label` — **新增与编辑均支持**:`appliedRegionType: SPECIFIED` + `regionIds`/`groupIds`/`locationIds: ["ALL"]` → `AppliedRegionType=ALL`(Create/Update 共用 `LabelRegionScopeHelper.ResolveScopeForSaveAsync`
  - `POST/PUT /api/app/product-category` — **新增与编辑均支持**:`availabilityType: SPECIFIED` + `regionIds`/`groupIds`/`locationIds: ["ALL"]` → `AvailabilityType=ALL`
  - `POST/PUT /api/app/product` — **新增与编辑均支持**:`availabilityType: SPECIFIED` + `partnerId: "ALL"` 或 `groupIds`/`locationIds: ["ALL"]` → `AvailabilityType=ALL`,清空 `fl_location_product` 快照
  
  ### label-multiple-option 编辑传 ALL 示例
  
  ```json
  PUT /api/app/label-multiple-option/{id}
  {
    "optionName": "Size Options",
    "appliedPartnerType": "SPECIFIED",
    "companyIds": ["{partnerGuid1}", "{partnerGuid2}"],
    "availabilityType": "SPECIFIED",
    "regionIds": ["ALL"],
    "groupIds": ["ALL"],
    "locationIds": ["ALL"],
    "state": true
  }
  ```
  
  **落库**`AvailabilityType = ALL`,`fl_label_multiple_option_location` 无快照行;GET 回显 `regionIds` / `locationIds: ["ALL"]`
  
  ### label-template 编辑传 ALL 示例
  
  ```json
  PUT /api/app/label-template/{templateCode}
  {
    "appliedPartnerType": "SPECIFIED",
    "companyIds": ["{partnerGuid1}", "{partnerGuid2}"],
    "appliedRegionType": "SPECIFIED",
    "regionIds": ["ALL"],
    "groupIds": ["ALL"],
    "appliedLocation": "SPECIFIED",
    "locationIds": ["ALL"],
    "appliedLocationIds": ["ALL"]
  }
  ```
  
  **落库**`AppliedPartnerType = SPECIFIED` 且写 `fl_label_template_partner`;`AppliedRegionType` / `AppliedLocationType = ALL`,Region/Location 关联表无快照;GET 回显 Region/Location Id 为 `["ALL"]`
  
  ### 改动文件
  
  | 文件 | 变更 |
  |------|------|
  | `Helpers/AllScopeBindingHelper.cs` | Normalize / ShouldTreat 识别 ALL 哨兵 |
  | `Helpers/LocationScopeBindingHelper.cs` | Merge / Validate / ResolveEntityLocationIds 过滤 ALL |
  | `Helpers/LabelRegionScopeHelper.cs` | Label 保存路径 ALL 预处理 |
  | `Helpers/LabelTemplateScopeHelper.cs` | Create/Update 共用 ResolveScope 开头识别 ALL;ValidateRegion 过滤 ALL |
  | `Services/LabelMultipleOptionAppService.cs` | `ResolveMultipleOptionScopeForSaveAsync` 开头 ALL 早返回;Create/Update XML |
  | `Services/LabelTemplateAppService.cs` | 确认 Update 走 SaveTemplateScope + SetAppliedScopeTypes;Create/Update XML |
  
  ### 验证
  
  ```bash
  dotnet build module/food-labeling-us/FoodLabeling.Application/FoodLabeling.Application.csproj
  ```
  
  保存后:主表 `Applied*Type` / `AvailabilityType` 为 `ALL`,关联表无快照行;`GET` 详情仍回显 `["ALL"]`