增强原材料卡片管理功能,新增免密接口和数据处理逻辑,支持原材料卡片的增删改查操作。更新前端视图以支持多行拆码明细拼接,优化用户体验和系统实时数据同步能力。
This commit is contained in:
@@ -130,7 +130,12 @@ namespace YY.Admin.ViewModels.Control
|
||||
// 已实现页面:新增原料入场记录
|
||||
["RawMaterialEntryOperationView"] = "RawMaterialEntryOperationView",
|
||||
["/xslmes/rawMaterialEntryOperation"] = "RawMaterialEntryOperationView",
|
||||
["rawMaterialEntryOperation"] = "RawMaterialEntryOperationView"
|
||||
["rawMaterialEntryOperation"] = "RawMaterialEntryOperationView",
|
||||
|
||||
// 已实现页面:原材料卡片
|
||||
["RawMaterialCardListView"] = "RawMaterialCardListView",
|
||||
["/xslmes/mesXslRawMaterialCard"] = "RawMaterialCardListView",
|
||||
["mesXslRawMaterialCard"] = "RawMaterialCardListView"
|
||||
};
|
||||
|
||||
private MenuItem? _selectedMenuItem;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
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;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialCard;
|
||||
|
||||
public class RawMaterialCardEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IRawMaterialCardService _cardService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
|
||||
private MesXslRawMaterialCard? _card;
|
||||
public MesXslRawMaterialCard? Card
|
||||
{
|
||||
get => _card;
|
||||
set => SetProperty(ref _card, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Card?.Id);
|
||||
public string DialogTitle => IsAddMode ? "新增原材料卡片" : "编辑原材料卡片";
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> TestResultOptions { get; } = new();
|
||||
|
||||
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 RawMaterialCardEditDialogViewModel(
|
||||
IRawMaterialCardService cardService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_cardService = cardService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadDictOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_card_status");
|
||||
var testResultOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_test_result");
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("正常", "1"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("异常", "0"));
|
||||
}
|
||||
|
||||
TestResultOptions.Clear();
|
||||
foreach (var item in testResultOpts) TestResultOptions.Add(item);
|
||||
if (TestResultOptions.Count == 0)
|
||||
{
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("未检", "0"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("合格", "1"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("不合格", "2"));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("正常", "1"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("异常", "0"));
|
||||
|
||||
TestResultOptions.Clear();
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("未检", "0"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("合格", "1"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("不合格", "2"));
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Card = new MesXslRawMaterialCard
|
||||
{
|
||||
Status = "1",
|
||||
TestResult = "0",
|
||||
PriorityPickup = "0",
|
||||
EntryDate = DateTime.Today
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslRawMaterialCard card)
|
||||
{
|
||||
Card = new MesXslRawMaterialCard
|
||||
{
|
||||
Id = card.Id,
|
||||
Barcode = card.Barcode,
|
||||
BatchNo = card.BatchNo,
|
||||
EntryDate = card.EntryDate,
|
||||
MaterialId = card.MaterialId,
|
||||
MaterialName = card.MaterialName,
|
||||
MaterialDesc = card.MaterialDesc,
|
||||
SupplierId = card.SupplierId,
|
||||
SupplierName = card.SupplierName,
|
||||
ManufacturerMaterialName = card.ManufacturerMaterialName,
|
||||
ShelfLife = card.ShelfLife,
|
||||
TotalWeight = card.TotalWeight,
|
||||
RemainingWeight = card.RemainingWeight,
|
||||
RemainingQuantity = card.RemainingQuantity,
|
||||
Status = card.Status,
|
||||
TestResult = card.TestResult,
|
||||
WarehouseArea = card.WarehouseArea,
|
||||
UnloadOperator = card.UnloadOperator,
|
||||
PriorityPickup = card.PriorityPickup,
|
||||
TenantId = card.TenantId,
|
||||
UpdateTime = card.UpdateTime
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Card == null) return;
|
||||
if (string.IsNullOrWhiteSpace(Card.MaterialName))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("物料名称不能为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
ok = await _cardService.AddAsync(Card);
|
||||
if (ok) HandyControl.Controls.MessageBox.Success("新增原材料卡片成功!");
|
||||
else { HandyControl.Controls.MessageBox.Error("新增原材料卡片失败!"); return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = await _cardService.EditAsync(Card);
|
||||
if (!ok) { HandyControl.Controls.MessageBox.Error("编辑原材料卡片失败!"); return; }
|
||||
}
|
||||
Result = ok;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.RawMaterialCard;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialCard;
|
||||
|
||||
public class RawMaterialCardListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IRawMaterialCardService _cardService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private SubscriptionToken? _changedToken;
|
||||
private SubscriptionToken? _syncConflictToken;
|
||||
|
||||
private ObservableCollection<MesXslRawMaterialCard> _cards = new();
|
||||
public ObservableCollection<MesXslRawMaterialCard> Cards
|
||||
{
|
||||
get => _cards;
|
||||
set => SetProperty(ref _cards, value);
|
||||
}
|
||||
|
||||
private long _total;
|
||||
public long Total { get => _total; set => SetProperty(ref _total, value); }
|
||||
|
||||
private int _pageNo = 1;
|
||||
public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); }
|
||||
|
||||
private int _pageSize = 20;
|
||||
public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); }
|
||||
|
||||
private string? _filterBarcode;
|
||||
public string? FilterBarcode { get => _filterBarcode; set => SetProperty(ref _filterBarcode, value); }
|
||||
|
||||
private string? _filterBatchNo;
|
||||
public string? FilterBatchNo { get => _filterBatchNo; set => SetProperty(ref _filterBatchNo, value); }
|
||||
|
||||
private string? _filterMaterialName;
|
||||
public string? FilterMaterialName { get => _filterMaterialName; set => SetProperty(ref _filterMaterialName, value); }
|
||||
|
||||
private string? _filterSupplierName;
|
||||
public string? FilterSupplierName { get => _filterSupplierName; set => SetProperty(ref _filterSupplierName, value); }
|
||||
|
||||
private string? _filterStatus;
|
||||
public string? FilterStatus { get => _filterStatus; set => SetProperty(ref _filterStatus, value); }
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> TestResultOptions { get; } = new();
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslRawMaterialCard> EditCommand { get; }
|
||||
public DelegateCommand<MesXslRawMaterialCard> DeleteCommand { get; }
|
||||
public DelegateCommand<MesXslRawMaterialCard> TogglePriorityCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public RawMaterialCardListViewModel(
|
||||
IRawMaterialCardService cardService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_cardService = cardService;
|
||||
_dictSyncService = dictSyncService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterBarcode = null; FilterBatchNo = null; FilterMaterialName = null;
|
||||
FilterSupplierName = null; FilterStatus = null; PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslRawMaterialCard>(async c => await ShowEditDialogAsync(c));
|
||||
DeleteCommand = new DelegateCommand<MesXslRawMaterialCard>(async c => await DeleteAsync(c));
|
||||
TogglePriorityCommand = new DelegateCommand<MesXslRawMaterialCard>(async c => await TogglePriorityAsync(c));
|
||||
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
|
||||
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
|
||||
|
||||
_changedToken = _eventAggregator.GetEvent<RawMaterialCardChangedEvent>()
|
||||
.Subscribe(async p => await OnChangedAsync(p), ThreadOption.UIThread);
|
||||
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task OnChangedAsync(RawMaterialCardChangedPayload payload)
|
||||
{
|
||||
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.CardId))
|
||||
await RefreshSingleAsync(payload.CardId!);
|
||||
else
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshSingleAsync(string cardId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _cardService.GetByIdAsync(cardId);
|
||||
if (updated == null) return;
|
||||
var idx = Cards.ToList().FindIndex(c => string.Equals(c.Id, cardId, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0) Cards[idx] = updated;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[原材料卡片] 单条刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncConflict(SyncConflictPayload payload)
|
||||
{
|
||||
if (!string.Equals(payload.EntityName, "原材料卡片", StringComparison.OrdinalIgnoreCase)) return;
|
||||
var parts = new List<string>();
|
||||
if (payload.PushedCount > 0) parts.Add($"已同步 {payload.PushedCount} 条本地改动到服务器");
|
||||
if (payload.NewRecordsPushed > 0) parts.Add($"已上传 {payload.NewRecordsPushed} 条本地新增记录");
|
||||
if (payload.ConflictCount > 0) parts.Add($"{payload.ConflictCount} 条记录与服务器版本冲突,已保留服务器版本");
|
||||
if (parts.Count == 0) return;
|
||||
var message = string.Join("\n", parts);
|
||||
if (payload.ConflictCount > 0) Growl.Warning(message);
|
||||
else Growl.Success(message);
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadDictOptionsAsync();
|
||||
await UIHelper.WaitForRenderAsync();
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[原材料卡片] 初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_card_status", includeAll: true);
|
||||
var testResultOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_test_result", includeAll: true);
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
if (StatusOptions.Count == 0) StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
|
||||
TestResultOptions.Clear();
|
||||
foreach (var item in testResultOpts) TestResultOptions.Add(item);
|
||||
if (TestResultOptions.Count == 0) TestResultOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
}
|
||||
catch
|
||||
{
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("正常", "1"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("异常", "0"));
|
||||
|
||||
TestResultOptions.Clear();
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("未检", "0"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("合格", "1"));
|
||||
TestResultOptions.Add(new KeyValuePair<string, string>("不合格", "2"));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await _cardService.PageAsync(PageNo, PageSize,
|
||||
FilterBarcode, FilterBatchNo, FilterMaterialName, FilterSupplierName, FilterStatus);
|
||||
Cards = new ObservableCollection<MesXslRawMaterialCard>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载原材料卡片失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowAddDialogAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<RawMaterialCardEditDialogView>()
|
||||
.Initialize<RawMaterialCardEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开新增对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslRawMaterialCard card)
|
||||
{
|
||||
if (card == null) return;
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<RawMaterialCardEditDialogView>()
|
||||
.Initialize<RawMaterialCardEditDialogViewModel>(vm => vm.InitializeForEdit(card))
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开编辑对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslRawMaterialCard card)
|
||||
{
|
||||
if (card?.Id == null) return;
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
$"确定删除原材料卡片(条码:{card.Barcode})?此操作不可恢复!",
|
||||
"确认删除", MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
var ok = await _cardService.DeleteAsync(card.Id);
|
||||
if (ok) { Growl.Success("删除成功!"); await LoadAsync(); }
|
||||
else Growl.Error("删除失败!");
|
||||
}
|
||||
|
||||
private async Task TogglePriorityAsync(MesXslRawMaterialCard card)
|
||||
{
|
||||
if (card?.Id == null) return;
|
||||
var newVal = card.PriorityPickup == "1" ? "0" : "1";
|
||||
var ok = await _cardService.UpdatePriorityAsync(card.Id, newVal);
|
||||
if (ok)
|
||||
{
|
||||
card.PriorityPickup = newVal;
|
||||
RaisePropertyChanged(nameof(Cards));
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Error("优先出库设置失败!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_changedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<RawMaterialCardChangedEvent>().Unsubscribe(_changedToken);
|
||||
_changedToken = null;
|
||||
}
|
||||
if (_syncConflictToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_syncConflictToken);
|
||||
_syncConflictToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,21 @@ using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.RawMaterialEntry;
|
||||
using YY.Admin.ViewModels.WeightRecord;
|
||||
using YY.Admin.Views.WeightRecord;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IRawMaterialEntryService _entryService;
|
||||
/// <summary>供子页面(如独立新增页右侧面板)复用列表/详情查询能力。</summary>
|
||||
protected IRawMaterialEntryService EntryService => _entryService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IMixerMaterialService _mixerMaterialService;
|
||||
|
||||
// 加载完物料后用于回填 Edit 模式选中项
|
||||
private string? _pendingMaterialId;
|
||||
protected string? _pendingMaterialId;
|
||||
|
||||
private MesXslRawMaterialEntry? _entry;
|
||||
public MesXslRawMaterialEntry? Entry
|
||||
@@ -28,7 +32,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
set => SetProperty(ref _entry, value);
|
||||
}
|
||||
|
||||
private MesMixerMaterial? _selectedMaterial;
|
||||
protected MesMixerMaterial? _selectedMaterial;
|
||||
public MesMixerMaterial? SelectedMaterial
|
||||
{
|
||||
get => _selectedMaterial;
|
||||
@@ -94,19 +98,6 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -137,6 +128,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
public DelegateCommand ClearMaterialCommand { get; }
|
||||
public DelegateCommand OpenWeightRecordPickerCommand { get; }
|
||||
public DelegateCommand ClearWeightRecordCommand { get; }
|
||||
public DelegateCommand OpenSupplierPickerCommand { get; }
|
||||
public DelegateCommand ClearSupplierCommand { get; }
|
||||
|
||||
public RawMaterialEntryEditDialogViewModel(
|
||||
IRawMaterialEntryService entryService,
|
||||
@@ -157,6 +150,8 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
ClearMaterialCommand = new DelegateCommand(ClearMaterialSelection);
|
||||
OpenWeightRecordPickerCommand = new DelegateCommand(async () => await OpenWeightRecordPickerAsync());
|
||||
ClearWeightRecordCommand = new DelegateCommand(ClearWeightRecordSelection);
|
||||
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
|
||||
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
|
||||
SplitCodeDetails.CollectionChanged += OnSplitCodeDetailsCollectionChanged;
|
||||
_ = LoadAllAsync();
|
||||
}
|
||||
@@ -166,7 +161,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
await Task.WhenAll(LoadDictOptionsAsync(), LoadMaterialOptionsAsync());
|
||||
}
|
||||
|
||||
private async Task LoadMaterialOptionsAsync()
|
||||
protected async Task LoadMaterialOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -228,11 +223,13 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
});
|
||||
PopulateOptions(StatusOptions, statusOpts, Array.Empty<KeyValuePair<string, string>>());
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
}
|
||||
catch
|
||||
{
|
||||
FillFallbackOptions();
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +267,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
PopulateOptions(IsSpecialAdoptionOptions, Array.Empty<KeyValuePair<string, string>>(), ynDefault);
|
||||
}
|
||||
|
||||
private async Task AutoGenerateBarcodeAsync(string materialCode)
|
||||
protected async Task AutoGenerateBarcodeAsync(string materialCode)
|
||||
{
|
||||
IsGenerating = true;
|
||||
try
|
||||
@@ -286,7 +283,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
finally { IsGenerating = false; }
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
public virtual void InitializeForAdd()
|
||||
{
|
||||
_selectedMaterial = null;
|
||||
RaisePropertyChanged(nameof(SelectedMaterial));
|
||||
@@ -299,10 +296,10 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
};
|
||||
InitializeSplitCodeDetailsFromEntry();
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(TotalPortionsInput));
|
||||
RaisePropertyChanged(nameof(IsSpecialAdoptionValue));
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
@@ -346,20 +343,20 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
RaisePropertyChanged(nameof(TotalWeightInput));
|
||||
RaisePropertyChanged(nameof(TotalPortionsInput));
|
||||
RaisePropertyChanged(nameof(IsSpecialAdoptionValue));
|
||||
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
|
||||
RaisePropertyChanged(nameof(SplitPortionPackagesDisplay));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
protected virtual async Task SaveAsync()
|
||||
{
|
||||
if (Entry == null) return;
|
||||
try
|
||||
{
|
||||
ApplyFirstSplitDetailToEntry();
|
||||
RecalculatePortionWeight();
|
||||
ApplySplitDetailsToEntry();
|
||||
ApplyDefaultEntryStatusForAdd();
|
||||
ApplyHiddenFieldDefaultsForAdd();
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
@@ -481,6 +478,34 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private async Task OpenSupplierPickerAsync()
|
||||
{
|
||||
SupplierPickerDialogViewModel? pickerVm = null;
|
||||
bool confirmed;
|
||||
try
|
||||
{
|
||||
confirmed = await HandyControl.Controls.Dialog.Show<SupplierPickerDialogView>()
|
||||
.Initialize<SupplierPickerDialogViewModel>(vm => pickerVm = vm)
|
||||
.GetResultAsync<bool>();
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (!confirmed || pickerVm?.SelectedSupplier == null || Entry == null) return;
|
||||
|
||||
var selected = pickerVm.SelectedSupplier;
|
||||
Entry.SupplierId = selected.Id;
|
||||
Entry.SupplierName = selected.SupplierName;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private void ClearSupplierSelection()
|
||||
{
|
||||
if (Entry == null) return;
|
||||
Entry.SupplierId = null;
|
||||
Entry.SupplierName = null;
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
private void RecalculateShelfLife(MesMixerMaterial? material)
|
||||
{
|
||||
if (Entry == null || material?.ShelfLifeDays == null || material.ShelfLifeDays <= 0)
|
||||
@@ -491,23 +516,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
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()
|
||||
protected void ApplyDefaultEntryStatusForAdd()
|
||||
{
|
||||
if (!IsAddMode || Entry == null || !string.IsNullOrWhiteSpace(Entry.Status))
|
||||
{
|
||||
@@ -526,19 +535,92 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
Entry.Status = "0";
|
||||
}
|
||||
|
||||
private void InitializeSplitCodeDetailsFromEntry()
|
||||
/// <summary>
|
||||
/// 新增模式下为已在前端隐藏的字段补充默认值,确保保存到后端时不为空:
|
||||
/// 检测结果=未检、检测状态=送样、打印标记=未打印、入库结存=否。
|
||||
/// 字典就绪时取字典 code,未就绪时回退到约定的常用 code。
|
||||
/// </summary>
|
||||
protected void ApplyHiddenFieldDefaultsForAdd()
|
||||
{
|
||||
if (!IsAddMode || Entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Entry.TestResult))
|
||||
{
|
||||
Entry.TestResult = ResolveDefaultOptionValue(TestResultOptions, "未检", "0");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Entry.TestStatus))
|
||||
{
|
||||
Entry.TestStatus = ResolveDefaultOptionValue(TestStatusOptions, "送样", "0");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Entry.PrintFlag))
|
||||
{
|
||||
Entry.PrintFlag = ResolveDefaultOptionValue(PrintFlagOptions, "未打印", "0");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Entry.StockBalance))
|
||||
{
|
||||
Entry.StockBalance = ResolveDefaultOptionValue(StockBalanceOptions, "否", "0");
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
}
|
||||
|
||||
/// <summary>在字典选项中按 Key(显示标签)查找对应 Value(实际 code),找不到则回退到 fallback。</summary>
|
||||
private static string ResolveDefaultOptionValue(
|
||||
ObservableCollection<KeyValuePair<string, string>> options,
|
||||
string keyLabel,
|
||||
string fallbackValue)
|
||||
{
|
||||
var match = options.FirstOrDefault(x =>
|
||||
string.Equals(x.Key?.Trim(), keyLabel, StringComparison.OrdinalIgnoreCase));
|
||||
return !string.IsNullOrWhiteSpace(match.Value) ? match.Value : fallbackValue;
|
||||
}
|
||||
|
||||
protected void InitializeSplitCodeDetailsFromEntry()
|
||||
{
|
||||
SplitCodeDetails.Clear();
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem
|
||||
|
||||
// 三个字段是按拆码明细多行拼接的字符串(末尾带 /),解析回明细列表
|
||||
var portionsArr = SplitJoinedValues(Entry?.TotalPortions);
|
||||
var weightArr = SplitJoinedValues(Entry?.PortionWeight);
|
||||
var packagesArr = SplitJoinedValues(Entry?.PortionPackages);
|
||||
var rowCount = Math.Max(1, Math.Max(portionsArr.Length, Math.Max(weightArr.Length, packagesArr.Length)));
|
||||
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
Portions = Entry?.TotalPortions,
|
||||
PortionWeight = Entry?.PortionWeight,
|
||||
PortionPackages = Entry?.PortionPackages,
|
||||
WarehouseLocation = Entry?.WarehouseLocation
|
||||
});
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem
|
||||
{
|
||||
Portions = TryParseInt(GetAt(portionsArr, i)),
|
||||
PortionWeight = TryParseDouble(GetAt(weightArr, i)),
|
||||
PortionPackages = TryParseInt(GetAt(packagesArr, i)),
|
||||
// 库位是单值字段,仅赋给首行
|
||||
WarehouseLocation = i == 0 ? Entry?.WarehouseLocation : null,
|
||||
});
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(SplitCodeTableHeight));
|
||||
}
|
||||
|
||||
private static string[] SplitJoinedValues(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return Array.Empty<string>();
|
||||
return value
|
||||
.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => x.Length > 0)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static string? GetAt(string[] arr, int index) => index < arr.Length ? arr[index] : null;
|
||||
|
||||
private static int? TryParseInt(string? text)
|
||||
=> int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) ? v : null;
|
||||
|
||||
private static double? TryParseDouble(string? text)
|
||||
=> double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : null;
|
||||
|
||||
private void AddSplitDetailRow()
|
||||
{
|
||||
SplitCodeDetails.Add(new RawMaterialSplitDetailItem());
|
||||
@@ -625,24 +707,29 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
|
||||
return value.Value.ToString("0.##", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void ApplyFirstSplitDetailToEntry()
|
||||
/// <summary>
|
||||
/// 把 SplitCodeDetails 全部明细行的「份数 / 每份重量 / 每份包数」按 "x/y/z/" 拼接后持久化到 Entry,
|
||||
/// 库位仍取首行(业务上库位是单值)。与 SplitTotalPortionsDisplay 等只读展示属性同规则。
|
||||
/// </summary>
|
||||
private void ApplySplitDetailsToEntry()
|
||||
{
|
||||
if (Entry == null || SplitCodeDetails.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Entry == null) return;
|
||||
|
||||
var first = SplitCodeDetails[0];
|
||||
Entry.TotalPortions = first.Portions;
|
||||
Entry.PortionWeight = first.PortionWeight;
|
||||
Entry.PortionPackages = first.PortionPackages;
|
||||
Entry.WarehouseLocation = first.WarehouseLocation;
|
||||
Entry.TotalPortions = JoinSplitValue(it => it.Portions?.ToString(CultureInfo.InvariantCulture), true);
|
||||
Entry.PortionWeight = JoinSplitValue(it => FormatNullableDecimal(it.PortionWeight), true);
|
||||
Entry.PortionPackages = JoinSplitValue(it => it.PortionPackages?.ToString(CultureInfo.InvariantCulture), true);
|
||||
|
||||
if (SplitCodeDetails.Count > 0)
|
||||
{
|
||||
Entry.WarehouseLocation = SplitCodeDetails[0].WarehouseLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateSplitCodeTableHeight()
|
||||
{
|
||||
const double headerHeight = 36d;
|
||||
const double rowHeight = 36d;
|
||||
// 与拆码明细 XAML 行高保持一致:表头 40px、数据行 44px
|
||||
const double headerHeight = 40d;
|
||||
const double rowHeight = 44d;
|
||||
const double framePadding = 16d;
|
||||
const double minRows = 1d;
|
||||
const double maxRows = 6d;
|
||||
|
||||
@@ -1,17 +1,288 @@
|
||||
using Prism.Commands;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.RawMaterialEntry;
|
||||
|
||||
/// <summary>
|
||||
/// 「新增原料入场记录」独立页面:左侧表单逻辑继承编辑 VM,右侧展示当日入场简要列表并支持选中回填模板。
|
||||
/// </summary>
|
||||
public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogViewModel
|
||||
{
|
||||
private const int TodayListFetchSize = 5000;
|
||||
private readonly IRawMaterialCardService _rawMaterialCardService;
|
||||
|
||||
private static readonly JsonSerializerOptions LayoutJsonOpts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
private static string LayoutFilePath => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"YY.Admin",
|
||||
"raw-material-entry-add-layout.json");
|
||||
|
||||
private bool _suppressTodaySelectionReaction;
|
||||
|
||||
public ObservableCollection<MesXslRawMaterialEntry> TodayEntries { get; } = new();
|
||||
|
||||
private MesXslRawMaterialEntry? _selectedTodayEntry;
|
||||
public MesXslRawMaterialEntry? SelectedTodayEntry
|
||||
{
|
||||
get => _selectedTodayEntry;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _selectedTodayEntry, value)) return;
|
||||
if (_suppressTodaySelectionReaction || value == null) return;
|
||||
_ = ApplyTodayRowToFormAsync(value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isRightPanelExpanded = true;
|
||||
public bool IsRightPanelExpanded
|
||||
{
|
||||
get => _isRightPanelExpanded;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _isRightPanelExpanded, value)) return;
|
||||
SaveLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
private double _expandedRightPanelWidth = 280;
|
||||
/// <summary>右侧面板展开时的目标宽度(像素),由 GridSplitter 拖拽结束时回写。</summary>
|
||||
public double ExpandedRightPanelWidth
|
||||
{
|
||||
get => _expandedRightPanelWidth;
|
||||
private set
|
||||
{
|
||||
var v = Math.Clamp(value, 200, 560);
|
||||
if (SetProperty(ref _expandedRightPanelWidth, v))
|
||||
SaveLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>仅当入场记录已保存(有 Id)且存在拆码明细时允许生成原材料卡片。</summary>
|
||||
public bool CanGenerateCards => !string.IsNullOrWhiteSpace(Entry?.Id) && SplitCodeDetails.Count > 0;
|
||||
|
||||
public DelegateCommand ToggleRightPanelCommand { get; }
|
||||
public DelegateCommand RefreshTodayEntriesCommand { get; }
|
||||
public DelegateCommand GenerateRawMaterialCardsCommand { get; }
|
||||
|
||||
public RawMaterialEntryOperationViewModel(
|
||||
IRawMaterialEntryService entryService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IMixerMaterialService mixerMaterialService,
|
||||
IRawMaterialCardService rawMaterialCardService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager)
|
||||
: base(entryService, dictSyncService, mixerMaterialService, container, regionManager)
|
||||
{
|
||||
_rawMaterialCardService = rawMaterialCardService;
|
||||
LoadLayoutState();
|
||||
ToggleRightPanelCommand = new DelegateCommand(() => IsRightPanelExpanded = !IsRightPanelExpanded);
|
||||
RefreshTodayEntriesCommand = new DelegateCommand(async () => await LoadTodayEntriesAsync());
|
||||
GenerateRawMaterialCardsCommand = new DelegateCommand(async () => await GenerateRawMaterialCardsAsync());
|
||||
SplitCodeDetails.CollectionChanged += (_, _) => RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
}
|
||||
|
||||
public override void InitializeForAdd()
|
||||
{
|
||||
_suppressTodaySelectionReaction = true;
|
||||
_selectedTodayEntry = null;
|
||||
RaisePropertyChanged(nameof(SelectedTodayEntry));
|
||||
_suppressTodaySelectionReaction = false;
|
||||
base.InitializeForAdd();
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
}
|
||||
|
||||
/// <summary>页面首次加载时拉取「今日」列表(由 View Loaded 调用)。</summary>
|
||||
public async Task LoadTodayEntriesOnFirstShowAsync() => await LoadTodayEntriesAsync();
|
||||
|
||||
/// <summary>用户在拖拽结束 GridSplitter 后提交实际列宽。</summary>
|
||||
public void CommitRightPanelWidthFromView(double actualWidth)
|
||||
{
|
||||
if (!IsRightPanelExpanded || actualWidth < 1) return;
|
||||
ExpandedRightPanelWidth = actualWidth;
|
||||
}
|
||||
|
||||
private async Task LoadTodayEntriesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await EntryService.PageAsync(1, TodayListFetchSize);
|
||||
var today = DateTime.Today;
|
||||
var rows = result.Records
|
||||
.Where(e => IsTodayEntry(e, today))
|
||||
.OrderByDescending(e => e.EntryTime ?? e.CreateTime ?? DateTime.MinValue)
|
||||
.ToList();
|
||||
TodayEntries.Clear();
|
||||
foreach (var r in rows)
|
||||
TodayEntries.Add(r);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 列表失败不阻断左侧新增(与物料加载策略一致)
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTodayEntry(MesXslRawMaterialEntry e, DateTime today)
|
||||
{
|
||||
var byEntry = e.EntryTime?.Date == today;
|
||||
var byCreate = e.CreateTime?.Date == today;
|
||||
return byEntry || byCreate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击右侧今日记录:直接以「编辑模式」加载源记录到左侧表单(保留 Id/条码/批次号),
|
||||
/// 不再走「新增模板」逻辑,避免重新生成条码、覆盖原数据。
|
||||
/// </summary>
|
||||
private async Task ApplyTodayRowToFormAsync(MesXslRawMaterialEntry src)
|
||||
{
|
||||
// 复用基类 InitializeForEdit:保留 Id/Barcode/BatchNo/状态等所有字段,标题自动切到「编辑原料入场记录」
|
||||
base.InitializeForEdit(src);
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
|
||||
// 若物料列表此前未加载导致选中项未回填,则补一次拉取(与编辑弹窗逻辑一致)
|
||||
if (_pendingMaterialId != null)
|
||||
await LoadMaterialOptionsAsync();
|
||||
}
|
||||
|
||||
/// <summary>编辑保存成功后:刷新右侧今日列表并切回新增态,避免连续误改同一条。</summary>
|
||||
protected override async Task SaveAsync()
|
||||
{
|
||||
var wasEdit = !IsAddMode;
|
||||
await base.SaveAsync();
|
||||
RaisePropertyChanged(nameof(CanGenerateCards));
|
||||
if (wasEdit && Result && CloseAction == null)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Success("编辑成功!");
|
||||
await LoadTodayEntriesAsync();
|
||||
InitializeForAdd();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateRawMaterialCardsAsync()
|
||||
{
|
||||
if (Entry == null || string.IsNullOrWhiteSpace(Entry.Id))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("请先保存入场记录再生成原材料卡片!");
|
||||
return;
|
||||
}
|
||||
if (SplitCodeDetails.Count == 0)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("当前入场记录无拆分明细,无法生成原材料卡片!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var globalIndex = 1;
|
||||
var baseBarcode = Entry.Barcode ?? "";
|
||||
var failCount = 0;
|
||||
|
||||
foreach (var detail in SplitCodeDetails)
|
||||
{
|
||||
var portions = detail.Portions ?? 0;
|
||||
for (var i = 0; i < portions; i++)
|
||||
{
|
||||
var card = new MesXslRawMaterialCard
|
||||
{
|
||||
Barcode = baseBarcode + globalIndex.ToString("D3"),
|
||||
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
|
||||
};
|
||||
var ok = await _rawMaterialCardService.AddAsync(card);
|
||||
if (!ok) failCount++;
|
||||
globalIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新打印状态为「已打印」
|
||||
Entry.PrintFlag = "1";
|
||||
await EntryService.EditAsync(Entry);
|
||||
RaisePropertyChanged(nameof(Entry));
|
||||
|
||||
var total = globalIndex - 1;
|
||||
if (failCount == 0)
|
||||
HandyControl.Controls.MessageBox.Success($"已生成 {total} 张原材料卡片,打印状态已更新为「已打印」!");
|
||||
else
|
||||
HandyControl.Controls.MessageBox.Warning($"共生成 {total} 张,其中 {failCount} 张失败,请检查网络后重试!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"生成原材料卡片失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadLayoutState()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(LayoutFilePath)) return;
|
||||
var json = File.ReadAllText(LayoutFilePath);
|
||||
var dto = JsonSerializer.Deserialize<AddPageLayoutDto>(json, LayoutJsonOpts);
|
||||
if (dto == null) return;
|
||||
if (dto.ExpandedWidth is > 0 and < 2000)
|
||||
_expandedRightPanelWidth = Math.Clamp(dto.ExpandedWidth.Value, 200, 560);
|
||||
if (dto.IsExpanded.HasValue)
|
||||
_isRightPanelExpanded = dto.IsExpanded.Value;
|
||||
RaisePropertyChanged(nameof(ExpandedRightPanelWidth));
|
||||
RaisePropertyChanged(nameof(IsRightPanelExpanded));
|
||||
}
|
||||
catch { /* 布局文件损坏时忽略 */ }
|
||||
}
|
||||
|
||||
private void SaveLayoutState()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(LayoutFilePath);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
var dto = new AddPageLayoutDto
|
||||
{
|
||||
ExpandedWidth = _expandedRightPanelWidth,
|
||||
IsExpanded = _isRightPanelExpanded
|
||||
};
|
||||
File.WriteAllText(LayoutFilePath, JsonSerializer.Serialize(dto, LayoutJsonOpts));
|
||||
}
|
||||
catch { /* 写本地失败时不影响业务 */ }
|
||||
}
|
||||
|
||||
private sealed class AddPageLayoutDto
|
||||
{
|
||||
public double? ExpandedWidth { get; set; }
|
||||
public bool? IsExpanded { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user