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 | 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, | ... | ... |