桌面端原材料入场记录完善
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Data;
|
||||
using Prism.Commands;
|
||||
using Prism.Navigation;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
@@ -18,9 +19,9 @@ using YY.Admin.Views.RawMaterialEntry;
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
/// <summary>
|
||||
/// 「新增原料入场记录」独立页面:左侧表单逻辑继承编辑 VM,右侧展示当日入场简要列表并支持选中回填模板。
|
||||
/// 原料入场记录独立操作页(新增/编辑共用):左侧表单,右侧当日入场列表与打印预览。
|
||||
/// </summary>
|
||||
public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogViewModel
|
||||
public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogViewModel, INavigationAware
|
||||
{
|
||||
private const int TodayListFetchSize = 5000;
|
||||
private const string RawMaterialEntryBizCode = "1900000000000000530";
|
||||
@@ -30,6 +31,11 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
private readonly IPrintDotService _printDotService;
|
||||
private readonly IPrintBizTemplateBindService _printBizTemplateBindService;
|
||||
private readonly IPrintTemplateService _printTemplateService;
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】操作页支持导航进入编辑态-----------
|
||||
private string? _editingEntryId;
|
||||
/// <summary>是否已由 INavigationAware 处理过导航参数(避免 Loaded 兜底把编辑态冲掉)。</summary>
|
||||
public bool HasAppliedNavigation { get; private set; }
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】操作页支持导航进入编辑态-----------
|
||||
|
||||
private static readonly JsonSerializerOptions LayoutJsonOpts = new()
|
||||
{
|
||||
@@ -98,12 +104,28 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
|
||||
/// <summary>
|
||||
/// 「生成原材料卡片」按钮可用条件:
|
||||
/// 入场记录已保存(有 Id)且至少存在一行「未生成卡片 + 份数>0」的明细。
|
||||
/// 已打印态下用户「继续拆码」新增了行,按钮自动重新可用;全部行都已生成卡片时不可用。
|
||||
/// 入场记录已保存(有 Id)且至少存在一行「未生成卡片 + 份数>0」的明细,
|
||||
/// 且入场总重尚未被已打印卡片扣完。
|
||||
/// </summary>
|
||||
public bool CanGenerateCards =>
|
||||
!string.IsNullOrWhiteSpace(Entry?.Id)
|
||||
&& SplitCodeDetails.Any(d => !d.HasCard && (d.Portions ?? 0) > 0);
|
||||
&& SplitCodeDetails.Any(d => !d.HasCard && (d.Portions ?? 0) > 0)
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】已打印卡片扣完总重后禁用生成-----------
|
||||
&& GetEntryRemainingWeightForCards() > 0.009d;
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】已打印卡片扣完总重后禁用生成-----------
|
||||
|
||||
/// <summary>已打印卡片累计重量 = Σ(HasCard 行 份数×每份重量)。</summary>
|
||||
private double GetPrintedCardsWeight() =>
|
||||
SplitCodeDetails
|
||||
.Where(d => d.HasCard)
|
||||
.Sum(d => (d.PortionWeight ?? 0d) * (d.Portions ?? 0));
|
||||
|
||||
/// <summary>入场总重减去已打印卡片累计后的剩余可打印重量。</summary>
|
||||
private double GetEntryRemainingWeightForCards()
|
||||
{
|
||||
var total = Entry?.TotalWeight ?? 0d;
|
||||
return Math.Round(total - GetPrintedCardsWeight(), 2);
|
||||
}
|
||||
|
||||
public DelegateCommand ToggleRightPanelCommand { get; }
|
||||
public DelegateCommand RefreshTodayEntriesCommand { get; }
|
||||
@@ -244,13 +266,14 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
IMixerMaterialService mixerMaterialService,
|
||||
IMixerMaterialTareStrategyService tareStrategyService,
|
||||
ISupplierService supplierService,
|
||||
IWeightRecordService weightRecordService,
|
||||
IRawMaterialCardService rawMaterialCardService,
|
||||
IPrintDotService printDotService,
|
||||
IPrintBizTemplateBindService printBizTemplateBindService,
|
||||
IPrintTemplateService printTemplateService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager)
|
||||
: base(entryService, dictSyncService, mixerMaterialService, tareStrategyService, supplierService, container, regionManager)
|
||||
: base(entryService, dictSyncService, mixerMaterialService, tareStrategyService, supplierService, weightRecordService, container, regionManager)
|
||||
{
|
||||
_rawMaterialCardService = rawMaterialCardService;
|
||||
_printDotService = printDotService;
|
||||
@@ -314,7 +337,10 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
private void OnSplitDetailHasCardChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(RawMaterialSplitDetailItem.HasCard)
|
||||
or nameof(RawMaterialSplitDetailItem.Portions))
|
||||
or nameof(RawMaterialSplitDetailItem.Portions)
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】每份重量变化同步刷新可生成状态-----------
|
||||
or nameof(RawMaterialSplitDetailItem.PortionWeight))
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】每份重量变化同步刷新可生成状态-----------
|
||||
{
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
RaisePropertyChanged(nameof(CanResplit));
|
||||
@@ -329,71 +355,175 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
_selectedTodayEntry = null;
|
||||
RaisePropertyChanged(nameof(SelectedTodayEntry));
|
||||
_suppressTodaySelectionReaction = false;
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】切回新增态时清除编辑导航标记-----------
|
||||
_editingEntryId = null;
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】切回新增态时清除编辑导航标记-----------
|
||||
base.InitializeForAdd();
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
}
|
||||
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】操作页支持导航进入编辑态-----------
|
||||
public void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
HasAppliedNavigation = true;
|
||||
_ = ApplyNavigationAsync(navigationContext.Parameters);
|
||||
}
|
||||
|
||||
public bool IsNavigationTarget(NavigationContext navigationContext)
|
||||
{
|
||||
var parameters = navigationContext.Parameters;
|
||||
if (parameters.TryGetValue<string>("editEntryId", out var editId) && !string.IsNullOrWhiteSpace(editId))
|
||||
return string.Equals(_editingEntryId, editId, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return string.IsNullOrWhiteSpace(_editingEntryId) && IsAddMode;
|
||||
}
|
||||
|
||||
public void OnNavigatedFrom(NavigationContext navigationContext) { }
|
||||
|
||||
private async Task ApplyNavigationAsync(INavigationParameters parameters)
|
||||
{
|
||||
if (parameters.TryGetValue<string>("editEntryId", out var editId) && !string.IsNullOrWhiteSpace(editId))
|
||||
{
|
||||
_editingEntryId = editId;
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var entry = await EntryService.GetByIdAsync(editId);
|
||||
if (entry == null)
|
||||
{
|
||||
Growl.Warning($"未找到入场记录(Id={editId}),已切换为新增。");
|
||||
InitializeForAdd();
|
||||
return;
|
||||
}
|
||||
|
||||
await ApplyTodayRowToFormAsync(entry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载入场记录失败:{ex.Message}");
|
||||
InitializeForAdd();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_editingEntryId = null;
|
||||
InitializeForAdd();
|
||||
}
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】操作页支持导航进入编辑态-----------
|
||||
|
||||
/// <summary>保存后按业务打印绑定拉取模板与 printData,直接发送到 PrintDot 打印机(不弹预览窗)。</summary>
|
||||
private async Task SaveAndPrintAsync()
|
||||
{
|
||||
if (IsActionBusy) return;
|
||||
if (Entry == null) return;
|
||||
var wasEdit = !IsAddMode;
|
||||
var barcode = Entry.Barcode;
|
||||
BeginActionBusy("保存并打印中...");
|
||||
// 新增模式下:打印失败必须回滚删除刚生成的入场记录,避免“未打印成功却已落库”
|
||||
var createdInThisAttempt = IsAddMode;
|
||||
string? createdEntryId = null;
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
if (!await PersistEntryCoreAsync()) return;
|
||||
|
||||
string? entryId = Entry.Id;
|
||||
// Persist 后条码/主键可能已回写,再取一次
|
||||
var barcode = Entry?.Barcode;
|
||||
string? entryId = Entry?.Id;
|
||||
if (string.IsNullOrWhiteSpace(entryId) && !string.IsNullOrWhiteSpace(barcode))
|
||||
{
|
||||
var page = await EntryService.PageAsync(1, 50, barcode: barcode);
|
||||
entryId = page.Records
|
||||
entryId = page.Records?
|
||||
.FirstOrDefault(e => string.Equals(e.Barcode, barcode, StringComparison.OrdinalIgnoreCase))?.Id
|
||||
?? page.Records
|
||||
?? page.Records?
|
||||
.OrderByDescending(e => e.EntryTime ?? e.CreateTime ?? DateTime.MinValue)
|
||||
.FirstOrDefault()?.Id;
|
||||
if (!string.IsNullOrWhiteSpace(entryId) && Entry != null)
|
||||
Entry.Id = entryId;
|
||||
}
|
||||
|
||||
if (createdInThisAttempt)
|
||||
createdEntryId = entryId;
|
||||
|
||||
async Task RollbackCreatedAsync(string reason)
|
||||
{
|
||||
if (!createdInThisAttempt || string.IsNullOrWhiteSpace(createdEntryId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error(reason);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await EntryService.DeleteAsync(createdEntryId);
|
||||
if (Entry != null)
|
||||
{
|
||||
Entry.Id = null;
|
||||
Entry.Barcode = null;
|
||||
Entry.BatchNo = null;
|
||||
}
|
||||
Result = false;
|
||||
await LoadTodayEntriesAsync();
|
||||
}
|
||||
catch (Exception delEx)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error(
|
||||
$"{reason}\n同时回滚删除入场记录失败:{delEx.Message}\n请手动删除刚生成的记录。");
|
||||
return;
|
||||
}
|
||||
|
||||
HandyControl.Controls.MessageBox.Error($"{reason}\n已撤销本次入场记录,未成功打印不会保留数据。");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entryId))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("保存成功,但未能解析记录主键,无法打印。请从右侧列表选中该条后再试。");
|
||||
await RollbackCreatedAsync("保存后未能解析记录主键,无法打印。");
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
var (templateJson, printDataJson, err) = await EntryService.PrepareNativePrintAsync(entryId);
|
||||
if (!string.IsNullOrWhiteSpace(err))
|
||||
{
|
||||
var (templateJson, printDataJson, err) = await EntryService.PrepareNativePrintAsync(entryId);
|
||||
if (!string.IsNullOrWhiteSpace(err))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"保存成功,但打印准备失败:{err}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectedPrinterName = SelectedPrinter?.Name?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(selectedPrinterName))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("保存成功,但未选择打印机。请先选择打印机后再试。");
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonObject? dataObj = JsonNode.Parse(printDataJson) as JsonObject;
|
||||
dataObj ??= new JsonObject();
|
||||
var html = NativePrintRenderService.RenderToHtml(templateJson, dataObj);
|
||||
var tpl = BuildPrintTemplateForEntry(templateJson);
|
||||
var pdfBase64 = await HtmlToPdfRenderer.RenderAsync(
|
||||
html,
|
||||
tpl.PaperWidthMm ?? 210d,
|
||||
tpl.PaperHeightMm ?? 297d);
|
||||
var jobName = string.IsNullOrWhiteSpace(barcode) ? "原料入场记录" : $"原料入场记录-{barcode}";
|
||||
await _printDotService.PrintAsync(selectedPrinterName, pdfBase64, jobName, 1);
|
||||
}
|
||||
}
|
||||
await RollbackCreatedAsync($"打印准备失败:{err}");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(templateJson))
|
||||
{
|
||||
await RollbackCreatedAsync("打印模板为空,请先配置业务打印绑定。");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedPrinterName = SelectedPrinter?.Name?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(selectedPrinterName))
|
||||
{
|
||||
await RollbackCreatedAsync("未选择打印机,请先选择打印机后再试。");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject? dataObj = null;
|
||||
if (!string.IsNullOrWhiteSpace(printDataJson))
|
||||
dataObj = JsonNode.Parse(printDataJson) as JsonObject;
|
||||
dataObj ??= new JsonObject();
|
||||
var html = NativePrintRenderService.RenderToHtml(templateJson, dataObj);
|
||||
var tpl = BuildPrintTemplateForEntry(templateJson);
|
||||
var pdfBase64 = await HtmlToPdfRenderer.RenderAsync(
|
||||
html,
|
||||
tpl.PaperWidthMm ?? 210d,
|
||||
tpl.PaperHeightMm ?? 297d);
|
||||
if (string.IsNullOrWhiteSpace(pdfBase64))
|
||||
{
|
||||
await RollbackCreatedAsync("PDF 生成结果为空,打印失败。");
|
||||
return;
|
||||
}
|
||||
|
||||
var jobName = string.IsNullOrWhiteSpace(barcode) ? "原料入场记录" : $"原料入场记录-{barcode}";
|
||||
await _printDotService.PrintAsync(selectedPrinterName, pdfBase64, jobName, 1);
|
||||
|
||||
Growl.Success(new GrowlInfo
|
||||
{
|
||||
Message = "保存成功",
|
||||
Message = "保存并打印成功",
|
||||
ShowDateTime = false,
|
||||
WaitTime = 1
|
||||
});
|
||||
@@ -405,7 +535,33 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"保存或打印失败:{ex.Message}");
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】保存并打印失败时回滚新增记录-----------
|
||||
if (createdInThisAttempt && !string.IsNullOrWhiteSpace(createdEntryId))
|
||||
{
|
||||
try
|
||||
{
|
||||
await EntryService.DeleteAsync(createdEntryId);
|
||||
if (Entry != null)
|
||||
{
|
||||
Entry.Id = null;
|
||||
Entry.Barcode = null;
|
||||
Entry.BatchNo = null;
|
||||
}
|
||||
Result = false;
|
||||
await LoadTodayEntriesAsync();
|
||||
HandyControl.Controls.MessageBox.Error($"打印失败:{ex.Message}\n已撤销本次入场记录,未成功打印不会保留数据。");
|
||||
}
|
||||
catch (Exception delEx)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error(
|
||||
$"打印失败:{ex.Message}\n同时回滚删除入场记录失败:{delEx.Message}\n请手动删除刚生成的记录。");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"保存或打印失败:{ex.Message}");
|
||||
}
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】保存并打印失败时回滚新增记录-----------
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -536,6 +692,9 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
private async Task ApplyTodayRowToFormAsync(MesXslRawMaterialEntry src)
|
||||
{
|
||||
// 复用基类 InitializeForEdit:保留 Id/Barcode/BatchNo/状态等所有字段,标题自动切到「编辑原料入场记录」
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】今日列表选中同步编辑导航标记-----------
|
||||
_editingEntryId = src.Id;
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】今日列表选中同步编辑导航标记-----------
|
||||
base.InitializeForEdit(src);
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
|
||||
@@ -706,17 +865,36 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
|
||||
}
|
||||
}
|
||||
|
||||
// 总重核对:拆码明细合计 vs 基础资料总重
|
||||
//update-begin---author:jiangxh ---date:20260731 for:【原料入场】已打印卡片扣完总重禁止再打印-----------
|
||||
{
|
||||
var entryTotal = Entry.TotalWeight ?? 0d;
|
||||
var printedWeight = Math.Round(GetPrintedCardsWeight(), 2);
|
||||
var remaining = Math.Round(entryTotal - printedWeight, 2);
|
||||
if (remaining <= 0d)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning(
|
||||
$"入场总重 {entryTotal:0.##} 已被已打印原材料卡片总重 {printedWeight:0.##} 扣完,不能再打印原材料卡片。");
|
||||
return;
|
||||
}
|
||||
|
||||
var pendingWeight = Math.Round(
|
||||
pendingRows.Sum(d => (d.PortionWeight ?? 0d) * (d.Portions ?? 0)), 2);
|
||||
if (pendingWeight > remaining + 0.009d)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning(
|
||||
$"待打印卡片总重 {pendingWeight:0.##} 超过剩余可打印重量 {remaining:0.##}(入场总重 {entryTotal:0.##},已打印 {printedWeight:0.##}),请调整拆码明细后再生成。");
|
||||
return;
|
||||
}
|
||||
}
|
||||
//update-end---author:jiangxh ---date:20260731 for:【原料入场】已打印卡片扣完总重禁止再打印-----------
|
||||
|
||||
// 总重核对:拆码明细合计 vs 基础资料总重(剩余不足时上面已硬拦截;此处仅提示拆码合计偏少)
|
||||
{
|
||||
var splitTotal = SplitCodeDetails.Sum(d => (d.PortionWeight ?? 0d) * (d.Portions ?? 0));
|
||||
var basicTotal = Entry.TotalWeight ?? 0d;
|
||||
if (Math.Abs(splitTotal - basicTotal) > 0.01d)
|
||||
if (basicTotal - splitTotal > 0.01d)
|
||||
{
|
||||
string hint;
|
||||
if (splitTotal > basicTotal)
|
||||
hint = $"本次打印拆码总重 {splitTotal:0.##},超出基础资料总重 {basicTotal:0.##},超出部分是否需要核对?";
|
||||
else
|
||||
hint = $"本次打印拆码总重 {splitTotal:0.##},少于基础资料总重 {basicTotal:0.##},剩余部分将无法继续拆码,是否继续?";
|
||||
var hint = $"本次打印拆码总重 {splitTotal:0.##},少于基础资料总重 {basicTotal:0.##},剩余部分将无法继续拆码,是否继续?";
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
hint,
|
||||
"总重核对",
|
||||
|
||||
Reference in New Issue
Block a user