callm 1 день назад
Родитель
Сommit
0eec83cdca

+ 65 - 0
UAS_MES_YDCY/.workbuddy/memory/2026-10-10.md

@@ -0,0 +1,65 @@
+# 2026-10-10 Query_LoadMan 两项增强
+
+## 需求
+1. insert `loadman` 时一并写入人员名称 `lm_emname`
+2. 下线操作支持 DGV 批量勾选 + 按钮确认下线
+
+## 改动文件
+`FunctionCode/Query/Query_LoadMan.cs` + `Query_LoadMan.Designer.cs`(**未改 resx,无需改**)
+
+### 需求 A:写入 lm_emname
+原上线分支先用 `dh.CheckExist("SOPEMPLOYEEDETAIL", "sed_emcode=...")` 校验存在性,
+改造为**一次查询同时完成「校验存在」和「取名称」**:
+
+```csharp
+//上线:先校验人员编号存在于 SOPEMPLOYEEDETAIL,并取出人员名称(同一 sed_emcode 可能多条,取最大值聚合)
+string emname = dh.getFieldDataByCondition("SOPEMPLOYEEDETAIL", "max(sed_emname)", "sed_emcode='" + emcode + "'").ToString().Trim();
+if (emname == "")
+{
+    OperateResult.AppendText("<<" + emcode + "人员编号不存在\n", Color.Red, em_code);
+    em_code.Clear();
+    em_code.Focus();
+    return;
+}
+```
+
+insert 加列:
+```csharp
+dh.ExecuteSql("insert into loadman(lm_id,lm_linecode,lm_emcode,lm_emname,lm_uptime,lm_inman)" +
+    "values(loadman_seq.nextval,'" + linecode + "','" + emcode + "','" + emname + "',sysdate,'" + User.UserName + "')", "insert");
+OperateResult.AppendText("<<" + emcode + " " + emname + " 在" + linecode + "上线成功\n", Color.Green, em_code);
+```
+- 关键点:`getFieldDataByCondition(表, "max(字段)", 条件)` **可以传聚合表达式**,返回单值;
+  同一 `sed_emcode` 多条时用 `max()` 聚合去重(与 `LoadGridData` 里的 `group by sed_emcode` 口径一致)。
+- 不能只靠 `ToString()` 判空——Oracle 无记录时返回 `DBNull`,`DBNull.ToString()` == `""`,所以 `emname == ""` 同时覆盖「无记录」和「名称为空」两种情况,正好复用原提示语。
+
+### 需求 B:批量勾选下线
+**Designer 新增:**
+- `DataGridViewCheckBoxColumn choose`:`HeaderText="勾选"`,`Name="choose"`,`Width=70`,**不设 `DataPropertyName`**(避免绑定 DataTable 时找不到列报错),插在 `AddRange` 最前(`lm_id` 之前)→ DGV 最左列
+- `ButtonUtil.NormalButton ConfirmDown`:`Text="确认下线"`,`Location(1042,26)`,`Size(140,50)`,`TabIndex=7`
+  - 坐标依据:窗体 `ClientSize 1560×760`,按钮行 y=34(/26),原最右控件 `DownLoadMan(912,34)`,x=1042~1182 处空闲;`OperateResult` 位于 `x=1130,y=96` 起,**y 方向不冲突**
+  - `NormalButton` 属性集(照抄 `Query_LoadMake.ChangeMan`):`BackColor=Transparent`、`Font=微软雅黑 12F`、`IsShowBorder=true`、`Power=null`、`UseVisualStyleBackColor=false`
+  - **不设 NormalImage/DownImage/MoveImage** → 控件内部会自动取 `Properties.Resources` 默认图(无需 resx 资源项,所以本例 resx 完全不用动)
+  - `Controls.Add(this.ConfirmDown)` 放在 `Controls.Add` 列表**最前**(= z-order 最底,不会被别的控件压住)
+- 字段声明区加:`ConfirmDown`、`choose`(放在 `OperateResult` / `lm_id` 之间)
+
+**.cs 新增两个方法:**
+- `bool IfCheckRow()`:遍历 `DGV.Rows`,命中 `Cells["choose"].Value != null && FormattedValue.ToString() == "True"` 即 true
+  (写法与 `Query_LoadMake.ifcheckrow()` 一致;**用 `FormattedValue` 而非 `Value`**,因为未编辑过的复选框 `Value` 可能是 `null` 而 `FormattedValue` 已是 `"False"`)
+- `ConfirmDown_Click`:先 `IfCheckRow()` 空则红色提示;再逐行筛 `choose==True` 且 `lm_id` 非空,
+  执行 `update loadman set lm_downtime=sysdate where lm_id='xx' and lm_downtime is null`,
+  逐条 try/catch 累计 `sucCount`/`failCount`,最后汇总一行 + `LoadGridData()` + `em_code.Focus()`
+  - 用 **`lm_id` 主键更新**(比按 `lm_emcode` 稳):DGV 只加载 `lm_downtime is null` 的记录,勾选行必然在线
+  - 用 **(string,Color) 两参数重载**输出,避免 (string,Color,Control) 重载里 `ctl.Text=""` 把 `em_code` 清空
+
+**保留下来的原有能力:** `em_code_KeyDown` 的单人下线分支**未删除**,回车单人下线仍可用;仅在两处提示文案里补了「或勾选列表后点"确认下线"批量下线」。
+
+## 自检
+- 无本地编译(沙箱拦 MSBuild/csc),用 Python 剥字符串字面量后统计 `{}`/`()`/`[]` 平衡:
+  `Query_LoadMan.cs` 29/29、93/93、21/21;`Query_LoadMan.Designer.cs` 6/6、105/105、1/1 ✅
+- ⚠️ 教训:Edit 时 old_string 结尾带换行、new_string 不带,会把下一个方法签名和 `{` 挤到同一行
+  (本次 `UpLoadMan_CheckedChanged` 中招,已修正)。**old_string/new_string 的末尾换行必须一致。**
+
+## 待办
+- 需在 Visual Studio 中编译确认(本地无法编译)
+- DGV 刷新后勾选状态会因 `FillDgvWithDataTable` 重新绑定而清空,属预期

