新增原材料卡片生成逻辑,优化打印预览功能,支持卡片模板的加载和打印。重构相关方法以提升用户体验,确保生成的卡片数据有效,并处理打印异常情况。
This commit is contained in:
@@ -11,6 +11,7 @@ using YY.Admin.Core.Services;
|
||||
using YY.Admin.Infrastructure.Print;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Services.Service.Print;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
@@ -22,6 +23,7 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
private const int TodayListFetchSize = 5000;
|
||||
private const string RawMaterialEntryBizCode = "1900000000000000530";
|
||||
private const string RawMaterialEntryTemplateCode = "MES_RAW_MATERIAL_ENTRY";
|
||||
private const string RawMaterialCardTemplateCode = "MES_RAW_MATERIAL_CARD";
|
||||
private readonly IRawMaterialCardService _rawMaterialCardService;
|
||||
private readonly IPrintDotService _printDotService;
|
||||
private readonly IPrintBizTemplateBindService _printBizTemplateBindService;
|
||||
@@ -151,6 +153,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
private string _previewTemplateName = "原料入场记录";
|
||||
private string _previewTemplateCode = RawMaterialEntryTemplateCode;
|
||||
private string _previewFieldMappingJson = "[]";
|
||||
private bool _rawMaterialCardTemplateLoaded;
|
||||
private string _rawMaterialCardTemplateJson = string.Empty;
|
||||
private string _rawMaterialCardTemplateName = "原材料卡片";
|
||||
private string _rawMaterialCardTemplateCode = RawMaterialCardTemplateCode;
|
||||
private string _rawMaterialCardFieldMappingJson = "[]";
|
||||
|
||||
private bool _isPrintPreviewExpanded = true;
|
||||
/// <summary>右侧下方「入场标签打印预览」折叠面板是否展开。</summary>
|
||||
@@ -694,18 +701,58 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
}
|
||||
}
|
||||
|
||||
// 续号起点:已有卡片数 = Σ HasCard==true 行的 Portions;新增卡片从其后续编
|
||||
var alreadyGenerated = SplitCodeDetails
|
||||
.Where(d => d.HasCard)
|
||||
.Sum(d => d.Portions ?? 0);
|
||||
var plannedCards = BuildPlannedRawMaterialCards(Entry, pendingRows, alreadyGenerated + 1);
|
||||
if (plannedCards.Count == 0)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("未生成有效的原材料卡片预览数据,请检查拆码明细后重试。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await EnsureRawMaterialCardTemplateLoadedAsync())
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("未找到原材料卡片打印模板,请先同步“业务打印绑定/打印模板”后再试。");
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmWindow = new RawMaterialCardGenerateConfirmWindow(
|
||||
plannedCards,
|
||||
_rawMaterialCardTemplateName,
|
||||
_rawMaterialCardTemplateCode,
|
||||
_printDotService,
|
||||
row =>
|
||||
{
|
||||
var matched = plannedCards.FirstOrDefault(p =>
|
||||
string.Equals(p.DetailId, row.DetailId, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(p.Card.Barcode, row.Card.Barcode, StringComparison.OrdinalIgnoreCase));
|
||||
var target = matched ?? plannedCards[Math.Clamp(row.Index - 1, 0, plannedCards.Count - 1)];
|
||||
return BuildRawMaterialCardPreviewHtml(target);
|
||||
})
|
||||
{
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||
};
|
||||
var confirmed = confirmWindow.ShowDialog() == true;
|
||||
if (!confirmed) return;
|
||||
var selectedPrinterName = confirmWindow.SelectedPrinterName;
|
||||
if (string.IsNullOrWhiteSpace(selectedPrinterName))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("未选择打印机,已取消打印。");
|
||||
return;
|
||||
}
|
||||
|
||||
BeginActionBusy("生成中...");
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
// 续号起点:已有卡片数 = Σ HasCard==true 行的 Portions;新增卡片从其后续编
|
||||
var alreadyGenerated = SplitCodeDetails
|
||||
.Where(d => d.HasCard)
|
||||
.Sum(d => d.Portions ?? 0);
|
||||
var globalIndex = alreadyGenerated + 1;
|
||||
var baseBarcode = Entry.Barcode ?? "";
|
||||
var failCount = 0;
|
||||
var newCardCount = 0;
|
||||
var generatedCards = new List<MesXslRawMaterialCard>();
|
||||
// 按行收集成功标记:只要该行所有份都加卡成功,行 HasCard=true;
|
||||
// 中途任一份失败则保留 HasCard=false,下次再点「生成」时会重试该行
|
||||
var rowSuccessMap = new Dictionary<RawMaterialSplitDetailItem, bool>();
|
||||
@@ -740,7 +787,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
TenantId = Entry.TenantId
|
||||
};
|
||||
var ok = await _rawMaterialCardService.AddAsync(card);
|
||||
if (ok) newCardCount++;
|
||||
if (ok)
|
||||
{
|
||||
newCardCount++;
|
||||
generatedCards.Add(card);
|
||||
}
|
||||
else { failCount++; rowAllOk = false; }
|
||||
globalIndex++;
|
||||
}
|
||||
@@ -769,8 +820,17 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
RaisePropertyChanged(nameof(CanResplit));
|
||||
|
||||
if (newCardCount > 0 && generatedCards.Count > 0)
|
||||
{
|
||||
var printError = await PrintGeneratedRawMaterialCardsAsync(selectedPrinterName, generatedCards);
|
||||
if (!string.IsNullOrWhiteSpace(printError))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning($"卡片已生成 {newCardCount} 张,但打印存在异常:{printError}");
|
||||
}
|
||||
}
|
||||
|
||||
if (failCount == 0)
|
||||
HandyControl.Controls.MessageBox.Success($"已生成 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
|
||||
HandyControl.Controls.MessageBox.Success($"已生成并打印 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
|
||||
else
|
||||
HandyControl.Controls.MessageBox.Warning($"本次共尝试生成 {newCardCount + failCount} 张,成功 {newCardCount} 张,失败 {failCount} 张。失败的行未标记为「已打印」,可检查网络后再次点击「生成原材料卡片」重试。");
|
||||
}
|
||||
@@ -785,6 +845,215 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
}
|
||||
}
|
||||
|
||||
private List<RawMaterialCardGeneratePlanItem> BuildPlannedRawMaterialCards(
|
||||
MesXslRawMaterialEntry entry,
|
||||
IReadOnlyList<RawMaterialSplitDetailItem> pendingRows,
|
||||
int startIndex)
|
||||
{
|
||||
var list = new List<RawMaterialCardGeneratePlanItem>();
|
||||
var cursor = startIndex;
|
||||
var baseBarcode = entry.Barcode ?? string.Empty;
|
||||
foreach (var detail in pendingRows)
|
||||
{
|
||||
var portions = detail.Portions ?? 0;
|
||||
for (var i = 0; i < portions; i++)
|
||||
{
|
||||
var card = BuildRawMaterialCardFromDetail(entry, detail, baseBarcode + cursor.ToString("D3"));
|
||||
list.Add(new RawMaterialCardGeneratePlanItem
|
||||
{
|
||||
Card = card,
|
||||
DetailId = detail.Id,
|
||||
SourceRowNo = SplitCodeDetails.IndexOf(detail) + 1
|
||||
});
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static MesXslRawMaterialCard BuildRawMaterialCardFromDetail(
|
||||
MesXslRawMaterialEntry entry,
|
||||
RawMaterialSplitDetailItem detail,
|
||||
string barcode)
|
||||
{
|
||||
return new MesXslRawMaterialCard
|
||||
{
|
||||
SplitDetailId = detail.Id,
|
||||
Barcode = barcode,
|
||||
BatchNo = entry.BatchNo,
|
||||
EntryDate = entry.EntryTime?.Date ?? DateTime.Today,
|
||||
MaterialId = entry.MaterialId,
|
||||
MaterialName = entry.MaterialName,
|
||||
SupplierId = entry.SupplierId,
|
||||
SupplierName = entry.SupplierName,
|
||||
ManufacturerMaterialName = entry.ManufacturerMaterialName,
|
||||
ShelfLife = entry.ShelfLife,
|
||||
TotalWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
RemainingWeight = detail.PortionWeight.HasValue ? (decimal?)detail.PortionWeight.Value : null,
|
||||
RemainingQuantity = detail.PortionPackages,
|
||||
WarehouseArea = detail.WarehouseLocation,
|
||||
UnloadOperator = entry.UnloadOperator,
|
||||
Status = "1",
|
||||
TestResult = "0",
|
||||
PriorityPickup = "0",
|
||||
TenantId = entry.TenantId
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<bool> EnsureRawMaterialCardTemplateLoadedAsync()
|
||||
{
|
||||
if (_rawMaterialCardTemplateLoaded && !string.IsNullOrWhiteSpace(_rawMaterialCardTemplateJson))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var bindList = _printBizTemplateBindService.GetCached();
|
||||
if (bindList.Count == 0)
|
||||
{
|
||||
try { bindList = await _printBizTemplateBindService.ListAsync().ConfigureAwait(false); } catch { /* 忽略 */ }
|
||||
}
|
||||
var bind = bindList.FirstOrDefault(x => string.Equals(x.TemplateCode, RawMaterialCardTemplateCode, StringComparison.OrdinalIgnoreCase))
|
||||
?? bindList.FirstOrDefault(x => (x.BizName ?? string.Empty).Contains("原材料卡片", StringComparison.OrdinalIgnoreCase));
|
||||
if (bind == null) return false;
|
||||
|
||||
_rawMaterialCardFieldMappingJson = string.IsNullOrWhiteSpace(bind.FieldMappingJson) ? "[]" : bind.FieldMappingJson!;
|
||||
_rawMaterialCardTemplateCode = string.IsNullOrWhiteSpace(bind.TemplateCode) ? RawMaterialCardTemplateCode : bind.TemplateCode!;
|
||||
|
||||
var templates = _printTemplateService.GetCached();
|
||||
if (templates.Count == 0)
|
||||
{
|
||||
try { templates = await _printTemplateService.ListAsync().ConfigureAwait(false); } catch { /* 忽略 */ }
|
||||
}
|
||||
var tpl = templates.FirstOrDefault(t => !string.IsNullOrWhiteSpace(bind.TemplateId) && string.Equals(t.Id, bind.TemplateId, StringComparison.OrdinalIgnoreCase))
|
||||
?? templates.FirstOrDefault(t => string.Equals(t.TemplateCode, _rawMaterialCardTemplateCode, StringComparison.OrdinalIgnoreCase));
|
||||
if (tpl == null || string.IsNullOrWhiteSpace(tpl.TemplateJson)) return false;
|
||||
|
||||
_rawMaterialCardTemplateJson = tpl.TemplateJson!;
|
||||
_rawMaterialCardTemplateName = string.IsNullOrWhiteSpace(tpl.TemplateName) ? "原材料卡片" : tpl.TemplateName!;
|
||||
_rawMaterialCardTemplateLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private string BuildRawMaterialCardPreviewHtml(RawMaterialCardGeneratePlanItem planItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = BuildRawMaterialCardPrintData(planItem.Card);
|
||||
return NativePrintRenderService.RenderToHtml(_rawMaterialCardTemplateJson, data, enableScreenAutoFit: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BuildPrintPreviewErrorHtml($"模板预览失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private JsonObject BuildRawMaterialCardPrintData(MesXslRawMaterialCard card)
|
||||
{
|
||||
JsonObject printData = new();
|
||||
JsonNode? bizRoot = JsonSerializer.SerializeToNode(card, PreviewSnapshotJsonOpts);
|
||||
try
|
||||
{
|
||||
var mappingNode = JsonNode.Parse(string.IsNullOrWhiteSpace(_rawMaterialCardFieldMappingJson) ? "[]" : _rawMaterialCardFieldMappingJson) as JsonArray;
|
||||
if (mappingNode != null)
|
||||
{
|
||||
foreach (var rule in mappingNode)
|
||||
{
|
||||
if (rule is not JsonObject obj) continue;
|
||||
var templateField = obj["templateField"]?.GetValue<string>()?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(templateField)) continue;
|
||||
var bizField = obj["bizField"]?.GetValue<string>()?.Trim();
|
||||
JsonNode? val = string.IsNullOrWhiteSpace(bizField)
|
||||
? JsonValue.Create(string.Empty)
|
||||
: ResolvePath(bizRoot, bizField!);
|
||||
var normalized = NormalizePreviewNodeValue(val);
|
||||
SetPath(printData, templateField!, normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 映射失败时降级为模板字段补空,不中断后续流程
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var root = JsonNode.Parse(_rawMaterialCardTemplateJson);
|
||||
var fields = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
CollectTemplateBindFields(root, fields);
|
||||
foreach (var key in fields)
|
||||
{
|
||||
if (!HasPath(printData, key))
|
||||
{
|
||||
SetPath(printData, key, JsonValue.Create(string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略模板结构异常
|
||||
}
|
||||
return printData;
|
||||
}
|
||||
|
||||
private async Task<string?> PrintGeneratedRawMaterialCardsAsync(string printerName, IReadOnlyList<MesXslRawMaterialCard> cards)
|
||||
{
|
||||
if (cards.Count == 0) return null;
|
||||
if (string.IsNullOrWhiteSpace(printerName)) return "未选择打印机";
|
||||
if (!await EnsureRawMaterialCardTemplateLoadedAsync()) return "未找到原材料卡片打印模板";
|
||||
|
||||
var tpl = BuildPrintTemplateForRawMaterialCard(_rawMaterialCardTemplateJson);
|
||||
try
|
||||
{
|
||||
var idx = 0;
|
||||
foreach (var card in cards)
|
||||
{
|
||||
idx++;
|
||||
BeginActionBusy($"打印中({idx}/{cards.Count})...");
|
||||
var data = BuildRawMaterialCardPrintData(card);
|
||||
var html = NativePrintRenderService.RenderToHtml(_rawMaterialCardTemplateJson, data);
|
||||
var pdfBase64 = await HtmlToPdfRenderer.RenderAsync(
|
||||
html,
|
||||
tpl.PaperWidthMm ?? 210d,
|
||||
tpl.PaperHeightMm ?? 297d);
|
||||
var jobName = string.IsNullOrWhiteSpace(card.Barcode) ? "原材料卡片" : $"原材料卡片-{card.Barcode}";
|
||||
await _printDotService.PrintAsync(printerName, pdfBase64, jobName, 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private static PrintTemplate BuildPrintTemplateForRawMaterialCard(string? templateJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(templateJson) || templateJson == "{}")
|
||||
return new PrintTemplate { TemplateName = "原材料卡片", TemplateCode = RawMaterialCardTemplateCode };
|
||||
var root = JsonDocument.Parse(templateJson).RootElement;
|
||||
double w = 210, h = 297;
|
||||
if (root.TryGetProperty("page", out var page))
|
||||
{
|
||||
if (page.TryGetProperty("width", out var wEl)) w = wEl.GetDouble();
|
||||
if (page.TryGetProperty("height", out var hEl)) h = hEl.GetDouble();
|
||||
}
|
||||
return new PrintTemplate
|
||||
{
|
||||
TemplateName = "原材料卡片",
|
||||
TemplateCode = RawMaterialCardTemplateCode,
|
||||
PaperWidthMm = w,
|
||||
PaperHeightMm = h,
|
||||
PaperOrientation = w > h ? "横向" : "纵向",
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new PrintTemplate { TemplateName = "原材料卡片", TemplateCode = RawMaterialCardTemplateCode };
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginActionBusy(string text)
|
||||
{
|
||||
ActionBusyText = string.IsNullOrWhiteSpace(text) ? "处理中..." : text;
|
||||
@@ -1152,4 +1421,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
public double? ExpandedWidth { get; set; }
|
||||
public bool? IsExpanded { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RawMaterialCardGeneratePlanItem
|
||||
{
|
||||
public required MesXslRawMaterialCard Card { get; init; }
|
||||
public required string DetailId { get; init; }
|
||||
public int SourceRowNo { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user