From 07d5dea2be04f05deb7e8bca2f1069de034600ab Mon Sep 17 00:00:00 2001
From: 李曜臣
Date: Mon, 18 May 2026 21:08:40 +0800
Subject: [PATCH] 5-18代码优化
---
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionCreateInputVo.cs | 20 ++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListInputVo.cs | 10 ++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelMultipleOption/LabelMultipleOptionGetListOutputDto.cs | 12 ++++++++++++
美国版/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 | 20 ++++++++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListInputVo.cs | 10 ++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetListOutputDto.cs | 15 +++++++++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelType/LabelTypeGetOutputDto.cs | 8 ++++++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Reports/ReportsPrintLogListItemDto.cs | 4 +++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs | 12 +++++++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLocationDetailOutputDto.cs | 2 +-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogItemDto.cs | 2 +-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationAppService.cs | 2 +-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IReportsAppService.cs | 31 ++++++++++++++++++++++++++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs | 2 +-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs | 8 +++++++-
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionDbEntity.cs | 5 +++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypeDbEntity.cs | 5 +++++
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs | 326 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelTypeAppService.cs | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationAppService.cs | 22 +++++++++++++++-------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleMenuAppService.cs | 12 +++++++++---
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ReportsAppService.cs | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------------------------------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs | 6 +++---
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/System/UserService.cs | 11 +++++++++--
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/UserAggregateRoot.cs | 16 +++-------------
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs | 14 +++++++++-----
30 files changed, 1360 insertions(+), 223 deletions(-)
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 52a8cf9..6c779db 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
@@ -10,6 +10,26 @@ public class LabelMultipleOptionCreateInputVo
public bool State { get; set; } = true;
+ ///
+ /// 门店可用范围:ALL / SPECIFIED;传了 或 时自动为 SPECIFIED
+ ///
+ public string AvailabilityType { get; set; } = "ALL";
+
+ ///
+ /// 适用 Region(多选),fl_group.Id;与 合并去重
+ ///
+ public List? RegionIds { get; set; }
+
+ ///
+ /// 适用 Region(多选),与 相同
+ ///
+ public List? GroupIds { get; set; }
+
+ ///
+ /// 适用门店(多选),location.Id;与 Region 合并后写入 fl_label_multiple_option_location
+ ///
+ public List? LocationIds { get; set; }
+
public int OrderNum { get; set; }
}
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 dbf8365..718e484 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
@@ -7,5 +7,15 @@ public class LabelMultipleOptionGetListInputVo : PagedAndSortedResultRequestDto
public string? Keyword { get; set; }
public bool? State { get; set; }
+
+ ///
+ /// Region 筛选(fl_group.Id)
+ ///
+ public string? GroupId { get; set; }
+
+ ///
+ /// 门店筛选(location.Id);优先于
+ ///
+ public string? LocationId { get; set; }
}
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 f7af6bc..e5011fa 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
@@ -12,8 +12,20 @@ public class LabelMultipleOptionGetListOutputDto
public bool State { get; set; }
+ public string AvailabilityType { get; set; } = "ALL";
+
public int OrderNum { get; set; }
public DateTime LastEdited { get; set; }
+
+ /// 列表列 Region
+ public string Region { get; set; } = string.Empty;
+
+ /// 列表列 Location
+ public string Location { get; set; } = string.Empty;
+
+ public List RegionIds { get; set; } = new();
+
+ public List LocationIds { get; set; } = new();
}
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 0b181c6..23d92fd 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
@@ -13,5 +13,13 @@ public class LabelMultipleOptionGetOutputDto
public bool State { get; set; }
public int OrderNum { get; set; }
+
+ public string AvailabilityType { get; set; } = "ALL";
+
+ public List RegionIds { get; set; } = new();
+
+ public List GroupIds { get; set; } = new();
+
+ public List LocationIds { 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 27e3863..084c6ce 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
@@ -8,6 +8,26 @@ public class LabelTypeCreateInputVo
public bool State { get; set; } = true;
+ ///
+ /// 门店可用范围:ALL / SPECIFIED;传了 或 时自动为 SPECIFIED
+ ///
+ public string AvailabilityType { get; set; } = "ALL";
+
+ ///
+ /// 适用 Region(多选),fl_group.Id;与 合并去重
+ ///
+ public List? RegionIds { get; set; }
+
+ ///
+ /// 适用 Region(多选),与 相同
+ ///
+ public List? GroupIds { get; set; }
+
+ ///
+ /// 适用门店(多选),location.Id;与 Region 合并后写入 fl_label_type_location
+ ///
+ public List? LocationIds { get; set; }
+
public int OrderNum { get; set; }
}
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 af2f04c..1441853 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
@@ -7,5 +7,15 @@ public class LabelTypeGetListInputVo : PagedAndSortedResultRequestDto
public string? Keyword { get; set; }
public bool? State { get; set; }
+
+ ///
+ /// Region 筛选(fl_group.Id);仅返回在该 Region 下存在标签实例的类型
+ ///
+ public string? GroupId { get; set; }
+
+ ///
+ /// 门店筛选(location.Id);优先于
+ ///
+ public string? LocationId { get; set; }
}
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 1a63ae4..8914e18 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
@@ -10,10 +10,25 @@ public class LabelTypeGetListOutputDto
public bool State { get; set; }
+ public string AvailabilityType { get; set; } = "ALL";
+
public int OrderNum { get; set; }
+ /// 列表列 No. of Labels:该类型下未删除标签数(统计 fl_label,非库表物理列)
public long NoOfLabels { get; set; }
public DateTime LastEdited { get; set; }
+
+ /// 列表列 Region:由该类型下标签绑定的门店 GroupName 去重拼接
+ public string Region { get; set; } = string.Empty;
+
+ /// 列表列 Location:由该类型下标签绑定的门店名去重拼接
+ public string Location { get; set; } = string.Empty;
+
+ /// Region Id(fl_group.Id,由标签门店反推)
+ public List RegionIds { get; set; } = new();
+
+ /// 门店 Id(location.Id,由该类型下标签汇总)
+ public List LocationIds { get; set; } = new();
}
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 0763156..54440ba 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
@@ -11,5 +11,13 @@ public class LabelTypeGetOutputDto
public bool State { get; set; }
public int OrderNum { get; set; }
+
+ public string AvailabilityType { get; set; } = "ALL";
+
+ public List RegionIds { get; set; } = new();
+
+ public List GroupIds { get; set; } = new();
+
+ public List LocationIds { get; set; } = new();
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Reports/ReportsPrintLogListItemDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Reports/ReportsPrintLogListItemDto.cs
index c1857d1..2822a19 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Reports/ReportsPrintLogListItemDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Reports/ReportsPrintLogListItemDto.cs
@@ -8,7 +8,9 @@ public class ReportsPrintLogListItemDto
/// 打印任务 Id(fl_label_print_task.Id),重打时使用
public string TaskId { get; set; } = string.Empty;
- /// 标签编码(展示为 Label ID)
+ ///
+ /// 列表列 Label ID:门店当日打印序号(yyyyMMdd-n),非 fl_label.LabelCode
+ ///
public string LabelCode { get; set; } = string.Empty;
public string ProductName { get; set; } = "无";
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs
index 55f2c29..8c7697e 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs
@@ -18,7 +18,17 @@ public class TeamMemberGetListInputVo : PagedAndSortedResultRequestDto
public Guid? RoleId { get; set; }
///
- /// 门店ID(可选)
+ /// Company 筛选(fl_partner.Id);与 、 按「门店优先」解析范围
+ ///
+ public string? PartnerId { get; set; }
+
+ ///
+ /// Region 筛选(fl_group.Id);未传 时生效
+ ///
+ public string? GroupId { get; set; }
+
+ ///
+ /// 门店筛选(location.Id);最精确,传则忽略 /
///
public string? LocationId { get; set; }
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLocationDetailOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLocationDetailOutputDto.cs
index d684a8b..6c90125 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLocationDetailOutputDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLocationDetailOutputDto.cs
@@ -17,7 +17,7 @@ public class UsAppLocationDetailOutputDto
/// 门店电话(来自 location.Phone;空为「无」)
public string StorePhone { get; set; } = string.Empty;
- /// 营业时间;当前库无字段,固定返回「无」直至业务落库
+ /// 经营时间(location.OperatingHours 自由文本);库空或未维护时为「无」
public string OperatingHours { get; set; } = string.Empty;
/// 店长姓名;优先取绑定本店且角色名/编码含 manager 的用户
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogItemDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogItemDto.cs
index f006a1f..c4c6936 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogItemDto.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogItemDto.cs
@@ -38,7 +38,7 @@ public class PrintLogItemDto
/// 打印时间(PrintedAt ?? CreationTime)
public DateTime PrintedAt { get; set; }
- /// 操作人姓名(当前登录账号 Name)
+ /// 操作人姓名(任务 CreatedBy 对应用户;查看全店日志时为实际打印人)
public string OperatorName { get; set; } = string.Empty;
/// 门店名称
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationAppService.cs
index 7dd222b..0933892 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ILocationAppService.cs
@@ -12,7 +12,7 @@ namespace FoodLabeling.Application.Contracts.IServices;
public interface ILocationAppService : IApplicationService
{
///
- /// 门店分页列表
+ /// 门店分页列表;管理员返回全部门店,非管理员仅返回其绑定门店所属 Region(Partner + GroupName)下的门店。
///
/// 查询条件
Task> GetListAsync(LocationGetListInputVo input);
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IReportsAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IReportsAppService.cs
index d4536e0..08fae68 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IReportsAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IReportsAppService.cs
@@ -32,7 +32,8 @@ public interface IReportsAppService : IApplicationService
Task ReprintPrintLogAsync(UsAppLabelReprintInputVo input);
///
- /// Label Report 统计(卡片 + 分类柱数据 + 7 日趋势 + Top 产品);admin 统计全部,否则仅当前用户。
+ /// Label Report 统计(卡片 + 分类柱数据 + 7 日趋势 + Top 产品);
+ /// admin 统计全部门店;非管理员仅统计 userlocation 绑定门店内全部打印任务(不按 CreatedBy 过滤)。
///
Task GetLabelReportAsync(ReportsLabelReportQueryInputVo input);
@@ -40,4 +41,32 @@ public interface IReportsAppService : IApplicationService
/// Label Report 导出 PDF
///
Task ExportLabelReportPdfAsync(ReportsLabelReportQueryInputVo input);
+
+ ///
+ /// 按模板统计打印标签数量(分页列表:模板名称 + 打印数)。
+ ///
+ ///
+ /// 统计 fl_label_print_task,按 TemplateId 分组;数据范围与 label-report 一致:
+ /// 管理员可查全部门店(可按 Company/Region/Location 收窄);非管理员仅 userlocation 绑定门店内全部打印任务。
+ ///
+ /// 示例请求:
+ /// ```http
+ /// GET /api/app/reports/template-print-stat-list?SkipCount=1&MaxResultCount=20&StartDate=2026-04-07&EndDate=2026-05-18
+ /// Authorization: Bearer {token}
+ /// ```
+ ///
+ /// 参数说明:
+ /// - SkipCount / MaxResultCount: 分页(SkipCount 为 1-based 页码约定,与项目其它列表一致)
+ /// - StartDate / EndDate: 统计区间(含起止日;默认近 30 天至今天)
+ /// - PartnerId / GroupId / LocationId: 组织范围筛选
+ /// - Keyword: 模板名称模糊匹配
+ /// - Sorting: 可选 PrintedCount desc(默认按打印数降序)
+ ///
+ /// 分页与筛选条件
+ /// 各模板打印数量列表
+ /// 成功返回分页统计
+ /// 入参无效或未登录
+ /// 服务器错误
+ Task> GetTemplatePrintStatListAsync(
+ ReportsTemplatePrintStatGetListInputVo 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 8a764c2..7048114 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
@@ -44,7 +44,7 @@ public interface IUsAppAuthAppService : IApplicationService
/// 按门店 Id 查询 Location 详情(须为当前账号 userlocation 绑定门店)
///
/// 门店 Guid 字符串
- /// 店名、地址、电话、营业时间占位、店长信息
+ /// 店名、地址、电话、经营时间(operatingHours)、店长信息
/// 成功
/// 参数非法、未绑定或无权限
/// 服务器错误
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs
index c97133f..124a380 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs
@@ -1,4 +1,5 @@
using FoodLabeling.Application.Contracts.Dtos.Common;
+using FoodLabeling.Application.Contracts.Dtos.Reports;
using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling;
using Volo.Abp.Application.Services;
@@ -30,7 +31,12 @@ public interface IUsAppLabelingAppService : IApplicationService
Task ReprintAsync(UsAppLabelReprintInputVo input);
///
- /// App 打印日志:获取当前登录账号在当前门店打印的记录(分页,时间倒序)
+ /// App 打印日志:当前门店打印记录(分页)。管理员 / Partner 角色可见门店内全部;其它角色仅本人。
///
Task> GetPrintLogListAsync(PrintLogGetListInputVo input);
+
+ ///
+ /// App Label Report:当前门店统计。权限规则与 一致。
+ ///
+ Task GetLabelReportAsync(UsAppLabelReportQueryInputVo input);
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionDbEntity.cs
index b6fe0f4..231f746 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionDbEntity.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionDbEntity.cs
@@ -29,5 +29,10 @@ public class FlLabelMultipleOptionDbEntity
public int OrderNum { get; set; }
public bool State { 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/Services/DbModels/FlLabelTypeDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypeDbEntity.cs
index 875724a..aa92f95 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypeDbEntity.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypeDbEntity.cs
@@ -27,5 +27,10 @@ public class FlLabelTypeDbEntity
public int OrderNum { get; set; }
public bool State { 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/Services/LabelMultipleOptionAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelMultipleOptionAppService.cs
index 8cc24fa..d012d5c 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
@@ -3,6 +3,7 @@ using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.Dtos.LabelMultipleOption;
using FoodLabeling.Application.Contracts.IServices;
using FoodLabeling.Application.Services.DbModels;
+using FoodLabeling.Domain.Entities;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Services;
@@ -26,12 +27,16 @@ 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 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 = ApplyMultipleOptionScopeFilter(query, scopedLocationIds);
+
if (!string.IsNullOrWhiteSpace(input.Sorting))
{
query = query.OrderBy(input.Sorting);
@@ -42,15 +47,26 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
}
var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
- var items = entities.Select(x => new LabelMultipleOptionGetListOutputDto
+ var scopeMap = await BuildMultipleOptionScopeMapAsync(entities);
+
+ var items = entities.Select(x =>
{
- Id = x.Id,
- OptionCode = x.OptionCode,
- OptionName = x.OptionName,
- OptionValuesJson = x.OptionValuesJson,
- State = x.State,
- OrderNum = x.OrderNum,
- LastEdited = x.LastModificationTime ?? x.CreationTime
+ scopeMap.TryGetValue(x.Id, out var scope);
+ return new LabelMultipleOptionGetListOutputDto
+ {
+ Id = x.Id,
+ OptionCode = x.OptionCode,
+ OptionName = x.OptionName,
+ OptionValuesJson = x.OptionValuesJson,
+ State = x.State,
+ AvailabilityType = x.AvailabilityType,
+ OrderNum = x.OrderNum,
+ LastEdited = x.LastModificationTime ?? x.CreationTime,
+ Region = scope?.Region ?? EmptyDisplay,
+ Location = scope?.Location ?? EmptyDisplay,
+ RegionIds = scope?.RegionIds ?? new List(),
+ LocationIds = scope?.LocationIds ?? new List()
+ };
}).ToList();
return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items);
@@ -65,15 +81,21 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("多选项不存在");
}
- return new LabelMultipleOptionGetOutputDto
+ var dto = MapToGetOutput(entity);
+ if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
{
- Id = entity.Id,
- OptionCode = entity.OptionCode,
- OptionName = entity.OptionName,
- OptionValuesJson = entity.OptionValuesJson,
- State = entity.State,
- OrderNum = entity.OrderNum
- };
+ var locationIds = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => x.MultipleOptionId == entity.Id)
+ .Select(x => x.LocationId)
+ .ToListAsync();
+ dto.LocationIds = LocationScopeBindingHelper.NormalizeIds(locationIds);
+ var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
+ _dbContext.SqlSugarClient, locationIds);
+ dto.RegionIds = regionIds;
+ dto.GroupIds = regionIds;
+ }
+
+ return dto;
}
public async Task CreateAsync(LabelMultipleOptionCreateInputVo input)
@@ -85,6 +107,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("多选项编码和名称不能为空");
}
+ var (availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input);
+
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && (x.OptionCode == code || x.OptionName == name));
if (duplicated)
@@ -92,17 +116,27 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("多选项编码或名称已存在");
}
+ var now = DateTime.Now;
+ var currentUserId = CurrentUser?.Id?.ToString();
var entity = new FlLabelMultipleOptionDbEntity
{
Id = _guidGenerator.Create().ToString(),
+ IsDeleted = false,
+ CreationTime = now,
+ CreatorId = currentUserId,
+ LastModificationTime = now,
+ LastModifierId = currentUserId,
+ ConcurrencyStamp = _guidGenerator.Create().ToString("N"),
OptionCode = code,
OptionName = name,
OptionValuesJson = input.OptionValuesJson?.Trim(),
State = input.State,
+ AvailabilityType = availabilityType,
OrderNum = input.OrderNum
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
+ await SaveMultipleOptionLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now);
return await GetAsync(entity.Id);
}
@@ -122,6 +156,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
throw new UserFriendlyException("多选项编码和名称不能为空");
}
+ var (availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input);
+
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.Id != id && (x.OptionCode == code || x.OptionName == name));
if (duplicated)
@@ -133,11 +169,14 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
entity.OptionName = name;
entity.OptionValuesJson = input.OptionValuesJson?.Trim();
entity.State = input.State;
+ entity.AvailabilityType = availabilityType;
entity.OrderNum = input.OrderNum;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
+ await SaveMultipleOptionLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId,
+ entity.LastModificationTime ?? DateTime.Now);
return await GetAsync(id);
}
@@ -150,12 +189,266 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
return;
}
+ await _dbContext.SqlSugarClient.Deleteable()
+ .Where(x => x.MultipleOptionId == id)
+ .ExecuteCommandAsync();
+
entity.IsDeleted = true;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
}
+ private const string EmptyDisplay = "无";
+ private const string AllRegionsDisplay = "All Regions";
+ private const string AllLocationsDisplay = "All Locations";
+
+ private async Task<(string AvailabilityType, List LocationIds)> ResolveMultipleOptionScopeForSaveAsync(
+ LabelMultipleOptionCreateInputVo input)
+ {
+ var regionIds = NormalizeRegionIds(input);
+ var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds);
+ var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant();
+
+ var hasScopeArrays = input.RegionIds is not null || input.GroupIds is not null || input.LocationIds is not null;
+ if (regionIds.Count > 0 || explicitLocationIds.Count > 0)
+ {
+ availabilityType = "SPECIFIED";
+ }
+ else if (hasScopeArrays && string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase))
+ {
+ availabilityType = "ALL";
+ }
+
+ if (availabilityType != "ALL" && availabilityType != "SPECIFIED")
+ {
+ throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)");
+ }
+
+ if (availabilityType == "ALL")
+ {
+ return ("ALL", new List());
+ }
+
+ var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
+ _dbContext.SqlSugarClient, (IReadOnlyList?)null, regionIds, explicitLocationIds);
+ if (merged.Count == 0)
+ {
+ throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ }
+
+ await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
+ return ("SPECIFIED", merged);
+ }
+
+ private static List NormalizeRegionIds(LabelMultipleOptionCreateInputVo input)
+ {
+ var merged = new HashSet(StringComparer.Ordinal);
+ foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.RegionIds))
+ {
+ merged.Add(id);
+ }
+
+ foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.GroupIds))
+ {
+ merged.Add(id);
+ }
+
+ return merged.OrderBy(x => x, StringComparer.Ordinal).ToList();
+ }
+
+ private async Task SaveMultipleOptionLocationsAsync(
+ string multipleOptionId,
+ string availabilityType,
+ List locationIds,
+ string? currentUserId,
+ DateTime now)
+ {
+ await _dbContext.SqlSugarClient.Deleteable()
+ .Where(x => x.MultipleOptionId == multipleOptionId)
+ .ExecuteCommandAsync();
+
+ if (availabilityType != "SPECIFIED" || locationIds.Count == 0)
+ {
+ return;
+ }
+
+ var rows = locationIds.Select(locId => new FlLabelMultipleOptionLocationDbEntity
+ {
+ Id = _guidGenerator.Create().ToString(),
+ MultipleOptionId = multipleOptionId,
+ LocationId = locId,
+ CreationTime = now,
+ CreatorId = currentUserId
+ }).ToList();
+
+ await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync();
+ }
+
+ private static LabelMultipleOptionGetOutputDto MapToGetOutput(FlLabelMultipleOptionDbEntity x)
+ {
+ return new LabelMultipleOptionGetOutputDto
+ {
+ Id = x.Id,
+ OptionCode = x.OptionCode,
+ OptionName = x.OptionName,
+ OptionValuesJson = x.OptionValuesJson,
+ State = x.State,
+ OrderNum = x.OrderNum,
+ AvailabilityType = x.AvailabilityType
+ };
+ }
+
+ private static ISugarQueryable ApplyMultipleOptionScopeFilter(
+ ISugarQueryable query,
+ List? scopedLocationIds)
+ {
+ if (scopedLocationIds is null)
+ {
+ return query;
+ }
+
+ if (scopedLocationIds.Count == 0)
+ {
+ return query.Where(o => o.AvailabilityType == "ALL");
+ }
+
+ return query.Where(o =>
+ o.AvailabilityType == "ALL" ||
+ SqlFunc.Subqueryable()
+ .Where(ol => ol.MultipleOptionId == o.Id && scopedLocationIds.Contains(ol.LocationId))
+ .Any());
+ }
+
+ private async Task> BuildMultipleOptionScopeMapAsync(
+ List entities)
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ if (entities.Count == 0)
+ {
+ return result;
+ }
+
+ foreach (var e in entities.Where(x =>
+ !string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)))
+ {
+ result[e.Id] = new MultipleOptionScopeData
+ {
+ Region = AllRegionsDisplay,
+ Location = AllLocationsDisplay,
+ RegionIds = new List(),
+ LocationIds = new List()
+ };
+ }
+
+ var specifiedIds = entities
+ .Where(x => string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
+ .Select(x => x.Id)
+ .ToList();
+ if (specifiedIds.Count == 0)
+ {
+ return result;
+ }
+
+ var links = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => specifiedIds.Contains(x.MultipleOptionId))
+ .ToListAsync();
+
+ var locIdSet = links
+ .Select(x => x.LocationId)
+ .Where(x => !string.IsNullOrWhiteSpace(x))
+ .Select(x => x.Trim())
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
+
+ var locById = new Dictionary(StringComparer.Ordinal);
+ if (locIdSet.Count > 0)
+ {
+ var guidList = locIdSet.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList();
+ if (guidList.Count > 0)
+ {
+ var locs = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => !x.IsDeleted && guidList.Contains(x.Id))
+ .ToListAsync();
+ foreach (var loc in locs)
+ {
+ locById[loc.Id.ToString()] = loc;
+ }
+ }
+ }
+
+ foreach (var optionId in specifiedIds)
+ {
+ var optionLinks = links.Where(x => x.MultipleOptionId == optionId).ToList();
+ var locationIds = LocationScopeBindingHelper.NormalizeIds(
+ optionLinks.Select(x => x.LocationId).ToList());
+
+ if (optionLinks.Count == 0)
+ {
+ result[optionId] = new MultipleOptionScopeData
+ {
+ Region = EmptyDisplay,
+ Location = EmptyDisplay,
+ RegionIds = new List(),
+ LocationIds = new List()
+ };
+ continue;
+ }
+
+ var regions = new HashSet(StringComparer.OrdinalIgnoreCase);
+ var locationNames = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var lid in locationIds)
+ {
+ if (!locById.TryGetValue(lid, out var loc))
+ {
+ continue;
+ }
+
+ var groupName = loc.GroupName?.Trim();
+ if (!string.IsNullOrEmpty(groupName))
+ {
+ regions.Add(groupName);
+ }
+
+ var locName = loc.LocationName?.Trim();
+ if (string.IsNullOrEmpty(locName))
+ {
+ locName = loc.LocationCode?.Trim();
+ }
+
+ if (!string.IsNullOrEmpty(locName))
+ {
+ locationNames.Add(locName);
+ }
+ }
+
+ var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
+ _dbContext.SqlSugarClient, locationIds);
+
+ result[optionId] = new MultipleOptionScopeData
+ {
+ Region = regions.Count > 0
+ ? string.Join(", ", regions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
+ : EmptyDisplay,
+ Location = locationNames.Count > 0
+ ? string.Join(", ", locationNames.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
+ : EmptyDisplay,
+ RegionIds = regionIds,
+ LocationIds = locationIds
+ };
+ }
+
+ return result;
+ }
+
+ private sealed class MultipleOptionScopeData
+ {
+ public string Region { get; init; } = string.Empty;
+ public string Location { get; init; } = string.Empty;
+ public List RegionIds { get; init; } = new();
+ public List LocationIds { get; init; } = new();
+ }
+
private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total, List items)
{
var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount;
@@ -171,4 +464,3 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO
};
}
}
-
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 ac82144..29838ee 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
@@ -3,6 +3,7 @@ using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.Dtos.LabelType;
using FoodLabeling.Application.Contracts.IServices;
using FoodLabeling.Application.Services.DbModels;
+using FoodLabeling.Domain.Entities;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Services;
@@ -26,12 +27,16 @@ 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 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 = ApplyLabelTypeScopeFilter(query, scopedLocationIds);
+
if (!string.IsNullOrWhiteSpace(input.Sorting))
{
query = query.OrderBy(input.Sorting);
@@ -44,23 +49,28 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
var ids = entities.Select(x => x.Id).ToList();
- var countRows = await _dbContext.SqlSugarClient.Queryable()
- .Where(x => !x.IsDeleted)
- .Where(x => x.LabelTypeId != null && ids.Contains(x.LabelTypeId))
- .GroupBy(x => x.LabelTypeId)
- .Select(x => new { TypeId = x.LabelTypeId, Count = SqlFunc.AggregateCount(x.Id) })
- .ToListAsync();
- var countMap = countRows.ToDictionary(x => x.TypeId!, x => (long)x.Count);
+ var countMap = await BuildTypeLabelStatsMapAsync(ids, scopedLocationIds);
+ var scopeMap = await BuildTypeConfiguredScopeMapAsync(entities);
- var items = entities.Select(x => new LabelTypeGetListOutputDto
+ var items = entities.Select(x =>
{
- Id = x.Id,
- TypeCode = x.TypeCode,
- TypeName = x.TypeName,
- State = x.State,
- OrderNum = x.OrderNum,
- NoOfLabels = countMap.TryGetValue(x.Id, out var count) ? count : 0,
- LastEdited = x.LastModificationTime ?? x.CreationTime
+ scopeMap.TryGetValue(x.Id, out var scope);
+ countMap.TryGetValue(x.Id, out var stats);
+ return new LabelTypeGetListOutputDto
+ {
+ Id = x.Id,
+ TypeCode = x.TypeCode,
+ TypeName = x.TypeName,
+ State = x.State,
+ AvailabilityType = x.AvailabilityType,
+ OrderNum = x.OrderNum,
+ NoOfLabels = stats?.Count ?? 0,
+ LastEdited = stats?.MaxEdited ?? x.LastModificationTime ?? x.CreationTime,
+ Region = scope?.Region ?? EmptyDisplay,
+ Location = scope?.Location ?? EmptyDisplay,
+ RegionIds = scope?.RegionIds ?? new List(),
+ LocationIds = scope?.LocationIds ?? new List()
+ };
}).ToList();
return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items);
@@ -75,7 +85,21 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("标签类型不存在");
}
- return MapToGetOutput(entity);
+ var dto = MapToGetOutput(entity);
+ if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
+ {
+ var locationIds = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => x.LabelTypeId == entity.Id)
+ .Select(x => x.LocationId)
+ .ToListAsync();
+ dto.LocationIds = LocationScopeBindingHelper.NormalizeIds(locationIds);
+ var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
+ _dbContext.SqlSugarClient, locationIds);
+ dto.RegionIds = regionIds;
+ dto.GroupIds = regionIds;
+ }
+
+ return dto;
}
public async Task CreateAsync(LabelTypeCreateInputVo input)
@@ -87,6 +111,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("类型编码和名称不能为空");
}
+ var (availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input);
+
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && (x.TypeCode == code || x.TypeName == name));
if (duplicated)
@@ -94,16 +120,26 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("类型编码或名称已存在");
}
+ var now = DateTime.Now;
+ var currentUserId = CurrentUser?.Id?.ToString();
var entity = new FlLabelTypeDbEntity
{
Id = _guidGenerator.Create().ToString(),
+ IsDeleted = false,
+ CreationTime = now,
+ CreatorId = currentUserId,
+ LastModificationTime = now,
+ LastModifierId = currentUserId,
+ ConcurrencyStamp = _guidGenerator.Create().ToString("N"),
TypeCode = code,
TypeName = name,
State = input.State,
+ AvailabilityType = availabilityType,
OrderNum = input.OrderNum
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
+ await SaveTypeLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now);
return await GetAsync(entity.Id);
}
@@ -123,6 +159,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("类型编码和名称不能为空");
}
+ var (availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input);
+
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.Id != id && (x.TypeCode == code || x.TypeName == name));
if (duplicated)
@@ -133,11 +171,14 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
entity.TypeCode = code;
entity.TypeName = name;
entity.State = input.State;
+ entity.AvailabilityType = availabilityType;
entity.OrderNum = input.OrderNum;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
+ await SaveTypeLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId,
+ entity.LastModificationTime ?? DateTime.Now);
return await GetAsync(id);
}
@@ -157,12 +198,304 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
throw new UserFriendlyException("该标签类型已被标签引用,无法删除");
}
+ await _dbContext.SqlSugarClient.Deleteable()
+ .Where(x => x.LabelTypeId == id)
+ .ExecuteCommandAsync();
+
entity.IsDeleted = true;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
}
+ private const string EmptyDisplay = "无";
+ private const string AllRegionsDisplay = "All Regions";
+ private const string AllLocationsDisplay = "All Locations";
+
+ private async Task<(string AvailabilityType, List LocationIds)> ResolveTypeScopeForSaveAsync(
+ LabelTypeCreateInputVo input)
+ {
+ var regionIds = NormalizeRegionIds(input);
+ var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds);
+ var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant();
+
+ var hasScopeArrays = input.RegionIds is not null || input.GroupIds is not null || input.LocationIds is not null;
+ if (regionIds.Count > 0 || explicitLocationIds.Count > 0)
+ {
+ availabilityType = "SPECIFIED";
+ }
+ else if (hasScopeArrays && string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase))
+ {
+ availabilityType = "ALL";
+ }
+
+ if (availabilityType != "ALL" && availabilityType != "SPECIFIED")
+ {
+ throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)");
+ }
+
+ if (availabilityType == "ALL")
+ {
+ return ("ALL", new List());
+ }
+
+ var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
+ _dbContext.SqlSugarClient, (IReadOnlyList?)null, regionIds, explicitLocationIds);
+ if (merged.Count == 0)
+ {
+ throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店");
+ }
+
+ await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged);
+ return ("SPECIFIED", merged);
+ }
+
+ private static List NormalizeRegionIds(LabelTypeCreateInputVo input)
+ {
+ var merged = new HashSet(StringComparer.Ordinal);
+ foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.RegionIds))
+ {
+ merged.Add(id);
+ }
+
+ foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.GroupIds))
+ {
+ merged.Add(id);
+ }
+
+ return merged.OrderBy(x => x, StringComparer.Ordinal).ToList();
+ }
+
+ private static ISugarQueryable ApplyLabelTypeScopeFilter(
+ ISugarQueryable query,
+ List? scopedLocationIds)
+ {
+ if (scopedLocationIds is null)
+ {
+ return query;
+ }
+
+ if (scopedLocationIds.Count == 0)
+ {
+ return query.Where(t => t.AvailabilityType == "ALL");
+ }
+
+ return query.Where(t =>
+ t.AvailabilityType == "ALL" ||
+ SqlFunc.Subqueryable()
+ .Where(tl => tl.LabelTypeId == t.Id && scopedLocationIds.Contains(tl.LocationId))
+ .Any());
+ }
+
+ private async Task SaveTypeLocationsAsync(
+ string labelTypeId,
+ string availabilityType,
+ List locationIds,
+ string? currentUserId,
+ DateTime now)
+ {
+ await _dbContext.SqlSugarClient.Deleteable()
+ .Where(x => x.LabelTypeId == labelTypeId)
+ .ExecuteCommandAsync();
+
+ if (availabilityType != "SPECIFIED" || locationIds.Count == 0)
+ {
+ return;
+ }
+
+ var rows = locationIds.Select(locId => new FlLabelTypeLocationDbEntity
+ {
+ Id = _guidGenerator.Create().ToString(),
+ LabelTypeId = labelTypeId,
+ LocationId = locId,
+ CreationTime = now,
+ CreatorId = currentUserId
+ }).ToList();
+
+ await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync();
+ }
+
+ private async Task> BuildTypeLabelStatsMapAsync(
+ List typeIds,
+ List? scopedLocationIds)
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ if (typeIds.Count == 0)
+ {
+ return result;
+ }
+
+ var labelQuery = _dbContext.SqlSugarClient.Queryable()
+ .Where(x => !x.IsDeleted)
+ .Where(x => x.LabelTypeId != null && typeIds.Contains(x.LabelTypeId));
+
+ labelQuery = ApplyLabelScopeOnLabels(labelQuery, scopedLocationIds);
+
+ var rows = await labelQuery
+ .Select(x => new { x.LabelTypeId, x.CreationTime, x.LastModificationTime })
+ .ToListAsync();
+
+ foreach (var g in rows.GroupBy(x => x.LabelTypeId!))
+ {
+ result[g.Key] = new TypeLabelStats
+ {
+ Count = g.Count(),
+ MaxEdited = g.Max(l => l.LastModificationTime ?? l.CreationTime)
+ };
+ }
+
+ return result;
+ }
+
+ private async Task> BuildTypeConfiguredScopeMapAsync(
+ List entities)
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ if (entities.Count == 0)
+ {
+ return result;
+ }
+
+ foreach (var e in entities.Where(x =>
+ !string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)))
+ {
+ result[e.Id] = new TypeScopeData
+ {
+ Region = AllRegionsDisplay,
+ Location = AllLocationsDisplay,
+ RegionIds = new List(),
+ LocationIds = new List()
+ };
+ }
+
+ var specifiedIds = entities
+ .Where(x => string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))
+ .Select(x => x.Id)
+ .ToList();
+ if (specifiedIds.Count == 0)
+ {
+ return result;
+ }
+
+ var links = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => specifiedIds.Contains(x.LabelTypeId))
+ .ToListAsync();
+
+ var locIdSet = links
+ .Select(x => x.LocationId)
+ .Where(x => !string.IsNullOrWhiteSpace(x))
+ .Select(x => x.Trim())
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
+
+ var locById = new Dictionary(StringComparer.Ordinal);
+ if (locIdSet.Count > 0)
+ {
+ var guidList = locIdSet.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList();
+ if (guidList.Count > 0)
+ {
+ var locs = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => !x.IsDeleted && guidList.Contains(x.Id))
+ .ToListAsync();
+ foreach (var loc in locs)
+ {
+ locById[loc.Id.ToString()] = loc;
+ }
+ }
+ }
+
+ foreach (var typeId in specifiedIds)
+ {
+ var typeLinks = links.Where(x => x.LabelTypeId == typeId).ToList();
+ var locationIds = LocationScopeBindingHelper.NormalizeIds(
+ typeLinks.Select(x => x.LocationId).ToList());
+
+ if (typeLinks.Count == 0)
+ {
+ result[typeId] = new TypeScopeData
+ {
+ Region = EmptyDisplay,
+ Location = EmptyDisplay,
+ RegionIds = new List(),
+ LocationIds = new List()
+ };
+ continue;
+ }
+
+ var regions = new HashSet(StringComparer.OrdinalIgnoreCase);
+ var locationNames = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var lid in locationIds)
+ {
+ if (!locById.TryGetValue(lid, out var loc))
+ {
+ continue;
+ }
+
+ var groupName = loc.GroupName?.Trim();
+ if (!string.IsNullOrEmpty(groupName))
+ {
+ regions.Add(groupName);
+ }
+
+ var locName = loc.LocationName?.Trim();
+ if (string.IsNullOrEmpty(locName))
+ {
+ locName = loc.LocationCode?.Trim();
+ }
+
+ if (!string.IsNullOrEmpty(locName))
+ {
+ locationNames.Add(locName);
+ }
+ }
+
+ var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
+ _dbContext.SqlSugarClient, locationIds);
+
+ result[typeId] = new TypeScopeData
+ {
+ Region = regions.Count > 0
+ ? string.Join(", ", regions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
+ : EmptyDisplay,
+ Location = locationNames.Count > 0
+ ? string.Join(", ", locationNames.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
+ : EmptyDisplay,
+ RegionIds = regionIds,
+ LocationIds = locationIds
+ };
+ }
+
+ return result;
+ }
+
+ private static ISugarQueryable ApplyLabelScopeOnLabels(
+ ISugarQueryable labelQuery,
+ List? scopedLocationIds)
+ {
+ if (scopedLocationIds is null)
+ {
+ return labelQuery;
+ }
+
+ return scopedLocationIds.Count == 0
+ ? labelQuery.Where(_ => false)
+ : labelQuery.Where(l => scopedLocationIds.Contains(l.LocationId));
+ }
+
+ private sealed class TypeScopeData
+ {
+ public string Region { get; init; } = string.Empty;
+ public string Location { get; init; } = string.Empty;
+ public List RegionIds { get; init; } = new();
+ public List LocationIds { get; init; } = new();
+ }
+
+ private sealed class TypeLabelStats
+ {
+ public long Count { get; init; }
+ public DateTime MaxEdited { get; init; }
+ }
+
private static LabelTypeGetOutputDto MapToGetOutput(FlLabelTypeDbEntity x)
{
return new LabelTypeGetOutputDto
@@ -171,7 +504,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
TypeCode = x.TypeCode,
TypeName = x.TypeName,
State = x.State,
- OrderNum = x.OrderNum
+ OrderNum = x.OrderNum,
+ AvailabilityType = x.AvailabilityType
};
}
@@ -190,4 +524,3 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService
};
}
}
-
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationAppService.cs
index 80b3956..355f984 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationAppService.cs
@@ -22,13 +22,16 @@ namespace FoodLabeling.Application.Services;
public class LocationAppService : ApplicationService, ILocationAppService
{
private readonly ISqlSugarRepository _locationRepository;
+ private readonly ISqlSugarDbContext _dbContext;
private readonly IOptionsSnapshot _batchImportOptions;
public LocationAppService(
ISqlSugarRepository locationRepository,
+ ISqlSugarDbContext dbContext,
IOptionsSnapshot batchImportOptions)
{
_locationRepository = locationRepository;
+ _dbContext = dbContext;
_batchImportOptions = batchImportOptions;
}
@@ -37,7 +40,7 @@ public class LocationAppService : ApplicationService, ILocationAppService
{
RefAsync total = 0;
- var query = BuildFilteredQuery(input);
+ var query = await BuildFilteredQueryAsync(input);
if (!string.IsNullOrWhiteSpace(input.Sorting))
{
query = query.OrderBy(input.Sorting);
@@ -200,7 +203,7 @@ public class LocationAppService : ApplicationService, ILocationAppService
State = input.State
};
- var query = BuildFilteredQuery(exportFilter);
+ var query = await BuildFilteredQueryAsync(exportFilter);
if (!string.IsNullOrWhiteSpace(exportFilter.Sorting))
{
query = query.OrderBy(exportFilter.Sorting);
@@ -328,14 +331,20 @@ public class LocationAppService : ApplicationService, ILocationAppService
return result;
}
- private ISugarQueryable BuildFilteredQuery(LocationGetListInputVo input)
+ private async Task> BuildFilteredQueryAsync(LocationGetListInputVo input)
{
+ var scope = await LocationRegionScopeHelper.ResolveLocationListScopeAsync(CurrentUser, _dbContext);
+
var keyword = input.Keyword?.Trim();
var partner = input.Partner?.Trim();
var groupName = input.GroupName?.Trim();
- return _locationRepository._DbQueryable
- .Where(x => x.IsDeleted == false)
+ var query = _locationRepository._DbQueryable
+ .Where(x => x.IsDeleted == false);
+
+ query = LocationRegionScopeHelper.ApplyLocationListScope(query, scope);
+
+ return query
.WhereIF(!string.IsNullOrEmpty(partner), x => x.Partner == partner)
.WhereIF(!string.IsNullOrEmpty(groupName), x => x.GroupName == groupName)
.WhereIF(input.State is not null, x => x.State == input.State)
@@ -350,8 +359,7 @@ public class LocationAppService : ApplicationService, ILocationAppService
(x.ZipCode != null && x.ZipCode.Contains(keyword!)) ||
(x.Phone != null && x.Phone.Contains(keyword!)) ||
(x.Email != null && x.Email.Contains(keyword!)) ||
- (x.OperatingHours != null && x.OperatingHours.Contains(keyword!))
- );
+ (x.OperatingHours != null && x.OperatingHours.Contains(keyword!)));
}
private static LocationGetListOutputDto ToListDto(LocationAggregateRoot x) =>
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs
index b2f7950..05908a6 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs
@@ -1,10 +1,9 @@
using System.Text.Json;
using FoodLabeling.Application.Contracts.Constants;
-using FoodLabeling.Application.Contracts.Dtos.RbacRole;
using FoodLabeling.Application.Helpers;
+using FoodLabeling.Application.Contracts.Dtos.RbacRole;
using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.IServices;
-using FoodLabeling.Application.Services.DbModels;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp;
@@ -103,8 +102,8 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
throw new UserFriendlyException("角色不存在");
}
- var menuIds = await _dbContext.SqlSugarClient.Queryable()
- .Where(x => x.RoleId == id.ToString())
+ var menuIds = await _roleMenuRepository._DbQueryable
+ .Where(x => x.RoleId == id)
.Select(x => x.MenuId)
.ToListAsync();
@@ -118,7 +117,7 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
State = entity.State,
OrderNum = entity.OrderNum,
AccessPermissionCodes = DeserializeAccessPermissionCodes(entity.AccessPermissionCodesJson),
- MenuIds = menuIds
+ MenuIds = menuIds.Select(x => x.ToString()).ToList()
};
await FillAccessPermissionsAsync(new List { dto });
return dto;
@@ -212,20 +211,41 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
}
///
- /// 新增/编辑时按 menuIds 或 accessPermissions 绑定角色菜单(二者都未传则不改绑定)
+ /// 新增/编辑时按 menuIds 或 accessPermissions 绑定角色菜单(RoleMenu 表)。
///
private async Task ApplyRoleMenuBindingsAsync(Guid roleId, RbacRoleCreateInputVo input)
{
- if (input.MenuIds is not null)
+ var hasMenuIds = input.MenuIds is not null;
+ var hasAccessPermissions = input.AccessPermissions is not null;
+
+ if (hasMenuIds && input.MenuIds!.Count > 0)
{
await SetRoleMenusAsync(roleId, input.MenuIds);
return;
}
- if (input.AccessPermissions is not null)
+ if (hasAccessPermissions)
{
+ if (string.IsNullOrWhiteSpace(input.AccessPermissions))
+ {
+ await SetRoleMenusAsync(roleId, new List());
+ return;
+ }
+
var menuIds = await ResolveMenuIdsFromAccessPermissionsAsync(input.AccessPermissions);
+ if (menuIds.Count == 0)
+ {
+ throw new UserFriendlyException(
+ "accessPermissions 未匹配到任何菜单,请确认 PermissionCode 与菜单一致,或先执行 menu_backfill_permission_code.sql 回填 Menu.PermissionCode");
+ }
+
await SetRoleMenusAsync(roleId, menuIds);
+ return;
+ }
+
+ if (hasMenuIds && input.MenuIds!.Count == 0)
+ {
+ await SetRoleMenusAsync(roleId, new List());
}
}
@@ -250,10 +270,15 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
return;
}
- var entities = existMenuIds.Select(menuId => new RoleMenuEntity
+ var entities = existMenuIds.Select(menuId =>
{
- RoleId = roleId,
- MenuId = menuId
+ var entity = new RoleMenuEntity
+ {
+ RoleId = roleId,
+ MenuId = menuId
+ };
+ EntityHelper.TrySetId(entity, () => GuidGenerator.Create());
+ return entity;
}).ToList();
await _roleMenuRepository.InsertRangeAsync(entities);
@@ -261,24 +286,28 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
private async Task> ResolveMenuIdsFromAccessPermissionsAsync(string accessPermissions)
{
- var codes = accessPermissions
- .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
- .Where(c => !string.IsNullOrWhiteSpace(c))
- .Distinct(StringComparer.Ordinal)
- .ToList();
-
+ var codes = RbacAccessPermissionHelper.ParseAccessPermissionCodes(accessPermissions);
if (codes.Count == 0)
{
return new List();
}
+ var codeSet = new HashSet(codes, StringComparer.OrdinalIgnoreCase);
+
var menus = await _menuRepository._DbQueryable
- .Where(m => m.IsDeleted == false && m.PermissionCode != null)
- .Where(m => codes.Contains(m.PermissionCode!))
- .Select(m => m.Id)
+ .Where(m => m.IsDeleted == false)
+ .Select(m => new { m.Id, m.PermissionCode, m.Router })
.ToListAsync();
- return menus;
+ return menus
+ .Where(m =>
+ {
+ var effective = RbacAccessPermissionHelper.GetEffectivePermissionCode(m.PermissionCode, m.Router);
+ return effective is not null && codeSet.Contains(effective);
+ })
+ .Select(m => m.Id)
+ .Distinct()
+ .ToList();
}
private async Task FillAccessPermissionsAsync(List items)
@@ -296,7 +325,7 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
}
///
- /// 按角色汇总已绑定菜单上的 PermissionCode(去重、英文逗号+空格拼接)
+ /// Role → RoleMenu → Menu.PermissionCode(空则按 Router 推导)汇总 accessPermissions。
///
private async Task> GetAccessPermissionsByRoleIdsAsync(List roleIds)
{
@@ -319,11 +348,13 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
var menuIds = links.Select(x => x.MenuId).Distinct().ToList();
var menus = await _menuRepository._DbQueryable
.Where(m => menuIds.Contains(m.Id) && m.IsDeleted == false)
- .Select(m => new { m.Id, m.PermissionCode })
+ .Select(m => new { m.Id, m.PermissionCode, m.Router })
.ToListAsync();
- var permByMenuId = menus.ToDictionary(x => x.Id, x => x.PermissionCode);
+ var permByMenuId = menus.ToDictionary(
+ x => x.Id,
+ x => RbacAccessPermissionHelper.GetEffectivePermissionCode(x.PermissionCode, x.Router));
- var byRole = distinctRoleIds.ToDictionary(id => id, _ => new HashSet(StringComparer.Ordinal));
+ var byRole = distinctRoleIds.ToDictionary(id => id, _ => new HashSet(StringComparer.OrdinalIgnoreCase));
foreach (var link in links)
{
if (!permByMenuId.TryGetValue(link.MenuId, out var code) || string.IsNullOrWhiteSpace(code))
@@ -364,7 +395,6 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
await _roleDeptRepository.DeleteAsync(x => idList.Contains(x.RoleId));
await _userRoleRepository.DeleteAsync(x => idList.Contains(x.RoleId));
- // 角色表为软删(ISoftDelete)
await _roleRepository.DeleteAsync(x => idList.Contains(x.Id));
}
@@ -426,4 +456,3 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService
.ToList();
}
}
-
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleMenuAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleMenuAppService.cs
index e096e27..ff3dd07 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleMenuAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleMenuAppService.cs
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Services;
+using Volo.Abp.Domain.Entities;
using Volo.Abp.Uow;
using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -56,10 +57,15 @@ public class RbacRoleMenuAppService : ApplicationService, IRbacRoleMenuAppServic
await _roleMenuRepository.DeleteAsync(x => x.RoleId == input.RoleId);
- var entities = existMenuIds.Select(menuId => new RoleMenuEntity
+ var entities = existMenuIds.Select(menuId =>
{
- RoleId = input.RoleId,
- MenuId = menuId
+ var entity = new RoleMenuEntity
+ {
+ RoleId = input.RoleId,
+ MenuId = menuId
+ };
+ EntityHelper.TrySetId(entity, () => GuidGenerator.Create());
+ return entity;
}).ToList();
if (entities.Count > 0)
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 c9012f5..0baef6a 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
@@ -106,6 +106,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var userMap = await LoadUserNameMapAsync(pageRows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!).Distinct().ToList());
+ var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync(
+ _dbContext.SqlSugarClient,
+ pageRows.Select(x => new ReportsPrintLogDailyLabelIdHelper.PrintTaskScopeKey(
+ x.Id,
+ x.LocationId,
+ x.PrintedAt ?? DateTime.MinValue)).ToList());
+
var items = pageRows.Select(x =>
{
var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName)
@@ -114,10 +121,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var templateText = FormatTemplateDisplay(x.Width, x.Height, x.Unit, x.TemplateName);
var locText = FormatLocationText(x.LocName, x.LocCode);
var printedAt = x.PrintedAt ?? DateTime.MinValue;
+ var labelDisplayId = dailyLabelIdMap.TryGetValue(x.Id, out var dailyId) ? dailyId : "无";
return new ReportsPrintLogListItemDto
{
TaskId = x.Id,
- LabelCode = string.IsNullOrWhiteSpace(x.LabelCode) ? "无" : x.LabelCode.Trim(),
+ LabelCode = labelDisplayId,
ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(),
ProductCategoryName = string.IsNullOrWhiteSpace(x.ProductCategoryName)
? "无"
@@ -131,7 +139,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
PrintedByName = ResolveUserName(userMap, x.CreatedBy),
LocationText = locText,
LocationId = x.LocationId?.Trim(),
- ExpiryDateText = TryExtractExpiryText(x.PrintInputJson)
+ ExpiryDateText = ReportsPrintLogExpiryHelper.ExtractExpiryText(x.PrintInputJson)
};
}).ToList();
@@ -191,6 +199,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
t.PrintInputJson,
PrintedAt = SqlFunc.IsNull(t.PrintedAt, t.CreationTime),
t.CreatedBy,
+ t.LocationId,
LocName = loc.LocationName,
LocCode = loc.LocationCode
})
@@ -199,6 +208,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var userMap = await LoadUserNameMapAsync(rows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!).Distinct().ToList());
+ var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync(
+ _dbContext.SqlSugarClient,
+ rows.Select(x => new ReportsPrintLogDailyLabelIdHelper.PrintTaskScopeKey(
+ x.Id,
+ x.LocationId,
+ x.PrintedAt ?? DateTime.MinValue)).ToList());
+
var fileName = $"print-log_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf";
var document = Document.Create(container =>
{
@@ -236,8 +252,9 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName)
? x.ProductCategoryName!.Trim()
: (string.IsNullOrWhiteSpace(x.LabelCategoryName) ? "无" : x.LabelCategoryName.Trim());
+ var labelDisplayId = dailyLabelIdMap.TryGetValue(x.Id, out var dailyId) ? dailyId : "无";
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3)
- .Text(string.IsNullOrWhiteSpace(x.LabelCode) ? "无" : x.LabelCode.Trim());
+ .Text(labelDisplayId);
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3)
.Text(string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim());
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3).Text(cat);
@@ -251,7 +268,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3)
.Text(FormatLocationText(x.LocName, x.LocCode));
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3)
- .Text(TryExtractExpiryText(x.PrintInputJson));
+ .Text(ReportsPrintLogExpiryHelper.ExtractExpiryText(x.PrintInputJson));
}
});
});
@@ -338,7 +355,14 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var userMap = await LoadUserNameMapAsync(pageRows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!).Distinct().ToList());
- var items = pageRows.Select(x => MapPrintLogExportRowToListItem(x, userMap)).ToList();
+ var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync(
+ _dbContext.SqlSugarClient,
+ pageRows.Select(x => new ReportsPrintLogDailyLabelIdHelper.PrintTaskScopeKey(
+ x.Id,
+ x.LocationId,
+ x.PrintedAt ?? DateTime.MinValue)).ToList());
+
+ var items = pageRows.Select(x => MapPrintLogExportRowToListItem(x, userMap, dailyLabelIdMap)).ToList();
var ms = ReportsPrintLogExcelHelper.BuildWorkbook(items);
var fileName = $"print-log_{Clock.Now:yyyyMMdd-HHmmss}.xlsx";
@@ -351,6 +375,84 @@ public class ReportsAppService : ApplicationService, IReportsAppService
_usAppLabelingAppService.ReprintAsync(input);
///
+ public async Task> GetTemplatePrintStatListAsync(
+ ReportsTemplatePrintStatGetListInputVo input)
+ {
+ if (input is null)
+ {
+ throw new UserFriendlyException("入参不能为空");
+ }
+
+ if (!CurrentUser.Id.HasValue)
+ {
+ throw new UserFriendlyException("用户未登录");
+ }
+
+ var locationIds = await ReportsLocationScopeHelper.ResolveReportLocationIdsAsync(
+ CurrentUser,
+ _dbContext.SqlSugarClient,
+ input.PartnerId,
+ input.GroupId,
+ input.LocationId);
+ if (locationIds is not null && locationIds.Count == 0)
+ {
+ return EmptyTemplatePrintStatPage(input);
+ }
+
+ var (rangeStart, rangeEndExcl) = ResolveDateRange(input.StartDate, input.EndDate);
+ var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
+ var currentUserIdStr = CurrentUser.Id.Value.ToString();
+ var templateKeyword = input.Keyword?.Trim();
+
+ var groupedRows = await BuildReportTaskCore(locationIds, isAdmin, 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) =>
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= rangeStart &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < rangeEndExcl)
+ .WhereIF(!string.IsNullOrWhiteSpace(templateKeyword),
+ (t, l, p, lc, pc, loc, tpl) =>
+ tpl.TemplateName != null && tpl.TemplateName.Contains(templateKeyword!))
+ .GroupBy((t, l, p, lc, pc, loc, tpl) => new { t.TemplateId, tpl.TemplateName })
+ .Select((t, l, p, lc, pc, loc, tpl) => new
+ {
+ t.TemplateId,
+ tpl.TemplateName,
+ Cnt = SqlFunc.AggregateCount(t.Id)
+ })
+ .ToListAsync();
+
+ var ordered = groupedRows
+ .Select(x => new ReportsTemplatePrintStatListItemDto
+ {
+ TemplateId = string.IsNullOrWhiteSpace(x.TemplateId) ? null : x.TemplateId.Trim(),
+ TemplateName = string.IsNullOrWhiteSpace(x.TemplateName) ? "无" : x.TemplateName.Trim(),
+ PrintedCount = x.Cnt
+ })
+ .ToList();
+
+ if (!string.IsNullOrWhiteSpace(input.Sorting) &&
+ input.Sorting.Trim().Equals("PrintedCount asc", StringComparison.OrdinalIgnoreCase))
+ {
+ ordered = ordered.OrderBy(x => x.PrintedCount).ThenBy(x => x.TemplateName).ToList();
+ }
+ else
+ {
+ ordered = ordered.OrderByDescending(x => x.PrintedCount).ThenBy(x => x.TemplateName).ToList();
+ }
+
+ var total = ordered.Count;
+ var pageIndex = PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount);
+ var pageSize = input.MaxResultCount <= 0 ? total : input.MaxResultCount;
+ var offset = pageSize <= 0 ? 0 : (pageIndex - 1) * pageSize;
+ var pageItems = pageSize <= 0
+ ? ordered
+ : ordered.Skip(offset).Take(pageSize).ToList();
+
+ return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, pageItems);
+ }
+
+ ///
public async Task GetLabelReportAsync(ReportsLabelReportQueryInputVo input)
{
if (input is null)
@@ -363,7 +465,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)
{
return new ReportsLabelReportOutputDto();
@@ -382,13 +489,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var currentUserIdStr = CurrentUser.Id.Value.ToString();
var keyword = input.Keyword?.Trim();
- var totalCur = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword)
+ var totalCur = await BuildReportTaskCore(locationIds, isAdmin, 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)
+ var totalPrev = await BuildReportTaskCore(locationIds, isAdmin, 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)
@@ -399,7 +506,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)
+ var categoryRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
l.LabelCategoryId != null &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
@@ -410,7 +517,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var topCat = categoryRows.OrderByDescending(x => x.Cnt).FirstOrDefault();
- var productRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword)
+ var productRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false)
.Where((t, l, p, lc, pc, loc) =>
!string.IsNullOrEmpty(p.Id) &&
SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
@@ -431,7 +538,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var trendEndExcl = trendEndDay.AddDays(1);
- var trendRaw = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword)
+ var trendRaw = await BuildReportTaskCore(locationIds, isAdmin, 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)
@@ -585,7 +692,8 @@ public class ReportsAppService : ApplicationService, IReportsAppService
List? locationIds,
bool isAdmin,
string currentUserIdStr,
- string? keyword)
+ string? keyword,
+ bool restrictToCreator = true)
{
return _dbContext.SqlSugarClient.Queryable()
.LeftJoin((t, l) => t.LabelId == l.Id)
@@ -595,7 +703,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(!isAdmin, (t, l, p, lc, pc, loc) => t.CreatedBy == currentUserIdStr)
+ .WhereIF(restrictToCreator && !isAdmin, (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) =>
@@ -697,6 +805,21 @@ public class ReportsAppService : ApplicationService, IReportsAppService
};
}
+ private static PagedResultWithPageDto EmptyTemplatePrintStatPage(
+ ReportsTemplatePrintStatGetListInputVo input)
+ {
+ var pageSize = input.MaxResultCount <= 0 ? 0 : input.MaxResultCount;
+ var pageIndex = pageSize <= 0 ? 1 : PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount);
+ return new PagedResultWithPageDto
+ {
+ PageIndex = pageIndex,
+ PageSize = pageSize,
+ TotalCount = 0,
+ TotalPages = 0,
+ Items = new List()
+ };
+ }
+
private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total,
List items)
{
@@ -799,54 +922,6 @@ public class ReportsAppService : ApplicationService, IReportsAppService
return $"{ws}x{hs}{normalizedUnit}";
}
- private static string TryExtractExpiryText(string? printInputJson)
- {
- if (string.IsNullOrWhiteSpace(printInputJson))
- {
- return "无";
- }
-
- try
- {
- using var doc = JsonDocument.Parse(printInputJson);
- if (doc.RootElement.ValueKind != JsonValueKind.Object)
- {
- return "无";
- }
-
- foreach (var prop in doc.RootElement.EnumerateObject())
- {
- var key = prop.Name.Trim();
- if (!key.Equals("expiryDate", StringComparison.OrdinalIgnoreCase) &&
- !key.Equals("expiry", StringComparison.OrdinalIgnoreCase) &&
- !key.Equals("expirationDate", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- var v = prop.Value;
- if (v.ValueKind == JsonValueKind.String)
- {
- var s = v.GetString();
- return string.IsNullOrWhiteSpace(s) ? "无" : s.Trim();
- }
-
- if (v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var n))
- {
- return n.ToString(CultureInfo.InvariantCulture);
- }
-
- return v.ToString();
- }
- }
- catch
- {
- return "无";
- }
-
- return "无";
- }
-
private static IActionResult BuildEmptyPdf(string fileName)
{
QuestPDF.Settings.License = LicenseType.Community;
@@ -897,8 +972,10 @@ public class ReportsAppService : ApplicationService, IReportsAppService
public string? LocCode { get; set; }
}
- private static ReportsPrintLogListItemDto MapPrintLogExportRowToListItem(PrintLogExportRow x,
- Dictionary userMap)
+ private static ReportsPrintLogListItemDto MapPrintLogExportRowToListItem(
+ PrintLogExportRow x,
+ Dictionary userMap,
+ IReadOnlyDictionary dailyLabelIdMap)
{
var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName)
? x.ProductCategoryName!.Trim()
@@ -906,10 +983,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService
var templateText = FormatTemplateDisplay(x.Width, x.Height, x.Unit, x.TemplateName);
var locText = FormatLocationText(x.LocName, x.LocCode);
var printedAt = x.PrintedAt ?? DateTime.MinValue;
+ var labelDisplayId = dailyLabelIdMap.TryGetValue(x.Id, out var dailyId) ? dailyId : "无";
return new ReportsPrintLogListItemDto
{
TaskId = x.Id,
- LabelCode = string.IsNullOrWhiteSpace(x.LabelCode) ? "无" : x.LabelCode.Trim(),
+ LabelCode = labelDisplayId,
ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(),
ProductCategoryName = string.IsNullOrWhiteSpace(x.ProductCategoryName)
? "无"
@@ -923,7 +1001,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService
PrintedByName = ResolveUserName(userMap, x.CreatedBy),
LocationText = locText,
LocationId = x.LocationId?.Trim(),
- ExpiryDateText = TryExtractExpiryText(x.PrintInputJson)
+ ExpiryDateText = ReportsPrintLogExpiryHelper.ExtractExpiryText(x.PrintInputJson)
};
}
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs
index 5f35724..4d18690 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs
@@ -17,6 +17,8 @@ using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Guids;
using Yi.Framework.Rbac.Domain.Entities;
+using Yi.Framework.Rbac.Domain.Entities.ValueObjects;
+using Yi.Framework.Rbac.Domain.Helpers;
using Yi.Framework.Rbac.Domain.Managers;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -54,7 +56,10 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
var pageSize = input.MaxResultCount;
RefAsync total = 0;
- var query = await BuildFilteredUserQueryAsync(input);
+ var scopeLocationIds = await LocationScopeBindingHelper.ResolveFilteredLocationIdsForListAsync(
+ _dbContext.SqlSugarClient, input.PartnerId, input.GroupId, input.LocationId);
+
+ var query = await BuildFilteredUserQueryAsync(input, scopeLocationIds);
var users = await query
.OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!)
.OrderByDescending(u => u.CreationTime)
@@ -62,8 +67,8 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
var items = await MapUsersToOutputAsync(
users,
- input.LocationId,
- restrictAssignedLocationsToFilter: !string.IsNullOrWhiteSpace(input.LocationId));
+ scopeLocationIds,
+ restrictAssignedLocationsToFilter: scopeLocationIds is not null);
var totalCount = (long)total;
return new PagedResultWithPageDto
@@ -133,11 +138,15 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
{
var mergedLocationIds = await ResolveTeamMemberLocationIdsForSaveAsync(input);
- var user = new UserAggregateRoot(input.UserName.Trim(), input.Password, input.Phone, input.FullName.Trim())
+ var user = new UserAggregateRoot
{
+ UserName = input.UserName.Trim(),
Name = input.FullName.Trim(),
+ Nick = input.FullName.Trim(),
Email = input.Email?.Trim(),
- State = input.State
+ Phone = input.Phone,
+ State = input.State,
+ EncryPassword = new EncryPasswordValueObject(input.Password.Trim())
};
EntityHelper.TrySetId(user, _guidGenerator.Create);
@@ -172,13 +181,22 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
user.Phone = input.Phone;
user.State = input.State;
+ var passwordChanged = false;
if (!string.IsNullOrWhiteSpace(input.Password))
{
- user.EncryPassword.Password = input.Password;
- user.BuildPassword();
+ UserPasswordHelper.ApplyPlainPassword(user, input.Password);
+ passwordChanged = true;
}
await _userRepository.UpdateAsync(user);
+ if (passwordChanged)
+ {
+ await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync(
+ _userRepository,
+ user.Id,
+ user.EncryPassword.Password,
+ user.EncryPassword.Salt);
+ }
if (input.RoleId != null)
{
@@ -254,13 +272,19 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
{
QuestPDF.Settings.License = LicenseType.Community;
- var query = await BuildFilteredUserQueryAsync(input);
+ var scopeLocationIds = await LocationScopeBindingHelper.ResolveFilteredLocationIdsForListAsync(
+ _dbContext.SqlSugarClient, input.PartnerId, input.GroupId, input.LocationId);
+
+ var query = await BuildFilteredUserQueryAsync(input, scopeLocationIds);
var users = await query
.OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!)
.OrderByDescending(u => u.CreationTime)
.ToListAsync();
- var rows = await MapUsersToOutputAsync(users, locationFilter: null, restrictAssignedLocationsToFilter: false);
+ var rows = await MapUsersToOutputAsync(
+ users,
+ scopeLocationIds,
+ restrictAssignedLocationsToFilter: scopeLocationIds is not null);
var fileName = $"team-members_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf";
var document = Document.Create(container =>
@@ -492,7 +516,9 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
return result.Distinct().ToList();
}
- private async Task> BuildFilteredUserQueryAsync(TeamMemberGetListInputVo input)
+ private async Task> BuildFilteredUserQueryAsync(
+ TeamMemberGetListInputVo input,
+ List? scopeLocationIds)
{
var keyword = input.Keyword?.Trim();
var query = _userRepository._DbQueryable
@@ -513,15 +539,22 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
query = query.Where(u => userIds.Contains(u.Id));
}
- if (!string.IsNullOrWhiteSpace(input.LocationId))
+ if (scopeLocationIds is not null)
{
- var locId = input.LocationId.Trim();
- var userIdStrs = await _dbContext.SqlSugarClient.Queryable()
- .Where(x => !x.IsDeleted && x.LocationId == locId)
- .Select(x => x.UserId)
- .ToListAsync();
- var allowed = new HashSet(userIdStrs);
- query = query.Where(u => allowed.Contains(u.Id.ToString()));
+ if (scopeLocationIds.Count == 0)
+ {
+ query = query.Where(_ => false);
+ }
+ else
+ {
+ var scopeSet = new HashSet(scopeLocationIds, StringComparer.Ordinal);
+ var userIdStrs = await _dbContext.SqlSugarClient.Queryable()
+ .Where(x => !x.IsDeleted && scopeSet.Contains(x.LocationId))
+ .Select(x => x.UserId)
+ .ToListAsync();
+ var allowed = new HashSet(userIdStrs);
+ query = query.Where(u => allowed.Contains(u.Id.ToString()));
+ }
}
return query;
@@ -529,7 +562,7 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
private async Task> MapUsersToOutputAsync(
List users,
- string? locationFilter,
+ List? scopeLocationIds,
bool restrictAssignedLocationsToFilter)
{
if (users.Count == 0)
@@ -552,9 +585,10 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService
var userLocQuery = _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted)
.Where(x => userIdStrings.Contains(x.UserId));
- if (restrictAssignedLocationsToFilter && !string.IsNullOrWhiteSpace(locationFilter))
+ if (restrictAssignedLocationsToFilter && scopeLocationIds is { Count: > 0 })
{
- userLocQuery = userLocQuery.Where(x => x.LocationId == locationFilter.Trim());
+ var scopeSet = new HashSet(scopeLocationIds, StringComparer.Ordinal);
+ userLocQuery = userLocQuery.Where(x => scopeSet.Contains(x.LocationId));
}
var userLocations = await userLocQuery.ToListAsync();
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 53aeea3..c2c5aee 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
@@ -25,10 +25,10 @@ using Volo.Abp.EventBus.Local;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using Volo.Abp.Users;
-using Yi.Framework.Core.Helper;
using Yi.Framework.Rbac.Application.Contracts.Dtos.Account;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Entities;
+using Yi.Framework.Rbac.Domain.Helpers;
using Yi.Framework.Rbac.Domain.Managers;
using Yi.Framework.Rbac.Domain.Shared.Consts;
using Yi.Framework.Rbac.Domain.Shared.Dtos;
@@ -102,7 +102,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
throw new UserFriendlyException("登录失败!邮箱不存在!");
}
- if (user.EncryPassword.Password != MD5Helper.SHA2Encode(input.Password, user.EncryPassword.Salt))
+ if (!UserPasswordHelper.VerifyPlainPassword(user, input.Password))
{
throw new UserFriendlyException(UserConst.Login_Error);
}
@@ -158,7 +158,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService
}
///
- /// 查询单个门店详情(Location 页):地址、门店电话、营业时间占位、店长(角色含 manager 的绑定用户)
+ /// 查询单个门店详情(Location 页):地址、门店电话、经营时间、店长(角色含 manager 的绑定用户)
///
///
/// 仅当当前登录用户在 userlocation 中绑定该 locationId 时可查;否则返回业务异常。
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 65a6bad..4237c31 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
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.Dtos.Label;
using FoodLabeling.Application.Contracts.Dtos.LabelTemplate;
+using FoodLabeling.Application.Contracts.Dtos.Reports;
using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling;
using FoodLabeling.Application.Contracts.IServices;
using FoodLabeling.Application.Helpers;
@@ -53,7 +54,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
/// L1 标签分类 fl_label_category(含 buttonAppearance;COLOR/IMAGE 展示值在 categoryPhotoUrl);仅对当前门店可用:ALL 或 SPECIFIED 且在 fl_label_category_location;
/// L2 产品分类 fl_product.CategoryId join fl_product_category(同上,展示值在 categoryPhotoUrl);
/// L3 产品卡片:按「产品 + 标签模板」拆分(同一 productId、不同 fl_label.TemplateId 为多张卡);L4 为该卡下与门店、标签分类、该产品、该模板关联的标签实例(fl_label + fl_label_type)。
- /// L2 仅包含对当前门店可用的类别:AvailabilityType=ALL,或 SPECIFIED 且在 fl_product_category_location 存在该门店记录;
+ /// L2 产品分类展示名来自 fl_product_category;产品范围已由 fl_location_product 限定当前门店,
+ /// 不再因产品分类 SPECIFIED 未配 fl_product_category_location 而整行过滤(避免 App 全店空数据)。
/// 未归类或分类行未关联到 fl_product_category 时仍归入「无」节点。
///
[Authorize]
@@ -68,6 +70,9 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
var keyword = input.Keyword?.Trim();
var filterCategoryId = input.LabelCategoryId?.Trim();
+ await UsAppPrintLogScopeHelper.EnsureUserBoundToLocationAsync(
+ CurrentUser, _dbContext.SqlSugarClient, locationId);
+
var productIds = await _dbContext.SqlSugarClient.Queryable()
.Where(x => x.LocationId == locationId)
.Select(x => x.ProductId)
@@ -624,9 +629,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
throw new UserFriendlyException("打印任务不存在");
}
- // 非 admin:仅允许重打自己在当前门店的任务;admin 可重打任意用户任务(仍须门店一致)
- var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser);
- if (!isAdmin && !string.Equals(old.CreatedBy?.Trim(), currentUserId, StringComparison.OrdinalIgnoreCase))
+ // 管理员 / Partner 角色:可重打当前门店任意用户任务;其它角色仅本人
+ var canViewAll = await UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync(
+ CurrentUser, _dbContext.SqlSugarClient);
+ if (!canViewAll && !string.Equals(old.CreatedBy?.Trim(), currentUserId, StringComparison.OrdinalIgnoreCase))
{
throw new UserFriendlyException("无权限重打该任务");
}
@@ -726,13 +732,14 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
}
///
- /// App 打印日志:获取当前登录账号在当前门店打印的记录(分页,时间倒序)
+ /// App 打印日志:当前门店打印记录(分页,时间倒序)
///
///
- /// 仅返回满足:
- /// - CreatedBy == CurrentUser.Id
- /// - LocationId == input.LocationId
- /// 的打印任务记录(fl_label_print_task)。
+ /// 数据范围(须已绑定 input.locationId):
+ ///
+ /// - 管理员()或角色码/名含 partner:该门店 全部 打印任务;
+ /// - 其它角色:仅 CreatedBy == CurrentUser.Id。
+ ///
///
/// 示例请求:
/// ```json
@@ -774,9 +781,12 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
}
var currentUserIdStr = CurrentUser.Id.Value.ToString();
+ await UsAppPrintLogScopeHelper.EnsureUserBoundToLocationAsync(
+ CurrentUser, _dbContext.SqlSugarClient, locationId);
- var currentUser = await _userRepository.GetByIdAsync(CurrentUser.Id.Value);
- var operatorName = currentUser?.Name?.Trim() ?? string.Empty;
+ var canViewAll = await UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync(
+ CurrentUser, _dbContext.SqlSugarClient);
+ var restrictToCreator = !canViewAll;
var locationName = "无";
if (Guid.TryParse(locationId, out var locationGuid))
@@ -797,13 +807,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
RefAsync total = 0;
- var query = _dbContext.SqlSugarClient
- .Queryable()
- .LeftJoin((t, l) => t.LabelId == l.Id)
- .LeftJoin((t, l, p) => t.ProductId == p.Id)
- .LeftJoin((t, l, p, lt) => t.LabelTypeId == lt.Id)
- .LeftJoin((t, l, p, lt, tpl) => t.TemplateId == tpl.Id)
- .Where((t, l, p, lt, tpl) => t.CreatedBy == currentUserIdStr && t.LocationId == locationId)
+ var query = UsAppPrintLogScopeHelper.BuildLocationPrintTaskQuery(
+ _dbContext.SqlSugarClient, locationId, restrictToCreator, currentUserIdStr)
.OrderBy((t, l, p, lt, tpl) => SqlFunc.IsNull(t.PrintedAt, t.CreationTime), OrderByType.Desc)
.OrderBy((t, l, p, lt, tpl) => t.CreationTime, OrderByType.Desc)
.Select((t, l, p, lt, tpl) => new
@@ -821,11 +826,16 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
TemplateUnit = tpl.Unit,
t.PrintInputJson,
t.PrintedAt,
- t.CreationTime
+ t.CreationTime,
+ t.CreatedBy
});
var pageRows = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
+ var operatorMap = await UsAppPrintLogScopeHelper.LoadOperatorNameMapAsync(
+ _dbContext.SqlSugarClient,
+ pageRows.Select(x => x.CreatedBy));
+
var items = pageRows.Select(x => new PrintLogItemDto
{
TaskId = x.Id,
@@ -839,7 +849,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
LabelSizeText = FormatLabelSizeWithUnit(x.TemplateWidth, x.TemplateHeight, x.TemplateUnit),
PrintInputJson = x.PrintInputJson,
PrintedAt = x.PrintedAt ?? x.CreationTime,
- OperatorName = operatorName,
+ OperatorName = UsAppPrintLogScopeHelper.ResolveOperatorName(operatorMap, x.CreatedBy),
LocationName = locationName
}).ToList();
@@ -858,6 +868,204 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
};
}
+ ///
+ /// App Label Report:当前门店打印统计(权限与 一致)
+ ///
+ ///
+ /// 示例:POST /api/app/us-app-labeling/get-label-report
+ /// ```json
+ /// { "locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8", "startDate": "2026-04-07", "endDate": "2026-05-18" }
+ /// ```
+ ///
+ [Authorize]
+ [HttpPost]
+ public virtual async Task GetLabelReportAsync(UsAppLabelReportQueryInputVo input)
+ {
+ if (input is null)
+ {
+ throw new UserFriendlyException("入参不能为空");
+ }
+
+ if (!CurrentUser.Id.HasValue)
+ {
+ throw new UserFriendlyException("用户未登录");
+ }
+
+ var locationId = input.LocationId?.Trim();
+ if (string.IsNullOrWhiteSpace(locationId))
+ {
+ throw new UserFriendlyException("门店Id不能为空");
+ }
+
+ await UsAppPrintLogScopeHelper.EnsureUserBoundToLocationAsync(
+ CurrentUser, _dbContext.SqlSugarClient, locationId);
+
+ var canViewAll = await UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync(
+ CurrentUser, _dbContext.SqlSugarClient);
+ var restrictToCreator = !canViewAll;
+ var currentUserIdStr = CurrentUser.Id.Value.ToString();
+ var keyword = input.Keyword?.Trim();
+
+ var (curStart, curEndExcl) = ResolveAppDateRange(input.StartDate, input.EndDate);
+ var span = curEndExcl - curStart;
+ if (span.TotalDays < 1)
+ {
+ span = TimeSpan.FromDays(1);
+ }
+
+ var prevEndExcl = curStart;
+ var prevStart = curStart - span;
+ var db = _dbContext.SqlSugarClient;
+
+ ISugarQueryable Core() =>
+ UsAppPrintLogScopeHelper.BuildLocationPrintTaskReportQuery(
+ db, locationId, restrictToCreator, currentUserIdStr, keyword);
+
+ var totalCur = await Core()
+ .Where((t, l, p, lc, pc, lt, tpl) =>
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl)
+ .CountAsync();
+
+ var totalPrev = await Core()
+ .Where((t, l, p, lc, pc, lt, tpl) =>
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= prevStart &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < prevEndExcl)
+ .CountAsync();
+
+ var dayCount = Math.Max(1, (int)Math.Ceiling((curEndExcl - curStart).TotalDays));
+ var prevDayCount = Math.Max(1, (int)Math.Ceiling((prevEndExcl - prevStart).TotalDays));
+ var avgDaily = Math.Round((decimal)totalCur / dayCount, 2);
+ var avgDailyPrev = Math.Round((decimal)totalPrev / prevDayCount, 2);
+
+ var categoryRows = await Core()
+ .Where((t, l, p, lc, pc, lt, tpl) =>
+ l.LabelCategoryId != null &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl)
+ .GroupBy((t, l, p, lc, pc, lt, tpl) => new { lc.Id, lc.CategoryName })
+ .Select((t, l, p, lc, pc, lt, tpl) => new { lc.Id, lc.CategoryName, Cnt = SqlFunc.AggregateCount(t.Id) })
+ .ToListAsync();
+
+ var topCat = categoryRows.OrderByDescending(x => x.Cnt).FirstOrDefault();
+
+ var productRows = await Core()
+ .Where((t, l, p, lc, pc, lt, tpl) =>
+ !string.IsNullOrEmpty(p.Id) &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl)
+ .GroupBy((t, l, p, lc, pc, lt, tpl) => new { p.Id, p.ProductName, Cat = pc.CategoryName })
+ .Select((t, l, p, lc, pc, lt, tpl) => new
+ {
+ p.Id,
+ p.ProductName,
+ CategoryName = pc.CategoryName,
+ Cnt = SqlFunc.AggregateCount(t.Id)
+ })
+ .ToListAsync();
+
+ var topProd = productRows.OrderByDescending(x => x.Cnt).FirstOrDefault();
+ var topList = productRows.OrderByDescending(x => x.Cnt).Take(20).ToList();
+
+ var trendEndDay = curEndExcl.Date.AddDays(-1);
+ var trendStartDay = trendEndDay.AddDays(-6);
+ if (trendStartDay < curStart.Date)
+ {
+ trendStartDay = curStart.Date;
+ }
+
+ var trendEndExcl = trendEndDay.AddDays(1);
+
+ var trendRaw = await Core()
+ .Where((t, l, p, lc, pc, lt, tpl) =>
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= trendStartDay &&
+ SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < trendEndExcl)
+ .Select((t, l, p, lc, pc, lt, tpl) => SqlFunc.IsNull(t.PrintedAt, t.CreationTime))
+ .ToListAsync();
+
+ var trendDict = trendRaw
+ .Where(x => x.HasValue)
+ .GroupBy(x => x!.Value.Date)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ var trend = new List();
+ for (var d = trendStartDay; d <= trendEndDay; d = d.AddDays(1))
+ {
+ trend.Add(new ReportsDailyCountDto
+ {
+ Date = d.ToString("yyyy-MM-dd"),
+ Count = trendDict.TryGetValue(d, out var c) ? c : 0
+ });
+ }
+
+ var byCategory = categoryRows
+ .OrderByDescending(x => x.Cnt)
+ .Select(x => new ReportsCategoryCountDto
+ {
+ CategoryId = string.IsNullOrWhiteSpace(x.Id) ? null : x.Id.Trim(),
+ CategoryName = string.IsNullOrWhiteSpace(x.CategoryName) ? null : x.CategoryName.Trim(),
+ Count = x.Cnt
+ })
+ .ToList();
+
+ var mostUsed = topList.Select(x =>
+ {
+ var pct = totalCur <= 0 ? 0m : Math.Round(x.Cnt * 100m / totalCur, 2);
+ return new ReportsTopProductRowDto
+ {
+ ProductId = string.IsNullOrWhiteSpace(x.Id) ? null : x.Id.Trim(),
+ ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? null : x.ProductName.Trim(),
+ CategoryName = string.IsNullOrWhiteSpace(x.CategoryName) ? null : x.CategoryName!.Trim(),
+ TotalPrinted = x.Cnt,
+ UsagePercent = pct
+ };
+ }).ToList();
+
+ return new ReportsLabelReportOutputDto
+ {
+ Summary = new ReportsLabelReportSummaryDto
+ {
+ TotalLabelsPrinted = totalCur,
+ TotalLabelsPrintedPrevPeriod = totalPrev,
+ TotalLabelsPrintedChangeRate = CalcAppChangeRate(totalCur, totalPrev),
+ MostPrintedCategoryName = string.IsNullOrWhiteSpace(topCat?.CategoryName) ? null : topCat.CategoryName.Trim(),
+ MostPrintedCategoryCount = topCat?.Cnt ?? 0,
+ TopProductName = string.IsNullOrWhiteSpace(topProd?.ProductName) ? null : topProd.ProductName.Trim(),
+ TopProductCount = topProd?.Cnt ?? 0,
+ AvgDailyPrints = avgDaily,
+ AvgDailyPrintsPrevPeriod = avgDailyPrev,
+ AvgDailyPrintsChangeRate = CalcAppChangeRate(avgDaily, avgDailyPrev)
+ },
+ LabelsByCategory = byCategory,
+ PrintVolumeTrend = trend,
+ MostUsedProducts = mostUsed
+ };
+ }
+
+ private static (DateTime rangeStart, DateTime rangeEndExcl) ResolveAppDateRange(DateTime? startDate, DateTime? endDate)
+ {
+ var endDay = (endDate ?? DateTime.Today).Date;
+ var endExcl = endDay.AddDays(1);
+ var start = (startDate ?? endDay.AddDays(-29)).Date;
+ if (start >= endExcl)
+ {
+ start = endExcl.AddDays(-1);
+ }
+
+ return (start, endExcl);
+ }
+
+ private static decimal CalcAppChangeRate(decimal current, decimal previous)
+ {
+ if (previous == 0)
+ {
+ return current > 0 ? 100m : 0m;
+ }
+
+ return Math.Round((current - previous) * 100m / previous, 2);
+ }
+
private async Task ResolveTemplateProductDefaultValuesJsonAsync(
string templateId,
string? productId,
@@ -896,23 +1104,11 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
.Where((lp, l, p, c, t, tpl, pc) => l.LocationId == locationId)
.Where((lp, l, p, c, t, tpl, pc) => !l.IsDeleted && l.State)
.Where((lp, l, p, c, t, tpl, pc) => !p.IsDeleted && p.State)
- .Where((lp, l, p, c, t, tpl, pc) =>
- !c.IsDeleted && c.State &&
- (c.AvailabilityType == "ALL" ||
- (c.AvailabilityType == "SPECIFIED" &&
- SqlFunc.Subqueryable()
- .Where(loc => loc.CategoryId == c.Id && loc.LocationId == locationId)
- .Any())))
+ .Where((lp, l, p, c, t, tpl, pc) => !c.IsDeleted && c.State)
.Where((lp, l, p, c, t, tpl, pc) => !t.IsDeleted && t.State)
.Where((lp, l, p, c, t, tpl, pc) => !tpl.IsDeleted)
.Where((lp, l, p, c, t, tpl, pc) =>
- pc.Id == null ||
- (!pc.IsDeleted && pc.State &&
- (pc.AvailabilityType == "ALL" ||
- (pc.AvailabilityType == "SPECIFIED" &&
- SqlFunc.Subqueryable()
- .Where(loc => loc.CategoryId == pc.Id && loc.LocationId == locationId)
- .Any()))))
+ pc.Id == null || (!pc.IsDeleted && pc.State))
.WhereIF(!string.IsNullOrWhiteSpace(filterCategoryId), (lp, l, p, c, t, tpl, pc) => l.LabelCategoryId == filterCategoryId)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), (lp, l, p, c, t, tpl, pc) =>
(l.LabelName != null && l.LabelName.Contains(keyword!)) ||
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/System/UserService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/System/UserService.cs
index 51146ff..d6c4072 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/System/UserService.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/System/UserService.cs
@@ -9,6 +9,7 @@ using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Entities;
+using Yi.Framework.Rbac.Domain.Helpers;
using Yi.Framework.Rbac.Domain.Entities.ValueObjects;
using Yi.Framework.Rbac.Domain.Managers;
using Yi.Framework.Rbac.Domain.Repositories;
@@ -155,15 +156,21 @@ namespace Yi.Framework.Rbac.Application.Services.System
var entity = await _repository.GetByIdAsync(id);
//更新密码,特殊处理
+ var passwordChanged = false;
if (!string.IsNullOrWhiteSpace(input.Password))
{
- entity.EncryPassword.Password = input.Password;
- entity.BuildPassword();
+ UserPasswordHelper.ApplyPlainPassword(entity, input.Password);
+ passwordChanged = true;
}
await MapToEntityAsync(input, entity);
var res1 = await _repository.UpdateAsync(entity);
+ if (passwordChanged)
+ {
+ await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync(
+ _repository, id, entity.EncryPassword.Password, entity.EncryPassword.Salt);
+ }
await _userManager.GiveUserSetRoleAsync(new List { id }, input.RoleIds);
await _userManager.GiveUserSetPostAsync(new List { id }, input.PostIds);
return await MapToGetOutputDtoAsync(entity);
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/UserAggregateRoot.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/UserAggregateRoot.cs
index 3f00e04..0f5c0b2 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/UserAggregateRoot.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/UserAggregateRoot.cs
@@ -4,6 +4,7 @@ using Volo.Abp.Domain.Entities;
using Yi.Framework.Core.Data;
using Yi.Framework.Core.Helper;
using Yi.Framework.Rbac.Domain.Entities.ValueObjects;
+using Yi.Framework.Rbac.Domain.Helpers;
using Yi.Framework.Rbac.Domain.Shared.Enums;
namespace Yi.Framework.Rbac.Domain.Entities
@@ -197,19 +198,8 @@ namespace Yi.Framework.Rbac.Domain.Entities
///
///
///
- public bool JudgePassword(string password)
- {
- if (EncryPassword.Salt is null)
- {
- throw new ArgumentNullException(EncryPassword.Salt);
- }
- var p = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
- if (EncryPassword.Password == MD5Helper.SHA2Encode(password, EncryPassword.Salt))
- {
- return true;
- }
- return false;
- }
+ public bool JudgePassword(string password) =>
+ UserPasswordHelper.VerifyPlainPassword(this, password);
}
diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs
index 1a9193b..2a766a1 100644
--- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs
+++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Managers/AccountManager.cs
@@ -13,6 +13,7 @@ using Volo.Abp.EventBus.Local;
using Volo.Abp.Security.Claims;
using Yi.Framework.Core.Helper;
using Yi.Framework.Rbac.Domain.Entities;
+using Yi.Framework.Rbac.Domain.Helpers;
using Yi.Framework.Rbac.Domain.Repositories;
using Yi.Framework.Rbac.Domain.Shared.Caches;
using Yi.Framework.Rbac.Domain.Shared.Consts;
@@ -244,9 +245,10 @@ namespace Yi.Framework.Rbac.Domain.Managers
{
throw new UserFriendlyException("无效更新!原密码错误!");
}
- user.EncryPassword.Password = newPassword;
- user.BuildPassword();
+ UserPasswordHelper.ApplyPlainPassword(user, newPassword);
await _repository.UpdateAsync(user);
+ await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync(
+ _repository, userId, user.EncryPassword.Password, user.EncryPassword.Salt);
}
///
@@ -258,9 +260,11 @@ namespace Yi.Framework.Rbac.Domain.Managers
public async Task RestPasswordAsync(Guid userId, string password)
{
var user = await _repository.GetByIdAsync(userId);
- user.EncryPassword.Password = password;
- user.BuildPassword();
- return await _repository.UpdateAsync(user);
+ UserPasswordHelper.ApplyPlainPassword(user, password);
+ var updated = await _repository.UpdateAsync(user);
+ await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync(
+ _repository, userId, user.EncryPassword.Password, user.EncryPassword.Salt);
+ return updated;
}
///
--
libgit2 0.21.4