+ 18 - 1
UAS_MES_YDCY/.workbuddy/memory/MEMORY.md

@@ -76,16 +76,33 @@
   再把每个 `DoCommandLog` 的 result 参数包成 `ResultWithRemark(...)`。注意原有 result 里已含逗号时(如 `"Key"+xx+"解冻,站点重置至"+stname`)拼接后会出现两个逗号,属预期。
 - 已套用:`Special_Reset`、`Special_ResetKeyFreeze`、`Special_ResetKeyRelease`、`Special_CancelCollection`(后者布局不同,备注放 `(68,362)`/`(226,358)` 554×280,正文 10.5F)。
 
+## DGV 勾选批量操作范式
+参考 `Query_LoadMan`(人员批量下线)、`Query_LoadMake`(批量切换人数):
+- **Designer**:`DataGridViewCheckBoxColumn`(`HeaderText="勾选"`,`Width≈70`,**不设 `DataPropertyName`** 避免绑定报错),
+  放进 `AddRange` 数组最前 → DGV 最左列;配套 `ButtonUtil.NormalButton`(如 `Text="确认下线"`),
+  `Controls.Add(按钮)` 放列表**最前**(z-order 最底,不会被遮挡)。
+- **判断是否有勾选**:用 `Cells["列名"].FormattedValue.ToString() == "True"`,
+  **不要用 `.Value`**——未手动点过的复选框 `.Value` 可能是 `null`,而 `FormattedValue` 已是 `"False"`。
+- **批量更新**:遍历 `DGV.Rows` 筛勾选行 → 按**主键 `lm_id`** 逐条 update(比按业务字段稳,因 DGV 只加载未下线记录),
+  逐条 try/catch 累计成功/失败数,最后汇总一行提示 + `LoadGridData()` 刷新。
+- 提示输出用 **(string, Color) 两参数重载**,避开 (string,Color,Control) 重载内部 `ctl.Text=""` 误清输入框。
+- 刷新后勾选状态会因重新绑定被清空,属预期。
+
 ## 数据访问
 - `dh.ExecuteSql(sql, "select")` 返回 `DataTable`;`dh.getFieldDataByCondition(表, 字段, 条件)` 返回单值。
+  - **`字段` 参数可传聚合表达式**(如 `"max(sed_emname)"`),一次查询同时完成「校验存在」+「取名称」:
+    无记录时返回 `DBNull`,`DBNull.ToString() == ""`,判空即可覆盖两种情况,可替代 `CheckExist`。
 - 表命名:工序表 `step`(`st_code`/`st_name`),工序类型表 `steptype`(`STP_CODE`/`STP_NAME`,注意是 STP_ 前缀)。
