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 加列)。
落库后回显示例:
{
"appliedPartnerType": "SPECIFIED",
"availabilityType": "SPECIFIED",
"regionIds": ["ALL"],
"groupIds": ["ALL"],
"locationIds": ["3a22a490-6bcc-ee4f-2163-44f3e7b82906"]
}
请求示例(新增 / 编辑)
{
"categoryName": "Prep",
"appliedPartnerType": "SPECIFIED",
"companyIds": ["{partnerGuid1}", "{partnerGuid2}"],
"availabilityType": "SPECIFIED",
"regionIds": ["ALL"],
"groupIds": ["ALL"],
"locationIds": ["ALL"],
"state": true,
"orderNum": 1
}
全选 Company + 全选门店:
{
"categoryName": "Prep",
"appliedPartnerType": "SPECIFIED",
"companyIds": ["ALL"],
"availabilityType": "SPECIFIED",
"regionIds": ["ALL"],
"locationIds": ["ALL"]
}
落库:AppliedPartnerType=ALL,AvailabilityType=ALL;不写 partner/location 关联快照。
响应示例(详情回显)
{
"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 范围 |
联调注意
- 先执行 SQL 迁移,再重启美国版 API。
- 编辑弹窗回显以
companyIds为准(与partnerIds等价)。 - 列表
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-typeGET /api/app/label-multiple-optionGET /api/app/label-templateGET /api/app/label-categoryGET /api/app/label(原已支持AppliedRegionType=ALL)GET /api/app/product-categoryGET /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 时会:
- 按 Company 关联表过滤(正确)
- 再把该公司下门店展开,与
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 |
示例
{
"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"] |