Commit c304e27c9ceb014c6d0332f224a75612da8a629e

Authored by 李曜臣
1 parent 599d2940

打印日志优化

美国版/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,