-- 员工上下线表 `loadman`:`LM_ID`(`loadman_seq.nextval`)/`LM_LINECODE`/`LM_EMCODE`/`LM_UPTIME`/`LM_DOWNTIME`/`LM_INMAN`;同构的 `loadmake`(工单上下线)有现成界面 `Query_LoadMake` 可作模板。
+- 员工上下线表 `loadman`:`LM_ID`(`loadman_seq.nextval`)/`LM_LINECODE`/`LM_EMCODE`/`LM_EMNAME`/`LM_UPTIME`/`LM_DOWNTIME`/`LM_INMAN`;同构的 `loadmake`(工单上下线)有现成界面 `Query_LoadMake` 可作模板。在线判定 = `lm_downtime is null`。
+- 人员名称来源 `SOPEMPLOYEEDETAIL`(`sed_emcode`/`sed_emname`):**同一 `sed_emcode` 可能多条**,取名称统一用 `max(sed_emname)` + `group by sed_emcode` 去重。
 - SQL 多为拼字符串 + Oracle 函数(`nvl`、`ROWNUM`、`DUAL`)。
 - **操作日志表 `commandlog`**:统一调 `LogicHandler.DoCommandLog(caller, userCode, makeCode, lineCode, sourceCode, operate, result, sncode, checkno)`(`UAS_MES_NEW.PublicMethod`),内部固定 insert + `commandlog_seq.nextval` + `sysdate`,调用方不传 ID/时间。惯例:`Tag.ToString(), User.UserCode, 工单号, User.UserLineCode, User.UserSourceCode, "操作名", "结果", sncode, ""`。
 
 ## 已知环境限制
 - 无法本地编译验证:`dotnet build` 卡在 NuGet restore;沙箱拦截 MSBuild.exe / csc.exe。
 - 改代码后需在 Visual Studio 中手动编译确认。
+- 自检手段:用 Python 剥离注释与字符串字面量后统计 `{}`/`()`/`[]` 平衡(脚本临时写到 `.workbuddy/`,用完删除)。
+- ⚠️ Edit 教训:`old_string` / `new_string` 的**末尾换行必须一致**,否则会把下一个方法签名和 `{` 挤到同一行(曾误伤 `UpLoadMan_CheckedChanged`)。
 
 ## 版本控制(git)
 - 仓库根在 **`C:\MES_NEW\mes-client`**(UAS_MES_YDCY 只是其中一个项目);远程 `mes-client` = `git@repos.ubtob.net:usoft/mes-client.git`(SSH,内网 10.1.81.211),分支 `master`,无 upstream 追踪。

+ 28 - 0
UAS_MES_YDCY/FunctionCode/Query/Query_LoadMan.Designer.cs

@@ -31,6 +31,7 @@ namespace UAS_MES_NEW.Query
             this.linecode_label = new System.Windows.Forms.Label();
             this.emcode_label = new System.Windows.Forms.Label();
             this.DGV = new System.Windows.Forms.DataGridView();
+            this.choose = new System.Windows.Forms.DataGridViewCheckBoxColumn();
             this.lm_id = new System.Windows.Forms.DataGridViewTextBoxColumn();
             this.lm_emcode = new System.Windows.Forms.DataGridViewTextBoxColumn();
             this.sed_emname = new System.Windows.Forms.DataGridViewTextBoxColumn();
@@ -41,6 +42,7 @@ namespace UAS_MES_NEW.Query
             this.DownLoadMan = new System.Windows.Forms.RadioButton();
             this.li_code = new UAS_MES_NEW.CustomControl.TextBoxWithIcon.SearchTextBox();
             this.em_code = new UAS_MES_NEW.CustomControl.TextBoxWithIcon.SnCollectionBox();
+            this.ConfirmDown = new UAS_MES_NEW.CustomControl.ButtonUtil.NormalButton();
             this.OperateResult = new UAS_MES_NEW.CustomControl.RichText.RichTextAutoBottom();
             ((System.ComponentModel.ISupportInitialize)(this.DGV)).BeginInit();
             this.SuspendLayout();
@@ -130,11 +132,27 @@ namespace UAS_MES_NEW.Query
             this.DownLoadMan.UseVisualStyleBackColor = true;
             this.DownLoadMan.CheckedChanged += new System.EventHandler(this.DownLoadMan_CheckedChanged);
             //
