From 1b68006dd4beff00099b4172b8240be6c0d00330 Mon Sep 17 00:00:00 2001
From: 李曜臣
Date: Wed, 8 Jul 2026 16:37:55 +0800
Subject: [PATCH] 20260708
---
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Common/ILabelEntityPartnerScopeInput.cs | 16 ++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs | 3 ++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelGetListInputVo.cs | 1 +
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelUpdateInputVo.cs | 3 ++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs | 15 ++++++++++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListInputVo.cs | 3 +++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs | 10 ++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs | 10 ++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionCreateInputVo.cs | 15 ++++++++++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListInputVo.cs | 3 +++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListOutputDto.cs | 8 ++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetOutputDto.cs | 8 ++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeCreateInputVo.cs | 15 ++++++++++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListInputVo.cs | 3 +++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListOutputDto.cs | 10 ++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetOutputDto.cs | 10 ++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListInputVo.cs | 7 ++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILabelAppService.cs | 4 ++--
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs | 6 +++---
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LabelEntityPartnerScopeHelper.cs | 733 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LocationScopeBindingHelper.cs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsDateTimeDisplayHelper.cs | 20 ++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsLocationScopeHelper.cs | 22 ++++++++--------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsPrintLogExpiryHelper.cs | 37 ++++---------------------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/TeamMemberRoleHelper.cs | 48 ++++++++++++++++++++++++++++++++++++++++--------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/UsAppPrintLogScopeHelper.cs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryPartnerDbEntity.cs | 18 ++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionPartnerDbEntity.cs | 18 ++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypePartnerDbEntity.cs | 18 ++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs | 59 ++++++++++++++++++++++++++++++++++++++++++-----------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelTypeAppService.cs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs | 53 +++++++++++++++++++++++++++++++++++++++++++++--------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ReportsAppService.cs | 105 ++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs | 26 +++++++++++++++-----------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs | 15 +++++++++++++--
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json | 2 +-
美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------------------------------------------------------------------------------------------------------------------------
美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------
美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------
美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
美国版/Food Labeling Management Platform/src/services/labelTypeService.ts | 42 +++++++++++++++++++++++++++++++++++++-----
美国版/Food Labeling Management Platform/src/types/label.ts | 4 ++--
美国版/Food Labeling Management Platform/src/types/labelCategory.ts | 15 +++++++++++++++
美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts | 14 ++++++++++++++
美国版/Food Labeling Management Platform/src/types/labelType.ts | 14 ++++++++++++++
51 files changed, 2430 insertions(+), 541 deletions(-)
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Common/ILabelEntityPartnerScopeInput.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LabelEntityPartnerScopeHelper.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryPartnerDbEntity.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionPartnerDbEntity.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypePartnerDbEntity.cs
create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Common/ILabelEntityPartnerScopeInput.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Common/ILabelEntityPartnerScopeInput.cs
new file mode 100644
index 0000000..b104e3e
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Common/ILabelEntityPartnerScopeInput.cs
@@ -0,0 +1,16 @@
+namespace FoodLabeling.Application.Contracts.Dtos.Common;
+
+///
+/// 标签类型 / 分类 / 多选项创建编辑入参中的 Company 适用范围字段。
+///
+public interface ILabelEntityPartnerScopeInput
+{
+ /// 适用 Company:ALL / SPECIFIED
+ string? AppliedPartnerType { get; }
+
+ /// 适用 Company(fl_partner.Id )
+ List? PartnerIds { get; }
+
+ /// 与 相同(兼容字段)
+ List? CompanyIds { get; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs
index 0103f54..759481e 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs
@@ -45,7 +45,8 @@ public class LabelCreateInputVo
public string LabelCategoryId { get; set; } = string.Empty;
- public string LabelTypeId { get; set; } = string.Empty;
+ /// 标签类型 Id(fl_label_type.Id );可选,未传时标签不绑定类型
+ public string? LabelTypeId { get; set; }
public List ProductIds { get; set; } = new();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelGetListInputVo.cs
index e58f7bd..737f7ba 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelGetListInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelGetListInputVo.cs
@@ -31,6 +31,7 @@ public class LabelGetListInputVo : PagedAndSortedResultRequestDto
public string? LabelCategoryId { get; set; }
+ /// 按标签类型 Id 筛选(可选)
public string? LabelTypeId { get; set; }
///
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelUpdateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelUpdateInputVo.cs
index 7f36f3d..c1904fa 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelUpdateInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelUpdateInputVo.cs
@@ -31,7 +31,8 @@ public class LabelUpdateInputVo
public string LabelCategoryId { get; set; } = string.Empty;
- public string LabelTypeId { get; set; } = string.Empty;
+ /// 标签类型 Id(fl_label_type.Id );可选,未传时清空类型绑定
+ public string? LabelTypeId { get; set; }
public List ProductIds { get; set; } = new();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs
index 349c5da..3e630ec 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs
@@ -1,6 +1,8 @@
+using FoodLabeling.Application.Contracts.Dtos.Common;
+
namespace FoodLabeling.Application.Contracts.Dtos.LabelCategory;
-public class LabelCategoryCreateInputVo
+public class LabelCategoryCreateInputVo : ILabelEntityPartnerScopeInput
{
public string CategoryCode { get; set; } = string.Empty;
@@ -24,6 +26,17 @@ public class LabelCategoryCreateInputVo
public string ButtonAppearance { get; set; } = "TEXT";
///
+ /// 适用 Company:ALL / SPECIFIED
+ ///
+ public string? AppliedPartnerType { get; set; }
+
+ /// 适用 Company(fl_partner.Id )
+ public List? PartnerIds { get; set; }
+
+ /// 与 相同(兼容字段)
+ public List? CompanyIds { get; set; }
+
+ ///
/// 门店可用范围:ALL / SPECIFIED
///
public string AvailabilityType { get; set; } = "ALL";
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListInputVo.cs
index b70e95d..cc078aa 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListInputVo.cs
@@ -8,6 +8,9 @@ public class LabelCategoryGetListInputVo : PagedAndSortedResultRequestDto
public bool? State { get; set; }
+ /// Company 筛选(fl_partner.Id )
+ public string? PartnerId { get; set; }
+
///
/// Region 筛选(fl_group.Id );含 availabilityType=ALL 的分类
///
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs
index 694232c..3f61caf 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs
@@ -18,6 +18,16 @@ public class LabelCategoryGetListOutputDto
public string AvailabilityType { get; set; } = "ALL";
+ public string AppliedPartnerType { get; set; } = "ALL";
+
+ /// 适用 Company 展示(ALL 时为 All Companies)
+ public string Company { get; set; } = string.Empty;
+
+ public List PartnerIds { get; set; } = new();
+
+ /// 与 相同
+ public List CompanyIds { get; set; } = new();
+
/// 适用 Region 展示(ALL 时为 All Regions)
public string Region { get; set; } = string.Empty;
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs
index 5ccdc73..6e6d7ef 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs
@@ -18,6 +18,16 @@ public class LabelCategoryGetOutputDto
public string AvailabilityType { get; set; } = "ALL";
+ public string AppliedPartnerType { get; set; } = "ALL";
+
+ /// 列表/详情 Company 展示
+ public string Company { get; set; } = string.Empty;
+
+ public List PartnerIds { get; set; } = new();
+
+ /// 与 相同
+ public List CompanyIds { get; set; } = new();
+
/// 适用 Region Id 列表(多选;ALL 时为空)
public List RegionIds { get; set; } = new();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionCreateInputVo.cs
index 007d8b5..7802d0d 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionCreateInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionCreateInputVo.cs
@@ -1,6 +1,8 @@
+using FoodLabeling.Application.Contracts.Dtos.Common;
+
namespace FoodLabeling.Application.Contracts.Dtos.LabelMultipleOption;
-public class LabelMultipleOptionCreateInputVo
+public class LabelMultipleOptionCreateInputVo : ILabelEntityPartnerScopeInput
{
/// 多选项编码(可选;未传或空字符串时存空,列表/详情出参为「无」)
public string? OptionCode { get; set; }
@@ -12,6 +14,17 @@ public class LabelMultipleOptionCreateInputVo
public bool State { get; set; } = true;
///
+ /// 适用 Company:ALL / SPECIFIED
+ ///
+ public string? AppliedPartnerType { get; set; }
+
+ /// 适用 Company(fl_partner.Id )
+ public List? PartnerIds { get; set; }
+
+ /// 与 相同(兼容字段)
+ public List? CompanyIds { get; set; }
+
+ ///
/// 门店可用范围:ALL / SPECIFIED;传了 或 时自动为 SPECIFIED
///
public string AvailabilityType { get; set; } = "ALL";
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListInputVo.cs
index 718e484..d283320 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListInputVo.cs
@@ -8,6 +8,9 @@ public class LabelMultipleOptionGetListInputVo : PagedAndSortedResultRequestDto
public bool? State { get; set; }
+ /// Company 筛选(fl_partner.Id )
+ public string? PartnerId { get; set; }
+
///
/// Region 筛选(fl_group.Id )
///
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListOutputDto.cs
index e5011fa..6d79240 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListOutputDto.cs
@@ -14,6 +14,14 @@ public class LabelMultipleOptionGetListOutputDto
public string AvailabilityType { get; set; } = "ALL";
+ public string AppliedPartnerType { get; set; } = "ALL";
+
+ public string Company { get; set; } = string.Empty;
+
+ public List PartnerIds { get; set; } = new();
+
+ public List CompanyIds { get; set; } = new();
+
public int OrderNum { get; set; }
public DateTime LastEdited { get; set; }
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetOutputDto.cs
index 23d92fd..865f190 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetOutputDto.cs
@@ -16,6 +16,14 @@ public class LabelMultipleOptionGetOutputDto
public string AvailabilityType { get; set; } = "ALL";
+ public string AppliedPartnerType { get; set; } = "ALL";
+
+ public string Company { get; set; } = string.Empty;
+
+ public List PartnerIds { get; set; } = new();
+
+ public List CompanyIds { get; set; } = new();
+
public List RegionIds { get; set; } = new();
public List GroupIds { get; set; } = new();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeCreateInputVo.cs
index 084c6ce..131dcdc 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeCreateInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeCreateInputVo.cs
@@ -1,6 +1,8 @@
+using FoodLabeling.Application.Contracts.Dtos.Common;
+
namespace FoodLabeling.Application.Contracts.Dtos.LabelType;
-public class LabelTypeCreateInputVo
+public class LabelTypeCreateInputVo : ILabelEntityPartnerScopeInput
{
public string TypeCode { get; set; } = string.Empty;
@@ -9,6 +11,17 @@ public class LabelTypeCreateInputVo
public bool State { get; set; } = true;
///
+ /// 适用 Company:ALL / SPECIFIED
+ ///
+ public string? AppliedPartnerType { get; set; }
+
+ /// 适用 Company(fl_partner.Id )
+ public List? PartnerIds { get; set; }
+
+ /// 与 相同(兼容字段)
+ public List? CompanyIds { get; set; }
+
+ ///
/// 门店可用范围:ALL / SPECIFIED;传了 或 时自动为 SPECIFIED
///
public string AvailabilityType { get; set; } = "ALL";
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListInputVo.cs
index 1441853..7b412cb 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListInputVo.cs
@@ -8,6 +8,9 @@ public class LabelTypeGetListInputVo : PagedAndSortedResultRequestDto
public bool? State { get; set; }
+ /// Company 筛选(fl_partner.Id )
+ public string? PartnerId { get; set; }
+
///
/// Region 筛选(fl_group.Id );仅返回在该 Region 下存在标签实例的类型
///
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListOutputDto.cs
index 8ede0d8..56ac9ca 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListOutputDto.cs
@@ -12,6 +12,16 @@ public class LabelTypeGetListOutputDto
public string AvailabilityType { get; set; } = "ALL";
+ public string AppliedPartnerType { get; set; } = "ALL";
+
+ /// 适用 Company 展示(ALL 时为 All Companies)
+ public string Company { get; set; } = string.Empty;
+
+ public List PartnerIds { get; set; } = new();
+
+ /// 与 相同
+ public List CompanyIds { get; set; } = new();
+
public int OrderNum { get; set; }
/// 列表列 No. of Labels:该类型下未删除标签数(统计 fl_label ,非库表物理列)
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetOutputDto.cs
index 54440ba..028a50b 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetOutputDto.cs
@@ -14,6 +14,16 @@ public class LabelTypeGetOutputDto
public string AvailabilityType { get; set; } = "ALL";
+ public string AppliedPartnerType { get; set; } = "ALL";
+
+ /// 列表/详情 Company 展示
+ public string Company { get; set; } = string.Empty;
+
+ public List PartnerIds { get; set; } = new();
+
+ /// 与 相同
+ public List CompanyIds { get; set; } = new();
+
public List RegionIds { get; set; } = new();
public List GroupIds { get; set; } = new();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListInputVo.cs
index 7350080..64d5e8b 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListInputVo.cs
@@ -18,11 +18,16 @@ public class ProductCategoryGetListInputVo : PagedAndSortedResultRequestDto
public bool? State { get; set; }
///
- /// 按 Region 筛选(fl_group.Id ):返回适用于该 Region 下任一门门店的分类,以及 availabilityType=ALL 的分类
+ /// 按 Region 筛选(fl_group.Id ):返回适用于该 Region 下任一门门店的分类
///
public string? GroupId { get; set; }
///
+ /// 按 Company 筛选(fl_partner.Id )
+ ///
+ public string? PartnerId { get; set; }
+
+ ///
/// 按门店筛选(location.Id ,Guid 字符串);优先于
///
public string? LocationId { get; set; }
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILabelAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILabelAppService.cs
index 92b6c80..a7870cf 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILabelAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILabelAppService.cs
@@ -11,7 +11,7 @@ namespace FoodLabeling.Application.Contracts.IServices;
public interface ILabelAppService : IApplicationService
{
///
- /// 按产品分页列表(一个产品展示多个标签)。支持 GroupId / LocationId 筛选(命中 fl_label_region 、fl_label_location 或 AppliedRegionType=ALL );出参含 locationIds 、region 、regionIds 、appliedRegionType 。
+ /// 按产品分页列表(一个产品展示多个标签)。支持 GroupId / LocationId 筛选(命中 fl_label_region 、fl_label_location 或 AppliedRegionType=ALL );labelTypeId 为可选筛选;出参含 locationIds 、region 、regionIds 、appliedRegionType 。
///
Task> GetListAsync(LabelGetListInputVo input);
@@ -21,7 +21,7 @@ public interface ILabelAppService : IApplicationService
Task GetAsync(string id);
///
- /// 新增标签。Body 支持 appliedRegionType (ALL/SPECIFIED)、regionIds / groupIds (落库 fl_label_region )、locationIds (主字段,落库 fl_label_location );locationId 与 locationIds 合并。
+ /// 新增标签。Body 支持 appliedRegionType (ALL/SPECIFIED)、regionIds / groupIds (落库 fl_label_region )、locationIds (主字段,落库 fl_label_location );locationId 与 locationIds 合并;labelTypeId 可选。
///
Task CreateAsync(LabelCreateInputVo input);
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs
index e49108e..18db503 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs
@@ -11,12 +11,12 @@ namespace FoodLabeling.Application.Contracts.IServices;
public interface IUsAppAuthAppService : IApplicationService
{
///
- /// App 登录:使用邮箱 + 密码校验并签发 Token,同时返回 userlocation 绑定的门店
+ /// App 登录:使用邮箱 + 密码校验并签发 Token,同时返回可选门店(Company Admin 为绑定 Company 下全部门店)
///
Task LoginAsync(UsAppLoginInputVo input);
///
- /// 获取当前登录账号已绑定的门店(用于切换门店等场景)
+ /// 获取当前登录账号可选门店(Company Admin 返回绑定 Company 下全部门店,用于切换门店等场景)
///
Task> GetMyLocationsAsync();
@@ -62,7 +62,7 @@ public interface IUsAppAuthAppService : IApplicationService
Task ChangePasswordAsync(UsAppChangePasswordInputVo input);
///
- /// 按门店 Id 查询 Location 详情(须为当前账号 userlocation 绑定门店)
+ /// 按门店 Id 查询 Location 详情(须为当前账号可选门店;Company Admin 含绑定 Company 下任意门店)
///
/// 门店 Guid 字符串
/// 店名、地址、电话、经营时间(operatingHours)、店长信息
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LabelEntityPartnerScopeHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LabelEntityPartnerScopeHelper.cs
new file mode 100644
index 0000000..748d61e
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LabelEntityPartnerScopeHelper.cs
@@ -0,0 +1,733 @@
+using FoodLabeling.Application.Services.DbModels;
+using SqlSugar;
+using Volo.Abp;
+using Volo.Abp.Guids;
+
+namespace FoodLabeling.Application.Helpers;
+
+///
+/// 标签类型 / 分类 / 多选项适用 Company:ALL/SPECIFIED + 多选落库(对齐 的 Company 维度)。
+///
+public static class LabelEntityPartnerScopeHelper
+{
+ public const string ScopeAll = "ALL";
+ public const string ScopeSpecified = "SPECIFIED";
+
+ public const string AllCompaniesDisplay = "All Companies";
+ public const string EmptyDisplay = "无";
+
+ /// 标签实体种类(对应主表与 partner 关联表)。
+ public enum LabelEntityPartnerKind
+ {
+ /// 标签类型 fl_label_type
+ Type,
+
+ /// 标签分类 fl_label_category
+ Category,
+
+ /// 标签多选项 fl_label_multiple_option
+ MultipleOption
+ }
+
+ public sealed class LabelEntityPartnerScopeSaveResult
+ {
+ public string AppliedPartnerType { get; init; } = ScopeAll;
+
+ public List PartnerIds { get; init; } = new();
+ }
+
+ public sealed class LabelEntityPartnerScopeDisplay
+ {
+ public string Company { get; init; } = AllCompaniesDisplay;
+
+ public string AppliedPartnerType { get; init; } = ScopeAll;
+
+ public List PartnerIds { get; init; } = new();
+ }
+
+ private static LabelEntityPartnerScopeSchemaStatus? _cachedSchema;
+
+ private sealed class LabelEntityPartnerScopeSchemaStatus
+ {
+ public bool HasTypePartnerColumn { get; init; }
+
+ public bool HasCategoryPartnerColumn { get; init; }
+
+ public bool HasMultipleOptionPartnerColumn { get; init; }
+
+ public bool HasTypePartnerTable { get; init; }
+
+ public bool HasCategoryPartnerTable { get; init; }
+
+ public bool HasMultipleOptionPartnerTable { get; init; }
+ }
+
+ ///
+ /// 合并 与 并去重排序。
+ ///
+ public static List NormalizePartnerIds(IReadOnlyList? partnerIds, IReadOnlyList? companyIds)
+ {
+ var merged = new HashSet(StringComparer.Ordinal);
+ foreach (var id in LocationScopeBindingHelper.NormalizeIds(partnerIds))
+ {
+ merged.Add(id);
+ }
+
+ foreach (var id in LocationScopeBindingHelper.NormalizeIds(companyIds))
+ {
+ merged.Add(id);
+ }
+
+ return merged.OrderBy(x => x, StringComparer.Ordinal).ToList();
+ }
+
+ ///
+ /// 解析适用 Company 范围类型(ALL/SPECIFIED)。
+ ///
+ public static string ResolveAppliedPartnerType(string? type, List partnerIds, bool hasArrayInPayload)
+ {
+ if (partnerIds.Count > 0)
+ {
+ return ScopeSpecified;
+ }
+
+ var normalized = (type ?? ScopeAll).Trim().ToUpperInvariant();
+ if (hasArrayInPayload && string.Equals(normalized, ScopeAll, StringComparison.OrdinalIgnoreCase))
+ {
+ return ScopeAll;
+ }
+
+ return string.Equals(normalized, ScopeSpecified, StringComparison.OrdinalIgnoreCase)
+ ? ScopeSpecified
+ : ScopeAll;
+ }
+
+ ///
+ /// 解析新增/编辑入参中的 Company 范围并校验 partner Id 有效性。
+ ///
+ public static async Task ResolvePartnerScopeForSaveAsync(
+ ISqlSugarClient db,
+ string? appliedPartnerType,
+ IReadOnlyList? partnerIds,
+ IReadOnlyList? companyIds)
+ {
+ var normalizedPartnerIds = NormalizePartnerIds(partnerIds, companyIds);
+ var partnerType = ResolveAppliedPartnerType(
+ appliedPartnerType,
+ normalizedPartnerIds,
+ partnerIds is not null || companyIds is not null);
+
+ ValidatePartnerType(partnerType);
+
+ if (string.Equals(partnerType, ScopeSpecified, StringComparison.OrdinalIgnoreCase)
+ && normalizedPartnerIds.Count == 0)
+ {
+ throw new UserFriendlyException("指定适用 Company 时,partnerIds/companyIds 不能为空");
+ }
+
+ if (normalizedPartnerIds.Count > 0)
+ {
+ await ValidatePartnerIdsExistAsync(db, normalizedPartnerIds);
+ }
+
+ return new LabelEntityPartnerScopeSaveResult
+ {
+ AppliedPartnerType = partnerType,
+ PartnerIds = normalizedPartnerIds
+ };
+ }
+
+ ///
+ /// 保存 Company 适用范围(关联表 + AppliedPartnerType 列);未执行迁移脚本时 no-op。
+ ///
+ public static async Task SavePartnerScopeAsync(
+ ISqlSugarClient db,
+ IGuidGenerator guidGenerator,
+ LabelEntityPartnerKind kind,
+ string entityId,
+ LabelEntityPartnerScopeSaveResult scope,
+ string? currentUserId,
+ DateTime now)
+ {
+ if (string.IsNullOrWhiteSpace(entityId))
+ {
+ return;
+ }
+
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerScopeForKind(schema, kind))
+ {
+ return;
+ }
+
+ await DeletePartnerScopeRowsAsync(db, kind, entityId);
+
+ if (string.Equals(scope.AppliedPartnerType, ScopeSpecified, StringComparison.OrdinalIgnoreCase)
+ && scope.PartnerIds.Count > 0)
+ {
+ await InsertPartnerRowsAsync(db, guidGenerator, kind, entityId, scope.PartnerIds, currentUserId, now);
+ }
+
+ await SetAppliedPartnerTypeAsync(db, kind, entityId, scope.AppliedPartnerType);
+ }
+
+ ///
+ /// 删除实体下 Company 关联行;未迁移 partner 表时跳过。
+ ///
+ public static async Task DeletePartnerScopeRowsAsync(
+ ISqlSugarClient db,
+ LabelEntityPartnerKind kind,
+ string entityId)
+ {
+ if (string.IsNullOrWhiteSpace(entityId))
+ {
+ return;
+ }
+
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerTableForKind(schema, kind))
+ {
+ return;
+ }
+
+ switch (kind)
+ {
+ case LabelEntityPartnerKind.Type:
+ await db.Deleteable()
+ .Where(x => x.LabelTypeId == entityId)
+ .ExecuteCommandAsync();
+ break;
+ case LabelEntityPartnerKind.Category:
+ await db.Deleteable()
+ .Where(x => x.CategoryId == entityId)
+ .ExecuteCommandAsync();
+ break;
+ case LabelEntityPartnerKind.MultipleOption:
+ await db.Deleteable()
+ .Where(x => x.MultipleOptionId == entityId)
+ .ExecuteCommandAsync();
+ break;
+ }
+ }
+
+ ///
+ /// 批量加载 Company 展示与 Id 数组(详情/列表)。
+ ///
+ public static async Task> BuildPartnerScopeDisplayMapAsync(
+ ISqlSugarClient db,
+ LabelEntityPartnerKind kind,
+ IReadOnlyList entityIds)
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ if (entityIds.Count == 0)
+ {
+ return result;
+ }
+
+ var ids = entityIds.Where(x => !string.IsNullOrWhiteSpace(x))
+ .Select(x => x.Trim())
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
+ if (ids.Count == 0)
+ {
+ return result;
+ }
+
+ var schema = await GetSchemaStatusAsync(db);
+ var hasScope = HasPartnerScopeForKind(schema, kind);
+
+ var partnerLinks = hasScope
+ ? await LoadPartnerLinksAsync(db, kind, ids)
+ : new List<(string EntityId, string PartnerId)>();
+
+ var partnerIdSet = partnerLinks.Select(x => x.PartnerId).Distinct(StringComparer.Ordinal).ToList();
+ var partnerNameById = await LoadPartnerNameMapAsync(db, partnerIdSet);
+
+ foreach (var entityId in ids)
+ {
+ var pIds = LocationScopeBindingHelper.NormalizeIds(
+ partnerLinks.Where(x => x.EntityId == entityId).Select(x => x.PartnerId).ToList());
+
+ var partnerType = pIds.Count > 0 ? ScopeSpecified : ScopeAll;
+ if (hasScope)
+ {
+ var storedType = await GetAppliedPartnerTypeAsync(db, kind, entityId);
+ if (!string.IsNullOrWhiteSpace(storedType))
+ {
+ partnerType = NormalizeScopeType(storedType, ScopeAll);
+ }
+ else if (pIds.Count > 0)
+ {
+ partnerType = ScopeSpecified;
+ }
+ }
+
+ var companyDisplay = string.Equals(partnerType, ScopeAll, StringComparison.OrdinalIgnoreCase)
+ ? AllCompaniesDisplay
+ : BuildCompanyDisplay(pIds, partnerNameById);
+
+ result[entityId] = new LabelEntityPartnerScopeDisplay
+ {
+ Company = companyDisplay,
+ AppliedPartnerType = partnerType,
+ PartnerIds = pIds
+ };
+ }
+
+ return result;
+ }
+
+ ///
+ /// 列表 Company 列展示文案。
+ ///
+ public static string BuildCompanyDisplay(
+ IReadOnlyList partnerIds,
+ IReadOnlyDictionary partnerNameById)
+ {
+ if (partnerIds.Count == 0)
+ {
+ return EmptyDisplay;
+ }
+
+ var names = partnerIds
+ .Select(id => partnerNameById.TryGetValue(id, out var name) ? name : id)
+ .Select(x => x?.Trim())
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ return names.Count > 0 ? string.Join(", ", names) : EmptyDisplay;
+ }
+
+ ///
+ /// 列表筛选:按可见 Company 过滤标签类型(与 Region/Location 筛选 AND)。
+ ///
+ public static async Task> ApplyTypePartnerListFilterAsync(
+ ISqlSugarClient db,
+ ISugarQueryable query,
+ IReadOnlyList? scopedPartnerIds)
+ {
+ if (scopedPartnerIds is null)
+ {
+ return query;
+ }
+
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerTableForKind(schema, LabelEntityPartnerKind.Type))
+ {
+ return query;
+ }
+
+ if (scopedPartnerIds.Count == 0)
+ {
+ return query.Where(t =>
+ !SqlFunc.Subqueryable()
+ .Where(p => p.LabelTypeId == t.Id)
+ .Any());
+ }
+
+ return query.Where(t =>
+ !SqlFunc.Subqueryable()
+ .Where(p => p.LabelTypeId == t.Id)
+ .Any()
+ || SqlFunc.Subqueryable()
+ .Where(p => p.LabelTypeId == t.Id && scopedPartnerIds.Contains(p.PartnerId))
+ .Any());
+ }
+
+ ///
+ /// 列表筛选:按可见 Company 过滤标签分类。
+ ///
+ public static async Task> ApplyCategoryPartnerListFilterAsync(
+ ISqlSugarClient db,
+ ISugarQueryable query,
+ IReadOnlyList? scopedPartnerIds)
+ {
+ if (scopedPartnerIds is null)
+ {
+ return query;
+ }
+
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerTableForKind(schema, LabelEntityPartnerKind.Category))
+ {
+ return query;
+ }
+
+ if (scopedPartnerIds.Count == 0)
+ {
+ return query.Where(c =>
+ !SqlFunc.Subqueryable()
+ .Where(p => p.CategoryId == c.Id)
+ .Any());
+ }
+
+ return query.Where(c =>
+ !SqlFunc.Subqueryable()
+ .Where(p => p.CategoryId == c.Id)
+ .Any()
+ || SqlFunc.Subqueryable()
+ .Where(p => p.CategoryId == c.Id && scopedPartnerIds.Contains(p.PartnerId))
+ .Any());
+ }
+
+ ///
+ /// 列表筛选:按可见 Company 过滤标签多选项。
+ ///
+ public static async Task> ApplyMultipleOptionPartnerListFilterAsync(
+ ISqlSugarClient db,
+ ISugarQueryable query,
+ IReadOnlyList? scopedPartnerIds)
+ {
+ if (scopedPartnerIds is null)
+ {
+ return query;
+ }
+
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerTableForKind(schema, LabelEntityPartnerKind.MultipleOption))
+ {
+ return query;
+ }
+
+ if (scopedPartnerIds.Count == 0)
+ {
+ return query.Where(o =>
+ !SqlFunc.Subqueryable()
+ .Where(p => p.MultipleOptionId == o.Id)
+ .Any());
+ }
+
+ return query.Where(o =>
+ !SqlFunc.Subqueryable()
+ .Where(p => p.MultipleOptionId == o.Id)
+ .Any()
+ || SqlFunc.Subqueryable()
+ .Where(p => p.MultipleOptionId == o.Id && scopedPartnerIds.Contains(p.PartnerId))
+ .Any());
+ }
+
+ ///
+ /// 由 Query partnerId 解析列表 Company 筛选 Id;未传则返回 null 。
+ ///
+ public static async Task?> ResolveScopedPartnerIdsForListAsync(
+ ISqlSugarClient db,
+ string? partnerId)
+ {
+ var pid = partnerId?.Trim();
+ if (string.IsNullOrWhiteSpace(pid))
+ {
+ return null;
+ }
+
+ var exists = await db.Queryable()
+ .AnyAsync(x => !x.IsDeleted && x.Id == pid);
+ return exists ? new List { pid } : new List();
+ }
+
+ ///
+ /// 由 Query partnerId 或可见门店范围解析列表 Company 筛选 Id;均未限时返回 null 。
+ ///
+ public static async Task?> ResolveListPartnerIdsAsync(
+ ISqlSugarClient db,
+ string? partnerId,
+ List? scopedLocationIds)
+ {
+ var fromQuery = await ResolveScopedPartnerIdsForListAsync(db, partnerId);
+ if (fromQuery is not null)
+ {
+ return fromQuery;
+ }
+
+ if (scopedLocationIds is null)
+ {
+ return null;
+ }
+
+ if (scopedLocationIds.Count == 0)
+ {
+ return new List();
+ }
+
+ return await LocationScopeBindingHelper.ResolvePartnerIdsFromLocationIdsAsync(db, scopedLocationIds);
+ }
+
+ /// 单元测试或切换库后调用。
+ public static void ResetSchemaCacheForTests() => _cachedSchema = null;
+
+ private static void ValidatePartnerType(string type)
+ {
+ if (type != ScopeAll && type != ScopeSpecified)
+ {
+ throw new UserFriendlyException("适用 Company 范围不合法(ALL/SPECIFIED)");
+ }
+ }
+
+ private static string NormalizeScopeType(string? type, string fallback)
+ {
+ var t = (type ?? fallback).Trim().ToUpperInvariant();
+ return t == ScopeSpecified ? ScopeSpecified : ScopeAll;
+ }
+
+ private static async Task ValidatePartnerIdsExistAsync(ISqlSugarClient db, List partnerIds)
+ {
+ if (partnerIds.Count == 0)
+ {
+ return;
+ }
+
+ var count = await db.Queryable()
+ .Where(x => !x.IsDeleted && partnerIds.Contains(x.Id))
+ .CountAsync();
+ if (count != partnerIds.Count)
+ {
+ throw new UserFriendlyException("存在无效的 Company(partnerIds/companyIds),请刷新后重试");
+ }
+ }
+
+ private static async Task GetSchemaStatusAsync(ISqlSugarClient db)
+ {
+ if (_cachedSchema is not null)
+ {
+ return _cachedSchema;
+ }
+
+ try
+ {
+ _cachedSchema = new LabelEntityPartnerScopeSchemaStatus
+ {
+ HasTypePartnerColumn = await ColumnExistsAsync(db, "fl_label_type", "AppliedPartnerType"),
+ HasCategoryPartnerColumn = await ColumnExistsAsync(db, "fl_label_category", "AppliedPartnerType"),
+ HasMultipleOptionPartnerColumn = await ColumnExistsAsync(
+ db, "fl_label_multiple_option", "AppliedPartnerType"),
+ HasTypePartnerTable = await TableExistsAsync(db, "fl_label_type_partner"),
+ HasCategoryPartnerTable = await TableExistsAsync(db, "fl_label_category_partner"),
+ HasMultipleOptionPartnerTable = await TableExistsAsync(db, "fl_label_multiple_option_partner")
+ };
+ }
+ catch
+ {
+ _cachedSchema = new LabelEntityPartnerScopeSchemaStatus();
+ }
+
+ return _cachedSchema;
+ }
+
+ private static bool HasPartnerScopeForKind(LabelEntityPartnerScopeSchemaStatus schema, LabelEntityPartnerKind kind) =>
+ HasPartnerTableForKind(schema, kind) && HasPartnerColumnForKind(schema, kind);
+
+ private static bool HasPartnerTableForKind(LabelEntityPartnerScopeSchemaStatus schema, LabelEntityPartnerKind kind) =>
+ kind switch
+ {
+ LabelEntityPartnerKind.Type => schema.HasTypePartnerTable,
+ LabelEntityPartnerKind.Category => schema.HasCategoryPartnerTable,
+ LabelEntityPartnerKind.MultipleOption => schema.HasMultipleOptionPartnerTable,
+ _ => false
+ };
+
+ private static bool HasPartnerColumnForKind(LabelEntityPartnerScopeSchemaStatus schema, LabelEntityPartnerKind kind) =>
+ kind switch
+ {
+ LabelEntityPartnerKind.Type => schema.HasTypePartnerColumn,
+ LabelEntityPartnerKind.Category => schema.HasCategoryPartnerColumn,
+ LabelEntityPartnerKind.MultipleOption => schema.HasMultipleOptionPartnerColumn,
+ _ => false
+ };
+
+ private static async Task ColumnExistsAsync(ISqlSugarClient db, string tableName, string columnName)
+ {
+ var count = await db.Ado.GetIntAsync(
+ """
+ SELECT COUNT(*)
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = @tableName
+ AND COLUMN_NAME = @columnName
+ """,
+ new { tableName, columnName });
+ return count > 0;
+ }
+
+ private static async Task TableExistsAsync(ISqlSugarClient db, string tableName)
+ {
+ var count = await db.Ado.GetIntAsync(
+ """
+ SELECT COUNT(*)
+ FROM information_schema.TABLES
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = @tableName
+ """,
+ new { tableName });
+ return count > 0;
+ }
+
+ private static async Task SetAppliedPartnerTypeAsync(
+ ISqlSugarClient db,
+ LabelEntityPartnerKind kind,
+ string entityId,
+ string appliedPartnerType)
+ {
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerColumnForKind(schema, kind))
+ {
+ return;
+ }
+
+ var tableName = kind switch
+ {
+ LabelEntityPartnerKind.Type => "fl_label_type",
+ LabelEntityPartnerKind.Category => "fl_label_category",
+ LabelEntityPartnerKind.MultipleOption => "fl_label_multiple_option",
+ _ => string.Empty
+ };
+
+ if (string.IsNullOrEmpty(tableName))
+ {
+ return;
+ }
+
+ await db.Ado.ExecuteCommandAsync(
+ $"UPDATE `{tableName}` SET AppliedPartnerType = @type WHERE Id = @id",
+ new { type = appliedPartnerType.Trim(), id = entityId.Trim() });
+ }
+
+ private static async Task GetAppliedPartnerTypeAsync(
+ ISqlSugarClient db,
+ LabelEntityPartnerKind kind,
+ string entityId)
+ {
+ var schema = await GetSchemaStatusAsync(db);
+ if (!HasPartnerColumnForKind(schema, kind))
+ {
+ return null;
+ }
+
+ var tableName = kind switch
+ {
+ LabelEntityPartnerKind.Type => "fl_label_type",
+ LabelEntityPartnerKind.Category => "fl_label_category",
+ LabelEntityPartnerKind.MultipleOption => "fl_label_multiple_option",
+ _ => string.Empty
+ };
+
+ if (string.IsNullOrEmpty(tableName))
+ {
+ return null;
+ }
+
+ var value = await db.Ado.GetScalarAsync(
+ $"SELECT AppliedPartnerType FROM `{tableName}` WHERE Id = @id LIMIT 1",
+ new { id = entityId.Trim() });
+
+ return value?.ToString()?.Trim();
+ }
+
+ private static async Task> LoadPartnerLinksAsync(
+ ISqlSugarClient db,
+ LabelEntityPartnerKind kind,
+ IReadOnlyList entityIds)
+ {
+ switch (kind)
+ {
+ case LabelEntityPartnerKind.Type:
+ {
+ var rows = await db.Queryable()
+ .Where(x => entityIds.Contains(x.LabelTypeId))
+ .ToListAsync();
+ return rows.Select(x => (x.LabelTypeId, x.PartnerId)).ToList();
+ }
+ case LabelEntityPartnerKind.Category:
+ {
+ var rows = await db.Queryable()
+ .Where(x => entityIds.Contains(x.CategoryId))
+ .ToListAsync();
+ return rows.Select(x => (x.CategoryId, x.PartnerId)).ToList();
+ }
+ case LabelEntityPartnerKind.MultipleOption:
+ {
+ var rows = await db.Queryable()
+ .Where(x => entityIds.Contains(x.MultipleOptionId))
+ .ToListAsync();
+ return rows.Select(x => (x.MultipleOptionId, x.PartnerId)).ToList();
+ }
+ default:
+ return new List<(string EntityId, string PartnerId)>();
+ }
+ }
+
+ private static async Task InsertPartnerRowsAsync(
+ ISqlSugarClient db,
+ IGuidGenerator guidGenerator,
+ LabelEntityPartnerKind kind,
+ string entityId,
+ IReadOnlyList partnerIds,
+ string? currentUserId,
+ DateTime now)
+ {
+ switch (kind)
+ {
+ case LabelEntityPartnerKind.Type:
+ {
+ var rows = partnerIds.Select(pid => new FlLabelTypePartnerDbEntity
+ {
+ Id = guidGenerator.Create().ToString(),
+ LabelTypeId = entityId,
+ PartnerId = pid,
+ CreationTime = now,
+ CreatorId = currentUserId
+ }).ToList();
+ await db.Insertable(rows).ExecuteCommandAsync();
+ break;
+ }
+ case LabelEntityPartnerKind.Category:
+ {
+ var rows = partnerIds.Select(pid => new FlLabelCategoryPartnerDbEntity
+ {
+ Id = guidGenerator.Create().ToString(),
+ CategoryId = entityId,
+ PartnerId = pid,
+ CreationTime = now,
+ CreatorId = currentUserId
+ }).ToList();
+ await db.Insertable(rows).ExecuteCommandAsync();
+ break;
+ }
+ case LabelEntityPartnerKind.MultipleOption:
+ {
+ var rows = partnerIds.Select(pid => new FlLabelMultipleOptionPartnerDbEntity
+ {
+ Id = guidGenerator.Create().ToString(),
+ MultipleOptionId = entityId,
+ PartnerId = pid,
+ CreationTime = now,
+ CreatorId = currentUserId
+ }).ToList();
+ await db.Insertable(rows).ExecuteCommandAsync();
+ break;
+ }
+ }
+ }
+
+ private static async Task> LoadPartnerNameMapAsync(
+ ISqlSugarClient db,
+ IReadOnlyList partnerIds)
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ if (partnerIds.Count == 0)
+ {
+ return result;
+ }
+
+ var partners = await db.Queryable()
+ .Where(x => !x.IsDeleted && partnerIds.Contains(x.Id))
+ .ToListAsync();
+ foreach (var p in partners)
+ {
+ var name = p.PartnerName?.Trim();
+ result[p.Id] = string.IsNullOrEmpty(name) ? p.Id : name;
+ }
+
+ return result;
+ }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LocationScopeBindingHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LocationScopeBindingHelper.cs
index e9ffbfc..1fe9817 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LocationScopeBindingHelper.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/LocationScopeBindingHelper.cs
@@ -54,6 +54,61 @@ public static class LocationScopeBindingHelper
return locationIds.OrderBy(x => x, StringComparer.Ordinal).ToList();
}
+ ///
+ /// App 可选门店 Id:Company Admin 展开为绑定 Company 下全部门店;其它角色为 userlocation 绑定。
+ ///
+ public static Task> ResolveAppAccessibleLocationIdsForUserAsync(
+ ISqlSugarClient db,
+ Guid userId) =>
+ ResolveAppAccessibleLocationIdsForUserAsync(db, userId, null);
+
+ ///
+ /// App 可选门店 Id;传入 时优先 JWT 声明识别 Company Admin,避免 ORM 查 Role 触发数据权限过滤器。
+ ///
+ public static async Task> ResolveAppAccessibleLocationIdsForUserAsync(
+ ISqlSugarClient db,
+ Guid userId,
+ ICurrentUser? currentUser)
+ {
+ var locationIds = await ResolveBoundLocationIdsForUserAsync(db, userId);
+ if (locationIds.Count == 0)
+ {
+ return locationIds;
+ }
+
+ var isCompanyAdmin = currentUser?.Id == userId
+ ? await TeamMemberRoleHelper.IsCompanyAdminUserAsync(db, currentUser)
+ : await TeamMemberRoleHelper.IsCompanyAdminUserAsync(db, userId);
+ if (!isCompanyAdmin)
+ {
+ return locationIds;
+ }
+
+ var partnerIds = await ResolvePartnerIdsFromLocationIdsAsync(db, locationIds);
+ if (partnerIds.Count == 0)
+ {
+ return locationIds;
+ }
+
+ var expanded = await ResolveLocationIdsFromPartnerIdsAsync(db, partnerIds);
+ if (expanded.Count == 0)
+ {
+ return locationIds;
+ }
+
+ var merged = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var id in expanded)
+ {
+ var key = TeamMemberListScopeHelper.NormalizeScopeKey(id);
+ if (!string.IsNullOrEmpty(key))
+ {
+ merged.Add(key);
+ }
+ }
+
+ return merged.OrderBy(x => x, StringComparer.Ordinal).ToList();
+ }
+
public static async Task> ResolveDisplayRegionIdsForCurrentUserAsync(
ISqlSugarClient db,
ICurrentUser currentUser)
@@ -63,12 +118,7 @@ public static class LocationScopeBindingHelper
return new List();
}
- var roleId = await db.Queryable()
- .Where(ur => ur.UserId == currentUser.Id.Value)
- .Select(ur => (Guid?)ur.RoleId)
- .FirstAsync();
-
- return await ResolveDisplayRegionIdsForUserAsync(db, currentUser, roleId);
+ return await ResolveDisplayRegionIdsForUserAsync(db, currentUser, null);
}
///
@@ -93,9 +143,7 @@ public static class LocationScopeBindingHelper
var partnerIds = await ResolvePartnerIdsFromLocationIdsAsync(db, locationIds);
var regionIds = await ResolveGroupIdsFromLocationIdsAsync(db, locationIds);
- var isCompanyAdmin = await TeamMemberRoleHelper.IsCompanyAdminUserAsync(db, currentUser)
- || await TeamMemberRoleHelper.IsCompanyAdminRoleAsync(db, roleId);
- if (isCompanyAdmin && partnerIds.Count > 0)
+ if (await TeamMemberRoleHelper.IsCompanyAdminUserAsync(db, currentUser) && partnerIds.Count > 0)
{
regionIds = await ResolveGroupIdsFromPartnerIdsAsync(db, partnerIds);
}
@@ -120,7 +168,7 @@ public static class LocationScopeBindingHelper
var partnerIds = await ResolvePartnerIdsFromLocationIdsAsync(db, locationIds);
var regionIds = await ResolveGroupIdsFromLocationIdsAsync(db, locationIds);
- if (await TeamMemberRoleHelper.IsCompanyAdminRoleAsync(db, roleId) && partnerIds.Count > 0)
+ if (await TeamMemberRoleHelper.IsCompanyAdminUserAsync(db, userId) && partnerIds.Count > 0)
{
regionIds = await ResolveGroupIdsFromPartnerIdsAsync(db, partnerIds);
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsDateTimeDisplayHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsDateTimeDisplayHelper.cs
index efb8bfd..ba86174 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsDateTimeDisplayHelper.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsDateTimeDisplayHelper.cs
@@ -409,9 +409,29 @@ public static class ReportsDateTimeDisplayHelper
return false;
+ }
+ /// 将已解析的到期时刻格式化为 Print Log 展示文案。
+ public static string FormatExpiryDateTime(DateTime dt)
+ {
+ if (dt == DateTime.MinValue)
+ {
+ return "无";
+ }
+
+ return dt.ToString(DisplayFormat, CultureInfo.InvariantCulture);
}
+ /// 解析到期字面量(含 anchor 补全年份/时间)。
+ public static bool TryParseExpiryDateTime(string s, DateTime anchor, out DateTime dt)
+ {
+ if (TryParseToDateTime(s, out dt))
+ {
+ return true;
+ }
+
+ return TryParsePartialDateTime(s, anchor, out dt);
+ }
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsLocationScopeHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsLocationScopeHelper.cs
index 1a35f5a..156af62 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsLocationScopeHelper.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsLocationScopeHelper.cs
@@ -8,7 +8,7 @@ namespace FoodLabeling.Application.Helpers;
///
/// Reports 门店数据范围:管理员可查全部(可按 Company/Region/Location 入参收窄);
-/// 非管理员仅统计 userlocation 绑定门店的打印任务。
+/// Company Admin 为绑定 Company 下全部门店;其它非管理员为 userlocation 绑定门店。
///
public static class ReportsLocationScopeHelper
{
@@ -16,7 +16,7 @@ public static class ReportsLocationScopeHelper
/// 解析报表统计适用的门店 Id 列表。
///
/// - 返回
null :不限制门店(管理员且未传 Company/Region/Location 筛选);
- /// - 返回空列表:无绑定门店或筛选后无交集;
+ /// - 返回空列表:无可访问门店或筛选后无交集;
/// - 返回非空列表:仅统计这些
fl_label_print_task.LocationId 。
///
///
@@ -39,22 +39,16 @@ public static class ReportsLocationScopeHelper
return new List();
}
- var userId = currentUser.Id.Value.ToString();
- var boundIds = LocationScopeBindingHelper.NormalizeIds(
- await db.Queryable()
- .Where(x => !x.IsDeleted && x.UserId == userId)
- .Select(x => x.LocationId)
- .Distinct()
- .ToListAsync());
-
- if (boundIds.Count == 0)
+ var accessibleIds = await LocationScopeBindingHelper.ResolveAppAccessibleLocationIdsForUserAsync(
+ db, currentUser.Id.Value, currentUser);
+ if (accessibleIds.Count == 0)
{
return new List();
}
if (filterIds is null)
{
- return boundIds;
+ return accessibleIds;
}
if (filterIds.Count == 0)
@@ -62,8 +56,8 @@ public static class ReportsLocationScopeHelper
return new List();
}
- var boundSet = new HashSet(boundIds, StringComparer.OrdinalIgnoreCase);
- return filterIds.Where(id => boundSet.Contains(id)).ToList();
+ var accessibleSet = new HashSet(accessibleIds, StringComparer.OrdinalIgnoreCase);
+ return filterIds.Where(id => accessibleSet.Contains(id)).ToList();
}
///
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsPrintLogExpiryHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsPrintLogExpiryHelper.cs
index 793ec38..72d2a33 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsPrintLogExpiryHelper.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/ReportsPrintLogExpiryHelper.cs
@@ -53,10 +53,10 @@ public static class ReportsPrintLogExpiryHelper
return ReportsDateTimeDisplayHelper.FormatExpiryDateTime(resolved);
}
- var raw = ExtractExpiryText(printInputJson);
+ var raw = ExtractExpiryText(printInputJson, reference);
if (string.Equals(raw, "无", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(renderTemplateJson))
{
- raw = ExtractExpiryText(renderTemplateJson);
+ raw = ExtractExpiryText(renderTemplateJson, reference);
}
return ReportsDateTimeDisplayHelper.FormatExpiryDisplay(raw, reference);
@@ -185,7 +185,7 @@ public static class ReportsPrintLogExpiryHelper
return false;
}
- return ReportsDateTimeDisplayHelper.TryParseToDateTime(display.Trim(), reference, out result);
+ return ReportsDateTimeDisplayHelper.TryParseExpiryDateTime(display.Trim(), reference, out result);
}
private static string? ResolveElementDisplayText(JsonElement el, DateTime reference)
@@ -453,35 +453,6 @@ public static class ReportsPrintLogExpiryHelper
dt.Hour != 0 || dt.Minute != 0;
///
- /// 解析并格式化为 Print Log Expiration 列展示。
- ///
- public static string ExtractFormattedExpiryText(
- string? printInputJson,
- string? renderTemplateJson = null,
- DateTime? baseTime = null,
- DateTime? printedAt = null)
- {
- var anchor = ResolveAnchor(baseTime, printedAt, printInputJson);
- var raw = ExtractExpiryText(printInputJson, anchor);
- if (IsMissingExpiry(raw) && !string.IsNullOrWhiteSpace(renderTemplateJson))
- {
- raw = ExtractExpiryText(renderTemplateJson, anchor);
- }
-
- if (TryParseOffsetPayload(raw.Trim(), out var amount, out var unit))
- {
- raw = ApplyOffset(anchor, amount, unit)
- .ToString(ReportsDateTimeDisplayHelper.DisplayFormat, CultureInfo.InvariantCulture);
- }
-
- return ReportsDateTimeDisplayHelper.FormatExpiryDisplay(raw, anchor);
- }
-
- /// 兼容旧调用:仅 PrintInputJson。
- public static string ExtractFormattedExpiryText(string? printInputJson) =>
- ExtractFormattedExpiryText(printInputJson, null, null, null);
-
- ///
/// 解析保质期展示文本;无法解析时返回「无」。
///
public static string ExtractExpiryText(string? printInputJson, DateTime anchor)
@@ -830,7 +801,7 @@ public static class ReportsPrintLogExpiryHelper
}
var t = raw.Trim();
- if (!t.StartsWith('{', StringComparison.Ordinal))
+ if (!t.StartsWith("{"))
{
if (Regex.IsMatch(t, @"^\d+$"))
{
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/TeamMemberRoleHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/TeamMemberRoleHelper.cs
index 2b06a92..50aee70 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/TeamMemberRoleHelper.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/TeamMemberRoleHelper.cs
@@ -1,6 +1,5 @@
using SqlSugar;
using Volo.Abp.Users;
-using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.Rbac.Domain.Shared.Consts;
namespace FoodLabeling.Application.Helpers;
@@ -55,12 +54,17 @@ public static class TeamMemberRoleHelper
return false;
}
- var roleId = await db.Queryable()
- .Where(ur => ur.UserId == currentUser.Id.Value)
- .Select(ur => (Guid?)ur.RoleId)
- .FirstAsync();
+ return await IsCompanyAdminUserAsync(db, currentUser.Id.Value);
+ }
- return await IsCompanyAdminRoleAsync(db, roleId);
+ ///
+ /// 指定用户是否为 Company Admin 类角色(按库内 Role 判定,用于登录等无 JWT 声明场景)。
+ ///
+ public static async Task IsCompanyAdminUserAsync(ISqlSugarClient db, Guid userId)
+ {
+ var roleRows = await LoadUserRoleRowsAsync(db, userId);
+ return roleRows.Any(r =>
+ IsCompanyAdminRoleName(r.RoleName) || IsCompanyAdminRoleToken(r.RoleCode));
}
///
@@ -73,8 +77,16 @@ public static class TeamMemberRoleHelper
return false;
}
- var role = await db.Queryable()
- .FirstAsync(x => !x.IsDeleted && x.Id == roleId.Value);
+ var roleRows = await db.Ado.SqlQueryAsync(
+ """
+ SELECT RoleCode, RoleName
+ FROM Role
+ WHERE IsDeleted = 0 AND Id = @RoleId
+ LIMIT 1
+ """,
+ new { RoleId = roleId.Value });
+
+ var role = roleRows.FirstOrDefault();
if (role is null)
{
return false;
@@ -84,6 +96,19 @@ public static class TeamMemberRoleHelper
}
///
+ /// 读取用户角色(raw SQL,避免 RBAC 数据权限过滤器中 Select 表达式导致 SqlSugar 解析失败)。
+ ///
+ private static Task> LoadUserRoleRowsAsync(ISqlSugarClient db, Guid userId) =>
+ db.Ado.SqlQueryAsync(
+ """
+ SELECT r.RoleCode, r.RoleName
+ FROM UserRole ur
+ INNER JOIN Role r ON ur.RoleId = r.Id
+ WHERE ur.UserId = @UserId AND r.IsDeleted = 0 AND r.State = 1
+ """,
+ new { UserId = userId });
+
+ ///
/// 列表/详情出参:Partner Admin → Company Admin(与 Web UI 一致)。
///
public static string? FormatDisplayRoleName(string? roleName)
@@ -148,4 +173,11 @@ public static class TeamMemberRoleHelper
private static string NormalizeRoleKey(string value) =>
new string(value.Trim().ToLowerInvariant().Where(c => char.IsLetterOrDigit(c)).ToArray());
+
+ private sealed class UserRoleNameRow
+ {
+ public string? RoleCode { get; set; }
+
+ public string? RoleName { get; set; }
+ }
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/UsAppPrintLogScopeHelper.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/UsAppPrintLogScopeHelper.cs
index be73bd4..8b8cd0a 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/UsAppPrintLogScopeHelper.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Helpers/UsAppPrintLogScopeHelper.cs
@@ -13,7 +13,7 @@ namespace FoodLabeling.Application.Helpers;
public static class UsAppPrintLogScopeHelper
{
///
- /// 管理员( )或角色码/名含 partner (忽略大小写,如 Partner Admin)。
+ /// 管理员、Company Admin,或角色码/名含 partner (如 Partner Admin):可查看门店下全部打印记录。
///
public static async Task CanViewAllPrintsAtLocationAsync(
ICurrentUser currentUser,
@@ -29,6 +29,11 @@ public static class UsAppPrintLogScopeHelper
return false;
}
+ if (await TeamMemberRoleHelper.IsCompanyAdminUserAsync(db, currentUser))
+ {
+ return true;
+ }
+
var roleRows = await db.Ado.SqlQueryAsync(
@"SELECT r.RoleCode, r.RoleName
FROM UserRole ur
@@ -41,30 +46,18 @@ public static class UsAppPrintLogScopeHelper
}
///
- /// 校验当前用户是否绑定指定门店。
+ /// 校验当前用户是否可访问指定门店(含 Company Admin 公司下全部门店)。
///
public static async Task EnsureUserBoundToLocationAsync(
ICurrentUser currentUser,
ISqlSugarClient db,
string locationId)
{
- if (currentUser.Id is null)
- {
- throw new UserFriendlyException("用户未登录");
- }
-
- var lid = locationId.Trim();
- var userIdStr = currentUser.Id.Value.ToString();
- var bound = await db.Queryable()
- .AnyAsync(x => !x.IsDeleted && x.UserId == userIdStr && x.LocationId == lid);
- if (!bound)
- {
- throw new UserFriendlyException("当前账号未绑定该门店,无法查看");
- }
+ await EnsureUserCanAccessLocationAsync(currentUser, db, locationId);
}
///
- /// 校验是否可访问指定门店:平台管理员( )不校验 userlocation;其它账号须绑定。
+ /// 校验是否可访问指定门店:平台管理员不校验;Company Admin 可访问绑定 Company 下任意门店;其它账号须 userlocation 绑定。
///
public static async Task EnsureUserCanAccessLocationAsync(
ICurrentUser currentUser,
@@ -76,7 +69,23 @@ public static class UsAppPrintLogScopeHelper
return;
}
- await EnsureUserBoundToLocationAsync(currentUser, db, locationId);
+ if (currentUser.Id is null)
+ {
+ throw new UserFriendlyException("用户未登录");
+ }
+
+ var lid = TeamMemberListScopeHelper.NormalizeScopeKey(locationId);
+ if (string.IsNullOrEmpty(lid))
+ {
+ throw new UserFriendlyException("门店参数无效");
+ }
+
+ var accessible = await LocationScopeBindingHelper.ResolveAppAccessibleLocationIdsForUserAsync(
+ db, currentUser.Id.Value, currentUser);
+ if (!accessible.Contains(lid, StringComparer.OrdinalIgnoreCase))
+ {
+ throw new UserFriendlyException("当前账号未绑定该门店,无法查看");
+ }
}
///
@@ -107,10 +116,9 @@ public static class UsAppPrintLogScopeHelper
var map = new Dictionary(StringComparer.OrdinalIgnoreCase);
var guids = userIdStrings
.Where(x => !string.IsNullOrWhiteSpace(x))
- .Select(x => x!.Trim())
- .Select(x => Guid.TryParse(x, out var g) ? g : (Guid?)null)
- .Where(x => x.HasValue)
- .Select(x => x!.Value)
+ .Select(x => TeamMemberListScopeHelper.NormalizeScopeKey(x!))
+ .Where(x => Guid.TryParse(x, out _))
+ .Select(Guid.Parse)
.Distinct()
.ToList();
if (guids.Count == 0)
@@ -118,19 +126,27 @@ public static class UsAppPrintLogScopeHelper
return map;
}
- var users = await db.Queryable()
- .Where(u => !u.IsDeleted && guids.Contains(u.Id))
- .Select(u => new { u.Id, u.Name, u.Nick, u.UserName })
- .ToListAsync();
+ var parameters = new List(guids.Count);
+ var inClause = new List(guids.Count);
+ for (var i = 0; i < guids.Count; i++)
+ {
+ var paramName = $"@uid{i}";
+ inClause.Add(paramName);
+ parameters.Add(new SugarParameter(paramName, guids[i]));
+ }
+
+ var users = await db.Ado.SqlQueryAsync(
+ $"""
+ SELECT Id, UserName, Name, Nick
+ FROM User
+ WHERE IsDeleted = 0 AND Id IN ({string.Join(", ", inClause)})
+ """,
+ parameters);
foreach (var u in users)
{
- var display = !string.IsNullOrWhiteSpace(u.Name)
- ? u.Name!.Trim()
- : !string.IsNullOrWhiteSpace(u.Nick)
- ? u.Nick!.Trim()
- : u.UserName?.Trim() ?? string.Empty;
- map[u.Id.ToString()] = string.IsNullOrWhiteSpace(display) ? "无" : display;
+ var display = BuildOperatorDisplayName(u.Name, u.Nick, u.UserName);
+ map[TeamMemberListScopeHelper.UserKey(u.Id)] = display;
}
return map;
@@ -143,7 +159,35 @@ public static class UsAppPrintLogScopeHelper
return "无";
}
- return map.TryGetValue(createdBy.Trim(), out var name) ? name : "无";
+ var key = TeamMemberListScopeHelper.NormalizeScopeKey(createdBy);
+ return map.TryGetValue(key, out var name) ? name : "无";
+ }
+
+ private static string BuildOperatorDisplayName(string? name, string? nick, string? userName)
+ {
+ if (!string.IsNullOrWhiteSpace(name))
+ {
+ return name.Trim();
+ }
+
+ if (!string.IsNullOrWhiteSpace(nick))
+ {
+ return nick.Trim();
+ }
+
+ var user = userName?.Trim();
+ return string.IsNullOrWhiteSpace(user) ? "无" : user;
+ }
+
+ private sealed class OperatorUserRow
+ {
+ public Guid Id { get; set; }
+
+ public string? UserName { get; set; }
+
+ public string? Name { get; set; }
+
+ public string? Nick { get; set; }
}
private static bool ContainsPartnerKeyword(string? value) =>
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryPartnerDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryPartnerDbEntity.cs
new file mode 100644
index 0000000..3d6bcbe
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryPartnerDbEntity.cs
@@ -0,0 +1,18 @@
+using SqlSugar;
+
+namespace FoodLabeling.Application.Services.DbModels;
+
+[SugarTable("fl_label_category_partner")]
+public class FlLabelCategoryPartnerDbEntity
+{
+ [SugarColumn(IsPrimaryKey = true)]
+ public string Id { get; set; } = string.Empty;
+
+ public string CategoryId { get; set; } = string.Empty;
+
+ public string PartnerId { get; set; } = string.Empty;
+
+ public DateTime CreationTime { get; set; }
+
+ public string? CreatorId { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionPartnerDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionPartnerDbEntity.cs
new file mode 100644
index 0000000..de72f62
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionPartnerDbEntity.cs
@@ -0,0 +1,18 @@
+using SqlSugar;
+
+namespace FoodLabeling.Application.Services.DbModels;
+
+[SugarTable("fl_label_multiple_option_partner")]
+public class FlLabelMultipleOptionPartnerDbEntity
+{
+ [SugarColumn(IsPrimaryKey = true)]
+ public string Id { get; set; } = string.Empty;
+
+ public string MultipleOptionId { get; set; } = string.Empty;
+
+ public string PartnerId { get; set; } = string.Empty;
+
+ public DateTime CreationTime { get; set; }
+
+ public string? CreatorId { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypePartnerDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypePartnerDbEntity.cs
new file mode 100644
index 0000000..4248b98
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypePartnerDbEntity.cs
@@ -0,0 +1,18 @@
+using SqlSugar;
+
+namespace FoodLabeling.Application.Services.DbModels;
+
+[SugarTable("fl_label_type_partner")]
+public class FlLabelTypePartnerDbEntity
+{
+ [SugarColumn(IsPrimaryKey = true)]
+ public string Id { get; set; } = string.Empty;
+
+ public string LabelTypeId { get; set; } = string.Empty;
+
+ public string PartnerId { get; set; } = string.Empty;
+
+ public DateTime CreationTime { get; set; }
+
+ public string? CreatorId { get; set; }
+}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs
index 4fe5403..2b7202a 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs
@@ -151,12 +151,15 @@ public class LabelAppService : ApplicationService, ILabelAppService
};
}
- // 查询标签基础信息(分类/类型/模板)
+ // 查询标签基础信息(分类/类型/模板);类型 LeftJoin,允许未绑定 labelTypeId 的标签出现在列表
var labelRows = await _dbContext.SqlSugarClient
- .Queryable(
- (l, c, t, tpl) => l.LabelCategoryId == c.Id && l.LabelTypeId == t.Id && l.TemplateId == tpl.Id)
+ .Queryable()
+ .InnerJoin((l, c) => l.LabelCategoryId == c.Id)
+ .LeftJoin((l, c, t) => l.LabelTypeId == t.Id)
+ .InnerJoin((l, c, t, tpl) => l.TemplateId == tpl.Id)
.Where((l, c, t, tpl) => pageLabelIds.Contains(l.Id))
- .Where((l, c, t, tpl) => !l.IsDeleted && !c.IsDeleted && !t.IsDeleted && !tpl.IsDeleted)
+ .Where((l, c, t, tpl) => !l.IsDeleted && !c.IsDeleted && !tpl.IsDeleted)
+ .Where((l, c, t, tpl) => t.Id == null || !t.IsDeleted)
.Select((l, c, t, tpl) => new
{
l.Id,
@@ -248,7 +251,7 @@ public class LabelAppService : ApplicationService, ILabelAppService
Products = products,
TemplateName = x.TemplateName ?? string.Empty,
TemplateCode = x.TemplateCode ?? string.Empty,
- LabelTypeName = x.LabelTypeName ?? string.Empty,
+ LabelTypeName = string.IsNullOrWhiteSpace(x.LabelTypeName) ? "无" : x.LabelTypeName,
State = x.State,
LastEdited = x.LastEdited,
HasError = false
@@ -289,8 +292,12 @@ public class LabelAppService : ApplicationService, ILabelAppService
.FirstAsync(x => x.Id == label.TemplateId);
var category = await db.Queryable()
.FirstAsync(x => x.Id == label.LabelCategoryId);
- var type = await db.Queryable()
- .FirstAsync(x => x.Id == label.LabelTypeId);
+ FlLabelTypeDbEntity? type = null;
+ if (!string.IsNullOrWhiteSpace(label.LabelTypeId))
+ {
+ type = await db.Queryable()
+ .FirstAsync(x => x.Id == label.LabelTypeId);
+ }
var productIds = await db.Queryable()
.Where(x => x.LabelId == label.Id)
@@ -368,10 +375,8 @@ public class LabelAppService : ApplicationService, ILabelAppService
{
throw new UserFriendlyException("标签分类Id不能为空");
}
- if (string.IsNullOrWhiteSpace(input.LabelTypeId))
- {
- throw new UserFriendlyException("标签类型Id不能为空");
- }
+
+ await EnsureLabelTypeExistsIfProvidedAsync(input.LabelTypeId);
var scope = await LabelRegionScopeHelper.ResolveScopeForSaveAsync(
_dbContext.SqlSugarClient,
@@ -413,7 +418,7 @@ public class LabelAppService : ApplicationService, ILabelAppService
TemplateId = template.Id,
LocationId = scope.PrimaryLocationId,
LabelCategoryId = input.LabelCategoryId?.Trim(),
- LabelTypeId = input.LabelTypeId?.Trim(),
+ LabelTypeId = NormalizeOptionalLabelTypeId(input.LabelTypeId),
State = input.State,
LabelType = null,
LabelInfoJson = input.LabelInfoJson == null ? null : JsonSerializer.Serialize(input.LabelInfoJson)
@@ -484,10 +489,8 @@ public class LabelAppService : ApplicationService, ILabelAppService
{
throw new UserFriendlyException("标签分类Id不能为空");
}
- if (string.IsNullOrWhiteSpace(input.LabelTypeId))
- {
- throw new UserFriendlyException("标签类型Id不能为空");
- }
+
+ await EnsureLabelTypeExistsIfProvidedAsync(input.LabelTypeId);
var scope = await LabelRegionScopeHelper.ResolveScopeForSaveAsync(
_dbContext.SqlSugarClient,
@@ -510,7 +513,7 @@ public class LabelAppService : ApplicationService, ILabelAppService
label.TemplateId = template.Id;
label.LocationId = scope.PrimaryLocationId;
label.LabelCategoryId = input.LabelCategoryId?.Trim();
- label.LabelTypeId = input.LabelTypeId?.Trim();
+ label.LabelTypeId = NormalizeOptionalLabelTypeId(input.LabelTypeId);
label.State = input.State;
label.LastModifierId = CurrentUser?.Id?.ToString();
label.LastModificationTime = now;
@@ -873,5 +876,27 @@ public class LabelAppService : ApplicationService, ILabelAppService
var v = (raw ?? string.Empty).Trim().ToLowerInvariant();
return v is "line" or "dotted" ? v : "none";
}
+
+ private static string? NormalizeOptionalLabelTypeId(string? labelTypeId)
+ {
+ var id = labelTypeId?.Trim();
+ return string.IsNullOrWhiteSpace(id) ? null : id;
+ }
+
+ private async Task EnsureLabelTypeExistsIfProvidedAsync(string? labelTypeId)
+ {
+ var id = NormalizeOptionalLabelTypeId(labelTypeId);
+ if (id is null)
+ {
+ return;
+ }
+
+ var exists = await _dbContext.SqlSugarClient.Queryable()
+ .AnyAsync(x => !x.IsDeleted && x.Id == id);
+ if (!exists)
+ {
+ throw new UserFriendlyException("标签类型不存在");
+ }
+ }
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs
index 90abbc1..0c23f30 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs
@@ -35,7 +35,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
(x.DisplayText != null && x.DisplayText.Contains(keyword!)))
.WhereIF(input.State != null, x => x.State == input.State);
- query = await ApplyCategoryScopeFilterAsync(query, input.GroupId, input.LocationId);
+ query = await ApplyCategoryScopeFilterAsync(query, input.PartnerId, input.GroupId, input.LocationId);
// Sorting 仅允许白名单字段,避免 Unknown column/注入风险
if (!string.IsNullOrWhiteSpace(input.Sorting))
@@ -72,10 +72,15 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
var labelStatsMap = await BuildCategoryLabelStatsMapAsync(ids);
var scopeMap = await BuildCategoryScopeMapAsync(entities);
+ var partnerScopeMap = await LabelEntityPartnerScopeHelper.BuildPartnerScopeDisplayMapAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Category,
+ ids);
var items = entities.Select(x =>
{
scopeMap.TryGetValue(x.Id, out var scope);
+ partnerScopeMap.TryGetValue(x.Id, out var partnerScope);
labelStatsMap.TryGetValue(x.Id, out var labelStats);
var creationTime = ResolveCategoryCreationTime(x);
return new LabelCategoryGetListOutputDto
@@ -88,6 +93,10 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
State = x.State,
ButtonAppearance = x.ButtonAppearance,
AvailabilityType = x.AvailabilityType,
+ AppliedPartnerType = partnerScope?.AppliedPartnerType ?? LabelEntityPartnerScopeHelper.ScopeAll,
+ Company = partnerScope?.Company ?? LabelEntityPartnerScopeHelper.AllCompaniesDisplay,
+ PartnerIds = partnerScope?.PartnerIds ?? new List(),
+ CompanyIds = partnerScope?.PartnerIds ?? new List(),
OrderNum = x.OrderNum,
NoOfLabels = labelStats?.Count ?? 0,
CreationTime = creationTime,
@@ -112,6 +121,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
}
var dto = MapToGetOutput(entity);
+ await ApplyPartnerScopeToGetOutputAsync(dto, entity.Id);
if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
{
var locationIds = await _dbContext.SqlSugarClient.Queryable()
@@ -139,7 +149,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
var displayText = input.DisplayText?.Trim();
var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance);
- var (availabilityType, mergedLocationIds) = await ResolveCategoryScopeForSaveAsync(input);
+ var (partnerScope, availabilityType, mergedLocationIds) = await ResolveCategoryScopeForSaveAsync(input);
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && (x.CategoryCode == code || x.CategoryName == name));
@@ -170,6 +180,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
+ await SaveCategoryPartnerScopeAsync(entity.Id, partnerScope, currentUserId, now);
await SaveCategoryLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now);
return await GetAsync(entity.Id);
}
@@ -192,7 +203,7 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
var displayText = input.DisplayText?.Trim();
var appearance = CategoryAppearanceStorageHelper.NormalizeButtonAppearanceForStorage(input.ButtonAppearance);
- var (availabilityType, mergedLocationIds) = await ResolveCategoryScopeForSaveAsync(input);
+ var (partnerScope, availabilityType, mergedLocationIds) = await ResolveCategoryScopeForSaveAsync(input);
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.Id != id && (x.CategoryCode == code || x.CategoryName == name));
@@ -213,6 +224,8 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
+ await SaveCategoryPartnerScopeAsync(entity.Id, partnerScope, entity.LastModifierId,
+ entity.LastModificationTime ?? DateTime.Now);
await SaveCategoryLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId,
entity.LastModificationTime ?? DateTime.Now);
return await GetAsync(id);
@@ -234,6 +247,14 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
throw new UserFriendlyException("该标签分类已被标签引用,无法删除");
}
+ await LabelEntityPartnerScopeHelper.DeletePartnerScopeRowsAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Category,
+ id);
+ await _dbContext.SqlSugarClient.Deleteable()
+ .Where(x => x.CategoryId == id)
+ .ExecuteCommandAsync();
+
entity.IsDeleted = true;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
@@ -256,9 +277,15 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
};
}
- private async Task<(string AvailabilityType, List LocationIds)> ResolveCategoryScopeForSaveAsync(
+ private async Task<(LabelEntityPartnerScopeHelper.LabelEntityPartnerScopeSaveResult PartnerScope, string AvailabilityType, List LocationIds)> ResolveCategoryScopeForSaveAsync(
LabelCategoryCreateInputVo input)
{
+ var partnerScope = await LabelEntityPartnerScopeHelper.ResolvePartnerScopeForSaveAsync(
+ _dbContext.SqlSugarClient,
+ input.AppliedPartnerType,
+ input.PartnerIds,
+ input.CompanyIds);
+
var regionIds = NormalizeRegionIds(input);
var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds);
var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant();
@@ -278,20 +305,37 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)");
}
- if (availabilityType == "ALL")
+ var partnerSpecified = string.Equals(
+ partnerScope.AppliedPartnerType,
+ LabelEntityPartnerScopeHelper.ScopeSpecified,
+ StringComparison.OrdinalIgnoreCase);
+ var locationSpecified = string.Equals(availabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase);
+
+ if (partnerSpecified || locationSpecified)
{
- return ("ALL", new List());
+ var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
+ _dbContext.SqlSugarClient,
+ partnerSpecified ? partnerScope.PartnerIds : null,
+ locationSpecified ? regionIds : null,
+ locationSpecified ? explicitLocationIds : null);
+ if (merged.Count == 0)
+ {
+ throw new UserFriendlyException("指定适用范围后至少需要匹配到一个有效门店");
+ }
+
+ if (locationSpecified)
+ {
+ await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
+ return (partnerScope, "SPECIFIED", merged);
+ }
}
- var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
- _dbContext.SqlSugarClient, (IReadOnlyList?)null, regionIds, explicitLocationIds);
- if (merged.Count == 0)
+ if (string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase))
{
- throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ return (partnerScope, "ALL", new List());
}
- await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
- return ("SPECIFIED", merged);
+ throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
}
private static List NormalizeRegionIds(LabelCategoryCreateInputVo input)
@@ -312,17 +356,24 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
private async Task> ApplyCategoryScopeFilterAsync(
ISugarQueryable query,
+ string? partnerId,
string? groupId,
string? locationId)
{
- var scopeLocIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync(
- _dbContext.SqlSugarClient, groupId, locationId);
- if (scopeLocIds is null)
+ var scopedLocationIds = await TeamMemberListScopeHelper.ResolveListLocationIdsAsync(
+ CurrentUser, _dbContext, partnerId, groupId, locationId);
+ var scopedPartnerIds = await LabelEntityPartnerScopeHelper.ResolveListPartnerIdsAsync(
+ _dbContext.SqlSugarClient, partnerId, scopedLocationIds);
+
+ query = await LabelEntityPartnerScopeHelper.ApplyCategoryPartnerListFilterAsync(
+ _dbContext.SqlSugarClient, query, scopedPartnerIds);
+
+ if (scopedLocationIds is null)
{
return query;
}
- if (scopeLocIds.Count == 0)
+ if (scopedLocationIds.Count == 0)
{
return query.Where(c => c.AvailabilityType == "ALL");
}
@@ -330,10 +381,43 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ
return query.Where(c =>
c.AvailabilityType == "ALL" ||
SqlFunc.Subqueryable()
- .Where(cl => cl.CategoryId == c.Id && scopeLocIds.Contains(cl.LocationId))
+ .Where(cl => cl.CategoryId == c.Id && scopedLocationIds.Contains(cl.LocationId))
.Any());
}
+ private async Task SaveCategoryPartnerScopeAsync(
+ string categoryId,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerScopeSaveResult partnerScope,
+ string? currentUserId,
+ DateTime now)
+ {
+ await LabelEntityPartnerScopeHelper.SavePartnerScopeAsync(
+ _dbContext.SqlSugarClient,
+ _guidGenerator,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Category,
+ categoryId,
+ partnerScope,
+ currentUserId,
+ now);
+ }
+
+ private async Task ApplyPartnerScopeToGetOutputAsync(LabelCategoryGetOutputDto dto, string entityId)
+ {
+ var map = await LabelEntityPartnerScopeHelper.BuildPartnerScopeDisplayMapAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Category,
+ new[] { entityId });
+ if (!map.TryGetValue(entityId, out var scope))
+ {
+ return;
+ }
+
+ dto.AppliedPartnerType = scope.AppliedPartnerType;
+ dto.Company = scope.Company;
+ dto.PartnerIds = scope.PartnerIds;
+ dto.CompanyIds = scope.PartnerIds;
+ }
+
private const string AllRegionsDisplay = "All Regions";
private const string AllLocationsDisplay = "All Locations";
private const string EmptyDisplay = "无";
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs
index a126fa3..e02596a 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs
@@ -27,14 +27,22 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
{
RefAsync total = 0;
var keyword = input.Keyword?.Trim();
- var scopedLocationIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync(
- _dbContext.SqlSugarClient, input.GroupId, input.LocationId);
+ var scopedLocationIds = await TeamMemberListScopeHelper.ResolveListLocationIdsAsync(
+ CurrentUser,
+ _dbContext,
+ input.PartnerId,
+ input.GroupId,
+ input.LocationId);
+ var scopedPartnerIds = await LabelEntityPartnerScopeHelper.ResolveListPartnerIdsAsync(
+ _dbContext.SqlSugarClient, input.PartnerId, scopedLocationIds);
var query = _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.OptionCode.Contains(keyword!) || x.OptionName.Contains(keyword!))
.WhereIF(input.State != null, x => x.State == input.State);
+ query = await LabelEntityPartnerScopeHelper.ApplyMultipleOptionPartnerListFilterAsync(
+ _dbContext.SqlSugarClient, query, scopedPartnerIds);
query = ApplyMultipleOptionScopeFilter(query, scopedLocationIds);
if (!string.IsNullOrWhiteSpace(input.Sorting))
@@ -48,10 +56,15 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
var scopeMap = await BuildMultipleOptionScopeMapAsync(entities);
+ var partnerScopeMap = await LabelEntityPartnerScopeHelper.BuildPartnerScopeDisplayMapAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.MultipleOption,
+ entities.Select(x => x.Id).ToList());
var items = entities.Select(x =>
{
scopeMap.TryGetValue(x.Id, out var scope);
+ partnerScopeMap.TryGetValue(x.Id, out var partnerScope);
return new LabelMultipleOptionGetListOutputDto
{
Id = x.Id,
@@ -60,6 +73,10 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
OptionValuesJson = x.OptionValuesJson,
State = x.State,
AvailabilityType = x.AvailabilityType,
+ AppliedPartnerType = partnerScope?.AppliedPartnerType ?? LabelEntityPartnerScopeHelper.ScopeAll,
+ Company = partnerScope?.Company ?? LabelEntityPartnerScopeHelper.AllCompaniesDisplay,
+ PartnerIds = partnerScope?.PartnerIds ?? new List(),
+ CompanyIds = partnerScope?.PartnerIds ?? new List(),
OrderNum = x.OrderNum,
LastEdited = x.LastModificationTime ?? x.CreationTime,
Region = scope?.Region ?? EmptyDisplay,
@@ -82,6 +99,7 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
}
var dto = MapToGetOutput(entity);
+ await ApplyPartnerScopeToGetOutputAsync(dto, entity.Id);
if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
{
var locationIds = await _dbContext.SqlSugarClient.Queryable()
@@ -107,7 +125,7 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("多选项名称不能为空");
}
- var (availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input);
+ var (partnerScope, availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input);
if (await IsMultipleOptionDuplicatedAsync(code, name, excludeId: null))
{
@@ -134,6 +152,7 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
+ await SaveMultipleOptionPartnerScopeAsync(entity.Id, partnerScope, currentUserId, now);
await SaveMultipleOptionLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now);
return await GetAsync(entity.Id);
}
@@ -154,7 +173,7 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("多选项名称不能为空");
}
- var (availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input);
+ var (partnerScope, availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input);
if (await IsMultipleOptionDuplicatedAsync(code, name, excludeId: id))
{
@@ -171,6 +190,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
+ await SaveMultipleOptionPartnerScopeAsync(entity.Id, partnerScope, entity.LastModifierId,
+ entity.LastModificationTime ?? DateTime.Now);
await SaveMultipleOptionLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId,
entity.LastModificationTime ?? DateTime.Now);
return await GetAsync(id);
@@ -185,6 +206,10 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
return;
}
+ await LabelEntityPartnerScopeHelper.DeletePartnerScopeRowsAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.MultipleOption,
+ id);
await _dbContext.SqlSugarClient.Deleteable()
.Where(x => x.MultipleOptionId == id)
.ExecuteCommandAsync();
@@ -199,9 +224,15 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
private const string AllRegionsDisplay = "All Regions";
private const string AllLocationsDisplay = "All Locations";
- private async Task<(string AvailabilityType, List LocationIds)> ResolveMultipleOptionScopeForSaveAsync(
+ private async Task<(LabelEntityPartnerScopeHelper.LabelEntityPartnerScopeSaveResult PartnerScope, string AvailabilityType, List LocationIds)> ResolveMultipleOptionScopeForSaveAsync(
LabelMultipleOptionCreateInputVo input)
{
+ var partnerScope = await LabelEntityPartnerScopeHelper.ResolvePartnerScopeForSaveAsync(
+ _dbContext.SqlSugarClient,
+ input.AppliedPartnerType,
+ input.PartnerIds,
+ input.CompanyIds);
+
var regionIds = NormalizeRegionIds(input);
var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds);
var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant();
@@ -221,20 +252,70 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)");
}
- if (availabilityType == "ALL")
+ var partnerSpecified = string.Equals(
+ partnerScope.AppliedPartnerType,
+ LabelEntityPartnerScopeHelper.ScopeSpecified,
+ StringComparison.OrdinalIgnoreCase);
+ var locationSpecified = string.Equals(availabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase);
+
+ if (partnerSpecified || locationSpecified)
{
- return ("ALL", new List());
+ var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
+ _dbContext.SqlSugarClient,
+ partnerSpecified ? partnerScope.PartnerIds : null,
+ locationSpecified ? regionIds : null,
+ locationSpecified ? explicitLocationIds : null);
+ if (merged.Count == 0)
+ {
+ throw new UserFriendlyException("指定适用范围后至少需要匹配到一个有效门店");
+ }
+
+ if (locationSpecified)
+ {
+ await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
+ return (partnerScope, "SPECIFIED", merged);
+ }
}
- var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
- _dbContext.SqlSugarClient, (IReadOnlyList?)null, regionIds, explicitLocationIds);
- if (merged.Count == 0)
+ if (string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase))
{
- throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ return (partnerScope, "ALL", new List());
+ }
+
+ throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ }
+
+ private async Task SaveMultipleOptionPartnerScopeAsync(
+ string multipleOptionId,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerScopeSaveResult partnerScope,
+ string? currentUserId,
+ DateTime now)
+ {
+ await LabelEntityPartnerScopeHelper.SavePartnerScopeAsync(
+ _dbContext.SqlSugarClient,
+ _guidGenerator,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.MultipleOption,
+ multipleOptionId,
+ partnerScope,
+ currentUserId,
+ now);
+ }
+
+ private async Task ApplyPartnerScopeToGetOutputAsync(LabelMultipleOptionGetOutputDto dto, string entityId)
+ {
+ var map = await LabelEntityPartnerScopeHelper.BuildPartnerScopeDisplayMapAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.MultipleOption,
+ new[] { entityId });
+ if (!map.TryGetValue(entityId, out var scope))
+ {
+ return;
}
- await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
- return ("SPECIFIED", merged);
+ dto.AppliedPartnerType = scope.AppliedPartnerType;
+ dto.Company = scope.Company;
+ dto.PartnerIds = scope.PartnerIds;
+ dto.CompanyIds = scope.PartnerIds;
}
private static List NormalizeRegionIds(LabelMultipleOptionCreateInputVo input)
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelTypeAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelTypeAppService.cs
index d012691..34ac840 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelTypeAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelTypeAppService.cs
@@ -27,14 +27,22 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
{
RefAsync total = 0;
var keyword = input.Keyword?.Trim();
- var scopedLocationIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync(
- _dbContext.SqlSugarClient, input.GroupId, input.LocationId);
+ var scopedLocationIds = await TeamMemberListScopeHelper.ResolveListLocationIdsAsync(
+ CurrentUser,
+ _dbContext,
+ input.PartnerId,
+ input.GroupId,
+ input.LocationId);
+ var scopedPartnerIds = await LabelEntityPartnerScopeHelper.ResolveListPartnerIdsAsync(
+ _dbContext.SqlSugarClient, input.PartnerId, scopedLocationIds);
var query = _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.TypeCode.Contains(keyword!) || x.TypeName.Contains(keyword!))
.WhereIF(input.State != null, x => x.State == input.State);
+ query = await LabelEntityPartnerScopeHelper.ApplyTypePartnerListFilterAsync(
+ _dbContext.SqlSugarClient, query, scopedPartnerIds);
query = ApplyLabelTypeScopeFilter(query, scopedLocationIds);
if (!string.IsNullOrWhiteSpace(input.Sorting))
@@ -51,10 +59,15 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
var countMap = await BuildTypeLabelStatsMapAsync(ids, scopedLocationIds);
var scopeMap = await BuildTypeConfiguredScopeMapAsync(entities);
+ var partnerScopeMap = await LabelEntityPartnerScopeHelper.BuildPartnerScopeDisplayMapAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Type,
+ ids);
var items = entities.Select(x =>
{
scopeMap.TryGetValue(x.Id, out var scope);
+ partnerScopeMap.TryGetValue(x.Id, out var partnerScope);
countMap.TryGetValue(x.Id, out var stats);
var locationDisplay = scope?.Location ?? EmptyDisplay;
return new LabelTypeGetListOutputDto
@@ -64,6 +77,10 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
TypeName = x.TypeName,
State = x.State,
AvailabilityType = x.AvailabilityType,
+ AppliedPartnerType = partnerScope?.AppliedPartnerType ?? LabelEntityPartnerScopeHelper.ScopeAll,
+ Company = partnerScope?.Company ?? LabelEntityPartnerScopeHelper.AllCompaniesDisplay,
+ PartnerIds = partnerScope?.PartnerIds ?? new List(),
+ CompanyIds = partnerScope?.PartnerIds ?? new List(),
OrderNum = x.OrderNum,
NoOfLabels = stats?.Count ?? 0,
LastEdited = stats?.MaxEdited ?? x.LastModificationTime ?? x.CreationTime,
@@ -88,6 +105,7 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
}
var dto = MapToGetOutput(entity);
+ await ApplyPartnerScopeToGetOutputAsync(dto, entity.Id);
if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
{
var locationIds = await _dbContext.SqlSugarClient.Queryable()
@@ -113,7 +131,7 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("类型编码和名称不能为空");
}
- var (availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input);
+ var (partnerScope, availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input);
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && (x.TypeCode == code || x.TypeName == name));
@@ -141,6 +159,7 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
+ await SaveTypePartnerScopeAsync(entity.Id, partnerScope, currentUserId, now);
await SaveTypeLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now);
return await GetAsync(entity.Id);
}
@@ -161,7 +180,7 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("类型编码和名称不能为空");
}
- var (availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input);
+ var (partnerScope, availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input);
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.Id != id && (x.TypeCode == code || x.TypeName == name));
@@ -179,6 +198,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
+ await SaveTypePartnerScopeAsync(entity.Id, partnerScope, entity.LastModifierId,
+ entity.LastModificationTime ?? DateTime.Now);
await SaveTypeLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId,
entity.LastModificationTime ?? DateTime.Now);
return await GetAsync(id);
@@ -200,6 +221,10 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("该标签类型已被标签引用,无法删除");
}
+ await LabelEntityPartnerScopeHelper.DeletePartnerScopeRowsAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Type,
+ id);
await _dbContext.SqlSugarClient.Deleteable()
.Where(x => x.LabelTypeId == id)
.ExecuteCommandAsync();
@@ -214,9 +239,15 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
private const string AllRegionsDisplay = "All Regions";
private const string AllLocationsDisplay = "All Locations";
- private async Task<(string AvailabilityType, List LocationIds)> ResolveTypeScopeForSaveAsync(
+ private async Task<(LabelEntityPartnerScopeHelper.LabelEntityPartnerScopeSaveResult PartnerScope, string AvailabilityType, List LocationIds)> ResolveTypeScopeForSaveAsync(
LabelTypeCreateInputVo input)
{
+ var partnerScope = await LabelEntityPartnerScopeHelper.ResolvePartnerScopeForSaveAsync(
+ _dbContext.SqlSugarClient,
+ input.AppliedPartnerType,
+ input.PartnerIds,
+ input.CompanyIds);
+
var regionIds = NormalizeRegionIds(input);
var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds);
var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant();
@@ -236,20 +267,70 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)");
}
- if (availabilityType == "ALL")
+ var partnerSpecified = string.Equals(
+ partnerScope.AppliedPartnerType,
+ LabelEntityPartnerScopeHelper.ScopeSpecified,
+ StringComparison.OrdinalIgnoreCase);
+ var locationSpecified = string.Equals(availabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase);
+
+ if (partnerSpecified || locationSpecified)
{
- return ("ALL", new List());
+ var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
+ _dbContext.SqlSugarClient,
+ partnerSpecified ? partnerScope.PartnerIds : null,
+ locationSpecified ? regionIds : null,
+ locationSpecified ? explicitLocationIds : null);
+ if (merged.Count == 0)
+ {
+ throw new UserFriendlyException("指定适用范围后至少需要匹配到一个有效门店");
+ }
+
+ if (locationSpecified)
+ {
+ await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
+ return (partnerScope, "SPECIFIED", merged);
+ }
}
- var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
- _dbContext.SqlSugarClient, (IReadOnlyList?)null, regionIds, explicitLocationIds);
- if (merged.Count == 0)
+ if (string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase))
{
- throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ return (partnerScope, "ALL", new List());
+ }
+
+ throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ }
+
+ private async Task SaveTypePartnerScopeAsync(
+ string labelTypeId,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerScopeSaveResult partnerScope,
+ string? currentUserId,
+ DateTime now)
+ {
+ await LabelEntityPartnerScopeHelper.SavePartnerScopeAsync(
+ _dbContext.SqlSugarClient,
+ _guidGenerator,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Type,
+ labelTypeId,
+ partnerScope,
+ currentUserId,
+ now);
+ }
+
+ private async Task ApplyPartnerScopeToGetOutputAsync(LabelTypeGetOutputDto dto, string entityId)
+ {
+ var map = await LabelEntityPartnerScopeHelper.BuildPartnerScopeDisplayMapAsync(
+ _dbContext.SqlSugarClient,
+ LabelEntityPartnerScopeHelper.LabelEntityPartnerKind.Type,
+ new[] { entityId });
+ if (!map.TryGetValue(entityId, out var scope))
+ {
+ return;
}
- await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
- return ("SPECIFIED", merged);
+ dto.AppliedPartnerType = scope.AppliedPartnerType;
+ dto.Company = scope.Company;
+ dto.PartnerIds = scope.PartnerIds;
+ dto.CompanyIds = scope.PartnerIds;
}
private static List NormalizeRegionIds(LabelTypeCreateInputVo input)
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs
index 8f6ff0d..c39f1d4 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs
@@ -41,7 +41,7 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp
(x.DisplayText != null && x.DisplayText.Contains(keyword!)))
.WhereIF(input.State != null, x => x.State == input.State);
- query = await ApplyCategoryScopeFilterAsync(query, input.GroupId, input.LocationId);
+ query = await ApplyCategoryScopeFilterAsync(query, input.PartnerId, input.GroupId, input.LocationId);
// Sorting 仅允许白名单字段,避免不同数据库列命名导致 Unknown column
// 同时避免将 input.Sorting 原样拼接到 SQL(存在注入风险)
@@ -116,6 +116,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp
throw new UserFriendlyException("类别不存在");
}
+ await EnsureCategoryVisibleToCurrentUserAsync(entity);
+
var dto = MapToGetOutput(entity);
if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
{
@@ -189,6 +191,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp
throw new UserFriendlyException("类别不存在");
}
+ await EnsureCategoryVisibleToCurrentUserAsync(entity);
+
var code = NormalizeCategoryCode(input.CategoryCode);
var name = input.CategoryName?.Trim();
if (string.IsNullOrWhiteSpace(name))
@@ -231,6 +235,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp
return;
}
+ await EnsureCategoryVisibleToCurrentUserAsync(entity);
+
// 若被产品引用则不允许删除
var usedByProduct = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.CategoryId == id);
@@ -321,28 +327,59 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp
private async Task> ApplyCategoryScopeFilterAsync(
ISugarQueryable query,
+ string? partnerId,
string? groupId,
string? locationId)
{
- var scopeLocIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync(
- _dbContext.SqlSugarClient, groupId, locationId);
- if (scopeLocIds is null)
+ var scopedLocationIds = await TeamMemberListScopeHelper.ResolveListLocationIdsAsync(
+ CurrentUser, _dbContext, partnerId, groupId, locationId);
+ if (scopedLocationIds is null)
{
return query;
}
- if (scopeLocIds.Count == 0)
+ if (scopedLocationIds.Count == 0)
{
- return query.Where(c => c.AvailabilityType == "ALL");
+ return query.Where(_ => false);
}
+ // 非平台管理员:仅返回绑定门店与可见范围有交集的分类(不再因 AvailabilityType=ALL 暴露其它公司数据)
return query.Where(c =>
- c.AvailabilityType == "ALL" ||
SqlFunc.Subqueryable()
- .Where(cl => cl.CategoryId == c.Id && scopeLocIds.Contains(cl.LocationId))
+ .Where(cl => cl.CategoryId == c.Id && scopedLocationIds.Contains(cl.LocationId))
.Any());
}
+ ///
+ /// 详情/编辑/删除:非管理员须与绑定门店范围有交集。
+ ///
+ private async Task EnsureCategoryVisibleToCurrentUserAsync(FlProductCategoryDbEntity entity)
+ {
+ if (ReportsRoleHelper.IsAdminRole(CurrentUser))
+ {
+ return;
+ }
+
+ var scopedLocationIds = await TeamMemberListScopeHelper.ResolveListLocationIdsAsync(
+ CurrentUser, _dbContext, null, null, null);
+ if (scopedLocationIds is null)
+ {
+ return;
+ }
+
+ if (scopedLocationIds.Count == 0)
+ {
+ throw new UserFriendlyException("无权访问该类别");
+ }
+
+ var visible = await _dbContext.SqlSugarClient.Queryable()
+ .AnyAsync(cl => cl.CategoryId == entity.Id && scopedLocationIds.Contains(cl.LocationId));
+ if (!visible)
+ {
+ throw new UserFriendlyException("无权访问该类别");
+ }
+ }
+
private async Task SaveCategoryLocationsAsync(
string categoryId,
string availabilityType,
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ReportsAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ReportsAppService.cs
index 6379a81..443f1ec 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ReportsAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ReportsAppService.cs
@@ -51,20 +51,25 @@ public class ReportsAppService : ApplicationService, IReportsAppService
throw new UserFriendlyException("用户未登录");
}
- var locationIds = await ResolveFilteredLocationIdsAsync(input.PartnerId, input.GroupId, input.LocationId);
+ var locationIds = await ReportsLocationScopeHelper.ResolveReportLocationIdsAsync(
+ CurrentUser,
+ _dbContext.SqlSugarClient,
+ input.PartnerId,
+ input.GroupId,
+ input.LocationId);
if (locationIds is not null && locationIds.Count == 0)
{
return EmptyPrintLogPage(input);
}
var (rangeStart, rangeEndExcl) = ResolveDateRange(input.StartDate, input.EndDate);
- var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
+ var viewAllPrints = await ResolveViewAllPrintsAsync();
var currentUserIdStr = CurrentUser.Id.Value.ToString();
var keyword = input.Keyword?.Trim();
RefAsync total = 0;
- var query = BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword)
+ var query = BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword)
.LeftJoin((t, l, p, lc, pc, loc, tpl) => t.TemplateId == tpl.Id)
.Where((t, l, p, lc, pc, loc, tpl) =>
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= rangeStart &&
@@ -166,18 +171,23 @@ public class ReportsAppService : ApplicationService, IReportsAppService
throw new UserFriendlyException("用户未登录");
}
- var locationIds = await ResolveFilteredLocationIdsAsync(input.PartnerId, input.GroupId, input.LocationId);
+ var locationIds = await ReportsLocationScopeHelper.ResolveReportLocationIdsAsync(
+ CurrentUser,
+ _dbContext.SqlSugarClient,
+ input.PartnerId,
+ input.GroupId,
+ input.LocationId);
if (locationIds is not null && locationIds.Count == 0)
{
return BuildEmptyPdf("print-log-empty.pdf");
}
var (rangeStart, rangeEndExcl) = ResolveDateRange(input.StartDate, input.EndDate);
- var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
+ var viewAllPrints = await ResolveViewAllPrintsAsync();
var currentUserIdStr = CurrentUser.Id.Value.ToString();
var keyword = input.Keyword?.Trim();
- var query = BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword)
+ var query = BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword)
.LeftJoin((t, l, p, lc, pc, loc, tpl) => t.TemplateId == tpl.Id)
.Where((t, l, p, lc, pc, loc, tpl) =>
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= rangeStart &&
@@ -305,7 +315,12 @@ public class ReportsAppService : ApplicationService, IReportsAppService
throw new UserFriendlyException("用户未登录");
}
- var locationIds = await ResolveFilteredLocationIdsAsync(input.PartnerId, input.GroupId, input.LocationId);
+ var locationIds = await ReportsLocationScopeHelper.ResolveReportLocationIdsAsync(
+ CurrentUser,
+ _dbContext.SqlSugarClient,
+ input.PartnerId,
+ input.GroupId,
+ input.LocationId);
if (locationIds is not null && locationIds.Count == 0)
{
var emptyMs = ReportsPrintLogExcelHelper.BuildWorkbook(Array.Empty());
@@ -315,11 +330,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService
}
var (rangeStart, rangeEndExcl) = ResolveDateRange(input.StartDate, input.EndDate);
- var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
+ var viewAllPrints = await ResolveViewAllPrintsAsync();
var currentUserIdStr = CurrentUser.Id.Value.ToString();
var keyword = input.Keyword?.Trim();
- var query = BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword)
+ var query = BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword)
.LeftJoin((t, l, p, lc, pc, loc, tpl) => t.TemplateId == tpl.Id)
.Where((t, l, p, lc, pc, loc, tpl) =>
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= rangeStart &&
@@ -414,11 +429,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService
}
var (rangeStart, rangeEndExcl) = ResolveDateRange(input.StartDate, input.EndDate);
- var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
+ var viewAllPrints = await ResolveViewAllPrintsAsync();
var currentUserIdStr = CurrentUser.Id.Value.ToString();
var templateKeyword = input.Keyword?.Trim();
- var groupedRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword: null,
+ var groupedRows = await BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword: null,
restrictToCreator: false)
.LeftJoin((t, l, p, lc, pc, loc, tpl) => t.TemplateId == tpl.Id)
.Where((t, l, p, lc, pc, loc, tpl) =>
@@ -499,17 +514,17 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var prevEndExcl = curStart;
var prevStart = curStart - span;
- var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
+ var viewAllPrints = await ResolveViewAllPrintsAsync();
var currentUserIdStr = CurrentUser.Id.Value.ToString();
var keyword = input.Keyword?.Trim();
- var totalCur = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
+ var totalCur = await BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl)
.CountAsync();
- var totalPrev = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
+ var totalPrev = await BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= prevStart &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < prevEndExcl)
@@ -520,7 +535,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var avgDaily = Math.Round((decimal)totalCur / dayCount, 2);
var avgDailyPrev = Math.Round((decimal)totalPrev / prevDayCount, 2);
- var categoryRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
+ var categoryRows = await BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
l.LabelCategoryId != null &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
@@ -531,7 +546,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var topCat = categoryRows.OrderByDescending(x => x.Cnt).FirstOrDefault();
- var productRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
+ var productRows = await BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
!string.IsNullOrEmpty(p.Id) &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
@@ -552,7 +567,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var trendEndExcl = trendEndDay.AddDays(1);
- var trendRaw = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
+ var trendRaw = await BuildReportTaskCore(locationIds, viewAllPrints, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= trendStartDay &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < trendEndExcl)
@@ -704,7 +719,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
FlProductCategoryDbEntity, LocationAggregateRoot>
BuildReportTaskCore(
List? locationIds,
- bool isAdmin,
+ bool viewAllPrints,
string currentUserIdStr,
string? keyword,
bool restrictToCreator = true)
@@ -717,7 +732,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
.LeftJoin((t, l, p, lc, pc, loc) =>
t.LocationId != null && SqlFunc.ToString(loc.Id) == t.LocationId)
.Where((t, l, p, lc, pc, loc) => !loc.IsDeleted)
- .WhereIF(restrictToCreator && !isAdmin, (t, l, p, lc, pc, loc) => t.CreatedBy == currentUserIdStr)
+ .WhereIF(restrictToCreator && !viewAllPrints, (t, l, p, lc, pc, loc) => t.CreatedBy == currentUserIdStr)
.WhereIF(locationIds is not null, (t, l, p, lc, pc, loc) => locationIds!.Contains(t.LocationId!))
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
(t, l, p, lc, pc, loc) =>
@@ -739,56 +754,8 @@ public class ReportsAppService : ApplicationService, IReportsAppService
private static decimal CalcChangeRate(int current, int previous) =>
CalcChangeRate((decimal)current, (decimal)previous);
- private async Task?> ResolveFilteredLocationIdsAsync(string? partnerId, string? groupId,
- string? locationId)
- {
- var locId = locationId?.Trim();
- if (!string.IsNullOrWhiteSpace(locId))
- {
- return new List { locId };
- }
-
- var gid = groupId?.Trim();
- var pid = partnerId?.Trim();
-
- if (string.IsNullOrWhiteSpace(pid) && string.IsNullOrWhiteSpace(gid))
- {
- return null;
- }
-
- var q = _dbContext.SqlSugarClient.Queryable().Where(x => !x.IsDeleted);
-
- if (!string.IsNullOrWhiteSpace(gid))
- {
- var g = await _dbContext.SqlSugarClient.Queryable()
- .FirstAsync(x => !x.IsDeleted && x.Id == gid);
- if (g is null)
- {
- return new List();
- }
-
- var gName = g.GroupName?.Trim() ?? string.Empty;
- var partner = await _dbContext.SqlSugarClient.Queryable()
- .FirstAsync(x => !x.IsDeleted && x.Id == g.PartnerId);
- var pName = partner?.PartnerName?.Trim() ?? string.Empty;
- q = q.Where(x => x.GroupName == gName && x.Partner == pName);
- }
- else if (!string.IsNullOrWhiteSpace(pid))
- {
- var partner = await _dbContext.SqlSugarClient.Queryable()
- .FirstAsync(x => !x.IsDeleted && x.Id == pid);
- if (partner is null)
- {
- return new List();
- }
-
- var pName = partner.PartnerName?.Trim() ?? string.Empty;
- q = q.Where(x => x.Partner == pName);
- }
-
- var ids = await q.Select(x => SqlFunc.ToString(x.Id)).ToListAsync();
- return ids;
- }
+ private Task ResolveViewAllPrintsAsync() =>
+ UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync(CurrentUser, _dbContext.SqlSugarClient);
private static (DateTime rangeStart, DateTime rangeEndExcl) ResolveDateRange(DateTime? startDate,
DateTime? endDate)
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
index 704240c..6b692e0 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
@@ -85,7 +85,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
///
///
/// 行为与系统 AccountService.PostLoginAsync 一致(含验证码、登录日志事件)。
- /// 门店数据来自 userlocation 与 location 表。
+ /// 门店来自 userlocation ;Company Admin 展开为绑定 Company 下全部门店。
///
/// 邮箱、密码;若系统开启验证码则需传 Uuid、Code
/// Token、RefreshToken 与绑定门店
@@ -150,7 +150,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
_forgotPasswordByEmailService.ResetPasswordByEmailAsync(input);
///
- /// 获取当前登录用户已绑定的门店(切换门店时可重新拉取)
+ /// 获取当前登录用户可选门店(切换门店时可重新拉取);Company Admin 返回绑定 Company 下全部门店。
///
[Authorize]
public virtual async Task> GetMyLocationsAsync()
@@ -549,21 +549,25 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
private async Task> LoadBoundLocationsAsync(Guid userId)
{
- var userIdStr = userId.ToString();
- var links = await _dbContext.SqlSugarClient.Queryable()
- .Where(x => !x.IsDeleted && x.UserId == userIdStr)
- .Select(x => x.LocationId)
- .ToListAsync();
+ var accessibleIds = await LocationScopeBindingHelper.ResolveAppAccessibleLocationIdsForUserAsync(
+ _dbContext.SqlSugarClient, userId);
+ if (accessibleIds.Count == 0)
+ {
+ return new List();
+ }
- if (links.Count == 0)
+ var guidList = accessibleIds
+ .Select(x => Guid.TryParse(x, out var g) ? g : (Guid?)null)
+ .Where(x => x.HasValue)
+ .Select(x => x!.Value)
+ .ToList();
+ if (guidList.Count == 0)
{
return new List();
}
- var wanted = links.Distinct().ToList();
var locations = (await _dbContext.SqlSugarClient.Queryable()
- .Where(x => !x.IsDeleted)
- .Where(x => wanted.Contains(x.Id.ToString()))
+ .Where(x => !x.IsDeleted && guidList.Contains(x.Id))
.ToListAsync())
.OrderBy(x => x.OrderNum)
.ThenBy(x => x.LocationName)
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs
index e397ea1..8606243 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs
@@ -945,9 +945,18 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
})
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
+ var pageTaskIds = pageRows.Select(x => x.Id).ToList();
+ var createdByByTaskId = pageTaskIds.Count == 0
+ ? new Dictionary(StringComparer.Ordinal)
+ : (await _dbContext.SqlSugarClient.Queryable()
+ .Where(t => pageTaskIds.Contains(t.Id))
+ .Select(t => new { t.Id, t.CreatedBy })
+ .ToListAsync())
+ .ToDictionary(x => x.Id, x => x.CreatedBy, StringComparer.Ordinal);
+
var operatorMap = await UsAppPrintLogScopeHelper.LoadOperatorNameMapAsync(
_dbContext.SqlSugarClient,
- pageRows.Select(x => x.CreatedBy));
+ createdByByTaskId.Values.Concat(pageRows.Select(x => x.CreatedBy)));
var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync(
_dbContext.SqlSugarClient,
@@ -968,7 +977,9 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
LabelSizeText = FormatLabelSizeWithUnit(x.TemplateWidth, x.TemplateHeight, x.TemplateUnit),
PrintInputJson = x.PrintInputJson,
PrintedAt = x.PrintedAt ?? x.CreationTime,
- OperatorName = UsAppPrintLogScopeHelper.ResolveOperatorName(operatorMap, x.CreatedBy),
+ OperatorName = UsAppPrintLogScopeHelper.ResolveOperatorName(
+ operatorMap,
+ createdByByTaskId.TryGetValue(x.Id, out var createdBy) ? createdBy : x.CreatedBy),
LocationName = locationName
}).ToList();
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql
new file mode 100644
index 0000000..d5886b1
--- /dev/null
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_entity_partner_scope.sql
@@ -0,0 +1,79 @@
+-- 标签类型 / 分类 / 多选项适用 Company 多选(ALL/SPECIFIED)
+-- 执行前请备份;可重复执行
+
+SET @db := DATABASE();
+
+-- fl_label_type.AppliedPartnerType
+SET @col_type_partner := (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'fl_label_type' AND COLUMN_NAME = 'AppliedPartnerType'
+);
+SET @ddl_type_partner := IF(
+ @col_type_partner = 0,
+ 'ALTER TABLE `fl_label_type` ADD COLUMN `AppliedPartnerType` varchar(20) NOT NULL DEFAULT ''ALL'' COMMENT ''适用Company:ALL/SPECIFIED'' AFTER `AvailabilityType`',
+ 'SELECT 1'
+);
+PREPARE stmt_type_partner FROM @ddl_type_partner;
+EXECUTE stmt_type_partner;
+DEALLOCATE PREPARE stmt_type_partner;
+
+-- fl_label_category.AppliedPartnerType
+SET @col_cat_partner := (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'fl_label_category' AND COLUMN_NAME = 'AppliedPartnerType'
+);
+SET @ddl_cat_partner := IF(
+ @col_cat_partner = 0,
+ 'ALTER TABLE `fl_label_category` ADD COLUMN `AppliedPartnerType` varchar(20) NOT NULL DEFAULT ''ALL'' COMMENT ''适用Company:ALL/SPECIFIED'' AFTER `AvailabilityType`',
+ 'SELECT 1'
+);
+PREPARE stmt_cat_partner FROM @ddl_cat_partner;
+EXECUTE stmt_cat_partner;
+DEALLOCATE PREPARE stmt_cat_partner;
+
+-- fl_label_multiple_option.AppliedPartnerType
+SET @col_opt_partner := (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'fl_label_multiple_option' AND COLUMN_NAME = 'AppliedPartnerType'
+);
+SET @ddl_opt_partner := IF(
+ @col_opt_partner = 0,
+ 'ALTER TABLE `fl_label_multiple_option` ADD COLUMN `AppliedPartnerType` varchar(20) NOT NULL DEFAULT ''ALL'' COMMENT ''适用Company:ALL/SPECIFIED'' AFTER `AvailabilityType`',
+ 'SELECT 1'
+);
+PREPARE stmt_opt_partner FROM @ddl_opt_partner;
+EXECUTE stmt_opt_partner;
+DEALLOCATE PREPARE stmt_opt_partner;
+
+CREATE TABLE IF NOT EXISTS `fl_label_type_partner` (
+ `Id` varchar(36) NOT NULL COMMENT '主键',
+ `LabelTypeId` varchar(36) NOT NULL COMMENT 'fl_label_type.Id',
+ `PartnerId` varchar(36) NOT NULL COMMENT 'fl_partner.Id',
+ `CreationTime` datetime NOT NULL COMMENT '创建时间',
+ `CreatorId` varchar(36) DEFAULT NULL COMMENT '创建者',
+ PRIMARY KEY (`Id`),
+ KEY `idx_fl_ltyp_template` (`LabelTypeId`),
+ KEY `idx_fl_ltyp_partner` (`PartnerId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签类型适用Company';
+
+CREATE TABLE IF NOT EXISTS `fl_label_category_partner` (
+ `Id` varchar(36) NOT NULL COMMENT '主键',
+ `CategoryId` varchar(36) NOT NULL COMMENT 'fl_label_category.Id',
+ `PartnerId` varchar(36) NOT NULL COMMENT 'fl_partner.Id',
+ `CreationTime` datetime NOT NULL COMMENT '创建时间',
+ `CreatorId` varchar(36) DEFAULT NULL COMMENT '创建者',
+ PRIMARY KEY (`Id`),
+ KEY `idx_fl_lcatp_category` (`CategoryId`),
+ KEY `idx_fl_lcatp_partner` (`PartnerId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签分类适用Company';
+
+CREATE TABLE IF NOT EXISTS `fl_label_multiple_option_partner` (
+ `Id` varchar(36) NOT NULL COMMENT '主键',
+ `MultipleOptionId` varchar(36) NOT NULL COMMENT 'fl_label_multiple_option.Id',
+ `PartnerId` varchar(36) NOT NULL COMMENT 'fl_partner.Id',
+ `CreationTime` datetime NOT NULL COMMENT '创建时间',
+ `CreatorId` varchar(36) DEFAULT NULL COMMENT '创建者',
+ PRIMARY KEY (`Id`),
+ KEY `idx_fl_lmop_option` (`MultipleOptionId`),
+ KEY `idx_fl_lmop_partner` (`PartnerId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签多选项适用Company';
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json b/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json
index 1078ea3..1a80493 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json
@@ -20,7 +20,7 @@
},
//应用启动:SelfUrl 供 Program.cs UseUrls 绑定;用 0.0.0.0 避免写成固定局域网 IP 在本机无该网卡时启动失败(WinError 10049)
"App": {
- "SelfUrl": "http://192.168.31.89:19001",
+ "SelfUrl": "http://192.168.31.88:19001",
"CorsOrigins": "http://localhost:19001;http://localhost:18000;http://localhost:5666;http://localhost:5173;http://localhost:3000"
},
//配置
diff --git a/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx b/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx
index 098453a..f9fa6da 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/LabelCategoriesView.tsx
@@ -60,6 +60,7 @@ import {
} from "../ui/pagination";
import {
getLabelCategories,
+ getLabelCategory,
createLabelCategory,
updateLabelCategory,
deleteLabelCategory,
@@ -68,10 +69,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildSpecifiedLocationPayload,
- effectiveScopePartnerId,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- scopePartnerValidationMessage,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -841,7 +841,9 @@ function CreateLabelCategoryDialog({
const [displayTextForPhoto, setDisplayTextForPhoto] = useState("");
const [buttonBgColor, setButtonBgColor] = useState("#3B82F6");
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
categoryName: "",
@@ -862,7 +864,9 @@ function CreateLabelCategoryDialog({
setDisplayTextForPhoto("");
setButtonBgColor("#3B82F6");
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
categoryName: "",
@@ -920,27 +924,20 @@ function CreateLabelCategoryDialog({
}
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
@@ -971,8 +968,7 @@ function CreateLabelCategoryDialog({
buttonImageUrl: apSel.image ? String(form.categoryPhotoUrl ?? "").trim() || null : null,
buttonAppearance: tokens,
buttonStyleJson,
- availabilityType: "SPECIFIED",
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label category created.", {
description: "The label category has been created successfully.",
@@ -1022,6 +1018,11 @@ function CreateLabelCategoryDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1228,11 +1229,14 @@ function EditLabelCategoryDialog({
onUpdated: () => void;
}) {
const [submitting, setSubmitting] = useState(false);
+ const [loadingDetail, setLoadingDetail] = useState(false);
const [apSel, setApSel] = useState({ text: true, color: false, image: false });
const [displayTextForPhoto, setDisplayTextForPhoto] = useState("");
const [buttonBgColor, setButtonBgColor] = useState("#3B82F6");
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
categoryName: "",
@@ -1249,104 +1253,145 @@ function EditLabelCategoryDialog({
}, [apSel.text, apSel.color]);
useEffect(() => {
- if (!open || !category) return;
+ if (!open || !category?.id) return;
+
+ const ac = new AbortController();
+ setLoadingDetail(true);
+
+ (async () => {
+ try {
+ const detail = await getLabelCategory(category.id, ac.signal);
+ if (ac.signal.aborted) return;
+ const row = detail.id ? detail : category;
+
+ const hydrateAvailability = () => {
+ if (requireCompanySelection) {
+ const scope = hydrateEntityScopeFromDto(row, locations, partners, groups);
+ setSelectedPartnerIds(scope.partnerIds);
+ setSelectedRegionIds(scope.regionIds);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ return;
+ }
+ const rawAt = String(row.availabilityType ?? "ALL").trim().toUpperCase();
+ if (rawAt !== "SPECIFIED") {
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
+ return;
+ }
+ const lids = (row.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
+ const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
+ setSelectedPartnerId(scope.partnerId);
+ setSelectedRegionNames(scope.regionNames);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
+ };
+
+ const styleParsed = parseCategoryButtonStyleV1(
+ row.buttonStyleJson ?? (row as { ButtonStyleJson?: string | null }).ButtonStyleJson,
+ );
+ if (styleParsed) {
+ const sel = appearanceSelectionFromTokens(styleParsed.appearances);
+ setApSel(sel);
+ setDisplayTextForPhoto(String(styleParsed.displayText ?? ""));
+ setButtonBgColor(styleParsed.buttonBgColor || "#3B82F6");
+ setForm({
+ categoryName: row.categoryName ?? "",
+ categoryPhotoUrl: sel.image ? row.categoryPhotoUrl ?? null : null,
+ state: row.state ?? true,
+ orderNum: row.orderNum ?? null,
+ });
+ hydrateAvailability();
+ return;
+ }
- const hydrateAvailability = () => {
- const rawAt = String(category.availabilityType ?? "ALL").trim().toUpperCase();
- if (rawAt !== "SPECIFIED") {
+ const rawBa =
+ row.buttonAppearance ??
+ (row as { ButtonAppearance?: string | null }).ButtonAppearance ??
+ undefined;
+ const tokens = parseAppearanceTokens(rawBa);
+ const valArrZip = parseCategoryPhotoUrlValueArray(row.categoryPhotoUrl);
+ if (valArrZip && tokens.length > 0 && tokens.length === valArrZip.length) {
+ const merged = visualInputFromAppearanceAndValueArray(tokens, valArrZip, {
+ categoryName: row.categoryName,
+ name: undefined,
+ buttonTextColor: row.buttonTextColor ?? null,
+ });
+ const selZip = appearanceSelectionFromTokens(tokens);
+ setApSel(selZip);
+ setDisplayTextForPhoto(String(merged.displayText ?? ""));
+ setButtonBgColor(merged.buttonBgColor?.trim() ? merged.buttonBgColor : "#3B82F6");
+ setForm({
+ categoryName: row.categoryName ?? "",
+ categoryPhotoUrl: selZip.image ? (merged.buttonImageUrl ?? "").trim() || null : null,
+ state: row.state ?? true,
+ orderNum: row.orderNum ?? null,
+ });
+ hydrateAvailability();
+ return;
+ }
+
+ let sel = appearanceSelectionFromTokens(tokens);
+ if (tokens.length === 0) {
+ const v = resolveCategoryButtonVisual({
+ buttonAppearance: rawBa,
+ displayText: row.displayText,
+ buttonBgColor: row.buttonBgColor,
+ buttonImageUrl: row.buttonImageUrl,
+ categoryPhotoUrl: row.categoryPhotoUrl,
+ categoryName: row.categoryName,
+ });
+ if (v.mode === "image") sel = { text: false, color: false, image: true };
+ else if (v.mode === "colorText") sel = { text: true, color: true, image: false };
+ else if (v.mode === "color") sel = { text: false, color: true, image: false };
+ else if (v.mode === "text") sel = { text: true, color: false, image: false };
+ else sel = { text: true, color: false, image: false };
+ }
+ setApSel(sel);
+ const photo = String(row.categoryPhotoUrl ?? "").trim();
+ const disp = String(row.displayText ?? "").trim();
+ const bgField = normalizeHexColor(row.buttonBgColor);
+ const hexFromPhoto = normalizeHexColor(photo);
+ setDisplayTextForPhoto(
+ String(
+ disp ||
+ (!sel.image && sel.text && photo && !hexFromPhoto && !looksLikeImageUrl(photo) ? photo : "") ||
+ (!sel.image && sel.text && !sel.color ? (row.categoryName ?? "").trim() : ""),
+ ),
+ );
+ setButtonBgColor(bgField || (sel.color && hexFromPhoto ? hexFromPhoto : "") || "#3B82F6");
+ setForm({
+ categoryName: row.categoryName ?? "",
+ categoryPhotoUrl: sel.image ? row.categoryPhotoUrl ?? null : null,
+ state: row.state ?? true,
+ orderNum: row.orderNum ?? null,
+ });
+ hydrateAvailability();
+ } catch {
+ if (ac.signal.aborted) return;
+ setForm({
+ categoryName: category.categoryName ?? "",
+ categoryPhotoUrl: category.categoryPhotoUrl ?? null,
+ state: category.state ?? true,
+ orderNum: category.orderNum ?? null,
+ });
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
- return;
+ } finally {
+ if (!ac.signal.aborted) setLoadingDetail(false);
}
- const lids = (category.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
- const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
- setSelectedPartnerId(scope.partnerId);
- setSelectedRegionNames(scope.regionNames);
- setSelectedLocationIds(scope.locationIds);
- };
-
- const styleParsed = parseCategoryButtonStyleV1(
- category.buttonStyleJson ?? (category as { ButtonStyleJson?: string | null }).ButtonStyleJson,
- );
- if (styleParsed) {
- const sel = appearanceSelectionFromTokens(styleParsed.appearances);
- setApSel(sel);
- setDisplayTextForPhoto(String(styleParsed.displayText ?? ""));
- setButtonBgColor(styleParsed.buttonBgColor || "#3B82F6");
- setForm({
- categoryName: category.categoryName ?? "",
- categoryPhotoUrl: sel.image ? category.categoryPhotoUrl ?? null : null,
- state: category.state ?? true,
- orderNum: category.orderNum ?? null,
- });
- hydrateAvailability();
- return;
- }
-
- const rawBa =
- category.buttonAppearance ??
- (category as { ButtonAppearance?: string | null }).ButtonAppearance ??
- undefined;
- const tokens = parseAppearanceTokens(rawBa);
- const valArrZip = parseCategoryPhotoUrlValueArray(category.categoryPhotoUrl);
- if (valArrZip && tokens.length > 0 && tokens.length === valArrZip.length) {
- const merged = visualInputFromAppearanceAndValueArray(tokens, valArrZip, {
- categoryName: category.categoryName,
- name: undefined,
- buttonTextColor: category.buttonTextColor ?? null,
- });
- const selZip = appearanceSelectionFromTokens(tokens);
- setApSel(selZip);
- setDisplayTextForPhoto(String(merged.displayText ?? ""));
- setButtonBgColor(merged.buttonBgColor?.trim() ? merged.buttonBgColor : "#3B82F6");
- setForm({
- categoryName: category.categoryName ?? "",
- categoryPhotoUrl: selZip.image ? (merged.buttonImageUrl ?? "").trim() || null : null,
- state: category.state ?? true,
- orderNum: category.orderNum ?? null,
- });
- hydrateAvailability();
- return;
- }
+ })();
- let sel = appearanceSelectionFromTokens(tokens);
- if (tokens.length === 0) {
- const v = resolveCategoryButtonVisual({
- buttonAppearance: rawBa,
- displayText: category.displayText,
- buttonBgColor: category.buttonBgColor,
- buttonImageUrl: category.buttonImageUrl,
- categoryPhotoUrl: category.categoryPhotoUrl,
- categoryName: category.categoryName,
- });
- if (v.mode === "image") sel = { text: false, color: false, image: true };
- else if (v.mode === "colorText") sel = { text: true, color: true, image: false };
- else if (v.mode === "color") sel = { text: false, color: true, image: false };
- else if (v.mode === "text") sel = { text: true, color: false, image: false };
- else sel = { text: true, color: false, image: false };
- }
- setApSel(sel);
- const photo = String(category.categoryPhotoUrl ?? "").trim();
- const disp = String(category.displayText ?? "").trim();
- const bgField = normalizeHexColor(category.buttonBgColor);
- const hexFromPhoto = normalizeHexColor(photo);
- setDisplayTextForPhoto(
- String(
- disp ||
- (!sel.image && sel.text && photo && !hexFromPhoto && !looksLikeImageUrl(photo) ? photo : "") ||
- (!sel.image && sel.text && !sel.color ? (category.categoryName ?? "").trim() : ""),
- ),
- );
- setButtonBgColor(bgField || (sel.color && hexFromPhoto ? hexFromPhoto : "") || "#3B82F6");
- setForm({
- categoryName: category.categoryName ?? "",
- categoryPhotoUrl: sel.image ? category.categoryPhotoUrl ?? null : null,
- state: category.state ?? true,
- orderNum: category.orderNum ?? null,
- });
- hydrateAvailability();
- }, [open, category, locations, partners, groups]);
+ return () => ac.abort();
+ }, [open, category, locations, partners, groups, requireCompanySelection]);
const submit = async () => {
if (!category?.id) return;
@@ -1391,27 +1436,20 @@ function EditLabelCategoryDialog({
}
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
@@ -1442,8 +1480,7 @@ function EditLabelCategoryDialog({
buttonImageUrl: apSel.image ? String(form.categoryPhotoUrl ?? "").trim() || null : null,
buttonAppearance: tokens,
buttonStyleJson,
- availabilityType: "SPECIFIED",
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label category updated.", {
description: "The label category has been updated successfully.",
@@ -1481,19 +1518,28 @@ function EditLabelCategoryDialog({
/>
-
+ {loadingDetail ? (
+ Loading company, region and location…
+ ) : (
+
+ )}
Button Appearance
@@ -1665,7 +1711,7 @@ function EditLabelCategoryDialog({
diff --git a/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx b/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
index bf48672..6be29a6 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/LabelTypesView.tsx
@@ -51,11 +51,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildSpecifiedLocationPayload,
- effectiveScopePartnerId,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- regionNamesToGroupIds,
- scopePartnerValidationMessage,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -822,7 +820,9 @@ function CreateLabelTypeDialog({
}) {
const [submitting, setSubmitting] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
typeName: "",
@@ -832,7 +832,9 @@ function CreateLabelTypeDialog({
const resetForm = () => {
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
typeName: "",
@@ -859,40 +861,29 @@ function CreateLabelTypeDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await createLabelType({
...form,
typeCode: typeCodeFromName(form.typeName),
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label type created.", {
description: "The label type has been created successfully.",
@@ -956,6 +947,11 @@ function CreateLabelTypeDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -996,7 +992,9 @@ function EditLabelTypeDialog({
const [submitting, setSubmitting] = useState(false);
const [loadingDetail, setLoadingDetail] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
typeName: "",
@@ -1021,12 +1019,24 @@ function EditLabelTypeDialog({
orderNum: detail.orderNum ?? type.orderNum ?? null,
});
+ if (requireCompanySelection) {
+ const scope = hydrateEntityScopeFromDto(detail, locations, partners, groups);
+ setSelectedPartnerIds(scope.partnerIds);
+ setSelectedRegionIds(scope.regionIds);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ return;
+ }
+
const lids = (detail.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
if (lids.length > 0) {
const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
setSelectedPartnerId(scope.partnerId);
setSelectedRegionNames(scope.regionNames);
setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1042,6 +1052,8 @@ function EditLabelTypeDialog({
setSelectedPartnerId(pid);
setSelectedRegionNames(names);
setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1057,6 +1069,8 @@ function EditLabelTypeDialog({
setSelectedRegionNames([]);
setSelectedLocationIds([]);
}
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
} catch {
if (ac.signal.aborted) return;
setForm({
@@ -1065,7 +1079,9 @@ function EditLabelTypeDialog({
orderNum: type.orderNum ?? null,
});
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
} finally {
if (!ac.signal.aborted) setLoadingDetail(false);
@@ -1073,7 +1089,7 @@ function EditLabelTypeDialog({
})();
return () => ac.abort();
- }, [open, type, locations, partners, groups]);
+ }, [open, type, locations, partners, groups, requireCompanySelection]);
const submit = async () => {
if (!type?.id) return;
@@ -1088,40 +1104,29 @@ function EditLabelTypeDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await updateLabelType(type.id, {
...form,
typeCode: type.typeCode ?? "",
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Label type updated.", {
description: "The label type has been updated successfully.",
@@ -1188,6 +1193,11 @@ function EditLabelTypeDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
)}
diff --git a/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx b/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
index 8c4f6bf..84be6ea 100755
--- a/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/labels/MultipleOptionsView.tsx
@@ -51,11 +51,9 @@ import { getLocations } from "../../services/locationService";
import { getGroups } from "../../services/groupService";
import { getPartners } from "../../services/partnerService";
import {
- buildSpecifiedLocationPayload,
- effectiveScopePartnerId,
+ buildEntityScopeSaveFromForm,
hydrateCategoryScopeFromLocationIds,
- regionNamesToGroupIds,
- scopePartnerValidationMessage,
+ hydrateEntityScopeFromDto,
} from "../../lib/categoryScopeForm";
import { CategoryScopeFields } from "../shared/category-scope-fields";
import { useCategoryScopeAuth } from "../../hooks/useCategoryScopeAuth";
@@ -806,7 +804,9 @@ function CreateMultipleOptionDialog({
}) {
const [submitting, setSubmitting] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
optionName: "",
@@ -818,7 +818,9 @@ function CreateMultipleOptionDialog({
const resetForm = () => {
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setForm({
optionName: "",
@@ -876,40 +878,29 @@ function CreateMultipleOptionDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await createLabelMultipleOption({
...form,
optionCode: optionCodeFromName(form.optionName),
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Multiple option created.", {
description: "The multiple option has been created successfully.",
@@ -1009,6 +1000,11 @@ function CreateMultipleOptionDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
@@ -1049,7 +1045,9 @@ function EditMultipleOptionDialog({
const [submitting, setSubmitting] = useState(false);
const [loadingDetail, setLoadingDetail] = useState(false);
const [selectedPartnerId, setSelectedPartnerId] = useState("");
+ const [selectedPartnerIds, setSelectedPartnerIds] = useState([]);
const [selectedRegionNames, setSelectedRegionNames] = useState([]);
+ const [selectedRegionIds, setSelectedRegionIds] = useState([]);
const [selectedLocationIds, setSelectedLocationIds] = useState([]);
const [form, setForm] = useState({
optionName: "",
@@ -1078,12 +1076,24 @@ function EditMultipleOptionDialog({
});
setNewValue("");
+ if (requireCompanySelection) {
+ const scope = hydrateEntityScopeFromDto(detail, locations, partners, groups);
+ setSelectedPartnerIds(scope.partnerIds);
+ setSelectedRegionIds(scope.regionIds);
+ setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerId("");
+ setSelectedRegionNames([]);
+ return;
+ }
+
const lids = (detail.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean);
if (lids.length > 0) {
const scope = hydrateCategoryScopeFromLocationIds(lids, locations, partners, groups);
setSelectedPartnerId(scope.partnerId);
setSelectedRegionNames(scope.regionNames);
setSelectedLocationIds(scope.locationIds);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1099,6 +1109,8 @@ function EditMultipleOptionDialog({
setSelectedPartnerId(pid);
setSelectedRegionNames(names);
setSelectedLocationIds([]);
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
return;
}
@@ -1114,6 +1126,8 @@ function EditMultipleOptionDialog({
setSelectedRegionNames([]);
setSelectedLocationIds([]);
}
+ setSelectedPartnerIds([]);
+ setSelectedRegionIds([]);
} catch {
if (ac.signal.aborted) return;
setForm({
@@ -1123,7 +1137,9 @@ function EditMultipleOptionDialog({
orderNum: option.orderNum ?? null,
});
setSelectedPartnerId("");
+ setSelectedPartnerIds([]);
setSelectedRegionNames([]);
+ setSelectedRegionIds([]);
setSelectedLocationIds([]);
setNewValue("");
} finally {
@@ -1132,7 +1148,7 @@ function EditMultipleOptionDialog({
})();
return () => ac.abort();
- }, [open, option, locations, partners, groups]);
+ }, [open, option, locations, partners, groups, requireCompanySelection]);
const addValue = () => {
const trimmed = newValue.trim();
@@ -1176,40 +1192,29 @@ function EditMultipleOptionDialog({
return;
}
- const scopePartnerId = effectiveScopePartnerId(
+ const scopePayload = buildEntityScopeSaveFromForm({
requireCompanySelection,
selectedPartnerId,
+ selectedPartnerIds,
+ selectedRegionNames,
+ selectedRegionIds,
+ selectedLocationIds,
fixedPartnerId,
- );
- const partnerErr = scopePartnerValidationMessage(requireCompanySelection, scopePartnerId);
- if (partnerErr) {
- toast.error("Validation failed", { description: partnerErr });
- return;
- }
-
- const locPayload = buildSpecifiedLocationPayload(
locations,
partners,
groups,
- scopePartnerId,
- selectedRegionNames,
- selectedLocationIds,
- );
- if (!locPayload.ok) {
- toast.error("Validation failed", { description: locPayload.message });
+ });
+ if (!scopePayload.ok) {
+ toast.error("Validation failed", { description: scopePayload.message });
return;
}
- const groupIds = regionNamesToGroupIds(selectedRegionNames, groups, scopePartnerId);
-
setSubmitting(true);
try {
await updateLabelMultipleOption(option.id, {
...form,
optionCode: option.optionCode ?? "",
- groupIds,
- regionIds: groupIds,
- locationIds: locPayload.locationIds,
+ ...scopePayload.body,
});
toast.success("Multiple option updated.", {
description: "The multiple option has been updated successfully.",
@@ -1312,6 +1317,11 @@ function EditMultipleOptionDialog({
onLocationChange={setSelectedLocationIds}
requireCompanySelection={requireCompanySelection}
fixedPartnerId={fixedPartnerId}
+ templateScopeMode={requireCompanySelection}
+ selectedPartnerIds={selectedPartnerIds}
+ onPartnerIdsChange={setSelectedPartnerIds}
+ selectedRegionIds={selectedRegionIds}
+ onRegionIdsChange={setSelectedRegionIds}
/>
)}
diff --git a/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx b/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx
index eb986aa..8bf3835 100644
--- a/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx
+++ b/美国版/Food Labeling Management Platform/src/components/shared/category-scope-fields.tsx
@@ -390,15 +390,30 @@ export function CategoryScopeFields({
{requireCompanySelection ? (
Company *
-
-
Required. Select company before region and location.
+ {templateScopeMode ? (
+
+ ) : (
+
+ )}
+
+ {templateScopeMode
+ ? "Required. Leave empty or select all for every company; narrows region and location lists."
+ : "Required. Select company before region and location."}
+
) : null}
@@ -406,16 +421,24 @@ export function CategoryScopeFields({
Region *
- {companyReady ? "Required. Narrows the location list." : requireCompanySelection ? "Select a company first." : "Your account company is loading or not assigned."}
+ {templateScopeMode
+ ? companyReady
+ ? "Required. Leave empty or select all for every region in selected companies."
+ : "Select company first."
+ : companyReady
+ ? "Required. Narrows the location list."
+ : requireCompanySelection
+ ? "Select a company first."
+ : "Your account company is loading or not assigned."}
@@ -425,22 +448,34 @@ export function CategoryScopeFields({
onValuesChange={onLocationChange}
options={locationOptionsForPicker}
placeholder={
- companyReady
- ? "Select location(s)…"
- : requireCompanySelection
- ? "Select company first"
- : "Loading company scope…"
+ templateScopeMode
+ ? companyReady
+ ? "All Locations"
+ : requireCompanySelection
+ ? "Select company first"
+ : "Loading company scope…"
+ : companyReady
+ ? "Select location(s)…"
+ : requireCompanySelection
+ ? "Select company first"
+ : "Loading company scope…"
}
searchPlaceholder="Search locations…"
selectAllRowLabel="ALL"
- disabled={!companyReady}
+ disabled={!templateScopeMode && !companyReady}
/>
- {companyReady
- ? "Required. ALL selects every location in the filtered list (within selected regions)."
- : requireCompanySelection
- ? "Select a company first."
- : "Your account company is loading or not assigned."}
+ {templateScopeMode
+ ? companyReady
+ ? "Required. ALL selects every location in the filtered list (within selected regions)."
+ : requireCompanySelection
+ ? "Select a company first."
+ : "Your account company is loading or not assigned."
+ : companyReady
+ ? "Required. ALL selects every location in the filtered list (within selected regions)."
+ : requireCompanySelection
+ ? "Select a company first."
+ : "Your account company is loading or not assigned."}
diff --git a/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts b/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
index e9ff5f4..43b3601 100644
--- a/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
+++ b/美国版/Food Labeling Management Platform/src/lib/categoryScopeForm.ts
@@ -1,6 +1,7 @@
import type { GroupListItem } from "../types/group";
import type { LocationDto } from "../types/location";
import type { PartnerListItem } from "../types/partner";
+import type { AppliedScopeType } from "../types/labelTemplate";
import { parseAppliedScopeType } from "../types/labelTemplate";
/** 表单提交用的公司 Id(非管理员取账号绑定公司) */
@@ -424,3 +425,223 @@ export function buildProductScopeSavePayload(
}
return { locationIds: locIds };
}
+
+function resolveScopeDimensionForSave(
+ selected: string[],
+ available: string[],
+): { type: AppliedScopeType; ids: string[] } {
+ const avail = dedupeIds(available);
+ const sel = dedupeIds(selected).filter((id) => !avail.length || avail.includes(id));
+ if (!avail.length || !sel.length) {
+ return { type: "ALL", ids: [] };
+ }
+ if (sel.length >= avail.length && avail.every((id) => sel.includes(id))) {
+ return { type: "ALL", ids: [] };
+ }
+ return { type: "SPECIFIED", ids: sel };
+}
+
+/** 模板/实体表单:按当前选中 Id 计算可选 Company / Region / Location 全集 */
+export function computeTemplateScopeAvailableIds(
+ partners: PartnerListItem[],
+ groups: GroupListItem[],
+ locations: LocationDto[],
+ selectedPartnerIds: string[],
+ selectedRegionIds: string[],
+): {
+ availablePartnerIds: string[];
+ availableRegionIds: string[];
+ availableLocationIds: string[];
+} {
+ const availablePartnerIds = partners.map((p) => String(p.id ?? "").trim()).filter(Boolean);
+ const effectivePartnerIds =
+ selectedPartnerIds.length > 0 ? selectedPartnerIds : availablePartnerIds;
+ const availableRegionIds = regionOptionsForPartners(groups, effectivePartnerIds).map(
+ (o) => o.value,
+ );
+ const availableLocationIds = locationsScopedForTemplateScope(
+ locations,
+ partners,
+ groups,
+ effectivePartnerIds,
+ selectedRegionIds,
+ ).map((l) => l.id);
+ return { availablePartnerIds, availableRegionIds, availableLocationIds };
+}
+
+export type EntityScopePayloadBody = {
+ appliedPartnerType: AppliedScopeType;
+ partnerIds: string[];
+ companyIds: string[];
+ regionIds: string[];
+ groupIds: string[];
+ locationIds: string[];
+ availabilityType: "ALL" | "SPECIFIED";
+};
+
+/**
+ * 标签类型 / 分类 / 多选项保存:Company + Region + Location 三维适用范围。
+ * 空选或全选当前可选项 → 该维度 ALL;部分选中 → SPECIFIED + Id 数组。
+ */
+export function buildEntityPartnerScopePayload(input: {
+ selectedPartnerIds: string[];
+ selectedRegionIds: string[];
+ selectedLocationIds: string[];
+ availablePartnerIds: string[];
+ availableRegionIds: string[];
+ availableLocationIds: string[];
+}): { ok: true; body: EntityScopePayloadBody } | { ok: false; message: string } {
+ const partnerDim = resolveScopeDimensionForSave(
+ input.selectedPartnerIds,
+ input.availablePartnerIds,
+ );
+ const regionDim = resolveScopeDimensionForSave(
+ input.selectedRegionIds,
+ input.availableRegionIds,
+ );
+ const locationDim = resolveScopeDimensionForSave(
+ input.selectedLocationIds,
+ input.availableLocationIds,
+ );
+
+ const locationSpecified =
+ regionDim.type === "SPECIFIED" || locationDim.type === "SPECIFIED";
+ const availabilityType: "ALL" | "SPECIFIED" = locationSpecified ? "SPECIFIED" : "ALL";
+
+ const anySpecified =
+ partnerDim.type === "SPECIFIED" || locationSpecified;
+ const effectiveLocationIds =
+ locationDim.type === "SPECIFIED" ? locationDim.ids : input.availableLocationIds;
+
+ if (anySpecified && effectiveLocationIds.length === 0) {
+ return {
+ ok: false,
+ message: "Please adjust company, region, or location so at least one store matches.",
+ };
+ }
+
+ const partnerIds = partnerDim.type === "SPECIFIED" ? partnerDim.ids : [];
+ const regionIds = regionDim.type === "SPECIFIED" ? regionDim.ids : [];
+ const locationIds = locationDim.type === "SPECIFIED" ? locationDim.ids : [];
+
+ return {
+ ok: true,
+ body: {
+ appliedPartnerType: partnerDim.type,
+ partnerIds,
+ companyIds: partnerIds,
+ regionIds,
+ groupIds: regionIds,
+ locationIds,
+ availabilityType,
+ },
+ };
+}
+
+/** 创建/编辑弹窗:按管理员多选或单公司模式构建保存 Body */
+export function buildEntityScopeSaveFromForm(input: {
+ requireCompanySelection: boolean;
+ selectedPartnerId: string;
+ selectedPartnerIds: string[];
+ selectedRegionNames: string[];
+ selectedRegionIds: string[];
+ selectedLocationIds: string[];
+ fixedPartnerId: string;
+ locations: LocationDto[];
+ partners: PartnerListItem[];
+ groups: GroupListItem[];
+}): { ok: true; body: EntityScopePayloadBody } | { ok: false; message: string } {
+ if (input.requireCompanySelection) {
+ const { availablePartnerIds, availableRegionIds, availableLocationIds } =
+ computeTemplateScopeAvailableIds(
+ input.partners,
+ input.groups,
+ input.locations,
+ input.selectedPartnerIds,
+ input.selectedRegionIds,
+ );
+ return buildEntityPartnerScopePayload({
+ selectedPartnerIds: input.selectedPartnerIds,
+ selectedRegionIds: input.selectedRegionIds,
+ selectedLocationIds: input.selectedLocationIds,
+ availablePartnerIds,
+ availableRegionIds,
+ availableLocationIds,
+ });
+ }
+
+ const scopePartnerId = effectiveScopePartnerId(
+ false,
+ input.selectedPartnerId,
+ input.fixedPartnerId,
+ );
+ const partnerErr = scopePartnerValidationMessage(false, scopePartnerId);
+ if (partnerErr) {
+ return { ok: false, message: partnerErr };
+ }
+
+ const locPayload = buildSpecifiedLocationPayload(
+ input.locations,
+ input.partners,
+ input.groups,
+ scopePartnerId,
+ input.selectedRegionNames,
+ input.selectedLocationIds,
+ );
+ if (!locPayload.ok) {
+ return { ok: false, message: locPayload.message };
+ }
+
+ const groupIds = regionNamesToGroupIds(
+ input.selectedRegionNames,
+ input.groups,
+ scopePartnerId,
+ );
+ const partnerIds = scopePartnerId ? [scopePartnerId] : [];
+ return {
+ ok: true,
+ body: {
+ appliedPartnerType: partnerIds.length ? "SPECIFIED" : "ALL",
+ partnerIds,
+ companyIds: partnerIds,
+ regionIds: groupIds,
+ groupIds,
+ locationIds: locPayload.locationIds,
+ availabilityType: "SPECIFIED",
+ },
+ };
+}
+
+/** 编辑弹窗:从详情 DTO 回填三维 Id 数组(管理员多选模式) */
+export function hydrateEntityScopeFromDto(
+ dto: {
+ appliedPartnerType?: string | null;
+ appliedRegionType?: string | null;
+ appliedLocation?: string | null;
+ appliedLocationType?: string | null;
+ availabilityType?: string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ regionIds?: string[] | null;
+ groupIds?: string[] | null;
+ locationIds?: string[] | null;
+ appliedLocationIds?: string[] | null;
+ },
+ locations: LocationDto[],
+ partners: PartnerListItem[],
+ groups: GroupListItem[],
+): { partnerIds: string[]; regionIds: string[]; locationIds: string[] } {
+ return hydrateLabelTemplateScopeFromDto(
+ {
+ ...dto,
+ appliedLocation:
+ dto.appliedLocation ??
+ (String(dto.availabilityType ?? "").trim().toUpperCase() === "SPECIFIED"
+ ? "SPECIFIED"
+ : dto.appliedLocation),
+ },
+ locations,
+ partners,
+ groups,
+ );
+}
diff --git a/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts b/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts
index 6b18ab5..6c6dbfd 100644
--- a/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts
+++ b/美国版/Food Labeling Management Platform/src/services/labelCategoryService.ts
@@ -22,6 +22,13 @@ const PATH = "/label-category";
function normalizeLabelCategoryDto(raw: unknown): LabelCategoryDto {
const r = (raw && typeof raw === "object" ? raw : {}) as Record;
+ const parseIds = (v: unknown): string[] | undefined => {
+ if (!Array.isArray(v)) return undefined;
+ return [...new Set(v.map((x) => String(x).trim()).filter(Boolean))];
+ };
+ const groupIds = parseIds(r.groupIds ?? r.GroupIds ?? r.regionIds ?? r.RegionIds);
+ const locationIds = parseIds(r.locationIds ?? r.LocationIds);
+ const partnerIds = parseIds(r.partnerIds ?? r.PartnerIds ?? r.companyIds ?? r.CompanyIds);
const noRaw = r.noOfLabels ?? r.NoOfLabels;
const noOfLabels =
typeof noRaw === "number" && Number.isFinite(noRaw)
@@ -36,7 +43,60 @@ function normalizeLabelCategoryDto(raw: unknown): LabelCategoryDto {
: typeof orderRaw === "string" && orderRaw.trim() !== "" && Number.isFinite(Number(orderRaw))
? Number(orderRaw)
: null;
- return { ...(r as object), id: String(r.id ?? r.Id ?? ""), noOfLabels, orderNum } as LabelCategoryDto;
+ const companyRaw = r.company ?? r.Company;
+ return {
+ ...(r as object),
+ id: String(r.id ?? r.Id ?? ""),
+ appliedPartnerType: String(r.appliedPartnerType ?? r.AppliedPartnerType ?? "").trim() || undefined,
+ partnerIds,
+ companyIds: partnerIds,
+ availabilityType: String(r.availabilityType ?? r.AvailabilityType ?? "").trim() || undefined,
+ groupIds,
+ regionIds: groupIds,
+ locationIds,
+ company: typeof companyRaw === "string" ? companyRaw : null,
+ noOfLabels,
+ orderNum,
+ } as LabelCategoryDto;
+}
+
+function scopeBodyFromInput(input: LabelCategoryCreateInput): Record {
+ const groupIds = [
+ ...new Set(
+ [...(input.groupIds ?? []), ...(input.regionIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const locationIds = [
+ ...new Set((input.locationIds ?? []).map((x) => String(x).trim()).filter(Boolean)),
+ ];
+ const partnerIds = [
+ ...new Set(
+ [...(input.partnerIds ?? []), ...(input.companyIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const body: Record = {};
+ const appliedPartnerType = String(input.appliedPartnerType ?? "").trim().toUpperCase();
+ if (appliedPartnerType === "ALL" || appliedPartnerType === "SPECIFIED") {
+ body.appliedPartnerType = appliedPartnerType;
+ }
+ if (partnerIds.length) {
+ body.partnerIds = partnerIds;
+ body.companyIds = partnerIds;
+ }
+ const availabilityType = String(input.availabilityType ?? "ALL").trim().toUpperCase() || "ALL";
+ body.availabilityType = availabilityType;
+ if (groupIds.length) {
+ body.groupIds = groupIds;
+ body.regionIds = groupIds;
+ }
+ if (locationIds.length) {
+ body.locationIds = locationIds;
+ }
+ return body;
}
export async function getLabelCategories(input: LabelCategoryGetListInput, signal?: AbortSignal): Promise> {
@@ -60,11 +120,12 @@ export async function getLabelCategories(input: LabelCategoryGetListInput, signa
}
export async function getLabelCategory(id: string, signal?: AbortSignal): Promise {
- return api.requestJson({
+ const raw = await api.requestJson({
path: `${PATH}/${encodeURIComponent(id)}`,
method: "GET",
signal,
});
+ return normalizeLabelCategoryDto(raw);
}
export async function createLabelCategory(input: LabelCategoryCreateInput): Promise {
@@ -82,17 +143,13 @@ export async function createLabelCategory(input: LabelCategoryCreateInput): Prom
buttonStyleJson: (input.buttonStyleJson ?? "").trim() || null,
state: input.state ?? true,
orderNum: input.orderNum,
- availabilityType: String(input.availabilityType ?? "ALL").trim().toUpperCase() || "ALL",
- locationIds:
- Array.isArray(input.locationIds) && input.locationIds.length
- ? [...new Set(input.locationIds.map((x) => String(x).trim()).filter(Boolean))]
- : null,
+ ...scopeBodyFromInput(input),
},
});
}
export async function updateLabelCategory(id: string, input: LabelCategoryUpdateInput): Promise {
- return api.requestJson({
+ const raw = await api.requestJson({
path: `${PATH}/${encodeURIComponent(id)}`,
method: "PUT",
body: {
@@ -106,13 +163,10 @@ export async function updateLabelCategory(id: string, input: LabelCategoryUpdate
buttonStyleJson: (input.buttonStyleJson ?? "").trim() || null,
state: input.state ?? true,
orderNum: input.orderNum,
- availabilityType: String(input.availabilityType ?? "ALL").trim().toUpperCase() || "ALL",
- locationIds:
- Array.isArray(input.locationIds) && input.locationIds.length
- ? [...new Set(input.locationIds.map((x) => String(x).trim()).filter(Boolean))]
- : null,
+ ...scopeBodyFromInput(input),
},
});
+ return normalizeLabelCategoryDto(raw);
}
export async function deleteLabelCategory(id: string): Promise {
diff --git a/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts b/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts
index 88d3a70..15da9ad 100644
--- a/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts
+++ b/美国版/Food Labeling Management Platform/src/services/labelMultipleOptionService.ts
@@ -63,13 +63,35 @@ function normalizeLabelMultipleOptionDto(item: LabelMultipleOptionDto): LabelMul
const locationIds = parseIds(
(raw as Record).locationIds ?? (raw as Record).LocationIds,
);
+ const partnerIds = parseIds(
+ (raw as Record).partnerIds ??
+ (raw as Record).PartnerIds ??
+ (raw as Record).companyIds ??
+ (raw as Record).CompanyIds,
+ );
+ const companyRaw = (raw as Record).company ?? (raw as Record).Company;
return {
...item,
optionValuesJson: parseOptionValuesJsonField(raw.optionValuesJson),
lastEdited,
+ appliedPartnerType:
+ String(
+ (raw as Record).appliedPartnerType ??
+ (raw as Record).AppliedPartnerType ??
+ "",
+ ).trim() || undefined,
+ partnerIds,
+ companyIds: partnerIds,
+ availabilityType:
+ String(
+ (raw as Record).availabilityType ??
+ (raw as Record).AvailabilityType ??
+ "",
+ ).trim() || undefined,
groupIds,
regionIds: groupIds,
locationIds,
+ company: typeof companyRaw === "string" ? companyRaw : null,
};
}
@@ -96,11 +118,34 @@ function scopeBodyFromInput(input: LabelMultipleOptionCreateInput): Record String(x).trim()).filter(Boolean)),
];
- return {
- groupIds: groupIds.length ? groupIds : undefined,
- regionIds: groupIds.length ? groupIds : undefined,
- locationIds: locationIds.length ? locationIds : undefined,
- };
+ const partnerIds = [
+ ...new Set(
+ [...(input.partnerIds ?? []), ...(input.companyIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const body: Record = {};
+ const appliedPartnerType = String(input.appliedPartnerType ?? "").trim().toUpperCase();
+ if (appliedPartnerType === "ALL" || appliedPartnerType === "SPECIFIED") {
+ body.appliedPartnerType = appliedPartnerType;
+ }
+ if (partnerIds.length) {
+ body.partnerIds = partnerIds;
+ body.companyIds = partnerIds;
+ }
+ const availabilityType = String(input.availabilityType ?? "").trim().toUpperCase();
+ if (availabilityType === "ALL" || availabilityType === "SPECIFIED") {
+ body.availabilityType = availabilityType;
+ }
+ if (groupIds.length) {
+ body.groupIds = groupIds;
+ body.regionIds = groupIds;
+ }
+ if (locationIds.length) {
+ body.locationIds = locationIds;
+ }
+ return body;
}
export async function getLabelMultipleOptions(input: LabelMultipleOptionGetListInput, signal?: AbortSignal): Promise> {
diff --git a/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts b/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts
index 7566872..75b1682 100644
--- a/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts
+++ b/美国版/Food Labeling Management Platform/src/services/labelTypeService.ts
@@ -27,6 +27,10 @@ function normalizeLabelTypeDto(raw: unknown): LabelTypeDto {
};
const groupIds = parseIds(r.groupIds ?? r.GroupIds ?? r.regionIds ?? r.RegionIds);
const locationIds = parseIds(r.locationIds ?? r.LocationIds);
+ const partnerIds = parseIds(r.partnerIds ?? r.PartnerIds ?? r.companyIds ?? r.CompanyIds);
+ const appliedPartnerType = String(r.appliedPartnerType ?? r.AppliedPartnerType ?? "").trim() || undefined;
+ const availabilityType = String(r.availabilityType ?? r.AvailabilityType ?? "").trim() || undefined;
+ const companyRaw = r.company ?? r.Company;
const noRaw = r.noOfLabels ?? r.NoOfLabels;
const noOfLabels =
typeof noRaw === "number" && Number.isFinite(noRaw)
@@ -46,9 +50,14 @@ function normalizeLabelTypeDto(raw: unknown): LabelTypeDto {
return {
...(r as object),
id: String(r.id ?? r.Id ?? ""),
+ appliedPartnerType,
+ partnerIds,
+ companyIds: partnerIds,
+ availabilityType,
groupIds,
regionIds: groupIds,
locationIds,
+ company: typeof companyRaw === "string" ? companyRaw : null,
noOfLabels,
lastEdited,
region: typeof regionRaw === "string" ? regionRaw : null,
@@ -67,11 +76,34 @@ function scopeBodyFromInput(input: LabelTypeCreateInput): Record String(x).trim()).filter(Boolean)),
];
- return {
- groupIds: groupIds.length ? groupIds : undefined,
- regionIds: groupIds.length ? groupIds : undefined,
- locationIds: locationIds.length ? locationIds : undefined,
- };
+ const partnerIds = [
+ ...new Set(
+ [...(input.partnerIds ?? []), ...(input.companyIds ?? [])]
+ .map((x) => String(x).trim())
+ .filter(Boolean),
+ ),
+ ];
+ const body: Record = {};
+ const appliedPartnerType = String(input.appliedPartnerType ?? "").trim().toUpperCase();
+ if (appliedPartnerType === "ALL" || appliedPartnerType === "SPECIFIED") {
+ body.appliedPartnerType = appliedPartnerType;
+ }
+ if (partnerIds.length) {
+ body.partnerIds = partnerIds;
+ body.companyIds = partnerIds;
+ }
+ const availabilityType = String(input.availabilityType ?? "").trim().toUpperCase();
+ if (availabilityType === "ALL" || availabilityType === "SPECIFIED") {
+ body.availabilityType = availabilityType;
+ }
+ if (groupIds.length) {
+ body.groupIds = groupIds;
+ body.regionIds = groupIds;
+ }
+ if (locationIds.length) {
+ body.locationIds = locationIds;
+ }
+ return body;
}
export async function getLabelTypes(input: LabelTypeGetListInput, signal?: AbortSignal): Promise> {
diff --git a/美国版/Food Labeling Management Platform/src/types/label.ts b/美国版/Food Labeling Management Platform/src/types/label.ts
index 4176626..ee64a7f 100644
--- a/美国版/Food Labeling Management Platform/src/types/label.ts
+++ b/美国版/Food Labeling Management Platform/src/types/label.ts
@@ -66,7 +66,7 @@ export type LabelCreateInput = {
/** 适用门店(至少 1 个;Select All 时为当前 Region 下全部门店 Id) */
locationIds: string[];
labelCategoryId: string;
- labelTypeId: string;
+ labelTypeId?: string | null;
productIds: string[]; // 至少 1 个
labelInfoJson?: Record | null;
state?: boolean;
@@ -81,7 +81,7 @@ export type LabelUpdateInput = {
/** 适用门店(至少 1 个) */
locationIds: string[];
labelCategoryId: string;
- labelTypeId: string;
+ labelTypeId?: string | null;
productIds: string[]; // 至少 1 个
labelInfoJson?: Record | null;
state?: boolean;
diff --git a/美国版/Food Labeling Management Platform/src/types/labelCategory.ts b/美国版/Food Labeling Management Platform/src/types/labelCategory.ts
index eef20ba..60d57a3 100644
--- a/美国版/Food Labeling Management Platform/src/types/labelCategory.ts
+++ b/美国版/Food Labeling Management Platform/src/types/labelCategory.ts
@@ -18,10 +18,19 @@ export type LabelCategoryDto = {
/** 该分类下标签数量(列表列 No. of Label) */
noOfLabels?: number | null;
creationTime?: string | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
/** ALL / SPECIFIED */
availabilityType?: "ALL" | "SPECIFIED" | string | null;
+ /** SPECIFIED 时绑定的 Region Id */
+ regionIds?: string[] | null;
+ groupIds?: string[] | null;
/** SPECIFIED 时绑定的门店 Id */
locationIds?: string[] | null;
+ /** 列表:Company 展示 */
+ company?: string | null;
/** 最近编辑时间(列表接口映射 LastModificationTime ?? CreationTime) */
lastEdited?: string | null;
};
@@ -56,7 +65,13 @@ export type LabelCategoryCreateInput = {
buttonStyleJson?: string | null;
state?: boolean;
orderNum?: number | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
availabilityType?: "ALL" | "SPECIFIED" | string | null;
+ regionIds?: string[] | null;
+ groupIds?: string[] | null;
locationIds?: string[] | null;
};
diff --git a/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts b/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts
index 2defb00..927028d 100644
--- a/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts
+++ b/美国版/Food Labeling Management Platform/src/types/labelMultipleOption.ts
@@ -7,6 +7,12 @@ export type LabelMultipleOptionDto = {
state?: boolean | null;
orderNum?: number | null;
creationTime?: string | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
/** 列表:最近编辑时间 */
lastEdited?: string | null;
/** 适用范围:Region Id(`fl_group.Id`) */
@@ -14,6 +20,8 @@ export type LabelMultipleOptionDto = {
regionIds?: string[] | null;
/** 适用范围:门店 Id */
locationIds?: string[] | null;
+ /** 列表:Company 展示 */
+ company?: string | null;
};
export type PagedResultDto = {
@@ -39,6 +47,12 @@ export type LabelMultipleOptionCreateInput = {
optionValuesJson: string[];
state?: boolean;
orderNum?: number | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
groupIds?: string[] | null;
regionIds?: string[] | null;
locationIds?: string[] | null;
diff --git a/美国版/Food Labeling Management Platform/src/types/labelType.ts b/美国版/Food Labeling Management Platform/src/types/labelType.ts
index 0e14b33..4dc914c 100644
--- a/美国版/Food Labeling Management Platform/src/types/labelType.ts
+++ b/美国版/Food Labeling Management Platform/src/types/labelType.ts
@@ -5,11 +5,19 @@ export type LabelTypeDto = {
state?: boolean | null;
orderNum?: number | null;
creationTime?: string | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
/** 详情:Region Id 列表(`fl_group.Id`) */
groupIds?: string[] | null;
regionIds?: string[] | null;
/** 详情:门店 Id 列表 */
locationIds?: string[] | null;
+ /** 列表:Company 展示 */
+ company?: string | null;
/** 列表:该类型下标签数量 */
noOfLabels?: number | null;
/** 列表:最近编辑时间 */
@@ -44,6 +52,12 @@ export type LabelTypeCreateInput = {
typeName: string;
state?: boolean;
orderNum?: number | null;
+ /** 适用 Company:ALL / SPECIFIED */
+ appliedPartnerType?: "ALL" | "SPECIFIED" | string | null;
+ partnerIds?: string[] | null;
+ companyIds?: string[] | null;
+ /** 门店可用范围:ALL / SPECIFIED */
+ availabilityType?: "ALL" | "SPECIFIED" | string | null;
/** Region 多选(`fl_group.Id`) */
groupIds?: string[] | null;
regionIds?: string[] | null;
--
libgit2 0.21.4