using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
using Smart.CustomComponents;
namespace 成套报价软件
{
///
/// 调母排 的交互逻辑(事件处理)。
/// UI 布局定义见 调母排.ui.cs。
///
/// 用法:主窗口报价页 button15「调母排」传入主窗口引用弹出。
/// 功能:
/// ① 打开时从主窗口母排页带出 当前铜价/当前铝价/铜加工费/铝加工费
/// ② advancedDataGridView1 汇总当前项目所有图号→箱柜→元件里的 铜排/铝排
/// (按规格识别材质:LMY 开头=铝排,其余(TMY 等)=铜排;类型=母排 的行也算)
/// ③ 「计算」:按 新表价 = (铜价或铝价)×每米重量 + 对应加工费 重算表格的
/// "表价(更改后)"列;铜排铝排各自用对应价格与加工费
/// ④ 「更新到项目」:把"表价(更改后)"写回所有元件表(表价+派生列 SQL 重算),
/// 并通知主窗口刷新报价页
///
public partial class 调母排
{
// ===== 主窗口引用与上下文 =====
/// 主窗口引用(取母排页价格/加工费,更新后刷新报价页)。
private MainForm _main = null;
/// 当前项目编号。
private string _projectId = "";
/// 汇总表内存数据(元件名称/规格/单位/表价更改前/表价更改后)。
private DataTable _table = null;
/// 每行对应元件表名+行序号,更新到项目时定位用。
private List _rowKeys = new List();
/// 构造函数(独立运行测试用)。
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);
}
/// 带出价格时记录的"纯金属价(元/kg)"(未含加工费),联动重算用。
private decimal _纯铜价Kg = 0m;
private decimal _纯铝价Kg = 0m;
///
/// 加工费变化 → 重算对应价格框 = 纯金属价(kg) + 加工费(元/吨)/1000。
/// (加工费单位与价格一致:元/吨,除以 1000 变元/kg)
/// 与 tabPage12 的 Recalc铜价PerKg 公式相同。
///
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.##");
}
/// 主窗口调用:传主窗口引用,打开时自动带价格并汇总。
public 调母排(MainForm main) : this()
{
_main = main;
}
/// 窗体加载:先落盘+汇总母排,再自动抓取铜价/铝价填入。
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);
}
}
// ============================================================
// 数据路径与工具
// ============================================================
/// 主窗口实例上的当前项目编号(通过公共方式取,不依赖私有字段)。
private string ProjectId
{
get
{
if (!string.IsNullOrEmpty(_projectId)) return _projectId;
if (_main != null) _projectId = _main.GetCurrentProjectId();
return _projectId ?? "";
}
}
/// 当前项目报价库路径(与主窗口同规则)。
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;
}
// ============================================================
// ① 价格来源:读配置文件缓存(主窗口母排页抓价时写入),不发网络请求
// ============================================================
///
/// 从 shezhi\铜排参数.config.xml / 铝排参数.config.xml 读缓存的铜价/铝价(元/吨),
/// 加上加工费后换算成 元/kg 填入文本框(与 tabPage12 母排页口径一致:
/// 价格框显示的就是"金属价+加工费"的合价)。
/// ★ 缓存缺失的品种自动联网补抓(调母排自己抓,不依赖主窗口切换材质刷新过)。
///
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 parts = new List();
if (铜吨 > 0m) parts.Add("铜价日期:" + (string.IsNullOrEmpty(铜日) ? "?" : 铜日));
if (铝吨 > 0m) parts.Add("铝价日期:" + (string.IsNullOrEmpty(铝日) ? "?" : 铝日));
this.Text = "调母排" +
(parts.Count > 0 ? "(" + string.Join(" ", parts.ToArray()) + ")"
: "(获取价格中…)");
}
/// 长江有色数据接口(与主窗口母排页一致)。
private const string 价格接口 = "https://www.ccmn.cn/shop/historyData/getCjysAndCjysw";
/// 接口 AES 密钥(与主窗口一致)。
private const string AES密钥 = "ccmnCjysw1455881";
///
/// 后台联网抓缺失的金属价(need铜/need铝 指明缺哪个,至少一个为 true),
/// 抓到后:①写入配置缓存(下次不用再抓) ②回 UI 线程填入价格框(含加工费合价)。
/// 失败则标题栏提示手填。
///
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 parts = new List();
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 { }
}
});
}
/// AES-ECB(PKCS7) 解密接口 Base64 密文(与主窗口铜价AES解密一致)。
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(明文);
}
}
}
/// 从简单 JSON 取字符串字段(与MainForm 取JSON字段 一致)。
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("\\/", "/");
}
// ============================================================
// ② 汇总当前项目所有铜排/铝排元件
// ============================================================
/// 汇总表列定义。
private static readonly string[] SummaryCols =
new string[] { "元件名称", "规格", "单位", "表价(更改前)", "表价(更改后)" };
/// 按规格判断材质:true=铝排,false=铜排。LMY 前缀=铝,其余(TMY/裸规格)=铜。
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";
}
///
/// 解析规格得到 层数/宽度/厚度(与MainForm TryParse铜排规格 同规则):
/// 去掉 "TMY-"/"LMY-" 前缀;"2(60×6)"→2/60/6;"60×6"→1/60/6。
///
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;
}
/// 汇总当前项目所有元件表里的铜排/铝排行到表格(按 名称+规格 聚合数量列不做,逐行显示)。
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();
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 { }
}
/// 本次汇总扫过的元件表数量(诊断用)。
private int _表数 = 0;
/// 把内存表绑定到表格(只显示 5 个业务列)。
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;
}
// ============================================================
// ③ 计算:按当前价格+加工费重算"表价(更改后)"
// ============================================================
///
/// button1「计算」(与 tabPage12 母排页同规则):
/// 价格框已是"金属价+加工费"合价(元/kg)
/// 新表价 = round( 合价(元/kg) × 每米重量(kg/m), 0 )
/// 每米重量 = 层数 × 宽 × 厚 × 密度 / 1000 (铜 8.9 / 铝 2.7)
/// —— 即母排页 Recalc单价PerMeter 的批量版,加工费不重复加。
/// 复选框可只算一种材质。
///
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);
}
// ============================================================
// ④ 更新到项目
// ============================================================
///
/// button2「更新到项目」:把"表价(更改后)"写回所有元件表,
/// 派生列(采购单价/采购价/报出单价/报出价)用 SQL 表达式按每行自己的数量与系数重算,
/// 然后通知主窗口清缓存刷新报价页并全量重算。
///
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);
}
}
/// button3「退出」:关闭窗口。
private void button3_Click(object sender, EventArgs e)
{
this.Close();
}
}
}