Blame view

项目相关文档/2026-07-27代码优化.md 13 KB
30543ac5   李曜臣   泰鄂版同步代码
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
  # 2026-07-27 代码优化
  
  ## product-category 新增 Company 适用范围(companyIds)
  
  ### 背景
  
  产品分类(Product Category)与标签分类(Label Category)对齐,新增/编辑/详情/列表支持 **Company 多选** 传参与 **ALL 哨兵** 回显。
  
  ### 涉及接口
  
  | 方法 | 路径 |
  |------|------|
  | GET | `/api/app/product-category/{id}` |
  | GET | `/api/app/product-category`(列表 `items[]` 同步) |
  | POST | `/api/app/product-category` |
  | PUT | `/api/app/product-category/{id}` |
  
  示例详情:`GET /api/app/product-category/3a22b3d6-897d-d89a-e263-b30761fe48be`
  
  ### 新增入参字段(POST / PUT)
  
  | 字段 | 类型 | 说明 |
  |------|------|------|
  | `appliedPartnerType` | string | 适用 Company:`ALL` / `SPECIFIED` |
  | `partnerIds` | string[] | Company Id(`fl_partner.Id`);与 `companyIds` 合并去重 |
  | `companyIds` | string[] | 与 `partnerIds` 相同(推荐前端使用本字段) |
  
  原有 `availabilityType`、`regionIds` / `groupIds`、`locationIds` 规则不变,仍支持 `["ALL"]` 哨兵。
  
  ### 新增出参字段(GET 详情 / 列表)
  
  | 字段 | 类型 | 说明 |
  |------|------|------|
  | `appliedPartnerType` | string | `ALL` / `SPECIFIED` |
  | `company` | string | 展示文案:`All Companies` 或公司名称逗号拼接 |
  | `partnerIds` | string[] | Company Id 数组 |
  | `companyIds` | string[] | 与 `partnerIds` 相同 |
  
  ### ALL 哨兵约定(与 Team Member / Label Category 一致)
  
  | 入参 | 保存行为 |
  |------|----------|
  | `companyIds: ["ALL"]` 或 `appliedPartnerType: "ALL"` | 主表 `AppliedPartnerType=ALL`,不写 `fl_product_category_partner` 快照 |
  | `appliedPartnerType: "SPECIFIED"` + 具体 Guid | 写入 `fl_product_category_partner` 每 Company 一行 |
  | `appliedPartnerType: "SPECIFIED"` + `companyIds: ["ALL"]` | **识别为全选**,归档 `AppliedPartnerType=ALL` |
  | 勾选当前全部 Company(传全量 Guid) | 归档 `ALL`,编辑回显 `companyIds: ["ALL"]` |
  
  | 出参折叠 | 条件 |
  |----------|------|
  | `companyIds: ["ALL"]` | `AppliedPartnerType=ALL`,或绑定覆盖系统全部 Company |
  | `regionIds` / `locationIds: ["ALL"]` | 见下方「范围内 ALL」规则 |
  
  **说明**:Company 为 `ALL` 时 Region/Location 可为 `ALL`;Company 为 `SPECIFIED` 且 Region/Location 全选时,`companyIds` 仍保留具体 Company Guid(与 Label Category 一致)。
  
  ### 范围内 ALL(2026-07-27 修复)
  
  编辑/新增若已指定具体 `companyIds`/`partnerIds` 或具体 `groupIds`/`regionIds`,再传 `locationIds: ["ALL"]`(或仅 Region=`ALL`):
  
  | 入参 | 落库 | 回显 |
  |------|------|------|
  | 具体 Company + 具体 Region + `locationIds:["ALL"]` | `AvailabilityType=SPECIFIED`,展开该 Region(且属该 Company)下门店快照 | Region 仍为具体 Id;Location 可折叠为 `["ALL"]` |
  | 具体 Company + `locationIds:["ALL"]`(无具体 Region) | `SPECIFIED`,展开该公司全部门店 | Location/Region 按覆盖全集折叠 |
  | 无具体 Company 且无具体 Region + Location/Region ALL | 仍为全局 `AvailabilityType=ALL`(不写门店快照) | Region/Location 均为 `["ALL"]` |
  
  **修复前问题**:具体 Company + Region + `locationIds:["ALL"]` 被误归档为全局 `AvailabilityType=ALL`,列表 Region/Location 都显示 All。
  
  ### 具体 locationIds 被 Region 并集冲掉(2026-07-27 再修)
  
  前端编辑时常同时传 `groupIds`/`regionIds`(当前 Region)+ `locationIds`(用户勾选的门店)。旧逻辑 `MergeToLocationIdsAsync(Region ∪ Location)` 会把「只选 1 个门店」扩成整 Region,回显再折成 `locationIds:["ALL"]`
  
  | 入参 | 修复后落库 |
  |------|------------|
  | 具体 `locationIds: ["{loc}"]`(可同传 Region) | **只绑该门店**,`AvailabilityType=SPECIFIED`,回显仍为该 Guid |
  | 仅具体 Region、无具体 Location | 仍按 Region 展开门店 |
  | 具体 Location 覆盖 Company 下全部门店 | 仍可归档为全局 `AvailabilityType=ALL` |
  
  ### `locationIds:["ALL"]` 回显未折成 ALL(2026-07-27 再修)
  
  落库仍为 `AvailabilityType=SPECIFIED` + 区内门店快照(正确);但回显用 `ResolveAllLocationIdsAsync(Company, Region)` 时旧实现是 **Company∪Region 并集**,用「公司全部门店」去比,导致区内全选无法折叠为 `locationIds:["ALL"]`,列表 Location 也不显示 All。
  
  **修复**:同时传 Company + Region 时改为**交集**。再编:具体 Company + Region + `locationIds:["ALL"]` → 回显 `locationIds:["ALL"]`,`regionIds` 仍为具体 Region。
  
  ### label-category / label-type / label-multiple-option 范围内 ALL(2026-07-27)
  
  与 product-category 对齐:具体 Company + Region + `locationIds:["ALL"]` 不再误归档全局 `AvailabilityType=ALL`,改为展开区内门店快照(`SPECIFIED`),回显 `locationIds` 可折回 `["ALL"]`
  
  共用:`LocationScopeBindingHelper.ExpandScopedAllLocationsForSaveAsync`
  
  ### team-member 范围内 ALL(2026-07-27)
  
  | 入参 | 旧行为 | 新行为 |
  |------|--------|--------|
  | 具体 Company + 具体 Region + `locationIds/locations:["ALL"]` | 忽略 Region,展开**整公司**门店 | 展开**该 Region**(且属该公司)下门店 |
  | 回显 | 非整公司时 `locationIds` 无法折成 ALL | 区内全选时 `locationIds:["ALL"]`,`regionIds` 仍为具体 Region |
  
  ### Region=ALL + 单个 locationId(2026-07-27 再修)
  
  入参示例:`regionIds/groupIds:["ALL"]` + `locationIds:["{单店Guid}"]`
  
  | 旧问题 | 修复 |
  |--------|------|
  | 无 Region 维度字段,Region=ALL 无法落库;回显只能从门店反推具体 Region | 新增 `AppliedRegionType`(对齐 Label) |
  | 单店落库后 Region 回显不成 ALL | `AppliedRegionType=ALL` + `AvailabilityType=SPECIFIED` + 门店快照 |
  
  **DDL**:执行 `美国版/.../scripts/fl_entity_applied_region_type.sql`(给 label-category / product-category / label-type / label-multiple-option 加列)。
  
  落库后回显示例:
  ```json
  {
    "appliedPartnerType": "SPECIFIED",
    "availabilityType": "SPECIFIED",
    "regionIds": ["ALL"],
    "groupIds": ["ALL"],
    "locationIds": ["3a22a490-6bcc-ee4f-2163-44f3e7b82906"]
  }
  ```
  
  ### 请求示例(新增 / 编辑)
  
  ```json
  {
    "categoryName": "Prep",
    "appliedPartnerType": "SPECIFIED",
    "companyIds": ["{partnerGuid1}", "{partnerGuid2}"],
    "availabilityType": "SPECIFIED",
    "regionIds": ["ALL"],
    "groupIds": ["ALL"],
    "locationIds": ["ALL"],
    "state": true,
    "orderNum": 1
  }
  ```
  
  全选 Company + 全选门店:
  
  ```json
  {
    "categoryName": "Prep",
    "appliedPartnerType": "SPECIFIED",
    "companyIds": ["ALL"],
    "availabilityType": "SPECIFIED",
    "regionIds": ["ALL"],
    "locationIds": ["ALL"]
  }
  ```
  
  **落库**`AppliedPartnerType=ALL`,`AvailabilityType=ALL`;不写 partner/location 关联快照。
  
  ### 响应示例(详情回显)
  
  ```json
  {
    "id": "3a22b3d6-897d-d89a-e263-b30761fe48be",
    "categoryName": "Prep",
    "appliedPartnerType": "ALL",
    "company": "All Companies",
    "companyIds": ["ALL"],
    "partnerIds": ["ALL"],
    "availabilityType": "ALL",
    "regionIds": ["ALL"],
    "groupIds": ["ALL"],
    "locationIds": ["ALL"]
  }
  ```
  
  ### 数据库迁移(部署前执行)
  
  脚本路径:
  
  `美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_product_category_partner_scope.sql`
  
  内容概要:
  
  - `fl_product_category` 增加 `AppliedPartnerType`(默认 `ALL`
  - 新建 `fl_product_category_partner`(`CategoryId` + `PartnerId`
  
  未执行迁移时:`AppliedPartnerType=ALL` 可正常保存;`SPECIFIED` + 具体 `companyIds` 保存会提示执行迁移。
  
  ### 改动文件
  
  | 文件 | 变更 |
  |------|------|
  | `scripts/fl_product_category_partner_scope.sql` | **新增** 迁移脚本 |
  | `DbModels/FlProductCategoryDbEntity.cs` | `AppliedPartnerType` |
  | `DbModels/FlProductCategoryPartnerDbEntity.cs` | **新增** |
  | `Helpers/LabelEntityPartnerScopeHelper.cs` | `ProductCategory` 种类 + 列表筛选 |
  | `Dtos/ProductCategory/*` | 入参/出参 `companyIds` 等 |
  | `Services/ProductCategoryAppService.cs` | Create/Update/Get/List 集成 Company 范围 |
  
  ### 联调注意
  
  1. **先执行 SQL 迁移**,再重启美国版 API。
  2. 编辑弹窗回显以 `companyIds` 为准(与 `partnerIds` 等价)。
  3. 列表 `company` 为展示字段;勾选状态以 `companyIds` 数组为准。
  
  ---
  
  ## 列表筛选:ALL 绑定命中任意具体 Id(2026-07-27 续)
  
  ### 约定
  
  | 实体绑定 | Query 传入 | 应命中 |
  |----------|------------|--------|
  | Company = `ALL`(`appliedPartnerType=ALL` / 无 partner 关联行) | 任意 `partnerId` | ✅ |
  | Region = `ALL`(`availabilityType`/`appliedRegionType=ALL` / 无 region 关联行) | 任意 `groupId` | ✅ |
  | Location = `ALL`(`availabilityType`/`appliedLocationType=ALL`) | 任意 `locationId` | ✅ |
  | 维度 = `SPECIFIED` | 对应具体 Id | 仅关联命中 |
  
  ### 涉及列表接口
  
  - `GET /api/app/label-type`
  - `GET /api/app/label-multiple-option`
  - `GET /api/app/label-template`
  - `GET /api/app/label-category`
  - `GET /api/app/label`(原已支持 `AppliedRegionType=ALL`
  - `GET /api/app/product-category`
  - `GET /api/app/product`(原已支持 `AvailabilityType=ALL`
  - `GET /api/app/product-location`(按 `locationId` 时并入 `AvailabilityType=ALL` 产品)
  
  ### 实现要点
  
  | 文件 | 变更 |
  |------|------|
  | `LabelEntityPartnerScopeHelper` | Partner 筛选:`AppliedPartnerType=ALL` **或** SPECIFIED 关联命中 |
  | `LabelEntityListScopeHelper` | Location 筛选:`AvailabilityType=ALL` **或** SPECIFIED 门店命中(去掉「须 SPECIFIED Company」限制) |
  | `LabelTemplateScopeHelper.ApplyTemplateScopeFilterAsync` | Company/Region 无关联行视为 ALL;Location 保留 `AppliedLocationType=ALL` |
  | `ProductLocationAppService` | 按门店查时并入 `AvailabilityType=ALL` 产品 |
  
  ### 联调示例
  
  ```
  GET /api/app/label-type?partnerId={任意公司Guid}&SkipCount=0&MaxResultCount=50
  ```
  
  绑定 `appliedPartnerType=ALL` 的类型应出现在结果中。
  
  ```
  GET /api/app/product-location?locationId={任意门店Guid}&MaxResultCount=2000
  ```
  
  `AvailabilityType=ALL` 的产品应与该门店 SPECIFIED 关联产品一并返回。
  
  ---
  
  ## label-multiple-option 仅 PartnerId 查不到第二家公司(2026-07-27 续)
  
  ### 原因
  
  列表在传 `PartnerId` 时会:
  
  1. 按 Company 关联表过滤(正确)
  2. **再**把该公司下门店展开,与 `AvailabilityType` / 门店关联做 AND
  
  多公司绑定时,若第二家公司暂无门店、或门店未写入 location 关联表,第 2 步会把已命中的记录误杀。
  
  ### 修复
  
  - **仅传 `PartnerId`**(未传 `GroupId` / `LocationId`):只按 Company 维度过滤,跳过门店 Availability AND
  - 传了 `GroupId` / `LocationId`:仍按 Region/Location 过滤
  - 门店范围为 empty 时:保留 `AvailabilityType=ALL`,不再 `Where false` 清空
  
  涉及:`label-multiple-option` / `label-type` / `label-category` / `product-category` /
  `label-template` / `label` / `product` / `product-location`
  
  ### 各接口要点
  
  | 接口 | 修复 |
  |------|------|
  | label-type / label-category / product-category / label-multiple-option | 仅 `PartnerId` 跳过门店 AND |
  | label-template | 仅 `PartnerId`:**只返回** `fl_label_template_partner` 含该公司的模板;不再把「无关联行」当成 ALL(避免查出其他公司/`AppliedPartnerType=ALL` 的无关数据) |
  | label | 门店筛选补 `AppliedRegionType=ALL`;无门店时保留 ALL |
  | product | 仅 `PartnerId` 且公司无门店时保留 `AvailabilityType=ALL` |
  | product-location | 支持 `PartnerId`;按公司/门店查时并入 `AvailabilityType=ALL` |
  
  ---
  
  ## product 新增/编辑 `companyIds`(单选)(2026-07-27 续)
  
  ### 接口
  
  | 方法 | 路径 |
  |------|------|
  | POST | `/api/app/product` |
  | PUT | `/api/app/product/{id}` |
  | GET | `/api/app/product/{id}`(编辑回显) |
  
  ### 入参
  
  | 字段 | 类型 | 说明 |
  |------|------|------|
  | `companyIds` | string[] | **仅单选具体 Guid****不支持 ALL**;传多个 Guid 或含 ALL 报错 |
  | `partnerId` | string | 兼容旧字段;与 `companyIds` 同时传须一致;**不支持 ALL** |
  
  ### 出参(详情)
  
  | 字段 | 说明 |
  |------|------|
  | `companyIds` | 单选回显:最多 1 个具体 Guid;全部门店(AvailabilityType=ALL)时为空 |
  | `partnerId` | 与 `companyIds[0]` 对齐;无 Company 时为 null |
  
  ### 示例
  
  ```json
  {
    "productName": "Milk",
    "companyIds": ["3a22a4f2-1ec6-c7a1-b1a9-f82e71e9754c"],
    "availabilityType": "SPECIFIED",
    "groupIds": ["ALL"],
    "locationIds": ["ALL"]
  }
  ```
  
  `companyIds: ["ALL"]` 或 `partnerId: "ALL"` 将返回错误:`产品适用 Company 不支持 ALL,请传单个具体 Company Id`
  
  ### product `partnerId` 兼容数组 + 范围内 ALL(2026-07-27)
  
  | 问题 | 处理 |
  |------|------|
  | 前端传 `"partnerId":["{guid}"]` 导致 JSON 反序列化失败 | `PartnerId` 增加转换器,兼容字符串或数组(取首项) |
  | 增加 `regionIds` | 与 `groupIds` 合并解析 |
  | 具体 Company/Region + `locationIds:["ALL"]` | 同 product-category:展开为 SPECIFIED 快照,回显可折回 `["ALL"]` |