1250 lines
45 KiB
C#
1250 lines
45 KiB
C#
using FATrace.App.Model;
|
||
using FATrace.Model;
|
||
using NLog;
|
||
using System.Data;
|
||
using System.Net.NetworkInformation;
|
||
using System.Text;
|
||
|
||
namespace FATrace.App
|
||
{
|
||
public partial class frmMain : Form
|
||
{
|
||
public frmMain()
|
||
{
|
||
InitializeComponent();
|
||
//禁止编译器对跨线程访问做检查
|
||
Control.CheckForIllegalCrossThreadCalls = false;
|
||
|
||
// 切换到“历史数据”页时,确保下拉框数据已绑定
|
||
TabControlMain.SelectedIndexChanged += (s, e) =>
|
||
{
|
||
if (TabControlMain.SelectedTab == tabPage1)
|
||
EnsureSearchRawNameDataBound();
|
||
};
|
||
|
||
// 窗口首次显示后再绑定一次,避免某些情况下 Load 阶段控件状态未准备好
|
||
this.Shown += (s, e) =>
|
||
{
|
||
EnsureSearchRawNameDataBound();
|
||
};
|
||
}
|
||
|
||
//日志的实例化
|
||
private static Logger logger = LogManager.GetCurrentClassLogger();
|
||
|
||
// TouchSocket 版称重客户端
|
||
private TScalTcp? _scaleTcp;
|
||
|
||
// 打印机连接参数(用于状态检测)
|
||
private string _printerIp = "192.0.1.21";
|
||
private int _printerPort = 9100;
|
||
|
||
// 状态缓存,避免频繁重复写日志
|
||
private bool? _lastScaleOk = null;
|
||
private bool? _lastServerOk = null;
|
||
private bool? _lastPrinterOk = null;
|
||
|
||
// 统一更新状态栏文本与颜色(线程安全)
|
||
private void UpdateStatusLabel(ToolStripStatusLabel label, string text, Color backColor, Color foreColor)
|
||
{
|
||
void Apply()
|
||
{
|
||
label.Text = text;
|
||
label.BackColor = backColor;
|
||
label.ForeColor = foreColor;
|
||
}
|
||
if (InvokeRequired) BeginInvoke((Action)Apply); else Apply();
|
||
}
|
||
|
||
private void SetScaleStatusOk(string text)
|
||
{
|
||
UpdateStatusLabel(tslWeightState, text, Color.Green, Color.White);
|
||
_lastScaleOk = true;
|
||
}
|
||
private void SetScaleStatusFail(string text)
|
||
{
|
||
UpdateStatusLabel(tslWeightState, text, Color.Tomato, Color.White);
|
||
_lastScaleOk = false;
|
||
}
|
||
private void SetServerStatusOk(string text)
|
||
{
|
||
UpdateStatusLabel(tslServerState, text, Color.Green, Color.White);
|
||
_lastServerOk = true;
|
||
}
|
||
private void SetServerStatusFail(string text)
|
||
{
|
||
UpdateStatusLabel(tslServerState, text, Color.Tomato, Color.White);
|
||
_lastServerOk = false;
|
||
}
|
||
private void SetPrinterStatusOk(string text)
|
||
{
|
||
UpdateStatusLabel(tslPrintState, text, Color.Green, Color.White);
|
||
_lastPrinterOk = true;
|
||
}
|
||
private void SetPrinterStatusFail(string text)
|
||
{
|
||
UpdateStatusLabel(tslPrintState, text, Color.Tomato, Color.White);
|
||
_lastPrinterOk = false;
|
||
}
|
||
|
||
|
||
// 使用 ICMP Ping 检测主机可达,避免占用打印机 9100 端口而影响正常打印
|
||
private async Task<bool> PingAsync(string ip, int timeoutMs)
|
||
{
|
||
try
|
||
{
|
||
using var ping = new Ping();
|
||
var reply = await ping.SendPingAsync(ip, timeoutMs);
|
||
return reply.Status == IPStatus.Success;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private async Task CheckDbStatusAsync()
|
||
{
|
||
try
|
||
{
|
||
var ok = await Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
// 简单心跳
|
||
var obj = FSqlContext.FDb.Ado.ExecuteScalar("SELECT 1");
|
||
if (obj == null) return false;
|
||
// 兼容不同数据库返回类型(可能是 long/decimal/string)
|
||
var v = Convert.ToInt32(obj);
|
||
return v == 1;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
});
|
||
|
||
if (_lastServerOk != ok)
|
||
{
|
||
if (ok)
|
||
{
|
||
SetServerStatusOk("服务器正常");
|
||
logger.Info("数据库服务器心跳正常");
|
||
}
|
||
else
|
||
{
|
||
SetServerStatusFail("服务器异常");
|
||
logger.Error("数据库服务器心跳失败");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 状态未变化,但仍在首次加载时给出视觉反馈
|
||
if (ok && _lastServerOk == null) SetServerStatusOk("服务器正常");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetServerStatusFail($"服务器异常:{ex.Message}");
|
||
logger.Error(ex, "数据库服务器状态检测异常");
|
||
}
|
||
}
|
||
|
||
private async Task CheckPrinterStatusAsync()
|
||
{
|
||
try
|
||
{
|
||
// 使用 Ping 检测打印机在线状态,不占用打印端口
|
||
bool ok = await PingAsync(_printerIp, 2000);
|
||
if (_lastPrinterOk != ok)
|
||
{
|
||
if (ok)
|
||
{
|
||
SetPrinterStatusOk("打印机在线");
|
||
logger.Info($"打印机({_printerIp}:{_printerPort})在线");
|
||
}
|
||
else
|
||
{
|
||
SetPrinterStatusFail("打印机离线");
|
||
logger.Error($"打印机({_printerIp}:{_printerPort})离线");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (ok && _lastPrinterOk == null) SetPrinterStatusOk("打印机在线");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetPrinterStatusFail($"打印机异常:{ex.Message}");
|
||
logger.Error(ex, "打印机状态检测异常");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前确认者用户名
|
||
/// </summary>
|
||
public string CurrentCheckUserNo { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 当前操作者用户名
|
||
/// </summary>
|
||
public string CurrentOpUserNo { get; set; } = string.Empty;
|
||
|
||
public string CurrentOperationNoUserLevel { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 计算扫描 Task
|
||
/// </summary>
|
||
private static Task CycleTask { get; set; }
|
||
|
||
/// <summary>
|
||
/// 日信息
|
||
/// 防止跨日的生产的产量的清零
|
||
/// </summary>
|
||
private DaySgl CurDaySgl { get; set; }
|
||
|
||
/// <summary>
|
||
/// 线程的启用
|
||
/// </summary>
|
||
private bool ThreadEnable { get; set; } = true;
|
||
|
||
private async void frmMain_Load(object sender, EventArgs e) // 将加载事件改为 async 以便等待连接
|
||
{
|
||
this.TabControlMain.SelectedIndex = 1;
|
||
|
||
// 初始化打印机对象
|
||
CurZebraPrint = new ZebraPrint(_printerIp, _printerPort);
|
||
|
||
// 下面初始化称重服务(请按需修改 IP 与端口为仪表的实际地址)
|
||
var scaleIp = "192.168.0.80"; // 仪表的 TCP Server IP(示例值,实际请替换)
|
||
var scalePort = 10251; // 仪表的 TCP Server 端口(示例值,实际请替换)
|
||
|
||
//日产量初始获取信息
|
||
CurDayCount = GetDayCount();
|
||
|
||
ListRawCtrInfo = new List<RawCtrInfo>()
|
||
{
|
||
new RawCtrInfo(){
|
||
RawName="瑞士乳杆菌GCL1815",
|
||
RawCode="DYG05030013",
|
||
BtnControlName="btnRawName1",
|
||
RawSource=RawSource.China
|
||
},
|
||
new RawCtrInfo(){
|
||
RawName="抗性糊精",
|
||
RawCode="YG03031004",
|
||
BtnControlName="btnRawName3",
|
||
RawSource=RawSource.China
|
||
},
|
||
new RawCtrInfo(){
|
||
RawName="白砂糖",
|
||
RawCode="YG03010001",
|
||
BtnControlName="btnRawName2",
|
||
RawSource=RawSource.China
|
||
},
|
||
};
|
||
|
||
// 初始化历史查询下拉框数据
|
||
EnsureSearchRawNameDataBound();
|
||
|
||
CurDaySgl = new DaySgl();
|
||
CurDaySgl.DaySglEvent += DaySgl_DaySglEvent;
|
||
CycleScan();
|
||
|
||
try // 捕获连接过程中的异常,便于界面提示
|
||
{
|
||
// 使用 TouchSocket 版本的称重客户端
|
||
_scaleTcp = new TScalTcp(scaleIp, scalePort);
|
||
_scaleTcp.StableWeightReceived += ScaleTcp_StableWeightReceived; // 稳定值事件
|
||
_scaleTcp.CommunicationError += ScaleTcp_CommunicationError; // 通信异常事件
|
||
await _scaleTcp.StartAsync(); // 启动并连接
|
||
|
||
SetScaleStatusOk($"称重已连接 {scaleIp}:{scalePort}");
|
||
logger.Info($"称重连接成功:{scaleIp}:{scalePort}");
|
||
|
||
// 如需切回 SuperSocket 方案,请注释上方并取消下方注释
|
||
// _weightService = new TScalWeightServices(scaleIp, scalePort);
|
||
// _weightService.StableWeightReceived += WeightService_StableWeightReceived;
|
||
// _weightService.CommunicationError += WeightService_CommunicationError;
|
||
// await _weightService.StartAsync();
|
||
}
|
||
catch (Exception ex) // 连接失败时弹出提示
|
||
{
|
||
SetScaleStatusFail($"称重连接失败:{ex.Message}");
|
||
logger.Error(ex, "称重连接失败");
|
||
MessageBox.Show($"称重连接失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); // 显示错误
|
||
}
|
||
|
||
// 启动后台状态检测(DB与打印机)
|
||
_ = Task.Run(async () =>
|
||
{
|
||
await CheckDbStatusAsync();
|
||
await CheckPrinterStatusAsync();
|
||
});
|
||
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 跨日的生产的产量的清零
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void DaySgl_DaySglEvent(object? sender, string e)
|
||
{
|
||
CurDayCount = GetDayCount();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 周期扫描
|
||
/// </summary>
|
||
private void CycleScan()
|
||
{
|
||
CycleTask = Task.Run(async () =>
|
||
{
|
||
int loop = 0;
|
||
while (ThreadEnable)
|
||
{
|
||
await Task.Delay(3000);
|
||
|
||
// 称重连接状态轮询(防止某些厂商设备断线未触发回调)
|
||
bool scaleOk = _scaleTcp?.IsConnected ?? false;
|
||
if (_lastScaleOk != scaleOk)
|
||
{
|
||
if (scaleOk)
|
||
{
|
||
SetScaleStatusOk("称重已连接");
|
||
logger.Info("称重连接恢复");
|
||
}
|
||
else
|
||
{
|
||
SetScaleStatusFail("称重未连接");
|
||
logger.Error("称重连接断开");
|
||
}
|
||
}
|
||
|
||
// 每30秒检测一次服务器与打印机
|
||
loop = (loop + 1) % 1000000;
|
||
if (loop % 10 == 0)
|
||
await CheckDbStatusAsync();
|
||
if (loop % 10 == 5)
|
||
await CheckPrinterStatusAsync();
|
||
}
|
||
});
|
||
}
|
||
|
||
private void btnTest_Click(object sender, EventArgs e)
|
||
{
|
||
//new ZebraPrint("192.168.0.40", 9100).PrintWeight("添加剂21", 80.8, "20251021", 3, "02", 2583);
|
||
|
||
}
|
||
|
||
///// <summary>
|
||
///// 当前选择的原料名称
|
||
///// </summary>
|
||
//private string CurSelectedRawProInput.RawName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前重量数据
|
||
/// </summary>
|
||
private double CurWeight { get; set; } = 80.8;
|
||
|
||
///// <summary>
|
||
///// 当前剩余数据
|
||
///// </summary>
|
||
//private double CurRemainWeight { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前选中的生产原料数据
|
||
/// </summary>
|
||
private RawProInput CurSelectedRawProInput { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前打印的驱动数据
|
||
/// </summary>
|
||
private ZebraPrint CurZebraPrint { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当天的产量信息
|
||
/// </summary>
|
||
private DayCount CurDayCount { get; set; }
|
||
|
||
/// <summary>
|
||
/// 原料控制集合
|
||
/// </summary>
|
||
private List<RawCtrInfo> ListRawCtrInfo { get; set; }
|
||
|
||
/// <summary>
|
||
/// 选中的原料控件信息
|
||
/// </summary>
|
||
private RawCtrInfo SelectedRawCtrInfo { get; set; }
|
||
|
||
#region 原料名称按钮控制
|
||
|
||
/// <summary>
|
||
/// 高亮当前选中的原料按钮,并还原其他按钮样式
|
||
/// </summary>
|
||
private void HighlightRawButton(Button active)
|
||
{
|
||
var all = new[] { btnRawName1, btnRawName2, btnRawName3 };
|
||
foreach (var b in all)
|
||
{
|
||
if (b == null) continue;
|
||
if (ReferenceEquals(b, active))
|
||
{
|
||
b.UseVisualStyleBackColor = false;
|
||
b.BackColor = Color.DeepSkyBlue;
|
||
b.ForeColor = Color.White;
|
||
}
|
||
else
|
||
{
|
||
b.UseVisualStyleBackColor = true;
|
||
b.BackColor = SystemColors.Control;
|
||
b.ForeColor = SystemColors.ControlText;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统一处理原料按钮点击的选择与高亮
|
||
/// </summary>
|
||
private void OnRawNameButtonClicked(Button btn)
|
||
{
|
||
//CurSelectedRawName = btn.Text?.Trim();
|
||
HighlightRawButton(btn);
|
||
|
||
foreach (var RawCtrInfo in ListRawCtrInfo)
|
||
{
|
||
if (RawCtrInfo.BtnControlName == btn.Name)
|
||
{
|
||
SelectedRawCtrInfo = RawCtrInfo;
|
||
break;
|
||
}
|
||
}
|
||
|
||
var RawInfoData = CheckRawInfo(SelectedRawCtrInfo.RawName!);
|
||
|
||
if (RawInfoData.result)
|
||
{
|
||
//只取最新的第一个原料信息
|
||
CurSelectedRawProInput = RawInfoData.data!;
|
||
|
||
txtWeight.Text = CurSelectedRawProInput.Weight.ToString();
|
||
txtBatch.Text = CurSelectedRawProInput.Batch!;
|
||
txtShelfLife.Text = CurSelectedRawProInput.ShelfLife.ToString();
|
||
txtRemainWeight.Text = CurSelectedRawProInput.RemainWeight.ToString();
|
||
|
||
Task.Run(() =>
|
||
{
|
||
lblRawBeforeInfo.BeginInvoke(new Action(() =>
|
||
{
|
||
lblRawBeforeInfo.Visible = true;
|
||
}));
|
||
Thread.Sleep(3000);
|
||
lblRawBeforeInfo.BeginInvoke(new Action(() =>
|
||
{
|
||
lblRawBeforeInfo.Visible = false;
|
||
}));
|
||
});
|
||
|
||
}
|
||
else
|
||
{
|
||
//可能第一次使用的时候,需要初始化数据,把控件的信息给选中的模型
|
||
CurSelectedRawProInput = new RawProInput();
|
||
CurSelectedRawProInput.RawSource = SelectedRawCtrInfo.RawSource;
|
||
CurSelectedRawProInput.RawName = SelectedRawCtrInfo.RawName!;
|
||
CurSelectedRawProInput.RawCode = SelectedRawCtrInfo.RawCode!;
|
||
|
||
|
||
//清空分拆的数据
|
||
ClearInput();
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查原料信息
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private (bool result, RawProInput data) CheckRawInfo(string RawName)
|
||
{
|
||
//理论上如果存在的话,只有一个信息,因为一个原料没有用完的情况下,是不可以用下一个的产品的,否则提示
|
||
var ListRawProInput = FSqlContext.FDb.Select<RawProInput>().Where(a => a.RawName == RawName && a.RawState == RawSplitState.Splitting).OrderByDescending(a => a.CreateTime).ToList();
|
||
if (ListRawProInput.Count() > 0)
|
||
{
|
||
|
||
|
||
return (true, ListRawProInput.FirstOrDefault()!);
|
||
}
|
||
else
|
||
{
|
||
//不存在的话,说明是新的原料,可以直接使用
|
||
return (false, new RawProInput());
|
||
}
|
||
|
||
|
||
}
|
||
|
||
private void btnRawName1_Click(object sender, EventArgs e)
|
||
{
|
||
if (sender is Button btn)
|
||
{
|
||
OnRawNameButtonClicked(btn);
|
||
}
|
||
}
|
||
|
||
private void btnRawName3_Click(object sender, EventArgs e)
|
||
{
|
||
if (sender is Button btn)
|
||
{
|
||
OnRawNameButtonClicked(btn);
|
||
}
|
||
}
|
||
|
||
private void btnRawName2_Click(object sender, EventArgs e)
|
||
{
|
||
if (sender is Button btn)
|
||
{
|
||
OnRawNameButtonClicked(btn);
|
||
}
|
||
}
|
||
|
||
private void btnRawNameCommon_Click(object? sender, EventArgs e)
|
||
{
|
||
if (sender is Button btn)
|
||
{
|
||
OnRawNameButtonClicked(btn);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 确认输入数据
|
||
/// 大包装的产线数据
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void btnTrueProInput_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 1) 校验已选择的原料名称
|
||
if (CurSelectedRawProInput == null || string.IsNullOrWhiteSpace(CurSelectedRawProInput.RawName))
|
||
{
|
||
MessageBox.Show("请先在上方选择原料名称。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
|
||
// 2) 读取并校验输入框
|
||
var weightText = (txtWeight.Text ?? string.Empty).Trim();
|
||
var batchText = (txtBatch.Text ?? string.Empty).Trim();
|
||
var shelfLifeText = (txtShelfLife.Text ?? string.Empty).Trim();
|
||
|
||
if (string.IsNullOrWhiteSpace(weightText) ||
|
||
!double.TryParse(weightText.Replace(',', '.').Replace('。', '.'), out var weight) || weight <= 0)
|
||
{
|
||
MessageBox.Show("请输入有效的入库重量(Kg),必须为正数。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
txtWeight.Focus();
|
||
txtWeight.SelectAll();
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(batchText))
|
||
{
|
||
MessageBox.Show("请输入批号。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
txtBatch.Focus();
|
||
return;
|
||
}
|
||
if (batchText.Length > 20)
|
||
{
|
||
MessageBox.Show("批号长度不能超过20个字符。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
txtBatch.Focus();
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(shelfLifeText) ||
|
||
!int.TryParse(shelfLifeText.Replace(',', '.').Replace('。', '.'), out var shelfLife) || shelfLife <= 0)
|
||
{
|
||
MessageBox.Show("请输入有效的保质期(月),必须为正数。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
txtShelfLife.Focus();
|
||
txtShelfLife.SelectAll();
|
||
return;
|
||
}
|
||
|
||
// 3) 组装实体
|
||
var entity = new RawProInput
|
||
{
|
||
RawName = CurSelectedRawProInput!.RawName,
|
||
RawCode = CurSelectedRawProInput.RawCode,
|
||
Weight = Math.Round(weight, 2), // 保留3位小数,避免浮点抖动
|
||
Batch = batchText,
|
||
ShelfLife = shelfLife,
|
||
RawSource = CurSelectedRawProInput.RawSource,
|
||
RemainWeight = Math.Round(weight, 2),
|
||
RawState = RawSplitState.Splitting
|
||
};
|
||
|
||
// 4) 写入数据库(FreeSql)
|
||
var affRowsData = FSqlContext.FDb.Insert(entity).ExecuteInserted();
|
||
if (affRowsData.Count <= 0)
|
||
{
|
||
MessageBox.Show("保存失败:未能写入数据库。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
//插入成功后,更新当前选中的产品
|
||
CurSelectedRawProInput = affRowsData.FirstOrDefault()!;
|
||
|
||
// 5) 成功提示并清空输入
|
||
MessageBox.Show("保存成功。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"保存失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清零当前的原料
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void btnClearRaw_Click(object sender, EventArgs e)
|
||
{
|
||
DialogResult result = MessageBox.Show("您确定清零当前的原料?", "Confirmation",
|
||
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
||
if (result == DialogResult.No)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (CurSelectedRawProInput == null)
|
||
{
|
||
MessageBox.Show("请选中原料后再操作。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
|
||
var UpdatedData = FSqlContext.FDb.Update<RawProInput>()
|
||
.Set(a => a.RawState, RawSplitState.SplitComplete)
|
||
.Where(a => a.Id == CurSelectedRawProInput.Id)
|
||
.ExecuteUpdated();
|
||
|
||
if (UpdatedData.Count() > 0)
|
||
{
|
||
ClearInput();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 称重打印数据
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void btnWeightPrint_Click(object sender, EventArgs e)
|
||
{
|
||
if (CurSelectedRawProInput == null)
|
||
{
|
||
MessageBox.Show("请先选择要称重的产品", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
if (string.IsNullOrEmpty(CurSelectedRawProInput.RawName) || string.IsNullOrEmpty(CurSelectedRawProInput.Batch) || string.IsNullOrEmpty(CurSelectedRawProInput.RawCode))
|
||
{
|
||
MessageBox.Show("选择的产品信息无数据,请信息输入后再操作", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
|
||
|
||
//新的剩余重量 Kg
|
||
var NewRemainWeight = CurSelectedRawProInput.RemainWeight - CurWeight / 1000;
|
||
|
||
//当前产品的剩余重量
|
||
txtRemainWeight.Text = NewRemainWeight.ToString();
|
||
|
||
//二维码
|
||
var Code = GenCode(CurSelectedRawProInput.RawCode!, CurWeight, CurSelectedRawProInput.Batch!, CurSelectedRawProInput.ShelfLife, CurSelectedRawProInput.RawSource, CurDayCount.Count);
|
||
|
||
// 执行打印
|
||
try
|
||
{
|
||
UpdateStatusLabel(tslPrintState, "正在打印...", Color.Goldenrod, Color.White);
|
||
CurZebraPrint.PrintWeight(Code, CurSelectedRawProInput.RawName!,
|
||
CurWeight,
|
||
CurSelectedRawProInput.Batch!,
|
||
CurSelectedRawProInput.ShelfLife
|
||
);
|
||
SetPrinterStatusOk("打印成功");
|
||
logger.Info($"打印成功:{CurSelectedRawProInput.RawName} {CurWeight}g 批号{CurSelectedRawProInput.Batch}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetPrinterStatusFail($"打印失败:{ex.Message}");
|
||
logger.Error(ex, "打印失败");
|
||
MessageBox.Show($"打印失败:{ex.Message}", "打印机错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
|
||
//内包条码更新
|
||
txtCode.Text = Code;
|
||
|
||
// 写入称重使用记录
|
||
var Result = FSqlContext.FDb.Insert<RawProUse>(new RawProUse()
|
||
{
|
||
Batch = CurSelectedRawProInput.Batch,
|
||
InBagCode = Code,
|
||
BoxCode = Code + ",A",
|
||
OpUser = CurrentOpUserNo,
|
||
CheckUser = CurrentCheckUserNo,
|
||
RawCode = CurSelectedRawProInput.RawCode,
|
||
ShelfLife = CurSelectedRawProInput.ShelfLife,
|
||
RawName = CurSelectedRawProInput.RawName,
|
||
RemainWeight = NewRemainWeight,
|
||
Weight = CurWeight,
|
||
DeliveryDate= DateTime.Now.ToString("yyyy-MM-dd"),
|
||
WeightTime = DateTime.Now,
|
||
StockWeight = CurSelectedRawProInput.Weight,
|
||
}).ExecuteAffrows();
|
||
|
||
if (Result > 0)
|
||
{
|
||
//结束后这个重量
|
||
CurSelectedRawProInput.RemainWeight = NewRemainWeight;
|
||
txtRemainWeight.Text = CurSelectedRawProInput.RemainWeight.ToString();
|
||
|
||
//称重检查,如果剩余重量为0,进行提示,当剩余的重量小于称重的重量,进行提示
|
||
if (CurSelectedRawProInput.RemainWeight < CurWeight / 1000.0)
|
||
{
|
||
//剩余的重量小于称重的重量,进行提示,强制进行清零
|
||
Task.Run(() =>
|
||
{
|
||
lblRawUseStateTip.BeginInvoke(new Action(() =>
|
||
{
|
||
lblRawUseStateTip.Visible = true;
|
||
}));
|
||
Thread.Sleep(3000);
|
||
lblRawUseStateTip.BeginInvoke(new Action(() =>
|
||
{
|
||
lblRawUseStateTip.Visible = false;
|
||
}));
|
||
});
|
||
|
||
//更新数据,完成
|
||
FSqlContext.FDb.Update<RawProInput>()
|
||
.Set(a => a.RemainWeight, NewRemainWeight)
|
||
.Set(a => a.RawState, RawSplitState.SplitComplete)
|
||
.Where(a => a.Id == CurSelectedRawProInput.Id)
|
||
.ExecuteAffrows();
|
||
|
||
//当前的产品信息清零
|
||
CurSelectedRawProInput = null;
|
||
|
||
//清空分拆的数据
|
||
ClearInput();
|
||
|
||
//恢复btnRawName1,2,3的默认样式
|
||
SetDefaultStyle(panel1);
|
||
}
|
||
else
|
||
{
|
||
//当前分拆的产品的剩余重量更新数据
|
||
FSqlContext.FDb.Update<RawProInput>()
|
||
.Set(a => a.RemainWeight, NewRemainWeight)
|
||
.Where(a => a.Id == CurSelectedRawProInput.Id)
|
||
.ExecuteAffrows();
|
||
}
|
||
|
||
// 产量 +1
|
||
CurDayCount.Count = CurDayCount.Count + 1;
|
||
UpdateDayCount(CurDayCount);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前的日产量信息
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private DayCount GetDayCount()
|
||
{
|
||
var CurDayCountInfo = FSqlContext.FDb.Select<DayCount>().Where(a => a.DayInfo == DateTime.Now.ToString("yyyy-MM-dd")).First();
|
||
if (CurDayCountInfo == null)
|
||
{
|
||
//当日的日产量信息不存在,第一次的话就新建信息
|
||
var ReturnData = FSqlContext.FDb.Insert<DayCount>(new DayCount()
|
||
{
|
||
Count = 1,
|
||
DayInfo = DateTime.Now.ToString("yyyy-MM-dd"),
|
||
}).ExecuteInserted();
|
||
return ReturnData.FirstOrDefault()!;
|
||
}
|
||
else
|
||
{
|
||
//否则的话,获取当前的日产量信息
|
||
return CurDayCountInfo;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新日产量信息
|
||
/// </summary>
|
||
private bool UpdateDayCount(DayCount dayCount)
|
||
{
|
||
var Data = FSqlContext.FDb.Update<DayCount>()
|
||
.Set(a => a.Count, dayCount.Count)
|
||
.Set(a => a.UpdateTime, DateTime.Now)
|
||
.Where(a => a.Id == dayCount.Id)
|
||
.ExecuteAffrows();
|
||
|
||
if (Data > 0)
|
||
{
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 清空输入
|
||
/// </summary>
|
||
private void ClearInput()
|
||
{
|
||
txtWeight.Text = "";
|
||
txtBatch.Text = "";
|
||
txtShelfLife.Text = "";
|
||
txtRemainWeight.Text = "";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复panel1内按钮默认样式
|
||
/// </summary>
|
||
/// <param name="btn"></param>
|
||
private void SetDefaultStyle(Panel panel)
|
||
{
|
||
foreach (Control control in panel.Controls)
|
||
{
|
||
if (control is Button btn)
|
||
{
|
||
btn.BackColor = Color.Transparent;
|
||
btn.ForeColor = Color.Black;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成条码信息
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public string GenCode(string RawCode, double Weight, string Batch, int ShelfLife, RawSource Source, int DayCount)
|
||
{
|
||
//二维码数据
|
||
var Code = new StringBuilder();
|
||
Code.Append(RawCode);
|
||
Code.Append(',');
|
||
Code.Append(Batch);
|
||
Code.Append(',');
|
||
Code.Append(Weight.ToString().Replace(".", ""));
|
||
Code.Append(',');
|
||
Code.Append(ShelfLife.ToString());
|
||
Code.Append(',');
|
||
Code.Append(GetSourceInf(Source));
|
||
Code.Append(',');
|
||
Code.Append(DayCount.ToString());
|
||
|
||
return Code.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取枚举的信息
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public string GetSourceInf(RawSource rawSource)
|
||
{
|
||
switch (rawSource)
|
||
{
|
||
case RawSource.China:
|
||
return "01";
|
||
case RawSource.Japan:
|
||
return "02";
|
||
default:
|
||
return "01";
|
||
}
|
||
}
|
||
|
||
// TouchSocket 版本稳定数据事件处理
|
||
private void ScaleTcp_StableWeightReceived(object? sender, TScalTcp.StableWeightEventArgs e)
|
||
{
|
||
void UpdateUi()
|
||
{
|
||
CurWeight = (double)e.Value;
|
||
txtRtWeight.Text = e.Value.ToString("0.###");
|
||
// 数据收发正常
|
||
if (_lastScaleOk != true)
|
||
{
|
||
SetScaleStatusOk("称重通讯正常");
|
||
}
|
||
}
|
||
|
||
if (InvokeRequired)
|
||
BeginInvoke((Action)UpdateUi);
|
||
else
|
||
UpdateUi();
|
||
}
|
||
|
||
// TouchSocket 版本通信错误处理
|
||
private void ScaleTcp_CommunicationError(object? sender, string message)
|
||
{
|
||
void ShowError()
|
||
{
|
||
MessageBox.Show(message, "称重通信错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
}
|
||
|
||
// 更新状态与日志
|
||
SetScaleStatusFail(message);
|
||
logger.Error($"称重通信错误:{message}");
|
||
|
||
if (InvokeRequired)
|
||
BeginInvoke((Action)ShowError);
|
||
else
|
||
ShowError();
|
||
}
|
||
|
||
// 窗体关闭时,停止称重服务,释放网络连接
|
||
protected override void OnFormClosed(FormClosedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
_scaleTcp?.StopAsync().GetAwaiter().GetResult();
|
||
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
base.OnFormClosed(e);
|
||
}
|
||
|
||
/// <param name="e"></param>
|
||
private void btnLogin_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(txtCheckUserName.Text))
|
||
{
|
||
MessageBox.Show("请输入确认者用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
if (string.IsNullOrEmpty(txtOpName.Text))
|
||
{
|
||
MessageBox.Show("请输入操作者用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
if (string.IsNullOrEmpty(txtPassword.Text))
|
||
{
|
||
MessageBox.Show("请输入密码", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
|
||
var ListUser = FSqlContext.FDb.Select<TbUser>().Where(a => a.UserName == txtCheckUserName.Text.Trim()).ToList();
|
||
if (ListUser != null && ListUser.Count() > 0)
|
||
{
|
||
if (ListUser.FirstOrDefault().Password == txtPassword.Text.Trim())
|
||
{
|
||
MessageBox.Show("登录成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
Main_PopUserNameEvent(txtCheckUserName.Text.Trim(), txtOpName.Text.Trim(), ListUser.FirstOrDefault().AccessLevel);
|
||
|
||
this.TabControlMain.SelectedIndex = 0;
|
||
|
||
txtCheckUserName.Text = "";
|
||
txtPassword.Text = "";
|
||
//PopUserNameEvent(txtUserName.Text.Trim());
|
||
//this.Close();
|
||
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("密码错误!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("当前用户不存在!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
|
||
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 用户登录的事件发布方法
|
||
/// </summary>
|
||
/// <param name="UserName"></param>
|
||
private void Main_PopUserNameEvent(string CheckUserName, string OpUserName, string UserLevel)
|
||
{
|
||
try
|
||
{
|
||
CurrentCheckUserNo = CheckUserName;
|
||
CurrentOpUserNo = OpUserName;
|
||
|
||
CurrentOperationNoUserLevel = UserLevel;
|
||
Invoke(new Action(() =>
|
||
{
|
||
tslCurrentUser.Text = "确认者用户:" + CurrentCheckUserNo;
|
||
tslCurrentUser.BackColor = Color.Green;
|
||
tslCurrentUser.ForeColor = Color.White;
|
||
}));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
|
||
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
|
||
}
|
||
}
|
||
|
||
#region 系统按钮的操作
|
||
|
||
private void bntFrmLogin_Click(object sender, EventArgs e)
|
||
{
|
||
TabControlMain.SelectedIndex = 1;
|
||
}
|
||
|
||
private void btnSysConfig_Click(object sender, EventArgs e)
|
||
{
|
||
if (!string.IsNullOrEmpty(CurrentCheckUserNo))
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
private void btnExit_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
if (DialogResult.OK == MessageBox.Show("你确定要关闭程序系统吗?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning))
|
||
{
|
||
ThreadEnable = false;
|
||
// 断开连接
|
||
//_MelsecMcNet?.ConnectClose();
|
||
// 断开连接
|
||
|
||
|
||
this.Close();
|
||
System.Environment.Exit(System.Environment.ExitCode);
|
||
|
||
//Application.ExitThread();
|
||
//Application.Exit();
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
|
||
//ScanSerialPort.Close();
|
||
//HandSerialPort.Close();
|
||
//_MelsecMcNet?.ConnectClose();
|
||
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
|
||
}
|
||
}
|
||
|
||
private void btnMain_Click(object sender, EventArgs e)
|
||
{
|
||
if (!string.IsNullOrEmpty(CurrentCheckUserNo))
|
||
{
|
||
TabControlMain.SelectedIndex = 0;
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("请登录后再操作!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
}
|
||
}
|
||
|
||
private void btnHistoryData_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
TabControlMain.SelectedIndex = 2; // 切换到历史数据页
|
||
EnsureSearchRawNameDataBound(); // 确保下拉框有数据
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 历史数据搜索
|
||
|
||
private void btnSearchHistoryData_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 初始化下拉框数据源(仅在首次/为空时绑定)
|
||
EnsureSearchRawNameDataBound();
|
||
|
||
// 时间范围:按日期选择,自动扩大为整日区间
|
||
var startDate = dtpSearchStartTime.Value.Date; // 00:00:00
|
||
var endDate = dtpSearchEndTime.Value.Date.AddDays(1).AddTicks(-1); // 23:59:59.9999999
|
||
|
||
if (startDate > endDate)
|
||
{
|
||
MessageBox.Show("开始时间不能晚于结束时间。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
|
||
// 原料名称/编码关键字:允许为空(为空则不过滤)。
|
||
var rawKeyword = (cbxSearchRawName.Text ?? string.Empty).Trim();
|
||
|
||
// 批号:精确匹配
|
||
var batchKeyword = (txtSearchBatch.Text ?? string.Empty).Trim();
|
||
|
||
// 构建 FreeSql 查询
|
||
var select = FSqlContext.FDb.Select<RawProUse>()
|
||
.Where(a => a.WeightTime >= startDate && a.WeightTime <= endDate);
|
||
|
||
if (!string.IsNullOrWhiteSpace(rawKeyword))
|
||
// 名称与编码均支持模糊匹配
|
||
select = select.Where(a => (a.RawName != null && a.RawName.Contains(rawKeyword)) || (a.RawCode != null && a.RawCode.Contains(rawKeyword)));
|
||
|
||
if (!string.IsNullOrWhiteSpace(batchKeyword))
|
||
select = select.Where(a => a.Batch.Contains(batchKeyword));
|
||
|
||
var result = select.OrderByDescending(a => a.WeightTime).ToList();
|
||
|
||
// 绑定到 DataGridView
|
||
dataGridView1.AutoGenerateColumns = true;
|
||
dataGridView1.DataSource = result;
|
||
ApplyChineseColumnHeaders();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"查询失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保搜索条件中的“原料名称”下拉框已从 ListRawCtrInfo 绑定数据。
|
||
/// </summary>
|
||
private void EnsureSearchRawNameDataBound()
|
||
{
|
||
// 优先使用界面配置的原料集合;若为空则回退到数据库历史记录中的原料名称
|
||
string[] names = Array.Empty<string>();
|
||
if (ListRawCtrInfo != null && ListRawCtrInfo.Count > 0)
|
||
{
|
||
names = ListRawCtrInfo
|
||
.Where(x => !string.IsNullOrWhiteSpace(x.RawName))
|
||
.Select(x => x.RawName!.Trim())
|
||
.Distinct()
|
||
.ToArray();
|
||
}
|
||
else
|
||
{
|
||
try
|
||
{
|
||
names = FSqlContext.FDb.Select<RawProUse>()
|
||
.Where(a => a.RawName != null && a.RawName != "")
|
||
.OrderBy(a => a.RawName)
|
||
.ToList(a => a.RawName!)
|
||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||
.Distinct()
|
||
.ToArray();
|
||
}
|
||
catch { /* 忽略绑定期的数据库异常,保持界面可用 */ }
|
||
}
|
||
|
||
cbxSearchRawName.BeginUpdate();
|
||
try
|
||
{
|
||
cbxSearchRawName.DataSource = null;
|
||
cbxSearchRawName.Items.Clear();
|
||
if (names.Length > 0)
|
||
cbxSearchRawName.Items.AddRange(names);
|
||
|
||
// 允许直接输入(支持编码模糊输入),并打开联想补全
|
||
cbxSearchRawName.DropDownStyle = ComboBoxStyle.DropDown;
|
||
cbxSearchRawName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
|
||
// 使用自定义自动完成源,加入“名称”和“编码”,便于输入编码模糊匹配
|
||
var ac = new AutoCompleteStringCollection();
|
||
try
|
||
{
|
||
if (ListRawCtrInfo != null)
|
||
{
|
||
var codes = ListRawCtrInfo
|
||
.Where(x => !string.IsNullOrWhiteSpace(x.RawCode))
|
||
.Select(x => x.RawCode!.Trim());
|
||
var texts = names.Concat(codes).Distinct().ToArray();
|
||
ac.AddRange(texts);
|
||
}
|
||
else
|
||
{
|
||
ac.AddRange(names);
|
||
}
|
||
}
|
||
catch { ac.AddRange(names); }
|
||
cbxSearchRawName.AutoCompleteCustomSource = ac;
|
||
cbxSearchRawName.AutoCompleteSource = AutoCompleteSource.CustomSource;
|
||
cbxSearchRawName.SelectedIndex = -1; // 默认不选中
|
||
}
|
||
finally
|
||
{
|
||
cbxSearchRawName.EndUpdate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 DataGridView 列头设置为中文显示。
|
||
/// </summary>
|
||
private void ApplyChineseColumnHeaders()
|
||
{
|
||
// 隐藏行头(左侧灰条)
|
||
dataGridView1.RowHeadersVisible = false;
|
||
void SetHeader(string name, string text)
|
||
{
|
||
var col = dataGridView1.Columns[name];
|
||
if (col != null) col.HeaderText = text;
|
||
}
|
||
|
||
// 移除编号列
|
||
var idCol = dataGridView1.Columns["Id"]; if (idCol != null) dataGridView1.Columns.Remove(idCol);
|
||
SetHeader("RawCode", "原料编号");
|
||
SetHeader("RawName", "原料名称");
|
||
SetHeader("InBagCode", "内袋二维码");
|
||
SetHeader("BoxCode", "外箱二维码");
|
||
SetHeader("Batch", "原料批号");
|
||
SetHeader("ShelfLife", "保质期");
|
||
SetHeader("Weight", "称重重量(g)");
|
||
SetHeader("RemainWeight", "剩余重量(Kg)");
|
||
SetHeader("StockWeight", "入库总重量(Kg)");
|
||
SetHeader("WeightTime", "称重时间");
|
||
SetHeader("OpUser", "操作员");
|
||
SetHeader("CheckUser", "确认者");
|
||
SetHeader("OutTime", "出库时间");
|
||
SetHeader("CreateTime", "创建时间");
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
private void frmMain_FormClosed(object sender, FormClosedEventArgs e)
|
||
{
|
||
//CurZebraPrint.Close();
|
||
|
||
if (_scaleTcp != null)
|
||
{
|
||
_scaleTcp!.StopAsync();
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|