+            // ConfirmDown
+            //
+            this.ConfirmDown.BackColor = System.Drawing.Color.Transparent;
+            this.ConfirmDown.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
+            this.ConfirmDown.IsShowBorder = true;
+            this.ConfirmDown.Location = new System.Drawing.Point(1042, 26);
+            this.ConfirmDown.Margin = new System.Windows.Forms.Padding(4);
+            this.ConfirmDown.Name = "ConfirmDown";
+            this.ConfirmDown.Power = null;
+            this.ConfirmDown.Size = new System.Drawing.Size(140, 50);
+            this.ConfirmDown.TabIndex = 7;
+            this.ConfirmDown.Text = "确认下线";
+            this.ConfirmDown.UseVisualStyleBackColor = false;
+            this.ConfirmDown.Click += new System.EventHandler(this.ConfirmDown_Click);
+            //
             // DGV
             //
             this.DGV.AllowUserToAddRows = false;
             this.DGV.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
             this.DGV.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
+            this.choose,
             this.lm_id,
             this.lm_emcode,
             this.sed_emname,
@@ -148,6 +166,13 @@ namespace UAS_MES_NEW.Query
             this.DGV.Size = new System.Drawing.Size(1090, 640);
             this.DGV.TabIndex = 5;
             //
+            // choose
+            //
+            this.choose.HeaderText = "勾选";
+            this.choose.MinimumWidth = 10;
+            this.choose.Name = "choose";
+            this.choose.Width = 70;
+            //
             // lm_id
             //
             this.lm_id.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None;
@@ -215,6 +240,7 @@ namespace UAS_MES_NEW.Query
             this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
             this.ClientSize = new System.Drawing.Size(1560, 760);
+            this.Controls.Add(this.ConfirmDown);
             this.Controls.Add(this.li_code);
             this.Controls.Add(this.linecode_label);
             this.Controls.Add(this.em_code);
@@ -245,6 +271,8 @@ namespace UAS_MES_NEW.Query
         private System.Windows.Forms.RadioButton DownLoadMan;
         private System.Windows.Forms.DataGridView DGV;
         private CustomControl.RichText.RichTextAutoBottom OperateResult;
+        private CustomControl.ButtonUtil.NormalButton ConfirmDown;
+        private System.Windows.Forms.DataGridViewCheckBoxColumn choose;
         private System.Windows.Forms.DataGridViewTextBoxColumn lm_id;
         private System.Windows.Forms.DataGridViewTextBoxColumn lm_emcode;
         private System.Windows.Forms.DataGridViewTextBoxColumn sed_emname;

+ 71 - 7
UAS_MES_YDCY/FunctionCode/Query/Query_LoadMan.cs

@@ -33,7 +33,7 @@ namespace UAS_MES_NEW.Query
             li_code.Condition = "li_statuscode='AUDITED'";
             li_code.DbChange += Li_code_DbChange;
             //初始提示
-            OperateResult.AppendText(">>请选择线别,输入人员编号后回车执行" + (UpLoadMan.Checked ? "上线" : "下线") + "\n", Color.Black);
+            OperateResult.AppendText(">>请选择线别,输入人员编号后回车执行" + (UpLoadMan.Checked ? "上线" : "下线") + ";下线也可勾选列表后点\"确认下线\"批量处理\n", Color.Black);
             LoadGridData();
             em_code.Focus();
         }
@@ -75,8 +75,9 @@ namespace UAS_MES_NEW.Query
                     OperateResult.AppendText("<<请先选择线别\n", Color.Red, li_code);
                     return;
                 }
-                //上线:先校验人员编号存在于 SOPEMPLOYEEDETAIL
-                if (!dh.CheckExist("SOPEMPLOYEEDETAIL", "sed_emcode='" + emcode + "'"))
+                //上线:先校验人员编号存在于 SOPEMPLOYEEDETAIL,并取出人员名称(同一 sed_emcode 可能多条,取最大值聚合)
+                string emname = dh.getFieldDataByCondition("SOPEMPLOYEEDETAIL", "max(sed_emname)", "sed_emcode='" + emcode + "'").ToString().Trim();
+                if (emname == "")
                 {
                     OperateResult.AppendText("<<" + emcode + "人员编号不存在\n", Color.Red, em_code);
                     em_code.Clear();
@@ -100,9 +101,9 @@ namespace UAS_MES_NEW.Query
                     dh.ExecuteSql("update loadman set lm_downtime=sysdate where lm_emcode='" + emcode + "' and lm_downtime is null", "update");
                     OperateResult.AppendText(">>" + emcode + "已从" + oldLineCode + "自动下线\n", Color.Black);
                 }
