1025 lines
44 KiB
C#
1025 lines
44 KiB
C#
using System;
|
||
using System.Data;
|
||
using System.IO;
|
||
using System.Windows.Forms;
|
||
using WeifenLuo.WinFormsUI.Docking;
|
||
|
||
namespace 成套报价软件
|
||
{
|
||
/// <summary>
|
||
/// 停靠窗口:方案(储存报价方案)。
|
||
/// 项目页 = 方案列表(sjk\方案库\ 下每个 .db 文件 = 一个方案,即一份完整项目库副本);
|
||
/// 双击/右键打开 → 报价页三表从该方案文件加载(只读浏览)。
|
||
/// 与主窗口项目模型一致:一个方案一个库文件,天然隔离。
|
||
/// </summary>
|
||
public partial class DockWindow8 : DockContent
|
||
{
|
||
private const string 图号表名 = "advancedDataGridView9Table";
|
||
private string _当前方案文件 = "";
|
||
private int _联动层 = 0; // 联动加载深度(防嵌套重入)
|
||
|
||
public DockWindow8()
|
||
{
|
||
InitializeComponent();
|
||
this.BackColor = System.Drawing.Color.White;
|
||
|
||
// ===== 项目页(绑定模式,方案列表) =====
|
||
advancedDataGridView6.AutoGenerateColumns = false;
|
||
advancedDataGridView6.Dock = DockStyle.Fill; // 项目页网格填满页签
|
||
|
||
// ===== 报价页三表:★ 与主窗口完全相同的虚拟模式(MainForm.cs 52~60 原样复制) =====
|
||
advancedDataGridView图号.AutoGenerateColumns = false;
|
||
advancedDataGridView箱号.AutoGenerateColumns = false;
|
||
advancedDataGridView元件.AutoGenerateColumns = false;
|
||
advancedDataGridView图号.VirtualMode = true;
|
||
advancedDataGridView箱号.VirtualMode = true;
|
||
advancedDataGridView元件.VirtualMode = true;
|
||
|
||
// 虚拟模式供数/写回(主窗口 Dgv_CellValueNeeded/Dgv_CellValuePushed 的方案版)
|
||
advancedDataGridView图号.CellValueNeeded += 方案表_CellValueNeeded;
|
||
advancedDataGridView箱号.CellValueNeeded += 方案表_CellValueNeeded;
|
||
advancedDataGridView元件.CellValueNeeded += 方案表_CellValueNeeded;
|
||
advancedDataGridView图号.CellValuePushed += 方案表_CellValuePushed;
|
||
advancedDataGridView箱号.CellValuePushed += 方案表_CellValuePushed;
|
||
advancedDataGridView元件.CellValuePushed += 方案表_CellValuePushed;
|
||
|
||
// 编辑立即提交(主窗口 Grid_DirtyStateChanged 原样)
|
||
advancedDataGridView图号.CurrentCellDirtyStateChanged += 方案表_DirtyStateChanged;
|
||
advancedDataGridView箱号.CurrentCellDirtyStateChanged += 方案表_DirtyStateChanged;
|
||
advancedDataGridView元件.CurrentCellDirtyStateChanged += 方案表_DirtyStateChanged;
|
||
|
||
// 数据异常静默
|
||
advancedDataGridView图号.DataError += (s, e) => { e.ThrowException = false; };
|
||
advancedDataGridView箱号.DataError += (s, e) => { e.ThrowException = false; };
|
||
advancedDataGridView元件.DataError += (s, e) => { e.ThrowException = false; };
|
||
|
||
// ★ 报价页三表联动(点图号→箱号表,点箱号→元件表)
|
||
advancedDataGridView图号.SelectionChanged += 图号表_选中变化;
|
||
advancedDataGridView箱号.SelectionChanged += 箱号表_选中变化;
|
||
|
||
Load += (s, e) => 刷新项目页();
|
||
// ★ 每次窗口显示时自动刷新方案列表(存为方案后切过来立即看到)
|
||
VisibleChanged += (s, e) => { 写诊断("方案窗口 VisibleChanged, Visible=" + Visible); if (Visible) 刷新项目页(); };
|
||
|
||
// 报价页三表右键菜单(删除 = 从当前打开的方案文件中删除)
|
||
var 图号菜单 = new ContextMenuStrip();
|
||
图号菜单.Items.Add("从方案中删除该图号", null, 图号_删除_Click);
|
||
advancedDataGridView图号.ContextMenuStrip = 图号菜单;
|
||
|
||
var 箱号菜单 = new ContextMenuStrip();
|
||
箱号菜单.Items.Add("从方案中删除该箱号", null, 箱号_删除_Click);
|
||
advancedDataGridView箱号.ContextMenuStrip = 箱号菜单;
|
||
|
||
var 元件菜单 = new ContextMenuStrip();
|
||
元件菜单.Items.Add("从方案中删除选中元件", null, 元件_删除_Click);
|
||
advancedDataGridView元件.ContextMenuStrip = 元件菜单;
|
||
|
||
// 项目页右键菜单
|
||
var menu = new ContextMenuStrip();
|
||
menu.Items.Add("打开方案", null, 打开方案_Click);
|
||
menu.Items.Add("删除方案", null, 删除方案_Click);
|
||
advancedDataGridView6.ContextMenuStrip = menu;
|
||
|
||
// 项目页双击 = 打开方案
|
||
advancedDataGridView6.CellDoubleClick += 方案项目页_双击;
|
||
}
|
||
|
||
/// <summary>方案库目录(exe 旁 sjk\方案库\)。</summary>
|
||
private string 方案目录
|
||
{
|
||
get
|
||
{
|
||
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sjk", "方案库");
|
||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||
return dir;
|
||
}
|
||
}
|
||
|
||
/// <summary>主窗体引用(停靠面板的宿主窗体)。</summary>
|
||
private MainForm 主窗体
|
||
{
|
||
get { return DockPanel == null ? null : DockPanel.FindForm() as MainForm; }
|
||
}
|
||
|
||
/// <summary>绑定项目页网格(方案列表;由主窗体在方案数据变化时调用)。</summary>
|
||
public void 绑定项目页(System.Data.DataTable list)
|
||
{
|
||
advancedDataGridView6.AutoGenerateColumns = false;
|
||
advancedDataGridView6.DataSource = list;
|
||
advancedDataGridView6.Refresh();
|
||
}
|
||
|
||
/// <summary>重新加载当前打开的方案(粘贴/加入等操作完成后刷新显示;未打开则只刷方案列表)。</summary>
|
||
public void 重新加载当前方案()
|
||
{
|
||
if (!string.IsNullOrEmpty(_当前方案文件) && File.Exists(_当前方案文件))
|
||
打开方案(_当前方案文件);
|
||
刷新项目页();
|
||
}
|
||
|
||
/// <summary>主窗体"存为方案"后调用:切换并打开新存的方案文件,报价页立即显示其内容。</summary>
|
||
public void 打开方案文件(string 方案文件路径)
|
||
{
|
||
if (string.IsNullOrEmpty(方案文件路径) || !File.Exists(方案文件路径))
|
||
{
|
||
刷新项目页();
|
||
return;
|
||
}
|
||
_当前方案文件 = 方案文件路径;
|
||
打开方案(方案文件路径);
|
||
刷新项目页();
|
||
}
|
||
|
||
|
||
/// <summary>当前打开的方案文件路径(报价页三表的数据源;空=未打开)。</summary>
|
||
public string 当前方案文件
|
||
{
|
||
get { return _当前方案文件; }
|
||
}
|
||
|
||
/// <summary>方案报价页当前选中的图号名(图号表当前行"图号"列;未选中返回空)。
|
||
/// 供主窗口"加入方案库"定位插入目标:用户在方案里选中哪个图号,就插到哪个图号下。</summary>
|
||
public string 方案选中图号
|
||
{
|
||
get
|
||
{
|
||
if (advancedDataGridView图号 == null || advancedDataGridView图号.CurrentRow == null) return "";
|
||
return Cell文本(advancedDataGridView图号.CurrentRow, "图号");
|
||
}
|
||
}
|
||
|
||
/// <summary>方案报价页当前选中的箱号内部ID(箱号表当前行"内部ID"列,空则退回"箱号"显示名;未选中返回空)。
|
||
/// 供主窗口"加入方案库"定位插入目标:用户在方案里选中哪个箱号,元件就插到它的元件表。</summary>
|
||
public string 方案选中箱号ID
|
||
{
|
||
get
|
||
{
|
||
if (advancedDataGridView箱号 == null || advancedDataGridView箱号.CurrentRow == null) return "";
|
||
string id = Cell文本(advancedDataGridView箱号.CurrentRow, "内部ID");
|
||
if (string.IsNullOrEmpty(id)) id = Cell文本(advancedDataGridView箱号.CurrentRow, "箱号");
|
||
return id;
|
||
}
|
||
}
|
||
|
||
// ============ 项目页:方案列表 ============
|
||
|
||
/// <summary>方案窗口诊断日志(logs\方案窗口诊断.log)。</summary>
|
||
private static void 写诊断(string msg)
|
||
{
|
||
try
|
||
{
|
||
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
|
||
Directory.CreateDirectory(dir);
|
||
File.AppendAllText(Path.Combine(dir, "方案窗口诊断.log"),
|
||
DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg + System.Environment.NewLine);
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
/// <summary>扫描方案库目录,列出全部方案(每文件读项目信息+图号表汇总)。</summary>
|
||
public void 刷新项目页()
|
||
{
|
||
写诊断("刷新项目页 开始, 目录=" + 方案目录);
|
||
var list = new DataTable();
|
||
foreach (var c in new[] { "项目编号", "项目名称", "客户名称", "客户地址", "联系人",
|
||
"联系电话", "项目日期", "箱柜台数", "成本总价", "报出总价", "利润", "利润率", "文件名" })
|
||
list.Columns.Add(c);
|
||
|
||
try
|
||
{
|
||
Directory.CreateDirectory(方案目录);
|
||
// ★ 每文件最多尝试 2 次(短暂锁等待 300ms 后重试,防瞬时 BUSY 跳过)
|
||
foreach (var file in Directory.GetFiles(方案目录, "*.db"))
|
||
{
|
||
bool added = false;
|
||
for (int attempt = 0; attempt < 2 && !added; attempt++)
|
||
{
|
||
if (attempt > 0) System.Threading.Thread.Sleep(300);
|
||
try
|
||
{
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(file)))
|
||
{
|
||
conn.Open();
|
||
string 编号 = "", 名称 = "", 客户 = "", 地址 = "", 联系人 = "", 电话 = "", 日期 = "";
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 项目编号,项目名称,客户名称,客户地址,联系人,联系电话,项目日期 FROM 项目信息 LIMIT 1;", conn))
|
||
using (var r = cmd.ExecuteReader())
|
||
{
|
||
if (r.Read())
|
||
{
|
||
编号 = r.IsDBNull(0) ? "" : r.GetString(0);
|
||
名称 = r.IsDBNull(1) ? "" : r.GetString(1);
|
||
客户 = r.IsDBNull(2) ? "" : r.GetString(2);
|
||
地址 = r.IsDBNull(3) ? "" : r.GetString(3);
|
||
联系人 = r.IsDBNull(4) ? "" : r.GetString(4);
|
||
电话 = r.IsDBNull(5) ? "" : r.GetString(5);
|
||
日期 = r.IsDBNull(6) ? "" : r.GetString(6);
|
||
}
|
||
}
|
||
decimal 台数 = 0, 箱柜 = 0, 总价 = 0, 成本 = 0, 毛利 = 0;
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT COALESCE(SUM(CAST(\"台数\" AS REAL)),0), COALESCE(SUM(CAST(\"箱柜数量\" AS REAL)),0), " +
|
||
"COALESCE(SUM(CAST(\"总价\" AS REAL)),0), COALESCE(SUM(CAST(\"成本总价\" AS REAL)),0), " +
|
||
"COALESCE(SUM(CAST(\"毛利\" AS REAL)),0) FROM \"" + 图号表名 + "\";", conn))
|
||
using (var r = cmd.ExecuteReader())
|
||
{
|
||
if (r.Read())
|
||
{
|
||
台数 = Convert.ToDecimal(r.GetValue(0));
|
||
箱柜 = Convert.ToDecimal(r.GetValue(1));
|
||
总价 = Convert.ToDecimal(r.GetValue(2));
|
||
成本 = Convert.ToDecimal(r.GetValue(3));
|
||
毛利 = Convert.ToDecimal(r.GetValue(4));
|
||
}
|
||
}
|
||
list.Rows.Add(编号, 名称, 客户, 地址, 联系人, 电话, 日期,
|
||
台数.ToString("0.##"), 箱柜.ToString("0.##"), 总价.ToString("0.##"),
|
||
成本.ToString("0.##"), (成本 == 0 ? "" : (毛利 / 成本 * 100).ToString("0.##")),
|
||
Path.GetFileName(file));
|
||
added = true;
|
||
}
|
||
}
|
||
catch (Exception exFile)
|
||
{
|
||
写诊断("文件读取失败(第" + (attempt + 1) + "次): " + Path.GetFileName(file) + " - " + exFile.Message);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex) { 写诊断("扫描异常: " + ex.Message); }
|
||
|
||
写诊断("扫描完成, 方案行数=" + list.Rows.Count);
|
||
advancedDataGridView6.DataSource = list;
|
||
}
|
||
|
||
// ============ 项目页右键 ============
|
||
|
||
private DataGridViewRow 当前选中行()
|
||
{
|
||
if (advancedDataGridView6.CurrentRow != null && !advancedDataGridView6.CurrentRow.IsNewRow)
|
||
return advancedDataGridView6.CurrentRow;
|
||
foreach (DataGridViewRow r in advancedDataGridView6.SelectedRows)
|
||
if (!r.IsNewRow) return r;
|
||
return null;
|
||
}
|
||
|
||
private void 打开方案_Click(object sender, EventArgs e)
|
||
{
|
||
var row = 当前选中行();
|
||
if (row == null) { MessageBox.Show(this, "请先选择要打开的方案。", "打开方案", MessageBoxButtons.OK, MessageBoxIcon.Information); return; }
|
||
string file = 取行值(row, "文件名");
|
||
if (string.IsNullOrEmpty(file)) return;
|
||
打开方案(Path.Combine(方案目录, file));
|
||
}
|
||
|
||
/// <summary>项目页双击:打开该方案。</summary>
|
||
private void 方案项目页_双击(object sender, DataGridViewCellEventArgs e)
|
||
{
|
||
if (e.RowIndex < 0 || e.ColumnIndex < 0) return;
|
||
var row = advancedDataGridView6.Rows[e.RowIndex];
|
||
string file = 取行值(row, "文件名");
|
||
if (string.IsNullOrEmpty(file)) return;
|
||
打开方案(Path.Combine(方案目录, file));
|
||
}
|
||
|
||
private void 删除方案_Click(object sender, EventArgs e)
|
||
{
|
||
var row = 当前选中行();
|
||
if (row == null) { MessageBox.Show(this, "请先选择要删除的方案。", "删除方案", MessageBoxButtons.OK, MessageBoxIcon.Information); return; }
|
||
string file = 取行值(row, "文件名");
|
||
string 名称 = 取行值(row, "项目名称");
|
||
if (string.IsNullOrEmpty(file)) return;
|
||
if (MessageBox.Show(this, "确认删除方案 [" + 名称 + "]?\n\n方案文件将被删除,不影响任何项目数据。",
|
||
"删除方案", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
|
||
MessageBoxDefaultButton.Button2) != DialogResult.Yes) return;
|
||
|
||
try
|
||
{
|
||
string full = Path.Combine(方案目录, file);
|
||
if (File.Exists(full))
|
||
{
|
||
if (_当前方案文件 == full) { _当前方案文件 = ""; 清空报价页(); }
|
||
// ★ 清空连接池:池化连接会持有文件句柄,导致删除报"正由另一进程使用"
|
||
System.Data.SQLite.SQLiteConnection.ClearAllPools();
|
||
File.Delete(full);
|
||
}
|
||
刷新项目页();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(this, "删除失败:\n" + ex.Message, "删除方案", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
// ============ 方案内容删除(作用于当前打开的方案文件) ============
|
||
|
||
/// <summary>右键删除当前方案中的选中图号(连其箱号/元件子树)。</summary>
|
||
private void 图号_删除_Click(object sender, EventArgs e)
|
||
{
|
||
if (string.IsNullOrEmpty(_当前方案文件)) { MessageBox.Show(this, "请先打开一个方案。", "删除图号", MessageBoxButtons.OK, MessageBoxIcon.Information); return; }
|
||
if (advancedDataGridView图号.CurrentRow == null) return;
|
||
string 图号 = Cell文本(advancedDataGridView图号.CurrentRow, "图号");
|
||
if (string.IsNullOrEmpty(图号)) return;
|
||
if (MessageBox.Show(this, "确认从方案中删除图号 [" + 图号 + "]?\n其下所有箱号、元件也会一并删除。",
|
||
"删除图号", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
|
||
MessageBoxDefaultButton.Button2) != DialogResult.Yes) return;
|
||
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(_当前方案文件)))
|
||
{
|
||
conn.Open();
|
||
using (var tx = conn.BeginTransaction())
|
||
{
|
||
foreach (var t in 方案表清单(conn, tx, 图号, null))
|
||
Exec(conn, tx, "DROP TABLE IF EXISTS \"" + t + "\";");
|
||
Exec(conn, tx, "DELETE FROM 表索引 WHERE 图号 = @th;", new SQLiteP("@th", 图号));
|
||
Exec(conn, tx, "DELETE FROM \"" + 图号表名 + "\" WHERE 图号 = @th;", new SQLiteP("@th", 图号));
|
||
tx.Commit();
|
||
}
|
||
}
|
||
打开方案(_当前方案文件); // 重载刷新
|
||
}
|
||
|
||
/// <summary>右键删除当前方案中的选中箱号(连其元件表);并刷新图号行聚合。</summary>
|
||
private void 箱号_删除_Click(object sender, EventArgs e)
|
||
{
|
||
if (string.IsNullOrEmpty(_当前方案文件)) { MessageBox.Show(this, "请先打开一个方案。", "删除箱号", MessageBoxButtons.OK, MessageBoxIcon.Information); return; }
|
||
if (advancedDataGridView箱号.CurrentRow == null) return;
|
||
string 箱号 = Cell文本(advancedDataGridView箱号.CurrentRow, "内部ID");
|
||
if (string.IsNullOrEmpty(箱号)) 箱号 = Cell文本(advancedDataGridView箱号.CurrentRow, "箱号");
|
||
string 图号 = advancedDataGridView图号.CurrentRow == null ? "" : Cell文本(advancedDataGridView图号.CurrentRow, "图号");
|
||
if (string.IsNullOrEmpty(箱号) || string.IsNullOrEmpty(图号)) return;
|
||
if (MessageBox.Show(this, "确认从方案中删除箱号 [" + 箱号 + "]?\n其元件数据也会一并删除。",
|
||
"删除箱号", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
|
||
MessageBoxDefaultButton.Button2) != DialogResult.Yes) return;
|
||
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(_当前方案文件)))
|
||
{
|
||
conn.Open();
|
||
using (var tx = conn.BeginTransaction())
|
||
{
|
||
foreach (var t in 方案表清单(conn, tx, 图号, 箱号))
|
||
Exec(conn, tx, "DROP TABLE IF EXISTS \"" + t + "\";");
|
||
Exec(conn, tx, "DELETE FROM 表索引 WHERE 图号 = @th AND 箱号 = @xh;",
|
||
new SQLiteP("@th", 图号), new SQLiteP("@xh", 箱号));
|
||
Exec(conn, tx, "DELETE FROM \"" + 查方案表("箱号", 图号, null) + "\" WHERE \"内部ID\" = @xh;",
|
||
new SQLiteP("@xh", 箱号));
|
||
tx.Commit();
|
||
}
|
||
}
|
||
打开方案(_当前方案文件);
|
||
}
|
||
|
||
/// <summary>右键删除当前方案中选中的元件行(删除后刷新箱号/图号聚合)。</summary>
|
||
private void 元件_删除_Click(object sender, EventArgs e)
|
||
{
|
||
if (string.IsNullOrEmpty(_当前方案文件)) { MessageBox.Show(this, "请先打开一个方案。", "删除元件", MessageBoxButtons.OK, MessageBoxIcon.Information); return; }
|
||
if (advancedDataGridView元件.CurrentRow == null) return;
|
||
string 序号 = Cell文本(advancedDataGridView元件.CurrentRow, "序号");
|
||
string 图号 = "", 箱号 = "";
|
||
if (advancedDataGridView图号.CurrentRow != null) 图号 = Cell文本(advancedDataGridView图号.CurrentRow, "图号");
|
||
if (advancedDataGridView箱号.CurrentRow != null)
|
||
{
|
||
箱号 = Cell文本(advancedDataGridView箱号.CurrentRow, "内部ID");
|
||
if (string.IsNullOrEmpty(箱号)) 箱号 = Cell文本(advancedDataGridView箱号.CurrentRow, "箱号");
|
||
}
|
||
if (string.IsNullOrEmpty(序号) || string.IsNullOrEmpty(图号) || string.IsNullOrEmpty(箱号)) return;
|
||
if (MessageBox.Show(this, "确认从方案中删除选中的元件?", "删除元件",
|
||
MessageBoxButtons.YesNo, MessageBoxIcon.Question,
|
||
MessageBoxDefaultButton.Button2) != DialogResult.Yes) return;
|
||
|
||
string yjTbl = 查方案表("元件", 图号, 箱号);
|
||
if (string.IsNullOrEmpty(yjTbl)) return;
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(_当前方案文件)))
|
||
{
|
||
conn.Open();
|
||
using (var tx = conn.BeginTransaction())
|
||
{
|
||
Exec(conn, tx, "DELETE FROM \"" + yjTbl + "\" WHERE 序号 = @s;", new SQLiteP("@s", 序号));
|
||
tx.Commit();
|
||
}
|
||
}
|
||
刷新元件和聚合();
|
||
}
|
||
|
||
/// <summary>简单参数封装(WHERE 参数)。</summary>
|
||
private struct SQLiteP
|
||
{
|
||
public string Name; public string Value;
|
||
public SQLiteP(string name, string value) { Name = name; Value = value; }
|
||
}
|
||
|
||
private static void Exec(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, string sql, params SQLiteP[] ps)
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx))
|
||
{
|
||
foreach (var p in ps)
|
||
cmd.Parameters.AddWithValue(p.Name, p.Value);
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
|
||
/// <summary>查方案文件的表索引:该图号(可选箱号)关联的全部实际表名。</summary>
|
||
private System.Collections.Generic.List<string> 方案表清单(
|
||
System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx,
|
||
string 图号, string 箱号)
|
||
{
|
||
var list = new System.Collections.Generic.List<string>();
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 实际表名 FROM 表索引 WHERE 图号 = @th" +
|
||
(箱号 != null ? " AND 箱号 = @xh" : "") + ";", conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@th", 图号);
|
||
if (箱号 != null) cmd.Parameters.AddWithValue("@xh", 箱号);
|
||
using (var r = cmd.ExecuteReader())
|
||
while (r.Read())
|
||
{
|
||
string t = r.IsDBNull(0) ? "" : r.GetString(0);
|
||
if (!string.IsNullOrEmpty(t)) list.Add(t);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
/// <summary>删除元件后重刷元件表与箱号/图号聚合(简单重开方案)。</summary>
|
||
private void 刷新元件和聚合()
|
||
{
|
||
string f = _当前方案文件;
|
||
if (string.IsNullOrEmpty(f)) return;
|
||
打开方案(f);
|
||
}
|
||
|
||
// ============ 报价页:从方案文件加载三表(虚拟模式,与主窗口 Reload 相同顺序) ============
|
||
|
||
/// <summary>当前三表的数据源字段(虚拟模式供数用;TableName=库中真实表名)。</summary>
|
||
private DataTable _方案图号表, _方案箱号表, _方案元件表;
|
||
|
||
private DataTable 取方案数据表(DataGridView dgv)
|
||
{
|
||
if (dgv == advancedDataGridView图号) return _方案图号表;
|
||
if (dgv == advancedDataGridView箱号) return _方案箱号表;
|
||
if (dgv == advancedDataGridView元件) return _方案元件表;
|
||
return null;
|
||
}
|
||
|
||
/// <summary>安全设置行数:ADGV 列头排序/过滤后会激活内部数据绑定,
|
||
/// 此时直接设 RowCount 抛"不能在数据绑定的 DataGridView 控件上设置 RowCount 属性"
|
||
/// (加入方案库成功但方案窗口刷新失败,界面看不到新数据)。先解绑再设。</summary>
|
||
private static void 安全设行数(DataGridView dgv, int count)
|
||
{
|
||
if (dgv.DataSource != null) dgv.DataSource = null;
|
||
dgv.RowCount = count;
|
||
dgv.Invalidate();
|
||
}
|
||
|
||
private void 清空方案表(DataGridView dgv)
|
||
{
|
||
if (dgv == advancedDataGridView图号) _方案图号表 = null;
|
||
else if (dgv == advancedDataGridView箱号) _方案箱号表 = null;
|
||
else if (dgv == advancedDataGridView元件) _方案元件表 = null;
|
||
安全设行数(dgv, 0);
|
||
}
|
||
|
||
/// <summary>加载一张表到虚拟模式(★先换字段表引用,再设 RowCount,最后 Invalidate——主窗口同序)。</summary>
|
||
private void 加载方案表(DataGridView dgv, string dbPath, string sql)
|
||
{
|
||
var table = new DataTable();
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(dbPath)))
|
||
{
|
||
conn.Open();
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
|
||
using (var ad = new System.Data.SQLite.SQLiteDataAdapter(cmd))
|
||
ad.Fill(table);
|
||
}
|
||
// ★ TableName=库中真实表名(Fill 不会自动设置;直写库 UPDATE 靠它定位表)
|
||
var mFrom = System.Text.RegularExpressions.Regex.Match(sql, @"FROM\s+""([^""]+)""");
|
||
if (mFrom.Success) table.TableName = mFrom.Groups[1].Value;
|
||
if (dgv == advancedDataGridView图号) _方案图号表 = table;
|
||
else if (dgv == advancedDataGridView箱号) _方案箱号表 = table;
|
||
else if (dgv == advancedDataGridView元件) _方案元件表 = table;
|
||
安全设行数(dgv, table.Rows.Count);
|
||
}
|
||
|
||
/// <summary>打开方案:整份加载到报价页三表。</summary>
|
||
private void 打开方案(string 方案文件)
|
||
{
|
||
写联动日志("打开方案: " + 方案文件);
|
||
if (!File.Exists(方案文件)) { MessageBox.Show(this, "方案文件不存在。", "打开方案", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; }
|
||
_联动层++;
|
||
try
|
||
{
|
||
_当前方案文件 = 方案文件;
|
||
tabControl1.SelectedTab = tabPage2;
|
||
|
||
加载方案表(advancedDataGridView图号, 方案文件,
|
||
"SELECT rowid AS \"_rowid_\", * FROM \"" + 图号表名 + "\" ORDER BY CAST(序号 AS INTEGER);");
|
||
清空方案表(advancedDataGridView箱号);
|
||
清空方案表(advancedDataGridView元件);
|
||
|
||
// 默认选中第一个图号 → 联动箱号/元件
|
||
if (advancedDataGridView图号.Rows.Count > 0)
|
||
{
|
||
advancedDataGridView图号.ClearSelection();
|
||
advancedDataGridView图号.Rows[0].Selected = true;
|
||
advancedDataGridView图号.CurrentCell = advancedDataGridView图号.Rows[0].Cells[0];
|
||
int old = _联动层; _联动层 = 0; // 程序化联动不受防重入 guard 挡
|
||
try { 图号表_选中变化(advancedDataGridView图号, EventArgs.Empty); }
|
||
finally { _联动层 = old; }
|
||
}
|
||
}
|
||
finally { _联动层 = 0; }
|
||
}
|
||
|
||
private void 图号表_选中变化(object sender, EventArgs e)
|
||
{
|
||
写联动日志("图号联动进入: 层=" + _联动层 + " 方案文件=" + _当前方案文件);
|
||
if (_联动层 > 0 || string.IsNullOrEmpty(_当前方案文件)) return;
|
||
if (advancedDataGridView图号.CurrentRow == null) return;
|
||
_联动层++;
|
||
try
|
||
{
|
||
string 图号 = Cell文本(advancedDataGridView图号.CurrentRow, "图号");
|
||
if (string.IsNullOrEmpty(图号)) return;
|
||
string xhTbl = 查方案表("箱号", 图号, null);
|
||
if (string.IsNullOrEmpty(xhTbl)) { 清空方案表(advancedDataGridView箱号); 清空方案表(advancedDataGridView元件); return; }
|
||
加载方案表(advancedDataGridView箱号, _当前方案文件,
|
||
"SELECT rowid AS \"_rowid_\", * FROM \"" + xhTbl + "\" ORDER BY CAST(序号 AS INTEGER);");
|
||
清空方案表(advancedDataGridView元件);
|
||
|
||
// 默认选中第一个箱号 → 元件
|
||
if (advancedDataGridView箱号.Rows.Count > 0)
|
||
{
|
||
advancedDataGridView箱号.ClearSelection();
|
||
advancedDataGridView箱号.Rows[0].Selected = true;
|
||
advancedDataGridView箱号.CurrentCell = advancedDataGridView箱号.Rows[0].Cells[0];
|
||
int old = _联动层; _联动层 = 0;
|
||
try { 箱号表_选中变化(advancedDataGridView箱号, EventArgs.Empty); }
|
||
finally { _联动层 = old; }
|
||
}
|
||
}
|
||
finally { _联动层 = 0; }
|
||
}
|
||
|
||
private void 箱号表_选中变化(object sender, EventArgs e)
|
||
{
|
||
写联动日志("箱号联动进入: 层=" + _联动层 + " 方案文件=" + _当前方案文件);
|
||
if (_联动层 > 0 || string.IsNullOrEmpty(_当前方案文件)) return;
|
||
if (advancedDataGridView图号.CurrentRow == null || advancedDataGridView箱号.CurrentRow == null) return;
|
||
_联动层++;
|
||
try
|
||
{
|
||
string 图号 = Cell文本(advancedDataGridView图号.CurrentRow, "图号");
|
||
string 箱号 = Cell文本(advancedDataGridView箱号.CurrentRow, "内部ID");
|
||
if (string.IsNullOrEmpty(箱号)) 箱号 = Cell文本(advancedDataGridView箱号.CurrentRow, "箱号");
|
||
if (string.IsNullOrEmpty(图号) || string.IsNullOrEmpty(箱号)) return;
|
||
string yjTbl = 查方案表("元件", 图号, 箱号);
|
||
if (string.IsNullOrEmpty(yjTbl)) { 清空方案表(advancedDataGridView元件); return; }
|
||
加载方案表(advancedDataGridView元件, _当前方案文件,
|
||
"SELECT rowid AS \"_rowid_\", * FROM \"" + yjTbl + "\" ORDER BY CAST(序号 AS INTEGER);");
|
||
}
|
||
finally { _联动层 = 0; }
|
||
}
|
||
|
||
private void 清空报价页()
|
||
{
|
||
清空方案表(advancedDataGridView图号);
|
||
清空方案表(advancedDataGridView箱号);
|
||
清空方案表(advancedDataGridView元件);
|
||
}
|
||
|
||
// ============ 工具 ============
|
||
|
||
/// <summary>方案联动专用日志(logs\方案联动.log)。</summary>
|
||
private static void 写联动日志(string msg)
|
||
{
|
||
try
|
||
{
|
||
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
|
||
Directory.CreateDirectory(dir);
|
||
File.AppendAllText(Path.Combine(dir, "方案联动.log"),
|
||
DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg + System.Environment.NewLine);
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
private string 连接串(string dbPath)
|
||
{
|
||
return "Data Source=\"" + dbPath + "\";Version=3;Journal Mode=WAL;Synchronous=Normal;Pooling=True;BusyTimeout=3000;";
|
||
}
|
||
|
||
private string 查方案表(string 类型, string 图号, string 箱号)
|
||
{
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(_当前方案文件)))
|
||
{
|
||
conn.Open();
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 实际表名 FROM 表索引 WHERE 类型=@t AND 图号=@th" +
|
||
(箱号 != null ? " AND 箱号=@xh" : "") + " LIMIT 1;", conn))
|
||
{
|
||
cmd.Parameters.AddWithValue("@t", 类型);
|
||
cmd.Parameters.AddWithValue("@th", 图号);
|
||
if (箱号 != null) cmd.Parameters.AddWithValue("@xh", 箱号);
|
||
object o = cmd.ExecuteScalar();
|
||
return o == null || o == DBNull.Value ? null : o.ToString();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>取行值:报价三表(虚拟模式)按 row.Index 从字段表取;项目页(绑定模式)从 DataBoundItem 取。</summary>
|
||
private string Cell文本(DataGridViewRow row, string dataPropertyName)
|
||
{
|
||
// 绑定模式(项目页方案列表):从数据源行取,不受网格列显示与否影响
|
||
var drv = row.DataBoundItem as System.Data.DataRowView;
|
||
if (drv != null && drv.Row != null && drv.Row.Table.Columns.Contains(dataPropertyName))
|
||
{
|
||
object v = drv.Row[dataPropertyName];
|
||
return v == null || v == DBNull.Value ? "" : v.ToString().Trim();
|
||
}
|
||
// 虚拟模式(报价三表):按行号查字段表
|
||
var table = 取方案数据表(row.DataGridView);
|
||
if (table != null && row.Index >= 0 && row.Index < table.Rows.Count &&
|
||
table.Columns.Contains(dataPropertyName))
|
||
{
|
||
object v = table.Rows[row.Index][dataPropertyName];
|
||
return v == null || v == DBNull.Value ? "" : v.ToString().Trim();
|
||
}
|
||
foreach (DataGridViewCell c in row.Cells)
|
||
{
|
||
if (c.OwningColumn != null && c.OwningColumn.DataPropertyName == dataPropertyName)
|
||
return c.Value == null ? "" : c.Value.ToString();
|
||
}
|
||
return "";
|
||
}
|
||
|
||
private string 取行值(DataGridViewRow row, string dataPropertyName)
|
||
{
|
||
return Cell文本(row, dataPropertyName);
|
||
}
|
||
|
||
// ============================================================
|
||
// 方案三表实时保存 —— 主窗口报价页保存链的完整复制
|
||
// (虚拟模式:CellValueNeeded 供数 / CellValuePushed 写回 /
|
||
// 内存重算链乐观显示 / 300ms 防抖队列 / 后台单事务写库+聚合)
|
||
// ============================================================
|
||
|
||
/// <summary>虚拟模式供数(主窗口 Dgv_CellValueNeeded 同款)。</summary>
|
||
private void 方案表_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
|
||
{
|
||
var dgv = sender as DataGridView;
|
||
var table = 取方案数据表(dgv);
|
||
if (table == null || e.RowIndex < 0 || e.RowIndex >= table.Rows.Count) { e.Value = ""; return; }
|
||
if (e.ColumnIndex < 0 || e.ColumnIndex >= dgv.Columns.Count) { e.Value = ""; return; }
|
||
string colName = dgv.Columns[e.ColumnIndex].DataPropertyName;
|
||
if (string.IsNullOrEmpty(colName) || !table.Columns.Contains(colName)) { e.Value = ""; return; }
|
||
object v = table.Rows[e.RowIndex][colName];
|
||
e.Value = (v == null || v == DBNull.Value) ? "" : v;
|
||
}
|
||
|
||
/// <summary>编辑立即提交(主窗口 Grid_DirtyStateChanged 同款)。</summary>
|
||
private void 方案表_DirtyStateChanged(object sender, EventArgs e)
|
||
{
|
||
var dgv = sender as DataGridView;
|
||
if (dgv != null && dgv.IsCurrentCellDirty)
|
||
dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
||
}
|
||
|
||
/// <summary>虚拟模式写回:写内存表 + 内存重算链(乐观显示) + 入队直写方案文件。</summary>
|
||
private void 方案表_CellValuePushed(object sender, DataGridViewCellValueEventArgs e)
|
||
{
|
||
写联动日志("[Pushed] 触发: 表=" + ((DataGridView)sender).Name + " 行=" + e.RowIndex + " 值=" + (e.Value == null ? "" : e.Value.ToString()));
|
||
var dgv = sender as DataGridView;
|
||
var table = 取方案数据表(dgv);
|
||
if (table == null || e.RowIndex < 0 || e.RowIndex >= table.Rows.Count) return;
|
||
if (e.ColumnIndex < 0 || e.ColumnIndex >= dgv.Columns.Count) return;
|
||
string colName = dgv.Columns[e.ColumnIndex].DataPropertyName;
|
||
if (string.IsNullOrEmpty(colName) || colName == "_rowid_" || !table.Columns.Contains(colName)) return;
|
||
|
||
table.Rows[e.RowIndex][colName] = (e.Value == null || e.Value == DBNull.Value) ? DBNull.Value : e.Value;
|
||
|
||
// ★ 内存重算链(乐观显示):元件→箱号→图号
|
||
if (_联动层 == 0 && !_方案派生列.Contains(colName))
|
||
{
|
||
_联动层++;
|
||
try
|
||
{
|
||
if (dgv == advancedDataGridView元件) 方案重算元件行(e.RowIndex);
|
||
else if (dgv == advancedDataGridView箱号) 方案重算箱号行(e.RowIndex);
|
||
else if (dgv == advancedDataGridView图号) 方案重算图号行(e.RowIndex);
|
||
}
|
||
finally { _联动层--; }
|
||
dgv.Invalidate();
|
||
if (dgv == advancedDataGridView元件)
|
||
{
|
||
advancedDataGridView箱号.Invalidate();
|
||
advancedDataGridView图号.Invalidate();
|
||
}
|
||
else if (dgv == advancedDataGridView箱号) advancedDataGridView图号.Invalidate();
|
||
}
|
||
|
||
// ★ 入队直写(主窗口 入队直写库 同款:300ms 防抖聚合)
|
||
if (!_方案派生列.Contains(colName))
|
||
{
|
||
写联动日志("[Pushed] 入队: " + colName);
|
||
入队方案直写(dgv, e.RowIndex, colName, e.Value);
|
||
}
|
||
else 写联动日志("[Pushed] 派生列跳过: " + colName);
|
||
}
|
||
|
||
// ---- 内存重算链(主窗口 RecalcYuanjianRow/RecalcXianghaoRowFromYuanjian/RecalcTuhaoRowFromXianghao 的方案版) ----
|
||
|
||
private static decimal 方案数值(object v)
|
||
{
|
||
decimal d;
|
||
return v == null || v == DBNull.Value ? 0 :
|
||
(decimal.TryParse(Convert.ToString(v).Trim(), System.Globalization.NumberStyles.Float,
|
||
System.Globalization.CultureInfo.InvariantCulture, out d) ? d : 0);
|
||
}
|
||
|
||
private void 方案重算元件行(int rowIndex)
|
||
{
|
||
var t = _方案元件表;
|
||
if (t == null || rowIndex < 0 || rowIndex >= t.Rows.Count) return;
|
||
var r = t.Rows[rowIndex];
|
||
decimal 表价 = 方案数值(r["表价"]), 采购系数 = 方案数值(r["采购系数"]),
|
||
报出系数 = 方案数值(r["报出系数"]), 数量 = 方案数值(r["数量"]);
|
||
decimal 采购单价 = 表价 * 采购系数;
|
||
if (t.Columns.Contains("采购单价")) r["采购单价"] = 采购单价;
|
||
if (t.Columns.Contains("采购价")) r["采购价"] = 采购单价 * 数量;
|
||
if (t.Columns.Contains("报出单价")) r["报出单价"] = 采购单价 * 报出系数;
|
||
if (t.Columns.Contains("报出价")) r["报出价"] = 采购单价 * 报出系数 * 数量;
|
||
|
||
方案重算箱号行全部(); // 所属箱号聚合 → 图号聚合
|
||
}
|
||
|
||
private void 方案重算箱号行(int rowIndex)
|
||
{
|
||
// 箱号行数量变:按内存元件表 SUM 重算本行 + 图号聚合
|
||
方案重算箱号行全部();
|
||
}
|
||
|
||
private void 方案重算箱号行全部()
|
||
{
|
||
var xh = _方案箱号表;
|
||
var yj = _方案元件表;
|
||
if (xh == null) return;
|
||
decimal 采购合计 = 0, 报出合计 = 0;
|
||
if (yj != null)
|
||
{
|
||
foreach (DataRow r in yj.Rows)
|
||
{
|
||
采购合计 += 方案数值(r["采购价"]);
|
||
报出合计 += 方案数值(r["报出价"]);
|
||
}
|
||
}
|
||
// 写到当前选中箱号行(方案库每箱号一张元件表,当前元件表即当前箱号)
|
||
if (advancedDataGridView箱号.CurrentRow != null)
|
||
{
|
||
int idx = advancedDataGridView箱号.CurrentRow.Index;
|
||
if (idx >= 0 && idx < xh.Rows.Count)
|
||
{
|
||
var row = xh.Rows[idx];
|
||
decimal 数量 = 方案数值(row["数量"]);
|
||
if (xh.Columns.Contains("单台成本")) row["单台成本"] = 采购合计;
|
||
if (xh.Columns.Contains("成本价")) row["成本价"] = 采购合计 * 数量;
|
||
if (xh.Columns.Contains("单台报出")) row["单台报出"] = 报出合计;
|
||
if (xh.Columns.Contains("报出价")) row["报出价"] = 报出合计 * 数量;
|
||
decimal 成本价 = 采购合计 * 数量, 报出价 = 报出合计 * 数量;
|
||
if (xh.Columns.Contains("利润")) row["利润"] = 报出价 - 成本价;
|
||
// ★ 口径(2026-09-29):利润率保持小数;毛利率存百分数值(×100,保留2位)
|
||
if (xh.Columns.Contains("利润率")) row["利润率"] = 成本价 == 0 ? 0 : (报出价 - 成本价) / 成本价;
|
||
if (xh.Columns.Contains("毛利率")) row["毛利率"] = 报出价 == 0 ? 0 : Math.Round((报出价 - 成本价) / 报出价 * 100m, 2);
|
||
}
|
||
}
|
||
方案重算图号行全部();
|
||
}
|
||
|
||
private void 方案重算图号行(int rowIndex)
|
||
{
|
||
方案重算图号行全部();
|
||
}
|
||
|
||
private void 方案重算图号行全部()
|
||
{
|
||
var th = _方案图号表;
|
||
var xh = _方案箱号表;
|
||
if (th == null || xh == null) return;
|
||
decimal 箱柜数量 = 0, 报出合计 = 0, 成本合计 = 0;
|
||
foreach (DataRow r in xh.Rows)
|
||
{
|
||
箱柜数量 += 方案数值(r["数量"]);
|
||
报出合计 += 方案数值(r["报出价"]);
|
||
成本合计 += 方案数值(r["成本价"]);
|
||
}
|
||
if (advancedDataGridView图号.CurrentRow != null)
|
||
{
|
||
int idx = advancedDataGridView图号.CurrentRow.Index;
|
||
if (idx >= 0 && idx < th.Rows.Count)
|
||
{
|
||
var row = th.Rows[idx];
|
||
decimal 台数 = 方案数值(row["台数"]);
|
||
decimal 总价 = 报出合计 * 台数, 成本总价 = 成本合计 * 台数;
|
||
if (th.Columns.Contains("箱柜数量")) row["箱柜数量"] = 箱柜数量;
|
||
if (th.Columns.Contains("单价")) row["单价"] = 报出合计;
|
||
if (th.Columns.Contains("总价")) row["总价"] = 总价;
|
||
if (th.Columns.Contains("成本总价")) row["成本总价"] = 成本总价;
|
||
if (th.Columns.Contains("毛利")) row["毛利"] = 总价 - 成本总价;
|
||
// ★ 口径统一(2026-09-29):毛利率存百分数值(×100,保留2位)
|
||
if (th.Columns.Contains("毛利率")) row["毛利率"] = 总价 == 0 ? 0 : Math.Round((总价 - 成本总价) / 总价 * 100m, 2);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---- 防抖队列 + 后台事务(主窗口 编辑直写库.cs 的方案版) ----
|
||
|
||
/// <summary>派生列集合(由计算维护;用户编辑这些列不触发直写)。</summary>
|
||
private static readonly System.Collections.Generic.HashSet<string> _方案派生列 =
|
||
new System.Collections.Generic.HashSet<string>(
|
||
new[] { "采购单价", "采购价", "报出单价", "报出价", "单台成本", "成本价", "单台报出",
|
||
"利润", "利润率", "毛利率", "箱柜数量", "单价", "总价", "成本总价", "毛利" });
|
||
|
||
/// <summary>待写库项:表名|rowid|列 → 值(后写覆盖先写)。主窗口 _待写项 同款。</summary>
|
||
private readonly System.Collections.Generic.Dictionary<string, string[]> _方案待写项 =
|
||
new System.Collections.Generic.Dictionary<string, string[]>();
|
||
|
||
/// <summary>防抖定时器(300ms)。主窗口 _直写Timer 同款。</summary>
|
||
private Timer _方案直写Timer;
|
||
|
||
private void 入队方案直写(DataGridView dgv, int rowIndex, string colName, object value)
|
||
{
|
||
var table = 取方案数据表(dgv);
|
||
if (table == null || !table.Columns.Contains("_rowid_")) return;
|
||
if (rowIndex < 0 || rowIndex >= table.Rows.Count) return;
|
||
long rowid = table.Rows[rowIndex]["_rowid_"] is long ? Convert.ToInt64(table.Rows[rowIndex]["_rowid_"]) : 0;
|
||
if (rowid <= 0) return;
|
||
|
||
lock (_方案待写项)
|
||
{
|
||
_方案待写项[table.TableName + "|" + rowid + "|" + colName] =
|
||
new[] { table.TableName, rowid.ToString(), colName, value == null ? "" : value.ToString() };
|
||
}
|
||
if (_方案直写Timer == null)
|
||
{
|
||
_方案直写Timer = new Timer();
|
||
_方案直写Timer.Interval = 300;
|
||
_方案直写Timer.Tick += 方案直写到期;
|
||
}
|
||
_方案直写Timer.Stop();
|
||
_方案直写Timer.Start();
|
||
}
|
||
|
||
/// <summary>防抖到期:批量直写方案文件(后台单事务)+按公式刷聚合。</summary>
|
||
private void 方案直写到期(object sender, EventArgs e)
|
||
{
|
||
_方案直写Timer.Stop();
|
||
if (_方案待写项.Count == 0 || string.IsNullOrEmpty(_当前方案文件)) return;
|
||
|
||
System.Collections.Generic.List<string[]> 批;
|
||
lock (_方案待写项)
|
||
{
|
||
批 = new System.Collections.Generic.List<string[]>(_方案待写项.Values);
|
||
_方案待写项.Clear();
|
||
}
|
||
|
||
string dbPath = _当前方案文件;
|
||
System.Threading.Tasks.Task.Run(delegate
|
||
{
|
||
try
|
||
{
|
||
using (var conn = new System.Data.SQLite.SQLiteConnection(连接串(dbPath)))
|
||
{
|
||
conn.Open();
|
||
using (var tx = conn.BeginTransaction())
|
||
{
|
||
// ★ 图号改名同步:改"图号"列时同步内部ID与表索引(否则联动按新名查不到旧表,箱号/元件全空)
|
||
foreach (var item in 批)
|
||
{
|
||
if (item[0] == 图号表名 && item[2] == "图号")
|
||
{
|
||
string 新名 = item[3].Trim();
|
||
if (新名.Length > 0)
|
||
{
|
||
string 旧名 = null;
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 图号 FROM \"" + 图号表名 + "\" WHERE rowid=@r;", conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@r", long.Parse(item[1]));
|
||
object o = cmd.ExecuteScalar();
|
||
旧名 = o == null || o == DBNull.Value ? null : o.ToString();
|
||
}
|
||
if (!string.IsNullOrEmpty(旧名) && 旧名 != 新名)
|
||
{
|
||
Exec(conn, tx, "UPDATE 表索引 SET 图号='" + 新名.Replace("'", "''") + "' WHERE 图号='" + 旧名.Replace("'", "''") + "';");
|
||
Exec(conn, tx, "UPDATE \"" + 图号表名 + "\" SET 内部ID='" + 新名.Replace("'", "''") + "' WHERE rowid=" + item[1] + ";");
|
||
写联动日志("[改名] 图号 " + 旧名 + " → " + 新名 + " (索引+内部ID已同步)");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
foreach (var item in 批)
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"UPDATE \"" + item[0].Replace("\"", "") + "\" SET \"" + item[2].Replace("\"", "") +
|
||
"\"=@v WHERE rowid=@r;", conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@v", item[3]);
|
||
cmd.Parameters.AddWithValue("@r", long.Parse(item[1]));
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
// 元件行:重算本行派生价
|
||
if (item[0].StartsWith("yj_"))
|
||
{
|
||
string yjSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "元件"), "ROW", null);
|
||
if (yjSet.Length > 0)
|
||
Exec(conn, tx, "UPDATE \"" + item[0] + "\" SET " + yjSet + " WHERE rowid=" + item[1] + ";");
|
||
}
|
||
}
|
||
|
||
// 聚合链:反查 图号/箱号 → 箱号行聚合 + 图号行聚合
|
||
string 图号 = null, 箱号 = null;
|
||
foreach (var item in 批)
|
||
{
|
||
if (item[0].StartsWith("yj_"))
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 图号,箱号 FROM 表索引 WHERE 类型='元件' AND 实际表名=@t LIMIT 1;", conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@t", item[0]);
|
||
using (var r = cmd.ExecuteReader())
|
||
if (r.Read()) { 图号 = r.IsDBNull(0) ? null : r.GetString(0); 箱号 = r.IsDBNull(1) ? null : r.GetString(1); }
|
||
}
|
||
}
|
||
else if (item[0].StartsWith("xh_"))
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 图号 FROM 表索引 WHERE 类型='箱号' AND 实际表名=@t LIMIT 1;", conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@t", item[0]);
|
||
using (var r = cmd.ExecuteReader())
|
||
if (r.Read()) { 图号 = r.IsDBNull(0) ? null : r.GetString(0); 箱号 = null; }
|
||
}
|
||
}
|
||
}
|
||
if (!string.IsNullOrEmpty(图号))
|
||
{
|
||
string xhTbl = 查方案表2(conn, tx, "箱号", 图号, null);
|
||
if (!string.IsNullOrEmpty(xhTbl))
|
||
{
|
||
if (!string.IsNullOrEmpty(箱号))
|
||
{
|
||
string yjTbl = 查方案表2(conn, tx, "元件", 图号, 箱号);
|
||
if (!string.IsNullOrEmpty(yjTbl))
|
||
{
|
||
string xhSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "箱号"), "ROW", yjTbl);
|
||
if (xhSet.Length > 0)
|
||
Exec(conn, tx, "UPDATE \"" + xhTbl + "\" SET " + xhSet + " WHERE \"内部ID\"='" + 箱号.Replace("'", "''") + "';");
|
||
}
|
||
}
|
||
string thSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "图号"), "ROW", xhTbl);
|
||
if (thSet.Length > 0)
|
||
Exec(conn, tx, "UPDATE \"" + 图号表名 + "\" SET " + thSet + " WHERE \"内部ID\"='" + 图号.Replace("'", "''") + "';");
|
||
}
|
||
}
|
||
tx.Commit();
|
||
}
|
||
}
|
||
写联动日志("方案直写完成: " + 批.Count + " 项");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
写联动日志("方案直写异常: " + ex.Message);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>简单 SQL 执行。</summary>
|
||
private static void Exec(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, string sql)
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx))
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
|
||
private string 查方案表2(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, string 类型, string 图号, string 箱号)
|
||
{
|
||
using (var cmd = new System.Data.SQLite.SQLiteCommand(
|
||
"SELECT 实际表名 FROM 表索引 WHERE 类型=@t AND 图号=@th" +
|
||
(箱号 != null ? " AND 箱号=@xh" : "") + " LIMIT 1;", conn, tx))
|
||
{
|
||
cmd.Parameters.AddWithValue("@t", 类型);
|
||
cmd.Parameters.AddWithValue("@th", 图号);
|
||
if (箱号 != null) cmd.Parameters.AddWithValue("@xh", 箱号);
|
||
object o = cmd.ExecuteScalar();
|
||
return o == null || o == DBNull.Value ? null : o.ToString();
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>方案窗口自己的 fak.db 连接(公式表来源;已打开)。</summary>
|
||
private System.Data.SQLite.SQLiteConnection GetFak连接()
|
||
{
|
||
var conn = new System.Data.SQLite.SQLiteConnection("Data Source=" +
|
||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sjk", "fak.db") + ";Version=3;");
|
||
conn.Open();
|
||
return conn;
|
||
}
|
||
}
|
||
} |