using HandyControl.Controls; using HandyControl.Tools.Extension; using System.Collections.ObjectModel; using YY.Admin.Core; using YY.Admin.Core.Entity; using YY.Admin.Core.Services; using YY.Admin.Services.Service; using YY.Admin.Views.RawMaterialEntry; namespace YY.Admin.ViewModels.RawMaterialEntry; public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResultable { private readonly IRawMaterialEntryService _entryService; private readonly IJeecgDictSyncService _dictSyncService; private readonly IMixerMaterialService _mixerMaterialService; // 加载完物料后用于回填 Edit 模式选中项 private string? _pendingMaterialId; private MesXslRawMaterialEntry? _entry; public MesXslRawMaterialEntry? Entry { get => _entry; set => SetProperty(ref _entry, value); } private MesMixerMaterial? _selectedMaterial; public MesMixerMaterial? SelectedMaterial { get => _selectedMaterial; set { if (!SetProperty(ref _selectedMaterial, value) || value == null || Entry == null) return; Entry.MaterialId = value.Id; Entry.MaterialCode = value.MaterialCode; Entry.MaterialName = value.MaterialName; Entry.ManufacturerMaterialName = value.AliasName; RecalculateShelfLife(value); RaisePropertyChanged(nameof(Entry)); RaisePropertyChanged(nameof(SelectedMaterialDisplay)); RaisePropertyChanged(nameof(HasSelectedMaterial)); // 新增模式自动生成条码/批次号 if (IsAddMode && !string.IsNullOrEmpty(value.MaterialCode)) _ = AutoGenerateBarcodeAsync(value.MaterialCode); } } private bool _isGenerating; public bool IsGenerating { get => _isGenerating; set => SetProperty(ref _isGenerating, value); } public bool IsAddMode => string.IsNullOrWhiteSpace(Entry?.Id); public string DialogTitle => IsAddMode ? "新增原料入场记录" : "编辑原料入场记录"; public string SelectedMaterialDisplay => _selectedMaterial == null ? "请选择密炼物料" : $"[{_selectedMaterial.MaterialCode}] {_selectedMaterial.MaterialName}"; public bool HasSelectedMaterial => _selectedMaterial != null; public string? IsSpecialAdoptionValue { get => Entry?.IsSpecialAdoption; set { if (Entry == null || Entry.IsSpecialAdoption == value) { return; } Entry.IsSpecialAdoption = value; if (!string.Equals(value, "1", StringComparison.Ordinal)) { Entry.SpecialAdoptionOperator = null; Entry.SpecialAdoptionTime = null; Entry.SpecialAdoptionReason = null; } RaisePropertyChanged(nameof(Entry)); RaisePropertyChanged(nameof(IsSpecialAdoptionValue)); } } public double? TotalWeightInput { get => Entry?.TotalWeight; set { if (Entry == null || Entry.TotalWeight == value) return; Entry.TotalWeight = value; RecalculatePortionWeight(); RaisePropertyChanged(nameof(Entry)); } } public int? TotalPortionsInput { get => Entry?.TotalPortions; set { if (Entry == null || Entry.TotalPortions == value) return; Entry.TotalPortions = value; RecalculatePortionWeight(); RaisePropertyChanged(nameof(Entry)); } } public ObservableCollection MaterialOptions { get; } = new(); public ObservableCollection> TestResultOptions { get; } = new(); public ObservableCollection> TestStatusOptions { get; } = new(); public ObservableCollection> PrintFlagOptions { get; } = new(); public ObservableCollection> StockBalanceOptions { get; } = new(); public ObservableCollection> IsSpecialAdoptionOptions { get; } = new(); public ObservableCollection> StatusOptions { get; } = new(); public ObservableCollection SplitCodeDetails { get; } = new(); public double SplitCodeTableHeight => CalculateSplitCodeTableHeight(); private bool _result; public bool Result { get => _result; set => SetProperty(ref _result, value); } public Action? CloseAction { get; set; } public DelegateCommand SaveCommand { get; } public DelegateCommand CancelCommand { get; } public DelegateCommand ResetCommand { get; } public DelegateCommand AddSplitDetailCommand { get; } public DelegateCommand RemoveSplitDetailCommand { get; } public DelegateCommand OpenMaterialPickerCommand { get; } public DelegateCommand ClearMaterialCommand { get; } public DelegateCommand OpenWeightRecordPickerCommand { get; } public DelegateCommand ClearWeightRecordCommand { get; } public RawMaterialEntryEditDialogViewModel( IRawMaterialEntryService entryService, IJeecgDictSyncService dictSyncService, IMixerMaterialService mixerMaterialService, IContainerExtension container, IRegionManager regionManager) : base(container, regionManager) { _entryService = entryService; _dictSyncService = dictSyncService; _mixerMaterialService = mixerMaterialService; SaveCommand = new DelegateCommand(async () => await SaveAsync()); CancelCommand = new DelegateCommand(() => CloseAction?.Invoke()); ResetCommand = new DelegateCommand(InitializeForAdd); AddSplitDetailCommand = new DelegateCommand(AddSplitDetailRow); RemoveSplitDetailCommand = new DelegateCommand(RemoveSplitDetailRow); OpenMaterialPickerCommand = new DelegateCommand(async () => await OpenMaterialPickerAsync()); ClearMaterialCommand = new DelegateCommand(ClearMaterialSelection); OpenWeightRecordPickerCommand = new DelegateCommand(async () => await OpenWeightRecordPickerAsync()); ClearWeightRecordCommand = new DelegateCommand(ClearWeightRecordSelection); SplitCodeDetails.CollectionChanged += (_, _) => RaisePropertyChanged(nameof(SplitCodeTableHeight)); _ = LoadAllAsync(); } private async Task LoadAllAsync() { await Task.WhenAll(LoadDictOptionsAsync(), LoadMaterialOptionsAsync()); } private async Task LoadMaterialOptionsAsync() { try { // 每次打开弹窗都主动拉一次,确保直接写库的数据也能同步到 await _mixerMaterialService.SyncFromRemoteAsync(); var result = await _mixerMaterialService.PageAsync(1, 2000); MaterialOptions.Clear(); foreach (var m in result.Records) MaterialOptions.Add(m); // Edit 模式:物料加载完后回填选中项 if (_pendingMaterialId != null) { var match = MaterialOptions.FirstOrDefault(m => string.Equals(m.Id, _pendingMaterialId, StringComparison.OrdinalIgnoreCase)); if (match != null) SelectedMaterial = match; _pendingMaterialId = null; } } catch { /* 无法加载物料列表时不阻断表单 */ } } private async Task LoadDictOptionsAsync() { try { var testResultOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_test_result"); var testStatusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_test_status"); var printFlagOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_print_flag"); var ynOpts = await _dictSyncService.GetDictOptionsAsync("yn"); var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_entry_status"); PopulateOptions(TestResultOptions, testResultOpts, new[] { new KeyValuePair("未检", "0"), new KeyValuePair("合格", "1"), new KeyValuePair("不合格", "2"), }); PopulateOptions(TestStatusOptions, testStatusOpts, new[] { new KeyValuePair("送样", "0"), new KeyValuePair("已批准", "1"), }); PopulateOptions(PrintFlagOptions, printFlagOpts, new[] { new KeyValuePair("未打印", "0"), new KeyValuePair("已打印", "1"), }); PopulateOptions(StockBalanceOptions, ynOpts, new[] { new KeyValuePair("否", "0"), new KeyValuePair("是", "1"), }); PopulateOptions(IsSpecialAdoptionOptions, ynOpts, new[] { new KeyValuePair("否", "0"), new KeyValuePair("是", "1"), }); PopulateOptions(StatusOptions, statusOpts, Array.Empty>()); ApplyDefaultEntryStatusForAdd(); } catch { FillFallbackOptions(); ApplyDefaultEntryStatusForAdd(); } } private static void PopulateOptions( ObservableCollection> target, IEnumerable> items, IEnumerable> fallback) { target.Clear(); var list = items.ToList(); foreach (var item in list.Count > 0 ? list : fallback.ToList()) target.Add(item); } private void FillFallbackOptions() { PopulateOptions(TestResultOptions, Array.Empty>(), new[] { new KeyValuePair("未检", "0"), new KeyValuePair("合格", "1"), new KeyValuePair("不合格", "2"), }); PopulateOptions(TestStatusOptions, Array.Empty>(), new[] { new KeyValuePair("送样", "0"), new KeyValuePair("已批准", "1"), }); PopulateOptions(PrintFlagOptions, Array.Empty>(), new[] { new KeyValuePair("未打印", "0"), new KeyValuePair("已打印", "1"), }); var ynDefault = new[] { new KeyValuePair("否", "0"), new KeyValuePair("是", "1") }; PopulateOptions(StockBalanceOptions, Array.Empty>(), ynDefault); PopulateOptions(IsSpecialAdoptionOptions, Array.Empty>(), ynDefault); } private async Task AutoGenerateBarcodeAsync(string materialCode) { IsGenerating = true; try { var code = await _entryService.GenerateBarcodeAsync(materialCode); if (!string.IsNullOrEmpty(code) && Entry != null) { Entry.Barcode = code; Entry.BatchNo = code; RaisePropertyChanged(nameof(Entry)); } } finally { IsGenerating = false; } } public void InitializeForAdd() { _selectedMaterial = null; RaisePropertyChanged(nameof(SelectedMaterial)); RaisePropertyChanged(nameof(SelectedMaterialDisplay)); RaisePropertyChanged(nameof(HasSelectedMaterial)); Entry = new MesXslRawMaterialEntry { EntryTime = DateTime.Now, IsSpecialAdoption = "0" }; InitializeSplitCodeDetailsFromEntry(); ApplyDefaultEntryStatusForAdd(); RaisePropertyChanged(nameof(IsAddMode)); RaisePropertyChanged(nameof(DialogTitle)); RaisePropertyChanged(nameof(TotalWeightInput)); RaisePropertyChanged(nameof(TotalPortionsInput)); RaisePropertyChanged(nameof(IsSpecialAdoptionValue)); } public void InitializeForEdit(MesXslRawMaterialEntry entry) { Entry = new MesXslRawMaterialEntry { Id = entry.Id, Barcode = entry.Barcode, BatchNo = entry.BatchNo, EntryTime = entry.EntryTime, WeightRecordId = entry.WeightRecordId, BillNo = entry.BillNo, MaterialId = entry.MaterialId, MaterialCode = entry.MaterialCode, MaterialName = entry.MaterialName, SupplyCustomer = entry.SupplyCustomer, SupplierId = entry.SupplierId, SupplierName = entry.SupplierName, ManufacturerMaterialName = entry.ManufacturerMaterialName, ShelfLife = entry.ShelfLife, TotalWeight = entry.TotalWeight, TotalPortions = entry.TotalPortions, PortionWeight = entry.PortionWeight, PortionPackages = entry.PortionPackages, TestResult = entry.TestResult, TestStatus = entry.TestStatus, PrintFlag = entry.PrintFlag, StockBalance = entry.StockBalance, WarehouseLocation = entry.WarehouseLocation, UnloadOperator = entry.UnloadOperator, IsSpecialAdoption = entry.IsSpecialAdoption, SpecialAdoptionOperator = entry.SpecialAdoptionOperator, SpecialAdoptionTime = entry.SpecialAdoptionTime, SpecialAdoptionReason = entry.SpecialAdoptionReason, Status = entry.Status, Remark = entry.Remark, TenantId = entry.TenantId, }; InitializeSplitCodeDetailsFromEntry(); // 若物料列表已加载则直接回填,否则记录 pending 等加载完后回填 if (MaterialOptions.Count > 0) { _selectedMaterial = MaterialOptions.FirstOrDefault(m => string.Equals(m.Id, entry.MaterialId, StringComparison.OrdinalIgnoreCase)); RaisePropertyChanged(nameof(SelectedMaterial)); RaisePropertyChanged(nameof(SelectedMaterialDisplay)); RaisePropertyChanged(nameof(HasSelectedMaterial)); } else { _pendingMaterialId = entry.MaterialId; } RaisePropertyChanged(nameof(IsAddMode)); RaisePropertyChanged(nameof(DialogTitle)); RaisePropertyChanged(nameof(TotalWeightInput)); RaisePropertyChanged(nameof(TotalPortionsInput)); RaisePropertyChanged(nameof(IsSpecialAdoptionValue)); } private async Task SaveAsync() { if (Entry == null) return; try { ApplyFirstSplitDetailToEntry(); RecalculatePortionWeight(); bool ok; if (IsAddMode) { ok = await _entryService.AddAsync(Entry); if (ok) HandyControl.Controls.MessageBox.Success("新增成功!"); else { HandyControl.Controls.MessageBox.Error("新增失败!"); return; } } else { ok = await _entryService.EditAsync(Entry); if (!ok) { HandyControl.Controls.MessageBox.Error("编辑失败!"); return; } } Result = ok; if (IsAddMode && CloseAction == null) { // 独立新增页面:保存成功后自动清空表单,便于连续录入 InitializeForAdd(); return; } CloseAction?.Invoke(); } catch (Exception ex) { HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}"); } } private async Task OpenWeightRecordPickerAsync() { WeightRecordPickerDialogViewModel? pickerVm = null; bool confirmed; try { confirmed = await HandyControl.Controls.Dialog.Show() .Initialize(vm => { pickerVm = vm; vm.Initialize(Entry?.BillNo); }) .GetResultAsync(); } catch { return; } if (!confirmed || pickerVm?.SelectedRecord == null || Entry == null) { return; } var selected = pickerVm.SelectedRecord; Entry.WeightRecordId = selected.Id; Entry.BillNo = selected.BillNo; Entry.SupplierName = selected.SenderUnit; Entry.SupplierId = null; RaisePropertyChanged(nameof(Entry)); } private async Task OpenMaterialPickerAsync() { RawMaterialEntryMaterialPickerDialogViewModel? pickerVm = null; bool confirmed; try { confirmed = await HandyControl.Controls.Dialog.Show() .Initialize(vm => { pickerVm = vm; vm.Initialize(Entry?.MaterialCode, Entry?.MaterialName); }) .GetResultAsync(); } catch { return; } if (!confirmed || pickerVm?.SelectedMaterial == null) { return; } SelectedMaterial = pickerVm.SelectedMaterial; } private void ClearMaterialSelection() { _selectedMaterial = null; RaisePropertyChanged(nameof(SelectedMaterial)); RaisePropertyChanged(nameof(SelectedMaterialDisplay)); RaisePropertyChanged(nameof(HasSelectedMaterial)); if (Entry == null) { return; } Entry.MaterialId = null; Entry.MaterialCode = null; Entry.MaterialName = null; Entry.ManufacturerMaterialName = null; Entry.ShelfLife = null; RaisePropertyChanged(nameof(Entry)); } private void ClearWeightRecordSelection() { if (Entry == null) { return; } Entry.WeightRecordId = null; Entry.BillNo = null; Entry.SupplierId = null; Entry.SupplierName = null; RaisePropertyChanged(nameof(Entry)); } private void RecalculateShelfLife(MesMixerMaterial? material) { if (Entry == null || material?.ShelfLifeDays == null || material.ShelfLifeDays <= 0) { return; } Entry.ShelfLife = DateTime.Now.Date.AddDays(material.ShelfLifeDays.Value).ToString("yyyy-MM-dd"); } private void RecalculatePortionWeight() { if (Entry == null) { return; } if (Entry.TotalWeight.HasValue && Entry.TotalPortions.HasValue && Entry.TotalPortions.Value > 0) { Entry.PortionWeight = Math.Round(Entry.TotalWeight.Value / Entry.TotalPortions.Value, 2, MidpointRounding.AwayFromZero); return; } Entry.PortionWeight = null; } private void ApplyDefaultEntryStatusForAdd() { if (!IsAddMode || Entry == null || !string.IsNullOrWhiteSpace(Entry.Status)) { return; } var pending = StatusOptions.FirstOrDefault(x => string.Equals(x.Key?.Trim(), "待处理", StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrWhiteSpace(pending.Value)) { Entry.Status = pending.Value; return; } // 字典未就绪时使用常见默认值 Entry.Status = "0"; } private void InitializeSplitCodeDetailsFromEntry() { SplitCodeDetails.Clear(); SplitCodeDetails.Add(new RawMaterialSplitDetailItem { Portions = Entry?.TotalPortions, PortionWeight = Entry?.PortionWeight, PortionPackages = Entry?.PortionPackages, WarehouseLocation = Entry?.WarehouseLocation }); RaisePropertyChanged(nameof(SplitCodeTableHeight)); } private void AddSplitDetailRow() { SplitCodeDetails.Add(new RawMaterialSplitDetailItem()); RaisePropertyChanged(nameof(SplitCodeTableHeight)); } private void RemoveSplitDetailRow(RawMaterialSplitDetailItem? item) { if (item == null) { return; } SplitCodeDetails.Remove(item); if (SplitCodeDetails.Count == 0) { SplitCodeDetails.Add(new RawMaterialSplitDetailItem()); } RaisePropertyChanged(nameof(SplitCodeTableHeight)); } private void ApplyFirstSplitDetailToEntry() { if (Entry == null || SplitCodeDetails.Count == 0) { return; } var first = SplitCodeDetails[0]; Entry.TotalPortions = first.Portions; Entry.PortionWeight = first.PortionWeight; Entry.PortionPackages = first.PortionPackages; Entry.WarehouseLocation = first.WarehouseLocation; } private double CalculateSplitCodeTableHeight() { const double headerHeight = 36d; const double rowHeight = 36d; const double framePadding = 16d; const double minRows = 1d; const double maxRows = 6d; var rowCount = Math.Clamp(SplitCodeDetails.Count, (int)minRows, (int)maxRows); return headerHeight + rowCount * rowHeight + framePadding; } } public class RawMaterialSplitDetailItem : BindableBase { private int? _portions; public int? Portions { get => _portions; set => SetProperty(ref _portions, value); } private double? _portionWeight; public double? PortionWeight { get => _portionWeight; set => SetProperty(ref _portionWeight, value); } private int? _portionPackages; public int? PortionPackages { get => _portionPackages; set => SetProperty(ref _portionPackages, value); } private string? _warehouseLocation; public string? WarehouseLocation { get => _warehouseLocation; set => SetProperty(ref _warehouseLocation, value); } }