668 lines
32 KiB
C#
668 lines
32 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Windows.Forms;
|
||
using Smart.CustomComponents;
|
||
|
||
namespace 成套报价软件
|
||
{
|
||
/// <summary>
|
||
/// 调母排 的交互逻辑(事件处理)。
|
||
/// UI 布局定义见 调母排.ui.cs。
|
||
///
|
||
/// 用法:主窗口报价页 button15「调母排」传入主窗口引用弹出。
|
||
/// 功能:
|
||
/// ① 打开时从主窗口母排页带出 当前铜价/当前铝价/铜加工费/铝加工费
|
||
/// ② advancedDataGridView1 汇总当前项目所有图号→箱柜→元件里的 铜排/铝排
|
||
/// (按规格识别材质:LMY 开头=铝排,其余(TMY 等)=铜排;类型=母排 的行也算)
|
||
/// ③ 「计算」:按 新表价 = (铜价或铝价)×每米重量 + 对应加工费 重算表格的
|
||
/// "表价(更改后)"列;铜排铝排各自用对应价格与加工费
|
||
/// ④ 「更新到项目」:把"表价(更改后)"写回所有元件表(表价+派生列 SQL 重算),
|
||
/// 并通知主窗口刷新报价页
|
||
/// </summary>
|
||
public partial class 调母排
|
||
{
|
||
// ===== 主窗口引用与上下文 =====
|
||
|
||
/// <summary>主窗口引用(取母排页价格/加工费,更新后刷新报价页)。</summary>
|
||
private MainForm _main = null;
|
||
|
||
/// <summary>当前项目编号。</summary>
|
||
private string _projectId = "";
|
||
|
||
/// <summary>汇总表内存数据(元件名称/规格/单位/表价更改前/表价更改后)。</summary>
|
||
private DataTable _table = null;
|
||
|
||
/// <summary>每行对应元件表名+行序号,更新到项目时定位用。</summary>
|
||
private List<string> _rowKeys = new List<string>();
|
||
|
||
/// <summary>构造函数(独立运行测试用)。</summary>
|
||
public 调母排()
|
||
{
|
||
InitializeComponent();
|
||
// 事件绑定(运行时绑,不动 ui.cs)
|
||
this.button1.Click += button1_Click; // 计算
|
||
this.button2.Click += button2_Click; // 更新到项目
|
||
this.button3.Click += button3_Click; // 退出
|
||
|
||
// ★ 加工费联动(与 tabPage12 母排页 Recalc铜价PerKg 同规则):
|
||
// 价格框显示 = (金属价/吨 + 加工费/吨) / 1000 → 元/kg
|
||
// 改加工费时自动把价格框重算成"金属价+加工费"合价
|
||
this.textBox1加工费1.TextChanged += (s, e) => Recalc铜价框(false);
|
||
this.textBox1加工费2.TextChanged += (s, e) => Recalc铜价框(true);
|
||
}
|
||
|
||
/// <summary>带出价格时记录的"纯金属价(元/kg)"(未含加工费),联动重算用。</summary>
|
||
private decimal _纯铜价Kg = 0m;
|
||
private decimal _纯铝价Kg = 0m;
|
||
|
||
/// <summary>
|
||
/// 加工费变化 → 重算对应价格框 = 纯金属价(kg) + 加工费(元/吨)/1000。
|
||
/// (加工费单位与价格一致:元/吨,除以 1000 变元/kg)
|
||
/// 与 tabPage12 的 Recalc铜价PerKg 公式相同。
|
||
/// </summary>
|
||
private void Recalc铜价框(bool 铝)
|
||
{
|
||
decimal 纯价 = 铝 ? _纯铝价Kg : _纯铜价Kg;
|
||
if (纯价 <= 0m) return; // 还没带出过价格,不动
|
||
decimal 费吨 = ParseDec(铝 ? this.textBox1加工费2.Text : this.textBox1加工费1.Text);
|
||
decimal 合价 = 纯价 + 费吨 / 1000m;
|
||
if (铝) this.textBox2当前铝价.Text = Math.Round(合价, 2).ToString("0.##");
|
||
else this.textBox1当前铜价.Text = Math.Round(合价, 2).ToString("0.##");
|
||
}
|
||
|
||
/// <summary>主窗口调用:传主窗口引用,打开时自动带价格并汇总。</summary>
|
||
public 调母排(MainForm main) : this()
|
||
{
|
||
_main = main;
|
||
}
|
||
|
||
/// <summary>窗体加载:先落盘+汇总母排,再自动抓取铜价/铝价填入。</summary>
|
||
protected override void OnLoad(EventArgs e)
|
||
{
|
||
base.OnLoad(e);
|
||
try
|
||
{
|
||
// ① 主窗口内存里未保存的元件编辑先落盘(本窗口直接读库,必须看到最新数据)
|
||
if (_main != null) _main.FlushQuoteTablesToDb();
|
||
|
||
// ② 加工费带出
|
||
this.textBox1加工费1.Text = _main != null ? _main.Get母排加工费(false) : "";
|
||
this.textBox1加工费2.Text = _main != null ? _main.Get母排加工费(true) : "";
|
||
|
||
// ③ 汇总整个项目的铜排/铝排
|
||
LoadSummary();
|
||
|
||
// ④ 自动抓取铜价+铝价(异步,抓到自动填入文本框)
|
||
FetchMetalPrices();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(this, "初始化失败:\n" + ex.Message, "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// 数据路径与工具
|
||
// ============================================================
|
||
|
||
/// <summary>主窗口实例上的当前项目编号(通过公共方式取,不依赖私有字段)。</summary>
|
||
private string ProjectId
|
||
{
|
||
get
|
||
{
|
||
if (!string.IsNullOrEmpty(_projectId)) return _projectId;
|
||
if (_main != null) _projectId = _main.GetCurrentProjectId();
|
||
return _projectId ?? "";
|
||
}
|
||
}
|
||
|
||
/// <summary>当前项目报价库路径(与主窗口同规则)。</summary>
|
||
private string QuoteDbPath
|
||
{
|
||
get
|
||
{
|
||
string safe = ProjectId;
|
||
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) safe = safe.Replace(c, '_');
|
||
return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
|
||
@"sjk\bjb\项目" + safe + ".db");
|
||
}
|
||
}
|
||
|
||
private static string ConnStr(string dbPath) { return "Data Source=" + dbPath + ";Version=3;"; }
|
||
|
||
private static decimal ParseDec(string s)
|
||
{
|
||
decimal d;
|
||
if (decimal.TryParse((s ?? "").Trim(), out d)) return d;
|
||
return 0m;
|
||
}
|
||
|
||
// ============================================================
|
||
// ① 价格来源:读配置文件缓存(主窗口母排页抓价时写入),不发网络请求
|
||
// ============================================================
|
||
|
||
/// <summary>
|
||
/// 从 shezhi\铜排参数.config.xml / 铝排参数.config.xml 读缓存的铜价/铝价(元/吨),
|
||
/// 加上加工费后换算成 元/kg 填入文本框(与 tabPage12 母排页口径一致:
|
||
/// 价格框显示的就是"金属价+加工费"的合价)。
|
||
/// ★ 缓存缺失的品种自动联网补抓(调母排自己抓,不依赖主窗口切换材质刷新过)。
|
||
/// </summary>
|
||
private void FetchMetalPrices()
|
||
{
|
||
string[] 铜缓存 = _main != null ? _main.Get母排价格缓存(false) : new string[] { "", "" };
|
||
string[] 铝缓存 = _main != null ? _main.Get母排价格缓存(true) : new string[] { "", "" };
|
||
|
||
decimal 铜吨 = ParseDec(铜缓存[0]);
|
||
decimal 铝吨 = ParseDec(铝缓存[0]);
|
||
|
||
// 缓存缺哪个,退回主窗口当前文本框值(材质对得上才用)
|
||
if (铜吨 <= 0m && _main != null && !_main.Get母排是铝排())
|
||
铜吨 = ParseDec(_main.Get母排当前价格文本());
|
||
if (铝吨 <= 0m && _main != null && _main.Get母排是铝排())
|
||
铝吨 = ParseDec(_main.Get母排当前价格文本());
|
||
|
||
// ★ 还有缺的 → 后台联网补抓(如铝价:主窗口一直在铜排模式,缓存里就没有铝价)
|
||
if (铜吨 <= 0m || 铝吨 <= 0m)
|
||
{
|
||
FetchMetalPricesOnline(铜吨 <= 0m, 铝吨 <= 0m);
|
||
}
|
||
|
||
// ★ 价格框 = (金属价/吨 + 加工费/吨) / 1000 = 金属价kg + 加工费kg
|
||
decimal 铜费吨 = ParseDec(this.textBox1加工费1.Text);
|
||
decimal 铝费吨 = ParseDec(this.textBox1加工费2.Text);
|
||
|
||
if (铜吨 > 0m)
|
||
{
|
||
_纯铜价Kg = Math.Round(铜吨 / 1000m, 2);
|
||
this.textBox1当前铜价.Text = Math.Round(_纯铜价Kg + 铜费吨 / 1000m, 2).ToString("0.##");
|
||
}
|
||
if (铝吨 > 0m)
|
||
{
|
||
_纯铝价Kg = Math.Round(铝吨 / 1000m, 2);
|
||
this.textBox2当前铝价.Text = Math.Round(_纯铝价Kg + 铝费吨 / 1000m, 2).ToString("0.##");
|
||
}
|
||
|
||
// 标题栏标注价格日期(铜/铝各自缓存的)
|
||
string 铜日 = 铜缓存[1];
|
||
string 铝日 = 铝缓存[1];
|
||
List<string> parts = new List<string>();
|
||
if (铜吨 > 0m) parts.Add("铜价日期:" + (string.IsNullOrEmpty(铜日) ? "?" : 铜日));
|
||
if (铝吨 > 0m) parts.Add("铝价日期:" + (string.IsNullOrEmpty(铝日) ? "?" : 铝日));
|
||
this.Text = "调母排" +
|
||
(parts.Count > 0 ? "(" + string.Join(" ", parts.ToArray()) + ")"
|
||
: "(获取价格中…)");
|
||
}
|
||
|
||
/// <summary>长江有色数据接口(与主窗口母排页一致)。</summary>
|
||
private const string 价格接口 = "https://www.ccmn.cn/shop/historyData/getCjysAndCjysw";
|
||
|
||
/// <summary>接口 AES 密钥(与主窗口一致)。</summary>
|
||
private const string AES密钥 = "ccmnCjysw1455881";
|
||
|
||
/// <summary>
|
||
/// 后台联网抓缺失的金属价(need铜/need铝 指明缺哪个,至少一个为 true),
|
||
/// 抓到后:①写入配置缓存(下次不用再抓) ②回 UI 线程填入价格框(含加工费合价)。
|
||
/// 失败则标题栏提示手填。
|
||
/// </summary>
|
||
private void FetchMetalPricesOnline(bool need铜, bool need铝)
|
||
{
|
||
System.Threading.Tasks.Task.Factory.StartNew(() =>
|
||
{
|
||
string 铜吨 = "", 铝吨 = "", 日期 = "";
|
||
try
|
||
{
|
||
System.Net.ServicePointManager.SecurityProtocol =
|
||
(System.Net.SecurityProtocolType)3072; // Tls12
|
||
string json;
|
||
using (var wc = new System.Net.WebClient())
|
||
{
|
||
wc.Encoding = System.Text.Encoding.UTF8;
|
||
wc.Headers[System.Net.HttpRequestHeader.UserAgent] =
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
||
wc.Headers[System.Net.HttpRequestHeader.Referer] = "https://www.ccmn.cn/";
|
||
json = wc.DownloadString(价格接口);
|
||
}
|
||
|
||
string showPriceList = 取JSON字段(json, "showPriceList");
|
||
if (!string.IsNullOrEmpty(showPriceList))
|
||
{
|
||
string plain = AES解密(showPriceList);
|
||
foreach (System.Text.RegularExpressions.Match m in
|
||
System.Text.RegularExpressions.Regex.Matches(plain, @"\{[^}]*\}"))
|
||
{
|
||
string obj = m.Value;
|
||
string 名称 = 取JSON字段(obj, "productSortName");
|
||
var 均价m = System.Text.RegularExpressions.Regex.Match(obj,
|
||
@"""avgPrice""\s*:\s*([0-9]+\.?[0-9]*)");
|
||
if (!均价m.Success) continue;
|
||
if (名称 == "1#铜" && need铜) { 铜吨 = 均价m.Groups[1].Value; 日期 = 取JSON字段(obj, "publishDate"); }
|
||
else if (名称 == "A00铝" && need铝) { 铝吨 = 均价m.Groups[1].Value; }
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
// 回 UI 线程填入
|
||
if (this.IsHandleCreated && !this.IsDisposed)
|
||
{
|
||
try
|
||
{
|
||
this.BeginInvoke(new System.Action(() =>
|
||
{
|
||
decimal 铜费吨 = ParseDec(this.textBox1加工费1.Text);
|
||
decimal 铝费吨 = ParseDec(this.textBox1加工费2.Text);
|
||
|
||
decimal d铜 = ParseDec(铜吨);
|
||
if (d铜 > 0m)
|
||
{
|
||
// 写入配置缓存(下次打开直接读,不再联网)
|
||
if (_main != null) _main.Save母排价格缓存Pub(false, 铜吨, 日期);
|
||
_纯铜价Kg = Math.Round(d铜 / 1000m, 2);
|
||
this.textBox1当前铜价.Text = Math.Round(_纯铜价Kg + 铜费吨 / 1000m, 2).ToString("0.##");
|
||
}
|
||
decimal d铝 = ParseDec(铝吨);
|
||
if (d铝 > 0m)
|
||
{
|
||
if (_main != null) _main.Save母排价格缓存Pub(true, 铝吨, 日期);
|
||
_纯铝价Kg = Math.Round(d铝 / 1000m, 2);
|
||
this.textBox2当前铝价.Text = Math.Round(_纯铝价Kg + 铝费吨 / 1000m, 2).ToString("0.##");
|
||
}
|
||
|
||
List<string> parts = new List<string>();
|
||
if (ParseDec(this.textBox1当前铜价.Text) > 0m) parts.Add("铜价OK");
|
||
if (ParseDec(this.textBox2当前铝价.Text) > 0m) parts.Add("铝价OK");
|
||
this.Text = "调母排" +
|
||
(parts.Count == 2 ? "(价格已就绪)" :
|
||
parts.Count > 0 ? "(" + string.Join(" ", parts.ToArray()) + " 另一价请手填)"
|
||
: "(价格获取失败,请手填)");
|
||
}));
|
||
}
|
||
catch { }
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>AES-ECB(PKCS7) 解密接口 Base64 密文(与主窗口铜价AES解密一致)。</summary>
|
||
private static string AES解密(string 加密Base64)
|
||
{
|
||
byte[] 密钥 = System.Text.Encoding.UTF8.GetBytes(AES密钥);
|
||
byte[] 密文 = System.Convert.FromBase64String(加密Base64);
|
||
using (var aes = System.Security.Cryptography.Aes.Create())
|
||
{
|
||
aes.Mode = System.Security.Cryptography.CipherMode.ECB;
|
||
aes.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
|
||
aes.Key = 密钥;
|
||
using (var 解密器 = aes.CreateDecryptor())
|
||
{
|
||
byte[] 明文 = 解密器.TransformFinalBlock(密文, 0, 密文.Length);
|
||
return System.Text.Encoding.UTF8.GetString(明文);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>从简单 JSON 取字符串字段(与MainForm 取JSON字段 一致)。</summary>
|
||
private static string 取JSON字段(string json, string key)
|
||
{
|
||
if (string.IsNullOrEmpty(json)) return "";
|
||
var m = System.Text.RegularExpressions.Regex.Match(json,
|
||
"\"" + System.Text.RegularExpressions.Regex.Escape(key) + "\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
|
||
if (!m.Success) return "";
|
||
return m.Groups[1].Value.Replace("\\\"", "\"").Replace("\\\\", "\\").Replace("\\/", "/");
|
||
}
|
||
|
||
// ============================================================
|
||
// ② 汇总当前项目所有铜排/铝排元件
|
||
// ============================================================
|
||
|
||
/// <summary>汇总表列定义。</summary>
|
||
private static readonly string[] SummaryCols =
|
||
new string[] { "元件名称", "规格", "单位", "表价(更改前)", "表价(更改后)" };
|
||
|
||
/// <summary>按规格判断材质:true=铝排,false=铜排。LMY 前缀=铝,其余(TMY/裸规格)=铜。</summary>
|
||
private static bool Is铝排(string 规格)
|
||
{
|
||
string s = (规格 ?? "").Trim().ToUpperInvariant();
|
||
return s.StartsWith("LMY") || s.StartsWith("L") && s.IndexOf('-') > 0 && s.Substring(0, s.IndexOf('-')) == "LMY";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析规格得到 层数/宽度/厚度(与MainForm TryParse铜排规格 同规则):
|
||
/// 去掉 "TMY-"/"LMY-" 前缀;"2(60×6)"→2/60/6;"60×6"→1/60/6。
|
||
/// </summary>
|
||
private static bool TryParse规格(string 规格, out int 层数, out decimal 宽, out decimal 厚)
|
||
{
|
||
层数 = 1; 宽 = 0m; 厚 = 0m;
|
||
if (string.IsNullOrWhiteSpace(规格)) return false;
|
||
string s = 规格.Trim();
|
||
int dash = s.IndexOf('-');
|
||
if (dash >= 0 && dash < s.Length - 1) s = s.Substring(dash + 1).Trim();
|
||
|
||
var m = System.Text.RegularExpressions.Regex.Match(s,
|
||
@"^(\d+)\s*\(\s*(\d+(?:\.\d+)?)\s*[×xX\*]\s*(\d+(?:\.\d+)?)\s*\)");
|
||
if (m.Success)
|
||
{
|
||
if (!int.TryParse(m.Groups[1].Value, out 层数)) 层数 = 1;
|
||
宽 = ParseDec(m.Groups[2].Value);
|
||
厚 = ParseDec(m.Groups[3].Value);
|
||
return 宽 > 0 && 厚 > 0;
|
||
}
|
||
m = System.Text.RegularExpressions.Regex.Match(s,
|
||
@"^(\d+(?:\.\d+)?)\s*[×xX\*]\s*(\d+(?:\.\d+)?)");
|
||
if (m.Success)
|
||
{
|
||
层数 = 1;
|
||
宽 = ParseDec(m.Groups[1].Value);
|
||
厚 = ParseDec(m.Groups[2].Value);
|
||
return 宽 > 0 && 厚 > 0;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>汇总当前项目所有元件表里的铜排/铝排行到表格(按 名称+规格 聚合数量列不做,逐行显示)。</summary>
|
||
private void LoadSummary()
|
||
{
|
||
_rowKeys.Clear();
|
||
_table = new DataTable();
|
||
foreach (string c in SummaryCols) _table.Columns.Add(c, typeof(string));
|
||
_table.Columns.Add("__表名", typeof(string)); // 内部:元件真实表名 yj_N
|
||
_table.Columns.Add("__序号", typeof(string)); // 内部:行序号
|
||
_table.Columns.Add("__铝排", typeof(bool)); // 内部:是否铝排
|
||
|
||
string dbPath = QuoteDbPath;
|
||
if (!System.IO.File.Exists(dbPath))
|
||
{
|
||
MessageBox.Show(this, "当前项目报价库不存在。", "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
BindSummary();
|
||
return;
|
||
}
|
||
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(dbPath)))
|
||
{
|
||
conn.Open();
|
||
|
||
// 所有元件表(表索引 类型=元件)
|
||
var yjTables = new List<string>();
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT DISTINCT 实际表名 FROM 表索引 WHERE 类型='元件';", conn))
|
||
using (var rd = cmd.ExecuteReader())
|
||
{
|
||
while (rd.Read())
|
||
{
|
||
string t = rd.IsDBNull(0) ? "" : rd.GetString(0);
|
||
if (!string.IsNullOrEmpty(t)) yjTables.Add(t);
|
||
}
|
||
}
|
||
_表数 = yjTables.Count;
|
||
|
||
foreach (string tbl in yjTables)
|
||
{
|
||
try
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 序号, 元件名称, 规格, 单位, 表价, 类型 FROM \"" + tbl + "\";", conn))
|
||
using (var rd = cmd.ExecuteReader())
|
||
{
|
||
while (rd.Read())
|
||
{
|
||
// ★ 注意:表价列在库里是 REAL 类型(浮点),不能用 GetString,
|
||
// 否则抛 InvalidCastException 被 catch 吞掉 → 一行都读不出来!
|
||
// 统一用 GetValue+ToString,TEXT/REAL 通吃。
|
||
string 序号 = rd.IsDBNull(0) ? "" : rd.GetValue(0).ToString();
|
||
string 名称 = rd.IsDBNull(1) ? "" : rd.GetValue(1).ToString();
|
||
string 规格 = rd.IsDBNull(2) ? "" : rd.GetValue(2).ToString();
|
||
string 单位 = rd.IsDBNull(3) ? "" : rd.GetValue(3).ToString();
|
||
string 表价 = rd.IsDBNull(4) ? "" : rd.GetValue(4).ToString();
|
||
string 类型 = rd.IsDBNull(5) ? "" : rd.GetValue(5).ToString();
|
||
|
||
// ★ 唯一识别条件:类型字段 = 母排(严格按库里数据,不做任何猜测)
|
||
if ((类型 ?? "").Trim() != "母排") continue;
|
||
|
||
// 材质按规格前缀:LMY=铝排,其余(TMY等)=铜排
|
||
bool 铝 = Is铝排(规格);
|
||
var row = _table.NewRow();
|
||
row["元件名称"] = 名称;
|
||
row["规格"] = 规格;
|
||
row["单位"] = 单位;
|
||
row["表价(更改前)"] = 表价;
|
||
row["表价(更改后)"] = 表价; // 默认等于更改前,等「计算」刷新
|
||
row["__表名"] = tbl;
|
||
row["__序号"] = 序号;
|
||
row["__铝排"] = 铝;
|
||
_table.Rows.Add(row);
|
||
_rowKeys.Add(tbl + "#" + 序号);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception exTbl)
|
||
{
|
||
// 单表读失败不中断其他表,但要留日志(之前静默吞掉表价REAL类型异常吃过亏)
|
||
try
|
||
{
|
||
System.IO.File.AppendAllText(
|
||
System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"logs\调母排.log"),
|
||
"[" + DateTime.Now.ToString("HH:mm:ss.fff") + "] 读表失败 " + tbl + ": " + exTbl.Message + "\r\n");
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
}
|
||
|
||
BindSummary();
|
||
|
||
// 诊断日志:确认读到了几个表/几行(排查"打开窗口没数据")
|
||
try
|
||
{
|
||
System.IO.File.AppendAllText(
|
||
System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"logs\调母排.log"),
|
||
"[" + DateTime.Now.ToString("HH:mm:ss.fff") + "] 项目=" + ProjectId +
|
||
" 元件表数=" + _表数 + " 母排行数=" + _table.Rows.Count + "\r\n");
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
/// <summary>本次汇总扫过的元件表数量(诊断用)。</summary>
|
||
private int _表数 = 0;
|
||
|
||
/// <summary>把内存表绑定到表格(只显示 5 个业务列)。</summary>
|
||
private void BindSummary()
|
||
{
|
||
var view = new DataTable();
|
||
foreach (string c in SummaryCols) view.Columns.Add(c, typeof(string));
|
||
foreach (DataRow r in _table.Rows)
|
||
{
|
||
var nr = view.NewRow();
|
||
foreach (string c in SummaryCols) nr[c] = r[c] ?? "";
|
||
view.Rows.Add(nr);
|
||
}
|
||
|
||
// ★ 设计器手工加的 5 列(Name=Column1..5)没设 DataPropertyName,
|
||
// 直接绑 DataSource 匹配不上会显示不出数据——按表头顺序接上数据列。
|
||
var grid = this.advancedDataGridView1;
|
||
grid.AutoGenerateColumns = false;
|
||
if (grid.Columns.Count >= SummaryCols.Length)
|
||
{
|
||
for (int i = 0; i < SummaryCols.Length; i++)
|
||
{
|
||
grid.Columns[i].DataPropertyName = SummaryCols[i];
|
||
}
|
||
}
|
||
grid.DataSource = view;
|
||
}
|
||
|
||
// ============================================================
|
||
// ③ 计算:按当前价格+加工费重算"表价(更改后)"
|
||
// ============================================================
|
||
|
||
/// <summary>
|
||
/// button1「计算」(与 tabPage12 母排页同规则):
|
||
/// 价格框已是"金属价+加工费"合价(元/kg)
|
||
/// 新表价 = round( 合价(元/kg) × 每米重量(kg/m), 0 )
|
||
/// 每米重量 = 层数 × 宽 × 厚 × 密度 / 1000 (铜 8.9 / 铝 2.7)
|
||
/// —— 即母排页 Recalc单价PerMeter 的批量版,加工费不重复加。
|
||
/// 复选框可只算一种材质。
|
||
/// </summary>
|
||
private void button1_Click(object sender, EventArgs e)
|
||
{
|
||
if (_table == null || _table.Rows.Count == 0)
|
||
{
|
||
MessageBox.Show(this, "没有可计算的铜排/铝排数据。", "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
|
||
decimal 铜价 = ParseDec(this.textBox1当前铜价.Text);
|
||
decimal 铝价 = ParseDec(this.textBox2当前铝价.Text);
|
||
|
||
int 算铜 = 0, 算铝 = 0, 跳过 = 0;
|
||
foreach (DataRow r in _table.Rows)
|
||
{
|
||
bool 铝 = (bool)r["__铝排"];
|
||
|
||
// 复选框:只算勾选的材质(两个都没勾=都算)
|
||
if (this.checkBox1.Checked && !this.checkBox2.Checked && 铝) { 跳过++; continue; }
|
||
if (this.checkBox2.Checked && !this.checkBox1.Checked && !铝) { 跳过++; continue; }
|
||
|
||
decimal 价 = 铝 ? 铝价 : 铜价; // 已含加工费的合价
|
||
if (价 <= 0m) { 跳过++; continue; } // 价格没填
|
||
|
||
int 层; decimal 宽, 厚;
|
||
if (!TryParse规格((r["规格"] ?? "").ToString(), out 层, out 宽, out 厚)) { 跳过++; continue; }
|
||
|
||
decimal 密度 = 铝 ? 2.7m : 8.9m;
|
||
decimal 每米重量 = 层 * 宽 * 厚 * 密度 / 1000m;
|
||
decimal 新表价 = Math.Round(价 * 每米重量, 0, MidpointRounding.AwayFromZero);
|
||
r["表价(更改后)"] = 新表价.ToString("0");
|
||
|
||
if (铝) 算铝++; else 算铜++;
|
||
}
|
||
|
||
BindSummary();
|
||
MessageBox.Show(this,
|
||
"计算完成!\n\n铜排 " + 算铜 + " 行,铝排 " + 算铝 + " 行" +
|
||
(跳过 > 0 ? "\n跳过 " + 跳过 + " 行(价格未填/规格无法解析/材质未勾选)" : ""),
|
||
"调母排", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
}
|
||
|
||
// ============================================================
|
||
// ④ 更新到项目
|
||
// ============================================================
|
||
|
||
/// <summary>
|
||
/// button2「更新到项目」:把"表价(更改后)"写回所有元件表,
|
||
/// 派生列(采购单价/采购价/报出单价/报出价)用 SQL 表达式按每行自己的数量与系数重算,
|
||
/// 然后通知主窗口清缓存刷新报价页并全量重算。
|
||
/// </summary>
|
||
private void button2_Click(object sender, EventArgs e)
|
||
{
|
||
if (_table == null || _table.Rows.Count == 0)
|
||
{
|
||
MessageBox.Show(this, "没有可更新的数据。", "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
|
||
// 统计有变化的行
|
||
int 有变化 = 0, 相同 = 0;
|
||
foreach (DataRow r in _table.Rows)
|
||
{
|
||
string 前 = (r["表价(更改前)"] ?? "").ToString().Trim();
|
||
string 后 = (r["表价(更改后)"] ?? "").ToString().Trim();
|
||
decimal d前, d后;
|
||
decimal.TryParse(前, out d前);
|
||
decimal.TryParse(后, out d后);
|
||
if (d后 != d前) 有变化++; else 相同++;
|
||
}
|
||
if (有变化 == 0)
|
||
{
|
||
MessageBox.Show(this, "没有价格变化(表价更改后=更改前),无需更新。\n请先点「计算」。", "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
|
||
if (MessageBox.Show(this,
|
||
"将把 " + 有变化 + " 条铜排/铝排的新表价更新到整个项目,\n" +
|
||
"并按各行的数量/系数自动重算派生列与汇总。\n\n确定更新吗?",
|
||
"更新到项目", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
|
||
MessageBoxDefaultButton.Button2) != DialogResult.Yes) return;
|
||
|
||
string dbPath = QuoteDbPath;
|
||
int 更新行 = 0;
|
||
try
|
||
{
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(ConnStr(dbPath)))
|
||
{
|
||
conn.Open();
|
||
using (var tx = conn.BeginTransaction())
|
||
{
|
||
try
|
||
{
|
||
foreach (DataRow r in _table.Rows)
|
||
{
|
||
string 前 = (r["表价(更改前)"] ?? "").ToString().Trim();
|
||
string 后 = (r["表价(更改后)"] ?? "").ToString().Trim();
|
||
decimal d前, d后;
|
||
decimal.TryParse(前, out d前);
|
||
decimal.TryParse(后, out d后);
|
||
if (d后 == d前) continue; // 无变化不写
|
||
|
||
string tbl = (string)r["__表名"];
|
||
string 序号 = (string)r["__序号"];
|
||
|
||
// 表价 + 派生列一起更新(与 RecalcYuanjianRow 公式一致):
|
||
// 采购单价 = 表价 × 采购系数
|
||
// 采购价 = 采购单价 × 数量
|
||
// 报出单价 = 采购单价 × 报出系数
|
||
// 报出价 = 报出单价 × 数量
|
||
string sql = "UPDATE \"" + tbl.Replace("\"", "") + "\" SET " +
|
||
"表价 = @bj, " +
|
||
"采购单价 = @bj * CAST(采购系数 AS REAL), " +
|
||
"采购价 = (@bj * CAST(采购系数 AS REAL)) * CAST(数量 AS REAL), " +
|
||
"报出单价 = (@bj * CAST(采购系数 AS REAL)) * CAST(报出系数 AS REAL), " +
|
||
"报出价 = ((@bj * CAST(采购系数 AS REAL)) * CAST(报出系数 AS REAL)) * CAST(数量 AS REAL) " +
|
||
"WHERE 序号 = @xh;";
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@bj", d后);
|
||
cmd.Parameters.AddWithValue("@xh", 序号);
|
||
更新行 += cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
tx.Commit();
|
||
}
|
||
catch
|
||
{
|
||
try { tx.Rollback(); } catch { }
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 同步"更改前"=新值(可再次计算而不重复更新)
|
||
foreach (DataRow r in _table.Rows) r["表价(更改前)"] = r["表价(更改后)"];
|
||
BindSummary();
|
||
|
||
// 通知主窗口:清缓存 + 全量重算 + 刷新报价页
|
||
if (_main != null) _main.RefreshAfter母排调价();
|
||
|
||
MessageBox.Show(this,
|
||
"更新完成!\n\n共更新 " + 更新行 + " 条元件表价,\n项目汇总已自动重算。",
|
||
"调母排", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(this, "更新失败:\n" + ex.Message, "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>button3「退出」:关闭窗口。</summary>
|
||
private void button3_Click(object sender, EventArgs e)
|
||
{
|
||
this.Close();
|
||
}
|
||
}
|
||
}
|