289 lines
11 KiB
C#
289 lines
11 KiB
C#
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; }
|
||
}
|
||
}
|