Commit 07d5dea2be04f05deb7e8bca2f1069de034600ab
1 parent
1aa1dca9
5-18代码优化
Showing
30 changed files
with
1360 additions
and
223 deletions
美国版/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 | @@ -10,6 +10,26 @@ public class LabelMultipleOptionCreateInputVo | ||
| 10 | 10 | ||
| 11 | public bool State { get; set; } = true; | 11 | public bool State { get; set; } = true; |
| 12 | 12 | ||
| 13 | + /// <summary> | ||
| 14 | + /// 门店可用范围:ALL / SPECIFIED;传了 <see cref="RegionIds"/> 或 <see cref="LocationIds"/> 时自动为 SPECIFIED | ||
| 15 | + /// </summary> | ||
| 16 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 17 | + | ||
| 18 | + /// <summary> | ||
| 19 | + /// 适用 Region(多选),<c>fl_group.Id</c>;与 <see cref="GroupIds"/> 合并去重 | ||
| 20 | + /// </summary> | ||
| 21 | + public List<string>? RegionIds { get; set; } | ||
| 22 | + | ||
| 23 | + /// <summary> | ||
| 24 | + /// 适用 Region(多选),与 <see cref="RegionIds"/> 相同 | ||
| 25 | + /// </summary> | ||
| 26 | + public List<string>? GroupIds { get; set; } | ||
| 27 | + | ||
| 28 | + /// <summary> | ||
| 29 | + /// 适用门店(多选),<c>location.Id</c>;与 Region 合并后写入 <c>fl_label_multiple_option_location</c> | ||
| 30 | + /// </summary> | ||
| 31 | + public List<string>? LocationIds { get; set; } | ||
| 32 | + | ||
| 13 | public int OrderNum { get; set; } | 33 | public int OrderNum { get; set; } |
| 14 | } | 34 | } |
| 15 | 35 |
美国版/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 | @@ -7,5 +7,15 @@ public class LabelMultipleOptionGetListInputVo : PagedAndSortedResultRequestDto | ||
| 7 | public string? Keyword { get; set; } | 7 | public string? Keyword { get; set; } |
| 8 | 8 | ||
| 9 | public bool? State { get; set; } | 9 | public bool? State { get; set; } |
| 10 | + | ||
| 11 | + /// <summary> | ||
| 12 | + /// Region 筛选(<c>fl_group.Id</c>) | ||
| 13 | + /// </summary> | ||
| 14 | + public string? GroupId { get; set; } | ||
| 15 | + | ||
| 16 | + /// <summary> | ||
| 17 | + /// 门店筛选(<c>location.Id</c>);优先于 <see cref="GroupId"/> | ||
| 18 | + /// </summary> | ||
| 19 | + public string? LocationId { get; set; } | ||
| 10 | } | 20 | } |
| 11 | 21 |
美国版/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 | @@ -12,8 +12,20 @@ public class LabelMultipleOptionGetListOutputDto | ||
| 12 | 12 | ||
| 13 | public bool State { get; set; } | 13 | public bool State { get; set; } |
| 14 | 14 | ||
| 15 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 16 | + | ||
| 15 | public int OrderNum { get; set; } | 17 | public int OrderNum { get; set; } |
| 16 | 18 | ||
| 17 | public DateTime LastEdited { get; set; } | 19 | public DateTime LastEdited { get; set; } |
| 20 | + | ||
| 21 | + /// <summary>列表列 Region</summary> | ||
| 22 | + public string Region { get; set; } = string.Empty; | ||
| 23 | + | ||
| 24 | + /// <summary>列表列 Location</summary> | ||
| 25 | + public string Location { get; set; } = string.Empty; | ||
| 26 | + | ||
| 27 | + public List<string> RegionIds { get; set; } = new(); | ||
| 28 | + | ||
| 29 | + public List<string> LocationIds { get; set; } = new(); | ||
| 18 | } | 30 | } |
| 19 | 31 |
美国版/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 | @@ -13,5 +13,13 @@ public class LabelMultipleOptionGetOutputDto | ||
| 13 | public bool State { get; set; } | 13 | public bool State { get; set; } |
| 14 | 14 | ||
| 15 | public int OrderNum { get; set; } | 15 | public int OrderNum { get; set; } |
| 16 | + | ||
| 17 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 18 | + | ||
| 19 | + public List<string> RegionIds { get; set; } = new(); | ||
| 20 | + | ||
| 21 | + public List<string> GroupIds { get; set; } = new(); | ||
| 22 | + | ||
| 23 | + public List<string> LocationIds { get; set; } = new(); | ||
| 16 | } | 24 | } |
| 17 | 25 |
美国版/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 | @@ -8,6 +8,26 @@ public class LabelTypeCreateInputVo | ||
| 8 | 8 | ||
| 9 | public bool State { get; set; } = true; | 9 | public bool State { get; set; } = true; |
| 10 | 10 | ||
| 11 | + /// <summary> | ||
| 12 | + /// 门店可用范围:ALL / SPECIFIED;传了 <see cref="RegionIds"/> 或 <see cref="LocationIds"/> 时自动为 SPECIFIED | ||
| 13 | + /// </summary> | ||
| 14 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 15 | + | ||
| 16 | + /// <summary> | ||
| 17 | + /// 适用 Region(多选),<c>fl_group.Id</c>;与 <see cref="GroupIds"/> 合并去重 | ||
| 18 | + /// </summary> | ||
| 19 | + public List<string>? RegionIds { get; set; } | ||
| 20 | + | ||
| 21 | + /// <summary> | ||
| 22 | + /// 适用 Region(多选),与 <see cref="RegionIds"/> 相同 | ||
| 23 | + /// </summary> | ||
| 24 | + public List<string>? GroupIds { get; set; } | ||
| 25 | + | ||
| 26 | + /// <summary> | ||
| 27 | + /// 适用门店(多选),<c>location.Id</c>;与 Region 合并后写入 <c>fl_label_type_location</c> | ||
| 28 | + /// </summary> | ||
| 29 | + public List<string>? LocationIds { get; set; } | ||
| 30 | + | ||
| 11 | public int OrderNum { get; set; } | 31 | public int OrderNum { get; set; } |
| 12 | } | 32 | } |
| 13 | 33 |
美国版/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 | @@ -7,5 +7,15 @@ public class LabelTypeGetListInputVo : PagedAndSortedResultRequestDto | ||
| 7 | public string? Keyword { get; set; } | 7 | public string? Keyword { get; set; } |
| 8 | 8 | ||
| 9 | public bool? State { get; set; } | 9 | public bool? State { get; set; } |
| 10 | + | ||
| 11 | + /// <summary> | ||
| 12 | + /// Region 筛选(<c>fl_group.Id</c>);仅返回在该 Region 下存在标签实例的类型 | ||
| 13 | + /// </summary> | ||
| 14 | + public string? GroupId { get; set; } | ||
| 15 | + | ||
| 16 | + /// <summary> | ||
| 17 | + /// 门店筛选(<c>location.Id</c>);优先于 <see cref="GroupId"/> | ||
| 18 | + /// </summary> | ||
| 19 | + public string? LocationId { get; set; } | ||
| 10 | } | 20 | } |
| 11 | 21 |
美国版/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 | @@ -10,10 +10,25 @@ public class LabelTypeGetListOutputDto | ||
| 10 | 10 | ||
| 11 | public bool State { get; set; } | 11 | public bool State { get; set; } |
| 12 | 12 | ||
| 13 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 14 | + | ||
| 13 | public int OrderNum { get; set; } | 15 | public int OrderNum { get; set; } |
| 14 | 16 | ||
| 17 | + /// <summary>列表列 No. of Labels:该类型下未删除标签数(统计 <c>fl_label</c>,非库表物理列)</summary> | ||
| 15 | public long NoOfLabels { get; set; } | 18 | public long NoOfLabels { get; set; } |
| 16 | 19 | ||
| 17 | public DateTime LastEdited { get; set; } | 20 | public DateTime LastEdited { get; set; } |
| 21 | + | ||
| 22 | + /// <summary>列表列 Region:由该类型下标签绑定的门店 <c>GroupName</c> 去重拼接</summary> | ||
| 23 | + public string Region { get; set; } = string.Empty; | ||
| 24 | + | ||
| 25 | + /// <summary>列表列 Location:由该类型下标签绑定的门店名去重拼接</summary> | ||
| 26 | + public string Location { get; set; } = string.Empty; | ||
| 27 | + | ||
| 28 | + /// <summary>Region Id(<c>fl_group.Id</c>,由标签门店反推)</summary> | ||
| 29 | + public List<string> RegionIds { get; set; } = new(); | ||
| 30 | + | ||
| 31 | + /// <summary>门店 Id(<c>location.Id</c>,由该类型下标签汇总)</summary> | ||
| 32 | + public List<string> LocationIds { get; set; } = new(); | ||
| 18 | } | 33 | } |
| 19 | 34 |
美国版/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 | @@ -11,5 +11,13 @@ public class LabelTypeGetOutputDto | ||
| 11 | public bool State { get; set; } | 11 | public bool State { get; set; } |
| 12 | 12 | ||
| 13 | public int OrderNum { get; set; } | 13 | public int OrderNum { get; set; } |
| 14 | + | ||
| 15 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 16 | + | ||
| 17 | + public List<string> RegionIds { get; set; } = new(); | ||
| 18 | + | ||
| 19 | + public List<string> GroupIds { get; set; } = new(); | ||
| 20 | + | ||
| 21 | + public List<string> LocationIds { get; set; } = new(); | ||
| 14 | } | 22 | } |
| 15 | 23 |
美国版/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 | @@ -8,7 +8,9 @@ public class ReportsPrintLogListItemDto | ||
| 8 | /// <summary>打印任务 Id(fl_label_print_task.Id),重打时使用</summary> | 8 | /// <summary>打印任务 Id(fl_label_print_task.Id),重打时使用</summary> |
| 9 | public string TaskId { get; set; } = string.Empty; | 9 | public string TaskId { get; set; } = string.Empty; |
| 10 | 10 | ||
| 11 | - /// <summary>标签编码(展示为 Label ID)</summary> | 11 | + /// <summary> |
| 12 | + /// 列表列 Label ID:门店当日打印序号(<c>yyyyMMdd-n</c>),非 <c>fl_label.LabelCode</c> | ||
| 13 | + /// </summary> | ||
| 12 | public string LabelCode { get; set; } = string.Empty; | 14 | public string LabelCode { get; set; } = string.Empty; |
| 13 | 15 | ||
| 14 | public string ProductName { get; set; } = "无"; | 16 | public string ProductName { get; set; } = "无"; |
美国版/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 | @@ -18,7 +18,17 @@ public class TeamMemberGetListInputVo : PagedAndSortedResultRequestDto | ||
| 18 | public Guid? RoleId { get; set; } | 18 | public Guid? RoleId { get; set; } |
| 19 | 19 | ||
| 20 | /// <summary> | 20 | /// <summary> |
| 21 | - /// 门店ID(可选) | 21 | + /// Company 筛选(<c>fl_partner.Id</c>);与 <see cref="GroupId"/>、<see cref="LocationId"/> 按「门店优先」解析范围 |
| 22 | + /// </summary> | ||
| 23 | + public string? PartnerId { get; set; } | ||
| 24 | + | ||
| 25 | + /// <summary> | ||
| 26 | + /// Region 筛选(<c>fl_group.Id</c>);未传 <see cref="LocationId"/> 时生效 | ||
| 27 | + /// </summary> | ||
| 28 | + public string? GroupId { get; set; } | ||
| 29 | + | ||
| 30 | + /// <summary> | ||
| 31 | + /// 门店筛选(<c>location.Id</c>);最精确,传则忽略 <see cref="PartnerId"/> / <see cref="GroupId"/> | ||
| 22 | /// </summary> | 32 | /// </summary> |
| 23 | public string? LocationId { get; set; } | 33 | public string? LocationId { get; set; } |
| 24 | 34 |
美国版/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 | @@ -17,7 +17,7 @@ public class UsAppLocationDetailOutputDto | ||
| 17 | /// <summary>门店电话(来自 location.Phone;空为「无」)</summary> | 17 | /// <summary>门店电话(来自 location.Phone;空为「无」)</summary> |
| 18 | public string StorePhone { get; set; } = string.Empty; | 18 | public string StorePhone { get; set; } = string.Empty; |
| 19 | 19 | ||
| 20 | - /// <summary>营业时间;当前库无字段,固定返回「无」直至业务落库</summary> | 20 | + /// <summary>经营时间(<c>location.OperatingHours</c> 自由文本);库空或未维护时为「无」</summary> |
| 21 | public string OperatingHours { get; set; } = string.Empty; | 21 | public string OperatingHours { get; set; } = string.Empty; |
| 22 | 22 | ||
| 23 | /// <summary>店长姓名;优先取绑定本店且角色名/编码含 manager 的用户</summary> | 23 | /// <summary>店长姓名;优先取绑定本店且角色名/编码含 manager 的用户</summary> |
美国版/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 | @@ -38,7 +38,7 @@ public class PrintLogItemDto | ||
| 38 | /// <summary>打印时间(PrintedAt ?? CreationTime)</summary> | 38 | /// <summary>打印时间(PrintedAt ?? CreationTime)</summary> |
| 39 | public DateTime PrintedAt { get; set; } | 39 | public DateTime PrintedAt { get; set; } |
| 40 | 40 | ||
| 41 | - /// <summary>操作人姓名(当前登录账号 Name)</summary> | 41 | + /// <summary>操作人姓名(任务 CreatedBy 对应用户;查看全店日志时为实际打印人)</summary> |
| 42 | public string OperatorName { get; set; } = string.Empty; | 42 | public string OperatorName { get; set; } = string.Empty; |
| 43 | 43 | ||
| 44 | /// <summary>门店名称</summary> | 44 | /// <summary>门店名称</summary> |
美国版/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; | @@ -12,7 +12,7 @@ namespace FoodLabeling.Application.Contracts.IServices; | ||
| 12 | public interface ILocationAppService : IApplicationService | 12 | public interface ILocationAppService : IApplicationService |
| 13 | { | 13 | { |
| 14 | /// <summary> | 14 | /// <summary> |
| 15 | - /// 门店分页列表 | 15 | + /// 门店分页列表;管理员返回全部门店,非管理员仅返回其绑定门店所属 Region(Partner + GroupName)下的门店。 |
| 16 | /// </summary> | 16 | /// </summary> |
| 17 | /// <param name="input">查询条件</param> | 17 | /// <param name="input">查询条件</param> |
| 18 | Task<PagedResultWithPageDto<LocationGetListOutputDto>> GetListAsync(LocationGetListInputVo input); | 18 | Task<PagedResultWithPageDto<LocationGetListOutputDto>> GetListAsync(LocationGetListInputVo input); |
美国版/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 | @@ -32,7 +32,8 @@ public interface IReportsAppService : IApplicationService | ||
| 32 | Task<UsAppLabelPrintOutputDto> ReprintPrintLogAsync(UsAppLabelReprintInputVo input); | 32 | Task<UsAppLabelPrintOutputDto> ReprintPrintLogAsync(UsAppLabelReprintInputVo input); |
| 33 | 33 | ||
| 34 | /// <summary> | 34 | /// <summary> |
| 35 | - /// Label Report 统计(卡片 + 分类柱数据 + 7 日趋势 + Top 产品);<c>admin</c> 统计全部,否则仅当前用户。 | 35 | + /// Label Report 统计(卡片 + 分类柱数据 + 7 日趋势 + Top 产品); |
| 36 | + /// <c>admin</c> 统计全部门店;非管理员仅统计 <c>userlocation</c> 绑定门店内全部打印任务(不按 CreatedBy 过滤)。 | ||
| 36 | /// </summary> | 37 | /// </summary> |
| 37 | Task<ReportsLabelReportOutputDto> GetLabelReportAsync(ReportsLabelReportQueryInputVo input); | 38 | Task<ReportsLabelReportOutputDto> GetLabelReportAsync(ReportsLabelReportQueryInputVo input); |
| 38 | 39 | ||
| @@ -40,4 +41,32 @@ public interface IReportsAppService : IApplicationService | @@ -40,4 +41,32 @@ public interface IReportsAppService : IApplicationService | ||
| 40 | /// Label Report 导出 PDF | 41 | /// Label Report 导出 PDF |
| 41 | /// </summary> | 42 | /// </summary> |
| 42 | Task<IActionResult> ExportLabelReportPdfAsync(ReportsLabelReportQueryInputVo input); | 43 | Task<IActionResult> ExportLabelReportPdfAsync(ReportsLabelReportQueryInputVo input); |
| 44 | + | ||
| 45 | + /// <summary> | ||
| 46 | + /// 按模板统计打印标签数量(分页列表:模板名称 + 打印数)。 | ||
| 47 | + /// </summary> | ||
| 48 | + /// <remarks> | ||
| 49 | + /// 统计 <c>fl_label_print_task</c>,按 <c>TemplateId</c> 分组;数据范围与 <c>label-report</c> 一致: | ||
| 50 | + /// 管理员可查全部门店(可按 Company/Region/Location 收窄);非管理员仅 <c>userlocation</c> 绑定门店内全部打印任务。 | ||
| 51 | + /// | ||
| 52 | + /// 示例请求: | ||
| 53 | + /// ```http | ||
| 54 | + /// GET /api/app/reports/template-print-stat-list?SkipCount=1&MaxResultCount=20&StartDate=2026-04-07&EndDate=2026-05-18 | ||
| 55 | + /// Authorization: Bearer {token} | ||
| 56 | + /// ``` | ||
| 57 | + /// | ||
| 58 | + /// 参数说明: | ||
| 59 | + /// - SkipCount / MaxResultCount: 分页(SkipCount 为 1-based 页码约定,与项目其它列表一致) | ||
| 60 | + /// - StartDate / EndDate: 统计区间(含起止日;默认近 30 天至今天) | ||
| 61 | + /// - PartnerId / GroupId / LocationId: 组织范围筛选 | ||
| 62 | + /// - Keyword: 模板名称模糊匹配 | ||
| 63 | + /// - Sorting: 可选 <c>PrintedCount desc</c>(默认按打印数降序) | ||
| 64 | + /// </remarks> | ||
| 65 | + /// <param name="input">分页与筛选条件</param> | ||
| 66 | + /// <returns>各模板打印数量列表</returns> | ||
| 67 | + /// <response code="200">成功返回分页统计</response> | ||
| 68 | + /// <response code="400">入参无效或未登录</response> | ||
| 69 | + /// <response code="500">服务器错误</response> | ||
| 70 | + Task<PagedResultWithPageDto<ReportsTemplatePrintStatListItemDto>> GetTemplatePrintStatListAsync( | ||
| 71 | + ReportsTemplatePrintStatGetListInputVo input); | ||
| 43 | } | 72 | } |
美国版/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 | @@ -44,7 +44,7 @@ public interface IUsAppAuthAppService : IApplicationService | ||
| 44 | /// 按门店 Id 查询 Location 详情(须为当前账号 userlocation 绑定门店) | 44 | /// 按门店 Id 查询 Location 详情(须为当前账号 userlocation 绑定门店) |
| 45 | /// </summary> | 45 | /// </summary> |
| 46 | /// <param name="locationId">门店 Guid 字符串</param> | 46 | /// <param name="locationId">门店 Guid 字符串</param> |
| 47 | - /// <returns>店名、地址、电话、营业时间占位、店长信息</returns> | 47 | + /// <returns>店名、地址、电话、经营时间(operatingHours)、店长信息</returns> |
| 48 | /// <response code="200">成功</response> | 48 | /// <response code="200">成功</response> |
| 49 | /// <response code="400">参数非法、未绑定或无权限</response> | 49 | /// <response code="400">参数非法、未绑定或无权限</response> |
| 50 | /// <response code="500">服务器错误</response> | 50 | /// <response code="500">服务器错误</response> |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs
| 1 | using FoodLabeling.Application.Contracts.Dtos.Common; | 1 | using FoodLabeling.Application.Contracts.Dtos.Common; |
| 2 | +using FoodLabeling.Application.Contracts.Dtos.Reports; | ||
| 2 | using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; | 3 | using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; |
| 3 | using Volo.Abp.Application.Services; | 4 | using Volo.Abp.Application.Services; |
| 4 | 5 | ||
| @@ -30,7 +31,12 @@ public interface IUsAppLabelingAppService : IApplicationService | @@ -30,7 +31,12 @@ public interface IUsAppLabelingAppService : IApplicationService | ||
| 30 | Task<UsAppLabelPrintOutputDto> ReprintAsync(UsAppLabelReprintInputVo input); | 31 | Task<UsAppLabelPrintOutputDto> ReprintAsync(UsAppLabelReprintInputVo input); |
| 31 | 32 | ||
| 32 | /// <summary> | 33 | /// <summary> |
| 33 | - /// App 打印日志:获取当前登录账号在当前门店打印的记录(分页,时间倒序) | 34 | + /// App 打印日志:当前门店打印记录(分页)。管理员 / Partner 角色可见门店内全部;其它角色仅本人。 |
| 34 | /// </summary> | 35 | /// </summary> |
| 35 | Task<PagedResultWithPageDto<PrintLogItemDto>> GetPrintLogListAsync(PrintLogGetListInputVo input); | 36 | Task<PagedResultWithPageDto<PrintLogItemDto>> GetPrintLogListAsync(PrintLogGetListInputVo input); |
| 37 | + | ||
| 38 | + /// <summary> | ||
| 39 | + /// App Label Report:当前门店统计。权限规则与 <see cref="GetPrintLogListAsync"/> 一致。 | ||
| 40 | + /// </summary> | ||
| 41 | + Task<ReportsLabelReportOutputDto> GetLabelReportAsync(UsAppLabelReportQueryInputVo input); | ||
| 36 | } | 42 | } |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelMultipleOptionDbEntity.cs
| @@ -29,5 +29,10 @@ public class FlLabelMultipleOptionDbEntity | @@ -29,5 +29,10 @@ public class FlLabelMultipleOptionDbEntity | ||
| 29 | public int OrderNum { get; set; } | 29 | public int OrderNum { get; set; } |
| 30 | 30 | ||
| 31 | public bool State { get; set; } | 31 | public bool State { get; set; } |
| 32 | + | ||
| 33 | + /// <summary> | ||
| 34 | + /// 门店可用范围:ALL / SPECIFIED | ||
| 35 | + /// </summary> | ||
| 36 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 32 | } | 37 | } |
| 33 | 38 |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelTypeDbEntity.cs
| @@ -27,5 +27,10 @@ public class FlLabelTypeDbEntity | @@ -27,5 +27,10 @@ public class FlLabelTypeDbEntity | ||
| 27 | public int OrderNum { get; set; } | 27 | public int OrderNum { get; set; } |
| 28 | 28 | ||
| 29 | public bool State { get; set; } | 29 | public bool State { get; set; } |
| 30 | + | ||
| 31 | + /// <summary> | ||
| 32 | + /// 门店可用范围:ALL / SPECIFIED | ||
| 33 | + /// </summary> | ||
| 34 | + public string AvailabilityType { get; set; } = "ALL"; | ||
| 30 | } | 35 | } |
| 31 | 36 |
美国版/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; | @@ -3,6 +3,7 @@ using FoodLabeling.Application.Contracts.Dtos.Common; | ||
| 3 | using FoodLabeling.Application.Contracts.Dtos.LabelMultipleOption; | 3 | using FoodLabeling.Application.Contracts.Dtos.LabelMultipleOption; |
| 4 | using FoodLabeling.Application.Contracts.IServices; | 4 | using FoodLabeling.Application.Contracts.IServices; |
| 5 | using FoodLabeling.Application.Services.DbModels; | 5 | using FoodLabeling.Application.Services.DbModels; |
| 6 | +using FoodLabeling.Domain.Entities; | ||
| 6 | using SqlSugar; | 7 | using SqlSugar; |
| 7 | using Volo.Abp; | 8 | using Volo.Abp; |
| 8 | using Volo.Abp.Application.Services; | 9 | using Volo.Abp.Application.Services; |
| @@ -26,12 +27,16 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -26,12 +27,16 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 26 | { | 27 | { |
| 27 | RefAsync<int> total = 0; | 28 | RefAsync<int> total = 0; |
| 28 | var keyword = input.Keyword?.Trim(); | 29 | var keyword = input.Keyword?.Trim(); |
| 30 | + var scopedLocationIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync( | ||
| 31 | + _dbContext.SqlSugarClient, input.GroupId, input.LocationId); | ||
| 29 | 32 | ||
| 30 | var query = _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionDbEntity>() | 33 | var query = _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionDbEntity>() |
| 31 | .Where(x => !x.IsDeleted) | 34 | .Where(x => !x.IsDeleted) |
| 32 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.OptionCode.Contains(keyword!) || x.OptionName.Contains(keyword!)) | 35 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.OptionCode.Contains(keyword!) || x.OptionName.Contains(keyword!)) |
| 33 | .WhereIF(input.State != null, x => x.State == input.State); | 36 | .WhereIF(input.State != null, x => x.State == input.State); |
| 34 | 37 | ||
| 38 | + query = ApplyMultipleOptionScopeFilter(query, scopedLocationIds); | ||
| 39 | + | ||
| 35 | if (!string.IsNullOrWhiteSpace(input.Sorting)) | 40 | if (!string.IsNullOrWhiteSpace(input.Sorting)) |
| 36 | { | 41 | { |
| 37 | query = query.OrderBy(input.Sorting); | 42 | query = query.OrderBy(input.Sorting); |
| @@ -42,15 +47,26 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -42,15 +47,26 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 42 | } | 47 | } |
| 43 | 48 | ||
| 44 | var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); | 49 | var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); |
| 45 | - var items = entities.Select(x => new LabelMultipleOptionGetListOutputDto | 50 | + var scopeMap = await BuildMultipleOptionScopeMapAsync(entities); |
| 51 | + | ||
| 52 | + var items = entities.Select(x => | ||
| 46 | { | 53 | { |
| 47 | - Id = x.Id, | ||
| 48 | - OptionCode = x.OptionCode, | ||
| 49 | - OptionName = x.OptionName, | ||
| 50 | - OptionValuesJson = x.OptionValuesJson, | ||
| 51 | - State = x.State, | ||
| 52 | - OrderNum = x.OrderNum, | ||
| 53 | - LastEdited = x.LastModificationTime ?? x.CreationTime | 54 | + scopeMap.TryGetValue(x.Id, out var scope); |
| 55 | + return new LabelMultipleOptionGetListOutputDto | ||
| 56 | + { | ||
| 57 | + Id = x.Id, | ||
| 58 | + OptionCode = x.OptionCode, | ||
| 59 | + OptionName = x.OptionName, | ||
| 60 | + OptionValuesJson = x.OptionValuesJson, | ||
| 61 | + State = x.State, | ||
| 62 | + AvailabilityType = x.AvailabilityType, | ||
| 63 | + OrderNum = x.OrderNum, | ||
| 64 | + LastEdited = x.LastModificationTime ?? x.CreationTime, | ||
| 65 | + Region = scope?.Region ?? EmptyDisplay, | ||
| 66 | + Location = scope?.Location ?? EmptyDisplay, | ||
| 67 | + RegionIds = scope?.RegionIds ?? new List<string>(), | ||
| 68 | + LocationIds = scope?.LocationIds ?? new List<string>() | ||
| 69 | + }; | ||
| 54 | }).ToList(); | 70 | }).ToList(); |
| 55 | 71 | ||
| 56 | return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); | 72 | return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); |
| @@ -65,15 +81,21 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -65,15 +81,21 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 65 | throw new UserFriendlyException("多选项不存在"); | 81 | throw new UserFriendlyException("多选项不存在"); |
| 66 | } | 82 | } |
| 67 | 83 | ||
| 68 | - return new LabelMultipleOptionGetOutputDto | 84 | + var dto = MapToGetOutput(entity); |
| 85 | + if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)) | ||
| 69 | { | 86 | { |
| 70 | - Id = entity.Id, | ||
| 71 | - OptionCode = entity.OptionCode, | ||
| 72 | - OptionName = entity.OptionName, | ||
| 73 | - OptionValuesJson = entity.OptionValuesJson, | ||
| 74 | - State = entity.State, | ||
| 75 | - OrderNum = entity.OrderNum | ||
| 76 | - }; | 87 | + var locationIds = await _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionLocationDbEntity>() |
| 88 | + .Where(x => x.MultipleOptionId == entity.Id) | ||
| 89 | + .Select(x => x.LocationId) | ||
| 90 | + .ToListAsync(); | ||
| 91 | + dto.LocationIds = LocationScopeBindingHelper.NormalizeIds(locationIds); | ||
| 92 | + var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync( | ||
| 93 | + _dbContext.SqlSugarClient, locationIds); | ||
| 94 | + dto.RegionIds = regionIds; | ||
| 95 | + dto.GroupIds = regionIds; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + return dto; | ||
| 77 | } | 99 | } |
| 78 | 100 | ||
| 79 | public async Task<LabelMultipleOptionGetOutputDto> CreateAsync(LabelMultipleOptionCreateInputVo input) | 101 | public async Task<LabelMultipleOptionGetOutputDto> CreateAsync(LabelMultipleOptionCreateInputVo input) |
| @@ -85,6 +107,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -85,6 +107,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 85 | throw new UserFriendlyException("多选项编码和名称不能为空"); | 107 | throw new UserFriendlyException("多选项编码和名称不能为空"); |
| 86 | } | 108 | } |
| 87 | 109 | ||
| 110 | + var (availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input); | ||
| 111 | + | ||
| 88 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionDbEntity>() | 112 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionDbEntity>() |
| 89 | .AnyAsync(x => !x.IsDeleted && (x.OptionCode == code || x.OptionName == name)); | 113 | .AnyAsync(x => !x.IsDeleted && (x.OptionCode == code || x.OptionName == name)); |
| 90 | if (duplicated) | 114 | if (duplicated) |
| @@ -92,17 +116,27 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -92,17 +116,27 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 92 | throw new UserFriendlyException("多选项编码或名称已存在"); | 116 | throw new UserFriendlyException("多选项编码或名称已存在"); |
| 93 | } | 117 | } |
| 94 | 118 | ||
| 119 | + var now = DateTime.Now; | ||
| 120 | + var currentUserId = CurrentUser?.Id?.ToString(); | ||
| 95 | var entity = new FlLabelMultipleOptionDbEntity | 121 | var entity = new FlLabelMultipleOptionDbEntity |
| 96 | { | 122 | { |
| 97 | Id = _guidGenerator.Create().ToString(), | 123 | Id = _guidGenerator.Create().ToString(), |
| 124 | + IsDeleted = false, | ||
| 125 | + CreationTime = now, | ||
| 126 | + CreatorId = currentUserId, | ||
| 127 | + LastModificationTime = now, | ||
| 128 | + LastModifierId = currentUserId, | ||
| 129 | + ConcurrencyStamp = _guidGenerator.Create().ToString("N"), | ||
| 98 | OptionCode = code, | 130 | OptionCode = code, |
| 99 | OptionName = name, | 131 | OptionName = name, |
| 100 | OptionValuesJson = input.OptionValuesJson?.Trim(), | 132 | OptionValuesJson = input.OptionValuesJson?.Trim(), |
| 101 | State = input.State, | 133 | State = input.State, |
| 134 | + AvailabilityType = availabilityType, | ||
| 102 | OrderNum = input.OrderNum | 135 | OrderNum = input.OrderNum |
| 103 | }; | 136 | }; |
| 104 | 137 | ||
| 105 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); | 138 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); |
| 139 | + await SaveMultipleOptionLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now); | ||
| 106 | return await GetAsync(entity.Id); | 140 | return await GetAsync(entity.Id); |
| 107 | } | 141 | } |
| 108 | 142 | ||
| @@ -122,6 +156,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -122,6 +156,8 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 122 | throw new UserFriendlyException("多选项编码和名称不能为空"); | 156 | throw new UserFriendlyException("多选项编码和名称不能为空"); |
| 123 | } | 157 | } |
| 124 | 158 | ||
| 159 | + var (availabilityType, mergedLocationIds) = await ResolveMultipleOptionScopeForSaveAsync(input); | ||
| 160 | + | ||
| 125 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionDbEntity>() | 161 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionDbEntity>() |
| 126 | .AnyAsync(x => !x.IsDeleted && x.Id != id && (x.OptionCode == code || x.OptionName == name)); | 162 | .AnyAsync(x => !x.IsDeleted && x.Id != id && (x.OptionCode == code || x.OptionName == name)); |
| 127 | if (duplicated) | 163 | if (duplicated) |
| @@ -133,11 +169,14 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -133,11 +169,14 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 133 | entity.OptionName = name; | 169 | entity.OptionName = name; |
| 134 | entity.OptionValuesJson = input.OptionValuesJson?.Trim(); | 170 | entity.OptionValuesJson = input.OptionValuesJson?.Trim(); |
| 135 | entity.State = input.State; | 171 | entity.State = input.State; |
| 172 | + entity.AvailabilityType = availabilityType; | ||
| 136 | entity.OrderNum = input.OrderNum; | 173 | entity.OrderNum = input.OrderNum; |
| 137 | entity.LastModificationTime = DateTime.Now; | 174 | entity.LastModificationTime = DateTime.Now; |
| 138 | entity.LastModifierId = CurrentUser?.Id?.ToString(); | 175 | entity.LastModifierId = CurrentUser?.Id?.ToString(); |
| 139 | 176 | ||
| 140 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | 177 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); |
| 178 | + await SaveMultipleOptionLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId, | ||
| 179 | + entity.LastModificationTime ?? DateTime.Now); | ||
| 141 | return await GetAsync(id); | 180 | return await GetAsync(id); |
| 142 | } | 181 | } |
| 143 | 182 | ||
| @@ -150,12 +189,266 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -150,12 +189,266 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 150 | return; | 189 | return; |
| 151 | } | 190 | } |
| 152 | 191 | ||
| 192 | + await _dbContext.SqlSugarClient.Deleteable<FlLabelMultipleOptionLocationDbEntity>() | ||
| 193 | + .Where(x => x.MultipleOptionId == id) | ||
| 194 | + .ExecuteCommandAsync(); | ||
| 195 | + | ||
| 153 | entity.IsDeleted = true; | 196 | entity.IsDeleted = true; |
| 154 | entity.LastModificationTime = DateTime.Now; | 197 | entity.LastModificationTime = DateTime.Now; |
| 155 | entity.LastModifierId = CurrentUser?.Id?.ToString(); | 198 | entity.LastModifierId = CurrentUser?.Id?.ToString(); |
| 156 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | 199 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); |
| 157 | } | 200 | } |
| 158 | 201 | ||
| 202 | + private const string EmptyDisplay = "无"; | ||
| 203 | + private const string AllRegionsDisplay = "All Regions"; | ||
| 204 | + private const string AllLocationsDisplay = "All Locations"; | ||
| 205 | + | ||
| 206 | + private async Task<(string AvailabilityType, List<string> LocationIds)> ResolveMultipleOptionScopeForSaveAsync( | ||
| 207 | + LabelMultipleOptionCreateInputVo input) | ||
| 208 | + { | ||
| 209 | + var regionIds = NormalizeRegionIds(input); | ||
| 210 | + var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds); | ||
| 211 | + var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); | ||
| 212 | + | ||
| 213 | + var hasScopeArrays = input.RegionIds is not null || input.GroupIds is not null || input.LocationIds is not null; | ||
| 214 | + if (regionIds.Count > 0 || explicitLocationIds.Count > 0) | ||
| 215 | + { | ||
| 216 | + availabilityType = "SPECIFIED"; | ||
| 217 | + } | ||
| 218 | + else if (hasScopeArrays && string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase)) | ||
| 219 | + { | ||
| 220 | + availabilityType = "ALL"; | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + if (availabilityType != "ALL" && availabilityType != "SPECIFIED") | ||
| 224 | + { | ||
| 225 | + throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)"); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + if (availabilityType == "ALL") | ||
| 229 | + { | ||
| 230 | + return ("ALL", new List<string>()); | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync( | ||
| 234 | + _dbContext.SqlSugarClient, (IReadOnlyList<string>?)null, regionIds, explicitLocationIds); | ||
| 235 | + if (merged.Count == 0) | ||
| 236 | + { | ||
| 237 | + throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店"); | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged); | ||
| 241 | + return ("SPECIFIED", merged); | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + private static List<string> NormalizeRegionIds(LabelMultipleOptionCreateInputVo input) | ||
| 245 | + { | ||
| 246 | + var merged = new HashSet<string>(StringComparer.Ordinal); | ||
| 247 | + foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.RegionIds)) | ||
| 248 | + { | ||
| 249 | + merged.Add(id); | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.GroupIds)) | ||
| 253 | + { | ||
| 254 | + merged.Add(id); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + return merged.OrderBy(x => x, StringComparer.Ordinal).ToList(); | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + private async Task SaveMultipleOptionLocationsAsync( | ||
| 261 | + string multipleOptionId, | ||
| 262 | + string availabilityType, | ||
| 263 | + List<string> locationIds, | ||
| 264 | + string? currentUserId, | ||
| 265 | + DateTime now) | ||
| 266 | + { | ||
| 267 | + await _dbContext.SqlSugarClient.Deleteable<FlLabelMultipleOptionLocationDbEntity>() | ||
| 268 | + .Where(x => x.MultipleOptionId == multipleOptionId) | ||
| 269 | + .ExecuteCommandAsync(); | ||
| 270 | + | ||
| 271 | + if (availabilityType != "SPECIFIED" || locationIds.Count == 0) | ||
| 272 | + { | ||
| 273 | + return; | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + var rows = locationIds.Select(locId => new FlLabelMultipleOptionLocationDbEntity | ||
| 277 | + { | ||
| 278 | + Id = _guidGenerator.Create().ToString(), | ||
| 279 | + MultipleOptionId = multipleOptionId, | ||
| 280 | + LocationId = locId, | ||
| 281 | + CreationTime = now, | ||
| 282 | + CreatorId = currentUserId | ||
| 283 | + }).ToList(); | ||
| 284 | + | ||
| 285 | + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + private static LabelMultipleOptionGetOutputDto MapToGetOutput(FlLabelMultipleOptionDbEntity x) | ||
| 289 | + { | ||
| 290 | + return new LabelMultipleOptionGetOutputDto | ||
| 291 | + { | ||
| 292 | + Id = x.Id, | ||
| 293 | + OptionCode = x.OptionCode, | ||
| 294 | + OptionName = x.OptionName, | ||
| 295 | + OptionValuesJson = x.OptionValuesJson, | ||
| 296 | + State = x.State, | ||
| 297 | + OrderNum = x.OrderNum, | ||
| 298 | + AvailabilityType = x.AvailabilityType | ||
| 299 | + }; | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + private static ISugarQueryable<FlLabelMultipleOptionDbEntity> ApplyMultipleOptionScopeFilter( | ||
| 303 | + ISugarQueryable<FlLabelMultipleOptionDbEntity> query, | ||
| 304 | + List<string>? scopedLocationIds) | ||
| 305 | + { | ||
| 306 | + if (scopedLocationIds is null) | ||
| 307 | + { | ||
| 308 | + return query; | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + if (scopedLocationIds.Count == 0) | ||
| 312 | + { | ||
| 313 | + return query.Where(o => o.AvailabilityType == "ALL"); | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + return query.Where(o => | ||
| 317 | + o.AvailabilityType == "ALL" || | ||
| 318 | + SqlFunc.Subqueryable<FlLabelMultipleOptionLocationDbEntity>() | ||
| 319 | + .Where(ol => ol.MultipleOptionId == o.Id && scopedLocationIds.Contains(ol.LocationId)) | ||
| 320 | + .Any()); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + private async Task<Dictionary<string, MultipleOptionScopeData>> BuildMultipleOptionScopeMapAsync( | ||
| 324 | + List<FlLabelMultipleOptionDbEntity> entities) | ||
| 325 | + { | ||
| 326 | + var result = new Dictionary<string, MultipleOptionScopeData>(StringComparer.Ordinal); | ||
| 327 | + if (entities.Count == 0) | ||
| 328 | + { | ||
| 329 | + return result; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + foreach (var e in entities.Where(x => | ||
| 333 | + !string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))) | ||
| 334 | + { | ||
| 335 | + result[e.Id] = new MultipleOptionScopeData | ||
| 336 | + { | ||
| 337 | + Region = AllRegionsDisplay, | ||
| 338 | + Location = AllLocationsDisplay, | ||
| 339 | + RegionIds = new List<string>(), | ||
| 340 | + LocationIds = new List<string>() | ||
| 341 | + }; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + var specifiedIds = entities | ||
| 345 | + .Where(x => string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)) | ||
| 346 | + .Select(x => x.Id) | ||
| 347 | + .ToList(); | ||
| 348 | + if (specifiedIds.Count == 0) | ||
| 349 | + { | ||
| 350 | + return result; | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + var links = await _dbContext.SqlSugarClient.Queryable<FlLabelMultipleOptionLocationDbEntity>() | ||
| 354 | + .Where(x => specifiedIds.Contains(x.MultipleOptionId)) | ||
| 355 | + .ToListAsync(); | ||
| 356 | + | ||
| 357 | + var locIdSet = links | ||
| 358 | + .Select(x => x.LocationId) | ||
| 359 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | ||
| 360 | + .Select(x => x.Trim()) | ||
| 361 | + .Distinct(StringComparer.Ordinal) | ||
| 362 | + .ToList(); | ||
| 363 | + | ||
| 364 | + var locById = new Dictionary<string, LocationAggregateRoot>(StringComparer.Ordinal); | ||
| 365 | + if (locIdSet.Count > 0) | ||
| 366 | + { | ||
| 367 | + var guidList = locIdSet.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList(); | ||
| 368 | + if (guidList.Count > 0) | ||
| 369 | + { | ||
| 370 | + var locs = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | ||
| 371 | + .Where(x => !x.IsDeleted && guidList.Contains(x.Id)) | ||
| 372 | + .ToListAsync(); | ||
| 373 | + foreach (var loc in locs) | ||
| 374 | + { | ||
| 375 | + locById[loc.Id.ToString()] = loc; | ||
| 376 | + } | ||
| 377 | + } | ||
| 378 | + } | ||
| 379 | + | ||
| 380 | + foreach (var optionId in specifiedIds) | ||
| 381 | + { | ||
| 382 | + var optionLinks = links.Where(x => x.MultipleOptionId == optionId).ToList(); | ||
| 383 | + var locationIds = LocationScopeBindingHelper.NormalizeIds( | ||
| 384 | + optionLinks.Select(x => x.LocationId).ToList()); | ||
| 385 | + | ||
| 386 | + if (optionLinks.Count == 0) | ||
| 387 | + { | ||
| 388 | + result[optionId] = new MultipleOptionScopeData | ||
| 389 | + { | ||
| 390 | + Region = EmptyDisplay, | ||
| 391 | + Location = EmptyDisplay, | ||
| 392 | + RegionIds = new List<string>(), | ||
| 393 | + LocationIds = new List<string>() | ||
| 394 | + }; | ||
| 395 | + continue; | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + var regions = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| 399 | + var locationNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| 400 | + foreach (var lid in locationIds) | ||
| 401 | + { | ||
| 402 | + if (!locById.TryGetValue(lid, out var loc)) | ||
| 403 | + { | ||
| 404 | + continue; | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + var groupName = loc.GroupName?.Trim(); | ||
| 408 | + if (!string.IsNullOrEmpty(groupName)) | ||
| 409 | + { | ||
| 410 | + regions.Add(groupName); | ||
| 411 | + } | ||
| 412 | + | ||
| 413 | + var locName = loc.LocationName?.Trim(); | ||
| 414 | + if (string.IsNullOrEmpty(locName)) | ||
| 415 | + { | ||
| 416 | + locName = loc.LocationCode?.Trim(); | ||
| 417 | + } | ||
| 418 | + | ||
| 419 | + if (!string.IsNullOrEmpty(locName)) | ||
| 420 | + { | ||
| 421 | + locationNames.Add(locName); | ||
| 422 | + } | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync( | ||
| 426 | + _dbContext.SqlSugarClient, locationIds); | ||
| 427 | + | ||
| 428 | + result[optionId] = new MultipleOptionScopeData | ||
| 429 | + { | ||
| 430 | + Region = regions.Count > 0 | ||
| 431 | + ? string.Join(", ", regions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)) | ||
| 432 | + : EmptyDisplay, | ||
| 433 | + Location = locationNames.Count > 0 | ||
| 434 | + ? string.Join(", ", locationNames.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)) | ||
| 435 | + : EmptyDisplay, | ||
| 436 | + RegionIds = regionIds, | ||
| 437 | + LocationIds = locationIds | ||
| 438 | + }; | ||
| 439 | + } | ||
| 440 | + | ||
| 441 | + return result; | ||
| 442 | + } | ||
| 443 | + | ||
| 444 | + private sealed class MultipleOptionScopeData | ||
| 445 | + { | ||
| 446 | + public string Region { get; init; } = string.Empty; | ||
| 447 | + public string Location { get; init; } = string.Empty; | ||
| 448 | + public List<string> RegionIds { get; init; } = new(); | ||
| 449 | + public List<string> LocationIds { get; init; } = new(); | ||
| 450 | + } | ||
| 451 | + | ||
| 159 | private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, List<T> items) | 452 | private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, List<T> items) |
| 160 | { | 453 | { |
| 161 | var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; | 454 | var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; |
| @@ -171,4 +464,3 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | @@ -171,4 +464,3 @@ public class LabelMultipleOptionAppService : ApplicationService, ILabelMultipleO | ||
| 171 | }; | 464 | }; |
| 172 | } | 465 | } |
| 173 | } | 466 | } |
| 174 | - |
美国版/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; | @@ -3,6 +3,7 @@ using FoodLabeling.Application.Contracts.Dtos.Common; | ||
| 3 | using FoodLabeling.Application.Contracts.Dtos.LabelType; | 3 | using FoodLabeling.Application.Contracts.Dtos.LabelType; |
| 4 | using FoodLabeling.Application.Contracts.IServices; | 4 | using FoodLabeling.Application.Contracts.IServices; |
| 5 | using FoodLabeling.Application.Services.DbModels; | 5 | using FoodLabeling.Application.Services.DbModels; |
| 6 | +using FoodLabeling.Domain.Entities; | ||
| 6 | using SqlSugar; | 7 | using SqlSugar; |
| 7 | using Volo.Abp; | 8 | using Volo.Abp; |
| 8 | using Volo.Abp.Application.Services; | 9 | using Volo.Abp.Application.Services; |
| @@ -26,12 +27,16 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -26,12 +27,16 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 26 | { | 27 | { |
| 27 | RefAsync<int> total = 0; | 28 | RefAsync<int> total = 0; |
| 28 | var keyword = input.Keyword?.Trim(); | 29 | var keyword = input.Keyword?.Trim(); |
| 30 | + var scopedLocationIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync( | ||
| 31 | + _dbContext.SqlSugarClient, input.GroupId, input.LocationId); | ||
| 29 | 32 | ||
| 30 | var query = _dbContext.SqlSugarClient.Queryable<FlLabelTypeDbEntity>() | 33 | var query = _dbContext.SqlSugarClient.Queryable<FlLabelTypeDbEntity>() |
| 31 | .Where(x => !x.IsDeleted) | 34 | .Where(x => !x.IsDeleted) |
| 32 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.TypeCode.Contains(keyword!) || x.TypeName.Contains(keyword!)) | 35 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.TypeCode.Contains(keyword!) || x.TypeName.Contains(keyword!)) |
| 33 | .WhereIF(input.State != null, x => x.State == input.State); | 36 | .WhereIF(input.State != null, x => x.State == input.State); |
| 34 | 37 | ||
| 38 | + query = ApplyLabelTypeScopeFilter(query, scopedLocationIds); | ||
| 39 | + | ||
| 35 | if (!string.IsNullOrWhiteSpace(input.Sorting)) | 40 | if (!string.IsNullOrWhiteSpace(input.Sorting)) |
| 36 | { | 41 | { |
| 37 | query = query.OrderBy(input.Sorting); | 42 | query = query.OrderBy(input.Sorting); |
| @@ -44,23 +49,28 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -44,23 +49,28 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 44 | var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); | 49 | var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); |
| 45 | var ids = entities.Select(x => x.Id).ToList(); | 50 | var ids = entities.Select(x => x.Id).ToList(); |
| 46 | 51 | ||
| 47 | - var countRows = await _dbContext.SqlSugarClient.Queryable<FlLabelDbEntity>() | ||
| 48 | - .Where(x => !x.IsDeleted) | ||
| 49 | - .Where(x => x.LabelTypeId != null && ids.Contains(x.LabelTypeId)) | ||
| 50 | - .GroupBy(x => x.LabelTypeId) | ||
| 51 | - .Select(x => new { TypeId = x.LabelTypeId, Count = SqlFunc.AggregateCount(x.Id) }) | ||
| 52 | - .ToListAsync(); | ||
| 53 | - var countMap = countRows.ToDictionary(x => x.TypeId!, x => (long)x.Count); | 52 | + var countMap = await BuildTypeLabelStatsMapAsync(ids, scopedLocationIds); |
| 53 | + var scopeMap = await BuildTypeConfiguredScopeMapAsync(entities); | ||
| 54 | 54 | ||
| 55 | - var items = entities.Select(x => new LabelTypeGetListOutputDto | 55 | + var items = entities.Select(x => |
| 56 | { | 56 | { |
| 57 | - Id = x.Id, | ||
| 58 | - TypeCode = x.TypeCode, | ||
| 59 | - TypeName = x.TypeName, | ||
| 60 | - State = x.State, | ||
| 61 | - OrderNum = x.OrderNum, | ||
| 62 | - NoOfLabels = countMap.TryGetValue(x.Id, out var count) ? count : 0, | ||
| 63 | - LastEdited = x.LastModificationTime ?? x.CreationTime | 57 | + scopeMap.TryGetValue(x.Id, out var scope); |
| 58 | + countMap.TryGetValue(x.Id, out var stats); | ||
| 59 | + return new LabelTypeGetListOutputDto | ||
| 60 | + { | ||
| 61 | + Id = x.Id, | ||
| 62 | + TypeCode = x.TypeCode, | ||
| 63 | + TypeName = x.TypeName, | ||
| 64 | + State = x.State, | ||
| 65 | + AvailabilityType = x.AvailabilityType, | ||
| 66 | + OrderNum = x.OrderNum, | ||
| 67 | + NoOfLabels = stats?.Count ?? 0, | ||
| 68 | + LastEdited = stats?.MaxEdited ?? x.LastModificationTime ?? x.CreationTime, | ||
| 69 | + Region = scope?.Region ?? EmptyDisplay, | ||
| 70 | + Location = scope?.Location ?? EmptyDisplay, | ||
| 71 | + RegionIds = scope?.RegionIds ?? new List<string>(), | ||
| 72 | + LocationIds = scope?.LocationIds ?? new List<string>() | ||
| 73 | + }; | ||
| 64 | }).ToList(); | 74 | }).ToList(); |
| 65 | 75 | ||
| 66 | return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); | 76 | return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items); |
| @@ -75,7 +85,21 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -75,7 +85,21 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 75 | throw new UserFriendlyException("标签类型不存在"); | 85 | throw new UserFriendlyException("标签类型不存在"); |
| 76 | } | 86 | } |
| 77 | 87 | ||
| 78 | - return MapToGetOutput(entity); | 88 | + var dto = MapToGetOutput(entity); |
| 89 | + if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)) | ||
| 90 | + { | ||
| 91 | + var locationIds = await _dbContext.SqlSugarClient.Queryable<FlLabelTypeLocationDbEntity>() | ||
| 92 | + .Where(x => x.LabelTypeId == entity.Id) | ||
| 93 | + .Select(x => x.LocationId) | ||
| 94 | + .ToListAsync(); | ||
| 95 | + dto.LocationIds = LocationScopeBindingHelper.NormalizeIds(locationIds); | ||
| 96 | + var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync( | ||
| 97 | + _dbContext.SqlSugarClient, locationIds); | ||
| 98 | + dto.RegionIds = regionIds; | ||
| 99 | + dto.GroupIds = regionIds; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + return dto; | ||
| 79 | } | 103 | } |
| 80 | 104 | ||
| 81 | public async Task<LabelTypeGetOutputDto> CreateAsync(LabelTypeCreateInputVo input) | 105 | public async Task<LabelTypeGetOutputDto> CreateAsync(LabelTypeCreateInputVo input) |
| @@ -87,6 +111,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -87,6 +111,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 87 | throw new UserFriendlyException("类型编码和名称不能为空"); | 111 | throw new UserFriendlyException("类型编码和名称不能为空"); |
| 88 | } | 112 | } |
| 89 | 113 | ||
| 114 | + var (availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input); | ||
| 115 | + | ||
| 90 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelTypeDbEntity>() | 116 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelTypeDbEntity>() |
| 91 | .AnyAsync(x => !x.IsDeleted && (x.TypeCode == code || x.TypeName == name)); | 117 | .AnyAsync(x => !x.IsDeleted && (x.TypeCode == code || x.TypeName == name)); |
| 92 | if (duplicated) | 118 | if (duplicated) |
| @@ -94,16 +120,26 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -94,16 +120,26 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 94 | throw new UserFriendlyException("类型编码或名称已存在"); | 120 | throw new UserFriendlyException("类型编码或名称已存在"); |
| 95 | } | 121 | } |
| 96 | 122 | ||
| 123 | + var now = DateTime.Now; | ||
| 124 | + var currentUserId = CurrentUser?.Id?.ToString(); | ||
| 97 | var entity = new FlLabelTypeDbEntity | 125 | var entity = new FlLabelTypeDbEntity |
| 98 | { | 126 | { |
| 99 | Id = _guidGenerator.Create().ToString(), | 127 | Id = _guidGenerator.Create().ToString(), |
| 128 | + IsDeleted = false, | ||
| 129 | + CreationTime = now, | ||
| 130 | + CreatorId = currentUserId, | ||
| 131 | + LastModificationTime = now, | ||
| 132 | + LastModifierId = currentUserId, | ||
| 133 | + ConcurrencyStamp = _guidGenerator.Create().ToString("N"), | ||
| 100 | TypeCode = code, | 134 | TypeCode = code, |
| 101 | TypeName = name, | 135 | TypeName = name, |
| 102 | State = input.State, | 136 | State = input.State, |
| 137 | + AvailabilityType = availabilityType, | ||
| 103 | OrderNum = input.OrderNum | 138 | OrderNum = input.OrderNum |
| 104 | }; | 139 | }; |
| 105 | 140 | ||
| 106 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); | 141 | await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); |
| 142 | + await SaveTypeLocationsAsync(entity.Id, availabilityType, mergedLocationIds, currentUserId, now); | ||
| 107 | return await GetAsync(entity.Id); | 143 | return await GetAsync(entity.Id); |
| 108 | } | 144 | } |
| 109 | 145 | ||
| @@ -123,6 +159,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -123,6 +159,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 123 | throw new UserFriendlyException("类型编码和名称不能为空"); | 159 | throw new UserFriendlyException("类型编码和名称不能为空"); |
| 124 | } | 160 | } |
| 125 | 161 | ||
| 162 | + var (availabilityType, mergedLocationIds) = await ResolveTypeScopeForSaveAsync(input); | ||
| 163 | + | ||
| 126 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelTypeDbEntity>() | 164 | var duplicated = await _dbContext.SqlSugarClient.Queryable<FlLabelTypeDbEntity>() |
| 127 | .AnyAsync(x => !x.IsDeleted && x.Id != id && (x.TypeCode == code || x.TypeName == name)); | 165 | .AnyAsync(x => !x.IsDeleted && x.Id != id && (x.TypeCode == code || x.TypeName == name)); |
| 128 | if (duplicated) | 166 | if (duplicated) |
| @@ -133,11 +171,14 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -133,11 +171,14 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 133 | entity.TypeCode = code; | 171 | entity.TypeCode = code; |
| 134 | entity.TypeName = name; | 172 | entity.TypeName = name; |
| 135 | entity.State = input.State; | 173 | entity.State = input.State; |
| 174 | + entity.AvailabilityType = availabilityType; | ||
| 136 | entity.OrderNum = input.OrderNum; | 175 | entity.OrderNum = input.OrderNum; |
| 137 | entity.LastModificationTime = DateTime.Now; | 176 | entity.LastModificationTime = DateTime.Now; |
| 138 | entity.LastModifierId = CurrentUser?.Id?.ToString(); | 177 | entity.LastModifierId = CurrentUser?.Id?.ToString(); |
| 139 | 178 | ||
| 140 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | 179 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); |
| 180 | + await SaveTypeLocationsAsync(entity.Id, availabilityType, mergedLocationIds, entity.LastModifierId, | ||
| 181 | + entity.LastModificationTime ?? DateTime.Now); | ||
| 141 | return await GetAsync(id); | 182 | return await GetAsync(id); |
| 142 | } | 183 | } |
| 143 | 184 | ||
| @@ -157,12 +198,304 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -157,12 +198,304 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 157 | throw new UserFriendlyException("该标签类型已被标签引用,无法删除"); | 198 | throw new UserFriendlyException("该标签类型已被标签引用,无法删除"); |
| 158 | } | 199 | } |
| 159 | 200 | ||
| 201 | + await _dbContext.SqlSugarClient.Deleteable<FlLabelTypeLocationDbEntity>() | ||
| 202 | + .Where(x => x.LabelTypeId == id) | ||
| 203 | + .ExecuteCommandAsync(); | ||
| 204 | + | ||
| 160 | entity.IsDeleted = true; | 205 | entity.IsDeleted = true; |
| 161 | entity.LastModificationTime = DateTime.Now; | 206 | entity.LastModificationTime = DateTime.Now; |
| 162 | entity.LastModifierId = CurrentUser?.Id?.ToString(); | 207 | entity.LastModifierId = CurrentUser?.Id?.ToString(); |
| 163 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); | 208 | await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); |
| 164 | } | 209 | } |
| 165 | 210 | ||
| 211 | + private const string EmptyDisplay = "无"; | ||
| 212 | + private const string AllRegionsDisplay = "All Regions"; | ||
| 213 | + private const string AllLocationsDisplay = "All Locations"; | ||
| 214 | + | ||
| 215 | + private async Task<(string AvailabilityType, List<string> LocationIds)> ResolveTypeScopeForSaveAsync( | ||
| 216 | + LabelTypeCreateInputVo input) | ||
| 217 | + { | ||
| 218 | + var regionIds = NormalizeRegionIds(input); | ||
| 219 | + var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(input.LocationIds); | ||
| 220 | + var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); | ||
| 221 | + | ||
| 222 | + var hasScopeArrays = input.RegionIds is not null || input.GroupIds is not null || input.LocationIds is not null; | ||
| 223 | + if (regionIds.Count > 0 || explicitLocationIds.Count > 0) | ||
| 224 | + { | ||
| 225 | + availabilityType = "SPECIFIED"; | ||
| 226 | + } | ||
| 227 | + else if (hasScopeArrays && string.Equals(availabilityType, "ALL", StringComparison.OrdinalIgnoreCase)) | ||
| 228 | + { | ||
| 229 | + availabilityType = "ALL"; | ||
| 230 | + } | ||
| 231 | + | ||
| 232 | + if (availabilityType != "ALL" && availabilityType != "SPECIFIED") | ||
| 233 | + { | ||
| 234 | + throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)"); | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + if (availabilityType == "ALL") | ||
| 238 | + { | ||
| 239 | + return ("ALL", new List<string>()); | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync( | ||
| 243 | + _dbContext.SqlSugarClient, (IReadOnlyList<string>?)null, regionIds, explicitLocationIds); | ||
| 244 | + if (merged.Count == 0) | ||
| 245 | + { | ||
| 246 | + throw new UserFriendlyException("指定适用区域或门店时,至少需要匹配到一个有效门店"); | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(_dbContext.SqlSugarClient, merged); | ||
| 250 | + return ("SPECIFIED", merged); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + private static List<string> NormalizeRegionIds(LabelTypeCreateInputVo input) | ||
| 254 | + { | ||
| 255 | + var merged = new HashSet<string>(StringComparer.Ordinal); | ||
| 256 | + foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.RegionIds)) | ||
| 257 | + { | ||
| 258 | + merged.Add(id); | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + foreach (var id in LocationScopeBindingHelper.NormalizeIds(input.GroupIds)) | ||
| 262 | + { | ||
| 263 | + merged.Add(id); | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + return merged.OrderBy(x => x, StringComparer.Ordinal).ToList(); | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + private static ISugarQueryable<FlLabelTypeDbEntity> ApplyLabelTypeScopeFilter( | ||
| 270 | + ISugarQueryable<FlLabelTypeDbEntity> query, | ||
| 271 | + List<string>? scopedLocationIds) | ||
| 272 | + { | ||
| 273 | + if (scopedLocationIds is null) | ||
| 274 | + { | ||
| 275 | + return query; | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + if (scopedLocationIds.Count == 0) | ||
| 279 | + { | ||
| 280 | + return query.Where(t => t.AvailabilityType == "ALL"); | ||
| 281 | + } | ||
| 282 | + | ||
| 283 | + return query.Where(t => | ||
| 284 | + t.AvailabilityType == "ALL" || | ||
| 285 | + SqlFunc.Subqueryable<FlLabelTypeLocationDbEntity>() | ||
| 286 | + .Where(tl => tl.LabelTypeId == t.Id && scopedLocationIds.Contains(tl.LocationId)) | ||
| 287 | + .Any()); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + private async Task SaveTypeLocationsAsync( | ||
| 291 | + string labelTypeId, | ||
| 292 | + string availabilityType, | ||
| 293 | + List<string> locationIds, | ||
| 294 | + string? currentUserId, | ||
| 295 | + DateTime now) | ||
| 296 | + { | ||
| 297 | + await _dbContext.SqlSugarClient.Deleteable<FlLabelTypeLocationDbEntity>() | ||
| 298 | + .Where(x => x.LabelTypeId == labelTypeId) | ||
| 299 | + .ExecuteCommandAsync(); | ||
| 300 | + | ||
| 301 | + if (availabilityType != "SPECIFIED" || locationIds.Count == 0) | ||
| 302 | + { | ||
| 303 | + return; | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + var rows = locationIds.Select(locId => new FlLabelTypeLocationDbEntity | ||
| 307 | + { | ||
| 308 | + Id = _guidGenerator.Create().ToString(), | ||
| 309 | + LabelTypeId = labelTypeId, | ||
| 310 | + LocationId = locId, | ||
| 311 | + CreationTime = now, | ||
| 312 | + CreatorId = currentUserId | ||
| 313 | + }).ToList(); | ||
| 314 | + | ||
| 315 | + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); | ||
| 316 | + } | ||
| 317 | + | ||
| 318 | + private async Task<Dictionary<string, TypeLabelStats>> BuildTypeLabelStatsMapAsync( | ||
| 319 | + List<string> typeIds, | ||
| 320 | + List<string>? scopedLocationIds) | ||
| 321 | + { | ||
| 322 | + var result = new Dictionary<string, TypeLabelStats>(StringComparer.Ordinal); | ||
| 323 | + if (typeIds.Count == 0) | ||
| 324 | + { | ||
| 325 | + return result; | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + var labelQuery = _dbContext.SqlSugarClient.Queryable<FlLabelDbEntity>() | ||
| 329 | + .Where(x => !x.IsDeleted) | ||
| 330 | + .Where(x => x.LabelTypeId != null && typeIds.Contains(x.LabelTypeId)); | ||
| 331 | + | ||
| 332 | + labelQuery = ApplyLabelScopeOnLabels(labelQuery, scopedLocationIds); | ||
| 333 | + | ||
| 334 | + var rows = await labelQuery | ||
| 335 | + .Select(x => new { x.LabelTypeId, x.CreationTime, x.LastModificationTime }) | ||
| 336 | + .ToListAsync(); | ||
| 337 | + | ||
| 338 | + foreach (var g in rows.GroupBy(x => x.LabelTypeId!)) | ||
| 339 | + { | ||
| 340 | + result[g.Key] = new TypeLabelStats | ||
| 341 | + { | ||
| 342 | + Count = g.Count(), | ||
| 343 | + MaxEdited = g.Max(l => l.LastModificationTime ?? l.CreationTime) | ||
| 344 | + }; | ||
| 345 | + } | ||
| 346 | + | ||
| 347 | + return result; | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + private async Task<Dictionary<string, TypeScopeData>> BuildTypeConfiguredScopeMapAsync( | ||
| 351 | + List<FlLabelTypeDbEntity> entities) | ||
| 352 | + { | ||
| 353 | + var result = new Dictionary<string, TypeScopeData>(StringComparer.Ordinal); | ||
| 354 | + if (entities.Count == 0) | ||
| 355 | + { | ||
| 356 | + return result; | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + foreach (var e in entities.Where(x => | ||
| 360 | + !string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase))) | ||
| 361 | + { | ||
| 362 | + result[e.Id] = new TypeScopeData | ||
| 363 | + { | ||
| 364 | + Region = AllRegionsDisplay, | ||
| 365 | + Location = AllLocationsDisplay, | ||
| 366 | + RegionIds = new List<string>(), | ||
| 367 | + LocationIds = new List<string>() | ||
| 368 | + }; | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + var specifiedIds = entities | ||
| 372 | + .Where(x => string.Equals(x.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)) | ||
| 373 | + .Select(x => x.Id) | ||
| 374 | + .ToList(); | ||
| 375 | + if (specifiedIds.Count == 0) | ||
| 376 | + { | ||
| 377 | + return result; | ||
| 378 | + } | ||
| 379 | + | ||
| 380 | + var links = await _dbContext.SqlSugarClient.Queryable<FlLabelTypeLocationDbEntity>() | ||
| 381 | + .Where(x => specifiedIds.Contains(x.LabelTypeId)) | ||
| 382 | + .ToListAsync(); | ||
| 383 | + | ||
| 384 | + var locIdSet = links | ||
| 385 | + .Select(x => x.LocationId) | ||
| 386 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | ||
| 387 | + .Select(x => x.Trim()) | ||
| 388 | + .Distinct(StringComparer.Ordinal) | ||
| 389 | + .ToList(); | ||
| 390 | + | ||
| 391 | + var locById = new Dictionary<string, LocationAggregateRoot>(StringComparer.Ordinal); | ||
| 392 | + if (locIdSet.Count > 0) | ||
| 393 | + { | ||
| 394 | + var guidList = locIdSet.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList(); | ||
| 395 | + if (guidList.Count > 0) | ||
| 396 | + { | ||
| 397 | + var locs = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | ||
| 398 | + .Where(x => !x.IsDeleted && guidList.Contains(x.Id)) | ||
| 399 | + .ToListAsync(); | ||
| 400 | + foreach (var loc in locs) | ||
| 401 | + { | ||
| 402 | + locById[loc.Id.ToString()] = loc; | ||
| 403 | + } | ||
| 404 | + } | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + foreach (var typeId in specifiedIds) | ||
| 408 | + { | ||
| 409 | + var typeLinks = links.Where(x => x.LabelTypeId == typeId).ToList(); | ||
| 410 | + var locationIds = LocationScopeBindingHelper.NormalizeIds( | ||
| 411 | + typeLinks.Select(x => x.LocationId).ToList()); | ||
| 412 | + | ||
| 413 | + if (typeLinks.Count == 0) | ||
| 414 | + { | ||
| 415 | + result[typeId] = new TypeScopeData | ||
| 416 | + { | ||
| 417 | + Region = EmptyDisplay, | ||
| 418 | + Location = EmptyDisplay, | ||
| 419 | + RegionIds = new List<string>(), | ||
| 420 | + LocationIds = new List<string>() | ||
| 421 | + }; | ||
| 422 | + continue; | ||
| 423 | + } | ||
| 424 | + | ||
| 425 | + var regions = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| 426 | + var locationNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| 427 | + foreach (var lid in locationIds) | ||
| 428 | + { | ||
| 429 | + if (!locById.TryGetValue(lid, out var loc)) | ||
| 430 | + { | ||
| 431 | + continue; | ||
| 432 | + } | ||
| 433 | + | ||
| 434 | + var groupName = loc.GroupName?.Trim(); | ||
| 435 | + if (!string.IsNullOrEmpty(groupName)) | ||
| 436 | + { | ||
| 437 | + regions.Add(groupName); | ||
| 438 | + } | ||
| 439 | + | ||
| 440 | + var locName = loc.LocationName?.Trim(); | ||
| 441 | + if (string.IsNullOrEmpty(locName)) | ||
| 442 | + { | ||
| 443 | + locName = loc.LocationCode?.Trim(); | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + if (!string.IsNullOrEmpty(locName)) | ||
| 447 | + { | ||
| 448 | + locationNames.Add(locName); | ||
| 449 | + } | ||
| 450 | + } | ||
| 451 | + | ||
| 452 | + var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync( | ||
| 453 | + _dbContext.SqlSugarClient, locationIds); | ||
| 454 | + | ||
| 455 | + result[typeId] = new TypeScopeData | ||
| 456 | + { | ||
| 457 | + Region = regions.Count > 0 | ||
| 458 | + ? string.Join(", ", regions.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)) | ||
| 459 | + : EmptyDisplay, | ||
| 460 | + Location = locationNames.Count > 0 | ||
| 461 | + ? string.Join(", ", locationNames.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)) | ||
| 462 | + : EmptyDisplay, | ||
| 463 | + RegionIds = regionIds, | ||
| 464 | + LocationIds = locationIds | ||
| 465 | + }; | ||
| 466 | + } | ||
| 467 | + | ||
| 468 | + return result; | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + private static ISugarQueryable<FlLabelDbEntity> ApplyLabelScopeOnLabels( | ||
| 472 | + ISugarQueryable<FlLabelDbEntity> labelQuery, | ||
| 473 | + List<string>? scopedLocationIds) | ||
| 474 | + { | ||
| 475 | + if (scopedLocationIds is null) | ||
| 476 | + { | ||
| 477 | + return labelQuery; | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + return scopedLocationIds.Count == 0 | ||
| 481 | + ? labelQuery.Where(_ => false) | ||
| 482 | + : labelQuery.Where(l => scopedLocationIds.Contains(l.LocationId)); | ||
| 483 | + } | ||
| 484 | + | ||
| 485 | + private sealed class TypeScopeData | ||
| 486 | + { | ||
| 487 | + public string Region { get; init; } = string.Empty; | ||
| 488 | + public string Location { get; init; } = string.Empty; | ||
| 489 | + public List<string> RegionIds { get; init; } = new(); | ||
| 490 | + public List<string> LocationIds { get; init; } = new(); | ||
| 491 | + } | ||
| 492 | + | ||
| 493 | + private sealed class TypeLabelStats | ||
| 494 | + { | ||
| 495 | + public long Count { get; init; } | ||
| 496 | + public DateTime MaxEdited { get; init; } | ||
| 497 | + } | ||
| 498 | + | ||
| 166 | private static LabelTypeGetOutputDto MapToGetOutput(FlLabelTypeDbEntity x) | 499 | private static LabelTypeGetOutputDto MapToGetOutput(FlLabelTypeDbEntity x) |
| 167 | { | 500 | { |
| 168 | return new LabelTypeGetOutputDto | 501 | return new LabelTypeGetOutputDto |
| @@ -171,7 +504,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -171,7 +504,8 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 171 | TypeCode = x.TypeCode, | 504 | TypeCode = x.TypeCode, |
| 172 | TypeName = x.TypeName, | 505 | TypeName = x.TypeName, |
| 173 | State = x.State, | 506 | State = x.State, |
| 174 | - OrderNum = x.OrderNum | 507 | + OrderNum = x.OrderNum, |
| 508 | + AvailabilityType = x.AvailabilityType | ||
| 175 | }; | 509 | }; |
| 176 | } | 510 | } |
| 177 | 511 | ||
| @@ -190,4 +524,3 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | @@ -190,4 +524,3 @@ public class LabelTypeAppService : ApplicationService, ILabelTypeAppService | ||
| 190 | }; | 524 | }; |
| 191 | } | 525 | } |
| 192 | } | 526 | } |
| 193 | - |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LocationAppService.cs
| @@ -22,13 +22,16 @@ namespace FoodLabeling.Application.Services; | @@ -22,13 +22,16 @@ namespace FoodLabeling.Application.Services; | ||
| 22 | public class LocationAppService : ApplicationService, ILocationAppService | 22 | public class LocationAppService : ApplicationService, ILocationAppService |
| 23 | { | 23 | { |
| 24 | private readonly ISqlSugarRepository<LocationAggregateRoot, Guid> _locationRepository; | 24 | private readonly ISqlSugarRepository<LocationAggregateRoot, Guid> _locationRepository; |
| 25 | + private readonly ISqlSugarDbContext _dbContext; | ||
| 25 | private readonly IOptionsSnapshot<FoodLabelingBatchImportOptions> _batchImportOptions; | 26 | private readonly IOptionsSnapshot<FoodLabelingBatchImportOptions> _batchImportOptions; |
| 26 | 27 | ||
| 27 | public LocationAppService( | 28 | public LocationAppService( |
| 28 | ISqlSugarRepository<LocationAggregateRoot, Guid> locationRepository, | 29 | ISqlSugarRepository<LocationAggregateRoot, Guid> locationRepository, |
| 30 | + ISqlSugarDbContext dbContext, | ||
| 29 | IOptionsSnapshot<FoodLabelingBatchImportOptions> batchImportOptions) | 31 | IOptionsSnapshot<FoodLabelingBatchImportOptions> batchImportOptions) |
| 30 | { | 32 | { |
| 31 | _locationRepository = locationRepository; | 33 | _locationRepository = locationRepository; |
| 34 | + _dbContext = dbContext; | ||
| 32 | _batchImportOptions = batchImportOptions; | 35 | _batchImportOptions = batchImportOptions; |
| 33 | } | 36 | } |
| 34 | 37 | ||
| @@ -37,7 +40,7 @@ public class LocationAppService : ApplicationService, ILocationAppService | @@ -37,7 +40,7 @@ public class LocationAppService : ApplicationService, ILocationAppService | ||
| 37 | { | 40 | { |
| 38 | RefAsync<int> total = 0; | 41 | RefAsync<int> total = 0; |
| 39 | 42 | ||
| 40 | - var query = BuildFilteredQuery(input); | 43 | + var query = await BuildFilteredQueryAsync(input); |
| 41 | if (!string.IsNullOrWhiteSpace(input.Sorting)) | 44 | if (!string.IsNullOrWhiteSpace(input.Sorting)) |
| 42 | { | 45 | { |
| 43 | query = query.OrderBy(input.Sorting); | 46 | query = query.OrderBy(input.Sorting); |
| @@ -200,7 +203,7 @@ public class LocationAppService : ApplicationService, ILocationAppService | @@ -200,7 +203,7 @@ public class LocationAppService : ApplicationService, ILocationAppService | ||
| 200 | State = input.State | 203 | State = input.State |
| 201 | }; | 204 | }; |
| 202 | 205 | ||
| 203 | - var query = BuildFilteredQuery(exportFilter); | 206 | + var query = await BuildFilteredQueryAsync(exportFilter); |
| 204 | if (!string.IsNullOrWhiteSpace(exportFilter.Sorting)) | 207 | if (!string.IsNullOrWhiteSpace(exportFilter.Sorting)) |
| 205 | { | 208 | { |
| 206 | query = query.OrderBy(exportFilter.Sorting); | 209 | query = query.OrderBy(exportFilter.Sorting); |
| @@ -328,14 +331,20 @@ public class LocationAppService : ApplicationService, ILocationAppService | @@ -328,14 +331,20 @@ public class LocationAppService : ApplicationService, ILocationAppService | ||
| 328 | return result; | 331 | return result; |
| 329 | } | 332 | } |
| 330 | 333 | ||
| 331 | - private ISugarQueryable<LocationAggregateRoot> BuildFilteredQuery(LocationGetListInputVo input) | 334 | + private async Task<ISugarQueryable<LocationAggregateRoot>> BuildFilteredQueryAsync(LocationGetListInputVo input) |
| 332 | { | 335 | { |
| 336 | + var scope = await LocationRegionScopeHelper.ResolveLocationListScopeAsync(CurrentUser, _dbContext); | ||
| 337 | + | ||
| 333 | var keyword = input.Keyword?.Trim(); | 338 | var keyword = input.Keyword?.Trim(); |
| 334 | var partner = input.Partner?.Trim(); | 339 | var partner = input.Partner?.Trim(); |
| 335 | var groupName = input.GroupName?.Trim(); | 340 | var groupName = input.GroupName?.Trim(); |
| 336 | 341 | ||
| 337 | - return _locationRepository._DbQueryable | ||
| 338 | - .Where(x => x.IsDeleted == false) | 342 | + var query = _locationRepository._DbQueryable |
| 343 | + .Where(x => x.IsDeleted == false); | ||
| 344 | + | ||
| 345 | + query = LocationRegionScopeHelper.ApplyLocationListScope(query, scope); | ||
| 346 | + | ||
| 347 | + return query | ||
| 339 | .WhereIF(!string.IsNullOrEmpty(partner), x => x.Partner == partner) | 348 | .WhereIF(!string.IsNullOrEmpty(partner), x => x.Partner == partner) |
| 340 | .WhereIF(!string.IsNullOrEmpty(groupName), x => x.GroupName == groupName) | 349 | .WhereIF(!string.IsNullOrEmpty(groupName), x => x.GroupName == groupName) |
| 341 | .WhereIF(input.State is not null, x => x.State == input.State) | 350 | .WhereIF(input.State is not null, x => x.State == input.State) |
| @@ -350,8 +359,7 @@ public class LocationAppService : ApplicationService, ILocationAppService | @@ -350,8 +359,7 @@ public class LocationAppService : ApplicationService, ILocationAppService | ||
| 350 | (x.ZipCode != null && x.ZipCode.Contains(keyword!)) || | 359 | (x.ZipCode != null && x.ZipCode.Contains(keyword!)) || |
| 351 | (x.Phone != null && x.Phone.Contains(keyword!)) || | 360 | (x.Phone != null && x.Phone.Contains(keyword!)) || |
| 352 | (x.Email != null && x.Email.Contains(keyword!)) || | 361 | (x.Email != null && x.Email.Contains(keyword!)) || |
| 353 | - (x.OperatingHours != null && x.OperatingHours.Contains(keyword!)) | ||
| 354 | - ); | 362 | + (x.OperatingHours != null && x.OperatingHours.Contains(keyword!))); |
| 355 | } | 363 | } |
| 356 | 364 | ||
| 357 | private static LocationGetListOutputDto ToListDto(LocationAggregateRoot x) => | 365 | private static LocationGetListOutputDto ToListDto(LocationAggregateRoot x) => |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs
| 1 | using System.Text.Json; | 1 | using System.Text.Json; |
| 2 | using FoodLabeling.Application.Contracts.Constants; | 2 | using FoodLabeling.Application.Contracts.Constants; |
| 3 | -using FoodLabeling.Application.Contracts.Dtos.RbacRole; | ||
| 4 | using FoodLabeling.Application.Helpers; | 3 | using FoodLabeling.Application.Helpers; |
| 4 | +using FoodLabeling.Application.Contracts.Dtos.RbacRole; | ||
| 5 | using FoodLabeling.Application.Contracts.Dtos.Common; | 5 | using FoodLabeling.Application.Contracts.Dtos.Common; |
| 6 | using FoodLabeling.Application.Contracts.IServices; | 6 | using FoodLabeling.Application.Contracts.IServices; |
| 7 | -using FoodLabeling.Application.Services.DbModels; | ||
| 8 | using Microsoft.AspNetCore.Mvc; | 7 | using Microsoft.AspNetCore.Mvc; |
| 9 | using SqlSugar; | 8 | using SqlSugar; |
| 10 | using Volo.Abp; | 9 | using Volo.Abp; |
| @@ -103,8 +102,8 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -103,8 +102,8 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 103 | throw new UserFriendlyException("角色不存在"); | 102 | throw new UserFriendlyException("角色不存在"); |
| 104 | } | 103 | } |
| 105 | 104 | ||
| 106 | - var menuIds = await _dbContext.SqlSugarClient.Queryable<RoleMenuDbEntity>() | ||
| 107 | - .Where(x => x.RoleId == id.ToString()) | 105 | + var menuIds = await _roleMenuRepository._DbQueryable |
| 106 | + .Where(x => x.RoleId == id) | ||
| 108 | .Select(x => x.MenuId) | 107 | .Select(x => x.MenuId) |
| 109 | .ToListAsync(); | 108 | .ToListAsync(); |
| 110 | 109 | ||
| @@ -118,7 +117,7 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -118,7 +117,7 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 118 | State = entity.State, | 117 | State = entity.State, |
| 119 | OrderNum = entity.OrderNum, | 118 | OrderNum = entity.OrderNum, |
| 120 | AccessPermissionCodes = DeserializeAccessPermissionCodes(entity.AccessPermissionCodesJson), | 119 | AccessPermissionCodes = DeserializeAccessPermissionCodes(entity.AccessPermissionCodesJson), |
| 121 | - MenuIds = menuIds | 120 | + MenuIds = menuIds.Select(x => x.ToString()).ToList() |
| 122 | }; | 121 | }; |
| 123 | await FillAccessPermissionsAsync(new List<RbacRoleGetListOutputDto> { dto }); | 122 | await FillAccessPermissionsAsync(new List<RbacRoleGetListOutputDto> { dto }); |
| 124 | return dto; | 123 | return dto; |
| @@ -212,20 +211,41 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -212,20 +211,41 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 212 | } | 211 | } |
| 213 | 212 | ||
| 214 | /// <summary> | 213 | /// <summary> |
| 215 | - /// 新增/编辑时按 menuIds 或 accessPermissions 绑定角色菜单(二者都未传则不改绑定) | 214 | + /// 新增/编辑时按 menuIds 或 accessPermissions 绑定角色菜单(RoleMenu 表)。 |
| 216 | /// </summary> | 215 | /// </summary> |
| 217 | private async Task ApplyRoleMenuBindingsAsync(Guid roleId, RbacRoleCreateInputVo input) | 216 | private async Task ApplyRoleMenuBindingsAsync(Guid roleId, RbacRoleCreateInputVo input) |
| 218 | { | 217 | { |
| 219 | - if (input.MenuIds is not null) | 218 | + var hasMenuIds = input.MenuIds is not null; |
| 219 | + var hasAccessPermissions = input.AccessPermissions is not null; | ||
| 220 | + | ||
| 221 | + if (hasMenuIds && input.MenuIds!.Count > 0) | ||
| 220 | { | 222 | { |
| 221 | await SetRoleMenusAsync(roleId, input.MenuIds); | 223 | await SetRoleMenusAsync(roleId, input.MenuIds); |
| 222 | return; | 224 | return; |
| 223 | } | 225 | } |
| 224 | 226 | ||
| 225 | - if (input.AccessPermissions is not null) | 227 | + if (hasAccessPermissions) |
| 226 | { | 228 | { |
| 229 | + if (string.IsNullOrWhiteSpace(input.AccessPermissions)) | ||
| 230 | + { | ||
| 231 | + await SetRoleMenusAsync(roleId, new List<Guid>()); | ||
| 232 | + return; | ||
| 233 | + } | ||
| 234 | + | ||
| 227 | var menuIds = await ResolveMenuIdsFromAccessPermissionsAsync(input.AccessPermissions); | 235 | var menuIds = await ResolveMenuIdsFromAccessPermissionsAsync(input.AccessPermissions); |
| 236 | + if (menuIds.Count == 0) | ||
| 237 | + { | ||
| 238 | + throw new UserFriendlyException( | ||
| 239 | + "accessPermissions 未匹配到任何菜单,请确认 PermissionCode 与菜单一致,或先执行 menu_backfill_permission_code.sql 回填 Menu.PermissionCode"); | ||
| 240 | + } | ||
| 241 | + | ||
| 228 | await SetRoleMenusAsync(roleId, menuIds); | 242 | await SetRoleMenusAsync(roleId, menuIds); |
| 243 | + return; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + if (hasMenuIds && input.MenuIds!.Count == 0) | ||
| 247 | + { | ||
| 248 | + await SetRoleMenusAsync(roleId, new List<Guid>()); | ||
| 229 | } | 249 | } |
| 230 | } | 250 | } |
| 231 | 251 | ||
| @@ -250,10 +270,15 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -250,10 +270,15 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 250 | return; | 270 | return; |
| 251 | } | 271 | } |
| 252 | 272 | ||
| 253 | - var entities = existMenuIds.Select(menuId => new RoleMenuEntity | 273 | + var entities = existMenuIds.Select(menuId => |
| 254 | { | 274 | { |
| 255 | - RoleId = roleId, | ||
| 256 | - MenuId = menuId | 275 | + var entity = new RoleMenuEntity |
| 276 | + { | ||
| 277 | + RoleId = roleId, | ||
| 278 | + MenuId = menuId | ||
| 279 | + }; | ||
| 280 | + EntityHelper.TrySetId(entity, () => GuidGenerator.Create()); | ||
| 281 | + return entity; | ||
| 257 | }).ToList(); | 282 | }).ToList(); |
| 258 | 283 | ||
| 259 | await _roleMenuRepository.InsertRangeAsync(entities); | 284 | await _roleMenuRepository.InsertRangeAsync(entities); |
| @@ -261,24 +286,28 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -261,24 +286,28 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 261 | 286 | ||
| 262 | private async Task<List<Guid>> ResolveMenuIdsFromAccessPermissionsAsync(string accessPermissions) | 287 | private async Task<List<Guid>> ResolveMenuIdsFromAccessPermissionsAsync(string accessPermissions) |
| 263 | { | 288 | { |
| 264 | - var codes = accessPermissions | ||
| 265 | - .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) | ||
| 266 | - .Where(c => !string.IsNullOrWhiteSpace(c)) | ||
| 267 | - .Distinct(StringComparer.Ordinal) | ||
| 268 | - .ToList(); | ||
| 269 | - | 289 | + var codes = RbacAccessPermissionHelper.ParseAccessPermissionCodes(accessPermissions); |
| 270 | if (codes.Count == 0) | 290 | if (codes.Count == 0) |
| 271 | { | 291 | { |
| 272 | return new List<Guid>(); | 292 | return new List<Guid>(); |
| 273 | } | 293 | } |
| 274 | 294 | ||
| 295 | + var codeSet = new HashSet<string>(codes, StringComparer.OrdinalIgnoreCase); | ||
| 296 | + | ||
| 275 | var menus = await _menuRepository._DbQueryable | 297 | var menus = await _menuRepository._DbQueryable |
| 276 | - .Where(m => m.IsDeleted == false && m.PermissionCode != null) | ||
| 277 | - .Where(m => codes.Contains(m.PermissionCode!)) | ||
| 278 | - .Select(m => m.Id) | 298 | + .Where(m => m.IsDeleted == false) |
| 299 | + .Select(m => new { m.Id, m.PermissionCode, m.Router }) | ||
| 279 | .ToListAsync(); | 300 | .ToListAsync(); |
| 280 | 301 | ||
| 281 | - return menus; | 302 | + return menus |
| 303 | + .Where(m => | ||
| 304 | + { | ||
| 305 | + var effective = RbacAccessPermissionHelper.GetEffectivePermissionCode(m.PermissionCode, m.Router); | ||
| 306 | + return effective is not null && codeSet.Contains(effective); | ||
| 307 | + }) | ||
| 308 | + .Select(m => m.Id) | ||
| 309 | + .Distinct() | ||
| 310 | + .ToList(); | ||
| 282 | } | 311 | } |
| 283 | 312 | ||
| 284 | private async Task FillAccessPermissionsAsync(List<RbacRoleGetListOutputDto> items) | 313 | private async Task FillAccessPermissionsAsync(List<RbacRoleGetListOutputDto> items) |
| @@ -296,7 +325,7 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -296,7 +325,7 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 296 | } | 325 | } |
| 297 | 326 | ||
| 298 | /// <summary> | 327 | /// <summary> |
| 299 | - /// 按角色汇总已绑定菜单上的 PermissionCode(去重、英文逗号+空格拼接) | 328 | + /// Role → RoleMenu → Menu.PermissionCode(空则按 Router 推导)汇总 accessPermissions。 |
| 300 | /// </summary> | 329 | /// </summary> |
| 301 | private async Task<Dictionary<Guid, string>> GetAccessPermissionsByRoleIdsAsync(List<Guid> roleIds) | 330 | private async Task<Dictionary<Guid, string>> GetAccessPermissionsByRoleIdsAsync(List<Guid> roleIds) |
| 302 | { | 331 | { |
| @@ -319,11 +348,13 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -319,11 +348,13 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 319 | var menuIds = links.Select(x => x.MenuId).Distinct().ToList(); | 348 | var menuIds = links.Select(x => x.MenuId).Distinct().ToList(); |
| 320 | var menus = await _menuRepository._DbQueryable | 349 | var menus = await _menuRepository._DbQueryable |
| 321 | .Where(m => menuIds.Contains(m.Id) && m.IsDeleted == false) | 350 | .Where(m => menuIds.Contains(m.Id) && m.IsDeleted == false) |
| 322 | - .Select(m => new { m.Id, m.PermissionCode }) | 351 | + .Select(m => new { m.Id, m.PermissionCode, m.Router }) |
| 323 | .ToListAsync(); | 352 | .ToListAsync(); |
| 324 | - var permByMenuId = menus.ToDictionary(x => x.Id, x => x.PermissionCode); | 353 | + var permByMenuId = menus.ToDictionary( |
| 354 | + x => x.Id, | ||
| 355 | + x => RbacAccessPermissionHelper.GetEffectivePermissionCode(x.PermissionCode, x.Router)); | ||
| 325 | 356 | ||
| 326 | - var byRole = distinctRoleIds.ToDictionary(id => id, _ => new HashSet<string>(StringComparer.Ordinal)); | 357 | + var byRole = distinctRoleIds.ToDictionary(id => id, _ => new HashSet<string>(StringComparer.OrdinalIgnoreCase)); |
| 327 | foreach (var link in links) | 358 | foreach (var link in links) |
| 328 | { | 359 | { |
| 329 | if (!permByMenuId.TryGetValue(link.MenuId, out var code) || string.IsNullOrWhiteSpace(code)) | 360 | if (!permByMenuId.TryGetValue(link.MenuId, out var code) || string.IsNullOrWhiteSpace(code)) |
| @@ -364,7 +395,6 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -364,7 +395,6 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 364 | await _roleDeptRepository.DeleteAsync(x => idList.Contains(x.RoleId)); | 395 | await _roleDeptRepository.DeleteAsync(x => idList.Contains(x.RoleId)); |
| 365 | await _userRoleRepository.DeleteAsync(x => idList.Contains(x.RoleId)); | 396 | await _userRoleRepository.DeleteAsync(x => idList.Contains(x.RoleId)); |
| 366 | 397 | ||
| 367 | - // 角色表为软删(ISoftDelete) | ||
| 368 | await _roleRepository.DeleteAsync(x => idList.Contains(x.Id)); | 398 | await _roleRepository.DeleteAsync(x => idList.Contains(x.Id)); |
| 369 | } | 399 | } |
| 370 | 400 | ||
| @@ -426,4 +456,3 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | @@ -426,4 +456,3 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService | ||
| 426 | .ToList(); | 456 | .ToList(); |
| 427 | } | 457 | } |
| 428 | } | 458 | } |
| 429 | - |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleMenuAppService.cs
| @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; | @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; | ||
| 4 | using SqlSugar; | 4 | using SqlSugar; |
| 5 | using Volo.Abp; | 5 | using Volo.Abp; |
| 6 | using Volo.Abp.Application.Services; | 6 | using Volo.Abp.Application.Services; |
| 7 | +using Volo.Abp.Domain.Entities; | ||
| 7 | using Volo.Abp.Uow; | 8 | using Volo.Abp.Uow; |
| 8 | using Yi.Framework.Rbac.Domain.Entities; | 9 | using Yi.Framework.Rbac.Domain.Entities; |
| 9 | using Yi.Framework.SqlSugarCore.Abstractions; | 10 | using Yi.Framework.SqlSugarCore.Abstractions; |
| @@ -56,10 +57,15 @@ public class RbacRoleMenuAppService : ApplicationService, IRbacRoleMenuAppServic | @@ -56,10 +57,15 @@ public class RbacRoleMenuAppService : ApplicationService, IRbacRoleMenuAppServic | ||
| 56 | 57 | ||
| 57 | await _roleMenuRepository.DeleteAsync(x => x.RoleId == input.RoleId); | 58 | await _roleMenuRepository.DeleteAsync(x => x.RoleId == input.RoleId); |
| 58 | 59 | ||
| 59 | - var entities = existMenuIds.Select(menuId => new RoleMenuEntity | 60 | + var entities = existMenuIds.Select(menuId => |
| 60 | { | 61 | { |
| 61 | - RoleId = input.RoleId, | ||
| 62 | - MenuId = menuId | 62 | + var entity = new RoleMenuEntity |
| 63 | + { | ||
| 64 | + RoleId = input.RoleId, | ||
| 65 | + MenuId = menuId | ||
| 66 | + }; | ||
| 67 | + EntityHelper.TrySetId(entity, () => GuidGenerator.Create()); | ||
| 68 | + return entity; | ||
| 63 | }).ToList(); | 69 | }).ToList(); |
| 64 | 70 | ||
| 65 | if (entities.Count > 0) | 71 | if (entities.Count > 0) |
美国版/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 | @@ -106,6 +106,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 106 | var userMap = await LoadUserNameMapAsync(pageRows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x)) | 106 | var userMap = await LoadUserNameMapAsync(pageRows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x)) |
| 107 | .Select(x => x!).Distinct().ToList()); | 107 | .Select(x => x!).Distinct().ToList()); |
| 108 | 108 | ||
| 109 | + var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync( | ||
| 110 | + _dbContext.SqlSugarClient, | ||
| 111 | + pageRows.Select(x => new ReportsPrintLogDailyLabelIdHelper.PrintTaskScopeKey( | ||
| 112 | + x.Id, | ||
| 113 | + x.LocationId, | ||
| 114 | + x.PrintedAt ?? DateTime.MinValue)).ToList()); | ||
| 115 | + | ||
| 109 | var items = pageRows.Select(x => | 116 | var items = pageRows.Select(x => |
| 110 | { | 117 | { |
| 111 | var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName) | 118 | var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName) |
| @@ -114,10 +121,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -114,10 +121,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 114 | var templateText = FormatTemplateDisplay(x.Width, x.Height, x.Unit, x.TemplateName); | 121 | var templateText = FormatTemplateDisplay(x.Width, x.Height, x.Unit, x.TemplateName); |
| 115 | var locText = FormatLocationText(x.LocName, x.LocCode); | 122 | var locText = FormatLocationText(x.LocName, x.LocCode); |
| 116 | var printedAt = x.PrintedAt ?? DateTime.MinValue; | 123 | var printedAt = x.PrintedAt ?? DateTime.MinValue; |
| 124 | + var labelDisplayId = dailyLabelIdMap.TryGetValue(x.Id, out var dailyId) ? dailyId : "无"; | ||
| 117 | return new ReportsPrintLogListItemDto | 125 | return new ReportsPrintLogListItemDto |
| 118 | { | 126 | { |
| 119 | TaskId = x.Id, | 127 | TaskId = x.Id, |
| 120 | - LabelCode = string.IsNullOrWhiteSpace(x.LabelCode) ? "无" : x.LabelCode.Trim(), | 128 | + LabelCode = labelDisplayId, |
| 121 | ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(), | 129 | ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(), |
| 122 | ProductCategoryName = string.IsNullOrWhiteSpace(x.ProductCategoryName) | 130 | ProductCategoryName = string.IsNullOrWhiteSpace(x.ProductCategoryName) |
| 123 | ? "无" | 131 | ? "无" |
| @@ -131,7 +139,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -131,7 +139,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 131 | PrintedByName = ResolveUserName(userMap, x.CreatedBy), | 139 | PrintedByName = ResolveUserName(userMap, x.CreatedBy), |
| 132 | LocationText = locText, | 140 | LocationText = locText, |
| 133 | LocationId = x.LocationId?.Trim(), | 141 | LocationId = x.LocationId?.Trim(), |
| 134 | - ExpiryDateText = TryExtractExpiryText(x.PrintInputJson) | 142 | + ExpiryDateText = ReportsPrintLogExpiryHelper.ExtractExpiryText(x.PrintInputJson) |
| 135 | }; | 143 | }; |
| 136 | }).ToList(); | 144 | }).ToList(); |
| 137 | 145 | ||
| @@ -191,6 +199,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -191,6 +199,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 191 | t.PrintInputJson, | 199 | t.PrintInputJson, |
| 192 | PrintedAt = SqlFunc.IsNull(t.PrintedAt, t.CreationTime), | 200 | PrintedAt = SqlFunc.IsNull(t.PrintedAt, t.CreationTime), |
| 193 | t.CreatedBy, | 201 | t.CreatedBy, |
| 202 | + t.LocationId, | ||
| 194 | LocName = loc.LocationName, | 203 | LocName = loc.LocationName, |
| 195 | LocCode = loc.LocationCode | 204 | LocCode = loc.LocationCode |
| 196 | }) | 205 | }) |
| @@ -199,6 +208,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -199,6 +208,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 199 | var userMap = await LoadUserNameMapAsync(rows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x)) | 208 | var userMap = await LoadUserNameMapAsync(rows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x)) |
| 200 | .Select(x => x!).Distinct().ToList()); | 209 | .Select(x => x!).Distinct().ToList()); |
| 201 | 210 | ||
| 211 | + var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync( | ||
| 212 | + _dbContext.SqlSugarClient, | ||
| 213 | + rows.Select(x => new ReportsPrintLogDailyLabelIdHelper.PrintTaskScopeKey( | ||
| 214 | + x.Id, | ||
| 215 | + x.LocationId, | ||
| 216 | + x.PrintedAt ?? DateTime.MinValue)).ToList()); | ||
| 217 | + | ||
| 202 | var fileName = $"print-log_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf"; | 218 | var fileName = $"print-log_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf"; |
| 203 | var document = Document.Create(container => | 219 | var document = Document.Create(container => |
| 204 | { | 220 | { |
| @@ -236,8 +252,9 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -236,8 +252,9 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 236 | var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName) | 252 | var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName) |
| 237 | ? x.ProductCategoryName!.Trim() | 253 | ? x.ProductCategoryName!.Trim() |
| 238 | : (string.IsNullOrWhiteSpace(x.LabelCategoryName) ? "无" : x.LabelCategoryName.Trim()); | 254 | : (string.IsNullOrWhiteSpace(x.LabelCategoryName) ? "无" : x.LabelCategoryName.Trim()); |
| 255 | + var labelDisplayId = dailyLabelIdMap.TryGetValue(x.Id, out var dailyId) ? dailyId : "无"; | ||
| 239 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) | 256 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) |
| 240 | - .Text(string.IsNullOrWhiteSpace(x.LabelCode) ? "无" : x.LabelCode.Trim()); | 257 | + .Text(labelDisplayId); |
| 241 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) | 258 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) |
| 242 | .Text(string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim()); | 259 | .Text(string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim()); |
| 243 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3).Text(cat); | 260 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3).Text(cat); |
| @@ -251,7 +268,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -251,7 +268,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 251 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) | 268 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) |
| 252 | .Text(FormatLocationText(x.LocName, x.LocCode)); | 269 | .Text(FormatLocationText(x.LocName, x.LocCode)); |
| 253 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) | 270 | table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(3) |
| 254 | - .Text(TryExtractExpiryText(x.PrintInputJson)); | 271 | + .Text(ReportsPrintLogExpiryHelper.ExtractExpiryText(x.PrintInputJson)); |
| 255 | } | 272 | } |
| 256 | }); | 273 | }); |
| 257 | }); | 274 | }); |
| @@ -338,7 +355,14 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -338,7 +355,14 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 338 | var userMap = await LoadUserNameMapAsync(pageRows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x)) | 355 | var userMap = await LoadUserNameMapAsync(pageRows.Select(x => x.CreatedBy).Where(x => !string.IsNullOrWhiteSpace(x)) |
| 339 | .Select(x => x!).Distinct().ToList()); | 356 | .Select(x => x!).Distinct().ToList()); |
| 340 | 357 | ||
| 341 | - var items = pageRows.Select(x => MapPrintLogExportRowToListItem(x, userMap)).ToList(); | 358 | + var dailyLabelIdMap = await ReportsPrintLogDailyLabelIdHelper.ResolveDailyLabelIdsAsync( |
| 359 | + _dbContext.SqlSugarClient, | ||
| 360 | + pageRows.Select(x => new ReportsPrintLogDailyLabelIdHelper.PrintTaskScopeKey( | ||
| 361 | + x.Id, | ||
| 362 | + x.LocationId, | ||
| 363 | + x.PrintedAt ?? DateTime.MinValue)).ToList()); | ||
| 364 | + | ||
| 365 | + var items = pageRows.Select(x => MapPrintLogExportRowToListItem(x, userMap, dailyLabelIdMap)).ToList(); | ||
| 342 | 366 | ||
| 343 | var ms = ReportsPrintLogExcelHelper.BuildWorkbook(items); | 367 | var ms = ReportsPrintLogExcelHelper.BuildWorkbook(items); |
| 344 | var fileName = $"print-log_{Clock.Now:yyyyMMdd-HHmmss}.xlsx"; | 368 | var fileName = $"print-log_{Clock.Now:yyyyMMdd-HHmmss}.xlsx"; |
| @@ -351,6 +375,84 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -351,6 +375,84 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 351 | _usAppLabelingAppService.ReprintAsync(input); | 375 | _usAppLabelingAppService.ReprintAsync(input); |
| 352 | 376 | ||
| 353 | /// <inheritdoc /> | 377 | /// <inheritdoc /> |
| 378 | + public async Task<PagedResultWithPageDto<ReportsTemplatePrintStatListItemDto>> GetTemplatePrintStatListAsync( | ||
| 379 | + ReportsTemplatePrintStatGetListInputVo input) | ||
| 380 | + { | ||
| 381 | + if (input is null) | ||
| 382 | + { | ||
| 383 | + throw new UserFriendlyException("入参不能为空"); | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + if (!CurrentUser.Id.HasValue) | ||
| 387 | + { | ||
| 388 | + throw new UserFriendlyException("用户未登录"); | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + var locationIds = await ReportsLocationScopeHelper.ResolveReportLocationIdsAsync( | ||
| 392 | + CurrentUser, | ||
| 393 | + _dbContext.SqlSugarClient, | ||
| 394 | + input.PartnerId, | ||
| 395 | + input.GroupId, | ||
| 396 | + input.LocationId); | ||
| 397 | + if (locationIds is not null && locationIds.Count == 0) | ||
| 398 | + { | ||
| 399 | + return EmptyTemplatePrintStatPage(input); | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + var (rangeStart, rangeEndExcl) = ResolveDateRange(input.StartDate, input.EndDate); | ||
| 403 | + var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser); | ||
| 404 | + var currentUserIdStr = CurrentUser.Id.Value.ToString(); | ||
| 405 | + var templateKeyword = input.Keyword?.Trim(); | ||
| 406 | + | ||
| 407 | + var groupedRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword: null, | ||
| 408 | + restrictToCreator: false) | ||
| 409 | + .LeftJoin<FlLabelTemplateDbEntity>((t, l, p, lc, pc, loc, tpl) => t.TemplateId == tpl.Id) | ||
| 410 | + .Where((t, l, p, lc, pc, loc, tpl) => | ||
| 411 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= rangeStart && | ||
| 412 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < rangeEndExcl) | ||
| 413 | + .WhereIF(!string.IsNullOrWhiteSpace(templateKeyword), | ||
| 414 | + (t, l, p, lc, pc, loc, tpl) => | ||
| 415 | + tpl.TemplateName != null && tpl.TemplateName.Contains(templateKeyword!)) | ||
| 416 | + .GroupBy((t, l, p, lc, pc, loc, tpl) => new { t.TemplateId, tpl.TemplateName }) | ||
| 417 | + .Select((t, l, p, lc, pc, loc, tpl) => new | ||
| 418 | + { | ||
| 419 | + t.TemplateId, | ||
| 420 | + tpl.TemplateName, | ||
| 421 | + Cnt = SqlFunc.AggregateCount(t.Id) | ||
| 422 | + }) | ||
| 423 | + .ToListAsync(); | ||
| 424 | + | ||
| 425 | + var ordered = groupedRows | ||
| 426 | + .Select(x => new ReportsTemplatePrintStatListItemDto | ||
| 427 | + { | ||
| 428 | + TemplateId = string.IsNullOrWhiteSpace(x.TemplateId) ? null : x.TemplateId.Trim(), | ||
| 429 | + TemplateName = string.IsNullOrWhiteSpace(x.TemplateName) ? "无" : x.TemplateName.Trim(), | ||
| 430 | + PrintedCount = x.Cnt | ||
| 431 | + }) | ||
| 432 | + .ToList(); | ||
| 433 | + | ||
| 434 | + if (!string.IsNullOrWhiteSpace(input.Sorting) && | ||
| 435 | + input.Sorting.Trim().Equals("PrintedCount asc", StringComparison.OrdinalIgnoreCase)) | ||
| 436 | + { | ||
| 437 | + ordered = ordered.OrderBy(x => x.PrintedCount).ThenBy(x => x.TemplateName).ToList(); | ||
| 438 | + } | ||
| 439 | + else | ||
| 440 | + { | ||
| 441 | + ordered = ordered.OrderByDescending(x => x.PrintedCount).ThenBy(x => x.TemplateName).ToList(); | ||
| 442 | + } | ||
| 443 | + | ||
| 444 | + var total = ordered.Count; | ||
| 445 | + var pageIndex = PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount); | ||
| 446 | + var pageSize = input.MaxResultCount <= 0 ? total : input.MaxResultCount; | ||
| 447 | + var offset = pageSize <= 0 ? 0 : (pageIndex - 1) * pageSize; | ||
| 448 | + var pageItems = pageSize <= 0 | ||
| 449 | + ? ordered | ||
| 450 | + : ordered.Skip(offset).Take(pageSize).ToList(); | ||
| 451 | + | ||
| 452 | + return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, pageItems); | ||
| 453 | + } | ||
| 454 | + | ||
| 455 | + /// <inheritdoc /> | ||
| 354 | public async Task<ReportsLabelReportOutputDto> GetLabelReportAsync(ReportsLabelReportQueryInputVo input) | 456 | public async Task<ReportsLabelReportOutputDto> GetLabelReportAsync(ReportsLabelReportQueryInputVo input) |
| 355 | { | 457 | { |
| 356 | if (input is null) | 458 | if (input is null) |
| @@ -363,7 +465,12 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -363,7 +465,12 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 363 | throw new UserFriendlyException("用户未登录"); | 465 | throw new UserFriendlyException("用户未登录"); |
| 364 | } | 466 | } |
| 365 | 467 | ||
| 366 | - var locationIds = await ResolveFilteredLocationIdsAsync(input.PartnerId, input.GroupId, input.LocationId); | 468 | + var locationIds = await ReportsLocationScopeHelper.ResolveReportLocationIdsAsync( |
| 469 | + CurrentUser, | ||
| 470 | + _dbContext.SqlSugarClient, | ||
| 471 | + input.PartnerId, | ||
| 472 | + input.GroupId, | ||
| 473 | + input.LocationId); | ||
| 367 | if (locationIds is not null && locationIds.Count == 0) | 474 | if (locationIds is not null && locationIds.Count == 0) |
| 368 | { | 475 | { |
| 369 | return new ReportsLabelReportOutputDto(); | 476 | return new ReportsLabelReportOutputDto(); |
| @@ -382,13 +489,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -382,13 +489,13 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 382 | var currentUserIdStr = CurrentUser.Id.Value.ToString(); | 489 | var currentUserIdStr = CurrentUser.Id.Value.ToString(); |
| 383 | var keyword = input.Keyword?.Trim(); | 490 | var keyword = input.Keyword?.Trim(); |
| 384 | 491 | ||
| 385 | - var totalCur = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword) | 492 | + var totalCur = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false) |
| 386 | .Where((t, l, p, lc, pc, loc) => | 493 | .Where((t, l, p, lc, pc, loc) => |
| 387 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && | 494 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && |
| 388 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl) | 495 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl) |
| 389 | .CountAsync(); | 496 | .CountAsync(); |
| 390 | 497 | ||
| 391 | - var totalPrev = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword) | 498 | + var totalPrev = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false) |
| 392 | .Where((t, l, p, lc, pc, loc) => | 499 | .Where((t, l, p, lc, pc, loc) => |
| 393 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= prevStart && | 500 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= prevStart && |
| 394 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < prevEndExcl) | 501 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < prevEndExcl) |
| @@ -399,7 +506,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -399,7 +506,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 399 | var avgDaily = Math.Round((decimal)totalCur / dayCount, 2); | 506 | var avgDaily = Math.Round((decimal)totalCur / dayCount, 2); |
| 400 | var avgDailyPrev = Math.Round((decimal)totalPrev / prevDayCount, 2); | 507 | var avgDailyPrev = Math.Round((decimal)totalPrev / prevDayCount, 2); |
| 401 | 508 | ||
| 402 | - var categoryRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword) | 509 | + var categoryRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false) |
| 403 | .Where((t, l, p, lc, pc, loc) => | 510 | .Where((t, l, p, lc, pc, loc) => |
| 404 | l.LabelCategoryId != null && | 511 | l.LabelCategoryId != null && |
| 405 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && | 512 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && |
| @@ -410,7 +517,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -410,7 +517,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 410 | 517 | ||
| 411 | var topCat = categoryRows.OrderByDescending(x => x.Cnt).FirstOrDefault(); | 518 | var topCat = categoryRows.OrderByDescending(x => x.Cnt).FirstOrDefault(); |
| 412 | 519 | ||
| 413 | - var productRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword) | 520 | + var productRows = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false) |
| 414 | .Where((t, l, p, lc, pc, loc) => | 521 | .Where((t, l, p, lc, pc, loc) => |
| 415 | !string.IsNullOrEmpty(p.Id) && | 522 | !string.IsNullOrEmpty(p.Id) && |
| 416 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && | 523 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && |
| @@ -431,7 +538,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -431,7 +538,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 431 | 538 | ||
| 432 | var trendEndExcl = trendEndDay.AddDays(1); | 539 | var trendEndExcl = trendEndDay.AddDays(1); |
| 433 | 540 | ||
| 434 | - var trendRaw = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword) | 541 | + var trendRaw = await BuildReportTaskCore(locationIds, isAdmin, currentUserIdStr, keyword, restrictToCreator: false) |
| 435 | .Where((t, l, p, lc, pc, loc) => | 542 | .Where((t, l, p, lc, pc, loc) => |
| 436 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= trendStartDay && | 543 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= trendStartDay && |
| 437 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < trendEndExcl) | 544 | SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < trendEndExcl) |
| @@ -585,7 +692,8 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -585,7 +692,8 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 585 | List<string>? locationIds, | 692 | List<string>? locationIds, |
| 586 | bool isAdmin, | 693 | bool isAdmin, |
| 587 | string currentUserIdStr, | 694 | string currentUserIdStr, |
| 588 | - string? keyword) | 695 | + string? keyword, |
| 696 | + bool restrictToCreator = true) | ||
| 589 | { | 697 | { |
| 590 | return _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>() | 698 | return _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>() |
| 591 | .LeftJoin<FlLabelDbEntity>((t, l) => t.LabelId == l.Id) | 699 | .LeftJoin<FlLabelDbEntity>((t, l) => t.LabelId == l.Id) |
| @@ -595,7 +703,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -595,7 +703,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 595 | .LeftJoin<LocationAggregateRoot>((t, l, p, lc, pc, loc) => | 703 | .LeftJoin<LocationAggregateRoot>((t, l, p, lc, pc, loc) => |
| 596 | t.LocationId != null && SqlFunc.ToString(loc.Id) == t.LocationId) | 704 | t.LocationId != null && SqlFunc.ToString(loc.Id) == t.LocationId) |
| 597 | .Where((t, l, p, lc, pc, loc) => !loc.IsDeleted) | 705 | .Where((t, l, p, lc, pc, loc) => !loc.IsDeleted) |
| 598 | - .WhereIF(!isAdmin, (t, l, p, lc, pc, loc) => t.CreatedBy == currentUserIdStr) | 706 | + .WhereIF(restrictToCreator && !isAdmin, (t, l, p, lc, pc, loc) => t.CreatedBy == currentUserIdStr) |
| 599 | .WhereIF(locationIds is not null, (t, l, p, lc, pc, loc) => locationIds!.Contains(t.LocationId!)) | 707 | .WhereIF(locationIds is not null, (t, l, p, lc, pc, loc) => locationIds!.Contains(t.LocationId!)) |
| 600 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), | 708 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), |
| 601 | (t, l, p, lc, pc, loc) => | 709 | (t, l, p, lc, pc, loc) => |
| @@ -697,6 +805,21 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -697,6 +805,21 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 697 | }; | 805 | }; |
| 698 | } | 806 | } |
| 699 | 807 | ||
| 808 | + private static PagedResultWithPageDto<ReportsTemplatePrintStatListItemDto> EmptyTemplatePrintStatPage( | ||
| 809 | + ReportsTemplatePrintStatGetListInputVo input) | ||
| 810 | + { | ||
| 811 | + var pageSize = input.MaxResultCount <= 0 ? 0 : input.MaxResultCount; | ||
| 812 | + var pageIndex = pageSize <= 0 ? 1 : PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount); | ||
| 813 | + return new PagedResultWithPageDto<ReportsTemplatePrintStatListItemDto> | ||
| 814 | + { | ||
| 815 | + PageIndex = pageIndex, | ||
| 816 | + PageSize = pageSize, | ||
| 817 | + TotalCount = 0, | ||
| 818 | + TotalPages = 0, | ||
| 819 | + Items = new List<ReportsTemplatePrintStatListItemDto>() | ||
| 820 | + }; | ||
| 821 | + } | ||
| 822 | + | ||
| 700 | private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, | 823 | private static PagedResultWithPageDto<T> BuildPagedResult<T>(int skipCount, int maxResultCount, int total, |
| 701 | List<T> items) | 824 | List<T> items) |
| 702 | { | 825 | { |
| @@ -799,54 +922,6 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -799,54 +922,6 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 799 | return $"{ws}x{hs}{normalizedUnit}"; | 922 | return $"{ws}x{hs}{normalizedUnit}"; |
| 800 | } | 923 | } |
| 801 | 924 | ||
| 802 | - private static string TryExtractExpiryText(string? printInputJson) | ||
| 803 | - { | ||
| 804 | - if (string.IsNullOrWhiteSpace(printInputJson)) | ||
| 805 | - { | ||
| 806 | - return "无"; | ||
| 807 | - } | ||
| 808 | - | ||
| 809 | - try | ||
| 810 | - { | ||
| 811 | - using var doc = JsonDocument.Parse(printInputJson); | ||
| 812 | - if (doc.RootElement.ValueKind != JsonValueKind.Object) | ||
| 813 | - { | ||
| 814 | - return "无"; | ||
| 815 | - } | ||
| 816 | - | ||
| 817 | - foreach (var prop in doc.RootElement.EnumerateObject()) | ||
| 818 | - { | ||
| 819 | - var key = prop.Name.Trim(); | ||
| 820 | - if (!key.Equals("expiryDate", StringComparison.OrdinalIgnoreCase) && | ||
| 821 | - !key.Equals("expiry", StringComparison.OrdinalIgnoreCase) && | ||
| 822 | - !key.Equals("expirationDate", StringComparison.OrdinalIgnoreCase)) | ||
| 823 | - { | ||
| 824 | - continue; | ||
| 825 | - } | ||
| 826 | - | ||
| 827 | - var v = prop.Value; | ||
| 828 | - if (v.ValueKind == JsonValueKind.String) | ||
| 829 | - { | ||
| 830 | - var s = v.GetString(); | ||
| 831 | - return string.IsNullOrWhiteSpace(s) ? "无" : s.Trim(); | ||
| 832 | - } | ||
| 833 | - | ||
| 834 | - if (v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var n)) | ||
| 835 | - { | ||
| 836 | - return n.ToString(CultureInfo.InvariantCulture); | ||
| 837 | - } | ||
| 838 | - | ||
| 839 | - return v.ToString(); | ||
| 840 | - } | ||
| 841 | - } | ||
| 842 | - catch | ||
| 843 | - { | ||
| 844 | - return "无"; | ||
| 845 | - } | ||
| 846 | - | ||
| 847 | - return "无"; | ||
| 848 | - } | ||
| 849 | - | ||
| 850 | private static IActionResult BuildEmptyPdf(string fileName) | 925 | private static IActionResult BuildEmptyPdf(string fileName) |
| 851 | { | 926 | { |
| 852 | QuestPDF.Settings.License = LicenseType.Community; | 927 | QuestPDF.Settings.License = LicenseType.Community; |
| @@ -897,8 +972,10 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -897,8 +972,10 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 897 | public string? LocCode { get; set; } | 972 | public string? LocCode { get; set; } |
| 898 | } | 973 | } |
| 899 | 974 | ||
| 900 | - private static ReportsPrintLogListItemDto MapPrintLogExportRowToListItem(PrintLogExportRow x, | ||
| 901 | - Dictionary<string, string> userMap) | 975 | + private static ReportsPrintLogListItemDto MapPrintLogExportRowToListItem( |
| 976 | + PrintLogExportRow x, | ||
| 977 | + Dictionary<string, string> userMap, | ||
| 978 | + IReadOnlyDictionary<string, string> dailyLabelIdMap) | ||
| 902 | { | 979 | { |
| 903 | var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName) | 980 | var cat = !string.IsNullOrWhiteSpace(x.ProductCategoryName) |
| 904 | ? x.ProductCategoryName!.Trim() | 981 | ? x.ProductCategoryName!.Trim() |
| @@ -906,10 +983,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -906,10 +983,11 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 906 | var templateText = FormatTemplateDisplay(x.Width, x.Height, x.Unit, x.TemplateName); | 983 | var templateText = FormatTemplateDisplay(x.Width, x.Height, x.Unit, x.TemplateName); |
| 907 | var locText = FormatLocationText(x.LocName, x.LocCode); | 984 | var locText = FormatLocationText(x.LocName, x.LocCode); |
| 908 | var printedAt = x.PrintedAt ?? DateTime.MinValue; | 985 | var printedAt = x.PrintedAt ?? DateTime.MinValue; |
| 986 | + var labelDisplayId = dailyLabelIdMap.TryGetValue(x.Id, out var dailyId) ? dailyId : "无"; | ||
| 909 | return new ReportsPrintLogListItemDto | 987 | return new ReportsPrintLogListItemDto |
| 910 | { | 988 | { |
| 911 | TaskId = x.Id, | 989 | TaskId = x.Id, |
| 912 | - LabelCode = string.IsNullOrWhiteSpace(x.LabelCode) ? "无" : x.LabelCode.Trim(), | 990 | + LabelCode = labelDisplayId, |
| 913 | ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(), | 991 | ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(), |
| 914 | ProductCategoryName = string.IsNullOrWhiteSpace(x.ProductCategoryName) | 992 | ProductCategoryName = string.IsNullOrWhiteSpace(x.ProductCategoryName) |
| 915 | ? "无" | 993 | ? "无" |
| @@ -923,7 +1001,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | @@ -923,7 +1001,7 @@ public class ReportsAppService : ApplicationService, IReportsAppService | ||
| 923 | PrintedByName = ResolveUserName(userMap, x.CreatedBy), | 1001 | PrintedByName = ResolveUserName(userMap, x.CreatedBy), |
| 924 | LocationText = locText, | 1002 | LocationText = locText, |
| 925 | LocationId = x.LocationId?.Trim(), | 1003 | LocationId = x.LocationId?.Trim(), |
| 926 | - ExpiryDateText = TryExtractExpiryText(x.PrintInputJson) | 1004 | + ExpiryDateText = ReportsPrintLogExpiryHelper.ExtractExpiryText(x.PrintInputJson) |
| 927 | }; | 1005 | }; |
| 928 | } | 1006 | } |
| 929 | } | 1007 | } |
美国版/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; | @@ -17,6 +17,8 @@ using Volo.Abp.Application.Services; | ||
| 17 | using Volo.Abp.Domain.Entities; | 17 | using Volo.Abp.Domain.Entities; |
| 18 | using Volo.Abp.Guids; | 18 | using Volo.Abp.Guids; |
| 19 | using Yi.Framework.Rbac.Domain.Entities; | 19 | using Yi.Framework.Rbac.Domain.Entities; |
| 20 | +using Yi.Framework.Rbac.Domain.Entities.ValueObjects; | ||
| 21 | +using Yi.Framework.Rbac.Domain.Helpers; | ||
| 20 | using Yi.Framework.Rbac.Domain.Managers; | 22 | using Yi.Framework.Rbac.Domain.Managers; |
| 21 | using Yi.Framework.SqlSugarCore.Abstractions; | 23 | using Yi.Framework.SqlSugarCore.Abstractions; |
| 22 | 24 | ||
| @@ -54,7 +56,10 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -54,7 +56,10 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 54 | var pageSize = input.MaxResultCount; | 56 | var pageSize = input.MaxResultCount; |
| 55 | RefAsync<int> total = 0; | 57 | RefAsync<int> total = 0; |
| 56 | 58 | ||
| 57 | - var query = await BuildFilteredUserQueryAsync(input); | 59 | + var scopeLocationIds = await LocationScopeBindingHelper.ResolveFilteredLocationIdsForListAsync( |
| 60 | + _dbContext.SqlSugarClient, input.PartnerId, input.GroupId, input.LocationId); | ||
| 61 | + | ||
| 62 | + var query = await BuildFilteredUserQueryAsync(input, scopeLocationIds); | ||
| 58 | var users = await query | 63 | var users = await query |
| 59 | .OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!) | 64 | .OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!) |
| 60 | .OrderByDescending(u => u.CreationTime) | 65 | .OrderByDescending(u => u.CreationTime) |
| @@ -62,8 +67,8 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -62,8 +67,8 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 62 | 67 | ||
| 63 | var items = await MapUsersToOutputAsync( | 68 | var items = await MapUsersToOutputAsync( |
| 64 | users, | 69 | users, |
| 65 | - input.LocationId, | ||
| 66 | - restrictAssignedLocationsToFilter: !string.IsNullOrWhiteSpace(input.LocationId)); | 70 | + scopeLocationIds, |
| 71 | + restrictAssignedLocationsToFilter: scopeLocationIds is not null); | ||
| 67 | 72 | ||
| 68 | var totalCount = (long)total; | 73 | var totalCount = (long)total; |
| 69 | return new PagedResultWithPageDto<TeamMemberGetListOutputDto> | 74 | return new PagedResultWithPageDto<TeamMemberGetListOutputDto> |
| @@ -133,11 +138,15 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -133,11 +138,15 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 133 | { | 138 | { |
| 134 | var mergedLocationIds = await ResolveTeamMemberLocationIdsForSaveAsync(input); | 139 | var mergedLocationIds = await ResolveTeamMemberLocationIdsForSaveAsync(input); |
| 135 | 140 | ||
| 136 | - var user = new UserAggregateRoot(input.UserName.Trim(), input.Password, input.Phone, input.FullName.Trim()) | 141 | + var user = new UserAggregateRoot |
| 137 | { | 142 | { |
| 143 | + UserName = input.UserName.Trim(), | ||
| 138 | Name = input.FullName.Trim(), | 144 | Name = input.FullName.Trim(), |
| 145 | + Nick = input.FullName.Trim(), | ||
| 139 | Email = input.Email?.Trim(), | 146 | Email = input.Email?.Trim(), |
| 140 | - State = input.State | 147 | + Phone = input.Phone, |
| 148 | + State = input.State, | ||
| 149 | + EncryPassword = new EncryPasswordValueObject(input.Password.Trim()) | ||
| 141 | }; | 150 | }; |
| 142 | 151 | ||
| 143 | EntityHelper.TrySetId(user, _guidGenerator.Create); | 152 | EntityHelper.TrySetId(user, _guidGenerator.Create); |
| @@ -172,13 +181,22 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -172,13 +181,22 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 172 | user.Phone = input.Phone; | 181 | user.Phone = input.Phone; |
| 173 | user.State = input.State; | 182 | user.State = input.State; |
| 174 | 183 | ||
| 184 | + var passwordChanged = false; | ||
| 175 | if (!string.IsNullOrWhiteSpace(input.Password)) | 185 | if (!string.IsNullOrWhiteSpace(input.Password)) |
| 176 | { | 186 | { |
| 177 | - user.EncryPassword.Password = input.Password; | ||
| 178 | - user.BuildPassword(); | 187 | + UserPasswordHelper.ApplyPlainPassword(user, input.Password); |
| 188 | + passwordChanged = true; | ||
| 179 | } | 189 | } |
| 180 | 190 | ||
| 181 | await _userRepository.UpdateAsync(user); | 191 | await _userRepository.UpdateAsync(user); |
| 192 | + if (passwordChanged) | ||
| 193 | + { | ||
| 194 | + await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync( | ||
| 195 | + _userRepository, | ||
| 196 | + user.Id, | ||
| 197 | + user.EncryPassword.Password, | ||
| 198 | + user.EncryPassword.Salt); | ||
| 199 | + } | ||
| 182 | 200 | ||
| 183 | if (input.RoleId != null) | 201 | if (input.RoleId != null) |
| 184 | { | 202 | { |
| @@ -254,13 +272,19 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -254,13 +272,19 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 254 | { | 272 | { |
| 255 | QuestPDF.Settings.License = LicenseType.Community; | 273 | QuestPDF.Settings.License = LicenseType.Community; |
| 256 | 274 | ||
| 257 | - var query = await BuildFilteredUserQueryAsync(input); | 275 | + var scopeLocationIds = await LocationScopeBindingHelper.ResolveFilteredLocationIdsForListAsync( |
| 276 | + _dbContext.SqlSugarClient, input.PartnerId, input.GroupId, input.LocationId); | ||
| 277 | + | ||
| 278 | + var query = await BuildFilteredUserQueryAsync(input, scopeLocationIds); | ||
| 258 | var users = await query | 279 | var users = await query |
| 259 | .OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!) | 280 | .OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!) |
| 260 | .OrderByDescending(u => u.CreationTime) | 281 | .OrderByDescending(u => u.CreationTime) |
| 261 | .ToListAsync(); | 282 | .ToListAsync(); |
| 262 | 283 | ||
| 263 | - var rows = await MapUsersToOutputAsync(users, locationFilter: null, restrictAssignedLocationsToFilter: false); | 284 | + var rows = await MapUsersToOutputAsync( |
| 285 | + users, | ||
| 286 | + scopeLocationIds, | ||
| 287 | + restrictAssignedLocationsToFilter: scopeLocationIds is not null); | ||
| 264 | 288 | ||
| 265 | var fileName = $"team-members_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf"; | 289 | var fileName = $"team-members_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf"; |
| 266 | var document = Document.Create(container => | 290 | var document = Document.Create(container => |
| @@ -492,7 +516,9 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -492,7 +516,9 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 492 | return result.Distinct().ToList(); | 516 | return result.Distinct().ToList(); |
| 493 | } | 517 | } |
| 494 | 518 | ||
| 495 | - private async Task<ISugarQueryable<UserAggregateRoot>> BuildFilteredUserQueryAsync(TeamMemberGetListInputVo input) | 519 | + private async Task<ISugarQueryable<UserAggregateRoot>> BuildFilteredUserQueryAsync( |
| 520 | + TeamMemberGetListInputVo input, | ||
| 521 | + List<string>? scopeLocationIds) | ||
| 496 | { | 522 | { |
| 497 | var keyword = input.Keyword?.Trim(); | 523 | var keyword = input.Keyword?.Trim(); |
| 498 | var query = _userRepository._DbQueryable | 524 | var query = _userRepository._DbQueryable |
| @@ -513,15 +539,22 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -513,15 +539,22 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 513 | query = query.Where(u => userIds.Contains(u.Id)); | 539 | query = query.Where(u => userIds.Contains(u.Id)); |
| 514 | } | 540 | } |
| 515 | 541 | ||
| 516 | - if (!string.IsNullOrWhiteSpace(input.LocationId)) | 542 | + if (scopeLocationIds is not null) |
| 517 | { | 543 | { |
| 518 | - var locId = input.LocationId.Trim(); | ||
| 519 | - var userIdStrs = await _dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>() | ||
| 520 | - .Where(x => !x.IsDeleted && x.LocationId == locId) | ||
| 521 | - .Select(x => x.UserId) | ||
| 522 | - .ToListAsync(); | ||
| 523 | - var allowed = new HashSet<string>(userIdStrs); | ||
| 524 | - query = query.Where(u => allowed.Contains(u.Id.ToString())); | 544 | + if (scopeLocationIds.Count == 0) |
| 545 | + { | ||
| 546 | + query = query.Where(_ => false); | ||
| 547 | + } | ||
| 548 | + else | ||
| 549 | + { | ||
| 550 | + var scopeSet = new HashSet<string>(scopeLocationIds, StringComparer.Ordinal); | ||
| 551 | + var userIdStrs = await _dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>() | ||
| 552 | + .Where(x => !x.IsDeleted && scopeSet.Contains(x.LocationId)) | ||
| 553 | + .Select(x => x.UserId) | ||
| 554 | + .ToListAsync(); | ||
| 555 | + var allowed = new HashSet<string>(userIdStrs); | ||
| 556 | + query = query.Where(u => allowed.Contains(u.Id.ToString())); | ||
| 557 | + } | ||
| 525 | } | 558 | } |
| 526 | 559 | ||
| 527 | return query; | 560 | return query; |
| @@ -529,7 +562,7 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -529,7 +562,7 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 529 | 562 | ||
| 530 | private async Task<List<TeamMemberGetListOutputDto>> MapUsersToOutputAsync( | 563 | private async Task<List<TeamMemberGetListOutputDto>> MapUsersToOutputAsync( |
| 531 | List<UserAggregateRoot> users, | 564 | List<UserAggregateRoot> users, |
| 532 | - string? locationFilter, | 565 | + List<string>? scopeLocationIds, |
| 533 | bool restrictAssignedLocationsToFilter) | 566 | bool restrictAssignedLocationsToFilter) |
| 534 | { | 567 | { |
| 535 | if (users.Count == 0) | 568 | if (users.Count == 0) |
| @@ -552,9 +585,10 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | @@ -552,9 +585,10 @@ public class TeamMemberAppService : ApplicationService, ITeamMemberAppService | ||
| 552 | var userLocQuery = _dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>() | 585 | var userLocQuery = _dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>() |
| 553 | .Where(x => !x.IsDeleted) | 586 | .Where(x => !x.IsDeleted) |
| 554 | .Where(x => userIdStrings.Contains(x.UserId)); | 587 | .Where(x => userIdStrings.Contains(x.UserId)); |
| 555 | - if (restrictAssignedLocationsToFilter && !string.IsNullOrWhiteSpace(locationFilter)) | 588 | + if (restrictAssignedLocationsToFilter && scopeLocationIds is { Count: > 0 }) |
| 556 | { | 589 | { |
| 557 | - userLocQuery = userLocQuery.Where(x => x.LocationId == locationFilter.Trim()); | 590 | + var scopeSet = new HashSet<string>(scopeLocationIds, StringComparer.Ordinal); |
| 591 | + userLocQuery = userLocQuery.Where(x => scopeSet.Contains(x.LocationId)); | ||
| 558 | } | 592 | } |
| 559 | 593 | ||
| 560 | var userLocations = await userLocQuery.ToListAsync(); | 594 | var userLocations = await userLocQuery.ToListAsync(); |
美国版/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; | @@ -25,10 +25,10 @@ using Volo.Abp.EventBus.Local; | ||
| 25 | using Volo.Abp.Security.Claims; | 25 | using Volo.Abp.Security.Claims; |
| 26 | using Volo.Abp.Uow; | 26 | using Volo.Abp.Uow; |
| 27 | using Volo.Abp.Users; | 27 | using Volo.Abp.Users; |
| 28 | -using Yi.Framework.Core.Helper; | ||
| 29 | using Yi.Framework.Rbac.Application.Contracts.Dtos.Account; | 28 | using Yi.Framework.Rbac.Application.Contracts.Dtos.Account; |
| 30 | using Yi.Framework.Rbac.Application.Contracts.IServices; | 29 | using Yi.Framework.Rbac.Application.Contracts.IServices; |
| 31 | using Yi.Framework.Rbac.Domain.Entities; | 30 | using Yi.Framework.Rbac.Domain.Entities; |
| 31 | +using Yi.Framework.Rbac.Domain.Helpers; | ||
| 32 | using Yi.Framework.Rbac.Domain.Managers; | 32 | using Yi.Framework.Rbac.Domain.Managers; |
| 33 | using Yi.Framework.Rbac.Domain.Shared.Consts; | 33 | using Yi.Framework.Rbac.Domain.Shared.Consts; |
| 34 | using Yi.Framework.Rbac.Domain.Shared.Dtos; | 34 | using Yi.Framework.Rbac.Domain.Shared.Dtos; |
| @@ -102,7 +102,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | @@ -102,7 +102,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | ||
| 102 | throw new UserFriendlyException("登录失败!邮箱不存在!"); | 102 | throw new UserFriendlyException("登录失败!邮箱不存在!"); |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | - if (user.EncryPassword.Password != MD5Helper.SHA2Encode(input.Password, user.EncryPassword.Salt)) | 105 | + if (!UserPasswordHelper.VerifyPlainPassword(user, input.Password)) |
| 106 | { | 106 | { |
| 107 | throw new UserFriendlyException(UserConst.Login_Error); | 107 | throw new UserFriendlyException(UserConst.Login_Error); |
| 108 | } | 108 | } |
| @@ -158,7 +158,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | @@ -158,7 +158,7 @@ public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | ||
| 158 | } | 158 | } |
| 159 | 159 | ||
| 160 | /// <summary> | 160 | /// <summary> |
| 161 | - /// 查询单个门店详情(Location 页):地址、门店电话、营业时间占位、店长(角色含 manager 的绑定用户) | 161 | + /// 查询单个门店详情(Location 页):地址、门店电话、经营时间、店长(角色含 manager 的绑定用户) |
| 162 | /// </summary> | 162 | /// </summary> |
| 163 | /// <remarks> | 163 | /// <remarks> |
| 164 | /// 仅当当前登录用户在 <c>userlocation</c> 中绑定该 <c>locationId</c> 时可查;否则返回业务异常。 | 164 | /// 仅当当前登录用户在 <c>userlocation</c> 中绑定该 <c>locationId</c> 时可查;否则返回业务异常。 |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs
| @@ -7,6 +7,7 @@ using System.Threading.Tasks; | @@ -7,6 +7,7 @@ using System.Threading.Tasks; | ||
| 7 | using FoodLabeling.Application.Contracts.Dtos.Common; | 7 | using FoodLabeling.Application.Contracts.Dtos.Common; |
| 8 | using FoodLabeling.Application.Contracts.Dtos.Label; | 8 | using FoodLabeling.Application.Contracts.Dtos.Label; |
| 9 | using FoodLabeling.Application.Contracts.Dtos.LabelTemplate; | 9 | using FoodLabeling.Application.Contracts.Dtos.LabelTemplate; |
| 10 | +using FoodLabeling.Application.Contracts.Dtos.Reports; | ||
| 10 | using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; | 11 | using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; |
| 11 | using FoodLabeling.Application.Contracts.IServices; | 12 | using FoodLabeling.Application.Contracts.IServices; |
| 12 | using FoodLabeling.Application.Helpers; | 13 | using FoodLabeling.Application.Helpers; |
| @@ -53,7 +54,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -53,7 +54,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 53 | /// L1 标签分类 fl_label_category(含 buttonAppearance;COLOR/IMAGE 展示值在 categoryPhotoUrl);仅对当前门店可用:ALL 或 SPECIFIED 且在 fl_label_category_location; | 54 | /// L1 标签分类 fl_label_category(含 buttonAppearance;COLOR/IMAGE 展示值在 categoryPhotoUrl);仅对当前门店可用:ALL 或 SPECIFIED 且在 fl_label_category_location; |
| 54 | /// L2 产品分类 fl_product.CategoryId join fl_product_category(同上,展示值在 categoryPhotoUrl); | 55 | /// L2 产品分类 fl_product.CategoryId join fl_product_category(同上,展示值在 categoryPhotoUrl); |
| 55 | /// L3 产品卡片:按「产品 + 标签模板」拆分(同一 productId、不同 fl_label.TemplateId 为多张卡);L4 为该卡下与门店、标签分类、该产品、该模板关联的标签实例(fl_label + fl_label_type)。 | 56 | /// L3 产品卡片:按「产品 + 标签模板」拆分(同一 productId、不同 fl_label.TemplateId 为多张卡);L4 为该卡下与门店、标签分类、该产品、该模板关联的标签实例(fl_label + fl_label_type)。 |
| 56 | - /// L2 仅包含对当前门店可用的类别:AvailabilityType=ALL,或 SPECIFIED 且在 fl_product_category_location 存在该门店记录; | 57 | + /// L2 产品分类展示名来自 fl_product_category;产品范围已由 fl_location_product 限定当前门店, |
| 58 | + /// 不再因产品分类 SPECIFIED 未配 fl_product_category_location 而整行过滤(避免 App 全店空数据)。 | ||
| 57 | /// 未归类或分类行未关联到 fl_product_category 时仍归入「无」节点。 | 59 | /// 未归类或分类行未关联到 fl_product_category 时仍归入「无」节点。 |
| 58 | /// </remarks> | 60 | /// </remarks> |
| 59 | [Authorize] | 61 | [Authorize] |
| @@ -68,6 +70,9 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -68,6 +70,9 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 68 | var keyword = input.Keyword?.Trim(); | 70 | var keyword = input.Keyword?.Trim(); |
| 69 | var filterCategoryId = input.LabelCategoryId?.Trim(); | 71 | var filterCategoryId = input.LabelCategoryId?.Trim(); |
| 70 | 72 | ||
| 73 | + await UsAppPrintLogScopeHelper.EnsureUserBoundToLocationAsync( | ||
| 74 | + CurrentUser, _dbContext.SqlSugarClient, locationId); | ||
| 75 | + | ||
| 71 | var productIds = await _dbContext.SqlSugarClient.Queryable<FlLocationProductDbEntity>() | 76 | var productIds = await _dbContext.SqlSugarClient.Queryable<FlLocationProductDbEntity>() |
| 72 | .Where(x => x.LocationId == locationId) | 77 | .Where(x => x.LocationId == locationId) |
| 73 | .Select(x => x.ProductId) | 78 | .Select(x => x.ProductId) |
| @@ -624,9 +629,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -624,9 +629,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 624 | throw new UserFriendlyException("打印任务不存在"); | 629 | throw new UserFriendlyException("打印任务不存在"); |
| 625 | } | 630 | } |
| 626 | 631 | ||
| 627 | - // 非 admin:仅允许重打自己在当前门店的任务;admin 可重打任意用户任务(仍须门店一致) | ||
| 628 | - var isAdmin = ReportsRoleHelper.IsAdminRole(CurrentUser); | ||
| 629 | - if (!isAdmin && !string.Equals(old.CreatedBy?.Trim(), currentUserId, StringComparison.OrdinalIgnoreCase)) | 632 | + // 管理员 / Partner 角色:可重打当前门店任意用户任务;其它角色仅本人 |
| 633 | + var canViewAll = await UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync( | ||
| 634 | + CurrentUser, _dbContext.SqlSugarClient); | ||
| 635 | + if (!canViewAll && !string.Equals(old.CreatedBy?.Trim(), currentUserId, StringComparison.OrdinalIgnoreCase)) | ||
| 630 | { | 636 | { |
| 631 | throw new UserFriendlyException("无权限重打该任务"); | 637 | throw new UserFriendlyException("无权限重打该任务"); |
| 632 | } | 638 | } |
| @@ -726,13 +732,14 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -726,13 +732,14 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 726 | } | 732 | } |
| 727 | 733 | ||
| 728 | /// <summary> | 734 | /// <summary> |
| 729 | - /// App 打印日志:获取当前登录账号在当前门店打印的记录(分页,时间倒序) | 735 | + /// App 打印日志:当前门店打印记录(分页,时间倒序) |
| 730 | /// </summary> | 736 | /// </summary> |
| 731 | /// <remarks> | 737 | /// <remarks> |
| 732 | - /// 仅返回满足: | ||
| 733 | - /// - CreatedBy == CurrentUser.Id | ||
| 734 | - /// - LocationId == input.LocationId | ||
| 735 | - /// 的打印任务记录(fl_label_print_task)。 | 738 | + /// 数据范围(须已绑定 <c>input.locationId</c>): |
| 739 | + /// <list type="bullet"> | ||
| 740 | + /// <item><b>管理员</b>(<see cref="ReportsRoleHelper.IsAdminRole"/>)或角色码/名含 <c>partner</c>:该门店 <b>全部</b> 打印任务;</item> | ||
| 741 | + /// <item>其它角色:仅 <c>CreatedBy == CurrentUser.Id</c>。</item> | ||
| 742 | + /// </list> | ||
| 736 | /// | 743 | /// |
| 737 | /// 示例请求: | 744 | /// 示例请求: |
| 738 | /// ```json | 745 | /// ```json |
| @@ -774,9 +781,12 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -774,9 +781,12 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 774 | } | 781 | } |
| 775 | 782 | ||
| 776 | var currentUserIdStr = CurrentUser.Id.Value.ToString(); | 783 | var currentUserIdStr = CurrentUser.Id.Value.ToString(); |
| 784 | + await UsAppPrintLogScopeHelper.EnsureUserBoundToLocationAsync( | ||
| 785 | + CurrentUser, _dbContext.SqlSugarClient, locationId); | ||
| 777 | 786 | ||
| 778 | - var currentUser = await _userRepository.GetByIdAsync(CurrentUser.Id.Value); | ||
| 779 | - var operatorName = currentUser?.Name?.Trim() ?? string.Empty; | 787 | + var canViewAll = await UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync( |
| 788 | + CurrentUser, _dbContext.SqlSugarClient); | ||
| 789 | + var restrictToCreator = !canViewAll; | ||
| 780 | 790 | ||
| 781 | var locationName = "无"; | 791 | var locationName = "无"; |
| 782 | if (Guid.TryParse(locationId, out var locationGuid)) | 792 | if (Guid.TryParse(locationId, out var locationGuid)) |
| @@ -797,13 +807,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -797,13 +807,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 797 | 807 | ||
| 798 | RefAsync<int> total = 0; | 808 | RefAsync<int> total = 0; |
| 799 | 809 | ||
| 800 | - var query = _dbContext.SqlSugarClient | ||
| 801 | - .Queryable<FlLabelPrintTaskDbEntity>() | ||
| 802 | - .LeftJoin<FlLabelDbEntity>((t, l) => t.LabelId == l.Id) | ||
| 803 | - .LeftJoin<FlProductDbEntity>((t, l, p) => t.ProductId == p.Id) | ||
| 804 | - .LeftJoin<FlLabelTypeDbEntity>((t, l, p, lt) => t.LabelTypeId == lt.Id) | ||
| 805 | - .LeftJoin<FlLabelTemplateDbEntity>((t, l, p, lt, tpl) => t.TemplateId == tpl.Id) | ||
| 806 | - .Where((t, l, p, lt, tpl) => t.CreatedBy == currentUserIdStr && t.LocationId == locationId) | 810 | + var query = UsAppPrintLogScopeHelper.BuildLocationPrintTaskQuery( |
| 811 | + _dbContext.SqlSugarClient, locationId, restrictToCreator, currentUserIdStr) | ||
| 807 | .OrderBy((t, l, p, lt, tpl) => SqlFunc.IsNull(t.PrintedAt, t.CreationTime), OrderByType.Desc) | 812 | .OrderBy((t, l, p, lt, tpl) => SqlFunc.IsNull(t.PrintedAt, t.CreationTime), OrderByType.Desc) |
| 808 | .OrderBy((t, l, p, lt, tpl) => t.CreationTime, OrderByType.Desc) | 813 | .OrderBy((t, l, p, lt, tpl) => t.CreationTime, OrderByType.Desc) |
| 809 | .Select((t, l, p, lt, tpl) => new | 814 | .Select((t, l, p, lt, tpl) => new |
| @@ -821,11 +826,16 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -821,11 +826,16 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 821 | TemplateUnit = tpl.Unit, | 826 | TemplateUnit = tpl.Unit, |
| 822 | t.PrintInputJson, | 827 | t.PrintInputJson, |
| 823 | t.PrintedAt, | 828 | t.PrintedAt, |
| 824 | - t.CreationTime | 829 | + t.CreationTime, |
| 830 | + t.CreatedBy | ||
| 825 | }); | 831 | }); |
| 826 | 832 | ||
| 827 | var pageRows = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); | 833 | var pageRows = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); |
| 828 | 834 | ||
| 835 | + var operatorMap = await UsAppPrintLogScopeHelper.LoadOperatorNameMapAsync( | ||
| 836 | + _dbContext.SqlSugarClient, | ||
| 837 | + pageRows.Select(x => x.CreatedBy)); | ||
| 838 | + | ||
| 829 | var items = pageRows.Select(x => new PrintLogItemDto | 839 | var items = pageRows.Select(x => new PrintLogItemDto |
| 830 | { | 840 | { |
| 831 | TaskId = x.Id, | 841 | TaskId = x.Id, |
| @@ -839,7 +849,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -839,7 +849,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 839 | LabelSizeText = FormatLabelSizeWithUnit(x.TemplateWidth, x.TemplateHeight, x.TemplateUnit), | 849 | LabelSizeText = FormatLabelSizeWithUnit(x.TemplateWidth, x.TemplateHeight, x.TemplateUnit), |
| 840 | PrintInputJson = x.PrintInputJson, | 850 | PrintInputJson = x.PrintInputJson, |
| 841 | PrintedAt = x.PrintedAt ?? x.CreationTime, | 851 | PrintedAt = x.PrintedAt ?? x.CreationTime, |
| 842 | - OperatorName = operatorName, | 852 | + OperatorName = UsAppPrintLogScopeHelper.ResolveOperatorName(operatorMap, x.CreatedBy), |
| 843 | LocationName = locationName | 853 | LocationName = locationName |
| 844 | }).ToList(); | 854 | }).ToList(); |
| 845 | 855 | ||
| @@ -858,6 +868,204 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -858,6 +868,204 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 858 | }; | 868 | }; |
| 859 | } | 869 | } |
| 860 | 870 | ||
| 871 | + /// <summary> | ||
| 872 | + /// App Label Report:当前门店打印统计(权限与 <see cref="GetPrintLogListAsync"/> 一致) | ||
| 873 | + /// </summary> | ||
| 874 | + /// <remarks> | ||
| 875 | + /// 示例:<c>POST /api/app/us-app-labeling/get-label-report</c> | ||
| 876 | + /// ```json | ||
| 877 | + /// { "locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8", "startDate": "2026-04-07", "endDate": "2026-05-18" } | ||
| 878 | + /// ``` | ||
| 879 | + /// </remarks> | ||
| 880 | + [Authorize] | ||
| 881 | + [HttpPost] | ||
| 882 | + public virtual async Task<ReportsLabelReportOutputDto> GetLabelReportAsync(UsAppLabelReportQueryInputVo input) | ||
| 883 | + { | ||
| 884 | + if (input is null) | ||
| 885 | + { | ||
| 886 | + throw new UserFriendlyException("入参不能为空"); | ||
| 887 | + } | ||
| 888 | + | ||
| 889 | + if (!CurrentUser.Id.HasValue) | ||
| 890 | + { | ||
| 891 | + throw new UserFriendlyException("用户未登录"); | ||
| 892 | + } | ||
| 893 | + | ||
| 894 | + var locationId = input.LocationId?.Trim(); | ||
| 895 | + if (string.IsNullOrWhiteSpace(locationId)) | ||
| 896 | + { | ||
| 897 | + throw new UserFriendlyException("门店Id不能为空"); | ||
| 898 | + } | ||
| 899 | + | ||
| 900 | + await UsAppPrintLogScopeHelper.EnsureUserBoundToLocationAsync( | ||
| 901 | + CurrentUser, _dbContext.SqlSugarClient, locationId); | ||
| 902 | + | ||
| 903 | + var canViewAll = await UsAppPrintLogScopeHelper.CanViewAllPrintsAtLocationAsync( | ||
| 904 | + CurrentUser, _dbContext.SqlSugarClient); | ||
| 905 | + var restrictToCreator = !canViewAll; | ||
| 906 | + var currentUserIdStr = CurrentUser.Id.Value.ToString(); | ||
| 907 | + var keyword = input.Keyword?.Trim(); | ||
| 908 | + | ||
| 909 | + var (curStart, curEndExcl) = ResolveAppDateRange(input.StartDate, input.EndDate); | ||
| 910 | + var span = curEndExcl - curStart; | ||
| 911 | + if (span.TotalDays < 1) | ||
| 912 | + { | ||
| 913 | + span = TimeSpan.FromDays(1); | ||
| 914 | + } | ||
| 915 | + | ||
| 916 | + var prevEndExcl = curStart; | ||
| 917 | + var prevStart = curStart - span; | ||
| 918 | + var db = _dbContext.SqlSugarClient; | ||
| 919 | + | ||
| 920 | + ISugarQueryable<FlLabelPrintTaskDbEntity, FlLabelDbEntity, FlProductDbEntity, FlLabelCategoryDbEntity, | ||
| 921 | + FlProductCategoryDbEntity, FlLabelTypeDbEntity, FlLabelTemplateDbEntity> Core() => | ||
| 922 | + UsAppPrintLogScopeHelper.BuildLocationPrintTaskReportQuery( | ||
| 923 | + db, locationId, restrictToCreator, currentUserIdStr, keyword); | ||
| 924 | + | ||
| 925 | + var totalCur = await Core() | ||
| 926 | + .Where((t, l, p, lc, pc, lt, tpl) => | ||
| 927 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && | ||
| 928 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl) | ||
| 929 | + .CountAsync(); | ||
| 930 | + | ||
| 931 | + var totalPrev = await Core() | ||
| 932 | + .Where((t, l, p, lc, pc, lt, tpl) => | ||
| 933 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= prevStart && | ||
| 934 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < prevEndExcl) | ||
| 935 | + .CountAsync(); | ||
| 936 | + | ||
| 937 | + var dayCount = Math.Max(1, (int)Math.Ceiling((curEndExcl - curStart).TotalDays)); | ||
| 938 | + var prevDayCount = Math.Max(1, (int)Math.Ceiling((prevEndExcl - prevStart).TotalDays)); | ||
| 939 | + var avgDaily = Math.Round((decimal)totalCur / dayCount, 2); | ||
| 940 | + var avgDailyPrev = Math.Round((decimal)totalPrev / prevDayCount, 2); | ||
| 941 | + | ||
| 942 | + var categoryRows = await Core() | ||
| 943 | + .Where((t, l, p, lc, pc, lt, tpl) => | ||
| 944 | + l.LabelCategoryId != null && | ||
| 945 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && | ||
| 946 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl) | ||
| 947 | + .GroupBy((t, l, p, lc, pc, lt, tpl) => new { lc.Id, lc.CategoryName }) | ||
| 948 | + .Select((t, l, p, lc, pc, lt, tpl) => new { lc.Id, lc.CategoryName, Cnt = SqlFunc.AggregateCount(t.Id) }) | ||
| 949 | + .ToListAsync(); | ||
| 950 | + | ||
| 951 | + var topCat = categoryRows.OrderByDescending(x => x.Cnt).FirstOrDefault(); | ||
| 952 | + | ||
| 953 | + var productRows = await Core() | ||
| 954 | + .Where((t, l, p, lc, pc, lt, tpl) => | ||
| 955 | + !string.IsNullOrEmpty(p.Id) && | ||
| 956 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= curStart && | ||
| 957 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < curEndExcl) | ||
| 958 | + .GroupBy((t, l, p, lc, pc, lt, tpl) => new { p.Id, p.ProductName, Cat = pc.CategoryName }) | ||
| 959 | + .Select((t, l, p, lc, pc, lt, tpl) => new | ||
| 960 | + { | ||
| 961 | + p.Id, | ||
| 962 | + p.ProductName, | ||
| 963 | + CategoryName = pc.CategoryName, | ||
| 964 | + Cnt = SqlFunc.AggregateCount(t.Id) | ||
| 965 | + }) | ||
| 966 | + .ToListAsync(); | ||
| 967 | + | ||
| 968 | + var topProd = productRows.OrderByDescending(x => x.Cnt).FirstOrDefault(); | ||
| 969 | + var topList = productRows.OrderByDescending(x => x.Cnt).Take(20).ToList(); | ||
| 970 | + | ||
| 971 | + var trendEndDay = curEndExcl.Date.AddDays(-1); | ||
| 972 | + var trendStartDay = trendEndDay.AddDays(-6); | ||
| 973 | + if (trendStartDay < curStart.Date) | ||
| 974 | + { | ||
| 975 | + trendStartDay = curStart.Date; | ||
| 976 | + } | ||
| 977 | + | ||
| 978 | + var trendEndExcl = trendEndDay.AddDays(1); | ||
| 979 | + | ||
| 980 | + var trendRaw = await Core() | ||
| 981 | + .Where((t, l, p, lc, pc, lt, tpl) => | ||
| 982 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= trendStartDay && | ||
| 983 | + SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < trendEndExcl) | ||
| 984 | + .Select((t, l, p, lc, pc, lt, tpl) => SqlFunc.IsNull(t.PrintedAt, t.CreationTime)) | ||
| 985 | + .ToListAsync(); | ||
| 986 | + | ||
| 987 | + var trendDict = trendRaw | ||
| 988 | + .Where(x => x.HasValue) | ||
| 989 | + .GroupBy(x => x!.Value.Date) | ||
| 990 | + .ToDictionary(g => g.Key, g => g.Count()); | ||
| 991 | + | ||
| 992 | + var trend = new List<ReportsDailyCountDto>(); | ||
| 993 | + for (var d = trendStartDay; d <= trendEndDay; d = d.AddDays(1)) | ||
| 994 | + { | ||
| 995 | + trend.Add(new ReportsDailyCountDto | ||
| 996 | + { | ||
| 997 | + Date = d.ToString("yyyy-MM-dd"), | ||
| 998 | + Count = trendDict.TryGetValue(d, out var c) ? c : 0 | ||
| 999 | + }); | ||
| 1000 | + } | ||
| 1001 | + | ||
| 1002 | + var byCategory = categoryRows | ||
| 1003 | + .OrderByDescending(x => x.Cnt) | ||
| 1004 | + .Select(x => new ReportsCategoryCountDto | ||
| 1005 | + { | ||
| 1006 | + CategoryId = string.IsNullOrWhiteSpace(x.Id) ? null : x.Id.Trim(), | ||
| 1007 | + CategoryName = string.IsNullOrWhiteSpace(x.CategoryName) ? null : x.CategoryName.Trim(), | ||
| 1008 | + Count = x.Cnt | ||
| 1009 | + }) | ||
| 1010 | + .ToList(); | ||
| 1011 | + | ||
| 1012 | + var mostUsed = topList.Select(x => | ||
| 1013 | + { | ||
| 1014 | + var pct = totalCur <= 0 ? 0m : Math.Round(x.Cnt * 100m / totalCur, 2); | ||
| 1015 | + return new ReportsTopProductRowDto | ||
| 1016 | + { | ||
| 1017 | + ProductId = string.IsNullOrWhiteSpace(x.Id) ? null : x.Id.Trim(), | ||
| 1018 | + ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? null : x.ProductName.Trim(), | ||
| 1019 | + CategoryName = string.IsNullOrWhiteSpace(x.CategoryName) ? null : x.CategoryName!.Trim(), | ||
| 1020 | + TotalPrinted = x.Cnt, | ||
| 1021 | + UsagePercent = pct | ||
| 1022 | + }; | ||
| 1023 | + }).ToList(); | ||
| 1024 | + | ||
| 1025 | + return new ReportsLabelReportOutputDto | ||
| 1026 | + { | ||
| 1027 | + Summary = new ReportsLabelReportSummaryDto | ||
| 1028 | + { | ||
| 1029 | + TotalLabelsPrinted = totalCur, | ||
| 1030 | + TotalLabelsPrintedPrevPeriod = totalPrev, | ||
| 1031 | + TotalLabelsPrintedChangeRate = CalcAppChangeRate(totalCur, totalPrev), | ||
| 1032 | + MostPrintedCategoryName = string.IsNullOrWhiteSpace(topCat?.CategoryName) ? null : topCat.CategoryName.Trim(), | ||
| 1033 | + MostPrintedCategoryCount = topCat?.Cnt ?? 0, | ||
| 1034 | + TopProductName = string.IsNullOrWhiteSpace(topProd?.ProductName) ? null : topProd.ProductName.Trim(), | ||
| 1035 | + TopProductCount = topProd?.Cnt ?? 0, | ||
| 1036 | + AvgDailyPrints = avgDaily, | ||
| 1037 | + AvgDailyPrintsPrevPeriod = avgDailyPrev, | ||
| 1038 | + AvgDailyPrintsChangeRate = CalcAppChangeRate(avgDaily, avgDailyPrev) | ||
| 1039 | + }, | ||
| 1040 | + LabelsByCategory = byCategory, | ||
| 1041 | + PrintVolumeTrend = trend, | ||
| 1042 | + MostUsedProducts = mostUsed | ||
| 1043 | + }; | ||
| 1044 | + } | ||
| 1045 | + | ||
| 1046 | + private static (DateTime rangeStart, DateTime rangeEndExcl) ResolveAppDateRange(DateTime? startDate, DateTime? endDate) | ||
| 1047 | + { | ||
| 1048 | + var endDay = (endDate ?? DateTime.Today).Date; | ||
| 1049 | + var endExcl = endDay.AddDays(1); | ||
| 1050 | + var start = (startDate ?? endDay.AddDays(-29)).Date; | ||
| 1051 | + if (start >= endExcl) | ||
| 1052 | + { | ||
| 1053 | + start = endExcl.AddDays(-1); | ||
| 1054 | + } | ||
| 1055 | + | ||
| 1056 | + return (start, endExcl); | ||
| 1057 | + } | ||
| 1058 | + | ||
| 1059 | + private static decimal CalcAppChangeRate(decimal current, decimal previous) | ||
| 1060 | + { | ||
| 1061 | + if (previous == 0) | ||
| 1062 | + { | ||
| 1063 | + return current > 0 ? 100m : 0m; | ||
| 1064 | + } | ||
| 1065 | + | ||
| 1066 | + return Math.Round((current - previous) * 100m / previous, 2); | ||
| 1067 | + } | ||
| 1068 | + | ||
| 861 | private async Task<string?> ResolveTemplateProductDefaultValuesJsonAsync( | 1069 | private async Task<string?> ResolveTemplateProductDefaultValuesJsonAsync( |
| 862 | string templateId, | 1070 | string templateId, |
| 863 | string? productId, | 1071 | string? productId, |
| @@ -896,23 +1104,11 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -896,23 +1104,11 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 896 | .Where((lp, l, p, c, t, tpl, pc) => l.LocationId == locationId) | 1104 | .Where((lp, l, p, c, t, tpl, pc) => l.LocationId == locationId) |
| 897 | .Where((lp, l, p, c, t, tpl, pc) => !l.IsDeleted && l.State) | 1105 | .Where((lp, l, p, c, t, tpl, pc) => !l.IsDeleted && l.State) |
| 898 | .Where((lp, l, p, c, t, tpl, pc) => !p.IsDeleted && p.State) | 1106 | .Where((lp, l, p, c, t, tpl, pc) => !p.IsDeleted && p.State) |
| 899 | - .Where((lp, l, p, c, t, tpl, pc) => | ||
| 900 | - !c.IsDeleted && c.State && | ||
| 901 | - (c.AvailabilityType == "ALL" || | ||
| 902 | - (c.AvailabilityType == "SPECIFIED" && | ||
| 903 | - SqlFunc.Subqueryable<FlLabelCategoryLocationDbEntity>() | ||
| 904 | - .Where(loc => loc.CategoryId == c.Id && loc.LocationId == locationId) | ||
| 905 | - .Any()))) | 1107 | + .Where((lp, l, p, c, t, tpl, pc) => !c.IsDeleted && c.State) |
| 906 | .Where((lp, l, p, c, t, tpl, pc) => !t.IsDeleted && t.State) | 1108 | .Where((lp, l, p, c, t, tpl, pc) => !t.IsDeleted && t.State) |
| 907 | .Where((lp, l, p, c, t, tpl, pc) => !tpl.IsDeleted) | 1109 | .Where((lp, l, p, c, t, tpl, pc) => !tpl.IsDeleted) |
| 908 | .Where((lp, l, p, c, t, tpl, pc) => | 1110 | .Where((lp, l, p, c, t, tpl, pc) => |
| 909 | - pc.Id == null || | ||
| 910 | - (!pc.IsDeleted && pc.State && | ||
| 911 | - (pc.AvailabilityType == "ALL" || | ||
| 912 | - (pc.AvailabilityType == "SPECIFIED" && | ||
| 913 | - SqlFunc.Subqueryable<FlProductCategoryLocationDbEntity>() | ||
| 914 | - .Where(loc => loc.CategoryId == pc.Id && loc.LocationId == locationId) | ||
| 915 | - .Any())))) | 1111 | + pc.Id == null || (!pc.IsDeleted && pc.State)) |
| 916 | .WhereIF(!string.IsNullOrWhiteSpace(filterCategoryId), (lp, l, p, c, t, tpl, pc) => l.LabelCategoryId == filterCategoryId) | 1112 | .WhereIF(!string.IsNullOrWhiteSpace(filterCategoryId), (lp, l, p, c, t, tpl, pc) => l.LabelCategoryId == filterCategoryId) |
| 917 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), (lp, l, p, c, t, tpl, pc) => | 1113 | .WhereIF(!string.IsNullOrWhiteSpace(keyword), (lp, l, p, c, t, tpl, pc) => |
| 918 | (l.LabelName != null && l.LabelName.Contains(keyword!)) || | 1114 | (l.LabelName != null && l.LabelName.Contains(keyword!)) || |
美国版/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; | @@ -9,6 +9,7 @@ using Yi.Framework.Rbac.Application.Contracts.Dtos.User; | ||
| 9 | using Yi.Framework.Rbac.Application.Contracts.IServices; | 9 | using Yi.Framework.Rbac.Application.Contracts.IServices; |
| 10 | using Yi.Framework.Rbac.Domain.Authorization; | 10 | using Yi.Framework.Rbac.Domain.Authorization; |
| 11 | using Yi.Framework.Rbac.Domain.Entities; | 11 | using Yi.Framework.Rbac.Domain.Entities; |
| 12 | +using Yi.Framework.Rbac.Domain.Helpers; | ||
| 12 | using Yi.Framework.Rbac.Domain.Entities.ValueObjects; | 13 | using Yi.Framework.Rbac.Domain.Entities.ValueObjects; |
| 13 | using Yi.Framework.Rbac.Domain.Managers; | 14 | using Yi.Framework.Rbac.Domain.Managers; |
| 14 | using Yi.Framework.Rbac.Domain.Repositories; | 15 | using Yi.Framework.Rbac.Domain.Repositories; |
| @@ -155,15 +156,21 @@ namespace Yi.Framework.Rbac.Application.Services.System | @@ -155,15 +156,21 @@ namespace Yi.Framework.Rbac.Application.Services.System | ||
| 155 | 156 | ||
| 156 | var entity = await _repository.GetByIdAsync(id); | 157 | var entity = await _repository.GetByIdAsync(id); |
| 157 | //更新密码,特殊处理 | 158 | //更新密码,特殊处理 |
| 159 | + var passwordChanged = false; | ||
| 158 | if (!string.IsNullOrWhiteSpace(input.Password)) | 160 | if (!string.IsNullOrWhiteSpace(input.Password)) |
| 159 | { | 161 | { |
| 160 | - entity.EncryPassword.Password = input.Password; | ||
| 161 | - entity.BuildPassword(); | 162 | + UserPasswordHelper.ApplyPlainPassword(entity, input.Password); |
| 163 | + passwordChanged = true; | ||
| 162 | } | 164 | } |
| 163 | 165 | ||
| 164 | await MapToEntityAsync(input, entity); | 166 | await MapToEntityAsync(input, entity); |
| 165 | 167 | ||
| 166 | var res1 = await _repository.UpdateAsync(entity); | 168 | var res1 = await _repository.UpdateAsync(entity); |
| 169 | + if (passwordChanged) | ||
| 170 | + { | ||
| 171 | + await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync( | ||
| 172 | + _repository, id, entity.EncryPassword.Password, entity.EncryPassword.Salt); | ||
| 173 | + } | ||
| 167 | await _userManager.GiveUserSetRoleAsync(new List<Guid> { id }, input.RoleIds); | 174 | await _userManager.GiveUserSetRoleAsync(new List<Guid> { id }, input.RoleIds); |
| 168 | await _userManager.GiveUserSetPostAsync(new List<Guid> { id }, input.PostIds); | 175 | await _userManager.GiveUserSetPostAsync(new List<Guid> { id }, input.PostIds); |
| 169 | return await MapToGetOutputDtoAsync(entity); | 176 | return await MapToGetOutputDtoAsync(entity); |
美国版/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; | @@ -4,6 +4,7 @@ using Volo.Abp.Domain.Entities; | ||
| 4 | using Yi.Framework.Core.Data; | 4 | using Yi.Framework.Core.Data; |
| 5 | using Yi.Framework.Core.Helper; | 5 | using Yi.Framework.Core.Helper; |
| 6 | using Yi.Framework.Rbac.Domain.Entities.ValueObjects; | 6 | using Yi.Framework.Rbac.Domain.Entities.ValueObjects; |
| 7 | +using Yi.Framework.Rbac.Domain.Helpers; | ||
| 7 | using Yi.Framework.Rbac.Domain.Shared.Enums; | 8 | using Yi.Framework.Rbac.Domain.Shared.Enums; |
| 8 | 9 | ||
| 9 | namespace Yi.Framework.Rbac.Domain.Entities | 10 | namespace Yi.Framework.Rbac.Domain.Entities |
| @@ -197,19 +198,8 @@ namespace Yi.Framework.Rbac.Domain.Entities | @@ -197,19 +198,8 @@ namespace Yi.Framework.Rbac.Domain.Entities | ||
| 197 | /// </summary> | 198 | /// </summary> |
| 198 | /// <param name="password"></param> | 199 | /// <param name="password"></param> |
| 199 | /// <returns></returns> | 200 | /// <returns></returns> |
| 200 | - public bool JudgePassword(string password) | ||
| 201 | - { | ||
| 202 | - if (EncryPassword.Salt is null) | ||
| 203 | - { | ||
| 204 | - throw new ArgumentNullException(EncryPassword.Salt); | ||
| 205 | - } | ||
| 206 | - var p = MD5Helper.SHA2Encode(password, EncryPassword.Salt); | ||
| 207 | - if (EncryPassword.Password == MD5Helper.SHA2Encode(password, EncryPassword.Salt)) | ||
| 208 | - { | ||
| 209 | - return true; | ||
| 210 | - } | ||
| 211 | - return false; | ||
| 212 | - } | 201 | + public bool JudgePassword(string password) => |
| 202 | + UserPasswordHelper.VerifyPlainPassword(this, password); | ||
| 213 | } | 203 | } |
| 214 | 204 | ||
| 215 | 205 |
美国版/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; | @@ -13,6 +13,7 @@ using Volo.Abp.EventBus.Local; | ||
| 13 | using Volo.Abp.Security.Claims; | 13 | using Volo.Abp.Security.Claims; |
| 14 | using Yi.Framework.Core.Helper; | 14 | using Yi.Framework.Core.Helper; |
| 15 | using Yi.Framework.Rbac.Domain.Entities; | 15 | using Yi.Framework.Rbac.Domain.Entities; |
| 16 | +using Yi.Framework.Rbac.Domain.Helpers; | ||
| 16 | using Yi.Framework.Rbac.Domain.Repositories; | 17 | using Yi.Framework.Rbac.Domain.Repositories; |
| 17 | using Yi.Framework.Rbac.Domain.Shared.Caches; | 18 | using Yi.Framework.Rbac.Domain.Shared.Caches; |
| 18 | using Yi.Framework.Rbac.Domain.Shared.Consts; | 19 | using Yi.Framework.Rbac.Domain.Shared.Consts; |
| @@ -244,9 +245,10 @@ namespace Yi.Framework.Rbac.Domain.Managers | @@ -244,9 +245,10 @@ namespace Yi.Framework.Rbac.Domain.Managers | ||
| 244 | { | 245 | { |
| 245 | throw new UserFriendlyException("无效更新!原密码错误!"); | 246 | throw new UserFriendlyException("无效更新!原密码错误!"); |
| 246 | } | 247 | } |
| 247 | - user.EncryPassword.Password = newPassword; | ||
| 248 | - user.BuildPassword(); | 248 | + UserPasswordHelper.ApplyPlainPassword(user, newPassword); |
| 249 | await _repository.UpdateAsync(user); | 249 | await _repository.UpdateAsync(user); |
| 250 | + await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync( | ||
| 251 | + _repository, userId, user.EncryPassword.Password, user.EncryPassword.Salt); | ||
| 250 | } | 252 | } |
| 251 | 253 | ||
| 252 | /// <summary> | 254 | /// <summary> |
| @@ -258,9 +260,11 @@ namespace Yi.Framework.Rbac.Domain.Managers | @@ -258,9 +260,11 @@ namespace Yi.Framework.Rbac.Domain.Managers | ||
| 258 | public async Task<bool> RestPasswordAsync(Guid userId, string password) | 260 | public async Task<bool> RestPasswordAsync(Guid userId, string password) |
| 259 | { | 261 | { |
| 260 | var user = await _repository.GetByIdAsync(userId); | 262 | var user = await _repository.GetByIdAsync(userId); |
| 261 | - user.EncryPassword.Password = password; | ||
| 262 | - user.BuildPassword(); | ||
| 263 | - return await _repository.UpdateAsync(user); | 263 | + UserPasswordHelper.ApplyPlainPassword(user, password); |
| 264 | + var updated = await _repository.UpdateAsync(user); | ||
| 265 | + await UserPasswordHelper.EnsurePasswordColumnsPersistedAsync( | ||
| 266 | + _repository, userId, user.EncryPassword.Password, user.EncryPassword.Salt); | ||
| 267 | + return updated; | ||
| 264 | } | 268 | } |
| 265 | 269 | ||
| 266 | /// <summary> | 270 | /// <summary> |