Commit ca114eb993958d591582b9c6952660a8f559e752
1 parent
c304e27c
打印日志接口实现
Showing
6 changed files
with
539 additions
and
1 deletions
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogGetListInputVo.cs
0 → 100644
| 1 | +using Volo.Abp.Application.Dtos; | |
| 2 | + | |
| 3 | +namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; | |
| 4 | + | |
| 5 | +/// <summary> | |
| 6 | +/// App 打印日志分页查询入参(仅当前登录账号 + 当前门店) | |
| 7 | +/// </summary> | |
| 8 | +public class PrintLogGetListInputVo : PagedAndSortedResultRequestDto | |
| 9 | +{ | |
| 10 | + /// <summary> | |
| 11 | + /// 当前门店 Id(location.Id,Guid 字符串) | |
| 12 | + /// </summary> | |
| 13 | + public string LocationId { get; set; } = string.Empty; | |
| 14 | +} | |
| 15 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/PrintLogItemDto.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; | |
| 2 | + | |
| 3 | +/// <summary> | |
| 4 | +/// 打印日志列表项 | |
| 5 | +/// </summary> | |
| 6 | +public class PrintLogItemDto | |
| 7 | +{ | |
| 8 | + /// <summary>任务Id(fl_label_print_task.Id)</summary> | |
| 9 | + public string TaskId { get; set; } = string.Empty; | |
| 10 | + | |
| 11 | + /// <summary>批次Id(同一次点击 Print 共享)</summary> | |
| 12 | + public string? BatchId { get; set; } | |
| 13 | + | |
| 14 | + /// <summary>第几份(从 1 开始)</summary> | |
| 15 | + public int CopyIndex { get; set; } | |
| 16 | + | |
| 17 | + /// <summary>标签Id</summary> | |
| 18 | + public string LabelId { get; set; } = string.Empty; | |
| 19 | + | |
| 20 | + /// <summary>标签编码</summary> | |
| 21 | + public string LabelCode { get; set; } = string.Empty; | |
| 22 | + | |
| 23 | + /// <summary>产品Id</summary> | |
| 24 | + public string? ProductId { get; set; } | |
| 25 | + | |
| 26 | + /// <summary>产品名称</summary> | |
| 27 | + public string ProductName { get; set; } = "无"; | |
| 28 | + | |
| 29 | + /// <summary>标签类型名称(来自 fl_label_type.TypeName)</summary> | |
| 30 | + public string TypeName { get; set; } = string.Empty; | |
| 31 | + | |
| 32 | + /// <summary>模板尺寸(来自 fl_label_template.Width/Height/Unit)</summary> | |
| 33 | + public string? LabelSizeText { get; set; } | |
| 34 | + | |
| 35 | + /// <summary>打印时间(PrintedAt ?? CreationTime)</summary> | |
| 36 | + public DateTime PrintedAt { get; set; } | |
| 37 | + | |
| 38 | + /// <summary>操作人姓名(当前登录账号 Name)</summary> | |
| 39 | + public string OperatorName { get; set; } = string.Empty; | |
| 40 | + | |
| 41 | + /// <summary>门店名称</summary> | |
| 42 | + public string LocationName { get; set; } = "无"; | |
| 43 | +} | |
| 44 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelReprintInputVo.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | + | |
| 4 | +namespace FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; | |
| 5 | + | |
| 6 | +/// <summary> | |
| 7 | +/// App 重新打印入参(根据历史任务Id重打) | |
| 8 | +/// </summary> | |
| 9 | +public class UsAppLabelReprintInputVo | |
| 10 | +{ | |
| 11 | + /// <summary> | |
| 12 | + /// 当前门店Id(用于权限校验,必须与历史任务一致) | |
| 13 | + /// </summary> | |
| 14 | + public string LocationId { get; set; } = string.Empty; | |
| 15 | + | |
| 16 | + /// <summary> | |
| 17 | + /// 历史打印任务Id(fl_label_print_task.Id) | |
| 18 | + /// </summary> | |
| 19 | + public string TaskId { get; set; } = string.Empty; | |
| 20 | + | |
| 21 | + /// <summary> | |
| 22 | + /// 重新打印份数(<=0 则按 1 处理;默认 1) | |
| 23 | + /// </summary> | |
| 24 | + public int PrintQuantity { get; set; } = 1; | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 客户端幂等请求Id(可选)。 | |
| 28 | + /// 同一个 clientRequestId 重复调用 reprint 接口时,后端会直接返回首次创建的 batchId/taskIds,不会重复写库。 | |
| 29 | + /// </summary> | |
| 30 | + public string? ClientRequestId { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 重新打印时可覆盖打印机Id(可选) | |
| 34 | + /// </summary> | |
| 35 | + public string? PrinterId { get; set; } | |
| 36 | + | |
| 37 | + /// <summary> | |
| 38 | + /// 重新打印时可覆盖打印机蓝牙 MAC(可选) | |
| 39 | + /// </summary> | |
| 40 | + public string? PrinterMac { get; set; } | |
| 41 | + | |
| 42 | + /// <summary> | |
| 43 | + /// 重新打印时可覆盖打印机地址(可选) | |
| 44 | + /// </summary> | |
| 45 | + public string? PrinterAddress { get; set; } | |
| 46 | +} | |
| 47 | + | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs
| 1 | 1 | using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; |
| 2 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 2 | 3 | using Volo.Abp.Application.Services; |
| 3 | 4 | |
| 4 | 5 | namespace FoodLabeling.Application.Contracts.IServices; |
| ... | ... | @@ -22,4 +23,14 @@ public interface IUsAppLabelingAppService : IApplicationService |
| 22 | 23 | /// App 打印:创建打印任务并落库打印明细(fl_label_print_task / fl_label_print_data) |
| 23 | 24 | /// </summary> |
| 24 | 25 | Task<UsAppLabelPrintOutputDto> PrintAsync(UsAppLabelPrintInputVo input); |
| 26 | + | |
| 27 | + /// <summary> | |
| 28 | + /// App 重新打印:根据历史任务Id重打(创建新任务与明细) | |
| 29 | + /// </summary> | |
| 30 | + Task<UsAppLabelPrintOutputDto> ReprintAsync(UsAppLabelReprintInputVo input); | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// App 打印日志:获取当前登录账号在当前门店打印的记录(分页,时间倒序) | |
| 34 | + /// </summary> | |
| 35 | + Task<PagedResultWithPageDto<PrintLogItemDto>> GetPrintLogListAsync(PrintLogGetListInputVo input); | |
| 25 | 36 | } | ... | ... |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs
| ... | ... | @@ -4,16 +4,21 @@ using System.Globalization; |
| 4 | 4 | using System.Linq; |
| 5 | 5 | using System.Text.Json; |
| 6 | 6 | using System.Threading.Tasks; |
| 7 | +using FoodLabeling.Application.Contracts.Dtos.Common; | |
| 7 | 8 | using FoodLabeling.Application.Contracts.Dtos.Label; |
| 8 | 9 | using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; |
| 9 | 10 | using FoodLabeling.Application.Contracts.IServices; |
| 11 | +using FoodLabeling.Application.Helpers; | |
| 10 | 12 | using FoodLabeling.Application.Services.DbModels; |
| 13 | +using FoodLabeling.Domain.Entities; | |
| 11 | 14 | using Microsoft.AspNetCore.Authorization; |
| 15 | +using Microsoft.AspNetCore.Mvc; | |
| 12 | 16 | using SqlSugar; |
| 13 | 17 | using Volo.Abp; |
| 14 | 18 | using Volo.Abp.Application.Services; |
| 15 | 19 | using Volo.Abp.Guids; |
| 16 | 20 | using Volo.Abp.Uow; |
| 21 | +using Yi.Framework.Rbac.Domain.Entities; | |
| 17 | 22 | using Yi.Framework.SqlSugarCore.Abstractions; |
| 18 | 23 | |
| 19 | 24 | namespace FoodLabeling.Application.Services; |
| ... | ... | @@ -26,12 +31,18 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ |
| 26 | 31 | private readonly ISqlSugarDbContext _dbContext; |
| 27 | 32 | private readonly ILabelAppService _labelAppService; |
| 28 | 33 | private readonly IGuidGenerator _guidGenerator; |
| 34 | + private readonly ISqlSugarRepository<UserAggregateRoot, Guid> _userRepository; | |
| 29 | 35 | |
| 30 | - public UsAppLabelingAppService(ISqlSugarDbContext dbContext, ILabelAppService labelAppService, IGuidGenerator guidGenerator) | |
| 36 | + public UsAppLabelingAppService( | |
| 37 | + ISqlSugarDbContext dbContext, | |
| 38 | + ILabelAppService labelAppService, | |
| 39 | + IGuidGenerator guidGenerator, | |
| 40 | + ISqlSugarRepository<UserAggregateRoot, Guid> userRepository) | |
| 31 | 41 | { |
| 32 | 42 | _dbContext = dbContext; |
| 33 | 43 | _labelAppService = labelAppService; |
| 34 | 44 | _guidGenerator = guidGenerator; |
| 45 | + _userRepository = userRepository; | |
| 35 | 46 | } |
| 36 | 47 | |
| 37 | 48 | /// <summary> |
| ... | ... | @@ -509,6 +520,297 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ |
| 509 | 520 | }; |
| 510 | 521 | } |
| 511 | 522 | |
| 523 | + /// <summary> | |
| 524 | + /// App 重新打印:根据历史任务Id重打(创建新任务与明细) | |
| 525 | + /// </summary> | |
| 526 | + [Authorize] | |
| 527 | + [UnitOfWork] | |
| 528 | + public virtual async Task<UsAppLabelPrintOutputDto> ReprintAsync(UsAppLabelReprintInputVo input) | |
| 529 | + { | |
| 530 | + if (input is null) | |
| 531 | + { | |
| 532 | + throw new UserFriendlyException("入参不能为空"); | |
| 533 | + } | |
| 534 | + | |
| 535 | + var locationId = input.LocationId?.Trim(); | |
| 536 | + if (string.IsNullOrWhiteSpace(locationId)) | |
| 537 | + { | |
| 538 | + throw new UserFriendlyException("门店Id不能为空"); | |
| 539 | + } | |
| 540 | + | |
| 541 | + var taskId = input.TaskId?.Trim(); | |
| 542 | + if (string.IsNullOrWhiteSpace(taskId)) | |
| 543 | + { | |
| 544 | + throw new UserFriendlyException("taskId不能为空"); | |
| 545 | + } | |
| 546 | + | |
| 547 | + var quantity = input.PrintQuantity <= 0 ? 1 : input.PrintQuantity; | |
| 548 | + var clientRequestId = input.ClientRequestId?.Trim(); | |
| 549 | + if (!string.IsNullOrWhiteSpace(clientRequestId)) | |
| 550 | + { | |
| 551 | + var existed = await _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>() | |
| 552 | + .Where(x => x.ClientRequestId == clientRequestId) | |
| 553 | + .OrderBy(x => x.CopyIndex) | |
| 554 | + .ToListAsync(); | |
| 555 | + | |
| 556 | + if (existed is not null && existed.Count > 0) | |
| 557 | + { | |
| 558 | + var existedBatchId = existed.First().BatchId; | |
| 559 | + var existedTaskIds = existed.Select(x => x.Id).ToList(); | |
| 560 | + return new UsAppLabelPrintOutputDto | |
| 561 | + { | |
| 562 | + TaskId = existedTaskIds.FirstOrDefault() ?? string.Empty, | |
| 563 | + PrintQuantity = existedTaskIds.Count, | |
| 564 | + BatchId = existedBatchId, | |
| 565 | + TaskIds = existedTaskIds | |
| 566 | + }; | |
| 567 | + } | |
| 568 | + } | |
| 569 | + | |
| 570 | + var currentUserId = CurrentUser?.Id?.ToString(); | |
| 571 | + if (string.IsNullOrWhiteSpace(currentUserId)) | |
| 572 | + { | |
| 573 | + throw new UserFriendlyException("未登录"); | |
| 574 | + } | |
| 575 | + | |
| 576 | + var old = await _dbContext.SqlSugarClient.Queryable<FlLabelPrintTaskDbEntity>() | |
| 577 | + .FirstAsync(x => x.Id == taskId); | |
| 578 | + if (old is null) | |
| 579 | + { | |
| 580 | + throw new UserFriendlyException("打印任务不存在"); | |
| 581 | + } | |
| 582 | + | |
| 583 | + // 仅允许重打自己在当前门店的任务 | |
| 584 | + if (!string.Equals(old.CreatedBy?.Trim(), currentUserId, StringComparison.OrdinalIgnoreCase)) | |
| 585 | + { | |
| 586 | + throw new UserFriendlyException("无权限重打该任务"); | |
| 587 | + } | |
| 588 | + if (!string.Equals(old.LocationId?.Trim(), locationId, StringComparison.OrdinalIgnoreCase)) | |
| 589 | + { | |
| 590 | + throw new UserFriendlyException("该任务不属于当前门店"); | |
| 591 | + } | |
| 592 | + | |
| 593 | + LabelTemplatePreviewDto? resolvedTemplate = null; | |
| 594 | + try | |
| 595 | + { | |
| 596 | + resolvedTemplate = JsonSerializer.Deserialize<LabelTemplatePreviewDto>(old.RenderTemplateJson); | |
| 597 | + } | |
| 598 | + catch | |
| 599 | + { | |
| 600 | + resolvedTemplate = null; | |
| 601 | + } | |
| 602 | + | |
| 603 | + if (resolvedTemplate is null) | |
| 604 | + { | |
| 605 | + throw new UserFriendlyException("历史任务渲染快照解析失败,无法重打"); | |
| 606 | + } | |
| 607 | + | |
| 608 | + var now = DateTime.Now; | |
| 609 | + var batchId = _guidGenerator.Create().ToString(); | |
| 610 | + var taskIds = new List<string>(); | |
| 611 | + | |
| 612 | + for (var i = 1; i <= quantity; i++) | |
| 613 | + { | |
| 614 | + var newTaskId = _guidGenerator.Create().ToString(); | |
| 615 | + taskIds.Add(newTaskId); | |
| 616 | + | |
| 617 | + var newTask = new FlLabelPrintTaskDbEntity | |
| 618 | + { | |
| 619 | + Id = newTaskId, | |
| 620 | + BatchId = batchId, | |
| 621 | + CopyIndex = i, | |
| 622 | + ClientRequestId = string.IsNullOrWhiteSpace(clientRequestId) ? null : clientRequestId, | |
| 623 | + LabelId = old.LabelId, | |
| 624 | + TemplateId = old.TemplateId, | |
| 625 | + LabelTypeId = old.LabelTypeId, | |
| 626 | + ProductId = old.ProductId, | |
| 627 | + LocationId = old.LocationId, | |
| 628 | + BaseTime = old.BaseTime, | |
| 629 | + PrintInputJson = old.PrintInputJson, | |
| 630 | + TemplateProductDefaultValuesJson = old.TemplateProductDefaultValuesJson, | |
| 631 | + RenderTemplateJson = old.RenderTemplateJson, | |
| 632 | + PrinterId = string.IsNullOrWhiteSpace(input.PrinterId) ? old.PrinterId : input.PrinterId.Trim(), | |
| 633 | + PrinterMac = string.IsNullOrWhiteSpace(input.PrinterMac) ? old.PrinterMac : input.PrinterMac.Trim(), | |
| 634 | + PrinterAddress = string.IsNullOrWhiteSpace(input.PrinterAddress) ? old.PrinterAddress : input.PrinterAddress.Trim(), | |
| 635 | + Status = "CREATED", | |
| 636 | + PrintedAt = null, | |
| 637 | + ErrorMessage = null, | |
| 638 | + CreatedBy = currentUserId, | |
| 639 | + CreationTime = now | |
| 640 | + }; | |
| 641 | + | |
| 642 | + await _dbContext.SqlSugarClient.Insertable(newTask).ExecuteCommandAsync(); | |
| 643 | + | |
| 644 | + var rows = resolvedTemplate.Elements.Select(e => | |
| 645 | + { | |
| 646 | + var cfgJson = e.ConfigJson is null ? null : JsonSerializer.Serialize(e.ConfigJson); | |
| 647 | + string? renderValue = null; | |
| 648 | + if (e.ConfigJson is JsonElement je && je.ValueKind == JsonValueKind.Object && je.TryGetProperty("text", out var tv)) | |
| 649 | + { | |
| 650 | + renderValue = tv.ValueKind == JsonValueKind.String ? tv.GetString() : tv.ToString(); | |
| 651 | + } | |
| 652 | + else if (e.ConfigJson is Dictionary<string, object?> dict && dict.TryGetValue("text", out var v)) | |
| 653 | + { | |
| 654 | + renderValue = v?.ToString(); | |
| 655 | + } | |
| 656 | + | |
| 657 | + return new FlLabelPrintDataDbEntity | |
| 658 | + { | |
| 659 | + Id = _guidGenerator.Create().ToString(), | |
| 660 | + PrintTaskId = newTaskId, | |
| 661 | + ElementId = e.Id?.Trim() ?? string.Empty, | |
| 662 | + ElementName = e.ElementName?.Trim(), | |
| 663 | + RenderValue = renderValue, | |
| 664 | + RenderConfigJson = cfgJson | |
| 665 | + }; | |
| 666 | + }).Where(x => !string.IsNullOrWhiteSpace(x.ElementId)).ToList(); | |
| 667 | + | |
| 668 | + if (rows.Count > 0) | |
| 669 | + { | |
| 670 | + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); | |
| 671 | + } | |
| 672 | + } | |
| 673 | + | |
| 674 | + return new UsAppLabelPrintOutputDto | |
| 675 | + { | |
| 676 | + TaskId = taskIds.FirstOrDefault() ?? string.Empty, | |
| 677 | + PrintQuantity = quantity, | |
| 678 | + BatchId = batchId, | |
| 679 | + TaskIds = taskIds | |
| 680 | + }; | |
| 681 | + } | |
| 682 | + | |
| 683 | + /// <summary> | |
| 684 | + /// App 打印日志:获取当前登录账号在当前门店打印的记录(分页,时间倒序) | |
| 685 | + /// </summary> | |
| 686 | + /// <remarks> | |
| 687 | + /// 仅返回满足: | |
| 688 | + /// - CreatedBy == CurrentUser.Id | |
| 689 | + /// - LocationId == input.LocationId | |
| 690 | + /// 的打印任务记录(fl_label_print_task)。 | |
| 691 | + /// | |
| 692 | + /// 示例请求: | |
| 693 | + /// ```json | |
| 694 | + /// { | |
| 695 | + /// "locationId": "11111111-1111-1111-1111-111111111111", | |
| 696 | + /// "skipCount": 1, | |
| 697 | + /// "maxResultCount": 20 | |
| 698 | + /// } | |
| 699 | + /// ``` | |
| 700 | + /// | |
| 701 | + /// 参数说明: | |
| 702 | + /// - locationId: 当前门店 Id(必填) | |
| 703 | + /// - skipCount: 页码(从 1 开始,遵循本项目约定) | |
| 704 | + /// - maxResultCount: 每页条数 | |
| 705 | + /// </remarks> | |
| 706 | + /// <param name="input">分页查询入参</param> | |
| 707 | + /// <returns>分页打印日志</returns> | |
| 708 | + /// <response code="200">成功</response> | |
| 709 | + /// <response code="400">参数错误/未登录</response> | |
| 710 | + /// <response code="500">服务器错误</response> | |
| 711 | + [Authorize] | |
| 712 | + [HttpPost] | |
| 713 | + public virtual async Task<PagedResultWithPageDto<PrintLogItemDto>> GetPrintLogListAsync(PrintLogGetListInputVo input) | |
| 714 | + { | |
| 715 | + if (input is null) | |
| 716 | + { | |
| 717 | + throw new UserFriendlyException("入参不能为空"); | |
| 718 | + } | |
| 719 | + | |
| 720 | + if (!CurrentUser.Id.HasValue) | |
| 721 | + { | |
| 722 | + throw new UserFriendlyException("用户未登录"); | |
| 723 | + } | |
| 724 | + | |
| 725 | + var locationId = input.LocationId?.Trim(); | |
| 726 | + if (string.IsNullOrWhiteSpace(locationId)) | |
| 727 | + { | |
| 728 | + throw new UserFriendlyException("门店Id不能为空"); | |
| 729 | + } | |
| 730 | + | |
| 731 | + var currentUserIdStr = CurrentUser.Id.Value.ToString(); | |
| 732 | + | |
| 733 | + var currentUser = await _userRepository.GetByIdAsync(CurrentUser.Id.Value); | |
| 734 | + var operatorName = currentUser?.Name?.Trim() ?? string.Empty; | |
| 735 | + | |
| 736 | + var locationName = "无"; | |
| 737 | + if (Guid.TryParse(locationId, out var locationGuid)) | |
| 738 | + { | |
| 739 | + var loc = await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | |
| 740 | + .Where(x => !x.IsDeleted && x.Id == locationGuid) | |
| 741 | + .Select(x => new { x.LocationCode, x.LocationName }) | |
| 742 | + .FirstAsync(); | |
| 743 | + if (loc is not null) | |
| 744 | + { | |
| 745 | + var name = loc.LocationName?.Trim(); | |
| 746 | + if (!string.IsNullOrWhiteSpace(name)) | |
| 747 | + { | |
| 748 | + locationName = name; | |
| 749 | + } | |
| 750 | + } | |
| 751 | + } | |
| 752 | + | |
| 753 | + RefAsync<int> total = 0; | |
| 754 | + | |
| 755 | + var query = _dbContext.SqlSugarClient | |
| 756 | + .Queryable<FlLabelPrintTaskDbEntity>() | |
| 757 | + .LeftJoin<FlLabelDbEntity>((t, l) => t.LabelId == l.Id) | |
| 758 | + .LeftJoin<FlProductDbEntity>((t, l, p) => t.ProductId == p.Id) | |
| 759 | + .LeftJoin<FlLabelTypeDbEntity>((t, l, p, lt) => t.LabelTypeId == lt.Id) | |
| 760 | + .LeftJoin<FlLabelTemplateDbEntity>((t, l, p, lt, tpl) => t.TemplateId == tpl.Id) | |
| 761 | + .Where((t, l, p, lt, tpl) => t.CreatedBy == currentUserIdStr && t.LocationId == locationId) | |
| 762 | + .OrderBy((t, l, p, lt, tpl) => SqlFunc.IsNull(t.PrintedAt, t.CreationTime), OrderByType.Desc) | |
| 763 | + .OrderBy((t, l, p, lt, tpl) => t.CreationTime, OrderByType.Desc) | |
| 764 | + .Select((t, l, p, lt, tpl) => new | |
| 765 | + { | |
| 766 | + t.Id, | |
| 767 | + t.BatchId, | |
| 768 | + t.CopyIndex, | |
| 769 | + t.LabelId, | |
| 770 | + LabelCode = l.LabelCode, | |
| 771 | + t.ProductId, | |
| 772 | + ProductName = p.ProductName, | |
| 773 | + TypeName = lt.TypeName, | |
| 774 | + TemplateWidth = tpl.Width, | |
| 775 | + TemplateHeight = tpl.Height, | |
| 776 | + TemplateUnit = tpl.Unit, | |
| 777 | + t.PrintedAt, | |
| 778 | + t.CreationTime | |
| 779 | + }); | |
| 780 | + | |
| 781 | + var pageRows = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); | |
| 782 | + | |
| 783 | + var items = pageRows.Select(x => new PrintLogItemDto | |
| 784 | + { | |
| 785 | + TaskId = x.Id, | |
| 786 | + BatchId = x.BatchId, | |
| 787 | + CopyIndex = x.CopyIndex, | |
| 788 | + LabelId = x.LabelId, | |
| 789 | + LabelCode = x.LabelCode ?? string.Empty, | |
| 790 | + ProductId = x.ProductId, | |
| 791 | + ProductName = string.IsNullOrWhiteSpace(x.ProductName) ? "无" : x.ProductName.Trim(), | |
| 792 | + TypeName = x.TypeName ?? string.Empty, | |
| 793 | + LabelSizeText = FormatLabelSizeWithUnit(x.TemplateWidth, x.TemplateHeight, x.TemplateUnit), | |
| 794 | + PrintedAt = x.PrintedAt ?? x.CreationTime, | |
| 795 | + OperatorName = operatorName, | |
| 796 | + LocationName = locationName | |
| 797 | + }).ToList(); | |
| 798 | + | |
| 799 | + var pageSize = input.MaxResultCount <= 0 ? items.Count : input.MaxResultCount; | |
| 800 | + var pageIndex = pageSize <= 0 ? 1 : PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount); | |
| 801 | + var totalCount = (long)total; | |
| 802 | + var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(totalCount / (double)pageSize); | |
| 803 | + | |
| 804 | + return new PagedResultWithPageDto<PrintLogItemDto> | |
| 805 | + { | |
| 806 | + PageIndex = pageIndex, | |
| 807 | + PageSize = pageSize, | |
| 808 | + TotalCount = totalCount, | |
| 809 | + TotalPages = totalPages, | |
| 810 | + Items = items | |
| 811 | + }; | |
| 812 | + } | |
| 813 | + | |
| 512 | 814 | private async Task<string?> ResolveTemplateProductDefaultValuesJsonAsync( |
| 513 | 815 | string templateId, |
| 514 | 816 | string? productId, |
| ... | ... | @@ -657,4 +959,13 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ |
| 657 | 959 | ? $"{ws}\"x{hs}\"" |
| 658 | 960 | : $"{ws}x{hs}{u}"; |
| 659 | 961 | } |
| 962 | + | |
| 963 | + private static string? FormatLabelSizeWithUnit(decimal w, decimal h, string unit) | |
| 964 | + { | |
| 965 | + var u = (unit ?? "inch").Trim().ToLowerInvariant(); | |
| 966 | + var ws = w.ToString(CultureInfo.InvariantCulture); | |
| 967 | + var hs = h.ToString(CultureInfo.InvariantCulture); | |
| 968 | + var normalizedUnit = u is "in" ? "inch" : u; | |
| 969 | + return $"{ws}x{hs}{normalizedUnit}"; | |
| 970 | + } | |
| 660 | 971 | } | ... | ... |
项目相关文档/标签模块接口对接说明.md
| ... | ... | @@ -964,3 +964,113 @@ curl -X POST "http://localhost:19001/api/app/us-app-labeling/print" \ |
| 964 | 964 | -d '{"locationId":"11111111-1111-1111-1111-111111111111","labelCode":"LBL_CHICKEN_DEFROST","productId":"22222222-2222-2222-2222-222222222222","printQuantity":2,"baseTime":"2026-03-26T10:30:00","printInputJson":{"price":"12.99"}}' |
| 965 | 965 | ``` |
| 966 | 966 | |
| 967 | +--- | |
| 968 | + | |
| 969 | +## 接口 10:App 打印日志(当前登录账号 + 当前门店) | |
| 970 | + | |
| 971 | +**场景**:移动端“打印记录/历史”页面。只展示**当前登录账号**在**当前门店**打印的记录,便于追溯/重打。 | |
| 972 | + | |
| 973 | +### 10.1 分页获取打印日志 | |
| 974 | + | |
| 975 | +#### HTTP | |
| 976 | + | |
| 977 | +- **方法**:`POST`(与本模块其它复杂入参接口一致;若与 Swagger 不一致,**以 Swagger 为准**) | |
| 978 | +- **路径**:`/api/app/us-app-labeling/get-print-log-list` | |
| 979 | +- **鉴权**:需要登录(`Authorization: Bearer ...`) | |
| 980 | + | |
| 981 | +#### 入参(Body:`PrintLogGetListInputVo`) | |
| 982 | + | |
| 983 | +> 本项目分页约定:`skipCount` 表示 **页码(从 1 开始)**,不是 0 基 offset。 | |
| 984 | + | |
| 985 | +| 参数名(JSON) | 类型 | 必填 | 说明 | | |
| 986 | +|---|---|---|---| | |
| 987 | +| `locationId` | string | 是 | 当前门店Id(仅返回该门店记录) | | |
| 988 | +| `skipCount` | number | 否 | 页码,从 1 开始;默认 1 | | |
| 989 | +| `maxResultCount` | number | 否 | 每页条数;默认按后端/ABP 默认 | | |
| 990 | + | |
| 991 | +#### 过滤条件(后端固定逻辑) | |
| 992 | + | |
| 993 | +- `fl_label_print_task.CreatedBy == CurrentUser.Id` | |
| 994 | +- `fl_label_print_task.LocationId == locationId` | |
| 995 | +- 按时间倒序:`PrintedAt ?? CreationTime`(越新的越靠前) | |
| 996 | + | |
| 997 | +#### 出参(`PagedResultWithPageDto<PrintLogItemDto>`) | |
| 998 | + | |
| 999 | +| 字段 | 类型 | 说明 | | |
| 1000 | +|---|---|---| | |
| 1001 | +| `pageIndex` | number | 当前页码(从 1 开始) | | |
| 1002 | +| `pageSize` | number | 每页条数 | | |
| 1003 | +| `totalCount` | number | 总条数 | | |
| 1004 | +| `totalPages` | number | 总页数 | | |
| 1005 | +| `items` | PrintLogItemDto[] | 列表 | | |
| 1006 | + | |
| 1007 | +`PrintLogItemDto`: | |
| 1008 | + | |
| 1009 | +| 字段 | 类型 | 说明 | | |
| 1010 | +|---|---|---| | |
| 1011 | +| `taskId` | string | 任务Id(fl_label_print_task.Id) | | |
| 1012 | +| `batchId` | string | 批次Id(同一次点击 Print 共享) | | |
| 1013 | +| `copyIndex` | number | 第几份(从 1 开始) | | |
| 1014 | +| `labelId` | string | 标签Id | | |
| 1015 | +| `labelCode` | string | 标签编码(来自 fl_label.LabelCode) | | |
| 1016 | +| `productId` | string | 产品Id | | |
| 1017 | +| `productName` | string | 产品名(来自 fl_product.ProductName;无则 “无”) | | |
| 1018 | +| `typeName` | string | 标签类型名称(来自 fl_label_type.TypeName) | | |
| 1019 | +| `labelSizeText` | string | 模板尺寸(宽高+单位,如 `2.00x2.00inch` / `6.00x4.00cm`) | | |
| 1020 | +| `printedAt` | string | 打印时间(PrintedAt ?? CreationTime) | | |
| 1021 | +| `operatorName` | string | 操作人姓名(当前登录账号 Name) | | |
| 1022 | +| `locationName` | string | 门店名称 | | |
| 1023 | + | |
| 1024 | +#### curl | |
| 1025 | + | |
| 1026 | +```bash | |
| 1027 | +curl -X POST "http://localhost:19001/api/app/us-app-labeling/get-print-log-list" \ | |
| 1028 | + -H "Authorization: <data.token>" \ | |
| 1029 | + -H "Content-Type: application/json" \ | |
| 1030 | + -d '{"locationId":"11111111-1111-1111-1111-111111111111","skipCount":1,"maxResultCount":20}' | |
| 1031 | +``` | |
| 1032 | + | |
| 1033 | +--- | |
| 1034 | + | |
| 1035 | +## 接口 11:App 重新打印(根据任务Id重打) | |
| 1036 | + | |
| 1037 | +**场景**:移动端“打印记录/历史”页面点击 **Reprint**。后端根据历史任务 `taskId` 创建一批新的打印任务与明细。 | |
| 1038 | + | |
| 1039 | +### 11.1 重打 | |
| 1040 | + | |
| 1041 | +#### HTTP | |
| 1042 | + | |
| 1043 | +- **方法**:`POST` | |
| 1044 | +- **路径**:`/api/app/us-app-labeling/reprint`(若与 Swagger 不一致,**以 Swagger 为准**) | |
| 1045 | +- **鉴权**:需要登录(`Authorization: Bearer ...`) | |
| 1046 | + | |
| 1047 | +#### 入参(Body:`UsAppLabelReprintInputVo`) | |
| 1048 | + | |
| 1049 | +| 参数名(JSON) | 类型 | 必填 | 说明 | | |
| 1050 | +|---|---|---|---| | |
| 1051 | +| `locationId` | string | 是 | 当前门店Id(后端校验历史任务必须属于该门店) | | |
| 1052 | +| `taskId` | string | 是 | 历史打印任务Id(`fl_label_print_task.Id`) | | |
| 1053 | +| `printQuantity` | number | 否 | 重新打印份数;`<=0` 按 1 处理;默认 1 | | |
| 1054 | +| `clientRequestId` | string | 否 | 客户端幂等请求Id;同一个值重复调用会直接返回首次创建的 `batchId/taskIds`,避免重复写库 | | |
| 1055 | +| `printerId` | string | 否 | 可选,覆盖历史任务的打印机Id | | |
| 1056 | +| `printerMac` | string | 否 | 可选,覆盖历史任务的打印机MAC | | |
| 1057 | +| `printerAddress` | string | 否 | 可选,覆盖历史任务的打印机地址 | | |
| 1058 | + | |
| 1059 | +#### 权限校验(后端固定逻辑) | |
| 1060 | + | |
| 1061 | +- 历史任务必须满足:`CreatedBy == CurrentUser.Id` | |
| 1062 | +- 且 `LocationId == locationId` | |
| 1063 | + | |
| 1064 | +#### 出参(`UsAppLabelPrintOutputDto`) | |
| 1065 | + | |
| 1066 | +字段与接口 9 一致:`taskId / printQuantity / batchId / taskIds` | |
| 1067 | + | |
| 1068 | +#### curl | |
| 1069 | + | |
| 1070 | +```bash | |
| 1071 | +curl -X POST "http://localhost:19001/api/app/us-app-labeling/reprint" \ | |
| 1072 | + -H "Authorization: <data.token>" \ | |
| 1073 | + -H "Content-Type: application/json" \ | |
| 1074 | + -d '{"locationId":"11111111-1111-1111-1111-111111111111","taskId":"3a205389-78dd-4750-51ab-720344c9f607","printQuantity":1}' | |
| 1075 | +``` | |
| 1076 | + | ... | ... |