diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs index 9a2aaa0..e456744 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs @@ -31,6 +31,12 @@ public class UsAppLabelPrintInputVo public int PrintQuantity { get; set; } = 1; /// + /// 客户端幂等请求Id(可选)。 + /// 同一个 clientRequestId 重复调用 print 接口时,后端会直接返回首次创建的 batchId/taskIds,不会重复写库。 + /// + public string? ClientRequestId { get; set; } + + /// /// 业务基准时间(用于 DATE/TIME 等元素的计算) /// public DateTime? BaseTime { get; set; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintOutputDto.cs index d7c202c..f6b6c80 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintOutputDto.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; /// @@ -8,5 +10,9 @@ public class UsAppLabelPrintOutputDto public string TaskId { get; set; } = string.Empty; public int PrintQuantity { get; set; } + + public string? BatchId { get; set; } + + public List TaskIds { get; set; } = new(); } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintDataDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintDataDbEntity.cs index 415b402..2bfa4bf 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintDataDbEntity.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintDataDbEntity.cs @@ -11,30 +11,14 @@ public class FlLabelPrintDataDbEntity [SugarColumn(IsPrimaryKey = true)] public string Id { get; set; } = string.Empty; - public bool IsDeleted { get; set; } + public string PrintTaskId { get; set; } = string.Empty; - public DateTime CreationTime { get; set; } + public string ElementId { get; set; } = string.Empty; - public string? CreatorId { get; set; } + public string? ElementName { get; set; } - public string? LastModifierId { get; set; } + public string? RenderValue { get; set; } - public DateTime? LastModificationTime { get; set; } - - public string ConcurrencyStamp { get; set; } = string.Empty; - - public string TaskId { get; set; } = string.Empty; - - public int? CopyIndex { get; set; } - - /// - /// 原始打印输入(json 字段,直接保存为字符串) - /// - public string? PrintInputJson { get; set; } - - /// - /// 解析后的可打印数据(建议保存为 json 字符串) - /// - public string? RenderDataJson { get; set; } + public string? RenderConfigJson { get; set; } } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintTaskDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintTaskDbEntity.cs index 8b73e7e..dd56f1d 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintTaskDbEntity.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintTaskDbEntity.cs @@ -11,36 +11,44 @@ public class FlLabelPrintTaskDbEntity [SugarColumn(IsPrimaryKey = true)] public string Id { get; set; } = string.Empty; - public bool IsDeleted { get; set; } + public string? BatchId { get; set; } - public DateTime CreationTime { get; set; } - - public string? CreatorId { get; set; } + public int CopyIndex { get; set; } = 1; - public string? LastModifierId { get; set; } + public string? ClientRequestId { get; set; } - public DateTime? LastModificationTime { get; set; } + public string LabelId { get; set; } = string.Empty; - public string ConcurrencyStamp { get; set; } = string.Empty; - - public string? LocationId { get; set; } + public string TemplateId { get; set; } = string.Empty; - public string? LabelCode { get; set; } + public string? LabelTypeId { get; set; } public string? ProductId { get; set; } - public string? LabelTypeId { get; set; } + public string? LocationId { get; set; } + + public DateTime? BaseTime { get; set; } - public string? TemplateCode { get; set; } + public string? PrintInputJson { get; set; } - public int PrintQuantity { get; set; } + public string? TemplateProductDefaultValuesJson { get; set; } - public DateTime? BaseTime { get; set; } + public string RenderTemplateJson { get; set; } = string.Empty; public string? PrinterId { get; set; } public string? PrinterMac { get; set; } public string? PrinterAddress { get; set; } + + public string Status { get; set; } = "CREATED"; + + public DateTime? PrintedAt { get; set; } + + public string? ErrorMessage { get; set; } + + public string? CreatedBy { get; set; } + + public DateTime CreationTime { get; set; } } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs index d0b96b7..c26db25 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs @@ -361,6 +361,28 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ } var quantity = input.PrintQuantity <= 0 ? 1 : input.PrintQuantity; + var clientRequestId = input.ClientRequestId?.Trim(); + if (!string.IsNullOrWhiteSpace(clientRequestId)) + { + // 幂等:同一个 clientRequestId 重复调用,直接返回首次创建的任务集合 + var existed = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.ClientRequestId == clientRequestId) + .OrderBy(x => x.CopyIndex) + .ToListAsync(); + + if (existed is not null && existed.Count > 0) + { + var existedBatchId = existed.First().BatchId; + var existedTaskIds = existed.Select(x => x.Id).ToList(); + return new UsAppLabelPrintOutputDto + { + TaskId = existedTaskIds.FirstOrDefault() ?? string.Empty, + PrintQuantity = existedTaskIds.Count, + BatchId = existedBatchId, + TaskIds = existedTaskIds + }; + } + } // 校验 label + location,并补齐一些顶部字段用于任务表落库 var labelRow = await _dbContext.SqlSugarClient @@ -372,9 +394,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ .Where((l, t, tpl) => l.LabelCode == labelCode) .Select((l, t, tpl) => new { + l.Id, l.LocationId, l.LabelTypeId, - TemplateCode = tpl.TemplateCode + l.TemplateId }) .FirstAsync(); @@ -388,84 +411,124 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ throw new UserFriendlyException("该标签不属于当前门店"); } + var previewProductId = await ResolvePreviewProductIdAsync(labelRow.Id, input.ProductId); + var normalizedPrintInput = input.PrintInputJson?.ToDictionary(x => x.Key, x => (object?)x.Value); + + // 解析模板 elements(与预览一致的渲染数据) + var resolvedTemplate = await _labelAppService.PreviewAsync(new LabelPreviewResolveInputVo + { + LabelCode = labelCode, + ProductId = previewProductId, + BaseTime = input.BaseTime, + PrintInputJson = normalizedPrintInput + }); + + var templateProductDefaultValuesJson = await ResolveTemplateProductDefaultValuesJsonAsync( + labelRow.TemplateId, + previewProductId, + labelRow.LabelTypeId); + var printInputJsonStr = input.PrintInputJson is null ? null : JsonSerializer.Serialize(input.PrintInputJson); + var renderTemplateJsonStr = JsonSerializer.Serialize(resolvedTemplate); - string renderDataJsonStr; - var snapshotOk = false; - if (input.TemplateSnapshot.HasValue) - { - var snapEl = input.TemplateSnapshot.Value; - if (snapEl.ValueKind == JsonValueKind.Object - && snapEl.TryGetProperty("elements", out var elArr) - && elArr.ValueKind == JsonValueKind.Array) - { - // App 与出纸一致的合并模板(label-template 同构),供打印历史/重打直接使用 - renderDataJsonStr = snapEl.GetRawText(); - snapshotOk = true; - } - } + var now = DateTime.Now; + var currentUserId = CurrentUser?.Id?.ToString(); + var batchId = _guidGenerator.Create().ToString(); + var taskIds = new List(); - if (!snapshotOk) + for (var i = 1; i <= quantity; i++) { - var resolvedTemplate = await _labelAppService.PreviewAsync(new LabelPreviewResolveInputVo + var taskId = _guidGenerator.Create().ToString(); + taskIds.Add(taskId); + + var task = new FlLabelPrintTaskDbEntity { - LabelCode = labelCode, - ProductId = input.ProductId?.Trim(), + Id = taskId, + BatchId = batchId, + CopyIndex = i, + ClientRequestId = string.IsNullOrWhiteSpace(clientRequestId) ? null : clientRequestId, + LabelId = labelRow.Id, + TemplateId = labelRow.TemplateId, + LabelTypeId = labelRow.LabelTypeId, + ProductId = previewProductId, + LocationId = locationId, BaseTime = input.BaseTime, - PrintInputJson = input.PrintInputJson - }); - renderDataJsonStr = JsonSerializer.Serialize(resolvedTemplate); - } + PrintInputJson = printInputJsonStr, + TemplateProductDefaultValuesJson = templateProductDefaultValuesJson, + RenderTemplateJson = renderTemplateJsonStr, + PrinterId = input.PrinterId?.Trim(), + PrinterMac = input.PrinterMac?.Trim(), + PrinterAddress = input.PrinterAddress?.Trim(), + Status = "CREATED", + PrintedAt = null, + ErrorMessage = null, + CreatedBy = currentUserId, + CreationTime = now + }; - var now = DateTime.Now; - var currentUserId = CurrentUser?.Id?.ToString(); - var taskId = _guidGenerator.Create().ToString(); + await _dbContext.SqlSugarClient.Insertable(task).ExecuteCommandAsync(); - var task = new FlLabelPrintTaskDbEntity - { - Id = taskId, - IsDeleted = false, - CreationTime = now, - CreatorId = currentUserId, - ConcurrencyStamp = _guidGenerator.Create().ToString("N"), - LocationId = locationId, - LabelCode = labelCode, - ProductId = input.ProductId?.Trim(), - LabelTypeId = labelRow.LabelTypeId, - TemplateCode = labelRow.TemplateCode, - PrintQuantity = quantity, - BaseTime = input.BaseTime, - PrinterId = input.PrinterId?.Trim(), - PrinterMac = input.PrinterMac?.Trim(), - PrinterAddress = input.PrinterAddress?.Trim() - }; + var rows = resolvedTemplate.Elements.Select(e => + { + var cfgJson = e.ConfigJson is null ? null : JsonSerializer.Serialize(e.ConfigJson); + string? renderValue = null; + if (e.ConfigJson is JsonElement je && je.ValueKind == JsonValueKind.Object && je.TryGetProperty("text", out var tv)) + { + renderValue = tv.ValueKind == JsonValueKind.String ? tv.GetString() : tv.ToString(); + } + else if (e.ConfigJson is Dictionary dict && dict.TryGetValue("text", out var v)) + { + renderValue = v?.ToString(); + } - await _dbContext.SqlSugarClient.Insertable(task).ExecuteCommandAsync(); + return new FlLabelPrintDataDbEntity + { + Id = _guidGenerator.Create().ToString(), + PrintTaskId = taskId, + ElementId = e.Id?.Trim() ?? string.Empty, + ElementName = e.ElementName?.Trim(), + RenderValue = renderValue, + RenderConfigJson = cfgJson + }; + }).Where(x => !string.IsNullOrWhiteSpace(x.ElementId)).ToList(); - var dataRows = Enumerable.Range(1, quantity).Select(i => new FlLabelPrintDataDbEntity - { - Id = _guidGenerator.Create().ToString(), - IsDeleted = false, - CreationTime = now, - CreatorId = currentUserId, - ConcurrencyStamp = _guidGenerator.Create().ToString("N"), - TaskId = taskId, - CopyIndex = i, - PrintInputJson = printInputJsonStr, - RenderDataJson = renderDataJsonStr - }).ToList(); - - await _dbContext.SqlSugarClient.Insertable(dataRows).ExecuteCommandAsync(); + if (rows.Count > 0) + { + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); + } + } return new UsAppLabelPrintOutputDto { - TaskId = taskId, - PrintQuantity = quantity + TaskId = taskIds.FirstOrDefault() ?? string.Empty, + PrintQuantity = quantity, + BatchId = batchId, + TaskIds = taskIds }; } + private async Task ResolveTemplateProductDefaultValuesJsonAsync( + string templateId, + string? productId, + string labelTypeId) + { + if (string.IsNullOrWhiteSpace(templateId) || string.IsNullOrWhiteSpace(productId) || string.IsNullOrWhiteSpace(labelTypeId)) + { + return null; + } + + var productDefault = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.TemplateId == templateId) + .Where(x => x.ProductId == productId) + .Where(x => x.LabelTypeId == labelTypeId) + .OrderBy(x => x.OrderNum) + .FirstAsync(); + + return string.IsNullOrWhiteSpace(productDefault?.DefaultValuesJson) ? null : productDefault!.DefaultValuesJson; + } + private ISugarQueryable BuildLabelingJoinQuery( string locationId, List productIds,