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