Commit c304e27c9ceb014c6d0332f224a75612da8a629e
1 parent
599d2940
打印日志优化
Showing
5 changed files
with
163 additions
and
96 deletions
美国版/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 | @@ -31,6 +31,12 @@ public class UsAppLabelPrintInputVo | ||
| 31 | public int PrintQuantity { get; set; } = 1; | 31 | public int PrintQuantity { get; set; } = 1; |
| 32 | 32 | ||
| 33 | /// <summary> | 33 | /// <summary> |
| 34 | + /// 客户端幂等请求Id(可选)。 | ||
| 35 | + /// 同一个 clientRequestId 重复调用 print 接口时,后端会直接返回首次创建的 batchId/taskIds,不会重复写库。 | ||
| 36 | + /// </summary> | ||
| 37 | + public string? ClientRequestId { get; set; } | ||
| 38 | + | ||
| 39 | + /// <summary> | ||
| 34 | /// 业务基准时间(用于 DATE/TIME 等元素的计算) | 40 | /// 业务基准时间(用于 DATE/TIME 等元素的计算) |
| 35 | /// </summary> | 41 | /// </summary> |
| 36 | public DateTime? BaseTime { get; set; } | 42 | public DateTime? BaseTime { get; set; } |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintOutputDto.cs
| 1 | +using System.Collections.Generic; | ||
| 2 | + | ||
| 1 | namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; | 3 | namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; |
| 2 | 4 | ||
| 3 | /// <summary> | 5 | /// <summary> |
| @@ -8,5 +10,9 @@ public class UsAppLabelPrintOutputDto | @@ -8,5 +10,9 @@ public class UsAppLabelPrintOutputDto | ||
| 8 | public string TaskId { get; set; } = string.Empty; | 10 | public string TaskId { get; set; } = string.Empty; |
| 9 | 11 | ||
| 10 | public int PrintQuantity { get; set; } | 12 | public int PrintQuantity { get; set; } |
| 13 | + | ||
| 14 | + public string? BatchId { get; set; } | ||
| 15 | + | ||
| 16 | + public List<string> TaskIds { get; set; } = new(); | ||
| 11 | } | 17 | } |
| 12 | 18 |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintDataDbEntity.cs
| @@ -11,30 +11,14 @@ public class FlLabelPrintDataDbEntity | @@ -11,30 +11,14 @@ public class FlLabelPrintDataDbEntity | ||
| 11 | [SugarColumn(IsPrimaryKey = true)] | 11 | [SugarColumn(IsPrimaryKey = true)] |
| 12 | public string Id { get; set; } = string.Empty; | 12 | public string Id { get; set; } = string.Empty; |
| 13 | 13 | ||
| 14 | - public bool IsDeleted { get; set; } | 14 | + public string PrintTaskId { get; set; } = string.Empty; |
| 15 | 15 | ||
| 16 | - public DateTime CreationTime { get; set; } | 16 | + public string ElementId { get; set; } = string.Empty; |
| 17 | 17 | ||
| 18 | - public string? CreatorId { get; set; } | 18 | + public string? ElementName { get; set; } |
| 19 | 19 | ||
| 20 | - public string? LastModifierId { get; set; } | 20 | + public string? RenderValue { get; set; } |
| 21 | 21 | ||
| 22 | - public DateTime? LastModificationTime { get; set; } | ||
| 23 | - | ||
| 24 | - public string ConcurrencyStamp { get; set; } = string.Empty; | ||
| 25 | - | ||
| 26 | - public string TaskId { get; set; } = string.Empty; | ||
| 27 | - | ||
| 28 | - public int? CopyIndex { get; set; } | ||
| 29 | - | ||
| 30 | - /// <summary> | ||
| 31 | - /// 原始打印输入(json 字段,直接保存为字符串) | ||
| 32 | - /// </summary> | ||
| 33 | - public string? PrintInputJson { get; set; } | ||
| 34 | - | ||
| 35 | - /// <summary> | ||
| 36 | - /// 解析后的可打印数据(建议保存为 json 字符串) | ||
| 37 | - /// </summary> | ||
| 38 | - public string? RenderDataJson { get; set; } | 22 | + public string? RenderConfigJson { get; set; } |
| 39 | } | 23 | } |
| 40 | 24 |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelPrintTaskDbEntity.cs
| @@ -11,36 +11,44 @@ public class FlLabelPrintTaskDbEntity | @@ -11,36 +11,44 @@ public class FlLabelPrintTaskDbEntity | ||
| 11 | [SugarColumn(IsPrimaryKey = true)] | 11 | [SugarColumn(IsPrimaryKey = true)] |
| 12 | public string Id { get; set; } = string.Empty; | 12 | public string Id { get; set; } = string.Empty; |
| 13 | 13 | ||
| 14 | - public bool IsDeleted { get; set; } | 14 | + public string? BatchId { get; set; } |
| 15 | 15 | ||
| 16 | - public DateTime CreationTime { get; set; } | ||
| 17 | - | ||
| 18 | - public string? CreatorId { get; set; } | 16 | + public int CopyIndex { get; set; } = 1; |
| 19 | 17 | ||
| 20 | - public string? LastModifierId { get; set; } | 18 | + public string? ClientRequestId { get; set; } |
| 21 | 19 | ||
| 22 | - public DateTime? LastModificationTime { get; set; } | 20 | + public string LabelId { get; set; } = string.Empty; |
| 23 | 21 | ||
| 24 | - public string ConcurrencyStamp { get; set; } = string.Empty; | ||
| 25 | - | ||
| 26 | - public string? LocationId { get; set; } | 22 | + public string TemplateId { get; set; } = string.Empty; |
| 27 | 23 | ||
| 28 | - public string? LabelCode { get; set; } | 24 | + public string? LabelTypeId { get; set; } |
| 29 | 25 | ||
| 30 | public string? ProductId { get; set; } | 26 | public string? ProductId { get; set; } |
| 31 | 27 | ||
| 32 | - public string? LabelTypeId { get; set; } | 28 | + public string? LocationId { get; set; } |
| 29 | + | ||
| 30 | + public DateTime? BaseTime { get; set; } | ||
| 33 | 31 | ||
| 34 | - public string? TemplateCode { get; set; } | 32 | + public string? PrintInputJson { get; set; } |
| 35 | 33 | ||
| 36 | - public int PrintQuantity { get; set; } | 34 | + public string? TemplateProductDefaultValuesJson { get; set; } |
| 37 | 35 | ||
| 38 | - public DateTime? BaseTime { get; set; } | 36 | + public string RenderTemplateJson { get; set; } = string.Empty; |
| 39 | 37 | ||
| 40 | public string? PrinterId { get; set; } | 38 | public string? PrinterId { get; set; } |
| 41 | 39 | ||
| 42 | public string? PrinterMac { get; set; } | 40 | public string? PrinterMac { get; set; } |
| 43 | 41 | ||
| 44 | public string? PrinterAddress { get; set; } | 42 | public string? PrinterAddress { get; set; } |
| 43 | + | ||
| 44 | + public string Status { get; set; } = "CREATED"; | ||
| 45 | + | ||
| 46 | + public DateTime? PrintedAt { get; set; } | ||
| 47 | + | ||
| 48 | + public string? ErrorMessage { get; set; } | ||
| 49 | + | ||
| 50 | + public string? CreatedBy { get; set; } | ||
| 51 | + | ||
| 52 | + public DateTime CreationTime { get; set; } | ||
| 45 | } | 53 | } |
| 46 | 54 |
美国版/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 | @@ -361,6 +361,28 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 361 | } | 361 | } |
| 362 | 362 | ||
| 363 | var quantity = input.PrintQuantity <= 0 ? 1 : input.PrintQuantity; | 363 | var quantity = input.PrintQuantity <= 0 ? 1 : input.PrintQuantity; |
| 364 | + var clientRequestId = input.ClientRequestId?.Trim(); | ||
| 365 | + if (!string.IsNullOrWhiteSpace(clientRequestId)) | ||
| 366 | + { | ||
| 367 | + // 幂等:同一个 clientRequestId 重复调用,直接返回首次创建的任务集合 | ||
| 368 | + var existed = await _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>() | ||
| 369 | + .Where(x => x.ClientRequestId == clientRequestId) | ||
| 370 | + .OrderBy(x => x.CopyIndex) | ||
| 371 | + .ToListAsync(); | ||
| 372 | + | ||
| 373 | + if (existed is not null && existed.Count > 0) | ||
| 374 | + { | ||
| 375 | + var existedBatchId = existed.First().BatchId; | ||
| 376 | + var existedTaskIds = existed.Select(x => x.Id).ToList(); | ||
| 377 | + return new UsAppLabelPrintOutputDto | ||
| 378 | + { | ||
| 379 | + TaskId = existedTaskIds.FirstOrDefault() ?? string.Empty, | ||
| 380 | + PrintQuantity = existedTaskIds.Count, | ||
| 381 | + BatchId = existedBatchId, | ||
| 382 | + TaskIds = existedTaskIds | ||
| 383 | + }; | ||
| 384 | + } | ||
| 385 | + } | ||
| 364 | 386 | ||
| 365 | // 校验 label + location,并补齐一些顶部字段用于任务表落库 | 387 | // 校验 label + location,并补齐一些顶部字段用于任务表落库 |
| 366 | var labelRow = await _dbContext.SqlSugarClient | 388 | var labelRow = await _dbContext.SqlSugarClient |
| @@ -372,9 +394,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -372,9 +394,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 372 | .Where((l, t, tpl) => l.LabelCode == labelCode) | 394 | .Where((l, t, tpl) => l.LabelCode == labelCode) |
| 373 | .Select((l, t, tpl) => new | 395 | .Select((l, t, tpl) => new |
| 374 | { | 396 | { |
| 397 | + l.Id, | ||
| 375 | l.LocationId, | 398 | l.LocationId, |
| 376 | l.LabelTypeId, | 399 | l.LabelTypeId, |
| 377 | - TemplateCode = tpl.TemplateCode | 400 | + l.TemplateId |
| 378 | }) | 401 | }) |
| 379 | .FirstAsync(); | 402 | .FirstAsync(); |
| 380 | 403 | ||
| @@ -388,84 +411,124 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | @@ -388,84 +411,124 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ | ||
| 388 | throw new UserFriendlyException("该标签不属于当前门店"); | 411 | throw new UserFriendlyException("该标签不属于当前门店"); |
| 389 | } | 412 | } |
| 390 | 413 | ||
| 414 | + var previewProductId = await ResolvePreviewProductIdAsync(labelRow.Id, input.ProductId); | ||
| 415 | + var normalizedPrintInput = input.PrintInputJson?.ToDictionary(x => x.Key, x => (object?)x.Value); | ||
| 416 | + | ||
| 417 | + // 解析模板 elements(与预览一致的渲染数据) | ||
| 418 | + var resolvedTemplate = await _labelAppService.PreviewAsync(new LabelPreviewResolveInputVo | ||
| 419 | + { | ||
| 420 | + LabelCode = labelCode, | ||
| 421 | + ProductId = previewProductId, | ||
| 422 | + BaseTime = input.BaseTime, | ||
| 423 | + PrintInputJson = normalizedPrintInput | ||
| 424 | + }); | ||
| 425 | + | ||
| 426 | + var templateProductDefaultValuesJson = await ResolveTemplateProductDefaultValuesJsonAsync( | ||
| 427 | + labelRow.TemplateId, | ||
| 428 | + previewProductId, | ||
| 429 | + labelRow.LabelTypeId); | ||
| 430 | + | ||
| 391 | var printInputJsonStr = input.PrintInputJson is null | 431 | var printInputJsonStr = input.PrintInputJson is null |
| 392 | ? null | 432 | ? null |
| 393 | : JsonSerializer.Serialize(input.PrintInputJson); | 433 | : JsonSerializer.Serialize(input.PrintInputJson); |
| 434 | + var renderTemplateJsonStr = JsonSerializer.Serialize(resolvedTemplate); | ||
| 394 | 435 | ||
| 395 | - string renderDataJsonStr; | ||
| 396 | - var snapshotOk = false; | ||
| 397 | - if (input.TemplateSnapshot.HasValue) | ||
| 398 | - { | ||
| 399 | - var snapEl = input.TemplateSnapshot.Value; | ||
| 400 | - if (snapEl.ValueKind == JsonValueKind.Object | ||
| 401 | - && snapEl.TryGetProperty("elements", out var elArr) | ||
| 402 | - && elArr.ValueKind == JsonValueKind.Array) | ||
| 403 | - { | ||
| 404 | - // App 与出纸一致的合并模板(label-template 同构),供打印历史/重打直接使用 | ||
| 405 | - renderDataJsonStr = snapEl.GetRawText(); | ||
| 406 | - snapshotOk = true; | ||
| 407 | - } | ||
| 408 | - } | 436 | + var now = DateTime.Now; |
| 437 | + var currentUserId = CurrentUser?.Id?.ToString(); | ||
| 438 | + var batchId = _guidGenerator.Create().ToString(); | ||
| 439 | + var taskIds = new List<string>(); | ||
| 409 | 440 | ||
| 410 | - if (!snapshotOk) | 441 | + for (var i = 1; i <= quantity; i++) |
| 411 | { | 442 | { |
| 412 | - var resolvedTemplate = await _labelAppService.PreviewAsync(new LabelPreviewResolveInputVo | 443 | + var taskId = _guidGenerator.Create().ToString(); |
| 444 | + taskIds.Add(taskId); | ||
| 445 | + | ||
| 446 | + var task = new FlLabelPrintTaskDbEntity | ||
| 413 | { | 447 | { |
| 414 | - LabelCode = labelCode, | ||
| 415 | - ProductId = input.ProductId?.Trim(), | 448 | + Id = taskId, |
| 449 | + BatchId = batchId, | ||
| 450 | + CopyIndex = i, | ||
| 451 | + ClientRequestId = string.IsNullOrWhiteSpace(clientRequestId) ? null : clientRequestId, | ||
| 452 | + LabelId = labelRow.Id, | ||
| 453 | + TemplateId = labelRow.TemplateId, | ||
| 454 | + LabelTypeId = labelRow.LabelTypeId, | ||
| 455 | + ProductId = previewProductId, | ||
| 456 | + LocationId = locationId, | ||
| 416 | BaseTime = input.BaseTime, | 457 | BaseTime = input.BaseTime, |
| 417 | - PrintInputJson = input.PrintInputJson | ||
| 418 | - }); | ||
| 419 | - renderDataJsonStr = JsonSerializer.Serialize(resolvedTemplate); | ||
| 420 | - } | 458 | + PrintInputJson = printInputJsonStr, |
| 459 | + TemplateProductDefaultValuesJson = templateProductDefaultValuesJson, | ||
| 460 | + RenderTemplateJson = renderTemplateJsonStr, | ||
| 461 | + PrinterId = input.PrinterId?.Trim(), | ||
| 462 | + PrinterMac = input.PrinterMac?.Trim(), | ||
| 463 | + PrinterAddress = input.PrinterAddress?.Trim(), | ||
| 464 | + Status = "CREATED", | ||
| 465 | + PrintedAt = null, | ||
| 466 | + ErrorMessage = null, | ||
| 467 | + CreatedBy = currentUserId, | ||
| 468 | + CreationTime = now | ||
| 469 | + }; | ||
| 421 | 470 | ||
| 422 | - var now = DateTime.Now; | ||
| 423 | - var currentUserId = CurrentUser?.Id?.ToString(); | ||
| 424 | - var taskId = _guidGenerator.Create().ToString(); | 471 | + await _dbContext.SqlSugarClient.Insertable(task).ExecuteCommandAsync(); |
| 425 | 472 | ||
| 426 | - var task = new FlLabelPrintTaskDbEntity | ||
| 427 | - { | ||
| 428 | - Id = taskId, | ||
| 429 | - IsDeleted = false, | ||
| 430 | - CreationTime = now, | ||
| 431 | - CreatorId = currentUserId, | ||
| 432 | - ConcurrencyStamp = _guidGenerator.Create().ToString("N"), | ||
| 433 | - LocationId = locationId, | ||
| 434 | - LabelCode = labelCode, | ||
| 435 | - ProductId = input.ProductId?.Trim(), | ||
| 436 | - LabelTypeId = labelRow.LabelTypeId, | ||
| 437 | - TemplateCode = labelRow.TemplateCode, | ||
| 438 | - PrintQuantity = quantity, | ||
| 439 | - BaseTime = input.BaseTime, | ||
| 440 | - PrinterId = input.PrinterId?.Trim(), | ||
| 441 | - PrinterMac = input.PrinterMac?.Trim(), | ||
| 442 | - PrinterAddress = input.PrinterAddress?.Trim() | ||
| 443 | - }; | 473 | + var rows = resolvedTemplate.Elements.Select(e => |
| 474 | + { | ||
| 475 | + var cfgJson = e.ConfigJson is null ? null : JsonSerializer.Serialize(e.ConfigJson); | ||
| 476 | + string? renderValue = null; | ||
| 477 | + if (e.ConfigJson is JsonElement je && je.ValueKind == JsonValueKind.Object && je.TryGetProperty("text", out var tv)) | ||
| 478 | + { | ||
| 479 | + renderValue = tv.ValueKind == JsonValueKind.String ? tv.GetString() : tv.ToString(); | ||
| 480 | + } | ||
| 481 | + else if (e.ConfigJson is Dictionary<string, object?> dict && dict.TryGetValue("text", out var v)) | ||
| 482 | + { | ||
| 483 | + renderValue = v?.ToString(); | ||
| 484 | + } | ||
| 444 | 485 | ||
| 445 | - await _dbContext.SqlSugarClient.Insertable(task).ExecuteCommandAsync(); | 486 | + return new FlLabelPrintDataDbEntity |
| 487 | + { | ||
| 488 | + Id = _guidGenerator.Create().ToString(), | ||
| 489 | + PrintTaskId = taskId, | ||
| 490 | + ElementId = e.Id?.Trim() ?? string.Empty, | ||
| 491 | + ElementName = e.ElementName?.Trim(), | ||
| 492 | + RenderValue = renderValue, | ||
| 493 | + RenderConfigJson = cfgJson | ||
| 494 | + }; | ||
| 495 | + }).Where(x => !string.IsNullOrWhiteSpace(x.ElementId)).ToList(); | ||
| 446 | 496 | ||
| 447 | - var dataRows = Enumerable.Range(1, quantity).Select(i => new FlLabelPrintDataDbEntity | ||
| 448 | - { | ||
| 449 | - Id = _guidGenerator.Create().ToString(), | ||
| 450 | - IsDeleted = false, | ||
| 451 | - CreationTime = now, | ||
| 452 | - CreatorId = currentUserId, | ||
| 453 | - ConcurrencyStamp = _guidGenerator.Create().ToString("N"), | ||
| 454 | - TaskId = taskId, | ||
| 455 | - CopyIndex = i, | ||
| 456 | - PrintInputJson = printInputJsonStr, | ||
| 457 | - RenderDataJson = renderDataJsonStr | ||
| 458 | - }).ToList(); | ||
| 459 | - | ||
| 460 | - await _dbContext.SqlSugarClient.Insertable(dataRows).ExecuteCommandAsync(); | 497 | + if (rows.Count > 0) |
| 498 | + { | ||
| 499 | + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); | ||
| 500 | + } | ||
| 501 | + } | ||
| 461 | 502 | ||
| 462 | return new UsAppLabelPrintOutputDto | 503 | return new UsAppLabelPrintOutputDto |
| 463 | { | 504 | { |
| 464 | - TaskId = taskId, | ||
| 465 | - PrintQuantity = quantity | 505 | + TaskId = taskIds.FirstOrDefault() ?? string.Empty, |
| 506 | + PrintQuantity = quantity, | ||
| 507 | + BatchId = batchId, | ||
| 508 | + TaskIds = taskIds | ||
| 466 | }; | 509 | }; |
| 467 | } | 510 | } |
| 468 | 511 | ||
| 512 | + private async Task<string?> ResolveTemplateProductDefaultValuesJsonAsync( | ||
| 513 | + string templateId, | ||
| 514 | + string? productId, | ||
| 515 | + string labelTypeId) | ||
| 516 | + { | ||
| 517 | + if (string.IsNullOrWhiteSpace(templateId) || string.IsNullOrWhiteSpace(productId) || string.IsNullOrWhiteSpace(labelTypeId)) | ||
| 518 | + { | ||
| 519 | + return null; | ||
| 520 | + } | ||
| 521 | + | ||
| 522 | + var productDefault = await _dbContext.SqlSugarClient.Queryable<FlLabelTemplateProductDefaultDbEntity>() | ||
| 523 | + .Where(x => x.TemplateId == templateId) | ||
| 524 | + .Where(x => x.ProductId == productId) | ||
| 525 | + .Where(x => x.LabelTypeId == labelTypeId) | ||
| 526 | + .OrderBy(x => x.OrderNum) | ||
| 527 | + .FirstAsync(); | ||
| 528 | + | ||
| 529 | + return string.IsNullOrWhiteSpace(productDefault?.DefaultValuesJson) ? null : productDefault!.DefaultValuesJson; | ||
| 530 | + } | ||
| 531 | + | ||
| 469 | private ISugarQueryable<FlLabelProductDbEntity, FlLabelDbEntity, FlProductDbEntity, FlLabelCategoryDbEntity, FlLabelTypeDbEntity, FlLabelTemplateDbEntity, FlProductCategoryDbEntity> BuildLabelingJoinQuery( | 532 | private ISugarQueryable<FlLabelProductDbEntity, FlLabelDbEntity, FlProductDbEntity, FlLabelCategoryDbEntity, FlLabelTypeDbEntity, FlLabelTemplateDbEntity, FlProductCategoryDbEntity> BuildLabelingJoinQuery( |
| 470 | string locationId, | 533 | string locationId, |
| 471 | List<string> productIds, | 534 | List<string> productIds, |