Files
ctbjrj/成套报价软件/xiangmu/调母排.cs
T

668 lines
32 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using 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();
}
}
}