新增磅单管理功能,支持免密接口和数据同步,更新相关控制器、实体和服务,优化权限管理,确保系统的灵活性和可扩展性。
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.ViewModels.WeightRecord;
|
||||
|
||||
public class CustomerPickerDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly ICustomerService _customerService;
|
||||
|
||||
private string? _searchText;
|
||||
public string? SearchText { get => _searchText; set => SetProperty(ref _searchText, value); }
|
||||
|
||||
public ObservableCollection<MesXslCustomer> Customers { get; } = new();
|
||||
|
||||
private MesXslCustomer? _selectedCustomer;
|
||||
public MesXslCustomer? SelectedCustomer
|
||||
{
|
||||
get => _selectedCustomer;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedCustomer, value);
|
||||
ConfirmCommand.RaiseCanExecuteChanged();
|
||||
RaisePropertyChanged(nameof(SelectedCustomerDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedCustomer));
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedCustomerDisplay => _selectedCustomer != null
|
||||
? $"[{_selectedCustomer.CustomerCode}] {_selectedCustomer.CustomerName}"
|
||||
: "选中客户后点击「确认选择」";
|
||||
|
||||
public bool HasSelectedCustomer => _selectedCustomer != null;
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ConfirmCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public CustomerPickerDialogViewModel(
|
||||
ICustomerService customerService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_customerService = customerService;
|
||||
SearchCommand = new DelegateCommand(async () => await LoadAsync());
|
||||
ConfirmCommand = new DelegateCommand(Confirm, () => SelectedCustomer != null);
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadAsync();
|
||||
}
|
||||
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var keyword = SearchText?.Trim();
|
||||
var result = await _customerService.PageAsync(1, 200, customerName: keyword);
|
||||
Customers.Clear();
|
||||
foreach (var c in result.Records)
|
||||
Customers.Add(c);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Customers.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
if (SelectedCustomer == null) return;
|
||||
Result = true;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.ViewModels.WeightRecord;
|
||||
|
||||
public class SupplierPickerDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly ISupplierService _supplierService;
|
||||
|
||||
private string? _searchText;
|
||||
public string? SearchText { get => _searchText; set => SetProperty(ref _searchText, value); }
|
||||
|
||||
public ObservableCollection<MesXslSupplier> Suppliers { get; } = new();
|
||||
|
||||
private MesXslSupplier? _selectedSupplier;
|
||||
public MesXslSupplier? SelectedSupplier
|
||||
{
|
||||
get => _selectedSupplier;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedSupplier, value);
|
||||
ConfirmCommand.RaiseCanExecuteChanged();
|
||||
RaisePropertyChanged(nameof(SelectedSupplierDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedSupplierDisplay => _selectedSupplier != null
|
||||
? $"[{_selectedSupplier.SupplierCode}] {_selectedSupplier.SupplierName}"
|
||||
: "选中供应商后点击「确认选择」";
|
||||
|
||||
public bool HasSelectedSupplier => _selectedSupplier != null;
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ConfirmCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public SupplierPickerDialogViewModel(
|
||||
ISupplierService supplierService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_supplierService = supplierService;
|
||||
SearchCommand = new DelegateCommand(async () => await LoadAsync());
|
||||
ConfirmCommand = new DelegateCommand(Confirm, () => SelectedSupplier != null);
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadAsync();
|
||||
}
|
||||
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var keyword = SearchText?.Trim();
|
||||
var result = await _supplierService.PageAsync(1, 200, supplierName: keyword);
|
||||
Suppliers.Clear();
|
||||
foreach (var s in result.Records)
|
||||
Suppliers.Add(s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Suppliers.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
if (SelectedSupplier == null) return;
|
||||
Result = true;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
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.WeightRecord;
|
||||
|
||||
public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IWeightRecordService _weightRecordService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
|
||||
private MesXslWeightRecord? _record;
|
||||
public MesXslWeightRecord? Record
|
||||
{
|
||||
get => _record;
|
||||
set => SetProperty(ref _record, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Record?.Id);
|
||||
public string DialogTitle => IsAddMode ? "新增磅单" : "编辑磅单";
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> InoutDirectionOptions { 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 WeightRecordEditDialogViewModel(
|
||||
IWeightRecordService weightRecordService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_weightRecordService = weightRecordService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadDictOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = await _dictSyncService.GetDictOptionsAsync("xslmes_inout_direction");
|
||||
InoutDirectionOptions.Clear();
|
||||
foreach (var item in options) InoutDirectionOptions.Add(item);
|
||||
if (InoutDirectionOptions.Count == 0) AddDefaultDirectionOptions();
|
||||
}
|
||||
catch { AddDefaultDirectionOptions(); }
|
||||
}
|
||||
|
||||
private void AddDefaultDirectionOptions()
|
||||
{
|
||||
InoutDirectionOptions.Clear();
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("进厂", "1"));
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("出厂", "2"));
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Record = new MesXslWeightRecord
|
||||
{
|
||||
WeighDate = DateTime.Today,
|
||||
InoutDirection = "1"
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslWeightRecord record)
|
||||
{
|
||||
Record = new MesXslWeightRecord
|
||||
{
|
||||
Id = record.Id,
|
||||
BillNo = record.BillNo,
|
||||
WeighDate = record.WeighDate,
|
||||
InoutDirection = record.InoutDirection,
|
||||
VehicleId = record.VehicleId,
|
||||
PlateNumber = record.PlateNumber,
|
||||
SenderUnit = record.SenderUnit,
|
||||
ReceiverUnit = record.ReceiverUnit,
|
||||
GoodsName = record.GoodsName,
|
||||
GrossWeight = record.GrossWeight,
|
||||
TareWeight = record.TareWeight,
|
||||
NetWeight = record.NetWeight,
|
||||
DriverName = record.DriverName,
|
||||
DriverPhone = record.DriverPhone,
|
||||
BillType = record.BillType,
|
||||
TenantId = record.TenantId,
|
||||
UpdateTime = record.UpdateTime
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Record == null) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Record.PlateNumber))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("车牌号不能为空!");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Record.InoutDirection))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("进出方向不能为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 净重自动计算
|
||||
if (Record.GrossWeight.HasValue && Record.TareWeight.HasValue)
|
||||
Record.NetWeight = Math.Round(Record.GrossWeight.Value - Record.TareWeight.Value, 2);
|
||||
|
||||
try
|
||||
{
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
ok = await _weightRecordService.AddAsync(Record);
|
||||
if (ok) HandyControl.Controls.MessageBox.Success("新增磅单成功!");
|
||||
else { HandyControl.Controls.MessageBox.Error("新增磅单失败!"); return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = await _weightRecordService.EditAsync(Record);
|
||||
if (!ok) { HandyControl.Controls.MessageBox.Error("编辑磅单失败!"); return; }
|
||||
}
|
||||
Result = ok;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
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.WeightRecord;
|
||||
|
||||
namespace YY.Admin.ViewModels.WeightRecord;
|
||||
|
||||
public class WeightRecordListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IWeightRecordService _weightRecordService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private SubscriptionToken? _changedToken;
|
||||
private SubscriptionToken? _conflictToken;
|
||||
|
||||
private ObservableCollection<MesXslWeightRecord> _records = new();
|
||||
public ObservableCollection<MesXslWeightRecord> Records
|
||||
{
|
||||
get => _records;
|
||||
set => SetProperty(ref _records, 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? _filterBillNo;
|
||||
public string? FilterBillNo { get => _filterBillNo; set => SetProperty(ref _filterBillNo, value); }
|
||||
|
||||
private string? _filterPlateNumber;
|
||||
public string? FilterPlateNumber { get => _filterPlateNumber; set => SetProperty(ref _filterPlateNumber, value); }
|
||||
|
||||
private string? _filterInoutDirection;
|
||||
public string? FilterInoutDirection { get => _filterInoutDirection; set => SetProperty(ref _filterInoutDirection, value); }
|
||||
|
||||
private string? _filterGoodsName;
|
||||
public string? FilterGoodsName { get => _filterGoodsName; set => SetProperty(ref _filterGoodsName, value); }
|
||||
|
||||
private string? _filterDriverName;
|
||||
public string? FilterDriverName { get => _filterDriverName; set => SetProperty(ref _filterDriverName, value); }
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> InoutDirectionOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> BillTypeOptions { get; } = new();
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslWeightRecord> EditCommand { get; }
|
||||
public DelegateCommand<MesXslWeightRecord> DeleteCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public WeightRecordListViewModel(
|
||||
IWeightRecordService weightRecordService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IDialogService dialogService,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_weightRecordService = weightRecordService;
|
||||
_dictSyncService = dictSyncService;
|
||||
_dialogService = dialogService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterBillNo = null; FilterPlateNumber = null; FilterInoutDirection = null;
|
||||
FilterGoodsName = null; FilterDriverName = null;
|
||||
PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslWeightRecord>(async r => await ShowEditDialogAsync(r));
|
||||
DeleteCommand = new DelegateCommand<MesXslWeightRecord>(async r => await DeleteAsync(r));
|
||||
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<MesXslWeightRecordChangedEvent>()
|
||||
.Subscribe(async _ => await LoadAsync(), ThreadOption.UIThread);
|
||||
_conflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
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 options = await _dictSyncService.GetDictOptionsAsync("xslmes_inout_direction", includeAll: true);
|
||||
InoutDirectionOptions.Clear();
|
||||
foreach (var item in options) InoutDirectionOptions.Add(item);
|
||||
if (InoutDirectionOptions.Count == 0) InoutDirectionOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
|
||||
var billTypeOptions = await _dictSyncService.GetDictOptionsAsync("xslmes_weight_bill_type", includeAll: true);
|
||||
BillTypeOptions.Clear();
|
||||
foreach (var item in billTypeOptions) BillTypeOptions.Add(item);
|
||||
if (BillTypeOptions.Count == 0) AddDefaultBillTypeOptions();
|
||||
}
|
||||
catch
|
||||
{
|
||||
InoutDirectionOptions.Clear();
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("进厂", "1"));
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("出厂", "2"));
|
||||
AddDefaultBillTypeOptions();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDefaultBillTypeOptions()
|
||||
{
|
||||
BillTypeOptions.Clear();
|
||||
BillTypeOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
BillTypeOptions.Add(new KeyValuePair<string, string>("已称毛重", "1"));
|
||||
BillTypeOptions.Add(new KeyValuePair<string, string>("称重完成", "2"));
|
||||
BillTypeOptions.Add(new KeyValuePair<string, string>("已称皮重", "3"));
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await _weightRecordService.PageAsync(
|
||||
PageNo, PageSize, FilterBillNo, FilterPlateNumber, FilterInoutDirection, FilterGoodsName, FilterDriverName);
|
||||
|
||||
// 填充字典显示文本
|
||||
var dictMap = InoutDirectionOptions.ToDictionary(x => x.Value, x => x.Key);
|
||||
var billTypeMap = BillTypeOptions.ToDictionary(x => x.Value, x => x.Key);
|
||||
foreach (var r in result.Records)
|
||||
{
|
||||
r.InoutDirectionText = dictMap.TryGetValue(r.InoutDirection ?? "", out var txt) ? txt : r.InoutDirection ?? "";
|
||||
r.BillTypeText = billTypeMap.TryGetValue(r.BillType ?? "", out var billTypeTxt) ? billTypeTxt : r.BillType ?? "";
|
||||
}
|
||||
|
||||
Records = new ObservableCollection<MesXslWeightRecord>(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<WeightRecordEditDialogView>()
|
||||
.Initialize<WeightRecordEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex) { Growl.Error($"打开新增对话框失败:{ex.Message}"); }
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslWeightRecord record)
|
||||
{
|
||||
if (record == null) return;
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<WeightRecordEditDialogView>()
|
||||
.Initialize<WeightRecordEditDialogViewModel>(vm => vm.InitializeForEdit(record))
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex) { Growl.Error($"打开编辑对话框失败:{ex.Message}"); }
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslWeightRecord record)
|
||||
{
|
||||
if (record?.Id == null) return;
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
$"确定删除磅单 {record.BillNo ?? record.PlateNumber}?此操作不可恢复!",
|
||||
"确认删除", MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
var ok = await _weightRecordService.DeleteAsync(record.Id);
|
||||
if (ok) { Growl.Success("删除成功!"); await LoadAsync(); }
|
||||
else Growl.Error("删除失败!");
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_changedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<MesXslWeightRecordChangedEvent>().Unsubscribe(_changedToken);
|
||||
_changedToken = null;
|
||||
}
|
||||
if (_conflictToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_conflictToken);
|
||||
_conflictToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,877 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Threading;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.WeightRecord;
|
||||
|
||||
namespace YY.Admin.ViewModels.WeightRecord;
|
||||
|
||||
/// <summary>
|
||||
/// 地磅称重操作页面 ViewModel
|
||||
/// 左侧:串口模拟实时数据(重量 + 车牌);右侧:称重信息录入表单。
|
||||
/// </summary>
|
||||
public class WeightRecordOperationViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IWeightRecordService _weightRecordService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IVehicleService _vehicleService;
|
||||
|
||||
// ─── 车辆档案匹配 ───
|
||||
private MesXslVehicle? _matchedVehicle;
|
||||
private string? _lastLookedUpPlate;
|
||||
|
||||
private string _vehicleLookupStatus = "None"; // None | Searching | Matched | NotFound
|
||||
public string VehicleLookupStatus
|
||||
{
|
||||
get => _vehicleLookupStatus;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _vehicleLookupStatus, value);
|
||||
RaisePropertyChanged(nameof(VehicleMatchText));
|
||||
RaisePropertyChanged(nameof(ShowVehicleMatchHint));
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowVehicleMatchHint => _vehicleLookupStatus != "None";
|
||||
public string VehicleMatchText => _vehicleLookupStatus switch
|
||||
{
|
||||
"Searching" => "正在匹配车辆档案...",
|
||||
"Matched" => $"✓ 已匹配车辆档案({_matchedVehicle?.VehicleBelongText})",
|
||||
"NotFound" => "未在档案库中找到,保存后将自动录入",
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
// ─── 串口模拟定时器 ───
|
||||
private readonly DispatcherTimer _serialTimer;
|
||||
private readonly Random _rnd = new();
|
||||
private double _baseWeight = 35000.0; // 基准重量(模拟车辆在磅上)
|
||||
private int _stableCountdown = 0; // 稳定倒计时(连续稳定帧数)
|
||||
private bool _vehiclePresent = false; // 是否有车辆在磅上
|
||||
private int _vehicleDetectCooldown = 0; // 车辆检测冷却帧
|
||||
private bool _isApplyingSelectedRecord;
|
||||
|
||||
// ─── 实时数据属性 ───
|
||||
|
||||
private double _currentWeight;
|
||||
public double CurrentWeight
|
||||
{
|
||||
get => _currentWeight;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _currentWeight, value);
|
||||
RaisePropertyChanged(nameof(CurrentWeightDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
public string CurrentWeightDisplay => $"{CurrentWeight:N2}";
|
||||
|
||||
private bool _isWeightStable;
|
||||
public bool IsWeightStable { get => _isWeightStable; set => SetProperty(ref _isWeightStable, value); }
|
||||
|
||||
private string _serialStatusText = "正在连接...";
|
||||
public string SerialStatusText { get => _serialStatusText; set => SetProperty(ref _serialStatusText, value); }
|
||||
|
||||
private bool _isSerialConnected;
|
||||
public bool IsSerialConnected { get => _isSerialConnected; set => SetProperty(ref _isSerialConnected, value); }
|
||||
|
||||
private string _detectedPlate = string.Empty;
|
||||
public string DetectedPlate { get => _detectedPlate; set => SetProperty(ref _detectedPlate, value); }
|
||||
|
||||
private bool _hasPlate;
|
||||
public bool HasPlate { get => _hasPlate; set => SetProperty(ref _hasPlate, value); }
|
||||
|
||||
public ObservableCollection<string> OperationLogs { get; } = new();
|
||||
|
||||
// ─── 采集状态 ───
|
||||
|
||||
private bool _grossWeightCaptured;
|
||||
public bool GrossWeightCaptured { get => _grossWeightCaptured; set => SetProperty(ref _grossWeightCaptured, value); }
|
||||
|
||||
private bool _tareWeightCaptured;
|
||||
public bool TareWeightCaptured { get => _tareWeightCaptured; set => SetProperty(ref _tareWeightCaptured, value); }
|
||||
|
||||
// ─── 表单绑定属性 ───
|
||||
|
||||
private DateTime _weighDate = DateTime.Today;
|
||||
public DateTime WeighDate { get => _weighDate; set => SetProperty(ref _weighDate, value); }
|
||||
|
||||
private string? _inoutDirection = "1";
|
||||
public string? InoutDirection
|
||||
{
|
||||
get => _inoutDirection;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _inoutDirection, value)) return;
|
||||
RaisePropertyChanged(nameof(CaptureGrossButtonText));
|
||||
RaisePropertyChanged(nameof(CaptureTareButtonText));
|
||||
CaptureGrossWeightCommand.RaiseCanExecuteChanged();
|
||||
CaptureTareWeightCommand.RaiseCanExecuteChanged();
|
||||
if (_isApplyingSelectedRecord) return;
|
||||
if (SelectedRecentWeightRecord != null)
|
||||
{
|
||||
ClearFormByDirectionSwitch();
|
||||
}
|
||||
_ = LoadRecentWeightRecordsAsync(PlateNumber);
|
||||
}
|
||||
}
|
||||
public string CaptureGrossButtonText => "采集毛重";
|
||||
public string CaptureTareButtonText => "采集皮重";
|
||||
|
||||
private string? _plateNumber;
|
||||
public string? PlateNumber
|
||||
{
|
||||
get => _plateNumber;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _plateNumber, value)) return;
|
||||
if (_isApplyingSelectedRecord) return;
|
||||
_ = LoadRecentWeightRecordsAsync(value);
|
||||
var trimmed = value?.Trim() ?? string.Empty;
|
||||
if (trimmed.Length >= 6)
|
||||
_ = LookupVehicleByPlateAsync(trimmed);
|
||||
else
|
||||
ClearVehicleMatch();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 发货/收货单位(含选择器状态) ───
|
||||
|
||||
private MesXslSupplier? _selectedSupplier;
|
||||
private MesXslCustomer? _selectedCustomer;
|
||||
|
||||
public bool HasSelectedSupplier => _selectedSupplier != null;
|
||||
public bool HasSelectedCustomer => _selectedCustomer != null;
|
||||
|
||||
public string SenderUnitDisplay => _selectedSupplier != null
|
||||
? $"[{_selectedSupplier.SupplierCode}] {_selectedSupplier.SupplierName}"
|
||||
: (!string.IsNullOrWhiteSpace(_senderUnit) ? _senderUnit : "点击右侧「选择」从供应商列表中选取");
|
||||
|
||||
public string ReceiverUnitDisplay => _selectedCustomer != null
|
||||
? $"[{_selectedCustomer.CustomerCode}] {_selectedCustomer.CustomerName}"
|
||||
: (!string.IsNullOrWhiteSpace(_receiverUnit) ? _receiverUnit : "点击右侧「选择」从客户列表中选取");
|
||||
|
||||
private string? _senderUnit;
|
||||
public string? SenderUnit
|
||||
{
|
||||
get => _senderUnit;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _senderUnit, value);
|
||||
RaisePropertyChanged(nameof(SenderUnitDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
private string? _receiverUnit;
|
||||
public string? ReceiverUnit
|
||||
{
|
||||
get => _receiverUnit;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _receiverUnit, value);
|
||||
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
private string? _goodsName;
|
||||
public string? GoodsName { get => _goodsName; set => SetProperty(ref _goodsName, value); }
|
||||
|
||||
private double? _grossWeight;
|
||||
public double? GrossWeight
|
||||
{
|
||||
get => _grossWeight;
|
||||
set { SetProperty(ref _grossWeight, value); RecalcNetWeight(); }
|
||||
}
|
||||
|
||||
private double? _tareWeight;
|
||||
public double? TareWeight
|
||||
{
|
||||
get => _tareWeight;
|
||||
set { SetProperty(ref _tareWeight, value); RecalcNetWeight(); }
|
||||
}
|
||||
|
||||
private double? _netWeight;
|
||||
public double? NetWeight { get => _netWeight; set => SetProperty(ref _netWeight, value); }
|
||||
|
||||
public string NetWeightDisplay => NetWeight.HasValue ? $"{NetWeight.Value:N2}" : "—";
|
||||
|
||||
private string? _driverName;
|
||||
public string? DriverName { get => _driverName; set => SetProperty(ref _driverName, value); }
|
||||
|
||||
private string? _driverPhone;
|
||||
public string? DriverPhone { get => _driverPhone; set => SetProperty(ref _driverPhone, value); }
|
||||
|
||||
// ─── 字典 ───
|
||||
public ObservableCollection<KeyValuePair<string, string>> InoutDirectionOptions { get; } = new();
|
||||
public ObservableCollection<WeightRecordSimpleItem> RecentWeightRecords { get; } = new();
|
||||
private bool _isPlateNumberLocked;
|
||||
public bool IsPlateNumberLocked
|
||||
{
|
||||
get => _isPlateNumberLocked;
|
||||
set => SetProperty(ref _isPlateNumberLocked, value);
|
||||
}
|
||||
private WeightRecordSimpleItem? _selectedRecentWeightRecord;
|
||||
public WeightRecordSimpleItem? SelectedRecentWeightRecord
|
||||
{
|
||||
get => _selectedRecentWeightRecord;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _selectedRecentWeightRecord, value)) return;
|
||||
IsPlateNumberLocked = value?.Source?.Id != null;
|
||||
if (value == null) return;
|
||||
ApplySelectedRecordToForm(value);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 命令 ───
|
||||
public DelegateCommand CaptureGrossWeightCommand { get; }
|
||||
public DelegateCommand CaptureTareWeightCommand { get; }
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand ClearCommand { get; }
|
||||
public DelegateCommand UseDetectedPlateCommand { get; }
|
||||
public DelegateCommand OpenSupplierPickerCommand { get; }
|
||||
public DelegateCommand OpenCustomerPickerCommand { get; }
|
||||
public DelegateCommand ClearSupplierCommand { get; }
|
||||
public DelegateCommand ClearCustomerCommand { get; }
|
||||
|
||||
public WeightRecordOperationViewModel(
|
||||
IWeightRecordService weightRecordService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IVehicleService vehicleService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_weightRecordService = weightRecordService;
|
||||
_dictSyncService = dictSyncService;
|
||||
_vehicleService = vehicleService;
|
||||
|
||||
CaptureGrossWeightCommand = new DelegateCommand(CaptureGrossWeight, CanCaptureGrossWeight)
|
||||
.ObservesProperty(() => IsWeightStable)
|
||||
.ObservesProperty(() => GrossWeightCaptured)
|
||||
.ObservesProperty(() => TareWeightCaptured);
|
||||
CaptureTareWeightCommand = new DelegateCommand(CaptureTareWeight, CanCaptureTareWeight)
|
||||
.ObservesProperty(() => IsWeightStable)
|
||||
.ObservesProperty(() => GrossWeightCaptured)
|
||||
.ObservesProperty(() => TareWeightCaptured);
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
ClearCommand = new DelegateCommand(ClearForm);
|
||||
UseDetectedPlateCommand = new DelegateCommand(() => PlateNumber = DetectedPlate, () => HasPlate)
|
||||
.ObservesProperty(() => HasPlate);
|
||||
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
|
||||
OpenCustomerPickerCommand = new DelegateCommand(async () => await OpenCustomerPickerAsync());
|
||||
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
|
||||
ClearCustomerCommand = new DelegateCommand(ClearCustomerSelection);
|
||||
|
||||
_ = LoadDictOptionsAsync();
|
||||
_ = LoadRecentWeightRecordsAsync(PlateNumber);
|
||||
|
||||
// 启动串口模拟定时器(200ms 刷新一次,约 5Hz)
|
||||
_serialTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_serialTimer.Tick += OnSerialTimerTick;
|
||||
_serialTimer.Start();
|
||||
|
||||
// 模拟1秒后串口连接成功
|
||||
var connectTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
connectTimer.Tick += (s, e) =>
|
||||
{
|
||||
((DispatcherTimer)s!).Stop();
|
||||
IsSerialConnected = true;
|
||||
SerialStatusText = "COM3 9600bps 已连接";
|
||||
AddLog("串口连接成功 COM3");
|
||||
// 3秒后模拟第一辆车到来
|
||||
var vehicleTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };
|
||||
vehicleTimer.Tick += (s2, e2) => { ((DispatcherTimer)s2!).Stop(); SimulateVehicleArrival(); };
|
||||
vehicleTimer.Start();
|
||||
};
|
||||
connectTimer.Start();
|
||||
}
|
||||
|
||||
private void OnSerialTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
if (!IsSerialConnected) return;
|
||||
|
||||
if (_vehiclePresent)
|
||||
{
|
||||
// 车辆在磅上:在基准重量附近波动
|
||||
var noise = (_rnd.NextDouble() - 0.5) * 80;
|
||||
CurrentWeight = Math.Max(0, _baseWeight + noise);
|
||||
|
||||
// 稳定检测:连续10帧波动小于30kg视为稳定
|
||||
if (Math.Abs(noise) < 30) _stableCountdown = Math.Min(_stableCountdown + 1, 10);
|
||||
else _stableCountdown = Math.Max(_stableCountdown - 2, 0);
|
||||
IsWeightStable = _stableCountdown >= 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无车辆:重量归零并稳定
|
||||
CurrentWeight = Math.Max(0, CurrentWeight - 500);
|
||||
if (CurrentWeight < 50) { CurrentWeight = 0; IsWeightStable = true; }
|
||||
else IsWeightStable = false;
|
||||
}
|
||||
|
||||
// 车辆检测冷却计数
|
||||
if (_vehicleDetectCooldown > 0) _vehicleDetectCooldown--;
|
||||
}
|
||||
|
||||
private void SimulateVehicleArrival()
|
||||
{
|
||||
_vehiclePresent = true;
|
||||
_stableCountdown = 0;
|
||||
_baseWeight = _rnd.Next(18000, 65000); // 18~65吨随机
|
||||
AddLog("检测到车辆入场");
|
||||
|
||||
// 模拟摄像头识别车牌(2秒后回传)
|
||||
var plateTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
|
||||
plateTimer.Tick += (s, e) =>
|
||||
{
|
||||
((DispatcherTimer)s!).Stop();
|
||||
var plates = new[] { "粤A88888", "粤B12345", "粤C98765", "湘A66666", "川B55555" };
|
||||
DetectedPlate = plates[_rnd.Next(plates.Length)];
|
||||
HasPlate = true;
|
||||
AddLog($"车牌识别:{DetectedPlate}");
|
||||
};
|
||||
plateTimer.Start();
|
||||
|
||||
// 模拟15秒后车辆离开(可手动操作)
|
||||
var leaveTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) };
|
||||
leaveTimer.Tick += (s, e) =>
|
||||
{
|
||||
((DispatcherTimer)s!).Stop();
|
||||
if (_vehiclePresent) SimulateVehicleLeave();
|
||||
};
|
||||
leaveTimer.Start();
|
||||
}
|
||||
|
||||
private void SimulateVehicleLeave()
|
||||
{
|
||||
_vehiclePresent = false;
|
||||
_stableCountdown = 0;
|
||||
HasPlate = false;
|
||||
AddLog("车辆离场");
|
||||
|
||||
// 8秒后下一辆车
|
||||
if (_vehicleDetectCooldown <= 0)
|
||||
{
|
||||
_vehicleDetectCooldown = 40; // 8秒 / 200ms = 40帧
|
||||
var nextTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(8) };
|
||||
nextTimer.Tick += (s, e) => { ((DispatcherTimer)s!).Stop(); SimulateVehicleArrival(); };
|
||||
nextTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureGrossWeight()
|
||||
{
|
||||
var weight = Math.Round(CurrentWeight, 2);
|
||||
GrossWeight = weight;
|
||||
GrossWeightCaptured = true;
|
||||
AddLog($"采集毛重:{GrossWeight:N2} kg");
|
||||
}
|
||||
|
||||
private void CaptureTareWeight()
|
||||
{
|
||||
var weight = Math.Round(CurrentWeight, 2);
|
||||
TareWeight = weight;
|
||||
TareWeightCaptured = true;
|
||||
AddLog($"采集皮重:{TareWeight:N2} kg");
|
||||
}
|
||||
|
||||
private bool CanCaptureGrossWeight()
|
||||
{
|
||||
return IsWeightStable && !GrossWeightCaptured;
|
||||
}
|
||||
|
||||
private bool CanCaptureTareWeight()
|
||||
{
|
||||
return IsWeightStable && !TareWeightCaptured;
|
||||
}
|
||||
|
||||
private void RecalcNetWeight()
|
||||
{
|
||||
if (GrossWeight.HasValue && TareWeight.HasValue)
|
||||
{
|
||||
NetWeight = Math.Round(GrossWeight.Value - TareWeight.Value, 2);
|
||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||
}
|
||||
else
|
||||
{
|
||||
NetWeight = null;
|
||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(PlateNumber))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("车牌号不能为空!");
|
||||
return;
|
||||
}
|
||||
var selectedSource = SelectedRecentWeightRecord?.Source;
|
||||
var isCompleteSelectedRecord = !string.IsNullOrWhiteSpace(selectedSource?.Id);
|
||||
|
||||
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
||||
if (!isCompleteSelectedRecord)
|
||||
{
|
||||
if (isOutbound && !TareWeight.HasValue)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("出厂流程请先采集皮重!");
|
||||
return;
|
||||
}
|
||||
if (!isOutbound && !GrossWeight.HasValue)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("进厂流程请先采集毛重!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var needSecondWeightCaptured = isOutbound ? GrossWeight.HasValue : TareWeight.HasValue;
|
||||
if (!needSecondWeightCaptured)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning(isOutbound
|
||||
? "当前为已称皮重单据补毛重,必须先采集毛重!"
|
||||
: "当前为已称毛重单据补皮重,必须先采集皮重!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var entity = new MesXslWeightRecord
|
||||
{
|
||||
Id = selectedSource?.Id,
|
||||
BillNo = selectedSource?.BillNo,
|
||||
WeighDate = WeighDate,
|
||||
InoutDirection = InoutDirection,
|
||||
PlateNumber = PlateNumber,
|
||||
SenderUnit = SenderUnit,
|
||||
ReceiverUnit = ReceiverUnit,
|
||||
GoodsName = GoodsName,
|
||||
GrossWeight = GrossWeight,
|
||||
TareWeight = TareWeight,
|
||||
NetWeight = NetWeight,
|
||||
DriverName = DriverName,
|
||||
DriverPhone = DriverPhone
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var ok = isCompleteSelectedRecord
|
||||
? await _weightRecordService.EditAsync(entity)
|
||||
: await _weightRecordService.AddAsync(entity);
|
||||
if (ok)
|
||||
{
|
||||
if (isCompleteSelectedRecord)
|
||||
{
|
||||
Growl.Success("皮重回填成功,单据已更新为称重完成!");
|
||||
AddLog(isOutbound
|
||||
? $"更新成功:{entity.BillNo ?? PlateNumber},毛重 {GrossWeight:N2} kg"
|
||||
: $"更新成功:{entity.BillNo ?? PlateNumber},皮重 {TareWeight:N2} kg");
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Success("磅单保存成功!");
|
||||
AddLog($"保存成功:{PlateNumber},净重 {NetWeight:N2} kg");
|
||||
}
|
||||
_ = UpsertVehicleAsync();
|
||||
await LoadRecentWeightRecordsAsync(PlateNumber);
|
||||
// 保存后重置重量相关字段,保留日期/方向
|
||||
GrossWeight = null; TareWeight = null; NetWeight = null;
|
||||
GrossWeightCaptured = false; TareWeightCaptured = false;
|
||||
SelectedRecentWeightRecord = null;
|
||||
IsPlateNumberLocked = false;
|
||||
PlateNumber = null; DetectedPlate = string.Empty; HasPlate = false;
|
||||
SenderUnit = null; ReceiverUnit = null; GoodsName = null;
|
||||
DriverName = null; DriverPhone = null;
|
||||
ClearSupplierSelection();
|
||||
ClearCustomerSelection();
|
||||
ClearVehicleMatch();
|
||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Error("磅单保存失败!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"保存失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearForm()
|
||||
{
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
"确认清空所有填写内容?", "确认清空",
|
||||
System.Windows.MessageBoxButton.OKCancel,
|
||||
System.Windows.MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
WeighDate = DateTime.Today;
|
||||
InoutDirection = "1";
|
||||
PlateNumber = null; SenderUnit = null; ReceiverUnit = null;
|
||||
GoodsName = null; GrossWeight = null; TareWeight = null; NetWeight = null;
|
||||
DriverName = null; DriverPhone = null;
|
||||
GrossWeightCaptured = false; TareWeightCaptured = false;
|
||||
DetectedPlate = string.Empty; HasPlate = false;
|
||||
SelectedRecentWeightRecord = null;
|
||||
IsPlateNumberLocked = false;
|
||||
ClearSupplierSelection();
|
||||
ClearCustomerSelection();
|
||||
ClearVehicleMatch();
|
||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||
AddLog("表单已清空");
|
||||
}
|
||||
|
||||
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) return;
|
||||
var supplier = pickerVm.SelectedSupplier;
|
||||
_selectedSupplier = supplier;
|
||||
SenderUnit = supplier.SupplierShortName ?? supplier.SupplierName;
|
||||
RaisePropertyChanged(nameof(SenderUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
AddLog($"已选发货单位:{supplier.SupplierName}");
|
||||
}
|
||||
|
||||
private async Task OpenCustomerPickerAsync()
|
||||
{
|
||||
CustomerPickerDialogViewModel? pickerVm = null;
|
||||
bool confirmed;
|
||||
try
|
||||
{
|
||||
confirmed = await HandyControl.Controls.Dialog.Show<CustomerPickerDialogView>()
|
||||
.Initialize<CustomerPickerDialogViewModel>(vm => { pickerVm = vm; })
|
||||
.GetResultAsync<bool>();
|
||||
}
|
||||
catch { return; }
|
||||
|
||||
if (!confirmed || pickerVm?.SelectedCustomer == null) return;
|
||||
var customer = pickerVm.SelectedCustomer;
|
||||
_selectedCustomer = customer;
|
||||
ReceiverUnit = customer.CustomerShortName ?? customer.CustomerName;
|
||||
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedCustomer));
|
||||
AddLog($"已选收货单位:{customer.CustomerName}");
|
||||
}
|
||||
|
||||
private void ClearSupplierSelection()
|
||||
{
|
||||
_selectedSupplier = null;
|
||||
SenderUnit = null;
|
||||
RaisePropertyChanged(nameof(SenderUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
}
|
||||
|
||||
private void ClearCustomerSelection()
|
||||
{
|
||||
_selectedCustomer = null;
|
||||
ReceiverUnit = null;
|
||||
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedCustomer));
|
||||
}
|
||||
|
||||
private void AddLog(string message)
|
||||
{
|
||||
var entry = $"{DateTime.Now:HH:mm:ss} {message}";
|
||||
OperationLogs.Insert(0, entry);
|
||||
while (OperationLogs.Count > 8) OperationLogs.RemoveAt(OperationLogs.Count - 1);
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = await _dictSyncService.GetDictOptionsAsync("xslmes_inout_direction");
|
||||
InoutDirectionOptions.Clear();
|
||||
foreach (var item in options) InoutDirectionOptions.Add(item);
|
||||
if (InoutDirectionOptions.Count == 0) AddDefaultDirectionOptions();
|
||||
}
|
||||
catch { AddDefaultDirectionOptions(); }
|
||||
}
|
||||
|
||||
private void AddDefaultDirectionOptions()
|
||||
{
|
||||
InoutDirectionOptions.Clear();
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("进厂", "1"));
|
||||
InoutDirectionOptions.Add(new KeyValuePair<string, string>("出厂", "2"));
|
||||
}
|
||||
|
||||
private async Task LoadRecentWeightRecordsAsync(string? plateNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plate = (plateNumber ?? string.Empty).Trim();
|
||||
RecentWeightRecords.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(plate))
|
||||
{
|
||||
SelectedRecentWeightRecord = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 按进出方向查询当天待补称单据:进厂=已称毛重,出厂=已称皮重。
|
||||
var page = await _weightRecordService.PageAsync(1, 100, filterPlateNumber: plate);
|
||||
var today = DateTime.Today;
|
||||
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
||||
var candidates = page.Records
|
||||
.Where(r =>
|
||||
string.Equals((r.PlateNumber ?? string.Empty).Trim(), plate, StringComparison.OrdinalIgnoreCase) &&
|
||||
r.WeighDate.HasValue &&
|
||||
r.WeighDate.Value.Date == today &&
|
||||
(isOutbound
|
||||
? (r.TareWeight.HasValue && !r.GrossWeight.HasValue)
|
||||
: (r.GrossWeight.HasValue && !r.TareWeight.HasValue)))
|
||||
.OrderByDescending(r => r.CreateTime ?? DateTime.MinValue)
|
||||
.ToList();
|
||||
|
||||
foreach (var record in candidates)
|
||||
{
|
||||
RecentWeightRecords.Add(new WeightRecordSimpleItem
|
||||
{
|
||||
Source = record,
|
||||
BillNo = record.BillNo ?? "-",
|
||||
PlateNumber = record.PlateNumber ?? "-",
|
||||
FirstWeightDisplay = isOutbound
|
||||
? (record.TareWeight.HasValue ? $"{record.TareWeight.Value:N2}" : "-")
|
||||
: (record.GrossWeight.HasValue ? $"{record.GrossWeight.Value:N2}" : "-")
|
||||
});
|
||||
}
|
||||
|
||||
SelectedRecentWeightRecord = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 查询失败时保持现有列表,不中断主流程
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySelectedRecordToForm(WeightRecordSimpleItem selected)
|
||||
{
|
||||
if (selected.Source == null) return;
|
||||
|
||||
// 带入历史磅单时,不触发车辆重新查询
|
||||
_matchedVehicle = null;
|
||||
_lastLookedUpPlate = selected.Source.PlateNumber?.Trim();
|
||||
VehicleLookupStatus = "None";
|
||||
|
||||
_isApplyingSelectedRecord = true;
|
||||
try
|
||||
{
|
||||
// 自动带入基础信息,并按进出方向保留"待补称"的第二次称重位。
|
||||
WeighDate = selected.Source.WeighDate ?? DateTime.Today;
|
||||
InoutDirection = selected.Source.InoutDirection;
|
||||
PlateNumber = selected.Source.PlateNumber;
|
||||
_selectedSupplier = null;
|
||||
_selectedCustomer = null;
|
||||
SenderUnit = selected.Source.SenderUnit;
|
||||
ReceiverUnit = selected.Source.ReceiverUnit;
|
||||
RaisePropertyChanged(nameof(SenderUnitDisplay));
|
||||
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
RaisePropertyChanged(nameof(HasSelectedCustomer));
|
||||
GoodsName = selected.Source.GoodsName;
|
||||
DriverName = selected.Source.DriverName;
|
||||
DriverPhone = selected.Source.DriverPhone;
|
||||
|
||||
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
||||
if (isOutbound)
|
||||
{
|
||||
TareWeight = selected.Source.TareWeight;
|
||||
TareWeightCaptured = TareWeight.HasValue;
|
||||
GrossWeight = null;
|
||||
GrossWeightCaptured = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
GrossWeight = selected.Source.GrossWeight;
|
||||
GrossWeightCaptured = GrossWeight.HasValue;
|
||||
TareWeight = null;
|
||||
TareWeightCaptured = false;
|
||||
}
|
||||
AddLog($"已带入榜单:{selected.BillNo}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isApplyingSelectedRecord = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearFormByDirectionSwitch()
|
||||
{
|
||||
// 已选榜单后切换进出方向,清空数据防止串单。
|
||||
WeighDate = DateTime.Today;
|
||||
PlateNumber = null;
|
||||
SenderUnit = null;
|
||||
ReceiverUnit = null;
|
||||
GoodsName = null;
|
||||
DriverName = null;
|
||||
DriverPhone = null;
|
||||
GrossWeight = null;
|
||||
TareWeight = null;
|
||||
NetWeight = null;
|
||||
GrossWeightCaptured = false;
|
||||
TareWeightCaptured = false;
|
||||
SelectedRecentWeightRecord = null;
|
||||
IsPlateNumberLocked = false;
|
||||
ClearSupplierSelection();
|
||||
ClearCustomerSelection();
|
||||
ClearVehicleMatch();
|
||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||
AddLog("已切换进出方向,当前表单数据已清空");
|
||||
}
|
||||
|
||||
// ─── 车辆档案查询与回写 ───
|
||||
|
||||
private async Task LookupVehicleByPlateAsync(string plate)
|
||||
{
|
||||
if (string.Equals(plate, _lastLookedUpPlate, StringComparison.OrdinalIgnoreCase)) return;
|
||||
_lastLookedUpPlate = plate;
|
||||
|
||||
VehicleLookupStatus = "Searching";
|
||||
try
|
||||
{
|
||||
var result = await _vehicleService.PageAsync(1, 1, plateNumber: plate);
|
||||
var vehicle = result.Records.FirstOrDefault(v =>
|
||||
string.Equals(v.PlateNumber?.Trim(), plate, StringComparison.OrdinalIgnoreCase)
|
||||
&& v.Status != "1");
|
||||
|
||||
if (vehicle != null)
|
||||
{
|
||||
_matchedVehicle = vehicle;
|
||||
VehicleLookupStatus = "Matched";
|
||||
ApplyVehicleToForm(vehicle);
|
||||
}
|
||||
else
|
||||
{
|
||||
_matchedVehicle = null;
|
||||
VehicleLookupStatus = "NotFound";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_matchedVehicle = null;
|
||||
VehicleLookupStatus = "None";
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyVehicleToForm(MesXslVehicle vehicle)
|
||||
{
|
||||
// 司机信息优先带出
|
||||
if (!string.IsNullOrWhiteSpace(vehicle.DriverName)) DriverName = vehicle.DriverName;
|
||||
if (!string.IsNullOrWhiteSpace(vehicle.DriverPhone)) DriverPhone = vehicle.DriverPhone;
|
||||
|
||||
// 根据车辆归属自动判断进出方向与发收货单位
|
||||
if (vehicle.VehicleBelong == "2") // 供应商 → 进厂
|
||||
{
|
||||
if (!_isApplyingSelectedRecord) InoutDirection = "1";
|
||||
_selectedSupplier = null;
|
||||
SenderUnit = vehicle.SupplierShortName ?? vehicle.SupplierName;
|
||||
RaisePropertyChanged(nameof(SenderUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedSupplier));
|
||||
}
|
||||
else if (vehicle.VehicleBelong == "1") // 客户 → 出厂
|
||||
{
|
||||
if (!_isApplyingSelectedRecord) InoutDirection = "2";
|
||||
_selectedCustomer = null;
|
||||
ReceiverUnit = vehicle.CustomerShortName;
|
||||
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
|
||||
RaisePropertyChanged(nameof(HasSelectedCustomer));
|
||||
}
|
||||
// VehicleBelong == "3"(本公司):方向不定,不自动切换
|
||||
|
||||
AddLog($"车辆档案匹配:{vehicle.PlateNumber}({vehicle.VehicleBelongText})");
|
||||
}
|
||||
|
||||
private void ClearVehicleMatch()
|
||||
{
|
||||
_matchedVehicle = null;
|
||||
_lastLookedUpPlate = null;
|
||||
VehicleLookupStatus = "None";
|
||||
}
|
||||
|
||||
private async Task UpsertVehicleAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(PlateNumber)) return;
|
||||
try
|
||||
{
|
||||
if (_matchedVehicle != null)
|
||||
{
|
||||
// 已有档案:仅在司机信息有变动时回写
|
||||
bool driverChanged =
|
||||
!string.Equals(DriverName, _matchedVehicle.DriverName) ||
|
||||
!string.Equals(DriverPhone, _matchedVehicle.DriverPhone);
|
||||
if (!driverChanged) return;
|
||||
|
||||
await _vehicleService.EditAsync(new MesXslVehicle
|
||||
{
|
||||
Id = _matchedVehicle.Id,
|
||||
PlateNumber = _matchedVehicle.PlateNumber,
|
||||
VehicleBelong = _matchedVehicle.VehicleBelong,
|
||||
TareWeightKg = _matchedVehicle.TareWeightKg,
|
||||
LoadCapacity = _matchedVehicle.LoadCapacity,
|
||||
SupplierId = _matchedVehicle.SupplierId,
|
||||
SupplierName = _matchedVehicle.SupplierName,
|
||||
SupplierShortName = _matchedVehicle.SupplierShortName,
|
||||
CustomerIds = _matchedVehicle.CustomerIds,
|
||||
CustomerShortName = _matchedVehicle.CustomerShortName,
|
||||
DriverName = DriverName,
|
||||
DriverPhone = DriverPhone,
|
||||
Status = _matchedVehicle.Status,
|
||||
TenantId = _matchedVehicle.TenantId,
|
||||
Version = _matchedVehicle.Version,
|
||||
});
|
||||
AddLog($"车辆档案已更新:{PlateNumber}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无档案:根据表单信息新建车辆档案
|
||||
var vehicleBelong = InoutDirection == "2" ? "1" : "2"; // 出厂→客户 进厂→供应商
|
||||
var newVehicle = new MesXslVehicle
|
||||
{
|
||||
PlateNumber = PlateNumber?.Trim(),
|
||||
VehicleBelong = vehicleBelong,
|
||||
SupplierId = _selectedSupplier?.Id,
|
||||
SupplierName = _selectedSupplier?.SupplierName,
|
||||
SupplierShortName = _selectedSupplier?.SupplierShortName,
|
||||
CustomerShortName = _selectedCustomer?.CustomerShortName
|
||||
?? (InoutDirection == "2" ? ReceiverUnit : null),
|
||||
DriverName = DriverName,
|
||||
DriverPhone = DriverPhone,
|
||||
Status = "0",
|
||||
};
|
||||
await _vehicleService.AddAsync(newVehicle);
|
||||
AddLog($"已新建车辆档案:{PlateNumber}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 车辆回写失败不影响磅单保存,静默处理
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
_serialTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public class WeightRecordSimpleItem
|
||||
{
|
||||
public MesXslWeightRecord? Source { get; set; }
|
||||
public string BillNo { get; set; } = "-";
|
||||
public string PlateNumber { get; set; } = "-";
|
||||
public string FirstWeightDisplay { get; set; } = "-";
|
||||
}
|
||||
Reference in New Issue
Block a user