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 31 public int PrintQuantity { get; set; } = 1;
32 32  
33 33 /// <summary>
  34 + /// 客户端幂等请求Id(可选)。
  35 + /// 同一个 clientRequestId 重复调用 print 接口时,后端会直接返回首次创建的 batchId/taskIds,不会重复写库。
  36 + /// </summary>
  37 + public string? ClientRequestId { get; set; }
  38 +
  39 + /// <summary>
34 40 /// 业务基准时间(用于 DATE/TIME 等元素的计算)
35 41 /// </summary>
36 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 3 namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling;
2 4  
3 5 /// <summary>
... ... @@ -8,5 +10,9 @@ public class UsAppLabelPrintOutputDto
8 10 public string TaskId { get; set; } = string.Empty;
9 11  
10 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 11 [SugarColumn(IsPrimaryKey = true)]
12 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 11 [SugarColumn(IsPrimaryKey = true)]
12 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 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 38 public string? PrinterId { get; set; }
41 39  
42 40 public string? PrinterMac { get; set; }
43 41  
44 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 361 }
362 362  
363 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 387 // 校验 label + location,并补齐一些顶部字段用于任务表落库
366 388 var labelRow = await _dbContext.SqlSugarClient
... ... @@ -372,9 +394,10 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
372 394 .Where((l, t, tpl) => l.LabelCode == labelCode)
373 395 .Select((l, t, tpl) => new
374 396 {
  397 + l.Id,
375 398 l.LocationId,
376 399 l.LabelTypeId,
377   - TemplateCode = tpl.TemplateCode
  400 + l.TemplateId
378 401 })
379 402 .FirstAsync();
380 403  
... ... @@ -388,84 +411,124 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ
388 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 431 var printInputJsonStr = input.PrintInputJson is null
392 432 ? null
393 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 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 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 532 private ISugarQueryable<FlLabelProductDbEntity, FlLabelDbEntity, FlProductDbEntity, FlLabelCategoryDbEntity, FlLabelTypeDbEntity, FlLabelTemplateDbEntity, FlProductCategoryDbEntity> BuildLabelingJoinQuery(
470 533 string locationId,
471 534 List<string> productIds,
... ...