1207 lines
47 KiB
C#
1207 lines
47 KiB
C#
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); }
|
||
|
||
/// <summary>
|
||
/// 手动输入毛重/皮重(仅用于测试联调,正式过磅请关闭)。
|
||
/// </summary>
|
||
private bool _isManualWeightEntry;
|
||
public bool IsManualWeightEntry
|
||
{
|
||
get => _isManualWeightEntry;
|
||
set
|
||
{
|
||
if (!SetProperty(ref _isManualWeightEntry, value)) return;
|
||
CaptureGrossWeightCommand.RaiseCanExecuteChanged();
|
||
CaptureTareWeightCommand.RaiseCanExecuteChanged();
|
||
}
|
||
}
|
||
|
||
// ─── 表单绑定属性 ───
|
||
|
||
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
|
||
{
|
||
if (!SetProperty(ref _grossWeight, value)) return;
|
||
// 测试用手动录入:有有效数值即视为已填写(用于「已采集」提示与保存)
|
||
if (IsManualWeightEntry)
|
||
GrossWeightCaptured = value.HasValue && value.Value > 0;
|
||
RecalcNetWeight();
|
||
}
|
||
}
|
||
|
||
private double? _tareWeight;
|
||
public double? TareWeight
|
||
{
|
||
get => _tareWeight;
|
||
set
|
||
{
|
||
if (!SetProperty(ref _tareWeight, value)) return;
|
||
if (IsManualWeightEntry)
|
||
TareWeightCaptured = value.HasValue && value.Value > 0;
|
||
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(() => IsManualWeightEntry)
|
||
.ObservesProperty(() => GrossWeightCaptured)
|
||
.ObservesProperty(() => TareWeightCaptured);
|
||
CaptureTareWeightCommand = new DelegateCommand(CaptureTareWeight, CanCaptureTareWeight)
|
||
.ObservesProperty(() => IsWeightStable)
|
||
.ObservesProperty(() => IsManualWeightEntry)
|
||
.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;
|
||
// 同一张单第二次上磅:模拟重量与已采值协调(先毛后皮 → 皮重小于毛重;先皮后毛 → 毛重大于皮重)
|
||
PickBaseWeightForCurrentWeighingScenario();
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按当前单据已填毛重/皮重生成模拟磅台基准重量,保证净重合理。
|
||
/// </summary>
|
||
private void PickBaseWeightForCurrentWeighingScenario()
|
||
{
|
||
if (HasEffectiveWeighValue(GrossWeight) && !HasEffectiveWeighValue(TareWeight))
|
||
{
|
||
var g = GrossWeight!.Value;
|
||
// 皮重需明显小于毛重,留出净重空间
|
||
var maxTare = Math.Max(0, g - 30);
|
||
var minTare = Math.Min(maxTare * 0.22, maxTare - 80);
|
||
minTare = Math.Max(0, minTare);
|
||
if (maxTare - minTare < 25)
|
||
minTare = Math.Max(0, maxTare * 0.45);
|
||
if (maxTare - minTare < 1)
|
||
maxTare = Math.Max(minTare + 1, g - 5);
|
||
_baseWeight = minTare + _rnd.NextDouble() * Math.Max(1e-6, maxTare - minTare);
|
||
}
|
||
else if (HasEffectiveWeighValue(TareWeight) && !HasEffectiveWeighValue(GrossWeight))
|
||
{
|
||
var t = TareWeight!.Value;
|
||
var lower = t + Math.Max(100, t * 0.05);
|
||
var span = 8000 + _rnd.NextDouble() * 12000;
|
||
var upper = Math.Min(65000, lower + span);
|
||
if (upper - lower < 500)
|
||
upper = lower + 500;
|
||
_baseWeight = lower + _rnd.NextDouble() * (upper - lower);
|
||
}
|
||
else
|
||
{
|
||
_baseWeight = _rnd.Next(18000, 65000); // 18~65 吨随机(首次过磅或两称已齐)
|
||
}
|
||
}
|
||
|
||
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()
|
||
{
|
||
if (GrossWeightCaptured) return false;
|
||
// 手动测试模式:无需等稳定即可点采集
|
||
if (IsManualWeightEntry) return true;
|
||
return IsWeightStable;
|
||
}
|
||
|
||
private bool CanCaptureTareWeight()
|
||
{
|
||
if (TareWeightCaptured) return false;
|
||
if (IsManualWeightEntry) return true;
|
||
return IsWeightStable;
|
||
}
|
||
|
||
private void RecalcNetWeight()
|
||
{
|
||
// 与后端一致:0 或占位视为“未称”,避免仅毛重时 net=tare(0) 即毛重、单据误判完成
|
||
if (HasEffectiveWeighValue(GrossWeight) && HasEffectiveWeighValue(TareWeight))
|
||
{
|
||
NetWeight = Math.Round(GrossWeight!.Value - TareWeight!.Value, 2);
|
||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||
}
|
||
else
|
||
{
|
||
NetWeight = null;
|
||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||
}
|
||
}
|
||
|
||
private static bool HasEffectiveWeighValue(double? kg) => kg.HasValue && kg.Value > 0;
|
||
|
||
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 && !HasEffectiveWeighValue(TareWeight))
|
||
{
|
||
HandyControl.Controls.MessageBox.Warning("出厂流程请先采集皮重!");
|
||
return;
|
||
}
|
||
if (!isOutbound && !HasEffectiveWeighValue(GrossWeight))
|
||
{
|
||
HandyControl.Controls.MessageBox.Warning("进厂流程请先采集毛重!");
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var needSecondWeightCaptured = isOutbound ? HasEffectiveWeighValue(GrossWeight) : HasEffectiveWeighValue(TareWeight);
|
||
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();
|
||
IsManualWeightEntry = false;
|
||
// 保存后立即按当前方向回填缓存,避免必须重进页面才显示
|
||
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();
|
||
IsManualWeightEntry = false;
|
||
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);
|
||
bool effT(double? w) => w.HasValue && w.Value > 0;
|
||
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
|
||
? (effT(r.TareWeight) && !effT(r.GrossWeight))
|
||
: (effT(r.GrossWeight) && !effT(r.TareWeight))))
|
||
.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
|
||
? (HasEffectiveWeighValue(record.TareWeight) ? $"{record.TareWeight!.Value:N2}" : "-")
|
||
: (HasEffectiveWeighValue(record.GrossWeight) ? $"{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 = HasEffectiveWeighValue(TareWeight);
|
||
GrossWeight = null;
|
||
GrossWeightCaptured = false;
|
||
}
|
||
else
|
||
{
|
||
GrossWeight = selected.Source.GrossWeight;
|
||
GrossWeightCaptured = HasEffectiveWeighValue(GrossWeight);
|
||
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();
|
||
IsManualWeightEntry = false;
|
||
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; } = "-";
|
||
}
|