From f0c14d8a4b4aa410e007a68a560fc6617b3a9b91 Mon Sep 17 00:00:00 2001 From: geht <2947093423@qq.com> Date: Thu, 14 May 2026 15:52:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8E=9F=E6=9D=90=E6=96=99?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=93=E5=8D=B0=E9=A2=84=E8=A7=88=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8D=A1=E7=89=87=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E7=9A=84=E5=8A=A0=E8=BD=BD=E5=92=8C=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E3=80=82=E9=87=8D=E6=9E=84=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=BB=A5=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=94=9F=E6=88=90=E7=9A=84=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E6=95=B0=E6=8D=AE=E6=9C=89=E6=95=88=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=89=93=E5=8D=B0=E5=BC=82=E5=B8=B8=E6=83=85?= =?UTF-8?q?=E5=86=B5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RawMaterialEntryOperationViewModel.cs | 288 ++++++++++++- .../RawMaterialCardGenerateConfirmWindow.xaml | 184 +++++++++ ...wMaterialCardGenerateConfirmWindow.xaml.cs | 389 ++++++++++++++++++ 3 files changed, 855 insertions(+), 6 deletions(-) create mode 100644 yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml create mode 100644 yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml.cs diff --git a/yy-admin-master/YY.Admin/ViewModels/RawMaterialEntry/RawMaterialEntryOperationViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/RawMaterialEntry/RawMaterialEntryOperationViewModel.cs index c38b8fa..7e36603 100644 --- a/yy-admin-master/YY.Admin/ViewModels/RawMaterialEntry/RawMaterialEntryOperationViewModel.cs +++ b/yy-admin-master/YY.Admin/ViewModels/RawMaterialEntry/RawMaterialEntryOperationViewModel.cs @@ -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; /// 右侧下方「入场标签打印预览」折叠面板是否展开。 @@ -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(); // 按行收集成功标记:只要该行所有份都加卡成功,行 HasCard=true; // 中途任一份失败则保留 HasCard=false,下次再点「生成」时会重试该行 var rowSuccessMap = new Dictionary(); @@ -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 BuildPlannedRawMaterialCards( + MesXslRawMaterialEntry entry, + IReadOnlyList pendingRows, + int startIndex) + { + var list = new List(); + 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 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()?.Trim(); + if (string.IsNullOrWhiteSpace(templateField)) continue; + var bizField = obj["bizField"]?.GetValue()?.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(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 PrintGeneratedRawMaterialCardsAsync(string printerName, IReadOnlyList 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; } + } } diff --git a/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml b/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml new file mode 100644 index 0000000..ef6abc6 --- /dev/null +++ b/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + +