admin/src/Oms.Application/Services/Channels/LabelTemplateService.cs
zzh 8a1649ea66
All checks were successful
Build and Deploy / deploy (push) Successful in 1m13s
feat: 入库箱唛/SKU 标签打印与订单改销售产品
支持按 box/sku 模板生成 PDF;待支付前可改产品并自动重排货重算费。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 18:39:07 +08:00

200 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Shared;
using Oms.Application.Labeling;
using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Consts;
using Oms.Domain.ChannelsAggregate.Entities;
using Oms.Domain.ChannelsAggregate.Repositories;
namespace Oms.Application.Services.Channels;
/// <summary>
/// 面单模板 API 服务。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-Channel")]
public class LabelTemplateService : ApplicationService
{
private ILabelTemplateRepository LabelTemplateRepository => LazyServiceProvider.GetRequiredService<ILabelTemplateRepository>();
private LabelPdfGenerationService LabelPdfGenerationService =>
LazyServiceProvider.GetRequiredService<LabelPdfGenerationService>();
/// <summary>
/// 标签模板列表(不含 JSON,供浏览/下拉)。
/// </summary>
[HttpGet("/channel/label-template/list")]
public async Task<OmsItemsResult<LabelTemplateListItemOutput>> GetListAsync(
[FromQuery] string? keyword = null,
[FromQuery] string? templateType = null)
{
var keywordValue = keyword?.Trim();
var typeValue = templateType?.Trim();
var hasKeyword = !string.IsNullOrEmpty(keywordValue);
var hasType = !string.IsNullOrEmpty(typeValue);
var entities = await LabelTemplateRepository.GetListAsync(x =>
(!hasKeyword || x.Name.Contains(keywordValue!)) &&
(!hasType || x.TemplateType == typeValue));
var items = entities
.OrderByDescending(x => x.UpdateTime)
.ThenByDescending(x => x.Id)
.Select(x => new LabelTemplateListItemOutput
{
Id = x.Id,
Name = x.Name,
TemplateType = x.TemplateType,
CreateTime = x.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(x.UpdateTime)
})
.ToList();
return new OmsItemsResult<LabelTemplateListItemOutput> { Items = items };
}
/// <summary>
/// 标签模板详情(含 TemplateJson,编辑用)。
/// </summary>
[HttpGet("/channel/label-template/{id:int}")]
public async Task<LabelTemplateSaveOutput> GetAsync(int id)
{
var entity = await GetEntityOrThrowAsync(id);
return MapOutput(entity);
}
/// <summary>
/// 设计器字段字典(可拖字段分组)。
/// </summary>
[HttpGet("/channel/label-template/field-dictionary")]
public Task<OmsItemsResult<LabelFieldGroup>> GetFieldDictionaryAsync()
{
return Task.FromResult(new OmsItemsResult<LabelFieldGroup>
{
Items = LabelFieldDictionary.GetGroups().ToList()
});
}
/// <summary>
/// 预览 PDF(同一套 ILabelPdfRenderer)。
/// </summary>
[HttpPost("/channel/label-template/preview-pdf")]
public async Task<FileContentResult> PreviewPdfAsync([FromBody] LabelTemplatePreviewInput input)
{
string? templateJson = null;
string? templateType = null;
if (input.TemplateId is > 0)
{
var entity = await GetEntityOrThrowAsync(input.TemplateId.Value);
templateJson = entity.TemplateJson;
templateType = entity.TemplateType;
}
else
{
templateJson = OmsDocHelper.SerializeJson(input.TemplateJson);
}
if (string.IsNullOrWhiteSpace(templateJson))
{
throw new BusinessException("模板 JSON 为空");
}
IReadOnlyDictionary<string, object?>? printData = null;
if (input.PrintData is { Count: > 0 })
{
printData = input.PrintData.ToDictionary(
x => x.Key,
x => x.Value,
StringComparer.OrdinalIgnoreCase);
}
else if (string.Equals(templateType, LabelTemplateTypeCodes.Box, StringComparison.OrdinalIgnoreCase))
{
printData = InboundBoxLabelPrintDataBuilder.BuildMock();
}
else if (string.Equals(templateType, LabelTemplateTypeCodes.Sku, StringComparison.OrdinalIgnoreCase))
{
printData = SkuLabelPrintDataBuilder.BuildMock();
}
var bytes = await LabelPdfGenerationService.PreviewAsync(templateJson, printData);
return new FileContentResult(bytes, "application/pdf")
{
FileDownloadName = $"label-preview-{DateTime.Now:yyyyMMddHHmmss}.pdf"
};
}
/// <summary>
/// 保存标签模板。
/// </summary>
[HttpPost("/channel/label-template/save")]
public async Task<LabelTemplateSaveOutput> SaveAsync(LabelTemplateSaveInput input)
{
LabelTemplate entity;
if (input.Id > 0)
{
entity = await GetEntityOrThrowAsync(input.Id);
await ValidateUniqueNameAsync(input.Name, input.TemplateType, entity.Id);
entity.Name = input.Name;
entity.TemplateType = input.TemplateType;
entity.TemplateJson = OmsDocHelper.SerializeJson(input.TemplateJson);
await LabelTemplateRepository.UpdateAsync(entity);
}
else
{
await ValidateUniqueNameAsync(input.Name, input.TemplateType, null);
entity = new LabelTemplate
{
Name = input.Name,
TemplateType = input.TemplateType,
TemplateJson = OmsDocHelper.SerializeJson(input.TemplateJson)
};
entity = await LabelTemplateRepository.InsertAsync(entity);
}
return MapOutput(entity);
}
/// <summary>
/// 删除标签模板。
/// </summary>
[HttpPost("/channel/label-template/delete")]
public async Task DeleteAsync(IdInput input)
{
var entity = await GetEntityOrThrowAsync(input.Id);
await LabelTemplateRepository.DeleteAsync(entity);
}
private async Task ValidateUniqueNameAsync(string name, string? templateType, int? id)
{
var existed = await LabelTemplateRepository.GetByNameAndTypeAsync(name, templateType);
if (existed != null && existed.Id != id)
{
throw new BusinessException("标签模板名称已存在");
}
}
private async Task<LabelTemplate> GetEntityOrThrowAsync(int id)
{
var entity = await LabelTemplateRepository.FirstOrDefaultAsync(x => x.Id == id);
if (entity == null)
{
throw new BusinessException("标签模板不存在");
}
return entity;
}
private static LabelTemplateSaveOutput MapOutput(LabelTemplate entity)
{
return new LabelTemplateSaveOutput
{
Id = entity.Id,
Name = entity.Name,
TemplateType = entity.TemplateType,
TemplateJson = OmsDocHelper.DeserializeJsonObject(entity.TemplateJson),
CreateTime = entity.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
};
}
}