增强原材料卡片管理功能,新增免密接口和数据处理逻辑,支持原材料卡片的增删改查操作。更新前端视图以支持多行拆码明细拼接,优化用户体验和系统实时数据同步能力。
This commit is contained in:
@@ -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