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; /// /// 「新增原料入场记录」独立页面:左侧表单逻辑继承编辑 VM,右侧展示当日入场简要列表并支持选中回填模板。 /// 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 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; /// 右侧面板展开时的目标宽度(像素),由 GridSplitter 拖拽结束时回写。 public double ExpandedRightPanelWidth { get => _expandedRightPanelWidth; private set { var v = Math.Clamp(value, 200, 560); if (SetProperty(ref _expandedRightPanelWidth, v)) SaveLayoutState(); } } /// 仅当入场记录已保存(有 Id)且存在拆码明细时允许生成原材料卡片。 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)); } /// 页面首次加载时拉取「今日」列表(由 View Loaded 调用)。 public async Task LoadTodayEntriesOnFirstShowAsync() => await LoadTodayEntriesAsync(); /// 用户在拖拽结束 GridSplitter 后提交实际列宽。 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; } /// /// 点击右侧今日记录:直接以「编辑模式」加载源记录到左侧表单(保留 Id/条码/批次号), /// 不再走「新增模板」逻辑,避免重新生成条码、覆盖原数据。 /// private async Task ApplyTodayRowToFormAsync(MesXslRawMaterialEntry src) { // 复用基类 InitializeForEdit:保留 Id/Barcode/BatchNo/状态等所有字段,标题自动切到「编辑原料入场记录」 base.InitializeForEdit(src); RaisePropertyChanged(nameof(CanGenerateCards)); // 若物料列表此前未加载导致选中项未回填,则补一次拉取(与编辑弹窗逻辑一致) if (_pendingMaterialId != null) await LoadMaterialOptionsAsync(); } /// 编辑保存成功后:刷新右侧今日列表并切回新增态,避免连续误改同一条。 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(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; } } }