-                dh.ExecuteSql("insert into loadman(lm_id,lm_linecode,lm_emcode,lm_uptime,lm_inman)" +
-                    "values(loadman_seq.nextval,'" + linecode + "','" + emcode + "',sysdate,'" + User.UserName + "')", "insert");
-                OperateResult.AppendText("<<" + emcode + "在" + linecode + "上线成功\n", Color.Green, em_code);
+                dh.ExecuteSql("insert into loadman(lm_id,lm_linecode,lm_emcode,lm_emname,lm_uptime,lm_inman)" +
+                    "values(loadman_seq.nextval,'" + linecode + "','" + emcode + "','" + emname + "',sysdate,'" + User.UserName + "')", "insert");
+                OperateResult.AppendText("<<" + emcode + " " + emname + " 在" + linecode + "上线成功\n", Color.Green, em_code);
             }
             else
             {
@@ -124,6 +125,69 @@ namespace UAS_MES_NEW.Query
             em_code.Focus();
         }
 
+        /// <summary>
+        /// 判断DGV中是否有勾选的行
+        /// </summary>
+        private bool IfCheckRow()
+        {
+            for (int i = 0; i < DGV.Rows.Count; i++)
+            {
+                if (DGV.Rows[i].Cells["choose"].Value != null && DGV.Rows[i].Cells["choose"].FormattedValue.ToString() == "True")
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// 批量下线:把DGV中勾选的行按 lm_id 逐条置 lm_downtime=sysdate
+        /// (DGV只加载未下线记录,勾选行必然在线,直接按主键更新即可)
+        /// </summary>
+        private void ConfirmDown_Click(object sender, EventArgs e)
+        {
+            if (!IfCheckRow())
+            {
+                OperateResult.AppendText("<<请先勾选需要下线的人员\n", Color.Red, em_code);
+                return;
+            }
+            DataGridViewRow row;
+            string lmId;
+            string emcode;
+            string emname;
+            string linecode;
+            int sucCount = 0;
+            int failCount = 0;
+            for (int i = 0; i < DGV.Rows.Count; i++)
+            {
+                row = DGV.Rows[i];
+                //只处理勾选且有主键的行
+                if (row.Cells["choose"].FormattedValue == null || row.Cells["choose"].FormattedValue.ToString() != "True")
+                    continue;
+                if (row.Cells["lm_id"].Value == null || row.Cells["lm_id"].Value.ToString() == "")
+                    continue;
+                lmId = row.Cells["lm_id"].Value.ToString();
+                emcode = row.Cells["lm_emcode"].Value == null ? "" : row.Cells["lm_emcode"].Value.ToString();
+                emname = row.Cells["sed_emname"].Value == null ? "" : row.Cells["sed_emname"].Value.ToString();
+                linecode = row.Cells["lm_linecode"].Value == null ? "" : row.Cells["lm_linecode"].Value.ToString();
+                try
+                {
+                    dh.ExecuteSql("update loadman set lm_downtime=sysdate where lm_id='" + lmId + "' and lm_downtime is null", "update");
+                    sucCount++;
+                    OperateResult.AppendText("<<" + emcode + " " + emname + " 已从" + linecode + "下线\n", Color.Green);
+                }
+                catch (Exception ex)
+                {
+                    failCount++;
+                    OperateResult.AppendText("<<" + emcode + " " + emname + " 下线失败:" + ex.Message + "\n", Color.Red);
+                }
+            }
+            OperateResult.AppendText(">>本次下线成功 " + sucCount + " 人" + (failCount > 0 ? ",失败 " + failCount + " 人" : "") + "\n", Color.Black);
+            //刷新后勾选状态会因重新绑定被清空,属预期
+            LoadGridData();
+            em_code.Focus();
+        }
+
         private void UpLoadMan_CheckedChanged(object sender, EventArgs e)
         {
             if (UpLoadMan.Checked)
@@ -137,7 +201,7 @@ namespace UAS_MES_NEW.Query
         {
             if (DownLoadMan.Checked)
             {
-                OperateResult.AppendText(">>已切换到下线模式,输入人员编号回车下线\n", Color.Black);
+                OperateResult.AppendText(">>已切换到下线模式,输入人员编号回车下线,或勾选列表后点\"确认下线\"批量下线\n", Color.Black);
                 em_code.Focus();
             }
         }