Files
qhmes/yy-admin-master/YY.Admin/ViewModels/WeightRecord/WeightRecordOperationViewModel.cs

1130 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using HandyControl.Controls;
using HandyControl.Tools.Extension;
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
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 readonly IMixerMaterialService _mixerMaterialService;
private readonly string _unitCacheFilePath;
// ─── 车辆档案匹配 ───
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();
}
TryApplyCachedUnitByDirection(value);
_ = 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;
private string? _cachedInboundReceiverUnit;
private string? _cachedOutboundSenderUnit;
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? _mixerMaterialIds;
private string? _mixerMaterialNames;
private bool _isMixerMaterialManual;
public bool IsMixerMaterialManual
{
get => _isMixerMaterialManual;
set
{
if (!SetProperty(ref _isMixerMaterialManual, value)) return;
// 切换为手动填写时,清空 ID避免保存时出现“名称手填 + ID遗留”不一致
if (value)
{
_mixerMaterialIds = null;
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
RaisePropertyChanged(nameof(IsMixerMaterialReadOnly));
OpenMixerMaterialPickerCommand.RaiseCanExecuteChanged();
}
}
public bool IsMixerMaterialReadOnly => !IsMixerMaterialManual;
public string? MixerMaterialNames
{
get => _mixerMaterialNames;
set
{
if (!SetProperty(ref _mixerMaterialNames, value)) return;
// 手动填写时名称由人工输入ID 不再有效
if (IsMixerMaterialManual)
{
_mixerMaterialIds = null;
}
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
}
public string MixerMaterialDisplay => !string.IsNullOrWhiteSpace(_mixerMaterialNames)
? _mixerMaterialNames
: "点击右侧「选择」选取密炼物料(可多选)";
public bool HasSelectedMixerMaterials => !string.IsNullOrWhiteSpace(_mixerMaterialNames);
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 OpenMixerMaterialPickerCommand { get; }
public DelegateCommand ClearSupplierCommand { get; }
public DelegateCommand ClearCustomerCommand { get; }
public DelegateCommand ClearMixerMaterialCommand { get; }
public WeightRecordOperationViewModel(
IWeightRecordService weightRecordService,
IJeecgDictSyncService dictSyncService,
IVehicleService vehicleService,
IMixerMaterialService mixerMaterialService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_weightRecordService = weightRecordService;
_dictSyncService = dictSyncService;
_vehicleService = vehicleService;
_mixerMaterialService = mixerMaterialService;
var cacheDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YY.Admin",
"cache");
Directory.CreateDirectory(cacheDir);
_unitCacheFilePath = Path.Combine(cacheDir, "weight-record-unit-cache.json");
LoadUnitCache();
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());
OpenMixerMaterialPickerCommand = new DelegateCommand(async () => await OpenMixerMaterialPickerAsync(), () => !IsMixerMaterialManual)
.ObservesProperty(() => IsMixerMaterialManual);
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
ClearCustomerCommand = new DelegateCommand(ClearCustomerSelection);
ClearMixerMaterialCommand = new DelegateCommand(ClearMixerMaterialSelection);
_ = LoadDictOptionsAsync();
_ = LoadRecentWeightRecordsAsync(PlateNumber);
TryApplyCachedUnitByDirection(InoutDirection);
// 启动串口模拟定时器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,
GrossWeight = GrossWeight,
TareWeight = TareWeight,
NetWeight = NetWeight,
DriverName = DriverName,
DriverPhone = DriverPhone,
MixerMaterialIds = _mixerMaterialIds,
MixerMaterialNames = _mixerMaterialNames,
// 勾选“手动”=2未勾选=1自动
MaterialType = IsMixerMaterialManual ? "2" : "1"
};
try
{
var ok = isCompleteSelectedRecord
? await _weightRecordService.EditAsync(entity)
: await _weightRecordService.AddAsync(entity);
if (ok)
{
CacheUnitByDirection();
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;
DriverName = null; DriverPhone = null;
ClearSupplierSelection();
ClearCustomerSelection();
ClearMixerMaterialSelection();
ClearVehicleMatch();
// 保存后立即按当前方向回填缓存,避免必须重进页面才显示
TryApplyCachedUnitByDirection(InoutDirection);
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;
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();
ClearMixerMaterialSelection();
ClearVehicleMatch();
RaisePropertyChanged(nameof(NetWeightDisplay));
AddLog("表单已清空");
}
private void CacheUnitByDirection()
{
var changed = false;
// 按需求:进厂缓存收货单位;出厂缓存发货单位
if (string.Equals(InoutDirection, "1", StringComparison.Ordinal))
{
if (!string.IsNullOrWhiteSpace(ReceiverUnit) && !string.Equals(_cachedInboundReceiverUnit, ReceiverUnit, StringComparison.Ordinal))
{
_cachedInboundReceiverUnit = ReceiverUnit;
changed = true;
}
}
else if (string.Equals(InoutDirection, "2", StringComparison.Ordinal)
&& !string.IsNullOrWhiteSpace(SenderUnit)
&& !string.Equals(_cachedOutboundSenderUnit, SenderUnit, StringComparison.Ordinal))
{
_cachedOutboundSenderUnit = SenderUnit;
changed = true;
}
if (changed)
{
SaveUnitCache();
}
}
private void TryApplyCachedUnitByDirection(string? inoutDirection)
{
// 切换方向时动态切换缓存展示:
// 进厂只展示“收货单位缓存”,出厂只展示“发货单位缓存”
if (string.Equals(inoutDirection, "1", StringComparison.Ordinal))
{
// 进厂:清空发货单位展示
_selectedSupplier = null;
SenderUnit = null;
RaisePropertyChanged(nameof(SenderUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedSupplier));
// 进厂:带出收货单位缓存(无缓存则清空)
if (!string.IsNullOrWhiteSpace(_cachedInboundReceiverUnit))
{
ReceiverUnit = _cachedInboundReceiverUnit;
AddLog($"已带出进厂收货单位缓存:{_cachedInboundReceiverUnit}");
}
else
{
_selectedCustomer = null;
ReceiverUnit = null;
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedCustomer));
}
return;
}
if (string.Equals(inoutDirection, "2", StringComparison.Ordinal))
{
// 出厂:清空收货单位展示
_selectedCustomer = null;
ReceiverUnit = null;
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedCustomer));
// 出厂:带出发货单位缓存(无缓存则清空)
if (!string.IsNullOrWhiteSpace(_cachedOutboundSenderUnit))
{
SenderUnit = _cachedOutboundSenderUnit;
AddLog($"已带出出厂发货单位缓存:{_cachedOutboundSenderUnit}");
}
else
{
_selectedSupplier = null;
SenderUnit = null;
RaisePropertyChanged(nameof(SenderUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedSupplier));
}
}
}
private void LoadUnitCache()
{
try
{
if (!File.Exists(_unitCacheFilePath)) return;
var json = File.ReadAllText(_unitCacheFilePath);
var cache = JsonSerializer.Deserialize<UnitDirectionCache>(json);
if (cache == null) return;
_cachedInboundReceiverUnit = string.IsNullOrWhiteSpace(cache.InboundReceiverUnit) ? null : cache.InboundReceiverUnit;
_cachedOutboundSenderUnit = string.IsNullOrWhiteSpace(cache.OutboundSenderUnit) ? null : cache.OutboundSenderUnit;
}
catch
{
// 缓存读取失败不影响主流程
}
}
private void SaveUnitCache()
{
try
{
var cache = new UnitDirectionCache
{
InboundReceiverUnit = _cachedInboundReceiverUnit,
OutboundSenderUnit = _cachedOutboundSenderUnit,
};
var json = JsonSerializer.Serialize(cache);
File.WriteAllText(_unitCacheFilePath, json);
}
catch
{
// 缓存写入失败不影响主流程
}
}
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 async Task OpenMixerMaterialPickerAsync()
{
MixerMaterialPickerDialogViewModel? pickerVm = null;
bool confirmed;
try
{
confirmed = await HandyControl.Controls.Dialog.Show<MixerMaterialPickerDialogView>()
.Initialize<MixerMaterialPickerDialogViewModel>(vm => { pickerVm = vm; })
.GetResultAsync<bool>();
}
catch { return; }
if (!confirmed || pickerVm == null) return;
var selected = pickerVm.SelectedMaterials;
if (selected.Count == 0) return;
_mixerMaterialIds = string.Join("", selected.Select(m => m.Source.Id ?? ""));
MixerMaterialNames = string.Join("", selected.Select(m => m.Source.MaterialName ?? ""));
if (IsMixerMaterialManual)
{
IsMixerMaterialManual = false;
}
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
AddLog($"已选密炼物料:{_mixerMaterialNames}");
}
private void ClearMixerMaterialSelection()
{
_mixerMaterialIds = null;
MixerMaterialNames = null;
IsMixerMaterialManual = false;
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
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));
DriverName = selected.Source.DriverName;
DriverPhone = selected.Source.DriverPhone;
_mixerMaterialIds = selected.Source.MixerMaterialIds;
MixerMaterialNames = selected.Source.MixerMaterialNames;
IsMixerMaterialManual = string.Equals(selected.Source.MaterialType, "2", StringComparison.Ordinal);
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
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;
DriverName = null;
DriverPhone = null;
GrossWeight = null;
TareWeight = null;
NetWeight = null;
GrossWeightCaptured = false;
TareWeightCaptured = false;
SelectedRecentWeightRecord = null;
IsPlateNumberLocked = false;
ClearSupplierSelection();
ClearCustomerSelection();
ClearMixerMaterialSelection();
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);
// 操作人员通过弹窗选了供应商,且与档案不同(含原来为空的情况)
bool supplierChanged = _matchedVehicle.VehicleBelong == "2"
&& _selectedSupplier != null
&& _selectedSupplier.Id != _matchedVehicle.SupplierId;
// 操作人员通过弹窗选了客户,且与档案不同(含原来为空的情况)
bool customerChanged = _matchedVehicle.VehicleBelong == "1"
&& _selectedCustomer != null
&& _selectedCustomer.Id != _matchedVehicle.CustomerIds;
if (!driverChanged && !supplierChanged && !customerChanged) return;
await _vehicleService.EditAsync(new MesXslVehicle
{
Id = _matchedVehicle.Id,
PlateNumber = _matchedVehicle.PlateNumber,
VehicleBelong = _matchedVehicle.VehicleBelong,
TareWeightKg = _matchedVehicle.TareWeightKg,
LoadCapacity = _matchedVehicle.LoadCapacity,
SupplierId = supplierChanged ? _selectedSupplier!.Id : _matchedVehicle.SupplierId,
SupplierName = supplierChanged ? _selectedSupplier!.SupplierName : _matchedVehicle.SupplierName,
SupplierShortName = supplierChanged ? _selectedSupplier!.SupplierShortName : _matchedVehicle.SupplierShortName,
CustomerIds = customerChanged ? _selectedCustomer!.Id : _matchedVehicle.CustomerIds,
CustomerShortName = customerChanged
? (_selectedCustomer!.CustomerShortName ?? _selectedCustomer.CustomerName)
: _matchedVehicle.CustomerShortName,
DriverName = DriverName,
DriverPhone = DriverPhone,
Status = _matchedVehicle.Status,
TenantId = _matchedVehicle.TenantId,
Version = _matchedVehicle.Version,
});
var parts = new List<string>();
if (driverChanged) parts.Add("司机信息");
if (supplierChanged) parts.Add($"供应商→{_selectedSupplier!.SupplierName}");
if (customerChanged) parts.Add($"客户→{_selectedCustomer!.CustomerName}");
AddLog($"车辆档案已更新:{PlateNumber}{string.Join("", parts)}");
}
else
{
// 只有明确查询过且未找到时才新建,避免从最近榜单带入时重复新建
if (VehicleLookupStatus != "NotFound") return;
var vehicleBelong = InoutDirection == "2" ? "1" : "2"; // 出厂→客户 进厂→供应商
var newVehicle = new MesXslVehicle
{
PlateNumber = PlateNumber?.Trim(),
VehicleBelong = vehicleBelong,
SupplierId = _selectedSupplier?.Id,
SupplierName = _selectedSupplier?.SupplierName,
SupplierShortName = _selectedSupplier?.SupplierShortName,
CustomerIds = _selectedCustomer?.Id,
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 UnitDirectionCache
{
public string? InboundReceiverUnit { get; set; }
public string? OutboundSenderUnit { get; set; }
}
public class WeightRecordSimpleItem
{
public MesXslWeightRecord? Source { get; set; }
public string BillNo { get; set; } = "-";
public string PlateNumber { get; set; } = "-";
public string FirstWeightDisplay { get; set; } = "-";
}