Explorar o código

增加周期性点检、GP12

jingbo hai 1 mes
pai
achega
cfd823dfc0
Modificáronse 100 ficheiros con 25025 adicións e 104 borrados
  1. 3 0
      .vscode/settings.json
  2. 227 0
      _build_mes_summary.py
  3. 183 0
      _build_screenshot_plan.py
  4. 82 0
      _extract_mes_progress.py
  5. 1850 0
      _extract_mes_progress_output.txt
  6. 1851 0
      _extract_out_cp65001.txt
  7. 461 0
      _gen_mes_ppt.py
  8. 343 0
      _mes_milestone_summary.txt
  9. 445 0
      _mes_screenshot_plan.txt
  10. BIN=BIN
      _ppt_img_cache/ERP对接-读取数据_w1000.png
  11. BIN=BIN
      _ppt_img_cache/OCR测试_w1000.png
  12. BIN=BIN
      _ppt_img_cache/OP050_w1000.jpg
  13. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_60b4a34fc5a9f4ca3195ea55e3a2ace1-787d700c-4131-4806-abbf-ff01c10c9679_w1200.png
  14. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-15f57559-f837-464f-af8a-b4e656767283_w1200.png
  15. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-1759c9f1-d702-4b17-b35b-0d8d8cfd66cf_w1200.png
  16. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-220cd888-53b8-4247-b74f-9b3b5d56a8bf_w1200.png
  17. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-74f8c1fc-7426-4472-9dda-a4abdb79cc23_w1200.png
  18. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-8dd92c21-a7aa-48d0-81a3-4a1107492a9a_w1200.png
  19. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-a4a18a5e-f2ab-42a3-a9c5-2bcfae5d1233_w1200.png
  20. BIN=BIN
      _ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-eb11b4ec-13e0-4792-833d-13a7d304a2a5_w1200.png
  21. BIN=BIN
      _ppt_img_cache/pda主页面_w1000.jpg
  22. BIN=BIN
      _tmp_decompile/CrudService.javap.txt
  23. 1426 0
      _tmp_decompile/EmpUserController.javap.txt
  24. 707 0
      _tmp_decompile/EmpUserServiceSupport.javap.txt
  25. 211 0
      _tmp_decompile/EmpUser_treeData.txt
  26. 1117 0
      _tmp_decompile/OfficeController.full.txt
  27. 857 0
      _tmp_decompile/OfficeController.javap.txt
  28. 1950 0
      _tmp_decompile/OfficeController.verbose.txt
  29. BIN=BIN
      _tmp_decompile/OfficeServiceSupport.local.txt
  30. 340 0
      _tmp_decompile/Office_treeData.txt
  31. 7 0
      _tmp_decompile/RoleController.javap.txt
  32. BIN=BIN
      _tmp_decompile/SysAutoConfiguration.local.txt
  33. BIN=BIN
      _tmp_decompile/TreeService.javap.txt
  34. 863 0
      db/mes_cloud_export_20260812_135015.sql
  35. 17 0
      db/mysql/add_erp_prd_in_stock_menu.sql
  36. 120 0
      db/mysql/add_mes_batch_quality.sql
  37. 84 0
      db/mysql/add_mes_batch_sn_batch_status.sql
  38. 29 0
      db/mysql/add_mes_batch_sn_erp_instock.sql
  39. 3 0
      db/mysql/add_mes_batch_sn_erp_transfer.sql
  40. 46 0
      db/mysql/add_mes_batch_sn_status_packing.sql
  41. 18 0
      db/mysql/add_mes_device_prevent_task_multi_user.sql
  42. 56 0
      db/mysql/add_mes_device_state_stop.sql
  43. 191 0
      db/mysql/add_mes_ding_msg.sql
  44. 66 0
      db/mysql/add_mes_erp_in_stock_inspect.sql
  45. 48 0
      db/mysql/add_mes_erp_interaction_log.sql
  46. 4 0
      db/mysql/add_mes_erp_interaction_log_response_duration.sql
  47. 64 0
      db/mysql/add_mes_erp_prd_instock_bind_menu.sql
  48. 16 0
      db/mysql/add_mes_erp_schedule_board_menu.sql
  49. 35 0
      db/mysql/add_mes_icons_category.sql
  50. 12 0
      db/mysql/add_mes_icons_category_default.sql
  51. 17 0
      db/mysql/add_mes_icons_category_id.sql
  52. 10 0
      db/mysql/add_mes_icons_grouped_config.sql
  53. 49 0
      db/mysql/add_mes_integrated_screen.sql
  54. 52 0
      db/mysql/add_mes_line_process_consume.sql
  55. 16 0
      db/mysql/add_mes_line_process_device.sql
  56. 68 0
      db/mysql/add_mes_material_kc_total.sql
  57. 102 0
      db/mysql/add_mes_pda_scan_record_menu.sql
  58. 39 0
      db/mysql/add_mes_process_consume_log.sql
  59. 109 0
      db/mysql/add_mes_product_glue_duration.sql
  60. 30 0
      db/mysql/add_mes_product_gp12_check.sql
  61. 53 0
      db/mysql/add_mes_qm_param_menu.sql
  62. 198 0
      db/mysql/add_mes_screen_notice_honor.sql
  63. 139 0
      db/mysql/add_mes_welder_param_record.sql
  64. 53 0
      db/mysql/add_mes_welding_param_menu.sql
  65. 30 0
      db/mysql/fix_employee_company_code_110.sql
  66. 174 0
      docs/PDAGP12检查接口说明.md
  67. 66 0
      docs/PDA产品修改记录接口说明.md
  68. 147 0
      docs/PDA功能模块接口说明.md
  69. 332 0
      docs/PDA开班点检接口说明.md
  70. 227 0
      docs/PDA批次码发货检接口说明.md
  71. 288 0
      docs/PDA返修接口说明.md
  72. 91 0
      docs/_pptx_inspect_out.txt
  73. BIN=BIN
      docs/ccd/MES与CCD接口.xlsx
  74. 263 0
      docs/ccd/MES与CCD接口说明.md
  75. 2551 0
      docs/erp/PDA_ERP接口汇总.md
  76. 464 0
      docs/erp/PDA批次码接口说明.md
  77. 856 0
      docs/erp/PDA来料检接口说明.md
  78. 416 0
      docs/erp/PDA生产入库接口说明.md
  79. 439 0
      docs/erp/PDA生产领料接口说明.md
  80. 864 0
      docs/erp/PDA直接调拨接口说明.md
  81. BIN=BIN
      docs/ppt_effect_shots/1_mes_screen.png
  82. BIN=BIN
      docs/ppt_effect_shots/2_oee_screen.png
  83. BIN=BIN
      docs/ppt_effect_shots/3_energy_screen.png
  84. BIN=BIN
      docs/ppt_effect_shots/4_erp_board.png
  85. BIN=BIN
      docs/ppt_effect_shots/5_trace_detail.png
  86. BIN=BIN
      docs/ppt_effect_shots/6_qm_white.png
  87. BIN=BIN
      docs/ppt_effect_shots/7_arc_weld.png
  88. BIN=BIN
      docs/ppt_effect_shots/8_pda_hev.png
  89. 49 0
      docs/六安裕洋MES实施进度_PPT大纲.md
  90. BIN=BIN
      docs/六安裕洋MES实施进度汇报.pptx
  91. 381 0
      docs/工位客户端设备状态上报接口说明.md
  92. BIN=BIN
      electron/dist/MES Cloud-1.0.0.exe
  93. BIN=BIN
      electron/dist/MES Cloud-1.0.0.exe.blockmap
  94. BIN=BIN
      electron/dist/win-unpacked/resources/elevate.exe
  95. 1600 0
      scripts/MesDbBackup.cs
  96. BIN=BIN
      scripts/MesDbBackup.exe
  97. 274 0
      scripts/backup-db.ps1
  98. 618 0
      scripts/backup-ui-server.ps1
  99. 228 104
      scripts/backup-ui.html
  100. 0 0
      scripts/db-config.json

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "java.compile.nullAnalysis.mode": "disabled"
+}

+ 227 - 0
_build_mes_summary.py

@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+import os
+import re
+from datetime import datetime
+from openpyxl import load_workbook
+
+BASE = r"c:\work\demo\dayang\mescloud"
+EXTRACT = os.path.join(BASE, "_extract_mes_progress_output.txt")
+STATUS_XLSX = r"c:\Users\j2742\Desktop\temp\大洋\实施文档\MES实施现状-260716.xlsx"
+PIC_DIR = r"c:\Users\j2742\Desktop\temp\大洋\pic"
+OUT = os.path.join(BASE, "_mes_milestone_summary.txt")
+
+CELL_RE = re.compile(r"^([A-Z]+)(\d+): (.+)$")
+
+
+def fmt_val(v):
+    if v is None:
+        return ""
+    if isinstance(v, datetime):
+        return v.strftime("%Y-%m-%d")
+    return str(v).strip()
+
+
+def extract_xlsx(path, max_rows=200, max_cols=30):
+    lines = []
+    wb = load_workbook(path, data_only=True)
+    lines.append(f"文件: {path}")
+    lines.append(f"工作表: {', '.join(wb.sheetnames)}")
+    for name in wb.sheetnames:
+        ws = wb[name]
+        lines.append("")
+        lines.append(f"--- 工作表: {name} (维度 {ws.dimensions}) ---")
+        for row in ws.iter_rows(min_row=1, max_row=max_rows, min_col=1, max_col=max_cols):
+            for cell in row:
+                v = cell.value
+                if v is not None and str(v).strip() != "":
+                    lines.append(f"  {cell.coordinate}: {fmt_val(v)}")
+    wb.close()
+    return lines
+
+
+def parse_extract_sheet(text, sheet_name):
+    pattern = rf"^SHEET: {re.escape(sheet_name)}\s*$"
+    lines = text.splitlines()
+    start = None
+    for i, line in enumerate(lines):
+        if re.match(pattern, line.strip()):
+            start = i
+            break
+    if start is None:
+        return []
+    out = []
+    for line in lines[start + 1:]:
+        stripped = line.strip()
+        if stripped.startswith("SHEET:"):
+            break
+        if stripped.startswith("--- Non-empty cells"):
+            break
+        m = CELL_RE.match(stripped)
+        if m:
+            out.append((m.group(1), int(m.group(2)), m.group(3)))
+    return out
+
+
+def cells_to_rows(cells):
+    rows = {}
+    for col, row, val in cells:
+        rows.setdefault(row, {})[col] = val
+    return rows
+
+
+def list_images(root):
+    exts = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".ico"}
+    found = []
+    for dirpath, _, filenames in os.walk(root):
+        for fn in filenames:
+            if os.path.splitext(fn)[1].lower() in exts:
+                rel = os.path.relpath(os.path.join(dirpath, fn), root)
+                found.append(rel.replace("\\", "/"))
+    return sorted(found, key=lambda x: x.lower())
+
+
+with open(EXTRACT, "r", encoding="utf-8") as f:
+    extract_text = f.read()
+
+sections = []
+sections.append("=" * 80)
+sections.append("六安裕洋 MES 实施里程碑与截图资源汇总")
+sections.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+sections.append("数据来源: 大洋-京宜安MES实施进度.xlsx、MES实施现状-260716.xlsx、pic/ 目录")
+sections.append("=" * 80)
+
+# A
+sections.append("")
+sections.append("A. 总进度 — 各周完成率(来源:总进度)")
+sections.append("-" * 80)
+zong_cells = parse_extract_sheet(extract_text, "总进度")
+zong_rows = cells_to_rows(zong_cells)
+sections.append("汇总表:")
+sections.append("  实施阶段 | 开始时间 | 结束时间 | 具体任务(周) | 任务数 | 完成数 | 合格率")
+for r in sorted(k for k in zong_rows if k >= 3):
+    d = zong_rows[r]
+    sections.append(
+        f"  {d.get('A','')} | {d.get('B','')} | {d.get('C','')} | {d.get('D','')} | "
+        f"{d.get('E','')} | {d.get('F','')} | {d.get('G','')}"
+    )
+
+# B
+sections.append("")
+sections.append("B. 全流程详细实施计划 — 阶段一至七(关键交付与状态)")
+sections.append("-" * 80)
+plan_cells = parse_extract_sheet(extract_text, "全流程详细实施计划")
+plan_rows = cells_to_rows(plan_cells)
+
+phases = []
+for r in sorted(plan_rows):
+    a = plan_rows[r].get("A", "")
+    if a and "阶段" in a:
+        phases.append((r, a.replace("\n", " ")))
+
+for idx, (prow, pname) in enumerate(phases):
+    end_row = phases[idx + 1][0] if idx + 1 < len(phases) else max(plan_rows) + 1
+    sections.append("")
+    sections.append(f"【{pname}】")
+    for row in range(prow + 1, end_row):
+        d = plan_rows.get(row, {})
+        sub = d.get("B", "")
+        if sub in ("项目准备", "软件框架搭建", "系统蓝图设计", "系统开发", "系统上线", "后续对接", "交付软件", "序号"):
+            continue
+        task = d.get("C", "")
+        if not task and not d.get("H"):
+            continue
+        seq = d.get("B", "")
+        content = d.get("H", "")
+        status = d.get("P", "")
+        deliverable = d.get("J", "")
+        parts = []
+        if seq:
+            parts.append(f"{seq}. {task}" if task else f"{seq}.")
+        else:
+            parts.append(task)
+        if content:
+            parts.append(f"工作: {content}")
+        if deliverable:
+            parts.append(f"输出: {deliverable}")
+        if status:
+            parts.append(f"状态: {status}")
+        sections.append("  " + " | ".join(p for p in parts if p))
+
+# C
+sections.append("")
+sections.append("C. 七月份第一周 / 七月份第二周 — 任务明细")
+sections.append("-" * 80)
+
+WEEKLY_COLS = ("B", "C", "D", "E", "F", "G", "H", "I", "J", "K")
+
+def format_weekly_task(row_num, d):
+    lines = []
+    title = d.get("B", "") or d.get("C", "")
+    seq = d.get("A", "")
+    head = f"  行{row_num}"
+    if seq:
+        head += f" / 序号{seq}"
+    lines.append(head + ":")
+    lines.append(f"    任务名称: {title}")
+    if d.get("C"):
+        lines.append(f"    工作内容: {d.get('C')}")
+    lines.append(f"    甲方对接人: {d.get('D', '')}")
+    lines.append(f"    乙方对接人: {d.get('E', '')}")
+    if d.get("F") or d.get("G"):
+        lines.append(f"    计划: {d.get('F', '')} ~ {d.get('G', '')}")
+    lines.append(f"    状态: {d.get('H', '')}")
+    if d.get("I"):
+        lines.append(f"    进展说明: {d.get('I')}")
+    if d.get("J"):
+        lines.append(f"    备注: {d.get('J')}")
+    k = d.get("K", "")
+    if k and not str(k).startswith("=DISPIMG"):
+        lines.append(f"    备注(K): {k}")
+    return lines
+
+for sheet in ("七月份第一周", "七月份第二周"):
+    sections.append("")
+    sections.append(f"=== {sheet} ===")
+    cells = parse_extract_sheet(extract_text, sheet)
+    rows = cells_to_rows(cells)
+    data_rows = [r for r in sorted(rows) if r >= 3 and any(rows[r].get(c) for c in WEEKLY_COLS)]
+    for r in data_rows:
+        sections.extend(format_weekly_task(r, rows[r]))
+
+# D
+sections.append("")
+sections.append("D. 问题清单(来源:问题清单工作表)")
+sections.append("-" * 80)
+prob_cells = parse_extract_sheet(extract_text, "问题清单")
+prob_rows = cells_to_rows(prob_cells)
+for r in sorted(prob_rows):
+    if r < 3:
+        continue
+    d = prob_rows[r]
+    if not (d.get("B") or d.get("C")):
+        continue
+    sections.append(
+        f"  {d.get('A', r-2)}. {d.get('B', '')} | 内容: {d.get('C', '')} | "
+        f"对接人: {d.get('D', '')} | 后续: {d.get('E', '')} | 进度: {d.get('F', '')}"
+    )
+
+# E
+sections.append("")
+sections.append("E. pic/ 目录截图文件完整列表")
+sections.append("-" * 80)
+imgs = list_images(PIC_DIR)
+sections.append(f"共 {len(imgs)} 个图片文件:")
+for im in imgs:
+    sections.append(f"  {im}")
+
+# F
+sections.append("")
+sections.append("F. MES实施现状-260716.xlsx 完整内容")
+sections.append("-" * 80)
+sections.extend(extract_xlsx(STATUS_XLSX))
+
+with open(OUT, "w", encoding="utf-8") as f:
+    f.write("\n".join(sections) + "\n")
+
+print(f"Wrote {OUT}")
+print(f"Lines: {len(sections)}")

+ 183 - 0
_build_screenshot_plan.py

@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+import os
+import re
+from datetime import datetime
+
+PIC_DIR = r"c:\Users\j2742\Desktop\temp\大洋\pic"
+SUMMARY = r"c:\work\demo\dayang\mescloud\_mes_milestone_summary.txt"
+OUT = r"c:\work\demo\dayang\mescloud\_mes_screenshot_plan.txt"
+
+def list_pic_files(root):
+    exts = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".ico"}
+    found = []
+    for dirpath, _, filenames in os.walk(root):
+        for fn in filenames:
+            if os.path.splitext(fn)[1].lower() in exts:
+                rel = os.path.relpath(os.path.join(dirpath, fn), root)
+                found.append(rel.replace("\\", "/"))
+    return sorted(found, key=lambda x: x.lower())
+
+def extract_sections(text):
+    markers = {
+        "A": r"^A\. 总进度",
+        "B": r"^B\. 全流程",
+        "C": r"^C\. 七月份",
+        "D": r"^D\. 问题清单",
+        "F": r"^F\. MES实施现状",
+    }
+    end_re = re.compile(r"^[A-F]\. ")
+    lines = text.splitlines()
+    sections = {}
+    for key, start_pat in markers.items():
+        start = None
+        for i, line in enumerate(lines):
+            if re.match(start_pat, line):
+                start = i
+                break
+        if start is None:
+            sections[key] = "(未找到)"
+            continue
+        end = len(lines)
+        for j in range(start + 1, len(lines)):
+            if end_re.match(lines[j]) and not re.match(start_pat, lines[j]):
+                end = j
+                break
+        sections[key] = "\n".join(lines[start:end]).strip()
+    return sections
+
+with open(SUMMARY, "r", encoding="utf-8") as f:
+    summary_text = f.read()
+
+pics = list_pic_files(PIC_DIR)
+secs = extract_sections(summary_text)
+
+lines = []
+lines.append("=" * 80)
+lines.append("MES 截图规划与能力现状分析")
+lines.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+lines.append("=" * 80)
+
+lines.append("")
+lines.append("一、pic/ 目录完整文件列表(相对路径,共 %d 个)" % len(pics))
+lines.append("-" * 80)
+for p in pics:
+    lines.append("  " + p)
+
+lines.append("")
+lines.append("=" * 80)
+lines.append("二、里程碑摘要引用(来自 _mes_milestone_summary.txt)")
+lines.append("=" * 80)
+
+for label in ("A", "B", "C", "D", "F"):
+    lines.append("")
+    lines.append(secs[label])
+    lines.append("")
+
+# Analysis
+lines.append("=" * 80)
+lines.append("三、已实现能力 vs 进行中/待完善(基于 Excel 与现状表)")
+lines.append("=" * 80)
+
+analysis = """
+【总体进度(A)】
+- 五月份:第一周 100%;第二周 66.67%(9 项完成 6);第三周 77.78%;第四周 100%。
+- 六月份:第一~三周均为 100%;第四周 12.50%(8 项仅完成 1,培训/手册类滞后于计划统计)。
+- 七月份第一周任务表显示培训、PDA(来料检/生产领料)、流程测试、驻场均已「完成」。
+- 七月份第二周:PDA 返修、大屏适配、115 工作站、实件测试、周会、驻场为「进行中」;CCD 工位对接「完成」。
+
+【已实现 / 已上线 / 试运行(生产或试点)】
+
+1. 基础平台与环境(阶段一~二,B 均为完成)
+   - 开发/生产环境:JDK、Tomcat、MySQL、MES 后端框架部署。
+   - 组织权限、系统配置、菜单与导入导出等后台基础能力。
+
+2. HEV 产线与工位客户端(阶段四,完成;F 现状确认)
+   - 镭雕工位、HEV 工作站:已采集数据到 MES(F-数据采集)。
+   - 点焊 OP060-OP130、弧焊 OP050/140/150/160、气密 OP190/220、拉铆 OP210 等工位客户端/逻辑开发在计划中标记完成;五月份第二周部分焊机/PLC 曾「进行中」,后续周报称 HEV 点位开发完成待测试。
+   - 气密:交互逻辑与客户端完成;气密数据采集在 F 中列为等待工件流转后陆续上线。
+   - 扫码枪工位软件、工控机/大屏/网络/IP 表:完成。
+
+3. 大屏与看板(完成 + F)
+   - 大屏页面开发、工业大屏安装完成;F 称「目前显示无问题」,七月份第二周「大屏显示适配」进行中(分辨率/布局优化)。
+
+4. PDA 移动端(完成 + C)
+   - PDA 模块开发(计划完成);七月第一周 PDA:来料检、生产领料已完成;七月第二周返修功能 PDA 化进行中。
+   - pic 可用:pda.jpg、pda主页面.jpg、pda软件.png、生产领料单.png、采购入库单.png 等。
+
+5. 过站、追溯、OCR(阶段五,完成)
+   - 工位过站流程测试、追溯测试、OCR 电泳后工件测试:计划状态完成。
+   - pic:OCR测试.png、白件气密时间.png、ccd时间.png。
+
+6. 返修与质量(阶段五 + C/D)
+   - 返修流程开发测试完成;返修数据录入完成。
+   - 质量管理模块开发(计划表中无 P 列「完成」标注,但六月份第三周质量模块与返修录入周报为完成)。
+   - 七月 PDA 返修进行中;pic:返修数据.png、返修问题清单.png、报废流程.png。
+
+7. 设备 / 物料 / 员工主数据(阶段五~六,周报完成)
+   - 设备管理、物料管理模块开发与信息录入:完成。
+   - 员工、班次、权限、组织架构录入:完成。
+   - pic:设备清单.png、设备管理清单.png、物料管理.png、员工信息管理.png、人员信息录入.png、班次.png 等。
+
+8. ERP 对接(阶段六,计划完成;F 与七月计划)
+   - 计划表:ERP 对接方案、ERP 开发均「完成」;六月份第二~四周 ERP 对接/开发周报完成。
+   - F(7/16):后台「ERP 对接需要梳理修改部分功能」——业务上仍处优化/二阶段。
+   - 七月第四周计划(F):ERP 梳理(7/20-7/24)。
+   - pic:ERP对接.png、ERP对接-读取数据.png、ERP流程.png、ERP对接文档.png。
+
+9. CCD 工位(C 七月第二周)
+   - CCD 工位对接:状态「完成」。
+   - pic:ccd时间.png(及对接文档目录 MES与CCD接口.xlsx 在实施包内)。
+
+10. 培训、手册与驻场(阶段七 + C)
+    - 操作手册、管理员/生产/操作工培训、驻场支持:计划表均为完成。
+    - 七月第一周培训(后台与现场)完成;现场生产培训备注依赖 HEV 生产开机。
+
+11. 能耗 / 电表
+    - 六月份电表安装调试、数据集成周报完成;pic:电表.png、能耗大屏.png。
+
+【进行中 / 待验证 / 待产线配合】
+
+1. 115 线体(B 计划完成 vs C/F)
+   - 115 弧焊/气密工位采集在总计划为完成;F:115 线体「等待工件加工」。
+   - 七月第二周「115 工作站对接」:进行中。
+
+2. 拉铆 / 装配 / 全实件联调(C/D)
+   - 问题清单:HEV 拉铆控制器维修、工艺参数待提供;实件测试「等待拉铆工序测试」。
+   - F:气密、装配工位等待工件流转陆续上线。
+
+3. PDA 返修、大屏适配(C 七月第二周):进行中。
+
+4. ERP 深度对接与流程梳理(F + 工作计划):非一次性关闭,需截图「现状 + 梳理中」。
+
+5. 登录安全(D):手机验证码/邮箱方案讨论中。
+
+6. 设备侧遗留(D/F):气密网线/MES 点位观察、设备点位交付节奏、OP100 异常停机等。
+
+【建议截图映射(能力 → pic 文件)】
+
+| 能力域 | 建议素材(pic/ 相对路径) | 说明 |
+|--------|---------------------------|------|
+| 系统总览 | 系统主页.png、系统管理.png、服务登录页面.png | 后台入口与模块 |
+| HEV 工作站 | HEV工作站对接逻辑流程图.png、HEV.jpg、OP050.jpg、拉铆工位.png | 客户端与工位 |
+| 气密 | 气密.jpg、气密工位.jpg、气密交互逻辑流程图.png、气密数据上传.jpg | 逻辑+现场 |
+| 大屏 | 大屏.png、大屏管理页面.png、生产大屏管理.jpg、排产计划.png、排产进度.png、小时产量.png、能耗大屏.png | 多类看板 |
+| PDA | pda主页面.jpg、pda软件.png、生产领料单.png | 已交付功能;返修待补新图 |
+| ERP | ERP流程.png、ERP对接.png、ERP对接-读取数据.png | 对接与读取 |
+| OCR/CCD | OCR测试.png、ccd时间.png | 识别与 CCD |
+| 质量返修 | 返修问题清单.png、返修数据.png、质量管理.png、报废流程.png | 质量域 |
+| 设备物料 | 设备管理清单.png、物料管理.png、电表.png | 主数据与能耗 |
+| 权限组织 | 权限管理.png、机构.png、员工岗位.png、蓝图.png | 实施文档类 |
+| 试运行说明 | 试运行情况说明/*.jpg | 异常场景说明 |
+
+【交付结论(一句话)】
+MES 已在 HEV 线实现镭雕与工作站数据采集、大屏展示、PDA 仓储类功能、后台设备/物料/质量/权限主数据、ERP 一期对接与 CCD 对接;115 线、拉铆实件全链路、PDA 返修、大屏适配及 ERP 业务梳理仍在推进,宜按「已实现(附截图)/ 进行中(附周报状态)/ 计划(F 七月第四周)」三组组织对外汇报材料。
+"""
+
+lines.append(analysis.strip())
+
+with open(OUT, "w", encoding="utf-8") as f:
+    f.write("\n".join(lines) + "\n")
+
+print("Wrote", OUT)
+print("Lines:", len(lines))
+print("Pics:", len(pics))

+ 82 - 0
_extract_mes_progress.py

@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+import os
+import sys
+
+try:
+    from openpyxl import load_workbook
+except ImportError:
+    import subprocess
+    subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl", "-q"])
+    from openpyxl import load_workbook
+
+out_path = r"c:\work\demo\dayang\mescloud\_extract_mes_progress_output.txt"
+out = open(out_path, "w", encoding="utf-8")
+
+def p(s=""):
+    out.write(s + "\n")
+
+xlsx_path = r"c:\Users\j2742\Desktop\temp\大洋\实施文档\大洋-京宜安MES实施进度.xlsx"
+
+p("=" * 80)
+p("EXCEL FILE: " + xlsx_path)
+p("=" * 80)
+
+wb = load_workbook(xlsx_path, data_only=True)
+p("\n1. SHEET NAMES:")
+for i, name in enumerate(wb.sheetnames, 1):
+    p(f"  {i}. {name}")
+
+max_rows, max_cols = 150, 20
+
+for sheet_name in wb.sheetnames:
+    ws = wb[sheet_name]
+    p("\n" + "=" * 80)
+    p(f"SHEET: {sheet_name}")
+    p(f"Dimensions (reported): {ws.dimensions}")
+    p("=" * 80)
+    count = 0
+    for row in ws.iter_rows(min_row=1, max_row=max_rows, min_col=1, max_col=max_cols):
+        for cell in row:
+            v = cell.value
+            if v is not None and str(v).strip() != "":
+                p(f"  {cell.coordinate}: {v}")
+                count += 1
+    p(f"  --- Non-empty cells in range (1-{max_rows}, A-T): {count} ---")
+
+wb.close()
+
+
+def list_dir_depth(path, max_depth, current_depth=0, prefix=""):
+    if not os.path.isdir(path):
+        p(f"  [NOT FOUND OR NOT A DIR]: {path}")
+        return
+    try:
+        entries = sorted(os.listdir(path))
+    except Exception as e:
+        p(f"  [ERROR]: {path}: {e}")
+        return
+    for name in entries:
+        full = os.path.join(path, name)
+        if os.path.isdir(full):
+            p(f"{prefix}[DIR]  {name}/")
+            if current_depth + 1 < max_depth:
+                list_dir_depth(full, max_depth, current_depth + 1, prefix + "    ")
+        else:
+            try:
+                size = os.path.getsize(full)
+                p(f"{prefix}[FILE] {name} ({size} bytes)")
+            except Exception:
+                p(f"{prefix}[FILE] {name}")
+
+p("\n" + "=" * 80)
+p(r"2. DIRECTORY: c:\Users\j2742\Desktop\temp\大洋\实施文档 (depth 2)")
+p("=" * 80)
+list_dir_depth(r"c:\Users\j2742\Desktop\temp\大洋\实施文档", 2)
+
+p("\n" + "=" * 80)
+p(r"3. DIRECTORY: c:\Users\j2742\Desktop\temp\大洋 (depth 2)")
+p("=" * 80)
+list_dir_depth(r"c:\Users\j2742\Desktop\temp\大洋", 2)
+
+out.close()
+print("Wrote", out_path)

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1850 - 0
_extract_mes_progress_output.txt


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1851 - 0
_extract_out_cp65001.txt


+ 461 - 0
_gen_mes_ppt.py

@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+"""生成六安裕洋MES实施进度汇报 PPT(含甘特图)"""
+from __future__ import annotations
+
+import shutil
+from datetime import date, timedelta
+from pathlib import Path
+
+from PIL import Image
+from pptx import Presentation
+from pptx.dml.color import RGBColor
+from pptx.enum.shapes import MSO_SHAPE
+from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
+from pptx.oxml.ns import nsmap
+from pptx.oxml import parse_xml
+from pptx.util import Inches, Pt, Emu
+
+# ---------- 路径 ----------
+PIC_DIR = Path(r"C:\Users\j2742\Desktop\temp\大洋\pic")
+ASSETS_DIR = Path(r"C:\Users\j2742\.cursor\projects\c-work-demo-dayang-mescloud\assets")
+OUT1 = Path(r"C:\Users\j2742\Desktop\temp\大洋\实施文档\六安裕洋MES实施进度汇报.pptx")
+OUT2 = Path(r"c:\work\demo\dayang\mescloud\docs\六安裕洋MES实施进度汇报.pptx")
+CACHE_DIR = Path(r"c:\work\demo\dayang\mescloud\_ppt_img_cache")
+
+# 用户精选实时效果图(第2页)
+EFFECT_IMAGES = [
+    ("生产管理大屏", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-eb11b4ec-13e0-4792-833d-13a7d304a2a5.png"),
+    ("设备综合效率", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-74f8c1fc-7426-4472-9dda-a4abdb79cc23.png"),
+    ("能源监控大屏", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-1759c9f1-d702-4b17-b35b-0d8d8cfd66cf.png"),
+    ("ERP排产看板", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-220cd888-53b8-4247-b74f-9b3b5d56a8bf.png"),
+    ("生产追溯明细", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-a4a18a5e-f2ab-42a3-a9c5-2bcfae5d1233.png"),
+    ("白件气密管理", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-8dd92c21-a7aa-48d0-81a3-4a1107492a9a.png"),
+    ("弧焊过程参数", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-15f57559-f837-464f-af8a-b4e656767283.png"),
+    ("PDA产线扫码", ASSETS_DIR / "c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_60b4a34fc5a9f4ca3195ea55e3a2ace1-787d700c-4131-4806-abbf-ff01c10c9679.png"),
+]
+# ---------- 配色 ----------
+HEADER_BG = RGBColor(0x0B, 0x3D, 0x5C)
+ACCENT_GREEN = RGBColor(0x1B, 0x8A, 0x5A)
+TEAL_BAR = RGBColor(0x0D, 0x7A, 0x6F)
+TEAL_LIGHT = RGBColor(0x2A, 0xA8, 0x98)
+AXIS_GRAY = RGBColor(0x90, 0xA4, 0xAE)
+TEXT_DARK = RGBColor(0x1A, 0x2A, 0x3A)
+TEXT_MUTED = RGBColor(0x5A, 0x6A, 0x7A)
+WHITE = RGBColor(0xFF, 0xFF, 0xFF)
+CARD_BG = RGBColor(0xF4, 0xF7, 0xFA)
+ROW_ALT = RGBColor(0xEE, 0xF4, 0xF8)
+LINE_SOFT = RGBColor(0xD0, 0xDC, 0xE4)
+
+SLIDE_W = Inches(13.333)
+SLIDE_H = Inches(7.5)
+
+
+def set_run_font(run, name="微软雅黑", size=12, bold=False, color=TEXT_DARK):
+    run.font.name = name
+    run.font.size = Pt(size)
+    run.font.bold = bold
+    run.font.color.rgb = color
+    # 东亚字体
+    rPr = run._r.get_or_add_rPr()
+    ea = rPr.find("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}rFonts")
+    # 使用 a:ea / a:latin
+    from pptx.oxml.ns import qn
+    for tag in ("latin", "ea", "cs"):
+        el = rPr.find(qn(f"a:{tag}"))
+        if el is None:
+            el = parse_xml(
+                f'<a:{tag} xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" typeface="{name}"/>'
+            )
+            rPr.append(el)
+        else:
+            el.set("typeface", name)
+
+
+def add_textbox(slide, left, top, width, height, text, size=12, bold=False, color=TEXT_DARK, align=PP_ALIGN.LEFT, font="微软雅黑"):
+    box = slide.shapes.add_textbox(left, top, width, height)
+    tf = box.text_frame
+    tf.word_wrap = True
+    p = tf.paragraphs[0]
+    p.alignment = align
+    run = p.add_run()
+    run.text = text
+    set_run_font(run, font, size, bold, color)
+    return box
+
+
+def add_rect(slide, left, top, width, height, fill, line=None):
+    shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height)
+    shape.fill.solid()
+    shape.fill.fore_color.rgb = fill
+    if line is None:
+        shape.line.fill.background()
+    else:
+        shape.line.color.rgb = line
+        shape.line.width = Pt(0.75)
+    return shape
+
+
+def add_round_rect(slide, left, top, width, height, fill, line=None):
+    shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
+    shape.fill.solid()
+    shape.fill.fore_color.rgb = fill
+    if line is None:
+        shape.line.fill.background()
+    else:
+        shape.line.color.rgb = line
+        shape.line.width = Pt(0.75)
+    # 较小圆角
+    try:
+        shape.adjustments[0] = 0.08
+    except Exception:
+        pass
+    return shape
+
+
+def date_to_x(d: date, axis_start: date, axis_end: date, chart_left, chart_width):
+    total = (axis_end - axis_start).days
+    if total <= 0:
+        return chart_left
+    ratio = max(0.0, min(1.0, (d - axis_start).days / total))
+    return chart_left + int(chart_width * ratio)
+
+
+def resize_image(src: Path, max_w: int = 1000) -> Path:
+    CACHE_DIR.mkdir(parents=True, exist_ok=True)
+    out = CACHE_DIR / f"{src.stem}_w{max_w}{src.suffix.lower()}"
+    if out.exists() and out.stat().st_mtime >= src.stat().st_mtime:
+        return out
+    img = Image.open(src)
+    if img.mode in ("RGBA", "P"):
+        img = img.convert("RGB")
+    w, h = img.size
+    if w > max_w:
+        nh = int(h * max_w / w)
+        img = img.resize((max_w, nh), Image.Resampling.LANCZOS)
+    # JPEG 统一
+    if out.suffix.lower() in (".jpg", ".jpeg"):
+        img.save(out, "JPEG", quality=88, optimize=True)
+    else:
+        img.save(out, "PNG", optimize=True)
+    return out
+
+
+def find_image(candidates: list[str]) -> Path | None:
+    for name in candidates:
+        p = PIC_DIR / name
+        if p.exists():
+            return p
+    return None
+
+
+# ---------- 甘特数据(实施阶段)----------
+# done=True 已完成;done=False 本月推进中
+GANTT_ROWS = [
+    ("阶段一 环境准备", date(2026, 4, 27), date(2026, 5, 6), True),
+    ("阶段二 软件预开发", date(2026, 4, 30), date(2026, 5, 6), True),
+    ("阶段三 蓝图设计", date(2026, 5, 7), date(2026, 6, 1), True),
+    ("阶段四 硬件与工位对接", date(2026, 5, 8), date(2026, 5, 16), True),
+    ("阶段五 流程与主数据", date(2026, 5, 18), date(2026, 5, 30), True),
+    ("阶段六 多系统对接", date(2026, 6, 1), date(2026, 6, 18), True),
+    ("阶段七 培训驻场", date(2026, 6, 22), date(2026, 6, 27), True),
+    ("七月 上线陪产", date(2026, 7, 1), date(2026, 7, 24), False),
+]
+
+AXIS_START = date(2026, 4, 27)
+AXIS_END = date(2026, 7, 24)
+TODAY = date(2026, 7, 21)
+ORANGE_BAR = RGBColor(0xE0, 0x8A, 0x2E)
+ORANGE_SOFT = RGBColor(0xF5, 0xE6, 0xD0)
+
+WEEKLY_PROGRESS = [
+    ("阶段三", "5/7-5/10", "五月第一周", "8", "8", "100%"),
+    ("阶段四", "5/11-5/16", "五月第二周", "9", "9", "100%"),
+    ("阶段四&五", "5/18-5/23", "五月第三周", "9", "9", "100%"),
+    ("阶段五", "5/25-5/30", "五月第四周", "7", "7", "100%"),
+    ("阶段六", "6/1-6/6", "六月第一周", "7", "7", "100%"),
+    ("阶段六", "6/8-6/13", "六月第二周", "6", "6", "100%"),
+    ("阶段六", "6/13-6/18", "六月第三周", "10", "10", "100%"),
+    ("阶段七", "6/22-6/27", "六月第四周", "8", "8", "100%"),
+]
+
+
+def build_slide1(prs: Presentation):
+    slide = prs.slides.add_slide(prs.slide_layouts[6])  # blank
+
+    # 顶栏
+    add_rect(slide, 0, 0, SLIDE_W, Inches(0.88), HEADER_BG)
+    add_textbox(
+        slide, Inches(0.28), Inches(0.10), Inches(8.8), Inches(0.40),
+        "六安裕洋MES项目实施进度汇报", size=24, bold=True, color=WHITE,
+    )
+    add_textbox(
+        slide, Inches(0.28), Inches(0.50), Inches(9.2), Inches(0.30),
+        "京宜安 · 截至2026年7月21日 · 5–6月周任务64项已100%完成 · 本月聚焦现场培训/实际上线/陪产",
+        size=11, bold=False, color=RGBColor(0xB8, 0xD4, 0xE8),
+    )
+
+    # 右上统计胶囊
+    stats = [("8周次", "5–6月"), ("64/64", "周任务完成"), ("100%", "合格率"), ("7月", "上线陪产")]
+    x0 = Inches(9.05)
+    for i, (v, lab) in enumerate(stats):
+        left = x0 + Inches(i * 1.02)
+        add_round_rect(slide, left, Inches(0.12), Inches(0.96), Inches(0.64), RGBColor(0x0E, 0x52, 0x78))
+        tone = ORANGE_BAR if i == 3 else (ACCENT_GREEN if i == 2 else WHITE)
+        add_textbox(slide, left, Inches(0.14), Inches(0.96), Inches(0.34), v, size=13, bold=True, color=tone, align=PP_ALIGN.CENTER)
+        add_textbox(slide, left, Inches(0.44), Inches(0.96), Inches(0.28), lab, size=8, color=RGBColor(0xA8, 0xC8, 0xDC), align=PP_ALIGN.CENTER)
+
+    # ===== 左侧:周任务明细表 =====
+    table_left = Inches(0.22)
+    table_top = Inches(1.00)
+    table_w = Inches(5.55)
+    table_h = Inches(4.55)
+    add_round_rect(slide, table_left, table_top, table_w, table_h, CARD_BG)
+    add_textbox(
+        slide, Inches(0.38), Inches(1.08), Inches(5.2), Inches(0.28),
+        "周任务明细(阶段三~七 · 全部完成)", size=12, bold=True, color=HEADER_BG,
+    )
+
+    # 表头
+    headers = [("阶段", 0.95), ("起止", 1.15), ("任务周", 1.35), ("完成", 0.70), ("合格率", 0.85)]
+    hx = Inches(0.38)
+    hy = Inches(1.42)
+    add_rect(slide, Inches(0.35), hy, Inches(5.28), Inches(0.30), HEADER_BG)
+    cx = hx
+    for title, w in headers:
+        add_textbox(slide, cx, hy + Inches(0.02), Inches(w), Inches(0.26), title, size=9, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
+        cx += Inches(w)
+
+    row_h = Inches(0.38)
+    for i, (phase, span, week, plan, done, rate) in enumerate(WEEKLY_PROGRESS):
+        y = Inches(1.74) + row_h * i
+        bg = WHITE if i % 2 else ROW_ALT
+        add_rect(slide, Inches(0.35), y, Inches(5.28), row_h - Inches(0.02), bg)
+        vals = [phase, span, week, f"{done}/{plan}", rate]
+        widths = [0.95, 1.15, 1.35, 0.70, 0.85]
+        cx = hx
+        for j, (val, w) in enumerate(zip(vals, widths)):
+            col = ACCENT_GREEN if j >= 3 else TEXT_DARK
+            add_textbox(
+                slide, cx, y + Inches(0.04), Inches(w), Inches(0.30),
+                val, size=9, bold=(j >= 3), color=col, align=PP_ALIGN.CENTER,
+            )
+            cx += Inches(w)
+
+    add_textbox(
+        slide, Inches(0.38), Inches(4.90), Inches(5.2), Inches(0.45),
+        "合计:8 周次 · 任务 64 · 完成 64 · 合格率 100%",
+        size=11, bold=True, color=ACCENT_GREEN,
+    )
+
+    # ===== 右侧:甘特图 =====
+    gantt_left = Inches(5.90)
+    gantt_top = Inches(1.00)
+    gantt_w = Inches(7.15)
+    gantt_h = Inches(4.55)
+    add_round_rect(slide, gantt_left, gantt_top, gantt_w, gantt_h, CARD_BG)
+    add_textbox(
+        slide, Inches(6.05), Inches(1.08), Inches(5.0), Inches(0.28),
+        "实施阶段甘特图(2026-04-27 ~ 07-24)", size=12, bold=True, color=HEADER_BG,
+    )
+    add_textbox(
+        slide, Inches(10.4), Inches(1.10), Inches(2.4), Inches(0.26),
+        "今日 7/21", size=9, bold=True, color=ORANGE_BAR, align=PP_ALIGN.RIGHT,
+    )
+
+    label_w = Inches(2.15)
+    chart_left = Inches(6.05) + label_w
+    chart_right = Inches(12.85)
+    chart_width = chart_right - chart_left
+    axis_y = Inches(1.38)
+    row_h_g = Inches(0.38)
+    bar_h = Inches(0.20)
+    first_row_y = Inches(1.68)
+
+    ticks = [
+        date(2026, 4, 27), date(2026, 5, 11), date(2026, 5, 25),
+        date(2026, 6, 8), date(2026, 6, 22), date(2026, 7, 6), date(2026, 7, 21),
+    ]
+    for d in ticks:
+        x = date_to_x(d, AXIS_START, AXIS_END, chart_left, chart_width)
+        line = slide.shapes.add_shape(
+            MSO_SHAPE.RECTANGLE, x, first_row_y - Inches(0.04),
+            Pt(0.75), row_h_g * len(GANTT_ROWS) + Inches(0.08),
+        )
+        line.fill.solid()
+        line.fill.fore_color.rgb = LINE_SOFT
+        line.line.fill.background()
+        add_textbox(
+            slide, x - Inches(0.28), axis_y, Inches(0.56), Inches(0.26),
+            f"{d.month}/{d.day}", size=7, color=AXIS_GRAY, align=PP_ALIGN.CENTER,
+        )
+
+    # 今日竖线
+    tx = date_to_x(TODAY, AXIS_START, AXIS_END, chart_left, chart_width)
+    today_line = slide.shapes.add_shape(
+        MSO_SHAPE.RECTANGLE, tx, first_row_y - Inches(0.04),
+        Pt(1.5), row_h_g * len(GANTT_ROWS) + Inches(0.08),
+    )
+    today_line.fill.solid()
+    today_line.fill.fore_color.rgb = ORANGE_BAR
+    today_line.line.fill.background()
+
+    for i, (name, start, end, done) in enumerate(GANTT_ROWS):
+        y = first_row_y + row_h_g * i
+        if i % 2 == 0:
+            add_rect(slide, Inches(6.00), y, Inches(6.95), row_h_g - Inches(0.02), ROW_ALT if done else ORANGE_SOFT)
+        add_textbox(
+            slide, Inches(6.05), y + Inches(0.05), label_w - Inches(0.08), Inches(0.28),
+            name, size=9, bold=True, color=TEXT_DARK,
+        )
+        x1 = date_to_x(start, AXIS_START, AXIS_END, chart_left, chart_width)
+        x2 = date_to_x(end, AXIS_START, AXIS_END, chart_left, chart_width)
+        bar_w = max(int(x2 - x1), int(Inches(0.10)))
+        by = y + (row_h_g - bar_h) // 2 - Inches(0.01)
+        fill = TEAL_BAR if done else ORANGE_BAR
+        bar = add_round_rect(slide, x1, by, bar_w, bar_h, fill)
+        try:
+            bar.adjustments[0] = 0.35
+        except Exception:
+            pass
+        span = f"{start.month}/{start.day}-{end.month}/{end.day}"
+        if bar_w > Inches(0.85):
+            add_textbox(slide, x1, by - Inches(0.02), bar_w, bar_h + Inches(0.04), span, size=7, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
+
+    # 图例
+    ly = Inches(5.15)
+    add_round_rect(slide, Inches(6.10), ly, Inches(0.22), Inches(0.14), TEAL_BAR)
+    add_textbox(slide, Inches(6.38), ly - Inches(0.04), Inches(1.4), Inches(0.24), "已完成", size=8, color=TEXT_MUTED)
+    add_round_rect(slide, Inches(7.85), ly, Inches(0.22), Inches(0.14), ORANGE_BAR)
+    add_textbox(slide, Inches(8.13), ly - Inches(0.04), Inches(2.6), Inches(0.24), "本月推进中(上线陪产)", size=8, color=TEXT_MUTED)
+
+    # ===== 底部:本月重点 =====
+    add_textbox(
+        slide, Inches(0.28), Inches(5.70), Inches(4), Inches(0.26),
+        "本月重点(2026年7月)", size=12, bold=True, color=HEADER_BG,
+    )
+    july_items = [
+        ("现场培训", "操作工/班组长/管理员\n工位扫码、异常处理、后台使用"),
+        ("实际上线", "HEV线正式跑生产\n数据进MES、大屏实时展示"),
+        ("陪产支持", "驻场观察产线运行\n问题闭环、系统优化"),
+    ]
+    by0 = Inches(6.00)
+    bw = Inches(4.20)
+    for i, (title, desc) in enumerate(july_items):
+        left = Inches(0.28) + bw * i
+        add_round_rect(slide, left, by0, bw - Inches(0.14), Inches(0.95), WHITE, ORANGE_BAR)
+        add_textbox(slide, left + Inches(0.14), by0 + Inches(0.08), bw - Inches(0.40), Inches(0.28), title, size=13, bold=True, color=ORANGE_BAR)
+        box = slide.shapes.add_textbox(left + Inches(0.14), by0 + Inches(0.38), bw - Inches(0.40), Inches(0.52))
+        tf = box.text_frame
+        tf.word_wrap = True
+        p = tf.paragraphs[0]
+        run = p.add_run()
+        run.text = desc
+        set_run_font(run, size=10, color=TEXT_MUTED)
+
+    add_textbox(
+        slide, Inches(0.28), Inches(7.15), Inches(12.7), Inches(0.25),
+        "六安裕洋 MES · 京宜安实施团队 · 进度汇报 · 第1页 · 2026-07-21",
+        size=8, color=AXIS_GRAY, align=PP_ALIGN.CENTER,
+    )
+
+
+def build_slide2(prs: Presentation, used_images: list[tuple[str, str]]):
+    slide = prs.slides.add_slide(prs.slide_layouts[6])
+
+    add_rect(slide, 0, 0, SLIDE_W, Inches(0.78), HEADER_BG)
+    add_textbox(
+        slide, Inches(0.35), Inches(0.18), Inches(10), Inches(0.42),
+        "效果展示 · 实时运行截图", size=22, bold=True, color=WHITE,
+    )
+    add_textbox(
+        slide, Inches(9.5), Inches(0.25), Inches(3.5), Inches(0.35),
+        "7月:现场培训 · 实际上线 · 陪产", size=11, color=RGBColor(0xB8, 0xD4, 0xE8), align=PP_ALIGN.RIGHT,
+    )
+
+    # 2x4 网格 — 使用用户精选效果图
+    caps_and_files = EFFECT_IMAGES
+
+    cols = 4
+    margin_l = Inches(0.32)
+    margin_t = Inches(0.95)
+    gap_x, gap_y = Inches(0.18), Inches(0.22)
+    cell_w = (SLIDE_W - margin_l * 2 - gap_x * (cols - 1)) / cols
+    cell_h = Inches(2.95)
+    img_area_h = Inches(2.45)
+    cap_h = Inches(0.38)
+
+    for idx, (caption, src) in enumerate(caps_and_files):
+        row = idx // cols
+        col = idx % cols
+        left = margin_l + col * (cell_w + gap_x)
+        top = margin_t + row * (cell_h + gap_y)
+
+        add_round_rect(slide, left, top, cell_w, cell_h, WHITE, LINE_SOFT)
+
+        if not src.exists():
+            add_textbox(slide, left + Inches(0.1), top + Inches(0.8), cell_w - Inches(0.2), Inches(0.5),
+                        "(图片缺失)", size=12, color=TEXT_MUTED, align=PP_ALIGN.CENTER)
+            used_images.append((caption, "MISSING"))
+        else:
+            cached = resize_image(src, 1200)
+            with Image.open(cached) as im:
+                iw, ih = im.size
+            max_w = cell_w - Inches(0.16)
+            max_h = img_area_h - Inches(0.12)
+            scale = min(max_w / iw, max_h / ih)
+            disp_w = int(iw * scale)
+            disp_h = int(ih * scale)
+            if disp_w > max_w:
+                disp_w = int(max_w)
+            if disp_h > max_h:
+                disp_h = int(max_h)
+            ix = left + (cell_w - disp_w) // 2
+            iy = top + Inches(0.08) + (max_h - disp_h) // 2
+            slide.shapes.add_picture(str(cached), ix, iy, width=disp_w, height=disp_h)
+            used_images.append((caption, src.name))
+
+        add_textbox(
+            slide, left + Inches(0.06), top + img_area_h, cell_w - Inches(0.12), cap_h,
+            caption, size=11, bold=True, color=HEADER_BG, align=PP_ALIGN.CENTER,
+        )
+
+    add_textbox(
+        slide, Inches(0.35), Inches(7.15), Inches(12.5), Inches(0.28),
+        "六安裕洋 MES · 京宜安实施团队 · 效果展示 · 第2页 · 2026-07-21",
+        size=8, color=AXIS_GRAY, align=PP_ALIGN.CENTER,
+    )
+
+
+def main():
+    OUT1.parent.mkdir(parents=True, exist_ok=True)
+    OUT2.parent.mkdir(parents=True, exist_ok=True)
+    CACHE_DIR.mkdir(parents=True, exist_ok=True)
+
+    prs = Presentation()
+    prs.slide_width = SLIDE_W
+    prs.slide_height = SLIDE_H
+
+    build_slide1(prs)
+    used: list[tuple[str, str]] = []
+    build_slide2(prs, used)
+
+    # 先写仓库副本(避免桌面文件被占用导致整次失败)
+    prs.save(str(OUT2))
+    print(f"OUT2: {OUT2}  exists={OUT2.exists()} size={OUT2.stat().st_size}")
+
+    desktop_out = OUT1
+    try:
+        prs.save(str(OUT1))
+    except PermissionError:
+        desktop_out = OUT1.with_name("六安裕洋MES实施进度汇报_效果图更新.pptx")
+        prs.save(str(desktop_out))
+        print(f"桌面原文件被占用,已另存为: {desktop_out}")
+    print(f"OUT1: {desktop_out}  exists={desktop_out.exists()} size={desktop_out.stat().st_size}")
+
+    print("=== 使用图片 ===")
+    for cap, name in used:
+        print(f"  - {cap}: {name}")
+
+
+if __name__ == "__main__":
+    main()

+ 343 - 0
_mes_milestone_summary.txt

@@ -0,0 +1,343 @@
+================================================================================
+六安裕洋 MES 实施里程碑与截图资源汇总
+生成时间: 2026-07-21 09:53:46
+数据来源: 大洋-京宜安MES实施进度.xlsx、MES实施现状-260716.xlsx、pic/ 目录
+================================================================================
+
+A. 总进度 — 各周完成率(来源:总进度)
+--------------------------------------------------------------------------------
+汇总表:
+  实施阶段 | 开始时间 | 结束时间 | 具体任务(周) | 任务数 | 完成数 | 合格率
+  阶段三 | 2026-05-07 00:00:00 | 2026-05-10 00:00:00 | 五月份第一周 | 8 | 8 | 100.00%
+  阶段四 | 2026-05-11 00:00:00 | 2026-05-16 00:00:00 | 五月份第二周 | 9 | 6 | 66.67%
+  阶段四&阶段五 | 2026-05-18 00:00:00 | 2026-05-23 00:00:00 | 五月份第三周 | 9 | 7 | 77.78%
+  阶段五 | 2026-05-25 00:00:00 | 2026-05-30 00:00:00 | 五月份第四周 | 7 | 7 | 100.00%
+  阶段六 | 2026-06-01 00:00:00 | 2026-06-06 00:00:00 | 六月份第一周 | 7 | 7 | 100.00%
+  阶段六 | 2026-06-08 00:00:00 | 2026-06-13 00:00:00 | 六月份第二周 | 6 | 6 | 100.00%
+  阶段六 | 2026-06-13 00:00:00 | 2026-06-18 00:00:00 | 六月份第三周 | 10 | 10 | 100.00%
+  阶段七 | 2026-06-22 00:00:00 | 2026-06-27 00:00:00 | 六月份第四周 | 8 | 1 | 12.50%
+
+B. 全流程详细实施计划 — 阶段一至七(关键交付与状态)
+--------------------------------------------------------------------------------
+
+【实施阶段】
+
+【阶段一:】
+  1. 开发环境部署 | 工作: 开发环境部署 | 状态: 完成
+  2. 安装配置 | 工作: 应用服务器操作系统、JDK、Tomcat | 状态: 完成
+  3. 初始化配置 | 工作: MySQL数据库安装 | 输出: mysql数据库账号密码等相关信息 | 状态: 完成
+  4. MES后端服务基础框架部署 | 工作: MES后端服务基础框架部署 | 输出: 系统地址 | 状态: 完成
+  5. 硬件清单确认 | 工作: 选择相关硬件型号 | 输出: 采购订单 | 状态: 完成
+  6. 硬件采购 | 工作: 硬件采购 | 输出: 物流信息整合 | 状态: 完成
+
+【阶段二:软件预开发】
+  1. 框架搭建 | 工作: 搭建基础框架 | 状态: 完成
+  2. 组织权限管理模块 | 工作: 确定客户组织架构 | 输出: 系统组织架构管理模块 | 状态: 完成
+  3. 系统配置模块 | 工作: 配置基础菜单,等待蓝图和完整需求数据后录入 | 状态: 完成
+
+【阶段三:蓝图设计】
+  1. 项目启动会 | 工作: 确定大致需求,划分功能模块责任,需求排期计划 | 状态: 完成
+  2. 实施方案研讨 | 工作: 讨论实施方案,确定具体需求
+  3. 实施方案订稿 | 工作: 根据结果修订实施方案 | 状态: 完成
+  4. 蓝图方案确认(根据实际情况调整) | 工作: 确定实施方案,相关负责人签字确认 | 状态: 完成
+
+【阶段四:】
+  1. 大屏开发 | 工作: 大屏页面开发 | 状态: 完成
+  2. 工控机、扫码枪安装 | 工作: 工控机、扫码枪安装 | 状态: 完成
+  3. 工业大屏安装 | 工作: 工业大屏安装,显示大屏内容 | 状态: 完成
+  4. 网络布线施工 | 工作: 根据施工要求,走线,按照电源网线等设备 | 状态: 完成
+  5. 交换机安装 | 工作: 测试调通网络 | 输出: 《设备IP地址表》 | 状态: 完成
+  6. OP060-OP130(HEV)工位数据采集 | 工作: 点焊工位数据收集 | 输出: 工作站交互逻辑图、工作站客户端 | 状态: 完成
+  7. OP050、OP140、OP150、OP160(HEV)工位数据采集 | 工作: 弧焊工位数据收集 | 输出: 工作站交互逻辑图、工作站客户端 | 状态: 完成
+  8. OP40、OP60、OP70、OP80(115)工位数据采集 | 工作: 弧焊工位数据收集 | 输出: 工作站交互逻辑图、工作站客户端 | 状态: 完成
+  9. OP190、OP220(HEV)工位数据采集 | 工作: 气密数据收集 | 输出: 气密交互逻辑图、气密客户端 | 状态: 完成
+  10. OP110、OP180(115)工位数据采集 | 工作: 气密数据收集 | 输出: 气密交互逻辑图、气密客户端 | 状态: 完成
+  11. OP210(HEV)、OP130(B13)、OP150(B13)工位数据采集 | 工作: 拉铆数据收集 | 输出: 拉铆工位客户端 | 状态: 完成
+  12. 镭雕工位 | 工作: 安装镭雕机以及工位客户端 | 输出: 镭雕工位客户端 | 状态: 完成
+  13. 扫码枪开发 | 工作: 对应工位扫码枪软件开发 | 状态: 完成
+  14. PDA模块开发 | 工作: 相关需要的功能开发 | 状态: 完成
+
+【阶段五:】
+  1. 工位过站流程测试 | 工作: 生产工件,跟随生产流程 | 状态: 完成
+  2. 追溯测试 | 工作: 随机抽取工件,查询所追数据 | 状态: 完成
+  OCR识别 | 工作: OCR识别电泳后工件测试 | 状态: 完成
+  3. 返修流程开发测试 | 工作: 返修流程开发测试 | 输出: 返修模块 | 状态: 完成
+  4. 新增需求开发 | 工作: 根据试运行情况,完善增加需求 | 状态: 完成
+  5. 录入员工基础数据 | 工作: 按照提供的员工,部门信息录入系统 | 输出: 《权限管理》 | 状态: 完成
+  9. 设备管理开发 | 工作: 对设备部分需求开发 | 输出: 设备管理模块 | 状态: 完成
+  10. 物料管理开发 | 工作: 对物料部分需求开发 | 输出: 物料管理模块 | 状态: 完成
+  11. 质量管理开发 | 工作: 对质量部分需求开发 | 输出: 质量管理模块
+  12. 录入返修信息 | 工作: 按照提供的物料数据录入 | 输出: 返修信息相关模块 | 状态: 完成
+  13. 录入物料信息 | 工作: 按照提供的返修数据录入 | 输出: 物料信息相关模块 | 状态: 完成
+  14. 录入设备信息 | 工作: 按照提供的设备数据录入 | 输出: 设备信息相关模板 | 状态: 完成
+
+【阶段六:多系统对接】
+  1. ERP对接 | 工作: 给出相关方案,与客户沟通,确定最终需求 | 状态: 完成
+  2. ERP开发 | 工作: 根据需求表进行逐步开发,测试,上线 | 状态: 完成
+
+【阶段七:】
+  1. 操作手册编写 | 工作: 根据流程工位,后台管理系统编写使用手册 | 状态: 完成
+  2. 系统管理员培训 | 工作: 系统配置、权限管理、数据维护 | 状态: 完成
+  3. 生产管理人员培训 | 工作: 进度追踪、报表查看 | 状态: 完成
+  4. 现场操作工培训 | 工作: 工位扫码、设备操作、异常上报 | 状态: 完成
+  5. 驻场支持 | 工作: 陪产,解决异常 | 状态: 完成
+
+C. 七月份第一周 / 七月份第二周 — 任务明细
+--------------------------------------------------------------------------------
+
+=== 七月份第一周 ===
+  行3 / 序号1:
+    任务名称: 培训
+    工作内容: 培训现场及管理后台使用
+    甲方对接人: 于龙泉、汪政、刘金泉、芮自飞、周立松、金波、张凤
+    乙方对接人: 黄飞杨
+    计划: 2026-07-01 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+    进展说明: 设备关联、物料管理、质量管理、生产管理
+    备注: 现场生产培训需等待至周一开始对HEV进行生产
+  行4 / 序号2:
+    任务名称: pda编写
+    工作内容: 生产、系统管理员、生产管理员培训
+    甲方对接人: 芮自飞、于龙泉
+    乙方对接人: 经博
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+    进展说明: 已经完成来料检、生产领料
+  行5 / 序号3:
+    任务名称: 流程测试
+    工作内容: 将前面未完成工作完成闭环
+    甲方对接人: 芮自飞
+    乙方对接人: 黄飞杨
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+    进展说明: 流程已经走通,可以进行实际测试
+  行6 / 序号4:
+    任务名称: 周会
+    工作内容: 1.MES日、周会召开总结
+    甲方对接人: 魏本武、于龙泉、汪政、刘金泉、芮自飞、周立松、金波、张凤
+    乙方对接人: 经博
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+  行7 / 序号5:
+    任务名称: 驻场支持
+    工作内容: 处理BUG
+    甲方对接人: 刘金泉
+    乙方对接人: 黄飞杨
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+
+=== 七月份第二周 ===
+  行3 / 序号2:
+    任务名称: pda编写
+    工作内容: 返修功能pda化
+    甲方对接人: 芮自飞
+    乙方对接人: 经博
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+  行4:
+    任务名称: 大屏显示适配
+    工作内容: 大屏显示适配
+    甲方对接人: 芮自飞
+    乙方对接人: 经博
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+  行5:
+    任务名称: ccd工位对接
+    工作内容: ccd工位对接
+    甲方对接人: 芮自飞
+    乙方对接人: 黄飞杨
+    状态: 完成
+  行6:
+    任务名称: 115工作站对接
+    工作内容: 115工作站对接
+    甲方对接人: 芮自飞
+    乙方对接人: 经博
+    状态: 进行中
+  行7 / 序号3:
+    任务名称: 实件测试
+    工作内容: 将前面未完成工作完成闭环
+    甲方对接人: 芮自飞
+    乙方对接人: 黄飞杨
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+    进展说明: 等待拉铆工序测试
+  行8 / 序号4:
+    任务名称: 周会
+    工作内容: 1.MES日、周会召开总结
+    甲方对接人: 魏本武、于龙泉、汪政、刘金泉、芮自飞、周立松、金波、张凤
+    乙方对接人: 经博
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+  行9 / 序号5:
+    任务名称: 驻场支持
+    工作内容: 处理BUG
+    甲方对接人: 刘金泉
+    乙方对接人: 黄飞杨
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+
+D. 问题清单(来源:问题清单工作表)
+--------------------------------------------------------------------------------
+  1. 工控机点位 | 内容: 点位不能确定 | 对接人: 刘金泉 | 后续: 打印纸质版布局图,进行会签 | 进度: 已解决
+  2. HEV装配工位 | 内容: 拉铆控制器维修中,工艺参数需要提供 | 对接人: 刘金泉 | 后续: 等待拉铆枪接入控制器 | 进度: 解决中
+  3. 气密设备出现故障 | 内容: 将服务器网线连入气密设备后出现设备断线情况,网线拔出后,也出现这种情况 | 对接人: 张风 | 后续: 网线已经拔出,需要观察是否因为增加与mes连接数据点位 | 进度: 解决中
+  4. 设备对接速度慢 | 内容: MES软件逻辑已经完成、设备点位暂未提供 | 对接人: 张风 | 后续: 等待设备厂商提供点位 | 进度: 解决中
+  5. 工控机扫码枪支架 | 内容: 支架未带放置扫码枪的支架 | 对接人: 芮自飞 | 后续: 已进行支架定制 | 进度: 解决中
+  6. 大屏显示 | 内容: 看板下方有白边(浏览器导航栏) | 对接人: 芮自飞 | 后续: 更换下载浏览器 | 进度: 已解决
+  7. 登录验证不安全 | 内容: 需要增加手机验证码或者邮箱 | 对接人: 芮自飞 | 后续: 讨论方案中 | 进度: 解决中
+
+E. pic/ 目录截图文件完整列表
+--------------------------------------------------------------------------------
+共 81 个图片文件:
+  7a35fb05b9a7f8d935511dad78001541.png
+  ccd时间.png
+  ERP对接-读取数据.png
+  ERP对接.png
+  ERP对接文档.png
+  ERP流程.png
+  HEV.jpg
+  HEV工作站对接逻辑流程图.png
+  HEV气密逻辑.jpg
+  OCR测试.png
+  OP050.jpg
+  pda.jpg
+  pda主页面.jpg
+  pda软件.png
+  五月份第四周会议纪要.png
+  人员信息录入.png
+  优化.png
+  修改人.png
+  修改时间.png
+  公司等级.png
+  公司等级去除.png
+  参数拆分.png
+  员工信息管理.png
+  员工复制.png
+  员工岗位.png
+  在线预览.png
+  大屏.png
+  大屏安装.jpg
+  大屏管理页面.png
+  大洋.ico
+  大洋.png
+  客户端下载.png
+  客户端优化.png
+  导入导出.png
+  小时产量.png
+  工号必填.png
+  布线图.jpg
+  技术方案.png
+  报废流程.png
+  拉铆工位.png
+  排产计划.png
+  排产进度.png
+  搜索栏.png
+  操作手册.png
+  操作视频.png
+  操作说明书.png
+  数据库搭建.png
+  服务登录页面.png
+  机构.png
+  权限分配.png
+  权限管理.png
+  气密.jpg
+  气密交互逻辑流程图.png
+  气密工位.jpg
+  气密数据上传.jpg
+  汉字输入框.jpg
+  物料管理.png
+  环境搭建.png
+  班次.png
+  生产人.png
+  生产大屏管理.jpg
+  生产领料单.png
+  电表.png
+  白件气密时间.png
+  系统主页.png
+  系统管理.png
+  职责划分.png
+  能耗大屏.png
+  菜单栏.png
+  蓝图.png
+  蓝图修改.png
+  设备清单.png
+  设备管理清单.png
+  试运行情况说明/前道工序未加工.jpg
+  试运行情况说明/当前工位有工件.jpg
+  试运行情况说明/未录入系统.jpg
+  质量管理.png
+  返修数据.png
+  返修问题清单.png
+  采购入库单.png
+  镭雕规则.png
+
+F. MES实施现状-260716.xlsx 完整内容
+--------------------------------------------------------------------------------
+文件: c:\Users\j2742\Desktop\temp\大洋\实施文档\MES实施现状-260716.xlsx
+工作表: MES系统推进现状, 问题, 工作计划
+
+--- 工作表: MES系统推进现状 (维度 A1:C5) ---
+  A1: MES系统推进现状
+  A2: 序号
+  B2: 功能
+  C2: 现状
+  A3: 1
+  B3: 数据采集
+  C3: 目前试运行的工位有:HEV线体中镭雕工位、HEV工作站,都已经可以采集数据到系统中。等待工件流转,陆续上线气密、装配工位;115线体等待工件加工;电泳后打印工件码软件准备完成。
+  A4: 2
+  B4: 大屏显示
+  C4: 目前显示无问题,等待后续根据现场进行优化
+  A5: 3
+  B5: 后台管理系统
+  C5: 持续优化功能,ERP对接需要梳理修改部分功能
+
+--- 工作表: 问题 (维度 A1:D5) ---
+  A1: 序号
+  B1: 工作站
+  C1: 问题描述
+  D1: 解决方案
+  A2: 1
+  B2: HEV-op100
+  C2: 加工时设备会异常停止
+  D2: 需要哈工人员协助
+  A3: 2
+  B3: HEV-镭雕
+  C3: 镭雕的工件码手机无法扫码(不影响扫码枪使用)
+  D3: 更改调试打码参数
+  A4: 3
+  B4: HEV-工作站
+  C4: 扫码枪需要白夜班使用,电池是否够用
+  D4: 规定时间内充电
+  A5: 4
+  B5: HEV-工作站
+  C5: 员工碰到异常情况不会处理
+  D5: 收集整理问题汇总,培训产线负责人
+
+--- 工作表: 工作计划 (维度 A1:K5) ---
+  A1: MES七月第四周实施计划
+  A2: 序号
+  B2: 工作项目
+  C2: 工作内容
+  D2: 甲方对接人
+  E2: 乙方对接人
+  F2: 开始时间
+  G2: 结束时间
+  H2: 完成情况
+  I2: 进展描述
+  J2: 备注
+  K2: 见证资料
+  B3: 陪产
+  C3: 观察产线运行情况,培训员工
+  E3: 黄飞杨
+  F3: 2026-07-20
+  G3: 2026-07-24
+  B4: ERP梳理
+  C4: 梳理erp业务流程对接详情
+  E4: 经博
+  F4: 2026-07-20
+  G4: 2026-07-24
+  B5: 系统优化
+  C5: 维修系统BUG,优化页面功能
+  E5: 黄飞杨
+  F5: 2026-07-20
+  G5: 2026-07-24

+ 445 - 0
_mes_screenshot_plan.txt

@@ -0,0 +1,445 @@
+================================================================================
+MES 截图规划与能力现状分析
+生成时间: 2026-07-21 09:54:22
+================================================================================
+
+一、pic/ 目录完整文件列表(相对路径,共 81 个)
+--------------------------------------------------------------------------------
+  7a35fb05b9a7f8d935511dad78001541.png
+  ccd时间.png
+  ERP对接-读取数据.png
+  ERP对接.png
+  ERP对接文档.png
+  ERP流程.png
+  HEV.jpg
+  HEV工作站对接逻辑流程图.png
+  HEV气密逻辑.jpg
+  OCR测试.png
+  OP050.jpg
+  pda.jpg
+  pda主页面.jpg
+  pda软件.png
+  五月份第四周会议纪要.png
+  人员信息录入.png
+  优化.png
+  修改人.png
+  修改时间.png
+  公司等级.png
+  公司等级去除.png
+  参数拆分.png
+  员工信息管理.png
+  员工复制.png
+  员工岗位.png
+  在线预览.png
+  大屏.png
+  大屏安装.jpg
+  大屏管理页面.png
+  大洋.ico
+  大洋.png
+  客户端下载.png
+  客户端优化.png
+  导入导出.png
+  小时产量.png
+  工号必填.png
+  布线图.jpg
+  技术方案.png
+  报废流程.png
+  拉铆工位.png
+  排产计划.png
+  排产进度.png
+  搜索栏.png
+  操作手册.png
+  操作视频.png
+  操作说明书.png
+  数据库搭建.png
+  服务登录页面.png
+  机构.png
+  权限分配.png
+  权限管理.png
+  气密.jpg
+  气密交互逻辑流程图.png
+  气密工位.jpg
+  气密数据上传.jpg
+  汉字输入框.jpg
+  物料管理.png
+  环境搭建.png
+  班次.png
+  生产人.png
+  生产大屏管理.jpg
+  生产领料单.png
+  电表.png
+  白件气密时间.png
+  系统主页.png
+  系统管理.png
+  职责划分.png
+  能耗大屏.png
+  菜单栏.png
+  蓝图.png
+  蓝图修改.png
+  设备清单.png
+  设备管理清单.png
+  试运行情况说明/前道工序未加工.jpg
+  试运行情况说明/当前工位有工件.jpg
+  试运行情况说明/未录入系统.jpg
+  质量管理.png
+  返修数据.png
+  返修问题清单.png
+  采购入库单.png
+  镭雕规则.png
+
+================================================================================
+二、里程碑摘要引用(来自 _mes_milestone_summary.txt)
+================================================================================
+
+A. 总进度 — 各周完成率(来源:总进度)
+--------------------------------------------------------------------------------
+汇总表:
+  实施阶段 | 开始时间 | 结束时间 | 具体任务(周) | 任务数 | 完成数 | 合格率
+  阶段三 | 2026-05-07 00:00:00 | 2026-05-10 00:00:00 | 五月份第一周 | 8 | 8 | 100.00%
+  阶段四 | 2026-05-11 00:00:00 | 2026-05-16 00:00:00 | 五月份第二周 | 9 | 6 | 66.67%
+  阶段四&阶段五 | 2026-05-18 00:00:00 | 2026-05-23 00:00:00 | 五月份第三周 | 9 | 7 | 77.78%
+  阶段五 | 2026-05-25 00:00:00 | 2026-05-30 00:00:00 | 五月份第四周 | 7 | 7 | 100.00%
+  阶段六 | 2026-06-01 00:00:00 | 2026-06-06 00:00:00 | 六月份第一周 | 7 | 7 | 100.00%
+  阶段六 | 2026-06-08 00:00:00 | 2026-06-13 00:00:00 | 六月份第二周 | 6 | 6 | 100.00%
+  阶段六 | 2026-06-13 00:00:00 | 2026-06-18 00:00:00 | 六月份第三周 | 10 | 10 | 100.00%
+  阶段七 | 2026-06-22 00:00:00 | 2026-06-27 00:00:00 | 六月份第四周 | 8 | 1 | 12.50%
+
+
+B. 全流程详细实施计划 — 阶段一至七(关键交付与状态)
+--------------------------------------------------------------------------------
+
+【实施阶段】
+
+【阶段一:】
+  1. 开发环境部署 | 工作: 开发环境部署 | 状态: 完成
+  2. 安装配置 | 工作: 应用服务器操作系统、JDK、Tomcat | 状态: 完成
+  3. 初始化配置 | 工作: MySQL数据库安装 | 输出: mysql数据库账号密码等相关信息 | 状态: 完成
+  4. MES后端服务基础框架部署 | 工作: MES后端服务基础框架部署 | 输出: 系统地址 | 状态: 完成
+  5. 硬件清单确认 | 工作: 选择相关硬件型号 | 输出: 采购订单 | 状态: 完成
+  6. 硬件采购 | 工作: 硬件采购 | 输出: 物流信息整合 | 状态: 完成
+
+【阶段二:软件预开发】
+  1. 框架搭建 | 工作: 搭建基础框架 | 状态: 完成
+  2. 组织权限管理模块 | 工作: 确定客户组织架构 | 输出: 系统组织架构管理模块 | 状态: 完成
+  3. 系统配置模块 | 工作: 配置基础菜单,等待蓝图和完整需求数据后录入 | 状态: 完成
+
+【阶段三:蓝图设计】
+  1. 项目启动会 | 工作: 确定大致需求,划分功能模块责任,需求排期计划 | 状态: 完成
+  2. 实施方案研讨 | 工作: 讨论实施方案,确定具体需求
+  3. 实施方案订稿 | 工作: 根据结果修订实施方案 | 状态: 完成
+  4. 蓝图方案确认(根据实际情况调整) | 工作: 确定实施方案,相关负责人签字确认 | 状态: 完成
+
+【阶段四:】
+  1. 大屏开发 | 工作: 大屏页面开发 | 状态: 完成
+  2. 工控机、扫码枪安装 | 工作: 工控机、扫码枪安装 | 状态: 完成
+  3. 工业大屏安装 | 工作: 工业大屏安装,显示大屏内容 | 状态: 完成
+  4. 网络布线施工 | 工作: 根据施工要求,走线,按照电源网线等设备 | 状态: 完成
+  5. 交换机安装 | 工作: 测试调通网络 | 输出: 《设备IP地址表》 | 状态: 完成
+  6. OP060-OP130(HEV)工位数据采集 | 工作: 点焊工位数据收集 | 输出: 工作站交互逻辑图、工作站客户端 | 状态: 完成
+  7. OP050、OP140、OP150、OP160(HEV)工位数据采集 | 工作: 弧焊工位数据收集 | 输出: 工作站交互逻辑图、工作站客户端 | 状态: 完成
+  8. OP40、OP60、OP70、OP80(115)工位数据采集 | 工作: 弧焊工位数据收集 | 输出: 工作站交互逻辑图、工作站客户端 | 状态: 完成
+  9. OP190、OP220(HEV)工位数据采集 | 工作: 气密数据收集 | 输出: 气密交互逻辑图、气密客户端 | 状态: 完成
+  10. OP110、OP180(115)工位数据采集 | 工作: 气密数据收集 | 输出: 气密交互逻辑图、气密客户端 | 状态: 完成
+  11. OP210(HEV)、OP130(B13)、OP150(B13)工位数据采集 | 工作: 拉铆数据收集 | 输出: 拉铆工位客户端 | 状态: 完成
+  12. 镭雕工位 | 工作: 安装镭雕机以及工位客户端 | 输出: 镭雕工位客户端 | 状态: 完成
+  13. 扫码枪开发 | 工作: 对应工位扫码枪软件开发 | 状态: 完成
+  14. PDA模块开发 | 工作: 相关需要的功能开发 | 状态: 完成
+
+【阶段五:】
+  1. 工位过站流程测试 | 工作: 生产工件,跟随生产流程 | 状态: 完成
+  2. 追溯测试 | 工作: 随机抽取工件,查询所追数据 | 状态: 完成
+  OCR识别 | 工作: OCR识别电泳后工件测试 | 状态: 完成
+  3. 返修流程开发测试 | 工作: 返修流程开发测试 | 输出: 返修模块 | 状态: 完成
+  4. 新增需求开发 | 工作: 根据试运行情况,完善增加需求 | 状态: 完成
+  5. 录入员工基础数据 | 工作: 按照提供的员工,部门信息录入系统 | 输出: 《权限管理》 | 状态: 完成
+  9. 设备管理开发 | 工作: 对设备部分需求开发 | 输出: 设备管理模块 | 状态: 完成
+  10. 物料管理开发 | 工作: 对物料部分需求开发 | 输出: 物料管理模块 | 状态: 完成
+  11. 质量管理开发 | 工作: 对质量部分需求开发 | 输出: 质量管理模块
+  12. 录入返修信息 | 工作: 按照提供的物料数据录入 | 输出: 返修信息相关模块 | 状态: 完成
+  13. 录入物料信息 | 工作: 按照提供的返修数据录入 | 输出: 物料信息相关模块 | 状态: 完成
+  14. 录入设备信息 | 工作: 按照提供的设备数据录入 | 输出: 设备信息相关模板 | 状态: 完成
+
+【阶段六:多系统对接】
+  1. ERP对接 | 工作: 给出相关方案,与客户沟通,确定最终需求 | 状态: 完成
+  2. ERP开发 | 工作: 根据需求表进行逐步开发,测试,上线 | 状态: 完成
+
+【阶段七:】
+  1. 操作手册编写 | 工作: 根据流程工位,后台管理系统编写使用手册 | 状态: 完成
+  2. 系统管理员培训 | 工作: 系统配置、权限管理、数据维护 | 状态: 完成
+  3. 生产管理人员培训 | 工作: 进度追踪、报表查看 | 状态: 完成
+  4. 现场操作工培训 | 工作: 工位扫码、设备操作、异常上报 | 状态: 完成
+  5. 驻场支持 | 工作: 陪产,解决异常 | 状态: 完成
+
+
+C. 七月份第一周 / 七月份第二周 — 任务明细
+--------------------------------------------------------------------------------
+
+=== 七月份第一周 ===
+  行3 / 序号1:
+    任务名称: 培训
+    工作内容: 培训现场及管理后台使用
+    甲方对接人: 于龙泉、汪政、刘金泉、芮自飞、周立松、金波、张凤
+    乙方对接人: 黄飞杨
+    计划: 2026-07-01 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+    进展说明: 设备关联、物料管理、质量管理、生产管理
+    备注: 现场生产培训需等待至周一开始对HEV进行生产
+  行4 / 序号2:
+    任务名称: pda编写
+    工作内容: 生产、系统管理员、生产管理员培训
+    甲方对接人: 芮自飞、于龙泉
+    乙方对接人: 经博
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+    进展说明: 已经完成来料检、生产领料
+  行5 / 序号3:
+    任务名称: 流程测试
+    工作内容: 将前面未完成工作完成闭环
+    甲方对接人: 芮自飞
+    乙方对接人: 黄飞杨
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+    进展说明: 流程已经走通,可以进行实际测试
+  行6 / 序号4:
+    任务名称: 周会
+    工作内容: 1.MES日、周会召开总结
+    甲方对接人: 魏本武、于龙泉、汪政、刘金泉、芮自飞、周立松、金波、张凤
+    乙方对接人: 经博
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+  行7 / 序号5:
+    任务名称: 驻场支持
+    工作内容: 处理BUG
+    甲方对接人: 刘金泉
+    乙方对接人: 黄飞杨
+    计划: 2026-06-29 00:00:00 ~ 2026-07-03 00:00:00
+    状态: 完成
+
+=== 七月份第二周 ===
+  行3 / 序号2:
+    任务名称: pda编写
+    工作内容: 返修功能pda化
+    甲方对接人: 芮自飞
+    乙方对接人: 经博
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+  行4:
+    任务名称: 大屏显示适配
+    工作内容: 大屏显示适配
+    甲方对接人: 芮自飞
+    乙方对接人: 经博
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+  行5:
+    任务名称: ccd工位对接
+    工作内容: ccd工位对接
+    甲方对接人: 芮自飞
+    乙方对接人: 黄飞杨
+    状态: 完成
+  行6:
+    任务名称: 115工作站对接
+    工作内容: 115工作站对接
+    甲方对接人: 芮自飞
+    乙方对接人: 经博
+    状态: 进行中
+  行7 / 序号3:
+    任务名称: 实件测试
+    工作内容: 将前面未完成工作完成闭环
+    甲方对接人: 芮自飞
+    乙方对接人: 黄飞杨
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+    进展说明: 等待拉铆工序测试
+  行8 / 序号4:
+    任务名称: 周会
+    工作内容: 1.MES日、周会召开总结
+    甲方对接人: 魏本武、于龙泉、汪政、刘金泉、芮自飞、周立松、金波、张凤
+    乙方对接人: 经博
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+  行9 / 序号5:
+    任务名称: 驻场支持
+    工作内容: 处理BUG
+    甲方对接人: 刘金泉
+    乙方对接人: 黄飞杨
+    计划: 2026-07-06 00:00:00 ~ 2026-07-10 00:00:00
+    状态: 进行中
+
+
+D. 问题清单(来源:问题清单工作表)
+--------------------------------------------------------------------------------
+  1. 工控机点位 | 内容: 点位不能确定 | 对接人: 刘金泉 | 后续: 打印纸质版布局图,进行会签 | 进度: 已解决
+  2. HEV装配工位 | 内容: 拉铆控制器维修中,工艺参数需要提供 | 对接人: 刘金泉 | 后续: 等待拉铆枪接入控制器 | 进度: 解决中
+  3. 气密设备出现故障 | 内容: 将服务器网线连入气密设备后出现设备断线情况,网线拔出后,也出现这种情况 | 对接人: 张风 | 后续: 网线已经拔出,需要观察是否因为增加与mes连接数据点位 | 进度: 解决中
+  4. 设备对接速度慢 | 内容: MES软件逻辑已经完成、设备点位暂未提供 | 对接人: 张风 | 后续: 等待设备厂商提供点位 | 进度: 解决中
+  5. 工控机扫码枪支架 | 内容: 支架未带放置扫码枪的支架 | 对接人: 芮自飞 | 后续: 已进行支架定制 | 进度: 解决中
+  6. 大屏显示 | 内容: 看板下方有白边(浏览器导航栏) | 对接人: 芮自飞 | 后续: 更换下载浏览器 | 进度: 已解决
+  7. 登录验证不安全 | 内容: 需要增加手机验证码或者邮箱 | 对接人: 芮自飞 | 后续: 讨论方案中 | 进度: 解决中
+
+
+F. MES实施现状-260716.xlsx 完整内容
+--------------------------------------------------------------------------------
+文件: c:\Users\j2742\Desktop\temp\大洋\实施文档\MES实施现状-260716.xlsx
+工作表: MES系统推进现状, 问题, 工作计划
+
+--- 工作表: MES系统推进现状 (维度 A1:C5) ---
+  A1: MES系统推进现状
+  A2: 序号
+  B2: 功能
+  C2: 现状
+  A3: 1
+  B3: 数据采集
+  C3: 目前试运行的工位有:HEV线体中镭雕工位、HEV工作站,都已经可以采集数据到系统中。等待工件流转,陆续上线气密、装配工位;115线体等待工件加工;电泳后打印工件码软件准备完成。
+  A4: 2
+  B4: 大屏显示
+  C4: 目前显示无问题,等待后续根据现场进行优化
+  A5: 3
+  B5: 后台管理系统
+  C5: 持续优化功能,ERP对接需要梳理修改部分功能
+
+--- 工作表: 问题 (维度 A1:D5) ---
+  A1: 序号
+  B1: 工作站
+  C1: 问题描述
+  D1: 解决方案
+  A2: 1
+  B2: HEV-op100
+  C2: 加工时设备会异常停止
+  D2: 需要哈工人员协助
+  A3: 2
+  B3: HEV-镭雕
+  C3: 镭雕的工件码手机无法扫码(不影响扫码枪使用)
+  D3: 更改调试打码参数
+  A4: 3
+  B4: HEV-工作站
+  C4: 扫码枪需要白夜班使用,电池是否够用
+  D4: 规定时间内充电
+  A5: 4
+  B5: HEV-工作站
+  C5: 员工碰到异常情况不会处理
+  D5: 收集整理问题汇总,培训产线负责人
+
+--- 工作表: 工作计划 (维度 A1:K5) ---
+  A1: MES七月第四周实施计划
+  A2: 序号
+  B2: 工作项目
+  C2: 工作内容
+  D2: 甲方对接人
+  E2: 乙方对接人
+  F2: 开始时间
+  G2: 结束时间
+  H2: 完成情况
+  I2: 进展描述
+  J2: 备注
+  K2: 见证资料
+  B3: 陪产
+  C3: 观察产线运行情况,培训员工
+  E3: 黄飞杨
+  F3: 2026-07-20
+  G3: 2026-07-24
+  B4: ERP梳理
+  C4: 梳理erp业务流程对接详情
+  E4: 经博
+  F4: 2026-07-20
+  G4: 2026-07-24
+  B5: 系统优化
+  C5: 维修系统BUG,优化页面功能
+  E5: 黄飞杨
+  F5: 2026-07-20
+  G5: 2026-07-24
+
+================================================================================
+三、已实现能力 vs 进行中/待完善(基于 Excel 与现状表)
+================================================================================
+【总体进度(A)】
+- 五月份:第一周 100%;第二周 66.67%(9 项完成 6);第三周 77.78%;第四周 100%。
+- 六月份:第一~三周均为 100%;第四周 12.50%(8 项仅完成 1,培训/手册类滞后于计划统计)。
+- 七月份第一周任务表显示培训、PDA(来料检/生产领料)、流程测试、驻场均已「完成」。
+- 七月份第二周:PDA 返修、大屏适配、115 工作站、实件测试、周会、驻场为「进行中」;CCD 工位对接「完成」。
+
+【已实现 / 已上线 / 试运行(生产或试点)】
+
+1. 基础平台与环境(阶段一~二,B 均为完成)
+   - 开发/生产环境:JDK、Tomcat、MySQL、MES 后端框架部署。
+   - 组织权限、系统配置、菜单与导入导出等后台基础能力。
+
+2. HEV 产线与工位客户端(阶段四,完成;F 现状确认)
+   - 镭雕工位、HEV 工作站:已采集数据到 MES(F-数据采集)。
+   - 点焊 OP060-OP130、弧焊 OP050/140/150/160、气密 OP190/220、拉铆 OP210 等工位客户端/逻辑开发在计划中标记完成;五月份第二周部分焊机/PLC 曾「进行中」,后续周报称 HEV 点位开发完成待测试。
+   - 气密:交互逻辑与客户端完成;气密数据采集在 F 中列为等待工件流转后陆续上线。
+   - 扫码枪工位软件、工控机/大屏/网络/IP 表:完成。
+
+3. 大屏与看板(完成 + F)
+   - 大屏页面开发、工业大屏安装完成;F 称「目前显示无问题」,七月份第二周「大屏显示适配」进行中(分辨率/布局优化)。
+
+4. PDA 移动端(完成 + C)
+   - PDA 模块开发(计划完成);七月第一周 PDA:来料检、生产领料已完成;七月第二周返修功能 PDA 化进行中。
+   - pic 可用:pda.jpg、pda主页面.jpg、pda软件.png、生产领料单.png、采购入库单.png 等。
+
+5. 过站、追溯、OCR(阶段五,完成)
+   - 工位过站流程测试、追溯测试、OCR 电泳后工件测试:计划状态完成。
+   - pic:OCR测试.png、白件气密时间.png、ccd时间.png。
+
+6. 返修与质量(阶段五 + C/D)
+   - 返修流程开发测试完成;返修数据录入完成。
+   - 质量管理模块开发(计划表中无 P 列「完成」标注,但六月份第三周质量模块与返修录入周报为完成)。
+   - 七月 PDA 返修进行中;pic:返修数据.png、返修问题清单.png、报废流程.png。
+
+7. 设备 / 物料 / 员工主数据(阶段五~六,周报完成)
+   - 设备管理、物料管理模块开发与信息录入:完成。
+   - 员工、班次、权限、组织架构录入:完成。
+   - pic:设备清单.png、设备管理清单.png、物料管理.png、员工信息管理.png、人员信息录入.png、班次.png 等。
+
+8. ERP 对接(阶段六,计划完成;F 与七月计划)
+   - 计划表:ERP 对接方案、ERP 开发均「完成」;六月份第二~四周 ERP 对接/开发周报完成。
+   - F(7/16):后台「ERP 对接需要梳理修改部分功能」——业务上仍处优化/二阶段。
+   - 七月第四周计划(F):ERP 梳理(7/20-7/24)。
+   - pic:ERP对接.png、ERP对接-读取数据.png、ERP流程.png、ERP对接文档.png。
+
+9. CCD 工位(C 七月第二周)
+   - CCD 工位对接:状态「完成」。
+   - pic:ccd时间.png(及对接文档目录 MES与CCD接口.xlsx 在实施包内)。
+
+10. 培训、手册与驻场(阶段七 + C)
+    - 操作手册、管理员/生产/操作工培训、驻场支持:计划表均为完成。
+    - 七月第一周培训(后台与现场)完成;现场生产培训备注依赖 HEV 生产开机。
+
+11. 能耗 / 电表
+    - 六月份电表安装调试、数据集成周报完成;pic:电表.png、能耗大屏.png。
+
+【进行中 / 待验证 / 待产线配合】
+
+1. 115 线体(B 计划完成 vs C/F)
+   - 115 弧焊/气密工位采集在总计划为完成;F:115 线体「等待工件加工」。
+   - 七月第二周「115 工作站对接」:进行中。
+
+2. 拉铆 / 装配 / 全实件联调(C/D)
+   - 问题清单:HEV 拉铆控制器维修、工艺参数待提供;实件测试「等待拉铆工序测试」。
+   - F:气密、装配工位等待工件流转陆续上线。
+
+3. PDA 返修、大屏适配(C 七月第二周):进行中。
+
+4. ERP 深度对接与流程梳理(F + 工作计划):非一次性关闭,需截图「现状 + 梳理中」。
+
+5. 登录安全(D):手机验证码/邮箱方案讨论中。
+
+6. 设备侧遗留(D/F):气密网线/MES 点位观察、设备点位交付节奏、OP100 异常停机等。
+
+【建议截图映射(能力 → pic 文件)】
+
+| 能力域 | 建议素材(pic/ 相对路径) | 说明 |
+|--------|---------------------------|------|
+| 系统总览 | 系统主页.png、系统管理.png、服务登录页面.png | 后台入口与模块 |
+| HEV 工作站 | HEV工作站对接逻辑流程图.png、HEV.jpg、OP050.jpg、拉铆工位.png | 客户端与工位 |
+| 气密 | 气密.jpg、气密工位.jpg、气密交互逻辑流程图.png、气密数据上传.jpg | 逻辑+现场 |
+| 大屏 | 大屏.png、大屏管理页面.png、生产大屏管理.jpg、排产计划.png、排产进度.png、小时产量.png、能耗大屏.png | 多类看板 |
+| PDA | pda主页面.jpg、pda软件.png、生产领料单.png | 已交付功能;返修待补新图 |
+| ERP | ERP流程.png、ERP对接.png、ERP对接-读取数据.png | 对接与读取 |
+| OCR/CCD | OCR测试.png、ccd时间.png | 识别与 CCD |
+| 质量返修 | 返修问题清单.png、返修数据.png、质量管理.png、报废流程.png | 质量域 |
+| 设备物料 | 设备管理清单.png、物料管理.png、电表.png | 主数据与能耗 |
+| 权限组织 | 权限管理.png、机构.png、员工岗位.png、蓝图.png | 实施文档类 |
+| 试运行说明 | 试运行情况说明/*.jpg | 异常场景说明 |
+
+【交付结论(一句话)】
+MES 已在 HEV 线实现镭雕与工作站数据采集、大屏展示、PDA 仓储类功能、后台设备/物料/质量/权限主数据、ERP 一期对接与 CCD 对接;115 线、拉铆实件全链路、PDA 返修、大屏适配及 ERP 业务梳理仍在推进,宜按「已实现(附截图)/ 进行中(附周报状态)/ 计划(F 七月第四周)」三组组织对外汇报材料。

BIN=BIN
_ppt_img_cache/ERP对接-读取数据_w1000.png


BIN=BIN
_ppt_img_cache/OCR测试_w1000.png


BIN=BIN
_ppt_img_cache/OP050_w1000.jpg


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_60b4a34fc5a9f4ca3195ea55e3a2ace1-787d700c-4131-4806-abbf-ff01c10c9679_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-15f57559-f837-464f-af8a-b4e656767283_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-1759c9f1-d702-4b17-b35b-0d8d8cfd66cf_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-220cd888-53b8-4247-b74f-9b3b5d56a8bf_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-74f8c1fc-7426-4472-9dda-a4abdb79cc23_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-8dd92c21-a7aa-48d0-81a3-4a1107492a9a_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-a4a18a5e-f2ab-42a3-a9c5-2bcfae5d1233_w1200.png


BIN=BIN
_ppt_img_cache/c__Users_j2742_AppData_Roaming_Cursor_User_workspaceStorage_empty-window_images_image-eb11b4ec-13e0-4792-833d-13a7d304a2a5_w1200.png


BIN=BIN
_ppt_img_cache/pda主页面_w1000.jpg


BIN=BIN
_tmp_decompile/CrudService.javap.txt


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1426 - 0
_tmp_decompile/EmpUserController.javap.txt


+ 707 - 0
_tmp_decompile/EmpUserServiceSupport.javap.txt

@@ -0,0 +1,707 @@
+Compiled from "EmpUserServiceSupport.java"
+public class com.jeesite.modules.sys.service.support.EmpUserServiceSupport extends com.jeesite.common.service.CrudService<com.jeesite.modules.sys.dao.EmpUserDao, com.jeesite.modules.sys.entity.EmpUser> implements com.jeesite.modules.sys.service.EmpUserService {
+  private com.jeesite.modules.sys.service.UserService userService;
+
+  private com.jeesite.modules.sys.service.EmployeeService employeeService;
+
+  private com.jeesite.modules.sys.dao.EmployeeOfficeDao employeeOfficeDao;
+
+  public com.jeesite.modules.sys.service.support.EmpUserServiceSupport();
+    Code:
+       0: aload_0
+       1: invokespecial #1                  // Method com/jeesite/common/service/CrudService."<init>":()V
+       4: return
+
+  private void corpModelValid() throws java.lang.Exception;
+    Code:
+       0: invokestatic  #2                  // Method com/jeesite/common/config/Global.isUseCorpModel:()Ljava/lang/Boolean;
+       3: ldc           #3                  // String user.useCorpModel
+       5: ldc           #4                  // String false
+       7: invokestatic  #5                  // Method com/jeesite/common/config/Global.getPropertyToBoolean:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Boolean;
+      10: invokevirtual #6                  // Method java/lang/Boolean.equals:(Ljava/lang/Object;)Z
+      13: ifne          26
+      16: new           #7                  // class java/lang/Exception
+      19: dup
+      20: ldc           #8                  // String \n\nuser.useCorpModel=true? 你开启了多租户模式,似乎你的当前版本不是JeeSite专业版。\n
+      22: invokespecial #9                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
+      25: athrow
+      26: return
+
+  public com.jeesite.modules.sys.entity.EmpUser get(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: invokespecial #10                 // Method com/jeesite/common/service/CrudService.get:(Lcom/jeesite/common/entity/DataEntity;)Lcom/jeesite/common/entity/DataEntity;
+       5: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       8: areturn
+
+  public void addDataScopeFilter(com.jeesite.modules.sys.entity.EmpUser, java.lang.String);
+    Code:
+       0: aload_1
+       1: invokevirtual #12                 // Method com/jeesite/modules/sys/entity/EmpUser.getSqlMap:()Lcom/jeesite/common/mybatis/mapper/SqlMap;
+       4: invokevirtual #13                 // Method com/jeesite/common/mybatis/mapper/SqlMap.getDataScope:()Lcom/jeesite/common/mybatis/mapper/query/QueryDataScope;
+       7: ldc           #14                 // String dsfOffice
+       9: ldc           #15                 // String Office
+      11: ldc           #16                 // String e.office_code
+      13: ldc           #17                 // String a.create_by
+      15: aload_2
+      16: ldc           #18                 // String office_user
+      18: invokevirtual #19                 // Method com/jeesite/common/mybatis/mapper/query/QueryDataScope.addFilter:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/jeesite/common/mybatis/mapper/query/QueryDataScope;
+      21: pop
+      22: invokestatic  #20                 // Method com/jeesite/modules/sys/utils/EmpUtils.getCompany:()Lcom/jeesite/modules/sys/entity/Company;
+      25: invokevirtual #21                 // Method com/jeesite/modules/sys/entity/Company.getCompanyCode:()Ljava/lang/String;
+      28: invokestatic  #22                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      31: ifeq          56
+      34: aload_1
+      35: invokevirtual #12                 // Method com/jeesite/modules/sys/entity/EmpUser.getSqlMap:()Lcom/jeesite/common/mybatis/mapper/SqlMap;
+      38: invokevirtual #13                 // Method com/jeesite/common/mybatis/mapper/SqlMap.getDataScope:()Lcom/jeesite/common/mybatis/mapper/query/QueryDataScope;
+      41: ldc           #23                 // String dsfCompany
+      43: ldc           #24                 // String Company
+      45: ldc           #25                 // String e.company_code
+      47: ldc           #17                 // String a.create_by
+      49: aload_2
+      50: ldc           #18                 // String office_user
+      52: invokevirtual #19                 // Method com/jeesite/common/mybatis/mapper/query/QueryDataScope.addFilter:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/jeesite/common/mybatis/mapper/query/QueryDataScope;
+      55: pop
+      56: return
+
+  public com.jeesite.common.entity.Page<com.jeesite.modules.sys.entity.EmpUser> findPage(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: invokespecial #26                 // Method com/jeesite/common/service/CrudService.findPage:(Lcom/jeesite/common/entity/DataEntity;)Lcom/jeesite/common/entity/Page;
+       5: areturn
+
+  public java.util.List<com.jeesite.modules.sys.entity.EmpUser> findUserList(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: getfield      #27                 // Field dao:Lcom/jeesite/common/dao/QueryDao;
+       4: checkcast     #28                 // class com/jeesite/modules/sys/dao/EmpUserDao
+       7: aload_1
+       8: invokeinterface #29,  2           // InterfaceMethod com/jeesite/modules/sys/dao/EmpUserDao.findUserList:(Lcom/jeesite/modules/sys/entity/EmpUser;)Ljava/util/List;
+      13: areturn
+
+  public java.util.List<com.jeesite.modules.sys.entity.EmpUser> findUserListByOfficeCodes(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: getfield      #27                 // Field dao:Lcom/jeesite/common/dao/QueryDao;
+       4: checkcast     #28                 // class com/jeesite/modules/sys/dao/EmpUserDao
+       7: aload_1
+       8: invokeinterface #30,  2           // InterfaceMethod com/jeesite/modules/sys/dao/EmpUserDao.findUserListByOfficeCodes:(Lcom/jeesite/modules/sys/entity/EmpUser;)Ljava/util/List;
+      13: areturn
+
+  public java.util.List<com.jeesite.modules.sys.entity.EmpUser> findUserListByRoleCodes(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: getfield      #27                 // Field dao:Lcom/jeesite/common/dao/QueryDao;
+       4: checkcast     #28                 // class com/jeesite/modules/sys/dao/EmpUserDao
+       7: aload_1
+       8: invokeinterface #31,  2           // InterfaceMethod com/jeesite/modules/sys/dao/EmpUserDao.findUserListByRoleCodes:(Lcom/jeesite/modules/sys/entity/EmpUser;)Ljava/util/List;
+      13: areturn
+
+  public java.util.List<com.jeesite.modules.sys.entity.EmpUser> findUserListByPostCodes(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: getfield      #27                 // Field dao:Lcom/jeesite/common/dao/QueryDao;
+       4: checkcast     #28                 // class com/jeesite/modules/sys/dao/EmpUserDao
+       7: aload_1
+       8: invokeinterface #32,  2           // InterfaceMethod com/jeesite/modules/sys/dao/EmpUserDao.findUserListByPostCodes:(Lcom/jeesite/modules/sys/entity/EmpUser;)Ljava/util/List;
+      13: areturn
+
+  public void save(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_1
+       1: invokevirtual #33                 // Method com/jeesite/modules/sys/entity/EmpUser.getIsNewRecord:()Z
+       4: ifeq          79
+       7: aload_1
+       8: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/EmpUser.getUserCode:()Ljava/lang/String;
+      11: invokestatic  #35                 // Method com/jeesite/common/lang/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z
+      14: ifeq          67
+      17: aload_0
+      18: getfield      #36                 // Field userService:Lcom/jeesite/modules/sys/service/UserService;
+      21: aload_1
+      22: aload_1
+      23: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+      26: invokeinterface #38,  3           // InterfaceMethod com/jeesite/modules/sys/service/UserService.genId:(Lcom/jeesite/common/entity/api/DataEntityApi;Ljava/lang/String;)V
+      31: aload_1
+      32: new           #39                 // class java/lang/StringBuilder
+      35: dup
+      36: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+      39: aload_1
+      40: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/EmpUser.getUserCode:()Ljava/lang/String;
+      43: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      46: ldc           #42                 // String _
+      48: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      51: iconst_4
+      52: invokestatic  #43                 // Method com/jeesite/common/idgen/IdGen.randomBase62:(I)Ljava/lang/String;
+      55: invokevirtual #44                 // Method java/lang/String.toLowerCase:()Ljava/lang/String;
+      58: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      61: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+      64: invokevirtual #46                 // Method com/jeesite/modules/sys/entity/EmpUser.setUserCode:(Ljava/lang/String;)V
+      67: aload_1
+      68: ldc           #47                 // String employee
+      70: invokevirtual #48                 // Method com/jeesite/modules/sys/entity/EmpUser.setUserType:(Ljava/lang/String;)V
+      73: aload_1
+      74: ldc           #49                 // String 0
+      76: invokevirtual #50                 // Method com/jeesite/modules/sys/entity/EmpUser.setMgrType:(Ljava/lang/String;)V
+      79: aload_1
+      80: invokevirtual #51                 // Method com/jeesite/modules/sys/entity/EmpUser.getEmployee:()Lcom/jeesite/modules/sys/entity/Employee;
+      83: astore_2
+      84: aload_2
+      85: invokevirtual #52                 // Method com/jeesite/modules/sys/entity/Employee.getEmpCode:()Ljava/lang/String;
+      88: invokestatic  #35                 // Method com/jeesite/common/lang/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z
+      91: ifeq          102
+      94: aload_2
+      95: aload_1
+      96: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/EmpUser.getUserCode:()Ljava/lang/String;
+      99: invokevirtual #53                 // Method com/jeesite/modules/sys/entity/Employee.setEmpCode:(Ljava/lang/String;)V
+     102: aload_2
+     103: invokevirtual #54                 // Method com/jeesite/modules/sys/entity/Employee.getEmpNo:()Ljava/lang/String;
+     106: invokestatic  #35                 // Method com/jeesite/common/lang/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z
+     109: ifeq          120
+     112: aload_2
+     113: aload_2
+     114: invokevirtual #52                 // Method com/jeesite/modules/sys/entity/Employee.getEmpCode:()Ljava/lang/String;
+     117: invokevirtual #55                 // Method com/jeesite/modules/sys/entity/Employee.setEmpNo:(Ljava/lang/String;)V
+     120: aload_2
+     121: invokevirtual #56                 // Method com/jeesite/modules/sys/entity/Employee.getEmpName:()Ljava/lang/String;
+     124: invokestatic  #35                 // Method com/jeesite/common/lang/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z
+     127: ifeq          138
+     130: aload_2
+     131: aload_1
+     132: invokevirtual #57                 // Method com/jeesite/modules/sys/entity/EmpUser.getUserName:()Ljava/lang/String;
+     135: invokevirtual #58                 // Method com/jeesite/modules/sys/entity/Employee.setEmpName:(Ljava/lang/String;)V
+     138: aload_1
+     139: aload_2
+     140: invokevirtual #52                 // Method com/jeesite/modules/sys/entity/Employee.getEmpCode:()Ljava/lang/String;
+     143: invokevirtual #59                 // Method com/jeesite/modules/sys/entity/EmpUser.setRefCode:(Ljava/lang/String;)V
+     146: aload_1
+     147: aload_2
+     148: invokevirtual #56                 // Method com/jeesite/modules/sys/entity/Employee.getEmpName:()Ljava/lang/String;
+     151: invokevirtual #60                 // Method com/jeesite/modules/sys/entity/EmpUser.setRefName:(Ljava/lang/String;)V
+     154: aload_0
+     155: getfield      #36                 // Field userService:Lcom/jeesite/modules/sys/service/UserService;
+     158: aload_1
+     159: invokeinterface #61,  2           // InterfaceMethod com/jeesite/modules/sys/service/UserService.save:(Lcom/jeesite/modules/sys/entity/User;)V
+     164: aload_2
+     165: aload_1
+     166: invokevirtual #33                 // Method com/jeesite/modules/sys/entity/EmpUser.getIsNewRecord:()Z
+     169: invokevirtual #62                 // Method com/jeesite/modules/sys/entity/Employee.setIsNewRecord:(Z)V
+     172: aload_0
+     173: getfield      #63                 // Field employeeService:Lcom/jeesite/modules/sys/service/EmployeeService;
+     176: aload_2
+     177: invokeinterface #64,  2           // InterfaceMethod com/jeesite/modules/sys/service/EmployeeService.save:(Lcom/jeesite/modules/sys/entity/Employee;)V
+     182: new           #65                 // class com/jeesite/modules/sys/entity/EmployeeOffice
+     185: dup
+     186: invokespecial #66                 // Method com/jeesite/modules/sys/entity/EmployeeOffice."<init>":()V
+     189: astore_3
+     190: aload_3
+     191: aload_2
+     192: invokevirtual #52                 // Method com/jeesite/modules/sys/entity/Employee.getEmpCode:()Ljava/lang/String;
+     195: invokevirtual #67                 // Method com/jeesite/modules/sys/entity/EmployeeOffice.setEmpCode:(Ljava/lang/String;)V
+     198: aload_0
+     199: getfield      #68                 // Field employeeOfficeDao:Lcom/jeesite/modules/sys/dao/EmployeeOfficeDao;
+     202: aload_3
+     203: invokeinterface #69,  2           // InterfaceMethod com/jeesite/modules/sys/dao/EmployeeOfficeDao.deleteByEntity:(Ljava/lang/Object;)J
+     208: pop2
+     209: aload_2
+     210: invokevirtual #70                 // Method com/jeesite/modules/sys/entity/Employee.getEmployeeOfficeList:()Ljava/util/List;
+     213: invokeinterface #71,  1           // InterfaceMethod java/util/List.size:()I
+     218: ifle          251
+     221: aload_2
+     222: invokevirtual #70                 // Method com/jeesite/modules/sys/entity/Employee.getEmployeeOfficeList:()Ljava/util/List;
+     225: aload_2
+     226: invokedynamic #72,  0             // InvokeDynamic #0:accept:(Lcom/jeesite/modules/sys/entity/Employee;)Ljava/util/function/Consumer;
+     231: invokeinterface #73,  2           // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
+     236: aload_0
+     237: getfield      #68                 // Field employeeOfficeDao:Lcom/jeesite/modules/sys/dao/EmployeeOfficeDao;
+     240: aload_2
+     241: invokevirtual #70                 // Method com/jeesite/modules/sys/entity/Employee.getEmployeeOfficeList:()Ljava/util/List;
+     244: aconst_null
+     245: invokeinterface #74,  3           // InterfaceMethod com/jeesite/modules/sys/dao/EmployeeOfficeDao.insertBatch:(Ljava/util/List;Ljava/lang/Integer;)J
+     250: pop2
+     251: return
+
+  public java.lang.String importData(org.springframework.web.multipart.MultipartFile, java.lang.Boolean);
+    Code:
+       0: aload_1
+       1: ifnonnull     22
+       4: new           #75                 // class com/jeesite/common/service/ServiceException
+       7: dup
+       8: aload_0
+       9: ldc           #76                 // String 请选择导入的数据文件!
+      11: iconst_0
+      12: anewarray     #77                 // class java/lang/String
+      15: invokevirtual #78                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      18: invokespecial #79                 // Method com/jeesite/common/service/ServiceException."<init>":(Ljava/lang/String;)V
+      21: athrow
+      22: iconst_0
+      23: istore_3
+      24: iconst_0
+      25: istore        4
+      27: new           #39                 // class java/lang/StringBuilder
+      30: dup
+      31: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+      34: astore        5
+      36: new           #39                 // class java/lang/StringBuilder
+      39: dup
+      40: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+      43: astore        6
+      45: new           #80                 // class com/jeesite/common/utils/excel/ExcelImport
+      48: dup
+      49: aload_1
+      50: iconst_2
+      51: iconst_0
+      52: invokestatic  #81                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+      55: invokespecial #82                 // Method com/jeesite/common/utils/excel/ExcelImport."<init>":(Lorg/springframework/web/multipart/MultipartFile;ILjava/lang/Object;)V
+      58: astore        7
+      60: aconst_null
+      61: astore        8
+      63: aload         7
+      65: ldc           #11                 // class com/jeesite/modules/sys/entity/EmpUser
+      67: iconst_0
+      68: anewarray     #77                 // class java/lang/String
+      71: invokevirtual #83                 // Method com/jeesite/common/utils/excel/ExcelImport.getDataList:(Ljava/lang/Class;[Ljava/lang/String;)Ljava/util/List;
+      74: astore        9
+      76: aload         9
+      78: invokeinterface #84,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
+      83: astore        10
+      85: aload         10
+      87: invokeinterface #85,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
+      92: ifeq          600
+      95: aload         10
+      97: invokeinterface #86,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
+     102: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+     105: astore        11
+     107: aload         11
+     109: iconst_0
+     110: anewarray     #87                 // class java/lang/Class
+     113: invokestatic  #88                 // Method com/jeesite/common/validator/ValidatorUtils.validateWithException:(Ljava/lang/Object;[Ljava/lang/Class;)V
+     116: aload         11
+     118: invokevirtual #51                 // Method com/jeesite/modules/sys/entity/EmpUser.getEmployee:()Lcom/jeesite/modules/sys/entity/Employee;
+     121: invokevirtual #89                 // Method com/jeesite/modules/sys/entity/Employee.getOffice:()Lcom/jeesite/modules/sys/entity/Office;
+     124: invokevirtual #90                 // Method com/jeesite/modules/sys/entity/Office.getOfficeCode:()Ljava/lang/String;
+     127: invokestatic  #35                 // Method com/jeesite/common/lang/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z
+     130: ifeq          183
+     133: iinc          4, 1
+     136: aload         6
+     138: new           #39                 // class java/lang/StringBuilder
+     141: dup
+     142: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     145: ldc           #91                 // String <br/>
+     147: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     150: iload         4
+     152: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     155: ldc           #93                 // String 、账号
+     157: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     160: aload         11
+     162: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     165: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     168: ldc           #94                 // String  导入失败:归属机构不能为空
+     170: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     173: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     176: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     179: pop
+     180: goto          85
+     183: aload         11
+     185: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     188: invokestatic  #95                 // Method com/jeesite/modules/sys/utils/UserUtils.getByLoginCode:(Ljava/lang/String;)Lcom/jeesite/modules/sys/entity/User;
+     191: astore        12
+     193: aload         12
+     195: ifnonnull     264
+     198: aload_0
+     199: aload         11
+     201: invokevirtual #96                 // Method save:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+     204: aload_0
+     205: getfield      #36                 // Field userService:Lcom/jeesite/modules/sys/service/UserService;
+     208: aload         11
+     210: invokeinterface #97,  2           // InterfaceMethod com/jeesite/modules/sys/service/UserService.saveAuth:(Lcom/jeesite/modules/sys/entity/User;)V
+     215: iinc          3, 1
+     218: aload         5
+     220: new           #39                 // class java/lang/StringBuilder
+     223: dup
+     224: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     227: ldc           #91                 // String <br/>
+     229: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     232: iload_3
+     233: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     236: ldc           #93                 // String 、账号
+     238: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     241: aload         11
+     243: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     246: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     249: ldc           #98                 // String  导入成功
+     251: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     254: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     257: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     260: pop
+     261: goto          394
+     264: aload_2
+     265: invokevirtual #99                 // Method java/lang/Boolean.booleanValue:()Z
+     268: ifeq          347
+     271: aload         11
+     273: aload         12
+     275: invokevirtual #100                // Method com/jeesite/modules/sys/entity/User.getUserCode:()Ljava/lang/String;
+     278: invokevirtual #46                 // Method com/jeesite/modules/sys/entity/EmpUser.setUserCode:(Ljava/lang/String;)V
+     281: aload_0
+     282: aload         11
+     284: invokevirtual #96                 // Method save:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+     287: aload_0
+     288: getfield      #36                 // Field userService:Lcom/jeesite/modules/sys/service/UserService;
+     291: aload         11
+     293: invokeinterface #97,  2           // InterfaceMethod com/jeesite/modules/sys/service/UserService.saveAuth:(Lcom/jeesite/modules/sys/entity/User;)V
+     298: iinc          3, 1
+     301: aload         5
+     303: new           #39                 // class java/lang/StringBuilder
+     306: dup
+     307: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     310: ldc           #91                 // String <br/>
+     312: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     315: iload_3
+     316: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     319: ldc           #93                 // String 、账号
+     321: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     324: aload         11
+     326: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     329: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     332: ldc           #101                // String  更新成功
+     334: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     337: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     340: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     343: pop
+     344: goto          394
+     347: iinc          4, 1
+     350: aload         6
+     352: new           #39                 // class java/lang/StringBuilder
+     355: dup
+     356: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     359: ldc           #91                 // String <br/>
+     361: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     364: iload         4
+     366: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     369: ldc           #93                 // String 、账号
+     371: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     374: aload         11
+     376: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     379: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     382: ldc           #102                // String  已存在
+     384: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     387: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     390: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     393: pop
+     394: goto          597
+     397: astore        12
+     399: iinc          4, 1
+     402: new           #39                 // class java/lang/StringBuilder
+     405: dup
+     406: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     409: ldc           #91                 // String <br/>
+     411: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     414: iload         4
+     416: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     419: ldc           #93                 // String 、账号
+     421: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     424: aload         11
+     426: invokevirtual #37                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     429: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     432: ldc           #103                // String  导入失败:
+     434: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     437: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     440: astore        13
+     442: aload         12
+     444: instanceof    #104                // class javax/validation/ConstraintViolationException
+     447: ifeq          551
+     450: aload         12
+     452: checkcast     #104                // class javax/validation/ConstraintViolationException
+     455: astore        14
+     457: aload         14
+     459: invokevirtual #105                // Method javax/validation/ConstraintViolationException.getConstraintViolations:()Ljava/util/Set;
+     462: invokeinterface #106,  1          // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
+     467: astore        15
+     469: aload         15
+     471: invokeinterface #85,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
+     476: ifeq          548
+     479: aload         15
+     481: invokeinterface #86,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
+     486: checkcast     #107                // class javax/validation/ConstraintViolation
+     489: astore        16
+     491: new           #39                 // class java/lang/StringBuilder
+     494: dup
+     495: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     498: aload         13
+     500: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     503: aload         16
+     505: invokeinterface #108,  1          // InterfaceMethod javax/validation/ConstraintViolation.getMessage:()Ljava/lang/String;
+     510: iconst_0
+     511: anewarray     #77                 // class java/lang/String
+     514: invokestatic  #109                // Method com/jeesite/common/config/Global.getText:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+     517: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     520: ldc           #110                // String  (
+     522: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     525: aload         16
+     527: invokeinterface #111,  1          // InterfaceMethod javax/validation/ConstraintViolation.getPropertyPath:()Ljavax/validation/Path;
+     532: invokevirtual #112                // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
+     535: ldc           #113                // String )
+     537: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     540: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     543: astore        13
+     545: goto          469
+     548: goto          576
+     551: new           #39                 // class java/lang/StringBuilder
+     554: dup
+     555: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     558: aload         13
+     560: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     563: aload         12
+     565: invokevirtual #114                // Method java/lang/Exception.getMessage:()Ljava/lang/String;
+     568: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     571: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     574: astore        13
+     576: aload         6
+     578: aload         13
+     580: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     583: pop
+     584: aload_0
+     585: getfield      #115                // Field logger:Lorg/slf4j/Logger;
+     588: aload         13
+     590: aload         12
+     592: invokeinterface #116,  3          // InterfaceMethod org/slf4j/Logger.error:(Ljava/lang/String;Ljava/lang/Throwable;)V
+     597: goto          85
+     600: aload         7
+     602: ifnull        687
+     605: aload         8
+     607: ifnull        630
+     610: aload         7
+     612: invokevirtual #117                // Method com/jeesite/common/utils/excel/ExcelImport.close:()V
+     615: goto          687
+     618: astore        9
+     620: aload         8
+     622: aload         9
+     624: invokevirtual #119                // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+     627: goto          687
+     630: aload         7
+     632: invokevirtual #117                // Method com/jeesite/common/utils/excel/ExcelImport.close:()V
+     635: goto          687
+     638: astore        9
+     640: aload         9
+     642: astore        8
+     644: aload         9
+     646: athrow
+     647: astore        17
+     649: aload         7
+     651: ifnull        684
+     654: aload         8
+     656: ifnull        679
+     659: aload         7
+     661: invokevirtual #117                // Method com/jeesite/common/utils/excel/ExcelImport.close:()V
+     664: goto          684
+     667: astore        18
+     669: aload         8
+     671: aload         18
+     673: invokevirtual #119                // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+     676: goto          684
+     679: aload         7
+     681: invokevirtual #117                // Method com/jeesite/common/utils/excel/ExcelImport.close:()V
+     684: aload         17
+     686: athrow
+     687: goto          725
+     690: astore        7
+     692: aload_0
+     693: getfield      #115                // Field logger:Lorg/slf4j/Logger;
+     696: aload         7
+     698: invokevirtual #114                // Method java/lang/Exception.getMessage:()Ljava/lang/String;
+     701: aload         7
+     703: invokeinterface #116,  3          // InterfaceMethod org/slf4j/Logger.error:(Ljava/lang/String;Ljava/lang/Throwable;)V
+     708: aload         6
+     710: aload         7
+     712: invokevirtual #114                // Method java/lang/Exception.getMessage:()Ljava/lang/String;
+     715: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     718: pop
+     719: aload         6
+     721: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     724: areturn
+     725: iload         4
+     727: ifle          775
+     730: aload         6
+     732: iconst_0
+     733: new           #39                 // class java/lang/StringBuilder
+     736: dup
+     737: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     740: ldc           #120                // String 很抱歉,导入失败!共
+     742: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     745: iload         4
+     747: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     750: ldc           #121                // String  条数据格式不正确,错误如下:
+     752: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     755: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     758: invokevirtual #122                // Method java/lang/StringBuilder.insert:(ILjava/lang/String;)Ljava/lang/StringBuilder;
+     761: pop
+     762: new           #75                 // class com/jeesite/common/service/ServiceException
+     765: dup
+     766: aload         6
+     768: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     771: invokespecial #79                 // Method com/jeesite/common/service/ServiceException."<init>":(Ljava/lang/String;)V
+     774: athrow
+     775: aload         5
+     777: iconst_0
+     778: new           #39                 // class java/lang/StringBuilder
+     781: dup
+     782: invokespecial #40                 // Method java/lang/StringBuilder."<init>":()V
+     785: ldc           #123                // String 恭喜您,数据已全部导入成功!共
+     787: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     790: iload_3
+     791: invokevirtual #92                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
+     794: ldc           #124                // String  条,数据如下:
+     796: invokevirtual #41                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     799: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     802: invokevirtual #122                // Method java/lang/StringBuilder.insert:(ILjava/lang/String;)Ljava/lang/StringBuilder;
+     805: pop
+     806: aload         5
+     808: invokevirtual #45                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     811: areturn
+    Exception table:
+       from    to  target type
+         107   180   397   Class java/lang/Exception
+         183   394   397   Class java/lang/Exception
+         610   615   618   Class java/lang/Throwable
+          63   600   638   Class java/lang/Throwable
+          63   600   647   any
+         659   664   667   Class java/lang/Throwable
+         638   649   647   any
+          45   687   690   Class java/lang/Exception
+
+  public void updateStatus(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: getfield      #36                 // Field userService:Lcom/jeesite/modules/sys/service/UserService;
+       4: aload_1
+       5: invokeinterface #125,  2          // InterfaceMethod com/jeesite/modules/sys/service/UserService.updateStatus:(Lcom/jeesite/modules/sys/entity/User;)V
+      10: aload_0
+      11: getfield      #63                 // Field employeeService:Lcom/jeesite/modules/sys/service/EmployeeService;
+      14: aload_1
+      15: invokevirtual #51                 // Method com/jeesite/modules/sys/entity/EmpUser.getEmployee:()Lcom/jeesite/modules/sys/entity/Employee;
+      18: invokeinterface #126,  2          // InterfaceMethod com/jeesite/modules/sys/service/EmployeeService.updateStatus:(Lcom/jeesite/common/entity/api/DataEntityApi;)V
+      23: return
+
+  public void delete(com.jeesite.modules.sys.entity.EmpUser);
+    Code:
+       0: aload_0
+       1: getfield      #36                 // Field userService:Lcom/jeesite/modules/sys/service/UserService;
+       4: aload_1
+       5: invokeinterface #127,  2          // InterfaceMethod com/jeesite/modules/sys/service/UserService.delete:(Lcom/jeesite/modules/sys/entity/User;)V
+      10: aload_0
+      11: getfield      #63                 // Field employeeService:Lcom/jeesite/modules/sys/service/EmployeeService;
+      14: aload_1
+      15: invokevirtual #51                 // Method com/jeesite/modules/sys/entity/EmpUser.getEmployee:()Lcom/jeesite/modules/sys/entity/Employee;
+      18: invokeinterface #128,  2          // InterfaceMethod com/jeesite/modules/sys/service/EmployeeService.delete:(Lcom/jeesite/modules/sys/entity/Employee;)V
+      23: return
+
+  public void delete(com.jeesite.common.entity.DataEntity);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #129                // Method delete:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+       8: return
+
+  public void save(com.jeesite.common.entity.DataEntity);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #96                 // Method save:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+       8: return
+
+  public void updateStatus(com.jeesite.common.entity.DataEntity);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #130                // Method updateStatus:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+       8: return
+
+  public void delete(com.jeesite.common.entity.api.DataEntityApi);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #129                // Method delete:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+       8: return
+
+  public void updateStatus(com.jeesite.common.entity.api.DataEntityApi);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #130                // Method updateStatus:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+       8: return
+
+  public void save(com.jeesite.common.entity.api.DataEntityApi);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #96                 // Method save:(Lcom/jeesite/modules/sys/entity/EmpUser;)V
+       8: return
+
+  public com.jeesite.common.entity.Page findPage(com.jeesite.common.entity.api.DataEntityApi);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #131                // Method findPage:(Lcom/jeesite/modules/sys/entity/EmpUser;)Lcom/jeesite/common/entity/Page;
+       8: areturn
+
+  public void addDataScopeFilter(com.jeesite.common.entity.api.DataEntityApi, java.lang.String);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: aload_2
+       6: invokevirtual #132                // Method addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/EmpUser;Ljava/lang/String;)V
+       9: return
+
+  public com.jeesite.common.entity.api.DataEntityApi get(com.jeesite.common.entity.api.DataEntityApi);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #133                // Method get:(Lcom/jeesite/modules/sys/entity/EmpUser;)Lcom/jeesite/modules/sys/entity/EmpUser;
+       8: areturn
+
+  public com.jeesite.common.entity.DataEntity get(com.jeesite.common.entity.DataEntity);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #133                // Method get:(Lcom/jeesite/modules/sys/entity/EmpUser;)Lcom/jeesite/modules/sys/entity/EmpUser;
+       8: areturn
+
+  public com.jeesite.common.entity.Page findPage(com.jeesite.common.entity.DataEntity);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: invokevirtual #131                // Method findPage:(Lcom/jeesite/modules/sys/entity/EmpUser;)Lcom/jeesite/common/entity/Page;
+       8: areturn
+
+  public void addDataScopeFilter(com.jeesite.common.entity.DataEntity, java.lang.String);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: checkcast     #11                 // class com/jeesite/modules/sys/entity/EmpUser
+       5: aload_2
+       6: invokevirtual #132                // Method addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/EmpUser;Ljava/lang/String;)V
+       9: return
+
+  private static void lambda$save$0(com.jeesite.modules.sys.entity.Employee, com.jeesite.modules.sys.entity.EmployeeOffice);
+    Code:
+       0: aload_1
+       1: invokestatic  #134                // Method com/jeesite/common/idgen/IdGen.nextId:()Ljava/lang/String;
+       4: invokevirtual #135                // Method com/jeesite/modules/sys/entity/EmployeeOffice.setId:(Ljava/lang/String;)V
+       7: aload_1
+       8: aload_0
+       9: invokevirtual #52                 // Method com/jeesite/modules/sys/entity/Employee.getEmpCode:()Ljava/lang/String;
+      12: invokevirtual #67                 // Method com/jeesite/modules/sys/entity/EmployeeOffice.setEmpCode:(Ljava/lang/String;)V
+      15: return
+}

+ 211 - 0
_tmp_decompile/EmpUser_treeData.txt

@@ -0,0 +1,211 @@
+  public java.util.List<java.util.Map<java.lang.String, java.lang.Object>> treeData(java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String, java.lang.String);
+    Code:
+       0: invokestatic  #156                // Method com/jeesite/common/collect/ListUtils.newArrayList:()Ljava/util/ArrayList;
+       3: astore        9
+       5: new           #4                  // class com/jeesite/modules/sys/entity/EmpUser
+       8: dup
+       9: invokespecial #109                // Method com/jeesite/modules/sys/entity/EmpUser."<init>":()V
+      12: astore        10
+      14: aload         10
+      16: invokevirtual #26                 // Method com/jeesite/modules/sys/entity/EmpUser.getEmployee:()Lcom/jeesite/modules/sys/entity/Employee;
+      19: astore        11
+      21: aload_2
+      22: ifnull        45
+      25: aload_2
+      26: arraylength
+      27: iconst_1
+      28: if_icmpne     45
+      31: aload         11
+      33: invokevirtual #27                 // Method com/jeesite/modules/sys/entity/Employee.getOffice:()Lcom/jeesite/modules/sys/entity/Office;
+      36: aload_2
+      37: iconst_0
+      38: aaload
+      39: invokevirtual #157                // Method com/jeesite/modules/sys/entity/Office.setOfficeCode:(Ljava/lang/String;)V
+      42: goto          54
+      45: aload         11
+      47: invokevirtual #27                 // Method com/jeesite/modules/sys/entity/Employee.getOffice:()Lcom/jeesite/modules/sys/entity/Office;
+      50: aload_2
+      51: invokevirtual #158                // Method com/jeesite/modules/sys/entity/Office.setId_in:([Ljava/lang/String;)V
+      54: aload         11
+      56: invokevirtual #27                 // Method com/jeesite/modules/sys/entity/Employee.getOffice:()Lcom/jeesite/modules/sys/entity/Office;
+      59: iconst_0
+      60: invokestatic  #28                 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
+      63: invokevirtual #29                 // Method com/jeesite/modules/sys/entity/Office.setIsQueryChildren:(Ljava/lang/Boolean;)V
+      66: aload         11
+      68: invokevirtual #30                 // Method com/jeesite/modules/sys/entity/Employee.getCompany:()Lcom/jeesite/modules/sys/entity/Company;
+      71: aload_3
+      72: invokevirtual #159                // Method com/jeesite/modules/sys/entity/Company.setCompanyCode:(Ljava/lang/String;)V
+      75: aload         11
+      77: invokevirtual #30                 // Method com/jeesite/modules/sys/entity/Employee.getCompany:()Lcom/jeesite/modules/sys/entity/Company;
+      80: iconst_0
+      81: invokestatic  #28                 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
+      84: invokevirtual #31                 // Method com/jeesite/modules/sys/entity/Company.setIsQueryChildren:(Ljava/lang/Boolean;)V
+      87: aload         11
+      89: aload         4
+      91: invokevirtual #160                // Method com/jeesite/modules/sys/entity/Employee.setPostCode:(Ljava/lang/String;)V
+      94: aload         10
+      96: aload         5
+      98: invokevirtual #161                // Method com/jeesite/modules/sys/entity/EmpUser.setRoleCode:(Ljava/lang/String;)V
+     101: aload         10
+     103: ldc           #129                // String 0
+     105: invokevirtual #126                // Method com/jeesite/modules/sys/entity/EmpUser.setStatus:(Ljava/lang/String;)V
+     108: aload         10
+     110: ldc           #15                 // String employee
+     112: invokevirtual #162                // Method com/jeesite/modules/sys/entity/EmpUser.setUserType:(Ljava/lang/String;)V
+     115: aload         6
+     117: ifnull        137
+     120: aload         6
+     122: invokevirtual #32                 // Method java/lang/Boolean.booleanValue:()Z
+     125: ifeq          137
+     128: invokestatic  #33                 // Method com/jeesite/common/config/Global.isStrictMode:()Ljava/lang/Boolean;
+     131: invokevirtual #32                 // Method java/lang/Boolean.booleanValue:()Z
+     134: ifeq          150
+     137: aload_0
+     138: getfield      #2                  // Field empUserService:Lcom/jeesite/modules/sys/service/EmpUserService;
+     141: aload         10
+     143: aload         8
+     145: invokeinterface #34,  3           // InterfaceMethod com/jeesite/modules/sys/service/EmpUserService.addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/EmpUser;Ljava/lang/String;)V
+     150: aload_0
+     151: getfield      #2                  // Field empUserService:Lcom/jeesite/modules/sys/service/EmpUserService;
+     154: aload         10
+     156: invokeinterface #93,  2           // InterfaceMethod com/jeesite/modules/sys/service/EmpUserService.findList:(Lcom/jeesite/common/entity/api/DataEntityApi;)Ljava/util/List;
+     161: astore        12
+     163: iconst_0
+     164: istore        13
+     166: iload         13
+     168: aload         12
+     170: invokeinterface #163,  1          // InterfaceMethod java/util/List.size:()I
+     175: if_icmpge     304
+     178: aload         12
+     180: iload         13
+     182: invokeinterface #164,  2          // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
+     187: checkcast     #4                  // class com/jeesite/modules/sys/entity/EmpUser
+     190: astore        14
+     192: invokestatic  #165                // Method com/jeesite/common/collect/MapUtils.newHashMap:()Ljava/util/HashMap;
+     195: astore        15
+     197: aload         15
+     199: ldc           #166                // String id
+     201: new           #94                 // class java/lang/StringBuilder
+     204: dup
+     205: invokespecial #95                 // Method java/lang/StringBuilder."<init>":()V
+     208: aload_1
+     209: ldc           #167                // String u_
+     211: invokestatic  #168                // Method com/jeesite/common/lang/ObjectUtils.defaultIfNull:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     214: checkcast     #53                 // class java/lang/String
+     217: invokevirtual #97                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     220: aload         14
+     222: invokevirtual #169                // Method com/jeesite/modules/sys/entity/EmpUser.getId:()Ljava/lang/String;
+     225: invokevirtual #97                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     228: invokevirtual #101                // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     231: invokeinterface #170,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     236: pop
+     237: aload         15
+     239: ldc           #171                // String pId
+     241: aload         14
+     243: invokevirtual #26                 // Method com/jeesite/modules/sys/entity/EmpUser.getEmployee:()Lcom/jeesite/modules/sys/entity/Employee;
+     246: invokevirtual #27                 // Method com/jeesite/modules/sys/entity/Employee.getOffice:()Lcom/jeesite/modules/sys/entity/Office;
+     249: invokevirtual #43                 // Method com/jeesite/modules/sys/entity/Office.getOfficeCode:()Ljava/lang/String;
+     252: ldc           #129                // String 0
+     254: invokestatic  #172                // Method com/jeesite/common/lang/StringUtils.defaultIfBlank:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
+     257: invokeinterface #170,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     262: pop
+     263: aload         15
+     265: ldc           #173                // String name
+     267: aload         7
+     269: aload         14
+     271: invokevirtual #71                 // Method com/jeesite/modules/sys/entity/EmpUser.getLoginCode:()Ljava/lang/String;
+     274: aload         14
+     276: invokevirtual #89                 // Method com/jeesite/modules/sys/entity/EmpUser.getUserName:()Ljava/lang/String;
+     279: invokestatic  #174                // Method com/jeesite/common/lang/StringUtils.getTreeNodeName:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+     282: invokeinterface #170,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     287: pop
+     288: aload         9
+     290: aload         15
+     292: invokeinterface #175,  2          // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
+     297: pop
+     298: iinc          13, 1
+     301: goto          166
+     304: aload         9
+     306: areturn
+    LineNumberTable:
+      line 413: 0
+      line 414: 5
+      line 415: 14
+      line 416: 21
+      line 417: 31
+      line 419: 45
+      line 421: 54
+      line 422: 66
+      line 423: 75
+      line 424: 87
+      line 425: 94
+      line 426: 101
+      line 427: 108
+      line 428: 115
+      line 429: 137
+      line 431: 150
+      line 432: 163
+      line 433: 178
+      line 434: 192
+      line 435: 197
+      line 436: 237
+      line 437: 263
+      line 438: 288
+      line 432: 298
+      line 440: 304
+    LocalVariableTable:
+      Start  Length  Slot  Name   Signature
+        192     106    14     e   Lcom/jeesite/modules/sys/entity/EmpUser;
+        197     101    15   map   Ljava/util/Map;
+        166     138    13     i   I
+          0     307     0  this   Lcom/jeesite/modules/sys/web/user/EmpUserController;
+          0     307     1 idPrefix   Ljava/lang/String;
+          0     307     2 officeCode   [Ljava/lang/String;
+          0     307     3 companyCode   Ljava/lang/String;
+          0     307     4 postCode   Ljava/lang/String;
+          0     307     5 roleCode   Ljava/lang/String;
+          0     307     6 isAll   Ljava/lang/Boolean;
+          0     307     7 isShowCode   Ljava/lang/String;
+          0     307     8 ctrlPermi   Ljava/lang/String;
+          5     302     9 mapList   Ljava/util/List;
+         14     293    10 empUser   Lcom/jeesite/modules/sys/entity/EmpUser;
+         21     286    11 employee   Lcom/jeesite/modules/sys/entity/Employee;
+        163     144    12  list   Ljava/util/List;
+
+  public java.lang.String empUserSelect(com.jeesite.modules.sys.entity.EmpUser, java.lang.String, org.springframework.ui.Model);
+    Code:
+       0: aload_2
+       1: invokestatic  #176                // Method com/jeesite/common/codec/EncodeUtils.decodeUrl:(Ljava/lang/String;)Ljava/lang/String;
+       4: astore        4
+       6: aload         4
+       8: ifnull        33
+      11: aload         4
+      13: invokestatic  #177                // Method com/alibaba/fastjson/JSONValidator.from:(Ljava/lang/String;)Lcom/alibaba/fastjson/JSONValidator;
+      16: invokevirtual #178                // Method com/alibaba/fastjson/JSONValidator.validate:()Z
+      19: ifeq          33
+      22: aload_3
+      23: ldc           #179                // String selectData
+      25: aload         4
+      27: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+      32: pop
+      33: aload_3
+      34: ldc           #5                  // String empUser
+      36: aload_1
+      37: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+      42: pop
+      43: ldc           #180                // String modules/sys/user/empUserSelect
+      45: areturn
+    LineNumberTable:
+      line 449: 0
+      line 450: 6
+      line 451: 22
+      line 457: 33
+      line 458: 43
+    LocalVariableTable:
+      Start  Length  Slot  Name   Signature
+          0      46     0  this   Lcom/jeesite/modules/sys/web/user/EmpUserController;
+          0      46     1 empUser   Lcom/jeesite/modules/sys/entity/EmpUser;
+          0      46     2 selectData   Ljava/lang/String;
+          0      46     3 model   Lorg/springframework/ui/Model;
+          6      40     4 selectDataJson   Ljava/lang/String;
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1117 - 0
_tmp_decompile/OfficeController.full.txt


+ 857 - 0
_tmp_decompile/OfficeController.javap.txt

@@ -0,0 +1,857 @@
+Compiled from "OfficeController.java"
+public class com.jeesite.modules.sys.web.OfficeController extends com.jeesite.common.web.BaseController {
+  private com.jeesite.modules.sys.service.OfficeService officeService;
+
+  private com.jeesite.modules.sys.web.user.EmpUserController empUserController;
+
+  public com.jeesite.modules.sys.web.OfficeController();
+    Code:
+       0: aload_0
+       1: invokespecial #1                  // Method com/jeesite/common/web/BaseController."<init>":()V
+       4: return
+
+  public com.jeesite.modules.sys.entity.Office get(java.lang.String, boolean);
+    Code:
+       0: aload_0
+       1: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+       4: aload_1
+       5: iload_2
+       6: invokeinterface #3,  3            // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.get:(Ljava/lang/String;Z)Lcom/jeesite/common/entity/api/DataEntityApi;
+      11: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+      14: areturn
+
+  public java.lang.String index(com.jeesite.modules.sys.entity.Office, org.springframework.ui.Model);
+    Code:
+       0: aload_2
+       1: ldc           #5                  // String office
+       3: aload_1
+       4: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+       9: pop
+      10: aload_2
+      11: ldc           #7                  // String ctrlPermi
+      13: ldc           #8                  // String user.adminCtrlPermi
+      15: ldc           #9                  // String 2
+      17: invokestatic  #10                 // Method com/jeesite/common/config/Global.getConfig:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      20: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+      25: pop
+      26: ldc           #11                 // String modules/sys/officeIndex
+      28: areturn
+
+  public java.lang.String list(com.jeesite.modules.sys.entity.Office, org.springframework.ui.Model);
+    Code:
+       0: aload_2
+       1: ldc           #5                  // String office
+       3: aload_1
+       4: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+       9: pop
+      10: aload_2
+      11: ldc           #7                  // String ctrlPermi
+      13: ldc           #8                  // String user.adminCtrlPermi
+      15: ldc           #9                  // String 2
+      17: invokestatic  #10                 // Method com/jeesite/common/config/Global.getConfig:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      20: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+      25: pop
+      26: ldc           #12                 // String modules/sys/officeList
+      28: areturn
+
+  public java.util.List<com.jeesite.modules.sys.entity.Office> listData(com.jeesite.modules.sys.entity.Office, java.lang.String);
+    Code:
+       0: aload_1
+       1: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+       4: invokestatic  #14                 // Method com/jeesite/common/lang/StringUtils.isBlank:(Ljava/lang/CharSequence;)Z
+       7: ifeq          16
+      10: aload_1
+      11: ldc           #15                 // String 0
+      13: invokevirtual #16                 // Method com/jeesite/modules/sys/entity/Office.setParentCode:(Ljava/lang/String;)V
+      16: aload_1
+      17: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+      20: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      23: ifne          46
+      26: aload_1
+      27: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+      30: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      33: ifne          46
+      36: aload_1
+      37: invokevirtual #20                 // Method com/jeesite/modules/sys/entity/Office.getFullName:()Ljava/lang/String;
+      40: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      43: ifeq          51
+      46: aload_1
+      47: aconst_null
+      48: invokevirtual #16                 // Method com/jeesite/modules/sys/entity/Office.setParentCode:(Ljava/lang/String;)V
+      51: aload_0
+      52: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      55: aload_1
+      56: aload_2
+      57: invokeinterface #21,  3           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/Office;Ljava/lang/String;)V
+      62: aload_0
+      63: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      66: aload_1
+      67: invokeinterface #22,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.findList:(Lcom/jeesite/modules/sys/entity/Office;)Ljava/util/List;
+      72: astore_3
+      73: aload_3
+      74: areturn
+
+  public java.lang.String form(com.jeesite.modules.sys.entity.Office, org.springframework.ui.Model);
+    Code:
+       0: aload_0
+       1: aload_1
+       2: invokevirtual #23                 // Method createNextNode:(Lcom/jeesite/modules/sys/entity/Office;)Lcom/jeesite/modules/sys/entity/Office;
+       5: astore_1
+       6: aload_1
+       7: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+      10: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      13: ifeq          36
+      16: aload_1
+      17: aload_0
+      18: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      21: aload_1
+      22: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+      25: invokeinterface #24,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.get:(Ljava/lang/String;)Lcom/jeesite/common/entity/api/DataEntityApi;
+      30: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+      33: invokevirtual #25                 // Method com/jeesite/modules/sys/entity/Office.setParent:(Lcom/jeesite/modules/sys/entity/Office;)V
+      36: aload_1
+      37: invokevirtual #26                 // Method com/jeesite/modules/sys/entity/Office.getIsNewRecord:()Z
+      40: ifeq          157
+      43: aload_1
+      44: bipush        30
+      46: invokestatic  #27                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+      49: invokevirtual #28                 // Method com/jeesite/modules/sys/entity/Office.setTreeSort:(Ljava/lang/Integer;)V
+      52: new           #4                  // class com/jeesite/modules/sys/entity/Office
+      55: dup
+      56: invokespecial #29                 // Method com/jeesite/modules/sys/entity/Office."<init>":()V
+      59: astore_3
+      60: aload_3
+      61: aload_1
+      62: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+      65: invokevirtual #16                 // Method com/jeesite/modules/sys/entity/Office.setParentCode:(Ljava/lang/String;)V
+      68: aload_0
+      69: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      72: aload_3
+      73: invokeinterface #30,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.getLastByParentCode:(Lcom/jeesite/common/entity/api/TreeEntityApi;)Lcom/jeesite/common/entity/api/TreeEntityApi;
+      78: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+      81: astore        4
+      83: aload         4
+      85: ifnull        121
+      88: aload_1
+      89: aload         4
+      91: invokevirtual #31                 // Method com/jeesite/modules/sys/entity/Office.getTreeSort:()Ljava/lang/Integer;
+      94: invokevirtual #32                 // Method java/lang/Integer.intValue:()I
+      97: bipush        30
+      99: iadd
+     100: invokestatic  #27                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+     103: invokevirtual #28                 // Method com/jeesite/modules/sys/entity/Office.setTreeSort:(Ljava/lang/Integer;)V
+     106: aload_1
+     107: aload         4
+     109: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     112: invokestatic  #33                 // Method com/jeesite/common/idgen/IdGen.nextCode:(Ljava/lang/String;)Ljava/lang/String;
+     115: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/Office.setViewCode:(Ljava/lang/String;)V
+     118: goto          157
+     121: aload_1
+     122: invokevirtual #35                 // Method com/jeesite/modules/sys/entity/Office.getParent:()Lcom/jeesite/modules/sys/entity/Office;
+     125: ifnull        157
+     128: aload_1
+     129: new           #36                 // class java/lang/StringBuilder
+     132: dup
+     133: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+     136: aload_1
+     137: invokevirtual #35                 // Method com/jeesite/modules/sys/entity/Office.getParent:()Lcom/jeesite/modules/sys/entity/Office;
+     140: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     143: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     146: ldc           #39                 // String 001
+     148: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     151: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     154: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/Office.setViewCode:(Ljava/lang/String;)V
+     157: aload_2
+     158: ldc           #5                  // String office
+     160: aload_1
+     161: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+     166: pop
+     167: aload_2
+     168: ldc           #7                  // String ctrlPermi
+     170: ldc           #8                  // String user.adminCtrlPermi
+     172: ldc           #9                  // String 2
+     174: invokestatic  #10                 // Method com/jeesite/common/config/Global.getConfig:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+     177: invokeinterface #6,  3            // InterfaceMethod org/springframework/ui/Model.addAttribute:(Ljava/lang/String;Ljava/lang/Object;)Lorg/springframework/ui/Model;
+     182: pop
+     183: ldc           #41                 // String modules/sys/officeForm
+     185: areturn
+
+  public com.jeesite.modules.sys.entity.Office createNextNode(com.jeesite.modules.sys.entity.Office);
+    Code:
+       0: aload_1
+       1: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+       4: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+       7: ifeq          30
+      10: aload_1
+      11: aload_0
+      12: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      15: aload_1
+      16: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+      19: invokeinterface #24,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.get:(Ljava/lang/String;)Lcom/jeesite/common/entity/api/DataEntityApi;
+      24: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+      27: invokevirtual #25                 // Method com/jeesite/modules/sys/entity/Office.setParent:(Lcom/jeesite/modules/sys/entity/Office;)V
+      30: aload_1
+      31: invokevirtual #26                 // Method com/jeesite/modules/sys/entity/Office.getIsNewRecord:()Z
+      34: ifeq          138
+      37: new           #4                  // class com/jeesite/modules/sys/entity/Office
+      40: dup
+      41: invokespecial #29                 // Method com/jeesite/modules/sys/entity/Office."<init>":()V
+      44: astore_2
+      45: aload_2
+      46: aload_1
+      47: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+      50: invokevirtual #16                 // Method com/jeesite/modules/sys/entity/Office.setParentCode:(Ljava/lang/String;)V
+      53: aload_0
+      54: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      57: aload_2
+      58: invokeinterface #30,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.getLastByParentCode:(Lcom/jeesite/common/entity/api/TreeEntityApi;)Lcom/jeesite/common/entity/api/TreeEntityApi;
+      63: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+      66: astore_3
+      67: aload_3
+      68: ifnull        102
+      71: aload_1
+      72: aload_3
+      73: invokevirtual #31                 // Method com/jeesite/modules/sys/entity/Office.getTreeSort:()Ljava/lang/Integer;
+      76: invokevirtual #32                 // Method java/lang/Integer.intValue:()I
+      79: bipush        30
+      81: iadd
+      82: invokestatic  #27                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+      85: invokevirtual #28                 // Method com/jeesite/modules/sys/entity/Office.setTreeSort:(Ljava/lang/Integer;)V
+      88: aload_1
+      89: aload_3
+      90: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+      93: invokestatic  #33                 // Method com/jeesite/common/idgen/IdGen.nextCode:(Ljava/lang/String;)Ljava/lang/String;
+      96: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/Office.setViewCode:(Ljava/lang/String;)V
+      99: goto          138
+     102: aload_1
+     103: invokevirtual #35                 // Method com/jeesite/modules/sys/entity/Office.getParent:()Lcom/jeesite/modules/sys/entity/Office;
+     106: ifnull        138
+     109: aload_1
+     110: new           #36                 // class java/lang/StringBuilder
+     113: dup
+     114: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+     117: aload_1
+     118: invokevirtual #35                 // Method com/jeesite/modules/sys/entity/Office.getParent:()Lcom/jeesite/modules/sys/entity/Office;
+     121: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     124: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     127: ldc           #39                 // String 001
+     129: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     132: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     135: invokevirtual #34                 // Method com/jeesite/modules/sys/entity/Office.setViewCode:(Ljava/lang/String;)V
+     138: aload_1
+     139: invokevirtual #31                 // Method com/jeesite/modules/sys/entity/Office.getTreeSort:()Ljava/lang/Integer;
+     142: ifnonnull     154
+     145: aload_1
+     146: bipush        30
+     148: invokestatic  #27                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+     151: invokevirtual #28                 // Method com/jeesite/modules/sys/entity/Office.setTreeSort:(Ljava/lang/Integer;)V
+     154: aload_1
+     155: areturn
+
+  public java.lang.String save(com.jeesite.modules.sys.entity.Office);
+    Code:
+       0: aload_0
+       1: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+       4: aload_1
+       5: invokeinterface #42,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.save:(Lcom/jeesite/modules/sys/entity/Office;)V
+      10: aload_0
+      11: ldc           #44                 // String true
+      13: ldc           #45                 // String 保存机构''{0}''成功
+      15: iconst_1
+      16: anewarray     #46                 // class java/lang/String
+      19: dup
+      20: iconst_0
+      21: aload_1
+      22: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+      25: aastore
+      26: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      29: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      32: areturn
+
+  public void exportData(com.jeesite.modules.sys.entity.Office, java.lang.Boolean, java.lang.String, javax.servlet.http.HttpServletResponse);
+    Code:
+       0: aload_2
+       1: ifnull        20
+       4: aload_2
+       5: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+       8: ifeq          20
+      11: invokestatic  #50                 // Method com/jeesite/common/config/Global.isStrictMode:()Ljava/lang/Boolean;
+      14: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+      17: ifeq          31
+      20: aload_0
+      21: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      24: aload_1
+      25: aload_3
+      26: invokeinterface #21,  3           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/Office;Ljava/lang/String;)V
+      31: aload_1
+      32: invokevirtual #51                 // Method com/jeesite/modules/sys/entity/Office.getSqlMap:()Lcom/jeesite/common/mybatis/mapper/SqlMap;
+      35: invokevirtual #52                 // Method com/jeesite/common/mybatis/mapper/SqlMap.getOrder:()Lcom/jeesite/common/mybatis/mapper/query/QueryOrder;
+      38: ldc           #53                 // String a.tree_sorts
+      40: invokevirtual #54                 // Method com/jeesite/common/mybatis/mapper/query/QueryOrder.setOrderBy:(Ljava/lang/String;)V
+      43: aload_0
+      44: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      47: aload_1
+      48: invokeinterface #22,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.findList:(Lcom/jeesite/modules/sys/entity/Office;)Ljava/util/List;
+      53: astore        5
+      55: new           #36                 // class java/lang/StringBuilder
+      58: dup
+      59: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+      62: ldc           #55                 // String 机构数据
+      64: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      67: ldc           #56                 // String yyyyMMddHHmmss
+      69: invokestatic  #57                 // Method com/jeesite/common/lang/DateUtils.getDate:(Ljava/lang/String;)Ljava/lang/String;
+      72: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      75: ldc           #58                 // String .xlsx
+      77: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      80: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+      83: astore        6
+      85: new           #59                 // class com/jeesite/common/utils/excel/ExcelExport
+      88: dup
+      89: ldc           #55                 // String 机构数据
+      91: ldc           #4                  // class com/jeesite/modules/sys/entity/Office
+      93: invokespecial #60                 // Method com/jeesite/common/utils/excel/ExcelExport."<init>":(Ljava/lang/String;Ljava/lang/Class;)V
+      96: astore        7
+      98: aconst_null
+      99: astore        8
+     101: aload         7
+     103: aload         5
+     105: invokevirtual #61                 // Method com/jeesite/common/utils/excel/ExcelExport.setDataList:(Ljava/util/List;)Lcom/jeesite/common/utils/excel/ExcelExport;
+     108: aload         4
+     110: aload         6
+     112: invokevirtual #62                 // Method com/jeesite/common/utils/excel/ExcelExport.write:(Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;)Lcom/jeesite/common/utils/excel/ExcelExport;
+     115: pop
+     116: aload         7
+     118: ifnull        203
+     121: aload         8
+     123: ifnull        146
+     126: aload         7
+     128: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+     131: goto          203
+     134: astore        9
+     136: aload         8
+     138: aload         9
+     140: invokevirtual #65                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+     143: goto          203
+     146: aload         7
+     148: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+     151: goto          203
+     154: astore        9
+     156: aload         9
+     158: astore        8
+     160: aload         9
+     162: athrow
+     163: astore        10
+     165: aload         7
+     167: ifnull        200
+     170: aload         8
+     172: ifnull        195
+     175: aload         7
+     177: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+     180: goto          200
+     183: astore        11
+     185: aload         8
+     187: aload         11
+     189: invokevirtual #65                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+     192: goto          200
+     195: aload         7
+     197: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+     200: aload         10
+     202: athrow
+     203: return
+    Exception table:
+       from    to  target type
+         126   131   134   Class java/lang/Throwable
+         101   116   154   Class java/lang/Throwable
+         101   116   163   any
+         175   180   183   Class java/lang/Throwable
+         154   165   163   any
+
+  public void importTemplate(javax.servlet.http.HttpServletResponse);
+    Code:
+       0: new           #4                  // class com/jeesite/modules/sys/entity/Office
+       3: dup
+       4: invokespecial #29                 // Method com/jeesite/modules/sys/entity/Office."<init>":()V
+       7: astore_2
+       8: iconst_1
+       9: anewarray     #4                  // class com/jeesite/modules/sys/entity/Office
+      12: dup
+      13: iconst_0
+      14: aload_2
+      15: aastore
+      16: invokestatic  #66                 // Method com/jeesite/common/collect/ListUtils.newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList;
+      19: astore_3
+      20: ldc           #67                 // String 机构数据模板.xlsx
+      22: astore        4
+      24: new           #59                 // class com/jeesite/common/utils/excel/ExcelExport
+      27: dup
+      28: ldc           #55                 // String 机构数据
+      30: ldc           #4                  // class com/jeesite/modules/sys/entity/Office
+      32: getstatic     #68                 // Field com/jeesite/common/utils/excel/annotation/ExcelField$Type.IMPORT:Lcom/jeesite/common/utils/excel/annotation/ExcelField$Type;
+      35: iconst_0
+      36: anewarray     #46                 // class java/lang/String
+      39: invokespecial #69                 // Method com/jeesite/common/utils/excel/ExcelExport."<init>":(Ljava/lang/String;Ljava/lang/Class;Lcom/jeesite/common/utils/excel/annotation/ExcelField$Type;[Ljava/lang/String;)V
+      42: astore        5
+      44: aconst_null
+      45: astore        6
+      47: aload         5
+      49: aload_3
+      50: invokevirtual #61                 // Method com/jeesite/common/utils/excel/ExcelExport.setDataList:(Ljava/util/List;)Lcom/jeesite/common/utils/excel/ExcelExport;
+      53: aload_1
+      54: aload         4
+      56: invokevirtual #62                 // Method com/jeesite/common/utils/excel/ExcelExport.write:(Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;)Lcom/jeesite/common/utils/excel/ExcelExport;
+      59: pop
+      60: aload         5
+      62: ifnull        147
+      65: aload         6
+      67: ifnull        90
+      70: aload         5
+      72: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+      75: goto          147
+      78: astore        7
+      80: aload         6
+      82: aload         7
+      84: invokevirtual #65                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+      87: goto          147
+      90: aload         5
+      92: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+      95: goto          147
+      98: astore        7
+     100: aload         7
+     102: astore        6
+     104: aload         7
+     106: athrow
+     107: astore        8
+     109: aload         5
+     111: ifnull        144
+     114: aload         6
+     116: ifnull        139
+     119: aload         5
+     121: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+     124: goto          144
+     127: astore        9
+     129: aload         6
+     131: aload         9
+     133: invokevirtual #65                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
+     136: goto          144
+     139: aload         5
+     141: invokevirtual #63                 // Method com/jeesite/common/utils/excel/ExcelExport.close:()V
+     144: aload         8
+     146: athrow
+     147: return
+    Exception table:
+       from    to  target type
+          70    75    78   Class java/lang/Throwable
+          47    60    98   Class java/lang/Throwable
+          47    60   107   any
+         119   124   127   Class java/lang/Throwable
+          98   109   107   any
+
+  public java.lang.String importData(org.springframework.web.multipart.MultipartFile, java.lang.String);
+    Code:
+       0: ldc           #70                 // String 1
+       2: aload_2
+       3: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+       6: istore_3
+       7: aload_0
+       8: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      11: aload_1
+      12: iload_3
+      13: invokestatic  #72                 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
+      16: invokeinterface #73,  3           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.importData:(Lorg/springframework/web/multipart/MultipartFile;Ljava/lang/Boolean;)Ljava/lang/String;
+      21: astore        4
+      23: aload_0
+      24: ldc           #44                 // String true
+      26: new           #36                 // class java/lang/StringBuilder
+      29: dup
+      30: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+      33: ldc           #74                 // String posfull:
+      35: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      38: aload         4
+      40: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      43: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+      46: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      49: areturn
+      50: astore_3
+      51: aload_0
+      52: ldc           #76                 // String false
+      54: new           #36                 // class java/lang/StringBuilder
+      57: dup
+      58: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+      61: ldc           #74                 // String posfull:
+      63: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      66: aload_3
+      67: invokevirtual #77                 // Method java/lang/Exception.getMessage:()Ljava/lang/String;
+      70: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      73: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+      76: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      79: areturn
+    Exception table:
+       from    to  target type
+           0    49    50   Class java/lang/Exception
+
+  public java.lang.String disable(com.jeesite.modules.sys.entity.Office);
+    Code:
+       0: new           #4                  // class com/jeesite/modules/sys/entity/Office
+       3: dup
+       4: invokespecial #29                 // Method com/jeesite/modules/sys/entity/Office."<init>":()V
+       7: astore_2
+       8: aload_2
+       9: ldc           #15                 // String 0
+      11: invokevirtual #78                 // Method com/jeesite/modules/sys/entity/Office.setStatus:(Ljava/lang/String;)V
+      14: aload_2
+      15: new           #36                 // class java/lang/StringBuilder
+      18: dup
+      19: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+      22: ldc           #79                 // String ,
+      24: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      27: aload_1
+      28: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+      31: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      34: ldc           #79                 // String ,
+      36: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+      39: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+      42: invokevirtual #81                 // Method com/jeesite/modules/sys/entity/Office.setParentCodes:(Ljava/lang/String;)V
+      45: aload_0
+      46: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      49: aload_2
+      50: invokeinterface #82,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.findCount:(Lcom/jeesite/common/entity/api/DataEntityApi;)J
+      55: lstore_3
+      56: lload_3
+      57: lconst_0
+      58: lcmp
+      59: ifle          78
+      62: aload_0
+      63: ldc           #76                 // String false
+      65: ldc           #83                 // String 该机构包含未停用的子机构!
+      67: iconst_0
+      68: anewarray     #46                 // class java/lang/String
+      71: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      74: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      77: areturn
+      78: aload_1
+      79: ldc           #9                  // String 2
+      81: invokevirtual #78                 // Method com/jeesite/modules/sys/entity/Office.setStatus:(Ljava/lang/String;)V
+      84: aload_0
+      85: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      88: aload_1
+      89: invokeinterface #84,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.updateStatus:(Lcom/jeesite/modules/sys/entity/Office;)V
+      94: aload_0
+      95: ldc           #44                 // String true
+      97: ldc           #85                 // String 停用机构''{0}''成功
+      99: iconst_1
+     100: anewarray     #46                 // class java/lang/String
+     103: dup
+     104: iconst_0
+     105: aload_1
+     106: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+     109: aastore
+     110: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+     113: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+     116: areturn
+
+  public java.lang.String enable(com.jeesite.modules.sys.entity.Office);
+    Code:
+       0: aload_1
+       1: ldc           #15                 // String 0
+       3: invokevirtual #78                 // Method com/jeesite/modules/sys/entity/Office.setStatus:(Ljava/lang/String;)V
+       6: aload_0
+       7: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      10: aload_1
+      11: invokeinterface #84,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.updateStatus:(Lcom/jeesite/modules/sys/entity/Office;)V
+      16: aload_0
+      17: ldc           #44                 // String true
+      19: ldc           #86                 // String 启用机构''{0}''成功
+      21: iconst_1
+      22: anewarray     #46                 // class java/lang/String
+      25: dup
+      26: iconst_0
+      27: aload_1
+      28: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+      31: aastore
+      32: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      35: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      38: areturn
+
+  public java.lang.String delete(com.jeesite.modules.sys.entity.Office);
+    Code:
+       0: aload_0
+       1: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+       4: aload_1
+       5: invokeinterface #87,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.delete:(Lcom/jeesite/modules/sys/entity/Office;)V
+      10: aload_0
+      11: ldc           #44                 // String true
+      13: ldc           #88                 // String 删除机构''{0}''成功
+      15: iconst_1
+      16: anewarray     #46                 // class java/lang/String
+      19: dup
+      20: iconst_0
+      21: aload_1
+      22: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+      25: aastore
+      26: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      29: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      32: areturn
+
+  public java.util.List<java.util.Map<java.lang.String, java.lang.Object>> treeData(java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+    Code:
+       0: invokestatic  #89                 // Method com/jeesite/common/collect/ListUtils.newArrayList:()Ljava/util/ArrayList;
+       3: astore        13
+       5: new           #4                  // class com/jeesite/modules/sys/entity/Office
+       8: dup
+       9: invokespecial #29                 // Method com/jeesite/modules/sys/entity/Office."<init>":()V
+      12: astore        14
+      14: aload         14
+      16: ldc           #15                 // String 0
+      18: invokevirtual #78                 // Method com/jeesite/modules/sys/entity/Office.setStatus:(Ljava/lang/String;)V
+      21: aload         14
+      23: aload         5
+      25: invokevirtual #90                 // Method com/jeesite/modules/sys/entity/Office.setCompanyCode:(Ljava/lang/String;)V
+      28: aload_3
+      29: ifnull        48
+      32: aload_3
+      33: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+      36: ifeq          48
+      39: invokestatic  #50                 // Method com/jeesite/common/config/Global.isStrictMode:()Ljava/lang/Boolean;
+      42: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+      45: ifeq          61
+      48: aload_0
+      49: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      52: aload         14
+      54: aload         12
+      56: invokeinterface #21,  3           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/Office;Ljava/lang/String;)V
+      61: aload_2
+      62: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      65: ifeq          74
+      68: aload         14
+      70: aload_2
+      71: invokevirtual #16                 // Method com/jeesite/modules/sys/entity/Office.setParentCode:(Ljava/lang/String;)V
+      74: aload         4
+      76: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      79: ifeq          94
+      82: aload         14
+      84: aload         4
+      86: ldc           #79                 // String ,
+      88: invokevirtual #91                 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
+      91: invokevirtual #92                 // Method com/jeesite/modules/sys/entity/Office.setOfficeType_in:([Ljava/lang/String;)V
+      94: invokestatic  #89                 // Method com/jeesite/common/collect/ListUtils.newArrayList:()Ljava/util/ArrayList;
+      97: astore        15
+      99: aload_0
+     100: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+     103: aload         14
+     105: invokeinterface #22,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.findList:(Lcom/jeesite/modules/sys/entity/Office;)Ljava/util/List;
+     110: astore        16
+     112: iconst_0
+     113: istore        17
+     115: iload         17
+     117: aload         16
+     119: invokeinterface #93,  1           // InterfaceMethod java/util/List.size:()I
+     124: if_icmpge     418
+     127: aload         16
+     129: iload         17
+     131: invokeinterface #94,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
+     136: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+     139: astore        18
+     141: ldc           #15                 // String 0
+     143: aload         18
+     145: invokevirtual #95                 // Method com/jeesite/modules/sys/entity/Office.getStatus:()Ljava/lang/String;
+     148: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     151: ifne          157
+     154: goto          412
+     157: aload_1
+     158: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+     161: ifeq          217
+     164: aload         18
+     166: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+     169: aload_1
+     170: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     173: ifeq          179
+     176: goto          412
+     179: aload         18
+     181: invokevirtual #96                 // Method com/jeesite/modules/sys/entity/Office.getParentCodes:()Ljava/lang/String;
+     184: new           #36                 // class java/lang/StringBuilder
+     187: dup
+     188: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+     191: ldc           #79                 // String ,
+     193: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     196: aload_1
+     197: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     200: ldc           #79                 // String ,
+     202: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     205: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     208: invokevirtual #97                 // Method java/lang/String.contains:(Ljava/lang/CharSequence;)Z
+     211: ifeq          217
+     214: goto          412
+     217: aload         15
+     219: aload         18
+     221: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+     224: invokeinterface #98,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
+     229: pop
+     230: invokestatic  #99                 // Method com/jeesite/common/collect/MapUtils.newHashMap:()Ljava/util/HashMap;
+     233: astore        19
+     235: aload         19
+     237: ldc           #100                // String id
+     239: aload         18
+     241: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+     244: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     249: pop
+     250: aload         19
+     252: ldc           #102                // String pId
+     254: aload         18
+     256: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+     259: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     264: pop
+     265: aload         18
+     267: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+     270: astore        20
+     272: ldc           #44                 // String true
+     274: aload         7
+     276: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     279: ifne          292
+     282: ldc           #70                 // String 1
+     284: aload         7
+     286: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     289: ifeq          299
+     292: aload         18
+     294: invokevirtual #20                 // Method com/jeesite/modules/sys/entity/Office.getFullName:()Ljava/lang/String;
+     297: astore        20
+     299: aload         19
+     301: ldc           #103                // String code
+     303: aload         18
+     305: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     308: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     313: pop
+     314: aload         19
+     316: ldc           #104                // String name
+     318: aload         6
+     320: aload         18
+     322: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     325: aload         20
+     327: invokestatic  #105                // Method com/jeesite/common/lang/StringUtils.getTreeNodeName:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+     330: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     335: pop
+     336: aload         19
+     338: ldc           #106                // String title
+     340: aload         18
+     342: invokevirtual #20                 // Method com/jeesite/modules/sys/entity/Office.getFullName:()Ljava/lang/String;
+     345: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     350: pop
+     351: aload         19
+     353: ldc           #107                // String isParent
+     355: aload         18
+     357: invokevirtual #108                // Method com/jeesite/modules/sys/entity/Office.getIsTreeLeaf:()Ljava/lang/Boolean;
+     360: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+     363: ifeq          388
+     366: aload         8
+     368: iconst_2
+     369: anewarray     #46                 // class java/lang/String
+     372: dup
+     373: iconst_0
+     374: ldc           #44                 // String true
+     376: aastore
+     377: dup
+     378: iconst_1
+     379: ldc           #109                // String lazy
+     381: aastore
+     382: invokestatic  #110                // Method com/jeesite/common/lang/StringUtils.inString:(Ljava/lang/String;[Ljava/lang/String;)Z
+     385: ifeq          392
+     388: iconst_1
+     389: goto          393
+     392: iconst_0
+     393: invokestatic  #72                 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
+     396: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     401: pop
+     402: aload         13
+     404: aload         19
+     406: invokeinterface #98,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
+     411: pop
+     412: iinc          17, 1
+     415: goto          115
+     418: aload         8
+     420: ldc           #44                 // String true
+     422: invokestatic  #111                // Method com/jeesite/common/lang/StringUtils.equals:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Z
+     425: ifeq          490
+     428: aload         15
+     430: invokeinterface #93,  1           // InterfaceMethod java/util/List.size:()I
+     435: ifle          490
+     438: aload_0
+     439: getfield      #112                // Field empUserController:Lcom/jeesite/modules/sys/web/user/EmpUserController;
+     442: aload         9
+     444: aload         15
+     446: aload         15
+     448: invokeinterface #93,  1           // InterfaceMethod java/util/List.size:()I
+     453: anewarray     #46                 // class java/lang/String
+     456: invokeinterface #113,  2          // InterfaceMethod java/util/List.toArray:([Ljava/lang/Object;)[Ljava/lang/Object;
+     461: checkcast     #114                // class "[Ljava/lang/String;"
+     464: aload         5
+     466: aload         10
+     468: aload         11
+     470: aload_3
+     471: aload         6
+     473: aload         12
+     475: invokevirtual #115                // Method com/jeesite/modules/sys/web/user/EmpUserController.treeData:(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
+     478: astore        17
+     480: aload         13
+     482: aload         17
+     484: invokeinterface #116,  2          // InterfaceMethod java/util/List.addAll:(Ljava/util/Collection;)Z
+     489: pop
+     490: aload         8
+     492: iconst_1
+     493: anewarray     #46                 // class java/lang/String
+     496: dup
+     497: iconst_0
+     498: ldc           #109                // String lazy
+     500: aastore
+     501: invokestatic  #110                // Method com/jeesite/common/lang/StringUtils.inString:(Ljava/lang/String;[Ljava/lang/String;)Z
+     504: ifeq          554
+     507: aload_2
+     508: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+     511: ifeq          554
+     514: aload_0
+     515: getfield      #112                // Field empUserController:Lcom/jeesite/modules/sys/web/user/EmpUserController;
+     518: aload         9
+     520: iconst_1
+     521: anewarray     #46                 // class java/lang/String
+     524: dup
+     525: iconst_0
+     526: aload_2
+     527: aastore
+     528: aload         5
+     530: aload         10
+     532: aload         11
+     534: aload_3
+     535: aload         6
+     537: aload         12
+     539: invokevirtual #115                // Method com/jeesite/modules/sys/web/user/EmpUserController.treeData:(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
+     542: astore        17
+     544: aload         13
+     546: aload         17
+     548: invokeinterface #116,  2          // InterfaceMethod java/util/List.addAll:(Ljava/util/Collection;)Z
+     553: pop
+     554: aload         13
+     556: areturn
+
+  public java.lang.String fixTreeData();
+    Code:
+       0: invokestatic  #117                // Method com/jeesite/modules/sys/utils/UserUtils.getUser:()Lcom/jeesite/modules/sys/entity/User;
+       3: invokevirtual #118                // Method com/jeesite/modules/sys/entity/User.isAdmin:()Z
+       6: ifne          25
+       9: aload_0
+      10: ldc           #76                 // String false
+      12: ldc           #119                // String 操作失败,只有管理员才能进行修复!
+      14: iconst_0
+      15: anewarray     #46                 // class java/lang/String
+      18: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      21: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      24: areturn
+      25: aload_0
+      26: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      29: invokeinterface #120,  1          // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.fixTreeData:()V
+      34: aload_0
+      35: ldc           #44                 // String true
+      37: ldc           #121                // String 数据修复成功
+      39: iconst_0
+      40: anewarray     #46                 // class java/lang/String
+      43: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      46: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      49: areturn
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1950 - 0
_tmp_decompile/OfficeController.verbose.txt


BIN=BIN
_tmp_decompile/OfficeServiceSupport.local.txt


+ 340 - 0
_tmp_decompile/Office_treeData.txt

@@ -0,0 +1,340 @@
+  public java.util.List<java.util.Map<java.lang.String, java.lang.Object>> treeData(java.lang.String, java.lang.String, java.lang.Boolean, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+    Code:
+       0: invokestatic  #89                 // Method com/jeesite/common/collect/ListUtils.newArrayList:()Ljava/util/ArrayList;
+       3: astore        13
+       5: new           #4                  // class com/jeesite/modules/sys/entity/Office
+       8: dup
+       9: invokespecial #29                 // Method com/jeesite/modules/sys/entity/Office."<init>":()V
+      12: astore        14
+      14: aload         14
+      16: ldc           #15                 // String 0
+      18: invokevirtual #78                 // Method com/jeesite/modules/sys/entity/Office.setStatus:(Ljava/lang/String;)V
+      21: aload         14
+      23: aload         5
+      25: invokevirtual #90                 // Method com/jeesite/modules/sys/entity/Office.setCompanyCode:(Ljava/lang/String;)V
+      28: aload_3
+      29: ifnull        48
+      32: aload_3
+      33: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+      36: ifeq          48
+      39: invokestatic  #50                 // Method com/jeesite/common/config/Global.isStrictMode:()Ljava/lang/Boolean;
+      42: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+      45: ifeq          61
+      48: aload_0
+      49: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      52: aload         14
+      54: aload         12
+      56: invokeinterface #21,  3           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.addDataScopeFilter:(Lcom/jeesite/modules/sys/entity/Office;Ljava/lang/String;)V
+      61: aload_2
+      62: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      65: ifeq          74
+      68: aload         14
+      70: aload_2
+      71: invokevirtual #16                 // Method com/jeesite/modules/sys/entity/Office.setParentCode:(Ljava/lang/String;)V
+      74: aload         4
+      76: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+      79: ifeq          94
+      82: aload         14
+      84: aload         4
+      86: ldc           #79                 // String ,
+      88: invokevirtual #91                 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
+      91: invokevirtual #92                 // Method com/jeesite/modules/sys/entity/Office.setOfficeType_in:([Ljava/lang/String;)V
+      94: invokestatic  #89                 // Method com/jeesite/common/collect/ListUtils.newArrayList:()Ljava/util/ArrayList;
+      97: astore        15
+      99: aload_0
+     100: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+     103: aload         14
+     105: invokeinterface #22,  2           // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.findList:(Lcom/jeesite/modules/sys/entity/Office;)Ljava/util/List;
+     110: astore        16
+     112: iconst_0
+     113: istore        17
+     115: iload         17
+     117: aload         16
+     119: invokeinterface #93,  1           // InterfaceMethod java/util/List.size:()I
+     124: if_icmpge     418
+     127: aload         16
+     129: iload         17
+     131: invokeinterface #94,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
+     136: checkcast     #4                  // class com/jeesite/modules/sys/entity/Office
+     139: astore        18
+     141: ldc           #15                 // String 0
+     143: aload         18
+     145: invokevirtual #95                 // Method com/jeesite/modules/sys/entity/Office.getStatus:()Ljava/lang/String;
+     148: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     151: ifne          157
+     154: goto          412
+     157: aload_1
+     158: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+     161: ifeq          217
+     164: aload         18
+     166: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+     169: aload_1
+     170: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     173: ifeq          179
+     176: goto          412
+     179: aload         18
+     181: invokevirtual #96                 // Method com/jeesite/modules/sys/entity/Office.getParentCodes:()Ljava/lang/String;
+     184: new           #36                 // class java/lang/StringBuilder
+     187: dup
+     188: invokespecial #37                 // Method java/lang/StringBuilder."<init>":()V
+     191: ldc           #79                 // String ,
+     193: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     196: aload_1
+     197: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     200: ldc           #79                 // String ,
+     202: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
+     205: invokevirtual #40                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
+     208: invokevirtual #97                 // Method java/lang/String.contains:(Ljava/lang/CharSequence;)Z
+     211: ifeq          217
+     214: goto          412
+     217: aload         15
+     219: aload         18
+     221: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+     224: invokeinterface #98,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
+     229: pop
+     230: invokestatic  #99                 // Method com/jeesite/common/collect/MapUtils.newHashMap:()Ljava/util/HashMap;
+     233: astore        19
+     235: aload         19
+     237: ldc           #100                // String id
+     239: aload         18
+     241: invokevirtual #80                 // Method com/jeesite/modules/sys/entity/Office.getId:()Ljava/lang/String;
+     244: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     249: pop
+     250: aload         19
+     252: ldc           #102                // String pId
+     254: aload         18
+     256: invokevirtual #13                 // Method com/jeesite/modules/sys/entity/Office.getParentCode:()Ljava/lang/String;
+     259: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     264: pop
+     265: aload         18
+     267: invokevirtual #19                 // Method com/jeesite/modules/sys/entity/Office.getOfficeName:()Ljava/lang/String;
+     270: astore        20
+     272: ldc           #44                 // String true
+     274: aload         7
+     276: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     279: ifne          292
+     282: ldc           #70                 // String 1
+     284: aload         7
+     286: invokevirtual #71                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
+     289: ifeq          299
+     292: aload         18
+     294: invokevirtual #20                 // Method com/jeesite/modules/sys/entity/Office.getFullName:()Ljava/lang/String;
+     297: astore        20
+     299: aload         19
+     301: ldc           #103                // String code
+     303: aload         18
+     305: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     308: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     313: pop
+     314: aload         19
+     316: ldc           #104                // String name
+     318: aload         6
+     320: aload         18
+     322: invokevirtual #17                 // Method com/jeesite/modules/sys/entity/Office.getViewCode:()Ljava/lang/String;
+     325: aload         20
+     327: invokestatic  #105                // Method com/jeesite/common/lang/StringUtils.getTreeNodeName:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+     330: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     335: pop
+     336: aload         19
+     338: ldc           #106                // String title
+     340: aload         18
+     342: invokevirtual #20                 // Method com/jeesite/modules/sys/entity/Office.getFullName:()Ljava/lang/String;
+     345: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     350: pop
+     351: aload         19
+     353: ldc           #107                // String isParent
+     355: aload         18
+     357: invokevirtual #108                // Method com/jeesite/modules/sys/entity/Office.getIsTreeLeaf:()Ljava/lang/Boolean;
+     360: invokevirtual #49                 // Method java/lang/Boolean.booleanValue:()Z
+     363: ifeq          388
+     366: aload         8
+     368: iconst_2
+     369: anewarray     #46                 // class java/lang/String
+     372: dup
+     373: iconst_0
+     374: ldc           #44                 // String true
+     376: aastore
+     377: dup
+     378: iconst_1
+     379: ldc           #109                // String lazy
+     381: aastore
+     382: invokestatic  #110                // Method com/jeesite/common/lang/StringUtils.inString:(Ljava/lang/String;[Ljava/lang/String;)Z
+     385: ifeq          392
+     388: iconst_1
+     389: goto          393
+     392: iconst_0
+     393: invokestatic  #72                 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
+     396: invokeinterface #101,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+     401: pop
+     402: aload         13
+     404: aload         19
+     406: invokeinterface #98,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
+     411: pop
+     412: iinc          17, 1
+     415: goto          115
+     418: aload         8
+     420: ldc           #44                 // String true
+     422: invokestatic  #111                // Method com/jeesite/common/lang/StringUtils.equals:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Z
+     425: ifeq          490
+     428: aload         15
+     430: invokeinterface #93,  1           // InterfaceMethod java/util/List.size:()I
+     435: ifle          490
+     438: aload_0
+     439: getfield      #112                // Field empUserController:Lcom/jeesite/modules/sys/web/user/EmpUserController;
+     442: aload         9
+     444: aload         15
+     446: aload         15
+     448: invokeinterface #93,  1           // InterfaceMethod java/util/List.size:()I
+     453: anewarray     #46                 // class java/lang/String
+     456: invokeinterface #113,  2          // InterfaceMethod java/util/List.toArray:([Ljava/lang/Object;)[Ljava/lang/Object;
+     461: checkcast     #114                // class "[Ljava/lang/String;"
+     464: aload         5
+     466: aload         10
+     468: aload         11
+     470: aload_3
+     471: aload         6
+     473: aload         12
+     475: invokevirtual #115                // Method com/jeesite/modules/sys/web/user/EmpUserController.treeData:(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
+     478: astore        17
+     480: aload         13
+     482: aload         17
+     484: invokeinterface #116,  2          // InterfaceMethod java/util/List.addAll:(Ljava/util/Collection;)Z
+     489: pop
+     490: aload         8
+     492: iconst_1
+     493: anewarray     #46                 // class java/lang/String
+     496: dup
+     497: iconst_0
+     498: ldc           #109                // String lazy
+     500: aastore
+     501: invokestatic  #110                // Method com/jeesite/common/lang/StringUtils.inString:(Ljava/lang/String;[Ljava/lang/String;)Z
+     504: ifeq          554
+     507: aload_2
+     508: invokestatic  #18                 // Method com/jeesite/common/lang/StringUtils.isNotBlank:(Ljava/lang/CharSequence;)Z
+     511: ifeq          554
+     514: aload_0
+     515: getfield      #112                // Field empUserController:Lcom/jeesite/modules/sys/web/user/EmpUserController;
+     518: aload         9
+     520: iconst_1
+     521: anewarray     #46                 // class java/lang/String
+     524: dup
+     525: iconst_0
+     526: aload_2
+     527: aastore
+     528: aload         5
+     530: aload         10
+     532: aload         11
+     534: aload_3
+     535: aload         6
+     537: aload         12
+     539: invokevirtual #115                // Method com/jeesite/modules/sys/web/user/EmpUserController.treeData:(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
+     542: astore        17
+     544: aload         13
+     546: aload         17
+     548: invokeinterface #116,  2          // InterfaceMethod java/util/List.addAll:(Ljava/util/Collection;)Z
+     553: pop
+     554: aload         13
+     556: areturn
+    LineNumberTable:
+      line 290: 0
+      line 291: 5
+      line 292: 14
+      line 293: 21
+      line 294: 28
+      line 295: 48
+      line 298: 61
+      line 299: 68
+      line 302: 74
+      line 303: 82
+      line 305: 94
+      line 306: 99
+      line 307: 112
+      line 308: 127
+      line 310: 141
+      line 311: 154
+      line 314: 157
+      line 315: 164
+      line 316: 176
+      line 318: 179
+      line 319: 214
+      line 322: 217
+      line 323: 230
+      line 324: 235
+      line 325: 250
+      line 326: 265
+      line 327: 272
+      line 328: 292
+      line 330: 299
+      line 331: 314
+      line 332: 336
+      line 334: 351
+      line 335: 402
+      line 307: 412
+      line 338: 418
+      line 339: 438
+      line 340: 448
+      line 342: 480
+      line 345: 490
+      line 346: 514
+      line 347: 539
+      line 349: 544
+      line 351: 554
+    LocalVariableTable:
+      Start  Length  Slot  Name   Signature
+        141     271    18     e   Lcom/jeesite/modules/sys/entity/Office;
+        235     177    19   map   Ljava/util/Map;
+        272     140    20  name   Ljava/lang/String;
+        115     303    17     i   I
+        480      10    17 userList   Ljava/util/List;
+        544      10    17 userList   Ljava/util/List;
+          0     557     0  this   Lcom/jeesite/modules/sys/web/OfficeController;
+          0     557     1 excludeCode   Ljava/lang/String;
+          0     557     2 parentCode   Ljava/lang/String;
+          0     557     3 isAll   Ljava/lang/Boolean;
+          0     557     4 officeTypes   Ljava/lang/String;
+          0     557     5 companyCode   Ljava/lang/String;
+          0     557     6 isShowCode   Ljava/lang/String;
+          0     557     7 isShowFullName   Ljava/lang/String;
+          0     557     8 isLoadUser   Ljava/lang/String;
+          0     557     9 userIdPrefix   Ljava/lang/String;
+          0     557    10 postCode   Ljava/lang/String;
+          0     557    11 roleCode   Ljava/lang/String;
+          0     557    12 ctrlPermi   Ljava/lang/String;
+          5     552    13 mapList   Ljava/util/List;
+         14     543    14 where   Lcom/jeesite/modules/sys/entity/Office;
+         99     458    15 idList   Ljava/util/List;
+        112     445    16  list   Ljava/util/List;
+
+  public java.lang.String fixTreeData();
+    Code:
+       0: invokestatic  #117                // Method com/jeesite/modules/sys/utils/UserUtils.getUser:()Lcom/jeesite/modules/sys/entity/User;
+       3: invokevirtual #118                // Method com/jeesite/modules/sys/entity/User.isAdmin:()Z
+       6: ifne          25
+       9: aload_0
+      10: ldc           #76                 // String false
+      12: ldc           #119                // String 操作失败,只有管理员才能进行修复!
+      14: iconst_0
+      15: anewarray     #46                 // class java/lang/String
+      18: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      21: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      24: areturn
+      25: aload_0
+      26: getfield      #2                  // Field officeService:Lcom/jeesite/modules/sys/service/OfficeService;
+      29: invokeinterface #120,  1          // InterfaceMethod com/jeesite/modules/sys/service/OfficeService.fixTreeData:()V
+      34: aload_0
+      35: ldc           #44                 // String true
+      37: ldc           #121                // String 数据修复成功
+      39: iconst_0
+      40: anewarray     #46                 // class java/lang/String
+      43: invokestatic  #47                 // Method text:(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
+      46: invokevirtual #48                 // Method renderResult:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+      49: areturn
+    LineNumberTable:
+      line 358: 0
+      line 359: 9
+      line 361: 25
+      line 362: 34
+    LocalVariableTable:
+      Start  Length  Slot  Name   Signature
+          0      50     0  this   Lcom/jeesite/modules/sys/web/OfficeController;
+}

+ 7 - 0
_tmp_decompile/RoleController.javap.txt

@@ -0,0 +1,7 @@
+javap : 错误: 找不到类: c:\work\demo\dayang\mescloud\_tmp_decompile\com\jeesite\modules\sys\web\RoleController.class
+所在位置 C:\Users\j2742\AppData\Local\Temp\ps-script-0c24ab7a-19f1-477a-8055-553cc30f83fe.ps1:118 字符: 412
++ ... p-Location; javap -c -p -l "$out\com\jeesite\modules\sys\web\RoleCont ...
++                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    + CategoryInfo          : NotSpecified: (错误: 找不到类: c:\wo...ontroller.class:String) [], RemoteException
+    + FullyQualifiedErrorId : NativeCommandError
+ 

BIN=BIN
_tmp_decompile/SysAutoConfiguration.local.txt


BIN=BIN
_tmp_decompile/TreeService.javap.txt


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 863 - 0
db/mes_cloud_export_20260812_135015.sql


+ 17 - 0
db/mysql/add_erp_prd_in_stock_menu.sql

@@ -0,0 +1,17 @@
+-- ERP 生产入库单查询菜单
+-- 父菜单:生产管理 (menu_code: 1672118505143263232)
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` = 'erpPrdInStock';
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'erpPrdInStock', '1672118505143263232', '0,1672118505143263232,',
+  151, '0000009290,0000000151,', '1', 1,
+  '生产管理/ERP生产入库单查询', 'ERP生产入库单查询', '1', '/mes/erpPrdInStock/list', '',
+  'fa-download', '', '', 'mes:erpPrdInStock:view', 82, '1', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), ''
+);

+ 120 - 0
db/mysql/add_mes_batch_quality.sql

@@ -0,0 +1,120 @@
+-- 批次码发货检记录表 + PC 菜单 + PDA 功能模块
+-- 状态流转:仓库中(0) --发货检OK--> 合格(1);NG 仍为仓库中,可再次检查;已发货(2)不可检查
+
+CREATE TABLE IF NOT EXISTS `mes_batch_quality` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `batch_sn` varchar(255) DEFAULT '' COMMENT '批次码',
+  `line_sn` varchar(100) DEFAULT '' COMMENT '产线编号',
+  `result` char(1) DEFAULT '' COMMENT '结果:1=OK 2=NG',
+  `remark` varchar(255) DEFAULT '' COMMENT '备注',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '检查人',
+  `create_date` datetime DEFAULT NULL COMMENT '检查时间',
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_batch_sn` (`batch_sn`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='批次码发货检记录';
+
+-- PC 菜单:生产管理 / 发货检记录
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesBatchQuality',
+  '1672118505143263232',
+  '0,1672118505143263232,',
+  158,
+  '0000009290,0000000158,',
+  '0',
+  1,
+  '生产管理/发货检记录',
+  '发货检记录',
+  '1',
+  '/mes/mesBatchQuality/list',
+  '',
+  'icon-check',
+  '',
+  '',
+  'mes:mesBatchQuality:view',
+  80,
+  '1',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesBatchQuality'
+);
+
+-- PDA 接口权限(角色授权时勾选)
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesBatchQualityApi',
+  'mesBatchQuality',
+  '0,1672118505143263232,mesBatchQuality,',
+  30,
+  '0000009290,0000000158,0000000030,',
+  '1',
+  2,
+  '生产管理/发货检记录/PDA接口',
+  'PDA接口',
+  '2',
+  '',
+  '',
+  '',
+  '',
+  '',
+  'mes:mesBatchQuality:api',
+  40,
+  '1',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesBatchQualityApi'
+);
+
+-- PDA 功能模块:发货检(质量管理分类)
+INSERT INTO `mes_icons` (
+  `id`, `category_id`, `title`, `mode`, `remark`, `status`, `auth`,
+  `create_by`, `create_date`, `update_by`, `update_date`
+)
+SELECT
+  '2090000000000000235',
+  '2075400793979899904',
+  '发货检',
+  '235',
+  '',
+  '0',
+  'mes:mesBatchQuality:api',
+  'system',
+  NOW(),
+  'system',
+  NOW()
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `mes_icons` WHERE `id` = '2090000000000000235' OR (`title` = '发货检' AND `mode` = '235')
+);

+ 84 - 0
db/mysql/add_mes_batch_sn_batch_status.sql

@@ -0,0 +1,84 @@
+-- 批次码记录表增加业务状态字段:0仓库中 1合格 2已发货,创建默认0
+ALTER TABLE `mes_batch_sn`
+  ADD COLUMN `batch_status` char(1) DEFAULT '0' COMMENT '状态:0仓库中 1合格 2已发货' AFTER `is_js`;
+
+-- 历史数据补默认值
+UPDATE `mes_batch_sn` SET `batch_status` = '0' WHERE `batch_status` IS NULL OR `batch_status` = '';
+
+-- 字典类型:批次码业务状态
+INSERT INTO `js_sys_dict_type` (
+	`id`, `dict_name`, `dict_type`, `is_sys`, `status`,
+	`create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+	'1999000000000000101',
+	'批次码-业务状态',
+	'mes_batch_sn_status',
+	'1',
+	'0',
+	'system',
+	NOW(),
+	'system',
+	NOW(),
+	'0仓库中 1合格 2已发货'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_type` WHERE `dict_type` = 'mes_batch_sn_status'
+);
+
+-- 字典项:0 仓库中
+INSERT INTO `js_sys_dict_data` (
+	`dict_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`,
+	`tree_leaf`, `tree_level`, `tree_names`, `dict_label`, `dict_value`,
+	`dict_icon`, `dict_type`, `is_sys`, `description`, `css_style`, `css_class`,
+	`status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`,
+	`corp_code`, `corp_name`
+)
+SELECT
+	'1999000000000000110', '0', '0,', 30, '0000000030,',
+	'1', 0, '仓库中', '仓库中', '0',
+	'', 'mes_batch_sn_status', '1', '创建后', '', 'tag blue',
+	'0', 'system', NOW(), 'system', NOW(), '',
+	'0', 'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_data` WHERE `dict_type` = 'mes_batch_sn_status' AND `dict_value` = '0'
+);
+
+-- 字典项:1 合格
+INSERT INTO `js_sys_dict_data` (
+	`dict_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`,
+	`tree_leaf`, `tree_level`, `tree_names`, `dict_label`, `dict_value`,
+	`dict_icon`, `dict_type`, `is_sys`, `description`, `css_style`, `css_class`,
+	`status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`,
+	`corp_code`, `corp_name`
+)
+SELECT
+	'1999000000000000111', '0', '0,', 40, '0000000040,',
+	'1', 0, '合格', '合格', '1',
+	'', 'mes_batch_sn_status', '1', '发货检后', '', 'tag green',
+	'0', 'system', NOW(), 'system', NOW(), '',
+	'0', 'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_data` WHERE `dict_type` = 'mes_batch_sn_status' AND `dict_value` = '1'
+);
+
+-- 字典项:2 已发货
+INSERT INTO `js_sys_dict_data` (
+	`dict_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`,
+	`tree_leaf`, `tree_level`, `tree_names`, `dict_label`, `dict_value`,
+	`dict_icon`, `dict_type`, `is_sys`, `description`, `css_style`, `css_class`,
+	`status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`,
+	`corp_code`, `corp_name`
+)
+SELECT
+	'1999000000000000112', '0', '0,', 50, '0000000050,',
+	'1', 0, '已发货', '已发货', '2',
+	'', 'mes_batch_sn_status', '1', '已发货', '', 'tag orange',
+	'0', 'system', NOW(), 'system', NOW(), '',
+	'0', 'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_data` WHERE `dict_type` = 'mes_batch_sn_status' AND `dict_value` = '2'
+);

+ 29 - 0
db/mysql/add_mes_batch_sn_erp_instock.sql

@@ -0,0 +1,29 @@
+-- MES 批次码与 ERP 生产入库关联字段
+ALTER TABLE `mes_batch_sn`
+  ADD COLUMN `instock_status` char(1) DEFAULT '0' COMMENT 'ERP入库状态:0待入库 1已入库' AFTER `line_sn`,
+  ADD COLUMN `mo_bill_no` varchar(100) DEFAULT NULL COMMENT 'ERP生产订单号' AFTER `instock_status`,
+  ADD COLUMN `mo_entry_id` varchar(50) DEFAULT NULL COMMENT 'ERP生产订单分录内码' AFTER `mo_bill_no`,
+  ADD COLUMN `erp_instock_bill_no` varchar(100) DEFAULT NULL COMMENT 'ERP生产入库单号' AFTER `mo_entry_id`,
+  ADD COLUMN `erp_lot_number` varchar(100) DEFAULT NULL COMMENT 'ERP批号(FLot)' AFTER `erp_instock_bill_no`,
+  ADD COLUMN `erp_instock_date` datetime DEFAULT NULL COMMENT 'ERP入库推送时间' AFTER `erp_lot_number`;
+
+CREATE TABLE IF NOT EXISTS `mes_erp_prd_instock_log` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `mo_bill_no` varchar(100) DEFAULT NULL COMMENT '生产订单号',
+  `mo_entry_id` varchar(50) DEFAULT NULL COMMENT '生产订单分录内码',
+  `material_number` varchar(100) DEFAULT NULL COMMENT '物料编码',
+  `erp_bill_no` varchar(100) DEFAULT NULL COMMENT 'ERP入库单号',
+  `erp_lot_number` varchar(100) DEFAULT NULL COMMENT 'ERP批号',
+  `total_qty` decimal(18,4) DEFAULT NULL COMMENT '入库合计数量',
+  `batch_count` int DEFAULT NULL COMMENT '绑定批次码个数',
+  `stock_number` varchar(50) DEFAULT NULL COMMENT '入库仓库编码',
+  `stock_loc_number` varchar(50) DEFAULT NULL COMMENT '入库仓位编码',
+  `batch_sn_list` varchar(2000) DEFAULT NULL COMMENT 'MES批次码列表(逗号分隔)',
+  `status` char(1) DEFAULT '0' COMMENT '状态:0成功',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_date` datetime DEFAULT NULL,
+  `remarks` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES推ERP生产入库日志';

+ 3 - 0
db/mysql/add_mes_batch_sn_erp_transfer.sql

@@ -0,0 +1,3 @@
+-- 批次码绑定 ERP 直接调拨单号(提交调拨成功后回写)
+ALTER TABLE `mes_batch_sn`
+  ADD COLUMN `erp_transfer_bill_no` varchar(100) DEFAULT NULL COMMENT 'ERP直接调拨单号' AFTER `erp_instock_date`;

+ 46 - 0
db/mysql/add_mes_batch_sn_status_packing.sql

@@ -0,0 +1,46 @@
+-- 批次码状态增加「待装中」:新建=待装中,生产入库后=仓库中
+-- 3待装中 -> 0仓库中 -> 1合格 -> 2已发货
+
+ALTER TABLE `mes_batch_sn`
+  MODIFY COLUMN `batch_status` char(1) DEFAULT '3' COMMENT '状态:3待装中 0仓库中 1合格 2已发货';
+
+-- 未生产入库、仍为仓库中(0) 的历史数据改为待装中
+UPDATE `mes_batch_sn`
+SET `batch_status` = '3'
+WHERE (`batch_status` = '0' OR `batch_status` IS NULL OR `batch_status` = '')
+  AND (`instock_status` IS NULL OR `instock_status` = '' OR `instock_status` = '0');
+
+-- 已生产入库但仍为空的,补为仓库中
+UPDATE `mes_batch_sn`
+SET `batch_status` = '0'
+WHERE (`batch_status` IS NULL OR `batch_status` = '')
+  AND `instock_status` = '1';
+
+UPDATE `js_sys_dict_type`
+SET `remarks` = '3待装中 0仓库中 1合格 2已发货'
+WHERE `dict_type` = 'mes_batch_sn_status';
+
+-- 字典项:3 待装中(排在最前)
+INSERT INTO `js_sys_dict_data` (
+	`dict_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`,
+	`tree_leaf`, `tree_level`, `tree_names`, `dict_label`, `dict_value`,
+	`dict_icon`, `dict_type`, `is_sys`, `description`, `css_style`, `css_class`,
+	`status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`,
+	`corp_code`, `corp_name`
+)
+SELECT
+	'1999000000000000113', '0', '0,', 20, '0000000020,',
+	'1', 0, '待装中', '待装中', '3',
+	'', 'mes_batch_sn_status', '1', '新建批次码,尚未生产入库', '', 'tag gray',
+	'0', 'system', NOW(), 'system', NOW(), '',
+	'0', 'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_data` WHERE `dict_type` = 'mes_batch_sn_status' AND `dict_value` = '3'
+);
+
+-- 0 仓库中:改为生产入库后,不再表示新建
+UPDATE `js_sys_dict_data`
+SET `description` = '生产入库后',
+	`remarks` = '生产入库后'
+WHERE `dict_type` = 'mes_batch_sn_status' AND `dict_value` = '0';

+ 18 - 0
db/mysql/add_mes_device_prevent_task_multi_user.sql

@@ -0,0 +1,18 @@
+-- 预防性维修计划:支持多执行人 + 记录实际完成人
+-- 1)执行人关系表
+CREATE TABLE IF NOT EXISTS `mes_device_prevent_task_user` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `prevent_task_id` varchar(64) NOT NULL COMMENT '预防性维修计划ID',
+  `user_code` varchar(64) NOT NULL COMMENT '执行人员编码',
+  PRIMARY KEY (`id`),
+  KEY `idx_prevent_task_id` (`prevent_task_id`),
+  KEY `idx_user_code` (`user_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预防性维修计划执行人关系表';
+
+-- 2)主表:执行人字段加长(逗号分隔冗余),增加实际完成人
+ALTER TABLE `mes_device_prevent_task`
+  MODIFY COLUMN `user_code` varchar(2000) DEFAULT NULL COMMENT '执行人员(多人逗号分隔冗余)';
+
+-- 若列已存在可忽略报错后跳过
+ALTER TABLE `mes_device_prevent_task`
+  ADD COLUMN `finish_user_code` varchar(64) DEFAULT NULL COMMENT '实际完成人' AFTER `user_code`;

+ 56 - 0
db/mysql/add_mes_device_state_stop.sql

@@ -0,0 +1,56 @@
+-- 设备运行状态字典增加:7 = 停机(工位退出/收班)
+INSERT INTO js_sys_dict_data (
+	dict_code,
+	parent_code,
+	parent_codes,
+	tree_sort,
+	tree_sorts,
+	tree_leaf,
+	tree_level,
+	tree_names,
+	dict_label,
+	dict_value,
+	dict_icon,
+	dict_type,
+	is_sys,
+	description,
+	css_style,
+	css_class,
+	status,
+	create_by,
+	create_date,
+	update_by,
+	update_date,
+	remarks,
+	corp_code,
+	corp_name
+)
+SELECT
+	'mes_device_state_7',
+	'0',
+	'0,',
+	210,
+	'0000000210,',
+	'1',
+	0,
+	'停机',
+	'停机',
+	'7',
+	'',
+	'mes_device_state',
+	'1',
+	'工位退出/收班',
+	'',
+	'',
+	'0',
+	'system',
+	NOW(),
+	'system',
+	NOW(),
+	'',
+	'0',
+	'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM js_sys_dict_data WHERE dict_type = 'mes_device_state' AND dict_value = '7'
+);

+ 191 - 0
db/mysql/add_mes_ding_msg.sql

@@ -0,0 +1,191 @@
+-- 钉钉消息推送配置(类型预置,页面只允许启停、按产线配人、测试发送)
+-- 挂载位置:系统管理 / 系统设置 / 钉钉消息推送
+
+CREATE TABLE IF NOT EXISTS `mes_ding_msg_config` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `msg_code` varchar(64) NOT NULL COMMENT '消息编码(代码写死)',
+  `msg_name` varchar(100) NOT NULL COMMENT '消息名称',
+  `module` varchar(20) NOT NULL COMMENT '模块:DEVICE/PROD/QUALITY',
+  `trigger_desc` varchar(500) DEFAULT '' COMMENT '触发说明(只读展示)',
+  `msg_content` text COMMENT '发送内容(页面可编辑)',
+  `is_enabled` char(1) NOT NULL DEFAULT '0' COMMENT '是否启用:1是 0否',
+  `sort_order` int DEFAULT 0 COMMENT '排序',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE KEY `uk_mes_ding_msg_code` (`msg_code`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='钉钉消息推送配置';
+
+CREATE TABLE IF NOT EXISTS `mes_ding_msg_line` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `config_id` varchar(64) NOT NULL COMMENT '消息配置ID',
+  `line_sn` varchar(64) NOT NULL COMMENT '产线编号',
+  `line_title` varchar(100) DEFAULT '' COMMENT '产线名称',
+  `user_codes` varchar(2000) DEFAULT '' COMMENT '接收人用户编码,逗号分隔',
+  `user_names` varchar(2000) DEFAULT '' COMMENT '接收人姓名,逗号分隔',
+  `mobiles` varchar(2000) DEFAULT '' COMMENT '接收人手机号,逗号分隔',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_mes_ding_msg_line_cfg` (`config_id`, `line_sn`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='钉钉消息产线接收人';
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesDingMsgConfig',
+  '1582259828007895040',
+  '0,1582259822626603008,1582259828007895040,',
+  180,
+  '0000009000,0000000500,0000000180,',
+  '1',
+  2,
+  '系统管理/系统设置/钉钉消息推送',
+  '钉钉消息推送',
+  '1',
+  '/mes/mesDingMsgConfig/list',
+  '',
+  'icon-speech',
+  '',
+  '',
+  'mes:mesDingMsgConfig:view',
+  40,
+  '1',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesDingMsgConfig'
+);
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesDingMsgConfigEdit',
+  'mesDingMsgConfig',
+  '0,1582259822626603008,1582259828007895040,mesDingMsgConfig,',
+  30,
+  '0000009000,0000000500,0000000180,0000000030,',
+  '1',
+  3,
+  '系统管理/系统设置/钉钉消息推送/编辑',
+  '编辑',
+  '2',
+  '',
+  '',
+  '',
+  '',
+  '',
+  'mes:mesDingMsgConfig:edit',
+  40,
+  '0',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesDingMsgConfigEdit'
+);
+
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'DEVICE_PREVENT_TASK','DEVICE_PREVENT_TASK','预防性任务提示','DEVICE','每天中午12点进行定时任务:查询预防性维护计划,在开始前3天或者已经开始(未结束),但是没有完成的任务,进行提醒','0',10,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='DEVICE_PREVENT_TASK');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'DEVICE_MAINTAIN_15','DEVICE_MAINTAIN_15','设备保养三级维护(提前15天)','DEVICE','每天中午12点进行定时任务:查询预防性维护计划,未完成且距离开始时间正好提前15天的任务进行提醒','0',11,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='DEVICE_MAINTAIN_15');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'DEVICE_MAINTAIN_7','DEVICE_MAINTAIN_7','设备保养三级维护(提前7天)','DEVICE','每天中午12点进行定时任务:查询预防性维护计划,未完成且距离开始时间正好提前7天的任务进行提醒','0',12,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='DEVICE_MAINTAIN_7');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'DEVICE_MAINTAIN_3','DEVICE_MAINTAIN_3','设备保养三级维护(提前3天)','DEVICE','每天中午12点进行定时任务:查询预防性维护计划,未完成且距离开始时间正好提前3天的任务进行提醒','0',13,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='DEVICE_MAINTAIN_3');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'PROD_PLAN_RATE','PROD_PLAN_RATE','生产计划完成率低于97%','PROD','每天中午12点进行定时任务:排产计划中的最后一天中午12:00,如果完成率低于97%,进行提醒','0',20,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='PROD_PLAN_RATE');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'PROD_MATERIAL_3DAY','PROD_MATERIAL_3DAY','低于3天计划量的物料','PROD','每天中午12点进行定时任务:查询当前日期的计划数据,计算平均每天需要做工数量,结合产线每个工位需要的物料进行计算,如果库存中物料少于三天的需要物料进行提醒','0',30,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='PROD_MATERIAL_3DAY');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'PROD_OEE_85','PROD_OEE_85','设备OEE低于85%','PROD','每1小时进行定时任务:看设备综合效率管理大屏中的平均开动率,如果低于85%,则发送数据','0',40,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='PROD_OEE_85');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'PROD_STOCK_500','PROD_STOCK_500','产品成品库存低于500件','PROD','每天中午12点进行定时任务:查询批次码记录表中仓库中的批次码,计算数量,如果少于500,进行提醒','0',50,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='PROD_STOCK_500');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_FIRST_NG','QM_FIRST_NG','首件NG时进行消息推送','QUALITY','触发任务:使用pda抽检模块提交首件检查为NG时触发','0',60,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_FIRST_NG');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_SPOT_NG','QM_SPOT_NG','抽检NG时进行消息推送','QUALITY','触发任务:使用pda抽检模块提交抽检为NG时触发','0',70,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_SPOT_NG');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_LAST_NG','QM_LAST_NG','末件NG时进行消息推送','QUALITY','触发任务:使用pda抽检模块提交末件检查为NG时触发','0',80,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_LAST_NG');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_REPAIR_ONLINE','QM_REPAIR_ONLINE','返修机完成重新上线','QUALITY','触发任务:返修检查OK时','0',90,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_REPAIR_ONLINE');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_SCRAP','QM_SCRAP','报废','QUALITY','触发任务:当调用报废接口提交时触发','0',100,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_SCRAP');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_NG_3','QM_NG_3','当班同产品不良数超过3','QUALITY','触发任务:加工提交结果为NG时,查询当班NG数量,当NG数量为4时触发','0',110,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_NG_3');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_NG_10','QM_NG_10','当班同产品不良数超过10','QUALITY','触发任务:加工提交结果为NG时,查询当班NG数量,当NG数量为11时触发','0',120,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_NG_10');
+INSERT INTO `mes_ding_msg_config` (`id`,`msg_code`,`msg_name`,`module`,`trigger_desc`,`is_enabled`,`sort_order`,`status`,`create_by`,`create_date`,`update_by`,`update_date`)
+SELECT 'QM_NG_50','QM_NG_50','当班同产品不良数超过50','QUALITY','触发任务:加工提交结果为NG时,查询当班NG数量,当NG数量为51时触发','0',130,'0','system',NOW(),'system',NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `mes_ding_msg_config` WHERE `msg_code`='QM_NG_50');
+
+INSERT INTO `js_sys_role_menu` (`role_code`, `menu_code`)
+SELECT 'corpAdmin', 'mesDingMsgConfig' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM `js_sys_role_menu` WHERE `role_code`='corpAdmin' AND `menu_code`='mesDingMsgConfig');
+
+INSERT INTO `js_sys_role_menu` (`role_code`, `menu_code`)
+SELECT 'corpAdmin', 'mesDingMsgConfigEdit' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM `js_sys_role_menu` WHERE `role_code`='corpAdmin' AND `menu_code`='mesDingMsgConfigEdit');
+
+-- 已有库补加发送内容字段
+SET @col_exists := (
+  SELECT COUNT(1) FROM information_schema.COLUMNS
+  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'mes_ding_msg_config' AND COLUMN_NAME = 'msg_content'
+);
+SET @sql := IF(@col_exists = 0,
+  'ALTER TABLE mes_ding_msg_config ADD COLUMN msg_content text COMMENT ''发送内容'' AFTER trigger_desc',
+  'SELECT 1');
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+ALTER TABLE mes_ding_msg_config MODIFY COLUMN trigger_desc varchar(500) DEFAULT '' COMMENT '触发说明';
+
+DELETE FROM mes_ding_msg_line WHERE config_id IN (
+  SELECT id FROM (
+    SELECT id FROM mes_ding_msg_config WHERE msg_code NOT IN (
+      'DEVICE_PREVENT_TASK','DEVICE_MAINTAIN_15','DEVICE_MAINTAIN_7','DEVICE_MAINTAIN_3',
+      'PROD_PLAN_RATE','PROD_MATERIAL_3DAY','PROD_OEE_85','PROD_STOCK_500',
+      'QM_FIRST_NG','QM_SPOT_NG','QM_LAST_NG','QM_REPAIR_ONLINE','QM_SCRAP','QM_NG_3','QM_NG_10','QM_NG_50'
+    )
+  ) t
+);
+DELETE FROM mes_ding_msg_config WHERE msg_code NOT IN (
+  'DEVICE_PREVENT_TASK','DEVICE_MAINTAIN_15','DEVICE_MAINTAIN_7','DEVICE_MAINTAIN_3',
+  'PROD_PLAN_RATE','PROD_MATERIAL_3DAY','PROD_OEE_85','PROD_STOCK_500',
+  'QM_FIRST_NG','QM_SPOT_NG','QM_LAST_NG','QM_REPAIR_ONLINE','QM_SCRAP','QM_NG_3','QM_NG_10','QM_NG_50'
+);
+
+

+ 66 - 0
db/mysql/add_mes_erp_in_stock_inspect.sql

@@ -0,0 +1,66 @@
+-- ERP 来料检记录(固定检查项:检验员、时间、OK/NG、数量、批号、备注)
+-- 父菜单:生产管理 (menu_code: 1672118505143263232)
+
+CREATE TABLE IF NOT EXISTS `mes_erp_in_stock_inspect` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `po_bill_no` varchar(64) NOT NULL COMMENT '采购订单号',
+  `check_result` varchar(8) NOT NULL COMMENT '检验结果 OK/NG',
+  `inspector_code` varchar(64) DEFAULT NULL COMMENT '检验员账号',
+  `inspector_name` varchar(100) DEFAULT NULL COMMENT '检验员',
+  `inspect_time` datetime NOT NULL COMMENT '检验时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  `stock_number` varchar(64) DEFAULT NULL COMMENT '入库仓库编码',
+  `stock_loc_number` varchar(64) DEFAULT NULL COMMENT '入库仓位编码',
+  `in_stock_bill_no` varchar(64) DEFAULT NULL COMMENT '生成的采购入库单号',
+  `in_stock_status` varchar(16) DEFAULT NULL COMMENT '入库状态 NONE/SUCCESS/FAIL',
+  `in_stock_message` varchar(500) DEFAULT NULL COMMENT '入库结果说明',
+  `line_count` int DEFAULT NULL COMMENT '明细行数',
+  `total_qty` decimal(18,6) DEFAULT NULL COMMENT '合计检验数量',
+  `channel` varchar(16) DEFAULT NULL COMMENT 'PC/PDA',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_erp_inspect_po` (`po_bill_no`),
+  KEY `idx_mes_erp_inspect_time` (`inspect_time`),
+  KEY `idx_mes_erp_inspect_result` (`check_result`),
+  KEY `idx_mes_erp_inspect_instock` (`in_stock_bill_no`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP来料检记录';
+
+CREATE TABLE IF NOT EXISTS `mes_erp_in_stock_inspect_item` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `inspect_id` varchar(64) NOT NULL COMMENT '来料检记录id',
+  `entry_id` varchar(64) DEFAULT NULL COMMENT '采购订单分录内码',
+  `seq` varchar(32) DEFAULT NULL COMMENT '行号',
+  `material_number` varchar(100) DEFAULT NULL COMMENT '物料编码',
+  `material_name` varchar(200) DEFAULT NULL COMMENT '物料名称',
+  `unit_name` varchar(64) DEFAULT NULL COMMENT '单位',
+  `inspect_qty` decimal(18,6) DEFAULT NULL COMMENT '检验数量',
+  `lot_number` varchar(100) DEFAULT NULL COMMENT '批号',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_erp_inspect_item_hid` (`inspect_id`),
+  KEY `idx_mes_erp_inspect_item_mat` (`material_number`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP来料检记录明细';
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` = 'mesErpInStockInspect';
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesErpInStockInspect', '1672118505143263232', '0,1672118505143263232,',
+  147, '0000009290,0000000147,', '1', 1,
+  '生产管理/ERP来料检记录', 'ERP来料检记录', '1', '/mes/mesErpInStockInspect/list', '',
+  'fa-clipboard', '', '', 'mes:mesErpInStockInspect:view', 84, '1', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), ''
+);

+ 48 - 0
db/mysql/add_mes_erp_interaction_log.sql

@@ -0,0 +1,48 @@
+-- ERP 下推交互日志(只读查询,自动写入)
+
+CREATE TABLE IF NOT EXISTS `mes_erp_interaction_log` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `biz_type` varchar(32) NOT NULL COMMENT '业务类型',
+  `source_bill_no` varchar(64) DEFAULT NULL COMMENT '源单号',
+  `source_entry_id` varchar(64) DEFAULT NULL COMMENT '源分录内码',
+  `erp_bill_no` varchar(64) DEFAULT NULL COMMENT 'ERP生成单号',
+  `push_result` char(1) NOT NULL COMMENT '1成功 0失败',
+  `result_message` varchar(500) DEFAULT NULL COMMENT '结果说明',
+  `line_count` int DEFAULT NULL COMMENT '明细行数',
+  `total_qty` decimal(18,6) DEFAULT NULL COMMENT '合计数量',
+  `qty_summary` varchar(100) DEFAULT NULL COMMENT '数量摘要展示',
+  `channel` varchar(16) DEFAULT NULL COMMENT 'PC/PDA',
+  `operator_code` varchar(64) DEFAULT NULL COMMENT '操作人账号',
+  `operator_name` varchar(100) DEFAULT NULL COMMENT '操作人姓名',
+  `request_summary` varchar(500) DEFAULT NULL COMMENT '请求摘要JSON(单号/行数/合计)',
+  `response_summary` varchar(1000) DEFAULT NULL COMMENT '失败时ERP错误摘要',
+  `response_duration_ms` int DEFAULT NULL COMMENT 'ERP接口响应时长(毫秒)',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_erp_ixlog_create` (`create_date`),
+  KEY `idx_mes_erp_ixlog_source` (`source_bill_no`),
+  KEY `idx_mes_erp_ixlog_erp` (`erp_bill_no`),
+  KEY `idx_mes_erp_ixlog_biz` (`biz_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP下推交互日志';
+
+-- 菜单:生产管理 (parent menu_code: 1672118505143263232)
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` = 'mesErpInteractionLog';
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesErpInteractionLog', '1672118505143263232', '0,1672118505143263232,',
+  154, '0000009290,0000000154,', '1', 1,
+  '生产管理/ERP下推日志', 'ERP下推日志', '1', '/mes/mesErpInteractionLog/list', '',
+  'fa-history', '', '', 'mes:mesErpInteractionLog:view', 85, '1', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), ''
+);

+ 4 - 0
db/mysql/add_mes_erp_interaction_log_response_duration.sql

@@ -0,0 +1,4 @@
+-- ERP 下推日志:增加 ERP 接口响应时长(毫秒)
+
+ALTER TABLE `mes_erp_interaction_log`
+  ADD COLUMN `response_duration_ms` int DEFAULT NULL COMMENT 'ERP接口响应时长(毫秒)' AFTER `response_summary`;

+ 64 - 0
db/mysql/add_mes_erp_prd_instock_bind_menu.sql

@@ -0,0 +1,64 @@
+-- 生产入库批次绑定明细菜单
+
+-- 父菜单:生产管理 (menu_code: 1672118505143263232)
+
+
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` = 'mesErpPrdInstockLog';
+
+
+
+INSERT INTO `js_sys_menu` (
+
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+
+) VALUES (
+
+  'mesErpPrdInstockLog', '1672118505143263232', '0,1672118505143263232,',
+
+  153, '0000009290,0000000153,', '1', 1,
+
+  '生产管理/生产入库批次绑定', '生产入库批次绑定', '1', '/mes/mesErpPrdInstockLog/list', '',
+
+  'fa-link', '', '', 'mes:mesErpPrdInstockLog:view', 84, '1', 'default', 'core', NULL, NULL,
+
+  '0', 'system', NOW(), 'system', NOW(), '独立菜单权限;从 ERP 入库单列表点「绑定」时 mes:erpPrdInStock:view 亦可访问'
+
+);
+
+
+
+-- 可选:作为 ERP 生产入库子权限(便于角色授权,与 erpPrdInStock 菜单同组)
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` = 'erpPrdInStockBind';
+
+INSERT INTO `js_sys_menu` (
+
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+
+) VALUES (
+
+  'erpPrdInStockBind', 'erpPrdInStock', '0,1672118505143263232,erpPrdInStock,',
+
+  30, '0000009290,0000000151,0000000030,', '1', 2,
+
+  '生产管理/ERP生产入库单查询/批次绑定明细', '批次绑定明细', '2', '', '',
+
+  '', '', '', 'mes:mesErpPrdInstockLog:view', 82, '0', 'default', 'core', NULL, NULL,
+
+  '0', 'system', NOW(), 'system', NOW(), ''
+
+);
+

+ 16 - 0
db/mysql/add_mes_erp_schedule_board_menu.sql

@@ -0,0 +1,16 @@
+-- ERP 排产看板菜单(甘特视图)
+-- 父菜单:生产管理 (menu_code: 1672118505143263232)
+-- 权限与列表页相同:mes:mesErpSchedule:view
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesErpScheduleBoard', '1672118505143263232', '0,1672118505143263232,',
+  155, '0000009290,0000000155,', '1', 1,
+  '生产管理/ERP排产看板', 'ERP排产看板', '1', '/mes/mesErpSchedule/board', '',
+  'fa-calendar', '', '', 'mes:mesErpSchedule:view', 80, '1', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), ''
+);

+ 35 - 0
db/mysql/add_mes_icons_category.sql

@@ -0,0 +1,35 @@
+-- 功能模块分类表及后台菜单
+-- 挂载位置:系统管理 / 系统设置 / 功能模块分类(在「功能模块」之前)
+
+CREATE TABLE IF NOT EXISTS `mes_icons_category` (
+  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
+  `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '分类名称',
+  `sort_order` int NULL DEFAULT 0 COMMENT '排序(升序)',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注',
+  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
+  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime NULL DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime NULL DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '功能模块分类表' ROW_FORMAT = DYNAMIC;
+
+-- 默认分类「其他」:未指定分类的功能模块归属此处(固定 ID,勿删)
+INSERT INTO `mes_icons_category` (
+  `id`, `title`, `sort_order`, `remark`, `status`, `create_by`, `create_date`, `update_by`, `update_date`
+) VALUES (
+  '1000000000000000001', '其他', 9999, '未指定分类的功能模块默认归属', '0', 'system', NOW(), 'system', NOW()
+);
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesIconsCategory', '1582259828007895040', '0,1582259822626603008,1582259828007895040,',
+  115, '0000009000,0000000500,0000000115,', '1', 2,
+  '系统管理/系统设置/功能模块分类', '功能模块分类', '1', '/mes/mesIconsCategory/list', '',
+  'icon-layers', '', '', 'mes:mesIconsCategory:view,mes:mesIconsCategory:edit', 40, '1', 'default', 'app', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), ''
+);

+ 12 - 0
db/mysql/add_mes_icons_category_default.sql

@@ -0,0 +1,12 @@
+-- 补录默认分类「其他」,并将未分类模块归入该分类
+-- 适用:已建表但未插入默认数据的库
+
+INSERT IGNORE INTO `mes_icons_category` (
+  `id`, `title`, `sort_order`, `remark`, `status`, `create_by`, `create_date`, `update_by`, `update_date`
+) VALUES (
+  '1000000000000000001', '其他', 9999, '未指定分类的功能模块默认归属', '0', 'system', NOW(), 'system', NOW()
+);
+
+UPDATE `mes_icons`
+SET `category_id` = '1000000000000000001'
+WHERE `category_id` IS NULL OR `category_id` = '';

+ 17 - 0
db/mysql/add_mes_icons_category_id.sql

@@ -0,0 +1,17 @@
+-- 功能模块关联分类
+
+ALTER TABLE `mes_icons`
+  ADD COLUMN `category_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所属分类ID' AFTER `id`,
+  ADD INDEX `idx_category_id` (`category_id`) USING BTREE;
+
+-- 默认分类「其他」(若已执行过 add_mes_icons_category.sql 可跳过插入)
+INSERT IGNORE INTO `mes_icons_category` (
+  `id`, `title`, `sort_order`, `remark`, `status`, `create_by`, `create_date`, `update_by`, `update_date`
+) VALUES (
+  '1000000000000000001', '其他', 9999, '未指定分类的功能模块默认归属', '0', 'system', NOW(), 'system', NOW()
+);
+
+-- 历史未分类模块归入「其他」
+UPDATE `mes_icons`
+SET `category_id` = '1000000000000000001'
+WHERE `category_id` IS NULL OR `category_id` = '';

+ 10 - 0
db/mysql/add_mes_icons_grouped_config.sql

@@ -0,0 +1,10 @@
+-- PDA 功能模块接口返回格式开关(系统设置 → 参数设置)
+-- mes.icons.grouped:0=扁平列表(兼容旧版 PDA) 1=按分类分组(新版 PDA)
+
+INSERT INTO `js_sys_config` (
+  `id`, `config_name`, `config_key`, `config_value`, `is_sys`,
+  `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  '2069501212292587521', 'PDA功能模块按分类分组', 'mes.icons.grouped', '1', '0',
+  'system', NOW(), 'system', NOW(), '0=扁平列表(兼容旧版PDA) 1=按分类分组(新版PDA两级菜单)'
+);

+ 49 - 0
db/mysql/add_mes_integrated_screen.sql

@@ -0,0 +1,49 @@
+-- 智能中控大屏配置及错误 URL 修正
+-- 1. 修正 /js/a/js/a/ 重复前缀
+-- 2. 修正缺少 context-path 的 /a/... 路径
+
+UPDATE mes_screen_config
+SET screen_url = REPLACE(screen_url, '/js/a/js/a/', '/js/a/')
+WHERE screen_url LIKE '%/js/a/js/a/%';
+
+UPDATE mes_screen_config
+SET screen_url = CONCAT('/js/a/', SUBSTRING(screen_url, 6))
+WHERE screen_url LIKE 'js/a/%';
+
+UPDATE mes_screen_config
+SET screen_url = CONCAT('/js', screen_url)
+WHERE screen_url LIKE '/a/%'
+  AND screen_url NOT LIKE '/js/%';
+
+INSERT INTO mes_screen_config (
+	id,
+	screen_name,
+	screen_code,
+	screen_url,
+	screen_type,
+	play_duration,
+	sort_order,
+	is_enabled,
+	remarks,
+	create_by,
+	create_date,
+	update_by,
+	update_date
+)
+SELECT
+	'mes_integrated_screen',
+	'MES智能中控大屏',
+	'MES_INTEGRATED_SCREEN',
+	'/js/a/mes/mesProduct/integratedScreen',
+	'生产管理',
+	60,
+	10,
+	'1',
+	'智能中控大屏,含生产订单、品质指标、设备状态等模块',
+	'system',
+	NOW(),
+	'system',
+	NOW()
+WHERE NOT EXISTS (
+	SELECT 1 FROM mes_screen_config WHERE screen_code = 'MES_INTEGRATED_SCREEN'
+);

+ 52 - 0
db/mysql/add_mes_line_process_consume.sql

@@ -0,0 +1,52 @@
+-- 工位加工消耗物料绑定(新表,替代旧 mes_line_process_material 的管理入口)
+
+CREATE TABLE IF NOT EXISTS `mes_line_process_consume` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `line_process_id` varchar(64) NOT NULL COMMENT '工位ID(mes_line_process.id)',
+  `material_id` varchar(64) NOT NULL COMMENT '物料ID(mes_material.id)',
+  `nums` int NOT NULL DEFAULT 1 COMMENT '每次加工消耗数量',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_mes_lpc_process_material` (`line_process_id`, `material_id`),
+  KEY `idx_mes_lpc_material` (`material_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工位加工消耗物料绑定(无需工艺号)';
+
+-- 若此前已建含 craft 的表,可执行下面两行清理(表不存在 craft 时请忽略报错)
+-- ALTER TABLE `mes_line_process_consume` DROP INDEX `idx_mes_lpc_craft`;
+-- ALTER TABLE `mes_line_process_consume` DROP COLUMN `craft`;
+
+-- 权限菜单:挂到生产管理下(与 ERP 相关菜单同级;可按需要挪到生产线管理)
+-- 父菜单:生产管理 (menu_code: 1672118505143263232)
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` IN ('mesLineProcessConsume', 'mesLineProcessConsumeEdit');
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesLineProcessConsume', '1672118505143263232', '0,1672118505143263232,',
+  156, '0000009290,0000000156,', '1', 1,
+  '生产管理/工位加工物料绑定', '工位加工物料绑定', '1', '/mes/mesLineProcessConsume/list', '',
+  'fa-cubes', '', '', 'mes:mesLineProcessConsume:view', 50, '0', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), '默认隐藏;从生产工位管理「物料」按钮进入'
+);
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesLineProcessConsumeEdit', 'mesLineProcessConsume', '0,1672118505143263232,mesLineProcessConsume,',
+  30, '0000009290,0000000156,0000000030,', '1', 2,
+  '生产管理/工位加工物料绑定/编辑', '编辑', '2', '', '',
+  '', '', '', 'mes:mesLineProcessConsume:edit', 40, '0', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), ''
+);

+ 16 - 0
db/mysql/add_mes_line_process_device.sql

@@ -0,0 +1,16 @@
+-- 子工位与设备关联表
+
+CREATE TABLE IF NOT EXISTS `mes_line_process_device` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `line_process_id` varchar(64) NOT NULL COMMENT '子工位ID(mes_line_process.id)',
+  `device_id` varchar(64) NOT NULL COMMENT '设备ID(mes_device.tree_code)',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_mes_lpd_process_device` (`line_process_id`, `device_id`),
+  KEY `idx_mes_lpd_device` (`device_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='子工位与设备关联';

+ 68 - 0
db/mysql/add_mes_material_kc_total.sql

@@ -0,0 +1,68 @@
+-- 物料总库存汇总表(按 material_id 对应 mes_material)
+-- 明细仍以 mes_material_kc_log 为准,本表维护每种物料当前总数量
+
+CREATE TABLE IF NOT EXISTS `mes_material_kc_total` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `material_id` varchar(64) NOT NULL COMMENT '物料ID(mes_material.id)',
+  `total_nums` bigint NOT NULL DEFAULT 0 COMMENT '总库存数量',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_mes_material_kc_total_mid` (`material_id`),
+  KEY `idx_mes_material_kc_total_nums` (`total_nums`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物料总库存(按物料汇总)';
+
+-- 用现有明细库存初始化(重复执行前先清空)
+DELETE FROM `mes_material_kc_total`;
+
+INSERT INTO `mes_material_kc_total` (
+  `id`, `material_id`, `total_nums`, `status`,
+  `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  CONCAT('kc_total_', a.material_id) AS id,
+  a.material_id,
+  COALESCE(SUM(a.nums), 0) AS total_nums,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  '由 mes_material_kc_log 初始化'
+FROM `mes_material_kc_log` a
+WHERE a.material_id IS NOT NULL AND a.material_id <> ''
+GROUP BY a.material_id;
+
+-- 菜单:来料管理 (parent menu_code: 1667359431003865088)
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` IN ('mesMaterialKcTotal', 'mesMaterialKcTotalEdit');
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesMaterialKcTotal', '1667359431003865088', '0,1667359431003865088,',
+  210, '0000009200,0000000210,', '1', 1,
+  '来料管理/物料总库存', '物料总库存', '1', '/mes/mesMaterialKcTotal/list', '',
+  'fa-cubes', '', '', 'mes:mesMaterialKcTotal:view', 50, '1', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), '按物料汇总库存;明细见来料库存记录'
+);
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesMaterialKcTotalEdit', 'mesMaterialKcTotal', '0,1667359431003865088,mesMaterialKcTotal,',
+  30, '0000009200,0000000210,0000000030,', '1', 2,
+  '来料管理/物料总库存/编辑', '编辑', '2', '', '',
+  '', '', '', 'mes:mesMaterialKcTotal:edit', 40, '0', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), '含按明细重算'
+);

+ 102 - 0
db/mysql/add_mes_pda_scan_record_menu.sql

@@ -0,0 +1,102 @@
+-- PDA扫码记录:字典 + PC 菜单(生产管理)
+-- 状态:0已完成(下工位扫码后回写) 1进行中(本工位刚扫码)
+
+-- 字典类型
+INSERT INTO `js_sys_dict_type` (
+	`id`, `dict_name`, `dict_type`, `is_sys`, `status`,
+	`create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+	'1999000000000000130',
+	'PDA扫码记录-状态',
+	'mes_pda_scan_status',
+	'1',
+	'0',
+	'system',
+	NOW(),
+	'system',
+	NOW(),
+	'0已完成 1进行中'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_type` WHERE `dict_type` = 'mes_pda_scan_status'
+);
+
+-- 字典项:0 已完成
+INSERT INTO `js_sys_dict_data` (
+	`dict_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`,
+	`tree_leaf`, `tree_level`, `tree_names`, `dict_label`, `dict_value`,
+	`dict_icon`, `dict_type`, `is_sys`, `description`, `css_style`, `css_class`,
+	`status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`,
+	`corp_code`, `corp_name`
+)
+SELECT
+	'1999000000000000131', '0', '0,', 30, '0000000030,',
+	'1', 0, '已完成', '已完成', '0',
+	'', 'mes_pda_scan_status', '1', '下工位扫码后', '', 'tag green',
+	'0', 'system', NOW(), 'system', NOW(), '',
+	'0', 'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_data` WHERE `dict_type` = 'mes_pda_scan_status' AND `dict_value` = '0'
+);
+
+-- 字典项:1 进行中
+INSERT INTO `js_sys_dict_data` (
+	`dict_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`,
+	`tree_leaf`, `tree_level`, `tree_names`, `dict_label`, `dict_value`,
+	`dict_icon`, `dict_type`, `is_sys`, `description`, `css_style`, `css_class`,
+	`status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`,
+	`corp_code`, `corp_name`
+)
+SELECT
+	'1999000000000000132', '0', '0,', 40, '0000000040,',
+	'1', 0, '进行中', '进行中', '1',
+	'', 'mes_pda_scan_status', '1', '本工位已扫码', '', 'tag blue',
+	'0', 'system', NOW(), 'system', NOW(), '',
+	'0', 'JeeSite'
+FROM DUAL
+WHERE NOT EXISTS (
+	SELECT 1 FROM `js_sys_dict_data` WHERE `dict_type` = 'mes_pda_scan_status' AND `dict_value` = '1'
+);
+
+-- PC 菜单:生产管理 / PDA扫码记录
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesPdaScanRecord',
+  '1672118505143263232',
+  '0,1672118505143263232,',
+  159,
+  '0000009290,0000000159,',
+  '1',
+  1,
+  '生产管理/PDA扫码记录',
+  'PDA扫码记录',
+  '1',
+  '/mes/mesPdaScanRecord/list',
+  '',
+  'icon-notebook',
+  '',
+  '',
+  'mes:mesPdaScanRecord:view',
+  80,
+  '1',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesPdaScanRecord'
+);

+ 39 - 0
db/mysql/add_mes_process_consume_log.sql

@@ -0,0 +1,39 @@
+-- 工位加工消耗扣料日志(幂等:同一 sn+oprno+line_sn 只扣一次)
+
+CREATE TABLE IF NOT EXISTS `mes_process_consume_log` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `sn` varchar(64) NOT NULL COMMENT '工件码',
+  `oprno` varchar(32) NOT NULL COMMENT '工位号(含子工位号)',
+  `line_sn` varchar(64) NOT NULL COMMENT '产线编号',
+  `parent_process_id` varchar(64) DEFAULT NULL COMMENT '父工位ID',
+  `material_id` varchar(64) NOT NULL COMMENT '物料ID',
+  `nums` int NOT NULL DEFAULT 0 COMMENT '本次扣减数量',
+  `batch_sn` varchar(255) DEFAULT NULL COMMENT '扣减批次码',
+  `kc_log_id` varchar(64) DEFAULT NULL COMMENT '库存明细ID',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_pcl_sn_opr` (`sn`, `oprno`, `line_sn`),
+  KEY `idx_mes_pcl_material` (`material_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='加工消耗扣料日志';
+
+-- 菜单:生产管理 (parent menu_code: 1672118505143263232)
+
+DELETE FROM `js_sys_menu` WHERE `menu_code` = 'mesProcessConsumeLog';
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+) VALUES (
+  'mesProcessConsumeLog', '1672118505143263232', '0,1672118505143263232,',
+  157, '0000009290,0000000157,', '1', 1,
+  '生产管理/加工消耗扣料日志', '加工消耗扣料日志', '1', '/mes/mesProcessConsumeLog/list', '',
+  'fa-list-alt', '', '', 'mes:mesProcessConsumeLog:view', 50, '1', 'default', 'core', NULL, NULL,
+  '0', 'system', NOW(), 'system', NOW(), '加工OK自动扣料记录,只读'
+);

+ 109 - 0
db/mysql/add_mes_product_glue_duration.sql

@@ -0,0 +1,109 @@
+-- 涂胶加工时长表及菜单(挂载:生产管理 / 工艺过程参数)
+
+CREATE TABLE IF NOT EXISTS `mes_product_glue_duration` (
+  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '编号',
+  `sn` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '工件码',
+  `oprno` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '工位号',
+  `line_sn` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产线编号',
+  `glue_duration` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '涂胶时长',
+  `remarks` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
+  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime NULL DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime NULL DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_mes_product_glue_duration_sn` (`sn`) USING BTREE,
+  KEY `idx_mes_product_glue_duration_oprno` (`oprno`) USING BTREE,
+  KEY `idx_mes_product_glue_duration_line_sn` (`line_sn`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '涂胶加工时长' ROW_FORMAT = DYNAMIC;
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesProductGlueDuration',
+  m.`parent_code`,
+  m.`parent_codes`,
+  IFNULL(m.`tree_sort`, 0) + 5,
+  m.`tree_sorts`,
+  '1',
+  m.`tree_level`,
+  REPLACE(m.`tree_names`, SUBSTRING_INDEX(m.`tree_names`, '/', -1), '涂胶加工时长'),
+  '涂胶加工时长',
+  '1',
+  '/mes/mesProductGlueDuration/list',
+  '',
+  IFNULL(m.`menu_icon`, 'icon-notebook'),
+  '',
+  '',
+  'mes:mesProductGlueDuration:view,mes:mesProductGlueDuration:edit',
+  IFNULL(m.`weight`, 40),
+  '1',
+  IFNULL(m.`sys_code`, 'default'),
+  IFNULL(m.`module_codes`, 'core'),
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM `js_sys_menu` m
+WHERE m.`menu_href` IN (
+  '/mes/mesProductQm/listWhitePart',
+  '/mes/mesProductArcWelding/listSpot',
+  '/mes/mesProductProd/list',
+  '/mes/mesProductQm/list'
+)
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_menu` x WHERE x.`menu_code` = 'mesProductGlueDuration'
+  )
+ORDER BY m.`update_date` DESC
+LIMIT 1;
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesProductGlueDurationEdit',
+  'mesProductGlueDuration',
+  CONCAT(m.`parent_codes`, 'mesProductGlueDuration,'),
+  30,
+  CONCAT(m.`tree_sorts`, '0000000030,'),
+  '1',
+  m.`tree_level` + 1,
+  CONCAT(m.`tree_names`, '/编辑'),
+  '编辑',
+  '2',
+  '',
+  '',
+  '',
+  '',
+  '',
+  'mes:mesProductGlueDuration:edit',
+  30,
+  '1',
+  IFNULL(m.`sys_code`, 'default'),
+  IFNULL(m.`module_codes`, 'core'),
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM `js_sys_menu` m
+WHERE m.`menu_code` = 'mesProductGlueDuration'
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_menu` x WHERE x.`menu_code` = 'mesProductGlueDurationEdit'
+  )
+LIMIT 1;

+ 30 - 0
db/mysql/add_mes_product_gp12_check.sql

@@ -0,0 +1,30 @@
+-- GP12检查项表,按工件类型维护
+
+CREATE TABLE IF NOT EXISTS `mes_product_gp12_check` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `title` varchar(255) DEFAULT NULL COMMENT '名称',
+  `cate_sn` varchar(50) DEFAULT NULL COMMENT '工件类型编号(mes_product_cate.sn)',
+  `status` char(1) DEFAULT '0' COMMENT '状态',
+  `input` char(1) DEFAULT NULL COMMENT '输入类型 1=选择框 2=输入框',
+  `sort` int(11) unsigned DEFAULT NULL COMMENT '排序,正序',
+  `min_value` decimal(15,3) DEFAULT NULL COMMENT '最小值',
+  `max_value` decimal(15,3) DEFAULT NULL COMMENT '最大值',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_gp12_check_cate` (`cate_sn`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GP12检查项表';
+
+-- GP12检查项记录表,关联 mes_product_gp12
+
+CREATE TABLE IF NOT EXISTS `mes_product_gp12_items` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `record_id` varchar(64) DEFAULT NULL COMMENT 'GP12记录id(mes_product_gp12.id)',
+  `check_id` varchar(64) DEFAULT NULL COMMENT '检查项id(mes_product_gp12_check.id)',
+  `content` varchar(255) DEFAULT NULL COMMENT '检查结果',
+  `result` varchar(10) DEFAULT NULL COMMENT '结果 OK/NG',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_gp12_items_record` (`record_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GP12检查项记录表';

+ 53 - 0
db/mysql/add_mes_qm_param_menu.sql

@@ -0,0 +1,53 @@
+-- 气密检测记录拆分为「白件气密」「总成气密」两个菜单
+-- 原菜单 href 含 mesProductQm 时更新为白件气密;并新增总成气密菜单(同父级)
+
+UPDATE `js_sys_menu`
+SET `menu_name` = '白件气密',
+    `menu_href` = '/mes/mesProductQm/listWhitePart',
+    `tree_names` = REPLACE(`tree_names`, '气密检测记录', '白件气密'),
+    `update_date` = NOW()
+WHERE (`menu_href` LIKE '%mesProductQm/list%'
+    OR `menu_href` LIKE '%mesProductQm/listWhitePart%')
+  AND `menu_name` IN ('气密检测记录', '白件气密');
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesProductQmAssembly',
+  m.`parent_code`,
+  m.`parent_codes`,
+  IFNULL(m.`tree_sort`, 0) + 1,
+  m.`tree_sorts`,
+  '1',
+  m.`tree_level`,
+  REPLACE(m.`tree_names`, '白件气密', '总成气密'),
+  '总成气密',
+  '1',
+  '/mes/mesProductQm/listAssembly',
+  '',
+  IFNULL(m.`menu_icon`, 'icon-notebook'),
+  '',
+  '',
+  m.`permission`,
+  IFNULL(m.`weight`, 40),
+  '1',
+  IFNULL(m.`sys_code`, 'default'),
+  IFNULL(m.`module_codes`, 'core'),
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM `js_sys_menu` m
+WHERE m.`menu_href` = '/mes/mesProductQm/listWhitePart'
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_menu` x WHERE x.`menu_code` = 'mesProductQmAssembly'
+  )
+LIMIT 1;

+ 198 - 0
db/mysql/add_mes_screen_notice_honor.sql

@@ -0,0 +1,198 @@
+-- 智能中控大屏:通知栏、荣誉墙
+-- 父菜单:生产管理 (menu_code: 1672118505143263232)
+
+CREATE TABLE IF NOT EXISTS `mes_screen_notice` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `title` varchar(100) DEFAULT '' COMMENT '标题(后台列表用)',
+  `content` varchar(1000) DEFAULT '' COMMENT '通知正文',
+  `publish_date` datetime DEFAULT NULL COMMENT '发布日期',
+  `sort_order` int DEFAULT 0 COMMENT '排序,越小越靠前',
+  `is_enabled` char(1) DEFAULT '1' COMMENT '是否上屏:1是 0否',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_mes_screen_notice_sort` (`is_enabled`, `sort_order`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='大屏通知栏';
+
+CREATE TABLE IF NOT EXISTS `mes_screen_honor` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `honor_title` varchar(50) DEFAULT '闪耀之星' COMMENT '奖项标题',
+  `emp_name` varchar(50) DEFAULT '' COMMENT '姓名',
+  `dept_name` varchar(100) DEFAULT '' COMMENT '部门',
+  `sort_order` int DEFAULT 0 COMMENT '排序,越小越靠前',
+  `is_enabled` char(1) DEFAULT '1' COMMENT '是否上屏:1是 0否',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态',
+  `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_mes_screen_honor_sort` (`is_enabled`, `sort_order`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='大屏荣誉墙';
+
+-- 生产管理 / 大屏通知
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesScreenNotice',
+  '1672118505143263232',
+  '0,1672118505143263232,',
+  160,
+  '0000009290,0000000160,',
+  '0',
+  1,
+  '生产管理/大屏通知',
+  '大屏通知',
+  '1',
+  '/mes/mesScreenNotice/list',
+  '',
+  'icon-speech',
+  '',
+  '',
+  'mes:mesScreenNotice:view',
+  80,
+  '1',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesScreenNotice'
+);
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesScreenNoticeEdit',
+  'mesScreenNotice',
+  '0,1672118505143263232,mesScreenNotice,',
+  30,
+  '0000009290,0000000160,0000000030,',
+  '1',
+  2,
+  '生产管理/大屏通知/编辑',
+  '编辑',
+  '2',
+  '',
+  '',
+  '',
+  '',
+  '',
+  'mes:mesScreenNotice:edit',
+  40,
+  '0',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesScreenNoticeEdit'
+);
+
+-- 生产管理 / 荣誉榜单
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesScreenHonor',
+  '1672118505143263232',
+  '0,1672118505143263232,',
+  161,
+  '0000009290,0000000161,',
+  '0',
+  1,
+  '生产管理/荣誉榜单',
+  '荣誉榜单',
+  '1',
+  '/mes/mesScreenHonor/list',
+  '',
+  'icon-trophy',
+  '',
+  '',
+  'mes:mesScreenHonor:view',
+  80,
+  '1',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesScreenHonor'
+);
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesScreenHonorEdit',
+  'mesScreenHonor',
+  '0,1672118505143263232,mesScreenHonor,',
+  30,
+  '0000009290,0000000161,0000000030,',
+  '1',
+  2,
+  '生产管理/荣誉榜单/编辑',
+  '编辑',
+  '2',
+  '',
+  '',
+  '',
+  '',
+  '',
+  'mes:mesScreenHonor:edit',
+  40,
+  '0',
+  'default',
+  'core',
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM DUAL
+WHERE NOT EXISTS (
+  SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = 'mesScreenHonorEdit'
+);

+ 139 - 0
db/mysql/add_mes_welder_param_record.sql

@@ -0,0 +1,139 @@
+-- 凸焊数据(焊机参数上传)表及菜单(挂载:生产管理 / 工艺过程参数)
+-- 说明:本工位无二维码/条码,表中不含 sn、line_sn
+
+CREATE TABLE IF NOT EXISTS `mes_welder_param_record` (
+  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
+  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
+  `create_date` datetime NULL DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
+  `update_date` datetime NULL DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
+  `oprno` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工序号,如 OP030A',
+  `ucode` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作员工号/登录账号',
+  `weld_count` int NOT NULL COMMENT '焊接计数(触发上传的计数值)',
+  `spec_no` int NULL DEFAULT NULL COMMENT '当前规范号',
+  `work_mode` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '工作模式:焊接/调整',
+  `run_state` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '运行状态:运行/待机',
+  `tooling_state` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修模状态:修模/正常',
+  `alarm_text` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '报警信息:无报警 / Erxx',
+  `cap_voltage` int NULL DEFAULT NULL COMMENT '电容电压 V',
+  `pre_press_time` int NULL DEFAULT NULL COMMENT '预压时间 t',
+  `press_time` int NULL DEFAULT NULL COMMENT '加压时间 t',
+  `spec_preheat_voltage` int NULL DEFAULT NULL COMMENT '规范-预热电压 V',
+  `spec_preheat_current` int NULL DEFAULT NULL COMMENT '规范-预热电流',
+  `spec_preheat_time` int NULL DEFAULT NULL COMMENT '规范-预热时间 ms',
+  `spec_weld_voltage` int NULL DEFAULT NULL COMMENT '规范-焊接电压 V',
+  `spec_weld_current` int NULL DEFAULT NULL COMMENT '规范-焊接电流',
+  `spec_weld_time` int NULL DEFAULT NULL COMMENT '规范-焊接时间 ms',
+  `spec_temper_voltage` int NULL DEFAULT NULL COMMENT '规范-回火电压 V',
+  `spec_temper_current` int NULL DEFAULT NULL COMMENT '规范-回火电流',
+  `spec_temper_time` int NULL DEFAULT NULL COMMENT '规范-回火时间 ms',
+  `preheat_voltage` int NULL DEFAULT NULL COMMENT '结果-预热电压 V',
+  `weld_voltage` int NULL DEFAULT NULL COMMENT '结果-焊接电压 V',
+  `temper_voltage` int NULL DEFAULT NULL COMMENT '结果-回火电压 V',
+  `preheat_peak_current` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结果-预热峰值电流',
+  `weld_peak_current` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结果-焊接峰值电流',
+  `temper_peak_current` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结果-回火峰值电流',
+  `record_time` datetime NOT NULL COMMENT '客户端采集时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE KEY `uk_welder_oprno_count` (`oprno`, `weld_count`) USING BTREE,
+  KEY `idx_welder_oprno_time` (`oprno`, `record_time`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '凸焊数据(焊机参数)' ROW_FORMAT = DYNAMIC;
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesWelderParamRecord',
+  m.`parent_code`,
+  m.`parent_codes`,
+  IFNULL(m.`tree_sort`, 0) + 2,
+  m.`tree_sorts`,
+  '1',
+  m.`tree_level`,
+  REPLACE(m.`tree_names`, SUBSTRING_INDEX(m.`tree_names`, '/', -1), '凸焊数据'),
+  '凸焊数据',
+  '1',
+  '/mes/mesWelderParamRecord/list',
+  '',
+  IFNULL(m.`menu_icon`, 'icon-notebook'),
+  '',
+  '',
+  'mes:mesWelderParamRecord:view,mes:mesWelderParamRecord:edit',
+  IFNULL(m.`weight`, 40),
+  '1',
+  IFNULL(m.`sys_code`, 'default'),
+  IFNULL(m.`module_codes`, 'core'),
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM `js_sys_menu` m
+WHERE m.`menu_href` IN (
+  '/mes/mesProductArcWelding/listSpot',
+  '/mes/mesProductArcWelding/listArc',
+  '/mes/mesProductGlueDuration/list',
+  '/mes/mesProductQm/listWhitePart',
+  '/mes/mesProductProd/list'
+)
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_menu` x WHERE x.`menu_code` = 'mesWelderParamRecord'
+  )
+ORDER BY
+  CASE m.`menu_href`
+    WHEN '/mes/mesProductArcWelding/listSpot' THEN 1
+    WHEN '/mes/mesProductArcWelding/listArc' THEN 2
+    ELSE 9
+  END,
+  m.`update_date` DESC
+LIMIT 1;
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesWelderParamRecordEdit',
+  'mesWelderParamRecord',
+  CONCAT(m.`parent_codes`, 'mesWelderParamRecord,'),
+  30,
+  CONCAT(m.`tree_sorts`, '0000000030,'),
+  '1',
+  m.`tree_level` + 1,
+  CONCAT(m.`tree_names`, '/编辑'),
+  '编辑',
+  '2',
+  '',
+  '',
+  '',
+  '',
+  '',
+  'mes:mesWelderParamRecord:edit',
+  30,
+  '1',
+  IFNULL(m.`sys_code`, 'default'),
+  IFNULL(m.`module_codes`, 'core'),
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM `js_sys_menu` m
+WHERE m.`menu_code` = 'mesWelderParamRecord'
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_menu` x WHERE x.`menu_code` = 'mesWelderParamRecordEdit'
+  )
+LIMIT 1;

+ 53 - 0
db/mysql/add_mes_welding_param_menu.sql

@@ -0,0 +1,53 @@
+-- 焊接参数拆分为「点焊参数」「弧焊参数」两个菜单
+-- 原菜单 href 含 mesProductArcWelding 时更新为点焊参数;并新增弧焊参数菜单(同父级)
+
+UPDATE `js_sys_menu`
+SET `menu_name` = '点焊参数',
+    `menu_href` = '/mes/mesProductArcWelding/listSpot',
+    `tree_names` = REPLACE(`tree_names`, '焊接参数', '点焊参数'),
+    `update_date` = NOW()
+WHERE (`menu_href` LIKE '%mesProductArcWelding/list%'
+    OR `menu_href` LIKE '%mesProductArcWelding/listSpot%')
+  AND `menu_name` IN ('焊接参数', '点焊参数');
+
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+  `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`,
+  `menu_title`, `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`
+)
+SELECT
+  'mesProductArcWeldingArc',
+  m.`parent_code`,
+  m.`parent_codes`,
+  IFNULL(m.`tree_sort`, 0) + 1,
+  m.`tree_sorts`,
+  '1',
+  m.`tree_level`,
+  REPLACE(m.`tree_names`, '点焊参数', '弧焊参数'),
+  '弧焊参数',
+  '1',
+  '/mes/mesProductArcWelding/listArc',
+  '',
+  IFNULL(m.`menu_icon`, 'icon-notebook'),
+  '',
+  '',
+  m.`permission`,
+  IFNULL(m.`weight`, 40),
+  '1',
+  IFNULL(m.`sys_code`, 'default'),
+  IFNULL(m.`module_codes`, 'core'),
+  NULL,
+  NULL,
+  '0',
+  'system',
+  NOW(),
+  'system',
+  NOW(),
+  ''
+FROM `js_sys_menu` m
+WHERE m.`menu_href` = '/mes/mesProductArcWelding/listSpot'
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_menu` x WHERE x.`menu_code` = 'mesProductArcWeldingArc'
+  )
+LIMIT 1;

+ 30 - 0
db/mysql/fix_employee_company_code_110.sql

@@ -0,0 +1,30 @@
+-- 修复员工归属公司编码错误:company_code='110' 在 js_sys_company 中不存在
+-- 正确公司应为 SD(安徽裕洋新能源科技有限公司)
+-- 现象:角色勾选了机构权限后,用户选择树仍缺失大量人员(被公司权限二次过滤)
+
+-- 1. 修复前核对
+SELECT company_code, COUNT(*) AS cnt
+FROM js_sys_employee
+WHERE company_code = '110'
+GROUP BY company_code;
+
+SELECT e.emp_code, e.emp_name, e.office_code, o.office_name, e.company_code
+FROM js_sys_employee e
+LEFT JOIN js_sys_office o ON o.office_code = e.office_code
+WHERE e.company_code = '110'
+ORDER BY e.office_code, e.emp_name;
+
+-- 2. 执行修复(161 人全部归属安徽裕洋各部门,可直接执行)
+-- 生产部120 / 技术质量部22 / 计划物料科10 / 综合管理科4 / 市场部2 / 财务部2 / 总经办1
+UPDATE js_sys_employee e
+JOIN js_sys_office o ON o.office_code = e.office_code
+SET e.company_code = 'SD',
+    e.company_name = '安徽裕洋新能源科技有限公司'
+WHERE e.company_code = '110'
+  AND (o.office_code = 'SD' OR o.parent_code = 'SD' OR o.parent_codes LIKE '%,SD,%');
+
+-- 3. 修复后核对:技术质量部应全部为 SD
+SELECT e.company_code, COUNT(*) AS cnt
+FROM js_sys_employee e
+WHERE e.office_code = '11005' AND e.status = '0'
+GROUP BY e.company_code;

+ 174 - 0
docs/PDAGP12检查接口说明.md

@@ -0,0 +1,174 @@
+# PDA GP12 检查接口说明
+
+> 模块:`MesProductGp12Controller`  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 返回格式:`submit` 为 JeeSite `renderResult`;`pdaCheckItems`、`pdaQueryBySn` 为 `CommonResp`(`result` / `message` / `data`)  
+> 说明:后台在「工件类型」中维护 GP12 检查项。员工用 PDA 扫工件码后,按该工件类型展示检查项并提交。
+
+---
+
+## 后台维护
+
+1. 打开 **工件类型管理**
+2. 对应类型操作列点击 **GP12检查项**
+3. 新增检查项:名称、输入类型(选择框/输入框)、排序、最小值/最大值(输入框时)
+
+检查项表:`mes_product_gp12_check`,通过 `cate_sn` 关联 `mes_product_cate.sn`。
+
+---
+
+## 1. 按工件码查询工件信息
+
+##### 请求URL
+- ` /js/a/mes/mesProductRecord/pdaQueryBySn `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|sn|是|string|工件码|
+
+##### 返回字段说明
+
+|字段路径|类型|中文说明|
+|:----|:-----|-----|
+|data.sn|string|工件码|
+|data.cate|string|原字段,工件 cate 去掉末尾 2 位|
+|data.cateSn|string|工件完整类型编号(`mes_product.cate`)|
+|data.title|string|工件名称|
+|data.oprno|string|固定 `GP12`|
+
+---
+
+## 2. 查询 GP12 检查项
+
+扫码后调用本接口,展示该工件类型下已启用的检查项。
+
+##### 请求URL
+- ` /js/a/mes/mesProductGp12/pdaCheckItems `
+
+##### 请求方式
+- POST / GET
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|sn|是|string|工件码,优先用此参数匹配检查项|
+|cateSn|否|string|工件类型编号,无工件码时可用|
+
+##### 参数示例
+```
+curl --location --request POST "http://127.0.0.1:8980/js/a/mes/mesProductGp12/pdaCheckItems?sn=工件码"
+```
+
+##### 返回示例
+
+```
+{
+    "result": "true",
+    "message": "成功",
+    "data": {
+        "sn": "工件码",
+        "cateSn": "HEVXT",
+        "oprno": "GP12",
+        "title": "工件名称",
+        "cate": "HEVXT",
+        "needImage": true,
+        "remarkRequired": false,
+        "list": [
+            {
+                "id": "检查项ID",
+                "title": "外观检查",
+                "cateSn": "HEVXT",
+                "input": "1",
+                "inputName": "选择框",
+                "minValue": null,
+                "maxValue": null,
+                "sort": 1,
+                "needImage": true
+            },
+            {
+                "id": "检查项ID2",
+                "title": "尺寸",
+                "cateSn": "HEVXT",
+                "input": "2",
+                "inputName": "输入框",
+                "minValue": "1.000",
+                "maxValue": "2.000",
+                "sort": 2
+            }
+        ]
+    }
+}
+```
+
+##### 返回字段说明
+
+|字段路径|类型|中文说明|
+|:----|:-----|-----|
+|data.list[].id|string|检查项ID,提交时回传|
+|data.list[].title|string|检查项名称|
+|data.list[].input|string|`1` 选择框,`2` 输入框|
+|data.list[].minValue / maxValue|string|输入框上下限,选择框可为空|
+|data.list[].needImage|boolean|该项必须上传照片|
+|data.needImage|boolean|每一项都必须上传照片|
+|data.remarkRequired|boolean|`false`,备注选填|
+
+---
+
+## 3. 提交 GP12 检查
+
+原接口不变:无检查项模板时,仍只提交 sn / oprno / lineSn / 照片。
+
+该工件类型已维护检查项时,必须带 `checkList`,提交全部检查项,且**每一项必须上传照片**。备注为选填。
+
+##### 请求URL
+- ` /js/a/mes/mesProductGp12/submit `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|sn|是|string|工件码|
+|oprno|是|string|工位号,一般为 GP12|
+|lineSn|是|string|产线编号|
+|remark|否|string|备注,选填|
+|ucode|否|string|PDA 账号,写入创建人|
+|mesProductGp12_image|否|string|整单照片文件ID,先调 `/js/a/file/upload`|
+|checkList|按模板|string|JSON 数组,见下|
+
+##### checkList 单项
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|id / checkId|是|string|检查项ID,来自接口 2|
+|content|是|string|选择框:`true`/`false`;输入框:数值|
+|mesProductGp12Items_image / image|是|string|该项照片文件ID,多个用逗号分隔|
+
+##### 参数示例
+```
+curl --location --request POST "http://127.0.0.1:8980/js/a/mes/mesProductGp12/submit" ^
+  --form "sn=工件码" ^
+  --form "oprno=GP12" ^
+  --form "lineSn=HEVXT" ^
+  --form "ucode=zhangsan" ^
+  --form "remark=选填备注" ^
+  --form "checkList=[{\"id\":\"检查项ID\",\"content\":\"true\",\"image\":\"文件ID1\"},{\"id\":\"检查项ID2\",\"content\":\"1.5\",\"image\":\"文件ID2\"}]"
+```
+
+##### 说明
+
+- 同一工件码已有 GP12 记录时,仍返回「该记录已存在」
+- 有检查项时,整单 `result` 由各项汇总:全部 OK 为 OK,否则 NG
+- 选择框:`true` / `1` / `OK` / `是` / `合格` 为 OK
+- 输入框:在最小~最大值范围内为 OK
+- **每一项都必须先上传照片**,缺一张即整单失败,返回「请为每一项上传照片」
+- 照片上传与点检相同:先 `POST /js/a/file/upload`,`bizType=mesProductGp12Items_image`,`uploadType=image`,再把文件 ID 写入检查项
+- 备注为选填,写入 GP12 记录的 `remark`

+ 66 - 0
docs/PDA产品修改记录接口说明.md

@@ -0,0 +1,66 @@
+# PDA 产品修改记录接口说明
+
+> 模块:`MesProductModifyRecordController`  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 返回格式:`CommonResp`(`result` / `message` / `data`)  
+> 登录会话:请求携带 `__ajax=json`、`__sid={token}`
+
+---
+
+## 查询产品修改记录列表
+
+##### 请求URL
+- ` /js/a/mes/mesProductModifyRecord/selectList `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必填|类型|说明|
+|:----|:---|:-----|-----|
+|pageNo|是|int|当前页,默认 1|
+|pageSize|是|int|页码大小,默认 20|
+
+##### 返回示例
+
+```
+{
+    "result": "true",
+    "message": "成功",
+    "data": [
+        {
+            "id": "1815591715404156928",
+            "sn": "0476962607160053",
+            "oprno": "OP190A",
+            "oprnoTitle": "气密检测",
+            "lineSn": "HEVXT",
+            "materielSn": "",
+            "craft": "100000",
+            "contentOld": "NG",
+            "content": "OK",
+            "source": "1",
+            "createDate": "2024-07-23 11:36:00",
+            "updateDate": "2024-07-23 11:36:00",
+            "createBy": "system",
+            "updateBy": "user1"
+        },
+        {
+            "id": "1815591715404156929",
+            "sn": "0476962607160054",
+            "oprno": "OP190A",
+            "oprnoTitle": "气密检测",
+            "lineSn": "HEVXT",
+            "materielSn": "",
+            "craft": "100000",
+            "contentOld": "OK",
+            "content": "NG",
+            "source": "1",
+            "createDate": "2024-07-23 12:10:00",
+            "updateDate": "2024-07-23 12:10:00",
+            "createBy": "system",
+            "updateBy": "user1"
+        }
+    ]
+}
+```

+ 147 - 0
docs/PDA功能模块接口说明.md

@@ -0,0 +1,147 @@
+# PDA 功能模块(icons)接口说明
+
+> 模块:`MesIconsController`  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 返回格式:`CommonResp`(`result` / `message` / `data`)  
+> 登录会话:请求携带 `__ajax=json`、`__sid={token}`
+
+---
+
+## 获取 PDA 功能模块列表
+
+##### 请求URL
+- ` /js/a/mes/mesIcons/icons `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|__ajax|是|string|固定 `json`|
+|__sid|是|string|登录会话 token|
+|title|否|string|按模块名称模糊查询(一般 PDA 不传)|
+|mode|否|string|按模块标识查询(一般 PDA 不传)|
+
+> **返回扁平或分组** 由后台 **系统设置 → 参数设置** 中 `mes.icons.grouped` 控制,PDA 无需传额外参数。  
+> `0` = 扁平列表(兼容旧版);`1` = 按分类分组(默认)。
+
+##### 参数示例
+
+```
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesIcons/icons?__ajax=json&__sid=b998858e224b469c84ca6d0711565f6a'
+```
+
+##### 返回示例
+
+```
+成功(分组,mes.icons.grouped = 1)
+{
+    "result": "true",
+    "message": "成功",
+    "data": [
+        {
+            "categoryId": "1677000000000000001",
+            "title": "仓储管理",
+            "sort": 10,
+            "children": [
+                {
+                    "title": "来料检",
+                    "mode": "227",
+                    "img": "http://127.0.0.1:8980/js/userfiles/fileupload/202307/xxx.png"
+                },
+                {
+                    "title": "生产入库",
+                    "mode": "240",
+                    "img": "http://127.0.0.1:8980/js/userfiles/fileupload/202307/yyy.png"
+                }
+            ]
+        },
+        {
+            "categoryId": "1000000000000000001",
+            "title": "其他",
+            "sort": 9999,
+            "children": [
+                {
+                    "title": "来料报检",
+                    "mode": "40",
+                    "img": "http://127.0.0.1:8980/js/userfiles/fileupload/202307/zzz.png"
+                }
+            ]
+        }
+    ]
+}
+```
+
+```
+成功(扁平,mes.icons.grouped = 0,兼容旧版 PDA)
+{
+    "result": "true",
+    "message": "成功",
+    "data": [
+        {
+            "title": "来料检",
+            "mode": "227",
+            "img": "http://127.0.0.1:8980/js/userfiles/fileupload/202307/xxx.png"
+        },
+        {
+            "title": "打包",
+            "mode": "231",
+            "img": "http://127.0.0.1:8980/js/userfiles/fileupload/202307/yyy.png"
+        }
+    ]
+}
+```
+
+```
+成功(无可见模块)
+{
+    "result": "true",
+    "message": "成功",
+    "data": []
+}
+```
+
+##### 返回字段说明
+
+**分组模式(`mes.icons.grouped = 1`)`data[]`:**
+
+|字段名|类型|说明|
+|:----|:-----|-----|
+|categoryId|string|分类 ID|
+|title|string|分类名称|
+|sort|int|排序值,升序展示|
+|children|array|该分类下可见模块列表|
+
+**`children[]` 模块对象:**
+
+|字段名|类型|说明|
+|:----|:-----|-----|
+|title|string|模块名称|
+|mode|string|模块标识,PDA 用于跳转对应功能页|
+|img|string|模块图标完整 URL;无图时为服务器 host|
+
+**扁平模式(`mes.icons.grouped = 0`)`data[]`:**
+
+|字段名|类型|说明|
+|:----|:-----|-----|
+|title|string|模块名称|
+|mode|string|模块标识|
+|img|string|模块图标完整 URL|
+
+##### 业务说明
+
+1. 按每条模块的 `auth` 权限过滤,用户无权限的模块不返回。
+2. 分组模式下,分类下没有任何可见模块时,该分类不返回。
+3. 模块未指定分类或分类已停用时,归入默认分类「其他」(ID:`1000000000000000001`)。
+4. 修改参数 `mes.icons.grouped` 后,建议清理缓存或重启应用后生效。
+
+---
+
+## 附录:参数设置
+
+|参数键名|参数名称|参数值|说明|
+|:----|:---|:-----|-----|
+|mes.icons.grouped|PDA功能模块按分类分组|0|扁平列表,兼容旧版 PDA|
+|mes.icons.grouped|PDA功能模块按分类分组|1|按分类分组,新版 PDA 两级菜单(默认)|

+ 332 - 0
docs/PDA开班点检接口说明.md

@@ -0,0 +1,332 @@
+# PDA 开班点检接口说明
+
+> 模块:`MesProcessCheckRecordController`  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 返回格式:`CommonResp`(`result` / `message` / `data`)  
+> 说明:工位客户端不再做开班点检,请在 PDA「开班点检」模块完成。点检模板绑定在父工位,PDA 选择子工位提交。
+
+---
+
+## 1. 线体查看接口
+
+##### 请求URL
+- ` /js/a/mes/mesProcessCheckRecord/pdaQueryLineSn `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|无|—|—|无需业务参数,返回全部启用中的线体|
+
+##### 参数示例
+```
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProcessCheckRecord/pdaQueryLineSn'
+```
+
+##### 返回示例
+
+```
+成功
+{
+    "result": "true",
+    "message": "成功",
+    "data": [
+        {
+            "lineSn": "HEVXT",
+            "title": "HEV箱体线"
+        },
+        {
+            "lineSn": "LB",
+            "title": "铝板线"
+        }
+    ]
+}
+```
+
+##### 返回字段说明
+
+|字段路径|类型|中文说明|
+|:----|:-----|-----|
+|result|string|业务成功 `"true"` / 失败 `"false"`|
+|message|string|提示信息|
+|data[].lineSn|string|线体编号|
+|data[].title|string|线体名称|
+
+---
+
+## 2. 工位查看接口(只显示子工位)
+
+##### 请求URL
+- ` /js/a/mes/mesProcessCheckRecord/pdaQueryOprno `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|lineSn|是|string|线体编号,来自接口 1|
+
+##### 参数示例
+```
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProcessCheckRecord/pdaQueryOprno?lineSn=HEVXT'
+```
+
+##### 返回示例
+
+```
+成功
+{
+    "result": "true",
+    "message": "成功",
+    "data": [
+        {
+            "id": "1700000000000000001",
+            "lineSn": "HEVXT",
+            "oprno": "OP010A",
+            "title": "上料"
+        },
+        {
+            "id": "1700000000000000002",
+            "lineSn": "HEVXT",
+            "oprno": "OP010B",
+            "title": "上料"
+        }
+    ]
+}
+```
+
+##### 返回字段说明
+
+|字段路径|类型|中文说明|
+|:----|:-----|-----|
+|result|string|业务成功 `"true"` / 失败 `"false"`|
+|message|string|提示信息,参数为空时为 `"参数错误"`|
+|data[].id|string|子工位 ID|
+|data[].lineSn|string|线体编号|
+|data[].oprno|string|子工位号,如 `OP010A`|
+|data[].title|string|工位名称|
+
+---
+
+## 3. 开班点检项接口
+
+##### 请求URL
+- ` /js/a/mes/mesProcessCheckRecord/pdaCheckItems `
+
+##### 请求方式
+- POST
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|lineSn|是|string|线体编号|
+|oprno|是|string|子工位号,如 `OP010A`|
+|ucode|是|string|用户账号,也可用 `userCode`|
+
+##### 参数示例
+```
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProcessCheckRecord/pdaCheckItems?lineSn=HEVXT&oprno=OP010A&ucode=user01'
+```
+
+##### 返回示例
+
+```
+成功
+{
+    "result": "true",
+    "message": "成功",
+    "data": {
+        "lineSn": "HEVXT",
+        "oprno": "OP010A",
+        "ucode": "user01",
+        "checked": false,
+        "list": [
+            {
+                "id": "1773335248913350656",
+                "title": "设备外观是否完好",
+                "type": "1",
+                "input": "1",
+                "inputName": "选择框",
+                "minValue": null,
+                "maxValue": null,
+                "sort": 10
+            },
+            {
+                "id": "1773335302935986176",
+                "title": "气压值",
+                "type": "1",
+                "input": "2",
+                "inputName": "输入框",
+                "minValue": "0.4",
+                "maxValue": "0.6",
+                "sort": 20
+            }
+        ]
+    }
+}
+```
+
+```
+无点检项
+{
+    "result": "true",
+    "message": "该工位未设置点检项",
+    "data": {
+        "lineSn": "HEVXT",
+        "oprno": "OP010A",
+        "ucode": "user01",
+        "checked": true,
+        "list": []
+    }
+}
+```
+
+##### 返回字段说明
+
+|字段路径|类型|中文说明|
+|:----|:-----|-----|
+|result|string|业务成功 `"true"` / 失败 `"false"`|
+|message|string|提示信息;工位不存在时为 `"工位信息不存在!"`|
+|data.lineSn|string|线体编号|
+|data.oprno|string|子工位号|
+|data.ucode|string|用户账号|
+|data.checked|boolean|当前班次是否已点检(或无需点检)|
+|data.list[].id|string|点检项 ID,提交时回传|
+|data.list[].title|string|点检项名称|
+|data.list[].type|string|点检类型,默认 `"1"` 点检内容|
+|data.list[].input|string|输入类型:`1`=选择框,`2`=输入框|
+|data.list[].inputName|string|输入类型中文:`选择框` / `输入框`|
+|data.list[].minValue|string|输入框最小值,选择框为 `null`|
+|data.list[].maxValue|string|输入框最大值,选择框为 `null`|
+|data.list[].sort|number|排序|
+
+##### 业务说明
+
+1. 选择框:PDA 展示勾选,提交 `content` 为 `true` / `false`(或 `OK` / `1` / `是` / `合格`)。
+2. 输入框:PDA 展示填数,提交实测值;有上下限时按范围判定 OK/NG。
+3. 点检项取自该子工位对应父工位上的模板。
+
+---
+
+## 4. 开班点检提交接口
+
+##### 请求URL
+- ` /js/a/mes/mesProcessCheckRecord/pdaCheckSubmit `
+
+##### 请求方式
+- POST(`application/x-www-form-urlencoded`,与 GP12 `mesProductGp12/submit` 相同)
+
+##### 参数
+
+|参数名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|lineSn|是|string|线体编号|
+|oprno|是|string|子工位号|
+|ucode|是|string|用户账号,也可用 `userCode`、`createBy`|
+|checkList|是|string|点检结果 JSON 数组,见下方结构|
+|mesProcessCheckRecordItemsList[i].mesProcessCheckRecordItems_image|是|string|第 i 项已上传图片的文件 ID,多个逗号分隔。也可写在 checkList 的 `mesProcessCheckRecordItems_image` 字段里|
+
+**checkList JSON 结构:**
+
+|字段名|必选|类型|说明|
+|:----|:---|:-----|-----|
+|id|是|string|点检项 ID,也可用 `checkId`|
+|content|是|string|选择框:`true`/`false`;输入框:实测数值。选择框未勾选可传空,服务端按 NG 处理|
+|mesProcessCheckRecordItems_image|否|string|该项已上传图片的文件 ID;不传则走上面的表单字段|
+
+**图片上传(与 GP12 相同):**
+
+1. 先调用 JeeSite 文件上传:`POST /js/a/file/upload`  
+   - 表单字段:文件二进制  
+   - `bizType` 固定 `mesProcessCheckRecordItems_image`  
+   - `uploadType=image`
+2. 将返回的文件 ID 填入 `mesProcessCheckRecordItemsList[i].mesProcessCheckRecordItems_image`(或 checkList 对应字段)再调本接口  
+3. 服务端 `FileUploadUtils.saveFileUpload` 绑定到该点检项
+
+##### 参数示例
+```
+# 1. 先上传照片(每一项各传一次)
+curl --location --request POST 'http://127.0.0.1:8980/js/a/file/upload' \
+--form 'file=@/tmp/check1.jpg' \
+--form 'bizType=mesProcessCheckRecordItems_image' \
+--form 'uploadType=image'
+
+# 2. 再提交点检(把上一步返回的文件 ID 填进去)
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProcessCheckRecord/pdaCheckSubmit' \
+--data-urlencode 'lineSn=HEVXT' \
+--data-urlencode 'oprno=OP010A' \
+--data-urlencode 'ucode=user01' \
+--data-urlencode 'checkList=[{"id":"1773335248913350656","content":"true"},{"id":"1773335302935986176","content":"0.52"}]' \
+--data-urlencode 'mesProcessCheckRecordItemsList[0].mesProcessCheckRecordItems_image=文件ID1' \
+--data-urlencode 'mesProcessCheckRecordItemsList[1].mesProcessCheckRecordItems_image=文件ID2'
+```
+
+##### 返回示例
+
+```
+成功
+{
+    "result": "true",
+    "message": "提交成功!",
+    "data": {
+        "id": "1900000000000000001",
+        "lineSn": "HEVXT",
+        "oprno": "OP010A",
+        "ucode": "user01",
+        "result": "OK",
+        "items": [
+            {
+                "id": "1900000000000000002",
+                "checkId": "1773335248913350656",
+                "content": "true",
+                "result": "OK"
+            },
+            {
+                "id": "1900000000000000003",
+                "checkId": "1773335302935986176",
+                "content": "0.52",
+                "result": "OK"
+            }
+        ]
+    }
+}
+```
+
+```
+失败(缺照片)
+{
+    "result": "false",
+    "message": "请为每一项上传照片"
+}
+```
+
+##### 返回字段说明
+
+|字段路径|类型|中文说明|
+|:----|:-----|-----|
+|result|string|业务成功 `"true"` / 失败 `"false"`|
+|message|string|提示信息|
+|data.id|string|开班点检记录 ID|
+|data.lineSn|string|线体编号|
+|data.oprno|string|子工位号|
+|data.ucode|string|提交人账号|
+|data.result|string|整单结果:全部合格 `"OK"`,否则 `"NG"`|
+|data.items[].id|string|点检项记录 ID|
+|data.items[].checkId|string|点检项模板 ID|
+|data.items[].content|string|填写值 / 选择值|
+|data.items[].result|string|该项结果 `"OK"` / `"NG"`|
+
+##### 业务说明
+
+1. **每一项都必须先上传照片**,缺一张即整单失败。上传方式与 GP12 相同:先 `POST /js/a/file/upload`,再把文件 ID 提交过来。
+2. 选择框判定合格值:`true`、`1`、`OK`、`是`、`合格`、`yes`。
+3. 输入框:在 `minValue`~`maxValue` 范围内为 OK;无上下限且有值则为 OK。
+4. 全部项 OK 时整单 `result=OK`,任一项 NG 则为 `NG`。
+5. 记录按子工位号保存,工位生产防呆按该子工位校验当前班次是否已开班点检。
+6. 常见失败信息:`参数错误`、`点检数据格式错误`、`该工位未设置点检项,无需点检!`、`工位信息不存在!`、`点检项不存在或与当前工位不匹配`、`有点检信息未提交`、`请为每一项上传照片`。

+ 227 - 0
docs/PDA批次码发货检接口说明.md

@@ -0,0 +1,227 @@
+# PDA 批次码发货检 — 接口说明
+
+> 模块:`MesBatchQualityController`  
+> 权限:`mes:mesBatchQuality:api`(APP 模块权限 → 发货检;或 生产管理 → 发货检记录 → PDA接口)  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 接口前缀:`/js/a`
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`)。  
+> 请统一使用 **POST**(表单或 query 参数均可)。  
+> **须先登录 MES**,请求携带会话(Cookie `jeesite.session.id` 或 `__sid` + `__ajax=json`)。
+
+---
+
+## 业务说明
+
+PDA 发货检:扫描批次码 → 查看状态和已绑定工件 → 填写 OK/NG、备注,可选拍照后提交。
+
+| 批次状态 `batchStatus` | 含义 | 可否发货检 |
+|------------------------|------|------------|
+| `3` | 待装中(新建默认) | 否,须先生产入库 |
+| `0` | 仓库中(生产入库后) | 已绑定工件时可检查 |
+| `1` | 合格(发货检 OK 后) | 不可再检查 |
+| `2` | 已发货 | 不可检查 |
+
+检查结果 `result`:`1` = OK,`2` = NG(也可传 `OK` / `NG`,服务端会转换成 1/2)。
+
+- **OK**:写入发货检记录,批次状态改为 `1`(合格)
+- **NG**:写入发货检记录,批次仍为 `0`(仓库中),可再次检查
+
+```text
+① mesBatchQuality/info     扫批次码,查状态 + 绑定工件
+        ↓ 填写 OK/NG、备注,可选上传照片
+② mesBatchQuality/add      提交发货检
+```
+
+---
+
+## 1. 查询详情
+
+**请求URL:** `/js/a/mes/mesBatchQuality/info`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| batchSn | 是 | string | 批次码(扫码值) |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchQuality/info?batchSn=20260804084909'
+```
+
+**返回示例(成功):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "lists": {
+      "id": "2072880747628806144",
+      "batchSn": "20260804084909",
+      "lineSn": "HEVXI",
+      "nums": 2,
+      "isJs": "1",
+      "isJsName": "未用完",
+      "batchStatus": "0",
+      "batchStatusName": "仓库中",
+      "createBy": "system",
+      "createDate": "2026-08-04 08:49:09",
+      "updateDate": "2026-08-04 09:10:00",
+      "canCheck": true,
+      "products": [
+        {
+          "sn": "0476962607160163",
+          "bindDate": "2026-08-04 09:10:00",
+          "bindBy": "system",
+          "state": "1",
+          "cate": "HEVXI",
+          "batchSn": "20260804084909"
+        }
+      ],
+      "checkId": "",
+      "result": "",
+      "resultName": "",
+      "remark": "",
+      "checkBy": "",
+      "checkDate": null,
+      "imgs": []
+    }
+  }
+}
+```
+
+已检查过(例如上次 NG)时,`result` / `remark` / `imgs` 为最近一次发货检记录。
+
+**返回字段说明(`data.lists`):**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 批次码记录主键 |
+| batchSn | string | 批次码 |
+| lineSn | string | 产线编号 |
+| nums | long | 已绑定数量 |
+| isJs | string | `1` 未用完 / `2` 已用完 |
+| isJsName | string | 是否用完中文 |
+| batchStatus | string | `3` 待装中 / `0` 仓库中 / `1` 合格 / `2` 已发货 |
+| batchStatusName | string | 状态中文 |
+| products | array | 已绑定工件列表(`MesProduct`) |
+| products[].sn | string | 工件码 |
+| products[].bindDate | string | 绑定时间 |
+| canCheck | boolean | `true` 时可提交发货检 |
+| result | string | 最近一次结果:`1` OK / `2` NG;未检查为空 |
+| remark | string | 最近一次备注 |
+| imgs | array | 最近一次照片,`{id, url, name, size}` |
+| checkId | string | 最近一次发货检记录 ID |
+
+**失败示例:**
+
+```json
+{
+  "result": "false",
+  "message": "批次信息不存在"
+}
+```
+
+| message | 原因 |
+|---------|------|
+| 参数错误 | `batchSn` 为空 |
+| 批次信息不存在 | 批次码无效 |
+
+**下一步:** `canCheck=true` 时进入填写页 → 接口 2
+
+---
+
+## 2. 提交发货检
+
+**请求URL:** `/js/a/mes/mesBatchQuality/add`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| batchSn | 是 | string | 批次码 |
+| result | 是 | string | `1` / `OK` = 合格;`2` / `NG` = 不合格 |
+| remark | 否 | string | 备注 |
+| mesBatchQuality_image | 否 | string | 已上传图片的文件 ID,多个逗号分隔;不传则不保存照片 |
+
+**图片上传(可选,与返修检查相同):**
+
+1. 先调用 JeeSite 文件上传:`POST /js/a/file/upload`  
+   - 表单字段:文件二进制  
+   - `bizType` 固定 `mesBatchQuality_image`  
+   - `uploadType=image`
+2. 将返回的文件 ID 填入 `mesBatchQuality_image` 再调本接口  
+3. 服务端 `FileUploadUtils.saveFileUpload` 绑定到发货检记录
+
+**参数示例:**
+
+```bash
+# 不传图片
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchQuality/add' \
+  --data-urlencode 'batchSn=20260804084909' \
+  --data-urlencode 'result=1' \
+  --data-urlencode 'remark=外观合格'
+
+# 带图片
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchQuality/add' \
+  --data-urlencode 'batchSn=20260804084909' \
+  --data-urlencode 'result=1' \
+  --data-urlencode 'remark=外观合格' \
+  --data-urlencode 'mesBatchQuality_image=文件ID1,文件ID2'
+```
+
+**返回示例(成功):**
+
+```json
+{
+  "result": "true",
+  "message": "成功"
+}
+```
+
+**失败示例:**
+
+```json
+{
+  "result": "false",
+  "message": "当前批次已检查"
+}
+```
+
+| message | 原因 |
+|---------|------|
+| 参数错误 | `batchSn` 为空,或 `result` 不是 1/2/OK/NG |
+| 批次信息不存在 | 批次码无效 |
+| 当前批次尚未生产入库,不能发货检 | 状态仍是待装中(`batchStatus=3`) |
+| 该批次码未绑定工件 | 没有已绑定工件,不能检查 |
+| 当前批次已检查 | 状态已是合格(`batchStatus=1`) |
+| 当前批次已发货,不能检查 | 状态已是已发货(`batchStatus=2`) |
+
+---
+
+## 鉴权说明
+
+未登录或 Session 过期时,HTTP 仍为 `200`,但响应体是 HTML 登录页,不是 JSON。缺少权限时返回 HTML **403 您的操作权限不足**。
+
+| 接口 | 权限 | 后台勾选位置 |
+|------|------|----------------|
+| `info` / `add` | `mes:mesBatchQuality:api` | 生产管理 → 发货检记录 → PDA接口;PDA 功能模块「发货检」`mode=235` |
+
+授权后请重新登录。
+
+---
+
+## 相关源码
+
+| 说明 | 路径 |
+|------|------|
+| 发货检接口 | `src/main/java/com/jeesite/modules/mes/web/MesBatchQualityController.java` |
+| 建表 / 菜单 / PDA 图标 | `db/mysql/add_mes_batch_quality.sql` |
+| 批次码主数据 | `MesBatchSnController`(状态字段 `batchStatus`) |

+ 288 - 0
docs/PDA返修接口说明.md

@@ -0,0 +1,288 @@
+# PDA 返修接口说明
+
+> 模块:`MesRepairController`  
+> 前端参考:`fxclient-web`(返修工位 Web 客户端)  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 接口前缀:`/js/a`
+
+> **范围说明:** 本文档仅描述 **返修工位** 作业(扫码 → 查看信息 → 开始返修)。  
+> 产线不良处理(白名单、`saveRepair` 等)不在 PDA 返修模块范围内,返修单须由产线或其他途径事先创建。
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`);`editRepair1` 返回 JeeSite `renderResult`(`result` / `message`)。  
+> 建议统一使用 **POST**,`Content-Type: application/x-www-form-urlencoded`。  
+> 登录会话:请求中携带 `__ajax=json`、`__sid={token}`(与 `fxclient-web` 一致)。
+
+---
+
+## 业务说明
+
+返修工位 PDA 负责处理 **已存在** 的返修单:
+
+| 步骤 | 说明 |
+|------|------|
+| 扫码 | 扫描工件号,加载当前返修单及 SOP 方案 |
+| 开始返修 | 填写返修人、时间,可选关联 SOP,状态变为「已返修 / 待质量确认」 |
+
+**前置条件:** 工件须已通过产线不良处理或后台创建返修单;否则 `getRepair` 会提示需先进行不良处理。
+
+**工位号 `fxOprno`:** 返修工位编号(如 `fx001`),由 PDA 本地配置,每次请求须传入。
+
+---
+
+## 状态流转(`mes_repair.state`)
+
+| state | 含义 | PDA 返修模块 |
+|-------|------|--------------|
+| `1` | 待返修 | 可扫码,可执行「开始返修」 |
+| `2` | 已返修 / 待质量确认 | 扫码后显示「等待检查」,本模块不再操作 |
+| `3` | 已检查 | 流程结束 |
+| `4` | 返修 NG 待处理 | 流程结束(NG) |
+
+```text
+【前置】产线/后台已创建返修单(state=1)
+        ↓
+【PDA 返修】getRepair 扫码
+        ↓
+【PDA 返修】editRepair1 开始返修(state→2)
+        ↓
+【其他模块】质量确认(不在本文档范围)
+```
+
+---
+
+## 页面流程
+
+```text
+① mesRepair/getRepair         扫工件码,加载返修单
+        ↓ 并行(可选)
+   mesRepair/cjjl             检查记录
+   mesRepair/infoByRepair     工位结果
+   mesRepair/sopList          修复方案列表
+        ↓
+② mesRepair/editRepair1       开始返修(state→2)
+```
+
+---
+
+## PDA 常量与路径对照
+
+| 常量名(PDA 侧) | 路径 |
+|------------------|------|
+| `GET_REPAIR` | `/js/a/mes/mesRepair/getRepair` |
+| `START_REPAIR` | `/js/a/mes/mesRepair/editRepair1` |
+| `GW_LIST` | `/js/a/mes/mesRepair/infoByRepair` |
+| `CHECK_RECORD` | `/js/a/mes/mesRepair/cjjl` |
+| `SOP_LIST` | `/js/a/mes/mesRepair/sopList` |
+
+---
+
+## 一、扫码加载返修单
+
+**请求 URL:** `/js/a/mes/mesRepair/getRepair`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| sn | 是 | string | 工件号 |
+| fxOprno | 是 | string | 返修工位编号,如 `fx001` |
+
+**前置条件:** 须已存在返修单;否则返回:
+
+```text
+当前工件{oprno}工位需要先进行不良处理!
+```
+
+**参数示例:**
+
+```bash
+curl --location --request POST \
+  'http://127.0.0.1:8980/js/a/mes/mesRepair/getRepair' \
+  --data-urlencode 'sn=0476962607070001' \
+  --data-urlencode 'fxOprno=fx001' \
+  --data-urlencode '__ajax=json' \
+  --data-urlencode '__sid=会话token'
+```
+
+**返回示例(实测):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "isNewRecord": false,
+    "id": "2074680482278412288",
+    "createDate": "2026-07-08 18:22",
+    "updateDate": "2026-07-08 18:22",
+    "createBy": "system",
+    "updateBy": "system",
+    "sn": "0476962607070001",
+    "fxOprno": "fx001",
+    "title": "左右吊耳组件弧焊",
+    "oprno": "OP050",
+    "state": "1",
+    "lineSn": "HEVXT",
+    "remark": "",
+    "result": "",
+    "type": "2",
+    "itemId": "2056991638449303552",
+    "itemTitle": "焊渣、毛刺、划伤",
+    "itemIntroduction": "手工打磨、除渣、去毛刺->气密",
+    "returnOprno": "",
+    "sopList": [],
+    "imgList": []
+  }
+}
+```
+
+> `sopList` 为空表示该返修单尚未关联 SOP;若 `sops` 字段有值,会返回已选方案详情,结构为 `{ id, title, imgList[] }`。
+
+**返回字段说明:**
+
+| 字段 | 说明 |
+|------|------|
+| id | 返修记录 ID,**开始返修 `editRepair1` 时传此值**(不是工件号 sn) |
+| sn | 工件号 |
+| fxOprno | 返修工位编号 |
+| oprno | 责任工位 |
+| lineSn | 产线编号 |
+| state | 当前状态,见上文状态表;`1` 时可开始返修 |
+| title | 责任工位名称(服务端按产线工序查询) |
+| itemTitle | 返修原因 |
+| itemIntroduction | 返工动作介绍 |
+| type | 返工类型 |
+| sopList | 已关联 SOP 列表;无关联时为空数组 `[]` |
+| sops | 已选 SOP ID(逗号分隔);无值时可能不返回 |
+| imgList | 不良照片列表;`getRepair` 通常为空,需详情时另查 |
+| createDate / updateDate 等 | JeeSite 实体公共字段,可忽略 |
+
+**界面步骤映射(与 `fxclient-web` 一致):**
+
+| data.state | 界面显示 |
+|------------|----------|
+| `1` | 等待返修(可点「开始返修」) |
+| `2` | 等待检查 |
+| `3` | 已检查(`result`:`1`=OK,`2`=NG) |
+
+---
+
+## 二、辅助信息(可选)
+
+扫码后可并行调用,用于展示工位历史、检查记录、SOP 方案。
+
+### 2.1 工位结果列表
+
+**请求 URL:** `/js/a/mes/mesRepair/infoByRepair`
+
+| 参数名 | 必选 | 说明 |
+|--------|------|------|
+| sn | 是 | 工件号 |
+| oprno | 是 | 责任工位 |
+
+**返回:** `data.lists[]` — 生产记录(craft 首位为 `1` 或 `5` 的项),含 `craft`、`title`、`content`。
+
+### 2.2 检查记录
+
+**请求 URL:** `/js/a/mes/mesRepair/cjjl`
+
+**参数:** 同工位结果列表
+
+**返回:** `data.lists` 为质量检查对象,含 `mesRepairItemsList[]`(问题清单)。
+
+### 2.3 修复方案(SOP)列表
+
+**请求 URL:** `/js/a/mes/mesRepair/sopList`
+
+**参数:** 无必填
+
+**返回:** `data.lists[]` — `{ id, title, remark, imgList[] }`
+
+---
+
+## 三、开始返修
+
+**请求 URL:** `/js/a/mes/mesRepair/editRepair1`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 说明 |
+|--------|------|------|
+| id | 是 | 返修记录 ID(来自 `getRepair` 的 `data.id`) |
+| state | 是 | 固定传 `2` |
+| fxBy | 是 | 返修人工号 |
+| fxDate | 是 | 返修时间,格式 `yyyy-MM-dd HH:mm:ss` |
+| sops | 否 | 所选 SOP ID,逗号分隔 |
+
+**业务规则:**
+
+- 返修单 `state` 须为 `1`(待返修)
+- 成功后 `state` 变为 `2`(已返修 / 待质量确认)
+
+**参数示例:**
+
+```bash
+curl --location --request POST \
+  'http://127.0.0.1:8980/js/a/mes/mesRepair/editRepair1' \
+  --data-urlencode 'id=1999999999999999991' \
+  --data-urlencode 'state=2' \
+  --data-urlencode 'fxBy=工号' \
+  --data-urlencode 'fxDate=2026-07-07 17:00:00' \
+  --data-urlencode 'sops=sopId1,sopId2' \
+  --data-urlencode '__ajax=json' \
+  --data-urlencode '__sid=会话token'
+```
+
+**返回:**
+
+```json
+{
+  "result": "true",
+  "message": "修改成功!"
+}
+```
+
+> **注意:** 本接口**无 `data` 字段**;不要与 `getRepair` 混淆。`id` 必须传返修记录主键(`getRepair` 返回的 `data.id`),不能传工件号 `sn`。
+
+---
+
+## 四、PDA 集成建议
+
+```text
+1. 本地配置 fxOprno(返修工位号)
+2. 扫描工件 sn → 调 getRepair
+3. 展示返修原因、SOP 列表(可选调 sopList / cjjl / infoByRepair)
+4. 用户确认后点「开始返修」→ 调 editRepair1(传 id、fxBy、fxDate、可选 sops)
+5. state=2 后提示「等待质量确认」,本模块流程结束
+```
+
+**注意:**
+
+- `fxOprno` 由 PDA 本地配置,不要写死为工件责任工位 `oprno`
+- 仅 `state=1` 时允许调用 `editRepair1`
+- 质量确认由其他模块处理,不在本文档范围
+
+---
+
+## 五、相关源码
+
+| 说明 | 路径 |
+|------|------|
+| 返修记录接口 | `src/main/java/com/jeesite/modules/mes/web/MesRepairController.java` |
+| fxclient 调用示例 | `fxclient-web/src/apps/home/api.js` |
+
+---
+
+## 六、与 fxclient-web 接口对照
+
+| fxclient-web 方法 | 后端路径 | PDA 是否必需 |
+|-------------------|----------|--------------|
+| `getRepair` | `mesRepair/getRepair` | **必需** |
+| `editRepair1` | `mesRepair/editRepair1` | **必需** |
+| `getGwList` | `mesRepair/infoByRepair` | 可选 |
+| `getcjll` | `mesRepair/cjjl` | 可选 |
+| `getSopList` | `mesRepair/sopList` | 可选 |

+ 91 - 0
docs/_pptx_inspect_out.txt

@@ -0,0 +1,91 @@
+SLIDE_COUNT: 2
+--- SLIDE1 TEXT FRAMES (up to 80) ---
+[0] ''
+[1] '六安裕洋MES项目实施进度汇报'
+[2] '京宜安 · 截至2026年7月21日 · 5–6月周任务64项已100%完成 · 本月聚焦现场培训/实际上线/陪产'
+[3] ''
+[4] '8周次'
+[5] '5–6月'
+[6] ''
+[7] '64/64'
+[8] '周任务完成'
+[9] ''
+[10] '100%'
+[11] '合格率'
+[12] ''
+[13] '7月'
+[14] '上线陪产'
+[15] ''
+[16] '周任务明细(阶段三~七 · 全部完成)'
+[17] ''
+[18] '阶段'
+[19] '起止'
+[20] '任务周'
+[21] '完成'
+[22] '合格率'
+[23] ''
+[24] '阶段三'
+[25] '5/7-5/10'
+[26] '五月第一周'
+[27] '8/8'
+[28] '100%'
+[29] ''
+[30] '阶段四'
+[31] '5/11-5/16'
+[32] '五月第二周'
+[33] '9/9'
+[34] '100%'
+[35] ''
+[36] '阶段四&五'
+[37] '5/18-5/23'
+[38] '五月第三周'
+[39] '9/9'
+[40] '100%'
+[41] ''
+[42] '阶段五'
+[43] '5/25-5/30'
+[44] '五月第四周'
+[45] '7/7'
+[46] '100%'
+[47] ''
+[48] '阶段六'
+[49] '6/1-6/6'
+[50] '六月第一周'
+[51] '7/7'
+[52] '100%'
+[53] ''
+[54] '阶段六'
+[55] '6/8-6/13'
+[56] '六月第二周'
+[57] '6/6'
+[58] '100%'
+[59] ''
+[60] '阶段六'
+[61] '6/13-6/18'
+[62] '六月第三周'
+[63] '10/10'
+[64] '100%'
+[65] ''
+[66] '阶段七'
+[67] '6/22-6/27'
+[68] '六月第四周'
+[69] '8/8'
+[70] '100%'
+[71] '合计:8 周次 · 任务 64 · 完成 64 · 合格率 100%'
+[72] ''
+[73] '实施阶段甘特图(2026-04-27 ~ 07-24)'
+[74] '今日 7/21'
+[75] ''
+[76] '4/27'
+[77] ''
+[78] '5/11'
+[79] ''
+--- STRING PRESENCE ---
+周任务明细: True
+本月重点: True
+现场培训: True
+实际上线: True
+陪产: True
+7月21: True
+100%: True
+SLIDE2_PICTURE_COUNT: 8

BIN=BIN
docs/ccd/MES与CCD接口.xlsx


+ 263 - 0
docs/ccd/MES与CCD接口说明.md

@@ -0,0 +1,263 @@
+# MES 与 CCD 视觉检测 — 接口说明
+
+> 模块:`MesProductRecordController#ccdcheck`  
+> 协议:HTTP POST,`application/x-www-form-urlencoded`  
+> 认证:匿名接口(`application.yml` 中已配置 `anon`)  
+> 正式地址示例:`http://192.168.9.180:8980/js/a/mes/mesProductRecord/ccdcheck`  
+> 本地调试示例:`http://127.0.0.1:8980/js/a/mes/mesProductRecord/ccdcheck`
+
+---
+
+## 调用流程
+
+```text
+① 01 序号检查     扫码后校验工件是否允许在本工位检测
+        ↓
+② 04 测试结果上传  检测过程中/结束后上传明细(有数据就传,没有可跳过)
+        ↓
+③ 05 序号过站     上报整件 OK/NG,MES 记录过站结果
+```
+
+> 三个命令通过同一 URL、同一参数名 `commandString` 区分,首字段为命令编号(TestType)。
+
+---
+
+## 通用约定
+
+### 请求
+
+| 项目 | 说明 |
+|------|------|
+| URL | `/js/a/mes/mesProductRecord/ccdcheck` |
+| 方法 | `POST` |
+| Content-Type | `application/x-www-form-urlencoded` |
+| 参数名 | `commandString`(整条命令字符串) |
+
+### 命令字符串格式
+
+- 字段之间用英文分号 `;` 分隔。
+- 末尾建议保留分号 `;`(与驭视科技 CCD 标准格式一致)。
+- 工位号格式为 `OP` 开头,可带后缀字母区分子工位,例如 `OP290B`、`OP390B`;MES 内部会规范化处理。
+
+### 响应
+
+返回 XML 字符串,正文为 `OK;` 或 `NG;原因;`:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<string xmlns="http://mesateapi.com/">OK;</string>
+```
+
+失败示例:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<string xmlns="http://mesateapi.com/">NG;工件码不能为空;</string>
+```
+
+---
+
+## 命令字段对照(通用头部)
+
+| 序号 | 字段名 | 01 必填 | 04 必填 | 05 必填 | 说明 |
+|------|--------|---------|---------|---------|------|
+| 0 | TestType | 是 | 是 | 是 | 命令编号:`01` / `04` / `05` |
+| 1 | 工号 | 是 | 是 | 是 | 操作员工号 |
+| 2 | 序号(工件码) | 是 | 是 | 是 | 产品条码/SN |
+| 3 | 工序名称(工位号) | 是 | 是 | 是 | 如 `OP290B` |
+| 4 | 资源名称 | 是 | 是 | 是 | 检测设备/工位资源名称 |
+| 5 | 设备编码 | 否 | 否 | 否 | 无则传空 |
+| 6 | 工治具编码 | 否 | 否 | 否 | 无则传空 |
+
+---
+
+## 1. 序号检查(TestType = 01)
+
+检测前校验工件是否允许在当前工位进行 CCD 检测(工艺路线、质量状态等)。
+
+### 命令格式
+
+```text
+01;工号;序号;工序名称;资源名称;设备编码;工治具编码;
+```
+
+共 **8** 段(含末尾空段),`split(";", -1)` 后长度为 8。
+
+### 参数示例
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProductRecord/ccdcheck' \
+  --header 'Content-Type: application/x-www-form-urlencoded' \
+  --data-urlencode 'commandString=01;EMP001;502402660085119990EBMA100082;OP390;Res001;MACHINE01;Fixture001;'
+```
+
+### 返回说明
+
+| 返回内容 | 含义 |
+|----------|------|
+| `OK;` | 允许检测 |
+| `NG;{原因};` | 不允许检测,原因由 MES 工艺/质量校验返回 |
+
+### 备注
+
+- 工号、序号、工序名称、资源名称为必填。
+- 设备编码、工治具编码为非必填,没有时传空即可。
+
+---
+
+## 2. 测试结果上传(TestType = 04)
+
+上传 CCD 各测点明细数据,写入 `mes_product_ccd` 表。**有明细就传,没有可不调用。**
+
+### 命令格式
+
+```text
+04;工号;序号;工序名称;资源名称;设备编码;工治具编码;测试开始时间;测点明细;
+```
+
+共 **10** 段(含末尾空段)。
+
+其中 **测点明细**(第 9 段,`parts[8]`)格式:
+
+```text
+序号&拍照序号&检测名称&检测参数&检测值&检测结果&检测时间,序号&拍照序号&...
+```
+
+- 多个测点之间用英文逗号 `,` 分隔。
+- 单个测点内 7 个字段之间用 `&` 分隔。
+- `检测时间` 格式:`yyyy-MM-dd HH:mm:ss`
+- `检测结果` 取值:`OK` / `NG`
+
+### 测点字段说明
+
+| 字段 | 说明 | 示例 |
+|------|------|------|
+| 序号 | 测点编号 | `1` |
+| 拍照序号 | 拍照区序号 | `2` |
+| 检测名称 | 检测项名称 | `拉铆螺母` |
+| 检测参数 | 规格/阈值描述 | `直径:3.5~15高度:1~10` |
+| 检测值 | 实测值 | `直径:6.50\|高度:2.22\|` |
+| 检测结果 | OK/NG | `OK` |
+| 检测时间 | 该测点检测时间 | `2024-05-06 15:19:07` |
+
+### 参数示例(单条测点)
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProductRecord/ccdcheck' \
+  --header 'Content-Type: application/x-www-form-urlencoded' \
+  --data-urlencode 'commandString=04;15112184644;502402660085119990E4GA100296;OP290B;OP290B;;;20240506120729;1@1@二维码有无@有无:1~1@502402660085119990E4GA100296重复扫描@OK@2024-05-06 12:03:44;'
+```
+
+### 参数示例(多条测点)
+
+```text
+04;15112184644;502402660085119990E54A100352;OP290B;OP290B;;;20240506152248;1@1@二维码有无@有无:1~1@502402660085119990E54A100352@OK@2024-05-06 15:19:02,2@2@拉铆螺母@直径:3.5~15高度:1~10@直径:10.59|高度:2.21|@OK@2024-05-06 15:19:07;
+```
+
+### 返回说明
+
+| 返回内容 | 含义 |
+|----------|------|
+| `OK;` | 明细保存成功 |
+| `NG;参数错误3;` | 分段数量不是 10 |
+| `NG;参数错误4;` | TestType 不是 04 |
+| `NG;参数错误5;` | 某个测点字段数不是 7 |
+| `NG;检测时间格式错误;` | 检测时间格式不正确 |
+| `NG;工件码不能为空;` | SN 为空 |
+| `NG;工位号不能为空;` | 工位号为空 |
+
+### 备注
+
+- `测试开始时间` 字段(第 8 段)格式为 `yyyyMMddHHmmss`,当前 MES 实现主要解析第 9 段测点明细,开始时间作记录参考。
+- 设备编码、工治具编码无值时保持空位(连续 `;`)。
+
+---
+
+## 3. 序号过站(TestType = 05)
+
+整件检测完成后,向 MES 上报最终 OK/NG 并执行过站。
+
+### 命令格式
+
+```text
+05;工号;序号;工序名称;资源名称;设备编码;工治具编码;测试结果;不良现象;
+```
+
+共 **10** 段(含末尾空段)。
+
+| 字段 | 说明 |
+|------|------|
+| 测试结果(第 8 段) | 必填,`OK` 或 `NG` |
+| 不良现象(第 9 段) | NG 时必填,填写最主要的一条不良描述;OK 时传空 |
+
+### 参数示例
+
+**过站 OK:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesProductRecord/ccdcheck' \
+  --header 'Content-Type: application/x-www-form-urlencoded' \
+  --data-urlencode 'commandString=05;15112184644;151245P00001213100100025072501024;OP390B;OP390B;;;OK;;'
+```
+
+**过站 NG:**
+
+```text
+05;EMP001;SN001;Station001;Res001;MACHINE01;Fixture001;NG;无法开机;
+```
+
+### 返回说明
+
+| 返回内容 | 含义 |
+|----------|------|
+| `OK;` | 过站成功 |
+| `NG;结果不能为空;` | 未传测试结果 |
+| `NG;结果错误;` | 测试结果不是 OK/NG |
+| `NG;操作失败;` | MES 内部过站处理异常 |
+
+### 备注
+
+- 工号、序号、工序、资源名称为必填。
+- 设备编码、工治具编码为非必填,没有时传空。
+- NG 时不良现象必须有值,且建议只传一条最重要的描述。
+
+---
+
+## 附录:标准模板示例
+
+以下为 Excel 原始标准格式示例,便于联调对照。
+
+### 01 模板
+
+```text
+01;EMP001;SN001;Station001;Res001;MACHINE01;Fixture001;
+```
+
+### 04 模板
+
+```text
+04;EMP001;SN001;Station001;Res001;MachineNo;Fixture001;20181002131019;序号1&拍照序号1&检测名称1&检测参数1&检测值1&检测结果1&2024-11-11 11:11:11,序号2&拍照序号2&检测名称2&检测参数2&检测值2&检测结果2&2025-10-22 22:22:22;
+```
+
+### 05 模板
+
+```text
+05;EMP001;SN001;Station001;Res001;MACHINE01;Fixture001;OK;;
+05;EMP001;SN001;Station001;Res001;MACHINE01;Fixture001;NG;无法开机;
+```
+
+---
+
+## 附录:MES 侧数据落库
+
+| 命令 | 处理逻辑 | 数据表 |
+|------|----------|--------|
+| 01 | 调用 `checkQualityCommon` 做进站校验 | — |
+| 04 | 批量插入测点明细 | `mes_product_ccd` |
+| 05 | 调用 `updateRecordResult` 写过站记录 | 生产记录相关表 |
+
+`mes_product_ccd` 主要字段:工件码、产线编号、工位号、检测设备名称、拍照区序号、测点编号、检测名称、检测参数、检测值、检测结果、检测时间。
+
+---
+
+*文档来源:`docs/ccd/MES与CCD接口.xlsx`(标准格式页),并结合 `MesProductRecordController` / `MesProductCcdService` 实现整理。*

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2551 - 0
docs/erp/PDA_ERP接口汇总.md


+ 464 - 0
docs/erp/PDA批次码接口说明.md

@@ -0,0 +1,464 @@
+# PDA 批次码 — 接口说明
+
+> 模块:`MesBatchSnController` / `MesJbLogController`  
+> 权限:`mes:mesBatchSn:view`(列表/详情)、`mes:mesBatchSn:edit`(新增/绑定)、`mes:mesJbLog:api`(解绑)  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 绑定上限:系统配置 `mes.batch.count`(每批次最多绑定工件数)
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`);PC 列表仍用 `listData`(直接返回 `Page`)。  
+> PDA 请统一使用 **POST**(表单或 query 参数均可)。  
+> **须先登录 MES**,请求携带会话(见下方「鉴权说明」)。
+
+---
+
+## 鉴权说明(Apifox / PDA 测试必读)
+
+本模块接口**需要登录**,未登录或 Session 过期时,HTTP 状态码仍为 `200`,但响应体是 **HTML 登录页**(`<title>登录 - MES生产管理系统</title>`),不是 JSON。
+
+| 现象 | 原因 |
+|------|------|
+| 返回 HTML 登录页 | Cookie / `__sid` 失效、未登录、或账号无权限 |
+| 返回 HTML **403 您的操作权限不足** | 已登录,但账号缺少接口所需权限(见下表) |
+| Apifox 显示 200 但解析失败 | 把登录页 HTML 当成了 JSON |
+
+**正确做法(任选其一):**
+
+1. **Cookie 方式(Apifox 推荐)**  
+   - 浏览器登录 MES → F12 → 复制 `jeesite.session.id`  
+   - 请求头:`Cookie: jeesite.session.id=你的会话ID`
+
+2. **参数方式(PDA 常用)**  
+   - 每个请求附带:`__ajax=json`、`__sid=登录后的会话ID`
+
+**Apifox 示例:**
+
+```bash
+curl --location --request POST \
+  'http://127.0.0.1:8980/js/a/mes/mesJbLog/getBatchInfo' \
+  --header 'Cookie: jeesite.session.id=你的会话ID' \
+  --data-urlencode 'batchSn=20260702172138' \
+  --data-urlencode '__ajax=json'
+```
+
+**权限要求:**
+
+| 接口 | 权限 | 后台勾选位置 |
+|------|------|----------------|
+| `pda/listData` | `mes:mesBatchSn:view` | 生产管理 → 批次码记录表 |
+| `createBatchSn` / `bindProduct` | `mes:mesBatchSn:edit` | 生产管理 → 批次码记录表 |
+| `getBatchInfo` / `addSave` | `mes:mesJbLog:api` | **APP模块权限 → 批次解绑** |
+
+> `getBatchInfo` 虽用于「批次详情查工件」,但代码挂在 `MesJbLogController`,权限标识为 `mes:mesJbLog:api`(与解绑共用)。PDA 测试账号须勾选此项,否则会 **403**。
+
+授权后重新登录,成功时 `getBatchInfo` 应返回 JSON,`Content-Type` 含 `json`,`data` 为数组(可为 `[]`)。
+
+---
+
+## 页面流程(4 步)
+
+```text
+① pda/listData               批次码列表(模块首页)→ 点行时缓存整行 row
+        ↓ 底部「新增批次码」
+② mesBatchSn/createBatchSn      按产线自动生成批次码
+        ↓ 点击列表中的批次码
+③ mesJbLog/getBatchInfo         详情 — 仅刷新已绑定工件列表(data[])
+        ↓ 详情头信息用 ① 的 row,不要用 ③ 拼批次头
+④ mesBatchSn/bindProduct        扫描绑定工件码
+   mesJbLog/addSave              解绑单个工件码
+```
+
+---
+
+## 1. 批次码列表(模块首页)
+
+**请求URL:** `/js/a/mes/mesBatchSn/pda/listData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码,默认 1 |
+| pageSize | 否 | int | 每页条数,默认 20 |
+| batchSn | 否 | string | 批次码 |
+| lineSn | 否 | string | 产线编号 |
+| isJs | 否 | string | `1` 未用完 / `2` 已用完 |
+| createDate_gte | 否 | string | 创建时间起,`yyyy-MM-dd HH:mm` |
+| createDate_lte | 否 | string | 创建时间止 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchSn/pda/listData?pageNo=1&pageSize=20'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 2,
+    "list": [
+      {
+        "id": "1999999999999999991",
+        "batchSn": "20260702172138",
+        "lineSn": "HEVXT",
+        "nums": 1,
+        "isJs": "1",
+        "instockStatus": "0",
+        "createBy": "system",
+        "createDate": "2026-07-02 17:21:38",
+        "updateDate": "2026-07-02 17:22:10"
+      }
+    ]
+  }
+}
+```
+
+**返回字段说明(data.list[] 单条):**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 主键,绑定工件时必传 |
+| batchSn | string | 批次码(扫码值) |
+| lineSn | string | 产线编号,如 HEVXT |
+| nums | long | 已绑定工件数量 |
+| isJs | string | `1` 未用完 / `2` 已用完 |
+| instockStatus | string | `0` 待入库 / `1` 已入库 |
+| createDate | string | 创建时间 |
+
+**下一步:** 点「新增批次码」→ 接口 2;点某行批次码 → 接口 3,传 `batchSn`
+
+---
+
+## 2. 新增批次码
+
+**请求URL:** `/js/a/mes/mesBatchSn/createBatchSn`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| lineSn | 是 | string | 产线编号 |
+
+新建后状态为 **待装中**(`batchStatus=3`),经生产入库接口成功后才改为 **仓库中**(`0`)。
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchSn/createBatchSn?lineSn=HEVXT'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "batchSn": "20260707103045"
+  }
+}
+```
+
+**失败示例:**
+
+```json
+{
+  "result": "false",
+  "message": "参数错误",
+  "data": null
+}
+```
+
+**说明:** 批次码按 `yyyyMMddHHmmss` 自动生成,初始 `nums=0`
+
+**下一步:** 回到列表刷新,或带 `batchSn` 进入接口 3 详情
+
+---
+
+## 3. 批次码详情 — 已绑定工件
+
+**请求URL:** `/js/a/mes/mesJbLog/getBatchInfo`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| batchSn | 是 | string | 批次码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesJbLog/getBatchInfo?batchSn=20260702172138'
+```
+
+### 与 PC 详情页的关系(重要)
+
+PC 端「批次码记录表详情」(`mesBatchSn/info`) 展示两块数据,**来源不同**:
+
+| PC 页面区块 | 数据来源 | PDA 如何获取 |
+|-------------|----------|--------------|
+| 批次码、产线编号、创建人、创建/更新时间 | `MesBatchSn` 主表 | **接口 1 列表行**(点进详情时把该行缓存下来) |
+| 产品信息(工件码、绑定时间) | `mes_product` 按 `batch_sn` 查询 | **本接口** `getBatchInfo` 的 `data[]` |
+
+源码(`MesJbLogController.getBatchInfo`)逻辑:
+
+1. 校验 `batchSn` 在 `mes_batch_sn` 中存在,不存在则失败  
+2. 查询 `mes_product.batch_sn = batchSn` 的工件列表  
+3. **`resp.setData(mp)` 只放 `List<MesProduct>`,不把批次主表 `MesBatchSn` 放进响应**
+
+因此:**不是接口漏了产品信息,而是批次头信息本来就不在这个接口里**;PDA 详情页需「列表行 + 本接口」拼出与 PC 一致的页面。
+
+### 返回示例(实测 `batchSn=20260702172138`)
+
+有绑定工件时:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": [
+    {
+      "isNewRecord": false,
+      "id": "2072880747628806144",
+      "createDate": "2026-07-03 11:10",
+      "updateDate": "2026-07-03 13:28",
+      "createBy": "system",
+      "updateBy": "system",
+      "sn": "0476966613765586",
+      "psn": "",
+      "title": "",
+      "state": "1",
+      "remark": "",
+      "batchSn": "20260702172138",
+      "weight": "",
+      "cate": "HEVXT",
+      "bindDate": "2026-07-03 13:28:50",
+      "bindBy": "system"
+    }
+  ]
+}
+```
+
+> 结构固定为 `MesProduct` 全字段;`id`、`bindBy`、`updateDate` 等随记录变化。`title` 在库中常为空;产品类型看 `cate`(如 `HEVXT`)。PC「产品信息」只展示 `sn`、`bindDate` 两列,与上表一致。
+
+未绑定任何工件时(批次存在但 `nums=0`):
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": []
+}
+```
+
+**失败示例:**
+
+```json
+{
+  "result": "false",
+  "message": "批次信息不存在",
+  "data": null
+}
+```
+
+### 返回字段说明
+
+**`data[]` 单条(`MesProduct` 实体,Jackson 序列化全字段):**
+
+| 字段 | 类型 | 说明 | PDA 详情是否常用 |
+|------|------|------|------------------|
+| sn | string | 工件码 | **是**(对应 PC「产品信息-工件码」) |
+| bindDate | string | 绑定时间 `yyyy-MM-dd HH:mm` | **是**(对应 PC「产品信息-绑定时间」) |
+| bindBy | string | 绑定人账号 | 可选 |
+| batchSn | string | 所属批次码 | 可与入参对照 |
+| title | string | 产品名称 | 库中常为空,不必展示 |
+| cate | string | 产品类型/产线类别 | 有值时展示,如 `HEVXT` |
+| state | string | 工件状态 | 字典 `mes_product_status` |
+| psn / remark / weight | string | 生产批号、备注、重量 | 一般可忽略 |
+| id / createDate / updateDate 等 | — | JeeSite 公共字段 | 可忽略 |
+
+> PC 详情「产品信息」表格只渲染 `sn`、`bindDate` 两列;接口实际返回完整 `MesProduct`,字段比 PC 展示更多。
+
+### PDA 详情页组装建议
+
+```text
+进入详情时携带列表行 row(接口 1):
+  row.batchSn、row.lineSn、row.nums、row.isJs、row.instockStatus、row.createDate …
+
+再调 getBatchInfo(batchSn=row.batchSn):
+  data[] → 工件列表(sn、bindDate)
+
+页面结构对齐 PC:
+  头部:批次码、产线编号(来自 row)
+  列表:产品信息(来自 data[])
+  底部:创建人、创建/更新时间(来自 row)
+```
+
+**下一步:** `row.isJs != '2'` 且 `row.instockStatus != '1'` 时可绑定 → 接口 4;每行工件可解绑 → 接口 5
+
+---
+
+## 4. 绑定工件码
+
+**请求URL:** `/js/a/mes/mesBatchSn/bindProduct`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| id | 是 | string | 批次码记录主键(列表 `id`) |
+| sn | 是 | string | 工件码(扫码或手输) |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchSn/bindProduct' \
+--data-urlencode 'id=1999999999999999991' \
+--data-urlencode 'sn=WP202607020001'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "绑定成功"
+}
+```
+
+**常见失败 message:**
+
+| message | 原因 |
+|---------|------|
+| 参数错误 | id 或 sn 为空 |
+| 批次码不存在 | id 无效 |
+| 当前批次码已用完,不能继续绑定 | isJs=2 |
+| 该批次码已入库,不能继续绑定 | instockStatus=1 |
+| 批次码绑定数量已满 | nums ≥ mes.batch.count |
+| 工件码不存在 | sn 不在 mes_product |
+| 该工件已绑定批次码 | 工件已有 batchSn |
+
+**下一步:** 绑定成功后刷新接口 3
+
+---
+
+## 5. 解绑工件码
+
+**请求URL:** `/js/a/mes/mesJbLog/addSave`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| batchSn | 是 | string | 批次码 |
+| sn | 是 | string | 要解绑的工件码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesJbLog/addSave' \
+--data-urlencode 'batchSn=20260702172138' \
+--data-urlencode 'sn=WP202607020001'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "操作成功",
+  "data": {}
+}
+```
+
+**常见失败 message:**
+
+| message | 原因 |
+|---------|------|
+| 参数错误 | batchSn 或 sn 为空 |
+| 批次码信息不存在 | 批次码无效 |
+| 当前批次已出货,不能解绑 | 已有出货记录 |
+| 当前工件已解绑 | 该 sn 未绑定此批次 |
+
+**说明:** 解绑单个工件,`nums` 自动减 1,并写入解绑记录
+
+**下一步:** 解绑成功后刷新接口 3
+
+---
+
+## 附录 A:新增批次码(手工指定码,可选)
+
+**请求URL:** `/js/a/mes/mesBatchSn/save`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| isNewRecord | 是 | boolean | 固定 `true` |
+| lineSn | 是 | string | 产线编号 |
+| batchSn | 否 | string | 留空则自动生成 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchSn/save' \
+--data-urlencode 'isNewRecord=true' \
+--data-urlencode 'lineSn=HEVXT'
+```
+
+---
+
+## 附录 B:产线当前可用批次(产线侧辅助)
+
+**请求URL:** `/js/a/mes/mesBatchSn/getBatchSn`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| lineSn | 是 | string | 产线编号 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesBatchSn/getBatchSn?lineSn=HEVXT'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "batchSn": "20260702172138"
+  }
+}
+```
+
+**说明:** 返回产线下未用完且未达绑定上限的批次码;无可用时 `batchSn` 为空字符串
+
+---
+
+## 与 ERP 生产入库的关系
+
+批次码绑定工件后(`nums > 0`),可在 **PDA 生产入库** 模块勾选该批次提交 ERP 入库,详见 [PDA生产入库接口说明.md](./PDA生产入库接口说明.md)。入库成功后 `instockStatus=1`,不可再绑定。

+ 856 - 0
docs/erp/PDA来料检接口说明.md

@@ -0,0 +1,856 @@
+# PDA 来料检 — 接口说明
+
+> 模块:`ErpInStockController`(提交)、`MesErpInStockInspectController`(查检验记录)  
+> 权限:`mes:erpInStock:view`(登录 MES 即可,**不要直连金蝶 ERP**)  
+> 服务地址示例:`http://127.0.0.1:8980`
+
+---
+
+## 本次变更说明(来料检记录)
+
+> 下文凡标注 **【新增】** 的字段/接口,均为「固定检查项 + OK/NG 留痕」改造新增;未标注的为原有能力。
+
+| 类型 | 内容 | 说明 |
+|------|------|------|
+| 【新增】必填参数 | `submitInStock.checkResult` | `OK` / `NG`;**不传会失败** |
+| 【新增】可选参数 | `inspectorName` / `inspectorCode` / `inspectTime` / `remarks` / `channel` | 检验员、时间、备注、渠道 |
+| 【新增】返回字段 | `data.inspectId` / `checkResult` / `inStockCreated` | 提交后带回检验记录信息 |
+| 【新增】业务规则 | 仅 `OK` 生成采购入库单;`NG` 只保存检验记录 | 原流程等价于默认一直 OK 入库 |
+| 【新增】初始化字段 | `inspectInit` 返回检验员/时间/结果选项 | 便于 PDA 预填 |
+| 【新增】接口 | `mesErpInStockInspect/pda/listData` | 查询来料检记录列表 |
+| 【新增】PC 菜单 | 生产管理 → ERP来料检记录 | 需执行 `db/mysql/add_mes_erp_in_stock_inspect.sql` |
+
+**PDA 适配要点:**
+
+1. 提交时 **必须** 传 `checkResult=OK` 或 `NG`
+2. `OK` 时仍须传仓库(及仓位,若 required)
+3. `NG` 时可不传仓库,接口仍返回 `result=true`,但 `inStockCreated=0`
+4. 用 `data.inStockCreated` 判断是否生成了 CGRK 入库单
+
+---
+
+## 页面流程
+
+```text
+① pda/listData(可选)  采购入库历史
+        ↓ 新增
+② pda/selectOrderData    采购订单列表
+        ↓ 点选 billNo
+③ pda/selectDetailData   待入分录,勾选 entryId
+        ↓ billNo + entryIds
+④ inspectInit            来料检页(仓库/数量/批号 + 【新增】固定检查项)
+   └ pda/stockLocOptions  选仓库后查仓位(required=true 必选)
+   └ pda/lotSearch        搜批号(可选)
+        ↓
+⑤ submitInStock          提交来料检【新增 checkResult】
+   ├ checkResult=OK → 保存检验记录 + 生成采购入库单(CGRK)
+   └ checkResult=NG → 仅保存检验记录,不生成入库单
+        ↓(可选)
+⑥ mesErpInStockInspect/pda/listData  【新增】查来料检记录
+```
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`);列表类接口 `data` 为分页结构。提交接口支持 **表单 POST** 与 **JSON Body** 两种方式。
+>
+> **固定检查项(【新增】):** 检验员、检验时间、OK/NG、数量、批号、备注。其中 `checkResult` 必填。
+
+---
+
+## 1. 采购订单列表
+
+**请求URL:** `/js/a/mes/erpInStock/pda/selectOrderData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码,默认 1 |
+| pageSize | 否 | int | 每页条数,默认 20 |
+| billNo | 否 | string | 采购订单号(可扫码/模糊查询) |
+| materialNumberQuery | 否 | string | 物料编码模糊 |
+| documentStatusQuery | 否 | string | 单据状态:A创建 B审核中 **C已审核** D重新审核;建议默认 C |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/selectOrderData?pageNo=1&pageSize=20&documentStatusQuery=C'
+```
+
+带订单号搜索:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/selectOrderData?pageNo=1&pageSize=20&documentStatusQuery=C&billNo=CGDD110260602003'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "billNo": "CGDD110260602003",
+        "date": "2026-06-02",
+        "documentStatus": "C",
+        "documentStatusName": "已审核",
+        "supplierNumber": "VEN001",
+        "supplierName": "某某供应商",
+        "purchaseOrgName": "安徽裕洋",
+        "entryCount": 3,
+        "totalQty": 1500
+      }
+    ]
+  }
+}
+```
+
+**页面显示:** `billNo`、`date`、`supplierName`、`documentStatusName`、`entryCount`
+
+**下一步:** 用户点击某行 → 进入接口 2,传入 `billNo`
+
+---
+
+## 2. 待入分录列表(勾选明细)
+
+**请求URL:** `/js/a/mes/erpInStock/pda/selectDetailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 采购订单号 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/selectDetailData?billNo=CGDD110260602003'
+```
+
+**返回示例:**
+
+成功(有剩余可入分录):
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 2,
+    "list": [
+    {
+      "entryId": "123456",
+      "seq": "1",
+      "materialNumber": "3.01.001",
+      "materialName": "某原材料",
+      "unitName": "千克",
+      "qty": 1000,
+      "instockQty": 200,
+      "remainQty": 800,
+      "deliveryDate": "2026-06-15"
+    }
+  ]
+  }
+}
+```
+
+成功(已全部入库,list 为空):
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 0,
+    "list": []
+  }
+}
+```
+
+**页面显示:** `seq`、`materialNumber`、`materialName`、`qty`(订单量)、`instockQty`(已入)、`remainQty`(剩余可入)、`unitName`
+
+**下一步:** 勾选 1~N 行 → 收集 `entryId`,逗号拼接 → 进入接口 3
+
+---
+
+## 3. 来料检页初始化
+
+**请求URL:** `/js/a/mes/erpInStock/inspectInit`
+
+**请求方式:** `GET` 或 `POST`(PDA 常用 POST + form 表单参数)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 采购订单号 |
+| entryIds | 是 | string / array | 分录内码;多条逗号分隔,或 JSON 数组;**仅一条时也传此字段** |
+
+**参数示例:**
+
+```bash
+# 多条
+curl --location --request GET 'http://127.0.0.1:8980/js/a/mes/erpInStock/inspectInit?billNo=CGDD110260602003&entryIds=123456,123457'
+# 单条
+curl --location --request GET 'http://127.0.0.1:8980/js/a/mes/erpInStock/inspectInit?billNo=CGDD110260602003&entryIds=123456'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "",
+  "data": {
+    "billNo": "CGDD110260602003",
+    "defaultStockNumber": "1100105",
+    "inspectorCode": "zhangsan",
+    "inspectorName": "张三",
+    "inspectTime": "2026-09-11 13:30:00",
+    "checkResultOptions": ["OK", "NG"],
+    "stockList": [
+      {
+        "stockNumber": "1100105",
+        "stockName": "五金库",
+        "displayLabel": "1100105 五金库"
+      }
+    ],
+    "stockLoc": {
+      "required": true,
+      "defaultLocNumber": "A11",
+      "locKey": "F100002",
+      "locHint": "共 278 个仓位,数据来自 ERP 仓位值集",
+      "locations": [
+        { "locNumber": "A11", "locName": "A11区", "locKey": "F100002", "displayLabel": "A11 A11区" }
+      ]
+    },
+    "lines": [
+      {
+        "entryId": "123456",
+        "seq": "1",
+        "materialNumber": "3.01.001",
+        "materialName": "某原材料",
+        "unitName": "千克",
+        "remainQty": 800,
+        "batchManage": true,
+        "suggestedLotNumber": "LOT202606001",
+        "lotHint": "共 3 条批号档案,已推荐第一条",
+        "availableLots": [
+          { "lotNumber": "LOT202606001", "qtyText": "0" }
+        ]
+      }
+    ]
+  }
+}
+```
+
+**返回字段说明(inspectInit data):**
+
+| 字段 | 类型 | 说明 | 变更 |
+|------|------|------|------|
+| billNo | string | 采购订单号 | 原有 |
+| defaultStockNumber | string | 默认仓库 | 原有 |
+| stockList | array | 仓库列表 | 原有 |
+| stockLoc | object | 默认仓库仓位 | 原有 |
+| lines | array | 待检分录 | 原有 |
+| inspectorCode | string | 默认检验员账号 | **【新增】** |
+| inspectorName | string | 默认检验员姓名 | **【新增】** |
+| inspectTime | string | 默认检验时间 `yyyy-MM-dd HH:mm:ss` | **【新增】** |
+| checkResultOptions | array | 结果选项,固定 `["OK","NG"]` | **【新增】** |
+
+失败(分录已无剩余):
+
+```json
+{
+  "result": "false",
+  "message": "所选分录已无剩余可入数量",
+  "billNo": "CGDD110260602003",
+  "lines": [],
+  "stockList": [],
+  "defaultStockNumber": "1100105"
+}
+```
+
+**页面显示:**
+
+- 顶部:`data.billNo`
+- **【新增】** 固定检查项:检验员默认 `inspectorName`,时间默认 `inspectTime`,结果选 `OK`/`NG`
+- 仓库下拉:`data.stockList`,默认 `data.defaultStockNumber`(选 OK 时必填)
+- 仓位:`data.stockLoc`(默认仓库);`required=true` 时必选仓位,切换仓库后调接口 4
+- 每行:`seq`、`materialNumber`、**`materialName`**、`remainQty`
+- 数量输入框:默认值 = `remainQty`,可改小,不能超过 `remainQty`
+- 批号:`batchManage=true` 时必填,默认 `suggestedLotNumber`,可点搜批号(接口 6)或手工输入
+
+**下一步:** 用户选 OK/NG →(OK 时选仓库/仓位)→ 填数量/批号/备注 → 调接口 8 提交
+
+---
+
+## 4. 仓位选项(选仓库后调用)
+
+**请求URL:** `/js/a/mes/erpInStock/pda/stockLocOptions`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| stockNumber | 是 | string | 仓库编码(来自 inspectInit 的 stockList) |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/stockLocOptions?stockNumber=1100105'
+```
+
+**返回示例:**
+
+成功(需选仓位):
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "required": true,
+    "defaultLocNumber": "A",
+    "locKey": "F100004",
+    "locHint": "共 12 个仓位,数据来自 ERP 仓位值集",
+    "locations": [
+      {
+        "locNumber": "A",
+        "locName": "A区",
+        "displayLabel": "A A区"
+      }
+    ]
+  }
+}
+```
+
+成功(不需选仓位):
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "required": false,
+    "defaultLocNumber": "",
+    "locHint": "该仓库未启用仓位或未配置仓位值集",
+    "locations": []
+  }
+}
+```
+
+**页面显示:** 仓位下拉/选择列表;`required=true` 时仓位为必填
+
+**下一步:** 选完仓位 → 继续填写数量/批号 → 接口 7 提交
+
+---
+
+## 5. 仓库列表(可选,inspectInit 已含可不调)
+
+**请求URL:** `/js/a/mes/erpInStock/stockList`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| 无 | — | — | — |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/stockList'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "defaultStockNumber": "1100105",
+    "stockHint": "共 5 个可用仓库",
+    "stockList": [
+      {
+        "stockNumber": "1100105",
+        "stockName": "五金库",
+        "displayLabel": "1100105 五金库"
+      }
+    ]
+  }
+}
+```
+
+---
+
+## 6. 批号搜索(可选)
+
+**请求URL:** `/js/a/mes/erpInStock/pda/lotSearch`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| materialNumber | 是 | string | 物料编码 |
+| lotKeyword | 否 | string | 批号关键字(模糊) |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/lotSearch?materialNumber=3.01.001&lotKeyword=LOT'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "lots": [
+      {
+        "lotNumber": "LOT202606001",
+        "qtyText": "0"
+      }
+    ],
+    "lotHint": "共查到 1 条批号档案"
+  }
+}
+```
+
+无结果(可手工录入新批号):
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "lots": [],
+    "lotHint": "未查到符合条件的 ERP 批号档案,可手工录入新批号"
+  }
+}
+```
+
+---
+
+## 7. 批号列表(可选,inspectInit 已含 availableLots 可不调)
+
+**请求URL:** `/js/a/mes/erpInStock/pda/lotOptions`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| materialNumber | 是 | string | 物料编码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/lotOptions?materialNumber=3.01.001'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "lots": [
+      {
+        "lotNumber": "LOT202606001",
+        "qtyText": "0"
+      }
+    ],
+    "suggestedLotNumber": "LOT202606001",
+    "lotHint": "共 3 条批号档案,已推荐第一条"
+  }
+}
+```
+
+---
+
+## 8. 提交来料检(【新增】OK/NG + 检验记录)
+
+> **破坏性变更:** 旧版可不传检验结果、直接入库;现版 **必须传 `checkResult`**,否则返回失败。
+
+**请求URL:** `/js/a/mes/erpInStock/submitInStock`
+
+**请求方式:** `POST`
+
+**Content-Type(二选一):**
+
+| 方式 | Content-Type | 说明 |
+|------|--------------|------|
+| PDA 推荐 | `application/x-www-form-urlencoded` | 表单参数,`lines` 传 JSON 数组字符串 |
+| 调试/脚本 | `application/json;charset=UTF-8` | 整包 JSON Body |
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 | 变更 |
+|--------|------|------|------|------|
+| billNo | 是 | string | 采购订单号 | 原有 |
+| checkResult | 是 | string | 检验结果:`OK` / `NG`(也可用 1/0、合格/不合格) | **【新增】必填** |
+| inspectorName | 否 | string | 检验员姓名,默认当前登录用户 | **【新增】** |
+| inspectorCode | 否 | string | 检验员账号,默认当前登录用户 | **【新增】** |
+| inspectTime | 否 | string | 检验时间,默认当前时间;格式 `yyyy-MM-dd HH:mm:ss` | **【新增】** |
+| remarks | 否 | string | 备注 | **【新增】** |
+| channel | 否 | string | `PC` / `PDA`,建议 PDA 传 `PDA` | **【新增】** |
+| stockNumber | 条件 | string | 入库仓库编码;**checkResult=OK 时必填**;NG 可不传 | 原有(规则收紧) |
+| stockLocNumber | 条件 | string | 入库仓位;`stockLoc.required=true` 且 OK 时必填 | 原有 |
+| stockLocKey | 条件 | string | 仓位维度键,与 `locations[].locKey` 一致 | 原有 |
+| entryIds | 条件 | string / array | 分录内码;与 lines 二选一或组合 | 原有 |
+| lines | 条件 | array / string | 明细 JSON 数组;表单方式时为 URL 编码后的 JSON 字符串 | 原有 |
+| lines[].entryId | 条件 | string | 可与顶层 entryIds 按顺序对应 | 原有 |
+| lines[].inspectQty | 条件 | number | 检验数量,缺省取 remainQty | 原有 |
+| lines[].lotNumber | 条件 | string | 批号管理物料必填 | 原有 |
+| lines[].materialName | 否 | string | 物料名称(展示用) | 原有 |
+
+**参数示例:**
+
+PDA 表单(OK 入库)**【新增 checkResult 等】**:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/submitInStock' \
+--header 'Content-Type: application/x-www-form-urlencoded' \
+--data-urlencode 'billNo=CGDD110260602003' \
+--data-urlencode 'checkResult=OK' \
+--data-urlencode 'inspectorName=张三' \
+--data-urlencode 'remarks=外观正常' \
+--data-urlencode 'channel=PDA' \
+--data-urlencode 'stockNumber=1100101' \
+--data-urlencode 'stockLocNumber=A11' \
+--data-urlencode 'lines=[{"entryId":"194383","inspectQty":1.0,"lotNumber":"11020260603"},{"entryId":"194384","inspectQty":1.0,"lotNumber":"20250101"}]'
+```
+
+JSON Body(NG 仅留痕)**【新增】**:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/submitInStock' \
+--header 'Content-Type: application/json;charset=UTF-8' \
+--data '{
+  "billNo": "CGDD110260602003",
+  "checkResult": "NG",
+  "inspectorName": "张三",
+  "remarks": "尺寸超差",
+  "channel": "PDA",
+  "entryIds": "123456",
+  "lines": [{"entryId":"123456","inspectQty":1.0,"lotNumber":"11020260603"}]
+}'
+```
+
+**返回示例:**
+
+OK 成功入库:
+
+```json
+{
+  "result": "true",
+  "message": "来料检合格,采购入库单保存成功,单号:CGRK110260624001",
+  "data": {
+    "inspectId": "xxx",
+    "checkResult": "OK",
+    "inStockCreated": "1",
+    "inStockBillNo": "CGRK110260624001"
+  }
+}
+```
+
+NG 仅保存检验记录:
+
+```json
+{
+  "result": "true",
+  "message": "来料检不合格,已保存检验记录,未生成采购入库单",
+  "data": {
+    "inspectId": "xxx",
+    "checkResult": "NG",
+    "inStockCreated": "0",
+    "inStockBillNo": ""
+  }
+}
+```
+
+失败(参数缺失示例)**【新增】**:
+
+```json
+{
+  "result": "false",
+  "message": "请选择来料检结果(OK/NG)"
+}
+```
+
+失败(仓位等,会记入 ERP 下推日志;若已写入检验记录会带 inspectId):
+
+```json
+{
+  "result": "false",
+  "message": "仓位维度【五金库货架仓位】不能为空",
+  "data": {
+    "inspectId": "xxx",
+    "checkResult": "OK",
+    "inStockCreated": "0"
+  }
+}
+```
+
+**返回 data 字段:**
+
+| 字段 | 类型 | 说明 | 变更 |
+|------|------|------|------|
+| inspectId | string | MES 来料检记录 id | **【新增】** |
+| checkResult | string | `OK` / `NG` | **【新增】** |
+| inStockCreated | string | `1` 已生成入库单 / `0` 未生成 | **【新增】** |
+| inStockBillNo | string | 采购入库单号 CGRK…;未生成时为空 | 原有(仍保留) |
+
+**页面显示:** 成功弹 `message`;用 `data.inStockCreated` 判断是否生成了入库单。
+
+**查记录:** PC 菜单「ERP来料检记录」;PDA 用接口 10。
+
+**下一步:** 完成;可返回订单列表、查检验记录(接口 10)或查入库历史(接口 9)
+
+---
+
+## 9. 采购入库历史(可选)
+
+> PDA 请用 **`pda/listData`**(`CommonResp`);PC 后台仍用 `listData`(直接返回 `Page`)。
+
+**请求URL:** `/js/a/mes/erpInStock/pda/listData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码 |
+| pageSize | 否 | int | 每页条数 |
+| poOrderNoQuery | 否 | string | 按采购订单号查 |
+| billNoQuery | 否 | string | 按入库单号 CGRK 查 |
+| materialNumberQuery | 否 | string | 物料编码模糊 |
+| documentStatusQuery | 否 | string | C=已审核 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/listData?pageNo=1&pageSize=20&poOrderNoQuery=CGDD110260602003'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "billNo": "CGRK110260624001",
+        "date": "2026-06-24",
+        "documentStatusName": "已审核",
+        "poOrderNo": "CGDD110260602003",
+        "supplierName": "某某供应商",
+        "materialNumber": "3.01.001",
+        "materialName": "某原材料",
+        "realQty": 800,
+        "unitName": "千克",
+        "lotNumber": "LOT202606001",
+        "stockName": "五金库"
+      }
+    ]
+  }
+}
+```
+
+失败:
+
+```json
+{
+  "result": "false",
+  "message": "查询失败原因",
+  "data": null
+}
+```
+
+---
+
+## 10. 来料检记录列表(【新增】整节)
+
+> 查询 MES 本地保存的来料检记录(含 OK/NG、检验员、时间、备注、是否已生成入库单)。  
+> 与接口 9「采购入库历史」不同:接口 9 查的是金蝶已生成的 CGRK;本接口查的是 MES 检验留痕(NG 也能查到)。
+
+**请求URL:** `/js/a/mes/mesErpInStockInspect/pda/listData`
+
+**请求方式:** `POST`
+
+**权限:** `mes:erpInStock:view`(与来料检相同,便于 PDA 复用)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 | 变更 |
+|--------|------|------|------|------|
+| pageNo | 否 | int | 页码,默认 1 | **【新增】接口** |
+| pageSize | 否 | int | 每页条数 | **【新增】接口** |
+| poBillNo | 否 | string | 采购订单号(模糊) | **【新增】接口** |
+| checkResult | 否 | string | `OK` / `NG` | **【新增】接口** |
+| inStockBillNo | 否 | string | 采购入库单号(模糊) | **【新增】接口** |
+| inspectorName | 否 | string | 检验员(模糊) | **【新增】接口** |
+| inspectTime_gte | 否 | datetime | 检验时间起 | **【新增】接口** |
+| inspectTime_lte | 否 | datetime | 检验时间止 | **【新增】接口** |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/mesErpInStockInspect/pda/listData?pageNo=1&pageSize=20&checkResult=NG&poBillNo=CGDD110260602003'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "id": "xxx",
+        "poBillNo": "CGDD110260602003",
+        "checkResult": "NG",
+        "checkResultLabel": "不合格",
+        "inspectorName": "张三",
+        "inspectTime": "2026-09-11 13:35:00",
+        "remarks": "尺寸超差",
+        "lineCount": 1,
+        "totalQty": 1.0,
+        "inStockStatus": "NONE",
+        "inStockStatusLabel": "未入库",
+        "inStockBillNo": "",
+        "inStockMessage": "来料检不合格,未生成采购入库单",
+        "channel": "PDA"
+      }
+    ]
+  }
+}
+```
+
+**list[] 主要字段:**
+
+| 字段 | 说明 | 变更 |
+|------|------|------|
+| id | 检验记录 id(= submit 返回的 inspectId) | **【新增】** |
+| poBillNo | 采购订单号 | **【新增】** |
+| checkResult | OK / NG | **【新增】** |
+| inspectorName / inspectTime / remarks | 检验员、时间、备注 | **【新增】** |
+| lineCount / totalQty | 行数、合计数量 | **【新增】** |
+| inStockStatus | `NONE` 未入库 / `SUCCESS` 已入库 / `FAIL` 入库失败 | **【新增】** |
+| inStockBillNo | 生成的 CGRK 单号 | **【新增】** |
+| channel | PC / PDA | **【新增】** |
+
+**PC 详情页:** `/js/a/mes/mesErpInStockInspect/detail?id=xxx`(含明细行:物料、数量、批号)
+
+---
+
+## 11. 查看入库单明细(可选)
+
+**请求URL:** `/js/a/mes/erpInStock/pda/detailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 采购入库单号 CGRK |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpInStock/pda/detailData?billNo=CGRK110260624001'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 2,
+    "list": [
+      {
+        "seq": "1",
+        "materialNumber": "3.01.001",
+        "materialName": "某原材料",
+        "realQty": 400,
+        "unitName": "千克",
+        "lotNumber": "LOT202606001",
+        "stockName": "五金库"
+      }
+    ]
+  }
+}
+```
+
+---
+
+## 附录:单据状态对照
+
+| 代码 | 含义 |
+|------|------|
+| A | 创建 |
+| B | 审核中 |
+| C | 已审核 |
+| D | 重新审核 |
+
+## 附录:常见失败 message
+
+| message | 原因 |
+|---------|------|
+| 请选择来料检结果(OK/NG) | **【新增】** 未传或非法 `checkResult` |
+| 所选分录已无剩余可入数量 | PO 行已入满,返回重新选单 |
+| 检验数量不能超过剩余可入数量 | inspectQty 大于 remainQty |
+| 启用批号管理,请从 ERP 选择或手工录入批号 | batchManage=true 但未传 lotNumber |
+| 请选择入库仓库 | checkResult=OK 但未传 stockNumber |
+| 所选仓库需要选择仓位 | 五金库等需仓位但未传 stockLocNumber |

+ 416 - 0
docs/erp/PDA生产入库接口说明.md

@@ -0,0 +1,416 @@
+# PDA 生产入库 — 接口说明
+
+> 模块:`ErpPrdInStockController`  
+> 权限:`mes:erpPrdInStock:view`(登录 MES 即可,**不要直连金蝶 ERP**)  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> 数量规则:**入库数量 = 所选 MES 批次码 `nums` 合计**,前端不传手工数量
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`);提交接口支持 **表单 POST** 与 **JSON Body** 两种方式。
+
+---
+
+## 页面流程(5 步)
+
+```text
+① pda/listData              生产入库单列表(模块首页)
+        ↓ 点「新增」
+② pda/selectOrderData       选生产订单(HEV/115 物料)
+        ↓ 点「选择」
+③ pda/selectDetailData      选订单分录(HEV/115 行)
+        ↓ 点「选批次入库」
+④ formInit                  勾选 MES 批次码 + 选仓库/仓位
+   └ batchScan               扫批次码校验(可选)
+   └ pda/stockLocOptions     选仓库后查仓位
+        ↓
+⑤ submit                    提交 ERP 生产入库(SCRK)
+```
+
+---
+
+## 1. 生产入库单列表(模块首页)
+
+**请求URL:** `/js/a/mes/erpPrdInStock/pda/listData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码,默认 1 |
+| pageSize | 否 | int | 每页条数,默认 20 |
+| billNoQuery | 否 | string | 入库单号 SCRK 模糊 |
+| moBillNoQuery | 否 | string | 生产订单号 |
+| materialNumberQuery | 否 | string | 物料编码模糊 |
+| documentStatusQuery | 否 | string | C=已审核 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/pda/listData?pageNo=1&pageSize=20'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "billNo": "SCRK110260605001",
+        "date": "2026-06-05",
+        "documentStatusName": "已审核",
+        "moBillNo": "SCDD110260605001",
+        "materialNumber": "8.10.HEV.10.047696",
+        "materialName": "下壳体",
+        "realQty": 150,
+        "unitName": "件",
+        "lotNumber": "110260605001",
+        "stockName": "五金库"
+      }
+    ]
+  }
+}
+```
+
+**下一步:** 点「新增」→ 接口 2;点某行 → 接口 9 查看明细
+
+---
+
+## 2. 选择生产订单
+
+**请求URL:** `/js/a/mes/erpPrdInStock/pda/selectOrderData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码 |
+| pageSize | 否 | int | 每页条数 |
+| billNo | 否 | string | 生产订单号(可扫码) |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/pda/selectOrderData?pageNo=1&pageSize=20'
+```
+
+**说明:** 仅含配置物料 `8.10.HEV.10.047696`、`8.35.115.10.5003146600021` 的订单
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 10,
+    "list": [
+      {
+        "billNo": "SCDD110260605001",
+        "date": "2026-06-01",
+        "documentStatusName": "已审核",
+        "inventoryCategory": "HEV",
+        "entryCount": 1,
+        "totalQty": 1500
+      }
+    ]
+  }
+}
+```
+
+**下一步:** 点某行 → 接口 3,传 `billNo`
+
+---
+
+## 3. 选择订单分录
+
+**请求URL:** `/js/a/mes/erpPrdInStock/pda/selectDetailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 生产订单号 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/pda/selectDetailData?billNo=SCDD110260605001'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "entryId": "266500",
+        "seq": "1",
+        "materialNumber": "8.10.HEV.10.047696",
+        "materialName": "下壳体",
+        "qty": 1500,
+        "instockQty": 264,
+        "remainQty": 1236,
+        "unitName": "件",
+        "workshopName": "HEV车间"
+      }
+    ]
+  }
+}
+```
+
+**下一步:** 选一行(`remainQty > 0`)→ 接口 4,传 `moBillNo` + `entryId`
+
+---
+
+## 4. 选批次入库页初始化
+
+**请求URL:** `/js/a/mes/erpPrdInStock/formInit`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| moBillNo | 是 | string | 生产订单号 |
+| entryId | 是 | string | 订单分录内码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/formInit?moBillNo=SCDD110260605001&entryId=266500'
+```
+
+**返回示例:**
+
+成功(有待入库批次):
+
+```json
+{
+  "result": "true",
+  "message": "",
+  "data": {
+    "moBillNo": "SCDD110260605001",
+    "entryId": "266500",
+    "moEntry": {
+      "seq": "1",
+      "materialNumber": "8.10.HEV.10.047696",
+      "qty": 1500,
+      "instockQty": 264,
+      "remainQty": 1236
+    },
+    "batchList": [
+      {
+        "id": "batch-id-001",
+        "batchSn": "20260527133003",
+        "lineSn": "HEVXT",
+        "nums": 50,
+        "instockStatus": "0"
+      }
+    ],
+    "defaultStockNumber": "1100105",
+    "stockList": [
+      { "stockNumber": "1100105", "stockName": "五金库", "displayLabel": "1100105 五金库" }
+    ]
+  }
+}
+```
+
+**下一步:** 勾选批次 → 选仓库/仓位 → 接口 8 提交
+
+---
+
+## 5. 扫批次码校验(可选)
+
+**请求URL:** `/js/a/mes/erpPrdInStock/batchScan`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| batchSn | 是 | string | MES 托盘批次码 |
+| materialNumber | 是 | string | 当前订单分录物料编码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/batchScan?batchSn=20260527133003&materialNumber=8.10.HEV.10.047696'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "可以入库",
+  "data": {
+    "batch": {
+      "id": "batch-id-001",
+      "batchSn": "20260527133003",
+      "lineSn": "HEVXT",
+      "nums": 50
+    }
+  }
+}
+```
+
+---
+
+## 6. 仓位选项(选仓库后)
+
+**请求URL:** `/js/a/mes/erpPrdInStock/pda/stockLocOptions`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| stockNumber | 是 | string | 仓库编码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/pda/stockLocOptions?stockNumber=1100105'
+```
+
+---
+
+## 7. 仓库列表(可选,formInit 已含可不调)
+
+**请求URL:** `/js/a/mes/erpPrdInStock/stockList`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:** 无
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/stockList'
+```
+
+---
+
+## 8. 提交生产入库
+
+**请求URL:** `/js/a/mes/erpPrdInStock/submit`
+
+**请求方式:** `POST`
+
+**Content-Type(二选一):**
+
+| 方式 | Content-Type | 说明 |
+|------|--------------|------|
+| PDA 推荐 | `application/x-www-form-urlencoded` | 表单参数,`batchIds` 传 JSON 数组字符串或逗号分隔 |
+| 调试/脚本 | `application/json;charset=UTF-8` | 整包 JSON Body |
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| moBillNo | 是 | string | 生产订单号 |
+| entryId | 是 | string | 订单分录内码 |
+| batchIds | 是 | array / string | `mes_batch_sn.id` **或批次码**。PDA 扫码后可直接传 `20260820150916`;也可用 `batchSns` / `batchSn` |
+| stockNumber | 是 | string | 入库仓库编码 |
+| stockLocNumber | 条件 | string | 仓库启用仓位时必填 |
+| stockLocKey | 否 | string | 仓位维度键,可选 |
+
+**参数示例:**
+
+PDA 表单:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/submit' \
+--header 'Content-Type: application/x-www-form-urlencoded' \
+--data-urlencode 'moBillNo=SCDD110260605001' \
+--data-urlencode 'entryId=266500' \
+--data-urlencode 'batchIds=20260820150916' \
+--data-urlencode 'stockNumber=1100105' \
+--data-urlencode 'stockLocNumber=AA'
+```
+
+JSON Body:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/submit' \
+--header 'Content-Type: application/json;charset=UTF-8' \
+--data '{"moBillNo":"SCDD110260605001","entryId":"266500","batchIds":["batch-id-001"],"stockNumber":"1100105","stockLocNumber":"AA"}'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "生产入库单保存成功,单号:SCRK110260605002;合计数量 100,绑定批次 1 个",
+  "data": {
+    "billNo": "SCRK110260605002",
+    "totalQty": 100,
+    "batchCount": 1,
+    "lotNumber": "110260605002"
+  }
+}
+```
+
+**说明:** 入库数量由后端按 `batchIds` 对应 `nums` 自动汇总,**不要传数量字段**
+
+---
+
+## 9. 查看生产入库单明细(可选)
+
+**请求URL:** `/js/a/mes/erpPrdInStock/pda/detailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 生产入库单号 SCRK |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPrdInStock/pda/detailData?billNo=SCRK110260605002'
+```
+
+---
+
+## 附录:数量字段说明
+
+| 字段 | 含义 |
+|------|------|
+| qty | 生产订单数量 |
+| instockQty | ERP 已审核生产入库实收合计 |
+| remainQty | 未入库 = qty − instockQty |
+| nums | MES 批次码已绑定工件数量 |
+| 本次入库数量 | Σ 所选批次 nums |
+
+## 附录:产线与物料
+
+| 产线 lineSn | 物料 |
+|-------------|------|
+| HEVXT | 8.10.HEV.10.047696 |
+| 115XT | 8.35.115.10.5003146600021 |

+ 439 - 0
docs/erp/PDA生产领料接口说明.md

@@ -0,0 +1,439 @@
+# PDA 生产领料 — 接口说明
+
+> 模块:`ErpPickMaterialBillController`(`/mes/erpPickMaterialBill`)  
+> 权限:`mes:erpPickMaterialBill:view`  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> **只登录 MES,不直连金蝶**
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`);列表类接口 `data` 为分页结构。提交接口支持 **表单 POST** 与 **JSON Body** 两种方式。
+
+---
+
+## 页面流程(5 步)
+
+```text
+① pda/listData           领料单列表
+        ↓ 新增
+② pda/selectOrderData    选生产订单(HEV/115 存货类别)
+        ↓
+③ pda/selectDetailData   选订单分录
+        ↓
+④ materialInit           用料清单 + 填领料数量/批号
+   └ pda/lotOptions      选批号(按行物料+仓库)
+   └ pda/lotSearch        搜批号
+        ↓
+⑤ submitPickMaterial     提交 ERP 领料单(SCLL)
+```
+
+---
+
+## 1. 领料单列表
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/pda/listData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码 |
+| pageSize | 否 | int | 每页条数 |
+| moBillNoQuery | 否 | string | 生产订单号 |
+| billNoQuery | 否 | string | 领料单号 SCLL |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/pda/listData?pageNo=1&pageSize=20'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "billNo": "SCLL110260603001",
+        "date": "2026-06-03",
+        "moBillNo": "SCDD110260603003",
+        "materialNumber": "3.02.xxx",
+        "materialName": "某原材料",
+        "actualQty": 100
+      }
+    ]
+  }
+}
+```
+
+**页面显示:** 领料单号、日期、生产订单、物料、实发数量
+
+**下一步:** 点「新增」→ 接口 2;点某行 → 接口 10 查看明细
+
+---
+
+## 2. 选择生产订单
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/pda/selectOrderData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码 |
+| pageSize | 否 | int | 每页条数 |
+| billNo | 否 | string | 生产订单号 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/pda/selectOrderData?pageNo=1&pageSize=20'
+```
+
+**说明:** 按存货类别 HEV / 欣旺达115 / 115 筛选(与 ERP 生产订单列表一致)
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 5,
+    "list": [
+      {
+        "billNo": "SCDD110260603003",
+        "documentStatusName": "已审核",
+        "inventoryCategory": "HEV",
+        "entryCount": 1,
+        "totalQty": 1200
+      }
+    ]
+  }
+}
+```
+
+**下一步:** 点选 → 接口 3,传 `billNo`
+
+---
+
+## 3. 选择订单分录
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/pda/selectDetailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 生产订单号 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/pda/selectDetailData?billNo=SCDD110260603003'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "entryId": "266268",
+        "seq": "1",
+        "materialNumber": "8.10.HEV.05.049025",
+        "materialName": "成品",
+        "qty": 1200,
+        "workshopName": "冲压壳体线"
+      }
+    ]
+  }
+}
+```
+
+**页面显示:** 行号、物料、订单数量、车间等
+
+**下一步:** 选一行 → 接口 4,传 `billNo` + `entryId`
+
+---
+
+## 4. 领料页初始化(用料清单)
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/materialInit`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 生产订单号 |
+| entryId | 是 | string | 订单分录内码 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/materialInit?billNo=SCDD110260603003&entryId=266268'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "",
+  "data": {
+    "billNo": "SCDD110260603003",
+    "entryId": "266268",
+    "materialName": "成品",
+    "moEntry": {
+      "seq": "1",
+      "materialNumber": "8.10.HEV.05.049025",
+      "materialName": "成品",
+      "qty": 1200
+    },
+    "materialList": [
+      {
+        "id": "250403",
+        "ppbomNo": "PPBOM00150358",
+        "materialNumber": "8.10.HEV.04.01001047696",
+        "materialName": "内胆",
+        "mustQty": 100,
+        "pickedQty": 20,
+        "noPickedQty": 80,
+        "canPickQty": 80,
+        "stockNumber": "1100101",
+        "stockName": "原材料仓",
+        "batchManage": true,
+        "lotPickable": true,
+        "pickStatusLabel": "可领料"
+      }
+    ]
+  }
+}
+```
+
+失败:
+
+```json
+{
+  "result": "false",
+  "message": "未找到生产订单分录",
+  "data": null
+}
+```
+
+**字段说明:**
+
+| 字段 | 含义 |
+|------|------|
+| `data.materialName` | 订单分录**成品**名称(页头,如「成品」) |
+| `materialList[].materialNumber` | **子项**物料编码 |
+| `materialList[].materialName` | **子项**物料名称(与 PC 用料清单「子项物料名称」列一致,如「内胆」) |
+
+**页面显示:** 列表每行用 `materialNumber` + `materialName` 展示子项;应发/已领/可领看 `mustQty`/`pickedQty`/`canPickQty`
+
+**下一步:** 填领料行 → 接口 5/6 查批号 → 接口 7 提交
+
+---
+
+## 5. 批号库存(按子项物料)
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/pda/lotOptions`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| materialNumber | 是 | string | 子项物料编码 |
+| stockNumber | 否 | string | 发料仓库(用料清单行带出) |
+| stockStatusNumber | 否 | string | 库存状态 |
+| stockOrgNumber | 否 | string | 库存组织,默认 110 |
+| pickQty | 否 | number | 本次领料数量(推荐批号用) |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/pda/lotOptions?materialNumber=3.02.xxx&stockNumber=1100101&pickQty=50'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "lots": [
+      { "lotNumber": "LOT001", "qtyText": "200" }
+    ],
+    "suggestedLotNumber": "LOT001",
+    "lotPickable": true,
+    "lotHint": "共 3 条可用批号"
+  }
+}
+```
+
+---
+
+## 6. 批号搜索
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/pda/lotSearch`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| materialNumber | 是 | string | 子项物料编码 |
+| stockNumber | 否 | string | 发料仓库 |
+| stockStatusNumber | 否 | string | 库存状态 |
+| stockOrgNumber | 否 | string | 库存组织 |
+| lotKeyword | 否 | string | 批号关键字 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/pda/lotSearch?materialNumber=3.02.xxx&stockNumber=1100101&lotKeyword=LOT'
+```
+
+---
+
+## 7. 提交领料
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/submitPickMaterial`
+
+**请求方式:** `POST`
+
+**Content-Type(二选一):**
+
+| 方式 | Content-Type | 说明 |
+|------|--------------|------|
+| PDA 推荐 | `application/x-www-form-urlencoded` | 表单参数,`lines` 传 JSON 数组字符串 |
+| 调试/脚本 | `application/json;charset=UTF-8` | 整包 JSON Body |
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 生产订单号 |
+| entryId | 是 | string | 订单分录内码 |
+| lines | 是 | array / string | 领料明细;表单方式时 `lines` 为 URL 编码后的 JSON 字符串 |
+| lines[].ppbomEntryId | 是 | string | 用料清单分录 id |
+| lines[].pickQty | 是 | number | 领料数量,≤ canPickQty |
+| lines[].lotNumber | 条件 | string | 批号管理物料必填 |
+
+**参数示例:**
+
+PDA 表单:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/submitPickMaterial' \
+--header 'Content-Type: application/x-www-form-urlencoded' \
+--data-urlencode 'billNo=SCDD110260603003' \
+--data-urlencode 'entryId=266268' \
+--data-urlencode 'lines=[{"ppbomEntryId":"250403","pickQty":50,"lotNumber":"LOT001"}]'
+```
+
+JSON Body:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/submitPickMaterial' \
+--header 'Content-Type: application/json;charset=UTF-8' \
+--data '{"billNo":"SCDD110260603003","entryId":"266268","lines":[{"ppbomEntryId":"250403","pickQty":50,"lotNumber":"LOT001"}]}'
+```
+
+**返回示例:**
+
+成功:
+
+```json
+{
+  "result": "true",
+  "message": "领料单保存成功,单号:SCLL110260603010",
+  "data": {
+    "pickBillNo": "SCLL110260603010",
+    "syncedKcCount": 1,
+    "syncedMaterialCount": 0,
+    "kcSyncFailed": false
+  }
+}
+```
+
+失败:
+
+```json
+{
+  "result": "false",
+  "message": "领料数量不能超过可领数量"
+}
+```
+
+---
+
+## 8. 查看领料单明细(可选)
+
+**请求URL:** `/js/a/mes/erpPickMaterialBill/pda/detailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 领料单号 SCLL |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpPickMaterialBill/pda/detailData?billNo=SCLL110260603010'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "seq": "1",
+        "materialNumber": "3.02.xxx",
+        "materialName": "子项物料",
+        "actualQty": 50,
+        "lotNumber": "LOT001",
+        "stockName": "原材料仓"
+      }
+    ]
+  }
+}
+```

+ 864 - 0
docs/erp/PDA直接调拨接口说明.md

@@ -0,0 +1,864 @@
+# PDA 直接调拨 — 接口说明
+
+> 模块:`ErpTransferDirectController`(`/mes/erpTransferDirect`)  
+> 权限:`mes:erpTransferDirect:view`  
+> 服务地址示例:`http://127.0.0.1:8980`  
+> **只登录 MES,不直连金蝶**;请求须带会话(Cookie `jeesite.session.id` 或 `__sid` + `__ajax=json`)。
+
+> PDA 统一返回 `CommonResp`(`result` / `message` / `data`)。  
+> 提交接口支持 **表单 POST** 与 **JSON Body** 两种方式。
+
+> 与 PC「增加直接调拨单」一致:**不关联生产订单**。点新增直接进入填单页;调拨数量通过扫描批次码累加;扫码时校验是否已发货检合格。
+
+---
+
+## 统一响应格式
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| result | string | `"true"` 业务成功 / `"false"` 业务失败 |
+| message | string | 提示信息。成功时可空或为「成功」;失败时为原因,PDA 直接展示 |
+| data | object / null | 业务数据。失败时也可能带部分字段(如扫码失败仍返回批次状态) |
+
+列表类接口的 `data` 为分页结构:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| data.pageNo | int | 当前页码 |
+| data.pageSize | int | 每页条数 |
+| data.count | long | 总记录数(ERP 游标式分页,末页可能为估算值) |
+| data.list | array | 当前页数据行 |
+
+---
+
+## 页面流程
+
+```text
+① pda/listData                         调拨单列表(模块首页)
+        ↓ 点「新增」(无需选生产订单)
+② formInit                             物料、单位、仓库列表、默认仓
+        ↓ 选调出仓库 / 调入仓库后
+③ erpStockPda/stockLocOptions          查该仓库仓位(调出、调入各调一次)
+   或 erpTransferDirect/pda/stockLocOptions
+        ↓ 选物料 + 调出仓库后
+④ pda/lotOptions                       查 ERP 即时库存可用批号
+        ↓ 扫批次码(可多次,累加数量)
+⑤ batchScan                            校验已发货检,qty += qtyIncrement
+        ↓ 填完仓库仓位、批号后
+⑥ submit                               提交;传 traySns(托盘码逗号拼接)
+```
+
+仓库列表:`formInit` 已返回 `stockList`,一般不必再调。若要单独刷新仓库,用接口 3。
+
+**发货检规则:**
+
+| 批次状态 `batchStatus` | 含义 | 可否扫码累加调拨数量 |
+|------------------------|------|----------------------|
+| `3` | 待装中(尚未生产入库) | 否 |
+| `0` | 仓库中(含发货检 NG) | 否 |
+| `1` | 合格(发货检 OK) | 是 |
+| `2` | 已发货 | 否 |
+
+---
+
+## 1. 调拨单列表
+
+模块首页。点某行可看明细(接口 8);点「新增」调接口 2。
+
+**请求URL:** `/js/a/mes/erpTransferDirect/pda/listData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| pageNo | 否 | int | 页码,默认 1 |
+| pageSize | 否 | int | 每页条数,默认 20 |
+| billNoQuery | 否 | string | 调拨单号模糊查询 |
+| materialNumberQuery | 否 | string | 物料编码。不传则 HEV+115;可传 `8.10.HEV.10.047696` 或 `8.35.115.10.5003146600021` |
+| documentStatusQuery | 否 | string | 单据状态:`A` 创建 / `B` 审核中 / `C` 已审核 / `D` 重新审核 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/pda/listData?pageNo=1&pageSize=20'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "billNo": "ZJDB110260605001",
+        "date": "2026-06-05",
+        "documentStatus": "C",
+        "documentStatusName": "已审核",
+        "billTypeName": "直接调拨",
+        "seq": "1",
+        "materialNumber": "8.10.HEV.10.047696",
+        "materialName": "下壳体",
+        "qty": 100,
+        "unitName": "个",
+        "srcStockNumber": "1100105",
+        "srcStockName": "五金库",
+        "destStockNumber": "1100217",
+        "destStockName": "成品仓",
+        "lotNumber": "110260605001"
+      }
+    ]
+  }
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` / `"false"` |
+| message | string | 提示信息 |
+| data.pageNo | int | 当前页码 |
+| data.pageSize | int | 每页条数 |
+| data.count | long | 总记录数(游标估算) |
+| data.list[] | array | 调拨明细分录(一行一条) |
+| data.list[].billNo | string | 直接调拨单号(ZJDB),点行看明细时回传 |
+| data.list[].date | string | 调拨日期,`yyyy-MM-dd` |
+| data.list[].documentStatus | string | 单据状态码:`A` 创建 / `B` 审核中 / `C` 已审核 / `D` 重新审核 |
+| data.list[].documentStatusName | string | 单据状态中文 |
+| data.list[].billTypeNumber | string | 单据类型编码 |
+| data.list[].billTypeName | string | 单据类型名称 |
+| data.list[].stockOrgNumber | string | 调入库存组织编码 |
+| data.list[].stockOrgName | string | 调入库存组织名称 |
+| data.list[].seq | string | 分录行号 |
+| data.list[].materialNumber | string | 物料编码 |
+| data.list[].materialName | string | 物料名称 |
+| data.list[].qty | number | 调拨数量 |
+| data.list[].unitName | string | 单位名称 |
+| data.list[].srcStockNumber | string | 调出仓库编码 |
+| data.list[].srcStockName | string | 调出仓库名称 |
+| data.list[].destStockNumber | string | 调入仓库编码 |
+| data.list[].destStockName | string | 调入仓库名称 |
+| data.list[].lotNumber | string | ERP 批号 |
+| data.list[].note | string | 备注,可空 |
+| data.list[].entryId | string | 分录内码 |
+| data.list[].id / fid | string | ERP 单据内码,一般不展示 |
+
+**下一步:** 点「新增」→ 接口 2;点某行 → 接口 8,传 `billNo`
+
+---
+
+## 2. 调拨页初始化
+
+点「新增」后调用。**无请求参数**,不传生产订单号。一次返回可调拨物料(含单位)、仓库列表、默认调出/调入仓。
+
+**请求URL:** `/js/a/mes/erpTransferDirect/formInit`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:** 无
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/formInit'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "transferCategory": "3",
+    "transferCategoryName": "外发调拨",
+    "scanHint": "调拨数量通过扫描批次码累加,扫码前须已完成发货检",
+    "materialList": [
+      {
+        "materialNumber": "8.10.HEV.10.047696",
+        "materialName": "HEV",
+        "displayLabel": "8.10.HEV.10.047696(HEV)",
+        "defaultUnitNumber": "Pcs",
+        "units": [
+          { "unitNumber": "Pcs", "unitName": "个", "displayLabel": "个" }
+        ]
+      },
+      {
+        "materialNumber": "8.35.115.10.5003146600021",
+        "materialName": "115",
+        "displayLabel": "8.35.115.10.5003146600021(115)",
+        "defaultUnitNumber": "Pcs",
+        "units": [
+          { "unitNumber": "Pcs", "unitName": "个", "displayLabel": "个" }
+        ]
+      }
+    ],
+    "defaultSrcStockNumber": "1100105",
+    "defaultDestStockNumber": "",
+    "stockList": [
+      {
+        "stockNumber": "1100105",
+        "stockName": "五金库",
+        "displayLabel": "1100105 五金库"
+      }
+    ]
+  }
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` / `"false"` |
+| message | string | 提示信息 |
+| data.transferCategory | string | 调拨类别编码,当前固定 `3`(外发调拨),展示用,提交不必回传 |
+| data.transferCategoryName | string | 调拨类别中文,固定「外发调拨」,页面标题/标签用 |
+| data.scanHint | string | 扫码说明文案,可展示在数量输入旁 |
+| data.defaultSrcStockNumber | string | 推荐默认**调出**仓库编码。进入页面时预选该仓,再调接口 4 查仓位 |
+| data.defaultDestStockNumber | string | 推荐默认**调入**仓库编码。空字符串表示未配置,须用户自选。不能与调出仓相同 |
+| data.materialList | array | 可调拨物料,目前 HEV、115 两条。下拉数据源 |
+| data.materialList[].materialNumber | string | 物料编码。提交 `materialNumber`、扫码 `materialNumber` 用此值 |
+| data.materialList[].materialName | string | 简称:`HEV` 或 `115` |
+| data.materialList[].displayLabel | string | 下拉展示文本,如 `8.10.HEV.10.047696(HEV)` |
+| data.materialList[].defaultUnitNumber | string | 该物料默认单位编码。提交 `unitNumber` 可直接用此值;无单位时可能缺此字段 |
+| data.materialList[].units | array | 该物料计量单位列表(来自 ERP) |
+| data.materialList[].units[].unitNumber | string | 单位编码,提交 `unitNumber` 用此值 |
+| data.materialList[].units[].unitName | string | 单位名称,如「个」 |
+| data.materialList[].units[].displayLabel | string | 单位下拉展示文本,优先名称 |
+| data.stockList | array | 可用仓库列表。调出仓、调入仓共用同一份,用户各选一次 |
+| data.stockList[].stockNumber | string | 仓库编码。提交 `srcStockNumber` / `destStockNumber`,以及查仓位的 `stockNumber` |
+| data.stockList[].stockName | string | 仓库名称 |
+| data.stockList[].displayLabel | string | 仓库下拉展示文本,一般为「编码 + 空格 + 名称」 |
+
+**页面用法:**
+
+1. 调拨类别只展示 `transferCategoryName`,不可改。
+2. 物料下拉绑定 `materialList`,选中后记录 `materialNumber`、`defaultUnitNumber`。
+3. 调出仓库、调入仓库都绑定 `stockList`;调出仓预选 `defaultSrcStockNumber`,调入仓预选 `defaultDestStockNumber`(有值时)。
+4. **选定(或切换)调出仓库后**,用该仓 `stockNumber` 调接口 4,加载调出仓位。
+5. **选定(或切换)调入仓库后**,再调一次接口 4,加载调入仓位。
+6. **选定物料和调出仓库后**,调接口 5 加载可用批号,选中后作为提交 `lotNumber`。
+7. 数量不要手填初始值,用接口 6 扫码累加。
+
+**下一步:** 选仓库 → 接口 4;选物料+调出仓 → 接口 5;扫批次码 → 接口 6;填完 → 接口 7
+
+---
+
+## 3. 仓库列表(可选)
+
+`formInit` 已含 `stockList`,一般不用再调。需要单独刷新仓库时用本接口。
+
+**推荐(来料检 / 生产入库 / 直接调拨共用):**
+
+**请求URL:** `/js/a/mes/erpStockPda/stockList`
+
+**本模块兼容别名:** `/js/a/mes/erpTransferDirect/stockList`(调拨多返回默认调入仓)
+
+**请求方式:** `POST`(也兼容 GET)
+
+**权限:** `mes:erpTransferDirect:view`(公共接口也可用来料检或生产入库的 view)
+
+**参数:** 无
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpStockPda/stockList'
+```
+
+或本模块:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/stockList'
+```
+
+**返回示例(本模块 `erpTransferDirect/stockList`):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "defaultStockNumber": "1100105",
+    "defaultSrcStockNumber": "1100105",
+    "defaultDestStockNumber": "",
+    "stockHint": "共 5 个可用仓库",
+    "stockList": [
+      {
+        "stockNumber": "1100105",
+        "stockName": "五金库",
+        "displayLabel": "1100105 五金库"
+      }
+    ]
+  }
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` / `"false"` |
+| message | string | 提示信息 |
+| data.stockList | array | 可用仓库,字段同接口 2 的 `stockList[]` |
+| data.stockList[].stockNumber | string | 仓库编码 |
+| data.stockList[].stockName | string | 仓库名称 |
+| data.stockList[].displayLabel | string | 下拉展示文本 |
+| data.defaultStockNumber | string | 通用默认仓库编码(与默认调出仓相同) |
+| data.defaultSrcStockNumber | string | 推荐调出仓库编码(仅本模块 `erpTransferDirect/stockList` 有) |
+| data.defaultDestStockNumber | string | 推荐调入仓库编码(仅本模块有;空表示未配置) |
+| data.stockHint | string | 加载提示,如「共 5 个可用仓库」;失败时提示检查 ERP 连接 |
+
+公共接口 `erpStockPda/stockList` **没有** `defaultSrcStockNumber` / `defaultDestStockNumber`,只有 `defaultStockNumber`、`stockHint`、`stockList`。
+
+**PDA 处理:** 用 `stockList` 刷新调出/调入仓库下拉。切换仓库后必须重新调接口 4。
+
+---
+
+## 4. 仓位选项(选仓库后必调)
+
+用户选定或切换**调出仓库**、**调入仓库**后,用对应 `stockNumber` 各调一次。  
+`formInit` **不含**仓位列表。
+
+**推荐(共用):**
+
+**请求URL:** `/js/a/mes/erpStockPda/stockLocOptions`
+
+**本模块兼容别名:**
+
+- `/js/a/mes/erpTransferDirect/pda/stockLocOptions`
+- `/js/a/mes/erpTransferDirect/stockLocOptions`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| stockNumber | 是 | string | 仓库编码,来自 `stockList[].stockNumber` |
+
+**参数示例:**
+
+调出仓:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpStockPda/stockLocOptions?stockNumber=1100105'
+```
+
+调入仓:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpStockPda/stockLocOptions?stockNumber=1100217'
+```
+
+**返回示例(仓库启用仓位):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "stockNumber": "1100105",
+    "required": true,
+    "defaultLocNumber": "A",
+    "locKey": "F100004",
+    "locKeys": ["F100004"],
+    "locHint": "共 278 个仓位,数据来自 ERP 仓位值集",
+    "locations": [
+      {
+        "locNumber": "A",
+        "locName": "A",
+        "locKey": "F100004",
+        "locEntryId": "123",
+        "displayLabel": "A"
+      }
+    ]
+  }
+}
+```
+
+**返回示例(仓库未启用仓位):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "stockNumber": "1100217",
+    "required": false,
+    "defaultLocNumber": "",
+    "locHint": "该仓库未启用仓位或未配置仓位值集",
+    "locations": []
+  }
+}
+```
+
+**失败示例:**
+
+```json
+{
+  "result": "false",
+  "message": "仓库编码不能为空",
+  "data": null
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` / `"false"` |
+| message | string | 成功为「成功」;未传仓库为「仓库编码不能为空」 |
+| data.stockNumber | string | 本次查询的仓库编码,与请求一致 |
+| data.required | boolean | **是否必须选仓位**。`true`:下拉必选,提交必须带仓位编码;`false`:可不选,提交不传仓位 |
+| data.defaultLocNumber | string | 推荐默认仓位编码,进入页/切仓后可预选;无则空 |
+| data.locHint | string | 提示文案,可显示在仓位下拉下方 |
+| data.locKey | string | 该仓库主仓位维度键(如 `F100004`),**仅展示/日志用**。提交可不传,MES 会按仓库+仓位编码自行解析 |
+| data.locKeys | array | 该仓库全部维度键。多维度仓库才有;可忽略 |
+| data.locations | array | 仓位下拉数据源。`required=false` 时为空数组 |
+| data.locations[].locNumber | string | 仓位编码。提交调出仓用 `srcStockLocNumber`,调入仓用 `destStockLocNumber` |
+| data.locations[].locName | string | 仓位名称 |
+| data.locations[].displayLabel | string | 下拉展示文本 |
+| data.locations[].locKey | string | 该仓位维度键,展示用,提交可不回传 |
+| data.locations[].locEntryId | string | 仓位值集分录内码,PDA 不用 |
+
+**PDA 处理:**
+
+| 步骤 | 说明 |
+|------|------|
+| 1 | 选中调出仓库 `srcStockNumber` 后,请求本接口,结果赋给**调出仓位**下拉 |
+| 2 | 选中调入仓库 `destStockNumber` 后,再请求一次,结果赋给**调入仓位**下拉 |
+| 3 | `required=true` 时仓位必选,默认选 `defaultLocNumber` |
+| 4 | `required=false` 时仓位可隐藏或显示「无库位」,提交不传对应 Loc 字段 |
+| 5 | 切换仓库必须重新调用,不要沿用上一仓库的仓位列表 |
+| 6 | **不要写死 `locKey`**。提交时 `srcStockLocKey` / `destStockLocKey` 可选 |
+
+**提交对应关系:**
+
+| 页面 | 仓库字段 | 仓位字段 | 仓位来源 |
+|------|----------|----------|----------|
+| 调出 | `srcStockNumber` | `srcStockLocNumber` | 调出仓这次 `locations[].locNumber` |
+| 调入 | `destStockNumber` | `destStockLocNumber` | 调入仓这次 `locations[].locNumber` |
+
+---
+
+## 5. 选批号(选物料和调出仓库后必调)
+
+用户选定 **物料** 和 **调出仓库** 后调用。查 ERP 即时库存中该物料在该仓的可用批号,供下拉选择。  
+切换物料或调出仓库后须重新调用。
+
+**请求URL:** `/js/a/mes/erpTransferDirect/pda/lotOptions`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| materialNumber | 是 | string | 物料编码,来自 `materialList[].materialNumber` |
+| stockNumber | 是 | string | **调出仓库**编码,来自 `stockList[].stockNumber` |
+| stockName | 否 | string | 仓库名称,仅用于缺批号时的提示文案 |
+| lotKeyword | 否 | string | 批号关键字。传入则按关键字搜索,不传则返回该仓全部可用批号 |
+| pickQty | 否 | number | 计划调拨数量,用于推荐 `suggestedLotNumber` |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/pda/lotOptions?materialNumber=8.10.HEV.10.047696&stockNumber=1100105'
+```
+
+带关键字搜索:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/pda/lotOptions?materialNumber=8.10.HEV.10.047696&stockNumber=1100105&lotKeyword=11026'
+```
+
+**返回示例(有可用批号):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "materialNumber": "8.10.HEV.10.047696",
+    "stockNumber": "1100105",
+    "lots": [
+      {
+        "lotNumber": "110260605001",
+        "qty": 100,
+        "qtyText": "100",
+        "stockNumber": "1100105",
+        "stockName": "五金库"
+      }
+    ],
+    "suggestedLotNumber": "110260605001",
+    "lotPickable": true,
+    "lotHint": "共 1 条可用批号"
+  }
+}
+```
+
+**返回示例(无批号):**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "materialNumber": "8.10.HEV.10.047696",
+    "stockNumber": "1100105",
+    "lots": [],
+    "suggestedLotNumber": "",
+    "lotPickable": false,
+    "lotHint": "未查到符合条件的批号库存"
+  }
+}
+```
+
+**失败示例:**
+
+```json
+{
+  "result": "false",
+  "message": "请先选择调出仓库",
+  "data": null
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` / `"false"`。未传物料或调出仓为 `"false"` |
+| message | string | 成功为「成功」;失败为「请先选择物料编码」或「请先选择调出仓库」 |
+| data.materialNumber | string | 本次查询的物料编码 |
+| data.stockNumber | string | 本次查询的调出仓库编码 |
+| data.lots | array | 可用批号列表,绑定下拉 |
+| data.lots[].lotNumber | string | ERP 批号。提交 `lotNumber` 用此值 |
+| data.lots[].qty | number | 该批号在该仓可用库存 |
+| data.lots[].qtyText | string | 可用库存展示文本 |
+| data.lots[].stockNumber | string | 仓库编码 |
+| data.lots[].stockName | string | 仓库名称 |
+| data.suggestedLotNumber | string | 推荐批号。有 `pickQty` 时优先选够量的最小批号;可预选 |
+| data.lotPickable | boolean | 是否有可选批号。`false` 时下拉为空,展示 `lotHint` |
+| data.lotHint | string | 提示文案,显示在批号下拉下方 |
+
+**PDA 处理:**
+
+1. 物料、调出仓库都选好后再调,缺一不可。
+2. 下拉绑定 `lots[]`,展示 `lotNumber` + `qtyText`。
+3. 有 `suggestedLotNumber` 时可预选。
+4. 选中后记录 `lotNumber`,提交时回传。
+5. 切换物料或调出仓库必须重新调用,并清空已选批号。
+
+**失败 message:**
+
+| message | 原因 |
+|---------|------|
+| 请先选择物料编码 | 未传 `materialNumber` |
+| 请先选择调出仓库 | 未传 `stockNumber` |
+
+---
+
+## 6. 扫批次码(累加数量 + 发货检校验)
+
+每扫一次调一次。成功则 `qty = qty + data.qtyIncrement`。  
+同一张调拨单应扫**同一物料、同一 ERP 批号**;可用返回的 `materialNumber` / `lotNumber` 自动带出。
+
+**请求URL:** `/js/a/mes/erpTransferDirect/batchScan`
+
+**请求方式:** `POST`(也兼容 GET)
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| batchSn | 是 | string | 批次码(扫码值) |
+| materialNumber | 否 | string | 当前已选物料编码。传入时须与该批次产线物料一致,用于防止 HEV/115 扫串 |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/batchScan?batchSn=20260804084909&materialNumber=8.10.HEV.10.047696'
+```
+
+**返回示例(成功,已发货检):**
+
+```json
+{
+  "result": "true",
+  "message": "扫码成功,可累加数量 2",
+  "data": {
+    "batchSn": "20260804084909",
+    "nums": 2,
+    "qtyIncrement": 2,
+    "lineSn": "HEVXT",
+    "materialNumber": "8.10.HEV.10.047696",
+    "materialName": "HEV",
+    "lotNumber": "110260605001",
+    "batchStatus": "1",
+    "batchStatusName": "合格",
+    "inspected": true
+  }
+}
+```
+
+**返回示例(失败,尚未发货检):**
+
+```json
+{
+  "result": "false",
+  "message": "批次码 20260804084909 尚未发货检,不能调拨",
+  "data": {
+    "batchSn": "20260804084909",
+    "nums": 2,
+    "qtyIncrement": 2,
+    "lineSn": "HEVXT",
+    "materialNumber": "8.10.HEV.10.047696",
+    "materialName": "HEV",
+    "lotNumber": "",
+    "batchStatus": "0",
+    "batchStatusName": "仓库中",
+    "inspected": false
+  }
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` 允许累加数量 / `"false"` 不允许 |
+| message | string | 成功含累加数量;失败为原因,PDA 弹窗展示 |
+| data.batchSn | string | 本次扫到的批次码 |
+| data.nums | long | 该批次已绑定工件数量 |
+| data.qtyIncrement | long | **本次应累加的调拨数量**,与 `nums` 相同。`qty += qtyIncrement` |
+| data.lineSn | string | 产线编号,如 `HEVXT`、`115XT` |
+| data.materialNumber | string | 按产线解析的物料编码。第一枪可用来自动选中物料 |
+| data.materialName | string | `HEV` 或 `115` |
+| data.lotNumber | string | 生产入库回写的 ERP 批号。有值时自动填提交用的 `lotNumber`;可能为空 |
+| data.batchStatus | string | 批次状态:`3` 待装中 / `0` 仓库中 / `1` 合格 / `2` 已发货 |
+| data.batchStatusName | string | 状态中文(字典 `mes_batch_sn_status`) |
+| data.inspected | boolean | 是否已发货检合格。仅 `true` 时 `result` 才为 `"true"` |
+
+**PDA 处理:**
+
+1. `result != "true"`:提示 `message`,数量不加。
+2. `result == "true"`:记录该 `batchSn`(提交 `traySns` 用逗号拼接),`qty += qtyIncrement`。
+3. 若页面还没选物料,用返回的 `materialNumber` 选中。
+4. 若 `lotNumber` 非空且页面批号为空,填入批号。
+5. 再扫时若物料或批号与已选不一致,接口或本地都应拒绝。
+6. 同一批次码不要重复累加。
+
+**失败 message:**
+
+| message | 原因 |
+|---------|------|
+| 批次码不能为空 | 未传 `batchSn` |
+| 未找到该 MES 批次码 | 批次码无效 |
+| 尚未生产入库,不能调拨 | `batchStatus=3` 待装中 |
+| 尚未发货检,不能调拨 | `batchStatus` 不是 `1`(含仓库中、发货检 NG) |
+| 已发货,不能调拨 | `batchStatus=2` |
+| 绑定数量为 0,不能调拨 | `nums<=0` |
+| 与当前选择物料不一致 | 传入的 `materialNumber` 与批次产线物料不符 |
+
+---
+
+## 7. 提交调拨
+
+**请求URL:** `/js/a/mes/erpTransferDirect/submit`
+
+**请求方式:** `POST`
+
+**Content-Type(二选一):**
+
+| 方式 | Content-Type | 说明 |
+|------|--------------|------|
+| PDA 推荐 | `application/x-www-form-urlencoded` | 表单参数 |
+| 调试/脚本 | `application/json;charset=UTF-8` | 整包 JSON Body |
+
+**请求参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| materialNumber | 是 | string | 物料编码,来自 `materialList[].materialNumber` 或扫码返回值 |
+| lotNumber | 是 | string | ERP 批号,优先用扫码返回的 `lotNumber` |
+| qty | 是 | number | 调拨数量 = 各次扫码 `qtyIncrement` 合计,且不超过该批号在调出仓可用量 |
+| unitNumber | 否 | string | 计量单位编码,来自 `defaultUnitNumber` 或 `units[].unitNumber`。不传则后台取物料默认单位 |
+| srcStockNumber | 是 | string | 调出仓库编码 |
+| srcStockLocNumber | 条件 | string | 调出仓位编码。调出仓 `required=true` 时必填 |
+| srcStockLocKey | 否 | string | 调出仓位维度键,可选,不传由后台解析 |
+| destStockNumber | 是 | string | 调入仓库编码,不能与调出仓相同 |
+| destStockLocNumber | 条件 | string | 调入仓位编码。调入仓 `required=true` 时必填 |
+| destStockLocKey | 否 | string | 调入仓位维度键,可选 |
+| traySns | 是 | string | **托盘码(MES 批次码)逗号拼接**,如 `20260804084909,20260804085001`。提交成功后将这些托盘改为已发货(`batchStatus=2`),并绑定本次调拨单号 |
+| batchSns | 否 | string / array | 兼容旧参数,与 `traySns` 合并去重。表单逗号分隔,JSON 可用数组 |
+
+> 不再传 `moBillNo` / `instockBillNo` / `instockEntryId`。  
+> 提交成功后:ERP 生成直接调拨单;同时把 `traySns` 中每个托盘的 `batch_status` 改为 `2`(已发货),`erp_transfer_bill_no` 写入本次 `billNo`。
+
+**参数示例:**
+
+PDA 表单:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/submit' \
+--header 'Content-Type: application/x-www-form-urlencoded' \
+--data-urlencode 'materialNumber=8.10.HEV.10.047696' \
+--data-urlencode 'lotNumber=110260605001' \
+--data-urlencode 'qty=2' \
+--data-urlencode 'unitNumber=Pcs' \
+--data-urlencode 'srcStockNumber=1100105' \
+--data-urlencode 'srcStockLocNumber=A' \
+--data-urlencode 'destStockNumber=1100217' \
+--data-urlencode 'traySns=20260804084909,20260804085001'
+```
+
+JSON Body:
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/submit' \
+--header 'Content-Type: application/json;charset=UTF-8' \
+--data '{"materialNumber":"8.10.HEV.10.047696","lotNumber":"110260605001","qty":2,"unitNumber":"Pcs","srcStockNumber":"1100105","srcStockLocNumber":"A","destStockNumber":"1100217","traySns":"20260804084909,20260804085001"}'
+```
+
+**返回示例(成功):**
+
+```json
+{
+  "result": "true",
+  "message": "直接调拨单保存成功,单号:ZJDB110260605001",
+  "data": {
+    "billNo": "ZJDB110260605001"
+  }
+}
+```
+
+**返回示例(失败):**
+
+```json
+{
+  "result": "false",
+  "message": "批次码 20260804084909 尚未发货检,不能调拨",
+  "data": null
+}
+```
+
+**返回字段说明:**
+
+| 字段路径 | 类型 | 说明 |
+|----------|------|------|
+| result | string | `"true"` 保存成功 / `"false"` 失败 |
+| message | string | 成功含 ERP 单号;失败为原因,PDA 展示 |
+| data | object | 失败时一般为 `null` |
+| data.billNo | string | 成功时 ERP 直接调拨单号(ZJDB) |
+
+**常见失败 message:**
+
+| message | 原因 |
+|---------|------|
+| 请选择物料编码 | 未传 `materialNumber` |
+| 物料 xxx 不在 HEV/115 调拨范围内 | 物料不在配置清单 |
+| 请选择批号 | 未传 `lotNumber` |
+| 调拨数量必须大于 0 | `qty` 空或 ≤0 |
+| 请选择调出仓库 / 请选择调入仓库 | 仓库未选 |
+| 调入仓库不能与调出仓库相同 | 两仓相同 |
+| 所选批号在调出仓库无可用库存 | ERP 即时库存无该批号 |
+| 调拨数量不能超过批号可用库存 n | `qty` 大于可用量 |
+| 调出仓库:… / 调入仓库:… | 仓位校验失败(启用仓位却未选等) |
+| 请选择计量单位 | 未传单位且 ERP 也未加载到默认单位 |
+| 尚未发货检 / 已发货 / 物料不一致 | 传入了 `traySns` 且复核未通过 |
+
+**传 `traySns` 时额外规则:**
+
+- 每条托盘码须已发货检合格(`batchStatus=1`)
+- 各托盘物料、ERP 批号须一致
+- 若未传 `materialNumber` / `lotNumber`,用扫码结果回填
+- 若未传 `qty`,用各托盘 `nums` 合计
+- **ERP 保存成功后**:托盘状态改为已发货(`2`),并写入调拨单号 `erp_transfer_bill_no`
+
+---
+
+## 8. 查看调拨单明细
+
+列表点某行调用。
+
+**请求URL:** `/js/a/mes/erpTransferDirect/pda/detailData`
+
+**请求方式:** `POST`
+
+**参数:**
+
+| 参数名 | 必选 | 类型 | 说明 |
+|--------|------|------|------|
+| billNo | 是 | string | 直接调拨单号 ZJDB,来自列表 `list[].billNo` |
+
+**参数示例:**
+
+```bash
+curl --location --request POST 'http://127.0.0.1:8980/js/a/mes/erpTransferDirect/pda/detailData?billNo=ZJDB110260605001'
+```
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": {
+    "pageNo": 1,
+    "pageSize": 20,
+    "count": 1,
+    "list": [
+      {
+        "seq": "1",
+        "materialNumber": "8.10.HEV.10.047696",
+        "materialName": "下壳体",
+        "qty": 100,
+        "unitName": "个",
+        "srcStockNumber": "1100105",
+        "srcStockName": "五金库",
+        "destStockNumber": "1100217",
+        "destStockName": "成品仓",
+        "lotNumber": "110260605001",
+        "documentStatusName": "已审核",
+        "date": "2026-06-05"
+      }
+    ]
+  }
+}
+```
+
+**返回字段说明:** 分页字段同接口 1;`list[]` 字段与接口 1 的 `list[]` 相同(该单全部分录)。展示建议:行号、物料、数量、单位、调出仓、调入仓、批号。
+
+---
+
+## 仓库 / 仓位选用小结
+
+| 用途 | 推荐 URL | 何时调用 |
+|------|----------|----------|
+| 物料 + 仓库(进新增页) | `POST /js/a/mes/erpTransferDirect/formInit` | 点新增一次 |
+| 只刷新仓库 | `POST /js/a/mes/erpStockPda/stockList` | 可选 |
+| 查仓位 | `POST /js/a/mes/erpStockPda/stockLocOptions?stockNumber=` | 调出仓、调入仓各一次;切换仓库再调 |
+| 选批号 | `POST /js/a/mes/erpTransferDirect/pda/lotOptions` | 选物料 + 调出仓库后;切换后再调 |
+| 扫码加数量 | `POST /js/a/mes/erpTransferDirect/batchScan` | 每扫一枪 |
+| 提交 | `POST /js/a/mes/erpTransferDirect/submit` | 填完提交,`traySns` 逗号拼接托盘码 |
+
+仓位规则:`data.required=true` 必须选仓位并提交对应 `*StockLocNumber`;`false` 不传仓位。不要写死 `locKey`。
+
+---
+
+## 兼容旧接口(新增流程不再调用)
+
+| 接口 | 说明 |
+|------|------|
+| `pda/selectOrderData` | 旧:选生产订单 |
+| `pda/instockSourceData` | 旧:按生产订单查入库源行 |

BIN=BIN
docs/ppt_effect_shots/1_mes_screen.png


BIN=BIN
docs/ppt_effect_shots/2_oee_screen.png


BIN=BIN
docs/ppt_effect_shots/3_energy_screen.png


BIN=BIN
docs/ppt_effect_shots/4_erp_board.png


BIN=BIN
docs/ppt_effect_shots/5_trace_detail.png


BIN=BIN
docs/ppt_effect_shots/6_qm_white.png


BIN=BIN
docs/ppt_effect_shots/7_arc_weld.png


BIN=BIN
docs/ppt_effect_shots/8_pda_hev.png


+ 49 - 0
docs/六安裕洋MES实施进度_PPT大纲.md

@@ -0,0 +1,49 @@
+# 六安裕洋 MES 实施进度汇报 — PPT 大纲
+
+## 文档信息
+- 标题:六安裕洋MES项目实施进度汇报
+- 汇报方:京宜安
+- 数据截至:**2026年7月21日**
+- 页数:2 页(16:9)
+- 输出:`六安裕洋MES实施进度汇报.pptx`
+
+## 数据口径
+- **5–6 月周任务**:阶段三至七,**8 周次 / 64 项 / 100% 完成**(全部达标)。
+- **本月(7 月)重点**:现场培训、实际上线、陪产(橙色条,推进中)。
+- 甘特时间轴:2026-04-27 ~ 07-24;今日竖线 **7/21**。
+
+## 第1页:周任务明细 + 甘特 + 本月重点
+1. 标题 / 副标题(截至 7/21;64 项已完成;本月聚焦培训/上线/陪产)
+2. 右上统计:8周次 | 64/64 | 100% | 7月上线陪产
+3. **左侧**:周任务明细表(阶段、起止、任务周、完成、合格率)
+4. **右侧**:实施阶段甘特图(阶段一~七完成青绿;七月上线陪产橙色)
+5. **底部三卡**:现场培训 | 实际上线 | 陪产支持
+
+## 第2页:效果展示(用户精选实时截图)
+- 2×4 网格,图源见 `docs/ppt_effect_shots/`
+  1. 生产管理大屏
+  2. 设备综合效率
+  3. 能源监控大屏
+  4. ERP排产看板
+  5. 生产追溯明细
+  6. 白件气密管理
+  7. 弧焊过程参数
+  8. PDA产线扫码(HEV)
+- 页眉标注本月主题:现场培训 · 实际上线 · 陪产
+
+## 甘特图阶段
+| 阶段 | 区间 | 状态 |
+|------|------|------|
+| 一 环境准备 | 4/27–5/6 | 完成 |
+| 二 软件预开发 | 4/30–5/6 | 完成 |
+| 三 蓝图设计 | 5/7–6/1 | 完成 |
+| 四 硬件与工位对接 | 5/8–5/16 | 完成 |
+| 五 流程与主数据 | 5/18–5/30 | 完成 |
+| 六 多系统对接 | 6/1–6/18 | 完成 |
+| 七 培训驻场 | 6/22–6/27 | 完成 |
+| 七月 上线陪产 | 7/1–7/24 | **进行中** |
+
+## 生成
+```bash
+py -3 _gen_mes_ppt.py
+```

BIN=BIN
docs/六安裕洋MES实施进度汇报.pptx


+ 381 - 0
docs/工位客户端设备状态上报接口说明.md

@@ -0,0 +1,381 @@
+# 工位客户端设备状态上报接口说明
+
+> 服务根地址:`http://127.0.0.1:8980`  
+> 上下文路径:`/js`  
+> 管理路径:`/a`  
+> **接口无需登录**(Shiro 已配置 `anon`)  
+> 上报数据用于 **设备综合效率管理大屏**(`mesDeviceOeeScreen`)的开动率、状态总览、最后上报时间等展示
+
+---
+
+## 一、背景说明
+
+### 1.1 大屏数据来源
+
+设备综合效率大屏通过接口 `POST /js/a/mes/mesDevice/oeeScreenData` 聚合展示,核心数据来自:
+
+| 数据表 | 用途 |
+|:-------|:-----|
+| `mes_device` | 设备主数据(名称、编号、产线、当前工作状态) |
+| `mes_device_state` | 设备状态变更历史(开始时间、结束时间、持续时长) |
+
+大屏当前展示的是 **时间开动率**(基于状态记录计算),**不是完整 OEE**(缺少计划产量、良品数、理想节拍)。
+
+### 1.2 客户端上报原则
+
+| 时机 | 建议 state | 说明 |
+|:-----|:-----------|:-----|
+| 工位登录成功 | `1` | 标记设备开始运行,开动率从此刻累计 |
+| 工位退出 / 收班 | 非 `1`/`2` | 停止开动率累计,刷新「最后上报」 |
+| 运行中心跳(可选) | `1` | 建议每 5~15 分钟一次,避免「最后上报」长期不更新 |
+| 故障 / 报警 / 维修 | `3`/`4`/`5`/`6` | 状态变化时立即上报 |
+| 故障恢复 | `1` | 恢复运行 |
+
+> **注意**:若工位登录后上报 `state=1`,但退出时不上报,设备会一直保持「运行」状态,大屏开动率可能虚高(即使「最后上报」已过期)。
+
+---
+
+## 二、设备状态码
+
+字典类型:`mes_device_state`
+
+| state | 含义 | 大屏归类 | 是否计入开动率 |
+|:------|:-----|:---------|:---------------|
+| `1` | 正常 | 运行 | 是 |
+| `2` | 手动 | 运行 | 是 |
+| `3` | 故障 | 故障 | 否 |
+| `4` | 维修 | 维修 | 否 |
+| `5` | 质量报警 | 报警 | 否 |
+| `6` | 物流堵料 | 报警 | 否 |
+
+> 当前字典 **没有「停机 / 待机」** 专用状态。工位退出时可暂用 `4`(维修/收班),或与现场统一约定后再调整;后续可在字典中扩展 `0`「停机」等状态。
+
+---
+
+## 三、推荐接口:上报设备状态(JSON)
+
+工位客户端 **优先使用本接口**,参数少、支持 JSON,与项目 `api.http` 示例一致。
+
+### 3.1 基本信息
+
+| 项 | 说明 |
+|:---|:-----|
+| 请求 URL | `POST {baseUrl}/js/a/mes/mesProductRecord/device` |
+| 示例 | `http://192.168.1.100:8980/js/a/mes/mesProductRecord/device` |
+| Content-Type | `application/json` 或 `application/x-www-form-urlencoded` |
+| 鉴权 | 无需登录,无需 `__sid` / token |
+
+### 3.2 请求参数
+
+| 参数 | 类型 | 必填 | 说明 |
+|:-----|:-----|:-----|:-----|
+| oprno | string | 是 | 设备编号,对应 `mes_device.sn`(大屏「设备编号」,如 `115OP030`) |
+| lineSn | string | 建议 | 产线编号,与设备档案一致(如 `115XT`) |
+| state | string | 是 | 设备状态码,见 [二、设备状态码](#二设备状态码) |
+| content | string | 否 | 状态描述,大屏设备卡片会展示(如「工位登录,开始运行」) |
+| codes | string | 否 | 故障码,一般场景可不传 |
+
+### 3.3 响应格式
+
+JeeSite 标准 JSON 字符串:
+
+```json
+{
+  "result": "true",
+  "message": "操作成功!"
+}
+```
+
+失败示例:
+
+```json
+{
+  "result": "false",
+  "message": "错误信息"
+}
+```
+
+### 3.4 请求示例
+
+#### 工位登录 — 上报运行
+
+```http
+POST /js/a/mes/mesProductRecord/device
+Content-Type: application/json
+
+{
+  "oprno": "115OP030",
+  "lineSn": "115XT",
+  "state": "1",
+  "content": "工位登录,开始运行"
+}
+```
+
+#### 工位退出 — 停止运行
+
+```http
+POST /js/a/mes/mesProductRecord/device
+Content-Type: application/json
+
+{
+  "oprno": "115OP030",
+  "lineSn": "115XT",
+  "state": "4",
+  "content": "工位退出,停止运行"
+}
+```
+
+#### 运行心跳(可选)
+
+```http
+POST /js/a/mes/mesProductRecord/device
+Content-Type: application/json
+
+{
+  "oprno": "115OP030",
+  "lineSn": "115XT",
+  "state": "1",
+  "content": "运行心跳"
+}
+```
+
+#### 设备故障
+
+```http
+POST /js/a/mes/mesProductRecord/device
+Content-Type: application/json
+
+{
+  "oprno": "115OP030",
+  "lineSn": "115XT",
+  "state": "3",
+  "content": "设备故障:伺服报警"
+}
+```
+
+#### form 表单方式(等价)
+
+```http
+POST /js/a/mes/mesProductRecord/device
+Content-Type: application/x-www-form-urlencoded
+
+__ajax=json&oprno=115OP030&lineSn=115XT&state=1&content=工位登录,开始运行
+```
+
+---
+
+## 四、备选接口:上报设备状态(Form)
+
+适用于不便发送 JSON 的客户端(如部分 PLC 网关、老旧 WinForm 组件)。
+
+### 4.1 基本信息
+
+| 项 | 说明 |
+|:---|:-----|
+| 请求 URL | `POST {baseUrl}/js/a/mes/mesProductRecord/deviceState` |
+| Content-Type | `application/x-www-form-urlencoded` |
+| 鉴权 | 无需登录 |
+
+### 4.2 请求参数
+
+| 参数 | 类型 | 必填 | 说明 |
+|:-----|:-----|:-----|:-----|
+| oprno | string | 是 | 设备编号 |
+| state | string | 是 | 设备状态码 |
+| productCate | string | 是 | 产线编号(等同 `lineSn`) |
+| deviceContent | string | 否 | 状态描述 |
+| paramsNums | int | 是 | 无故障码时固定传 `0` |
+| errorCode | string | 否 | 故障码;`paramsNums=0` 时可传空字符串 |
+
+### 4.3 响应格式
+
+`CommonResp` 结构:
+
+| 字段 | 类型 | 说明 |
+|:-----|:-----|:-----|
+| result | string | `"true"` 成功 / `"false"` 失败 |
+| message | string | 提示信息 |
+| data | string | 成功时为 `{state}OK`,如 `"1OK"` |
+
+```json
+{
+  "result": "true",
+  "message": "成功",
+  "data": "1OK"
+}
+```
+
+### 4.4 请求示例
+
+```http
+POST /js/a/mes/mesProductRecord/deviceState
+Content-Type: application/x-www-form-urlencoded
+
+oprno=115OP030&state=1&productCate=115XT&deviceContent=工位登录&paramsNums=0&errorCode=
+```
+
+---
+
+## 五、TCP 报文方式(MSBW,可选)
+
+若工位客户端通过 **Netty TCP 网关** 与 MES 通信,可使用设备状态报文 `MSBW`(与 HTTP 接口写入同一张表)。
+
+| 项 | 说明 |
+|:---|:-----|
+| 报文类型 | `MSBW` |
+| 处理入口 | `MesProductRecordController.deviceState(msg)` |
+| 说明 | 一般由设备网关 / 产线控制器上报,工位 WinForm 客户端通常直接用 HTTP 即可 |
+
+报文示例(注释格式,实际为定长协议):
+
+```
+aaaabbbbbABWMSBWGWOP040 LX01ID151245P00000106200123062900001      DA2023-08-18ZT14:32:33
+```
+
+- `GWOP040`:工位号(oprno)
+- `LX01`:状态码(01 → state=1)
+- 产线等信息按 `ProtocolParam` 解析
+
+> 工位客户端集成 **建议优先 HTTP `/device` 接口**,无需拼装 TCP 定长报文。
+
+---
+
+## 六、客户端集成流程
+
+```
+┌─────────────┐     登录成功      ┌──────────────────────────────┐
+│  工位客户端  │ ───────────────► │ POST /device  state=1        │
+└─────────────┘                   └──────────────────────────────┘
+       │                                        │
+       │  运行中(可选,每 5~15 分钟)            ▼
+       ├──────────────────────────────► mes_device_state 新增记录
+       │                                        │
+       │  退出 / 收班                           ▼
+       └──────────────────────────────► POST /device  state=4(或非 1/2)
+                                                │
+                                                ▼
+                                    上一条记录闭合 end_date / interval
+                                                │
+                                                ▼
+                                    大屏 oeeScreenData 刷新展示
+```
+
+### 6.1 集成检查清单
+
+- [ ] `oprno` 与 `mes_device.sn` 一致(可在 MES 设备管理模块核对)
+- [ ] `lineSn` 与设备档案产线一致
+- [ ] 登录成功后调用 `/device`,`state=1`
+- [ ] 退出 / 关闭客户端前调用 `/device`,`state` 为非运行状态
+- [ ] (可选)运行中定时心跳,`state=1`
+- [ ] 故障、报警发生时即时上报对应 `state`
+
+### 6.2 C# 调用示例
+
+```csharp
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+
+public static async Task<bool> ReportDeviceStateAsync(
+    string baseUrl, string oprno, string lineSn, string state, string content)
+{
+    using var client = new HttpClient();
+    var payload = new { oprno, lineSn, state, content };
+    var json = JsonSerializer.Serialize(payload);
+    var resp = await client.PostAsync(
+        $"{baseUrl.TrimEnd('/')}/js/a/mes/mesProductRecord/device",
+        new StringContent(json, Encoding.UTF8, "application/json"));
+    var body = await resp.Content.ReadAsStringAsync();
+    return body.Contains("\"result\":\"true\"");
+}
+
+// 工位登录
+await ReportDeviceStateAsync("http://192.168.1.100:8980", "115OP030", "115XT", "1", "工位登录,开始运行");
+
+// 工位退出
+await ReportDeviceStateAsync("http://192.168.1.100:8980", "115OP030", "115XT", "4", "工位退出,停止运行");
+```
+
+### 6.3 JavaScript 调用示例
+
+```javascript
+async function reportDeviceState(baseUrl, oprno, lineSn, state, content) {
+  const resp = await fetch(`${baseUrl}/js/a/mes/mesProductRecord/device`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ oprno, lineSn, state, content })
+  });
+  return resp.json();
+}
+
+// 工位登录
+await reportDeviceState('http://192.168.1.100:8980', '115OP030', '115XT', '1', '工位登录,开始运行');
+```
+
+---
+
+## 七、服务端处理逻辑(供联调参考)
+
+上报成功后,服务端执行:
+
+1. **`mes_device_state`**:闭合上一条记录的 `end_date`、`interval`,插入新状态记录(`start_date` 为当前时间)
+2. **`mes_device`**:更新 `work_status`、`work_time`
+
+大屏 `oeeScreenData` 读取逻辑摘要:
+
+- **当前状态**:取 `mes_device_state` 最新一条;无记录则用 `mes_device.work_status`
+- **开动率**:统计当日 state 为 `1` 或 `2` 的累计时长 ÷ 当日已过时长
+- **最后上报**:最新状态记录的 `start_date`;无记录则用 `mes_device.work_time`
+
+---
+
+## 八、常见问题
+
+### Q1:`oprno` 填工位号还是设备编号?
+
+填 **`mes_device.sn`**(设备档案中的「唯一编号 / 设备编号」)。大屏卡片「设备编号」列即此字段。若工位与设备一一对应,需先在 MES 设备管理中确认映射关系。
+
+### Q2:退出时 state 传什么?
+
+当前无「停机」状态,建议退出传 `4`,`content` 注明「工位退出」。若业务需要独立「待机」状态,需先在字典 `mes_device_state` 中扩展并在 OEE 计算逻辑中适配。
+
+### Q3:同一状态重复上报会怎样?
+
+`/device` 接口每次调用都会 **新增一条记录** 并闭合上一条,开动率统计仍正确;可用于心跳刷新「最后上报」时间。
+
+### Q4:接口需要登录吗?
+
+不需要。`application.yml` 中已配置:
+
+```yaml
+${adminPath}/mes/mesProductRecord/device = anon
+${adminPath}/mes/mesProductRecord/deviceState = anon
+```
+
+### Q5:上报失败如何排查?
+
+1. 确认 `oprno` 在 `mes_device` 表中存在  
+2. 确认 `state` 为 `1`~`6` 的有效值  
+3. 查看 MES 接口日志(`mes_log` 表,`apisave2` 会记录请求内容)
+
+---
+
+## 九、相关代码位置
+
+| 模块 | 路径 |
+|:-----|:-----|
+| HTTP 接口(推荐) | `MesProductRecordController.device()` |
+| HTTP 接口(Form) | `MesProductRecordController.deviceStateApi()` |
+| TCP MSBW | `MesProductRecordController.deviceState()` |
+| 状态持久化 | `MesDeviceStateService.updateState()` |
+| OEE 大屏数据 | `MesDeviceController.oeeScreenData()` |
+| 大屏页面 | `src/main/resources/views/modules/mes/mesDeviceOeeScreen.html` |
+| 请求体 DTO | `com.jeesite.modules.mes.req.MesDeviceStateReq` |
+
+---
+
+## 十、修订记录
+
+| 日期 | 说明 |
+|:-----|:-----|
+| 2026-06-30 | 初版:工位客户端设备状态上报接口说明 |

BIN=BIN
electron/dist/MES Cloud-1.0.0.exe


BIN=BIN
electron/dist/MES Cloud-1.0.0.exe.blockmap


BIN=BIN
electron/dist/win-unpacked/resources/elevate.exe


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1600 - 0
scripts/MesDbBackup.cs


BIN=BIN
scripts/MesDbBackup.exe


+ 274 - 0
scripts/backup-db.ps1

@@ -0,0 +1,274 @@
+#Requires -Version 5.1
+<#
+.SYNOPSIS
+    MES Cloud 数据库全量备份(gzip 压缩,不改 MySQL、不重启)
+
+.DESCRIPTION
+    放到数据库服务器上执行。每天 18:00 由计划任务触发。
+    每次导出完整库并压缩,按日期保存;超过保留天数的旧文件自动删除。
+    先写临时文件,成功后再替换,避免备份失败时把上一份可用备份写坏。
+
+    备份目录:
+      E:\MES_DB_Backup\mes_cloud_yyyyMMdd.sql.gz
+      E:\MES_DB_Backup\backup.log
+
+    手动用法:
+      powershell -ExecutionPolicy Bypass -File .\backup-db.ps1
+      powershell -ExecutionPolicy Bypass -File .\backup-db.ps1 -Install
+#>
+param(
+    [switch]$Install,
+    [string]$BackupDir = "",
+    [int]$KeepDaysParam = -1
+)
+
+$ErrorActionPreference = "Stop"
+try {
+    [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
+    $OutputEncoding = [System.Text.Encoding]::UTF8
+} catch { }
+
+. (Join-Path $PSScriptRoot "db-config.ps1")
+$dbCfg = Read-MesDbConfig
+$DbHost = [string]$dbCfg.host
+$DbPort = [int]$dbCfg.port
+$DbName = [string]$dbCfg.database
+$DbUser = [string]$dbCfg.user
+$DbPassword = [string]$dbCfg.password
+
+# ========== 备份目录等(连接信息请改 db-config.json 或页面) ==========
+$BackupRoot = "E:\MES_DB_Backup"
+$KeepDays = 7                         # 保留最近几天的备份,更早的自动删掉
+$TaskName = "MES_Cloud_DB_Backup"
+$TaskTime = "18:00"
+# 体积特别大、可接受不进备份的表,按需取消注释:
+# $IgnoreTables = @("mes_adw300_data")
+$IgnoreTables = @()
+# ========================================
+
+if (-not [string]::IsNullOrWhiteSpace($BackupDir)) {
+    $BackupRoot = $BackupDir.Trim().TrimEnd("\")
+}
+if ($KeepDaysParam -ge 0) {
+    $KeepDays = $KeepDaysParam
+}
+
+$LogFile = Join-Path $BackupRoot "backup.log"
+$CnfFile = Join-Path $env:TEMP ("mes_backup_{0}.cnf" -f $PID)
+$Stamp = Get-Date -Format "yyyyMMdd"
+$OutGz = Join-Path $BackupRoot ("mes_cloud_{0}.sql.gz" -f $Stamp)
+
+function Write-Log {
+    param([string]$Message, [string]$Level = "INFO")
+    $line = "{0} [{1}] {2}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Level, $Message
+    Write-Host $line
+    if (-not (Test-Path $BackupRoot)) {
+        New-Item -ItemType Directory -Path $BackupRoot -Force | Out-Null
+    }
+    Add-Content -Path $LogFile -Value $line -Encoding UTF8
+}
+
+function Rotate-LogIfNeeded {
+    if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length -gt 5MB)) {
+        $bak = $LogFile + ".old"
+        if (Test-Path $bak) { Remove-Item $bak -Force }
+        Move-Item $LogFile $bak -Force
+    }
+}
+
+function Find-MysqlTool {
+    param([string]$Name)
+    $cmd = Get-Command $Name -ErrorAction SilentlyContinue
+    if ($cmd -and $cmd.Source) { return $cmd.Source }
+
+    $binRoots = @(
+        "C:\Program Files\MySQL",
+        "C:\Program Files (x86)\MySQL",
+        "C:\mysql",
+        "D:\MySQL",
+        "E:\MySQL",
+        "C:\Program Files\MariaDB"
+    )
+    foreach ($root in $binRoots) {
+        if (-not (Test-Path $root)) { continue }
+        $hit = Get-ChildItem -Path $root -Filter ($Name + ".exe") -Recurse -ErrorAction SilentlyContinue |
+            Where-Object { $_.Directory.Name -eq "bin" } |
+            Select-Object -First 1
+        if ($hit) { return $hit.FullName }
+    }
+    return $null
+}
+
+function Compress-DumpToGzip {
+    param(
+        [string]$MysqldumpExe,
+        [string[]]$DumpArgs,
+        [string]$DestGz
+    )
+    $tmpGz = $DestGz + ".tmp"
+    if (Test-Path $tmpGz) { Remove-Item $tmpGz -Force }
+
+    $psi = New-Object System.Diagnostics.ProcessStartInfo
+    $psi.FileName = $MysqldumpExe
+    $psi.Arguments = ($DumpArgs | ForEach-Object {
+        '"' + ($_ -replace '"', '\"') + '"'
+    }) -join " "
+    $psi.UseShellExecute = $false
+    $psi.RedirectStandardOutput = $true
+    $psi.RedirectStandardError = $true
+    $psi.CreateNoWindow = $true
+
+    $proc = New-Object System.Diagnostics.Process
+    $proc.StartInfo = $psi
+    $errBuilder = New-Object System.Text.StringBuilder
+    $handler = {
+        if (-not [string]::IsNullOrEmpty($EventArgs.Data)) {
+            [void]$Event.MessageData.AppendLine($EventArgs.Data)
+        }
+    }
+    $errEvent = Register-ObjectEvent -InputObject $proc -EventName ErrorDataReceived -Action $handler -MessageData $errBuilder
+
+    if (-not $proc.Start()) {
+        throw "无法启动 mysqldump"
+    }
+    $proc.BeginErrorReadLine()
+
+    $outStream = $proc.StandardOutput.BaseStream
+    $fileStream = [System.IO.File]::Create($tmpGz)
+    $gzip = New-Object System.IO.Compression.GZipStream($fileStream, [System.IO.Compression.CompressionLevel]::Fastest)
+    try {
+        $outStream.CopyTo($gzip)
+    } finally {
+        $gzip.Dispose()
+        $fileStream.Dispose()
+    }
+    $proc.WaitForExit()
+    Unregister-Event -SourceIdentifier $errEvent.Name -ErrorAction SilentlyContinue | Out-Null
+    $errText = $errBuilder.ToString()
+    if ($errText) {
+        Write-Log ("mysqldump 输出: {0}" -f $errText.Trim()) "WARN"
+    }
+    if ($proc.ExitCode -ne 0) {
+        if (Test-Path $tmpGz) { Remove-Item $tmpGz -Force }
+        throw ("mysqldump 失败,退出码 {0}" -f $proc.ExitCode)
+    }
+    if (-not (Test-Path $tmpGz) -or (Get-Item $tmpGz).Length -lt 100) {
+        if (Test-Path $tmpGz) { Remove-Item $tmpGz -Force }
+        throw "备份文件过小,可能失败"
+    }
+    if (Test-Path $DestGz) { Remove-Item $DestGz -Force }
+    Move-Item $tmpGz $DestGz -Force
+}
+
+function Remove-OldBackups {
+    if ($KeepDays -le 0) {
+        Write-Log "未启用过期删除(KeepDays=0)"
+        return
+    }
+    $expire = (Get-Date).Date.AddDays(-$KeepDays)
+    Get-ChildItem -Path $BackupRoot -File -Filter "mes_cloud_*.sql.gz" -ErrorAction SilentlyContinue | ForEach-Object {
+        if ($_.Name -match "mes_cloud_(\d{8})\.sql\.gz") {
+            $day = [datetime]::ParseExact($Matches[1], "yyyyMMdd", $null)
+            if ($day -lt $expire) {
+                Remove-Item $_.FullName -Force
+                Write-Log ("已删除过期备份: {0}" -f $_.Name)
+            }
+        }
+    }
+    # 清理此前增量方案留下的目录(若有)
+    foreach ($oldDir in @("full", "incr")) {
+        $p = Join-Path $BackupRoot $oldDir
+        if (Test-Path $p) {
+            Remove-Item $p -Recurse -Force
+            Write-Log ("已删除旧目录: {0}" -f $p)
+        }
+    }
+}
+
+function Install-BackupTask {
+    $scriptPath = $PSCommandPath
+    $arg = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f $scriptPath
+    $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $arg
+    $trigger = New-ScheduledTaskTrigger -Daily -At ([datetime]::ParseExact($TaskTime, "HH:mm", $null))
+    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
+    $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 6)
+    Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
+    Write-Log ("已注册计划任务 [{0}],每天 {1} 执行: {2}" -f $TaskName, $TaskTime, $scriptPath)
+}
+
+# ---------- 主流程 ----------
+try {
+    if ($Install) {
+        $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).
+            IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+        if (-not $isAdmin) {
+            Start-Process -FilePath "powershell.exe" -Verb RunAs -ArgumentList @(
+                "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath, "-Install"
+            )
+            return
+        }
+        New-Item -ItemType Directory -Path $BackupRoot -Force | Out-Null
+        Install-BackupTask
+        return
+    }
+
+    Rotate-LogIfNeeded
+    New-Item -ItemType Directory -Path $BackupRoot -Force | Out-Null
+    Write-Log "======== 备份任务开始 ========"
+
+    $mysqldump = Find-MysqlTool "mysqldump"
+    if (-not $mysqldump) { throw "找不到 mysqldump.exe,请把 MySQL 的 bin 目录加入 PATH" }
+    Write-Log ("mysqldump: {0}" -f $mysqldump)
+
+    Write-MesMysqlCnf -Config $dbCfg -CnfPath $CnfFile
+    Write-Log ("连接 MySQL {0}:{1} 库 {2} 账号 {3}" -f $DbHost, $DbPort, $DbName, $DbUser)
+
+    $baseArgs = @(
+        "--defaults-extra-file=$CnfFile",
+        "--single-transaction",
+        "--quick",
+        "--routines",
+        "--triggers",
+        "--hex-blob",
+        "--default-character-set=utf8mb4",
+        "--max-allowed-packet=1G",
+        "--set-gtid-purged=OFF"
+    )
+    foreach ($t in $IgnoreTables) {
+        $baseArgs += ("--ignore-table={0}.{1}" -f $DbName, $t)
+    }
+
+    $extraAttempts = New-Object System.Collections.Generic.List[object]
+    $extraAttempts.Add([string[]]@())
+    $extraAttempts.Add([string[]]@("--column-statistics=0"))
+
+    Write-Log ("开始全量备份(gzip),目标: {0}" -f $OutGz)
+    $lastError = $null
+    $ok = $false
+    foreach ($extra in $extraAttempts) {
+        $dumpArgs = $baseArgs + $extra + @("--databases", $DbName)
+        try {
+            Compress-DumpToGzip -MysqldumpExe $mysqldump -DumpArgs $dumpArgs -DestGz $OutGz
+            $ok = $true
+            break
+        } catch {
+            $lastError = $_
+            Write-Log ("参数尝试失败,将换一套参数重试: {0}" -f $_.Exception.Message) "WARN"
+        }
+    }
+    if (-not $ok) { throw $lastError }
+
+    $sizeMb = [math]::Round((Get-Item $OutGz).Length / 1MB, 2)
+    Write-Log ("全量备份完成: {0} ({1} MB)" -f $OutGz, $sizeMb)
+    Remove-OldBackups
+    Write-Log "======== 备份任务结束 ========"
+    exit 0
+} catch {
+    Write-Log $_.Exception.Message "ERROR"
+    if ($_.ScriptStackTrace) { Write-Log $_.ScriptStackTrace "ERROR" }
+    exit 1
+} finally {
+    if (Test-Path $CnfFile) {
+        Remove-Item $CnfFile -Force -ErrorAction SilentlyContinue
+    }
+}

+ 618 - 0
scripts/backup-ui-server.ps1

@@ -0,0 +1,618 @@
+#Requires -Version 5.1
+<#
+.SYNOPSIS
+    独立备份页面:点选磁盘/文件夹,不手输路径。只监听 127.0.0.1。
+#>
+$ErrorActionPreference = "Stop"
+$RootDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$HtmlFile = Join-Path $RootDir "backup-ui.html"
+$BackupScript = Join-Path $RootDir "backup-db.ps1"
+$Port = 18765
+$Url = $null
+$listener = $null
+
+. (Join-Path $RootDir "db-config.ps1")
+
+if (-not (Test-Path $HtmlFile)) { throw "找不到 backup-ui.html" }
+if (-not (Test-Path $BackupScript)) { throw "找不到 backup-db.ps1" }
+
+Add-Type -AssemblyName System.Windows.Forms
+if (-not ("MesFolderPicker" -as [type])) {
+    Add-Type -ReferencedAssemblies System.Windows.Forms,System.Drawing -TypeDefinition @"
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Windows.Forms;
+
+public static class MesFolderPicker {
+    [DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd);
+    [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+    [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
+    [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
+    [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
+    [DllImport("user32.dll")] static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
+    [DllImport("user32.dll")] static extern bool BringWindowToTop(IntPtr hWnd);
+
+    public static string Pick(string description) {
+        string selected = null;
+        Thread t = new Thread(delegate() {
+            Form form = new Form();
+            form.Text = "MES Backup - Select Folder";
+            form.TopMost = true;
+            form.ShowInTaskbar = true;
+            form.StartPosition = FormStartPosition.CenterScreen;
+            form.Width = 420;
+            form.Height = 90;
+            form.FormBorderStyle = FormBorderStyle.FixedToolWindow;
+            form.MinimizeBox = false;
+            form.MaximizeBox = false;
+            form.Show();
+            form.WindowState = FormWindowState.Normal;
+            form.Activate();
+            ForceForeground(form.Handle);
+            Application.DoEvents();
+
+            FolderBrowserDialog dlg = new FolderBrowserDialog();
+            dlg.Description = description;
+            dlg.ShowNewFolderButton = true;
+            dlg.RootFolder = Environment.SpecialFolder.MyComputer;
+            DialogResult r = dlg.ShowDialog(form);
+            if (r == DialogResult.OK) {
+                selected = dlg.SelectedPath;
+            }
+            form.Close();
+            form.Dispose();
+        });
+        t.SetApartmentState(ApartmentState.STA);
+        t.Start();
+        t.Join();
+        return selected;
+    }
+
+    static void ForceForeground(IntPtr hwnd) {
+        IntPtr fg = GetForegroundWindow();
+        uint fgThread = GetWindowThreadProcessId(fg, IntPtr.Zero);
+        uint thisThread = GetCurrentThreadId();
+        if (fgThread != thisThread) {
+            AttachThreadInput(thisThread, fgThread, true);
+        }
+        ShowWindow(hwnd, 9);
+        BringWindowToTop(hwnd);
+        SetForegroundWindow(hwnd);
+        if (fgThread != thisThread) {
+            AttachThreadInput(thisThread, fgThread, false);
+        }
+    }
+}
+"@
+}
+
+$State = [hashtable]::Synchronized(@{
+    Running   = $false
+    Log       = New-Object System.Collections.ArrayList
+    Result    = ""
+    Error     = ""
+    Path      = ""
+    StartedAt = ""
+    EndedAt   = ""
+    Proc      = $null
+    OutFile   = ""
+    DbBytes   = 0
+})
+
+function Add-UiLog {
+    param([string]$Text)
+    if ([string]::IsNullOrWhiteSpace($Text)) { return }
+    [void]$State.Log.Add($Text)
+    if ($State.Log.Count -gt 800) {
+        $State.Log.RemoveRange(0, 200)
+    }
+}
+
+function Get-SafePath {
+    param([string]$Path)
+    if ([string]::IsNullOrWhiteSpace($Path)) { throw "请先选择保存位置" }
+    $p = $Path.Trim().Trim('"')
+    if ($p -notmatch '^[A-Za-z]:\\') { throw "只允许本机磁盘路径" }
+    $full = [System.IO.Path]::GetFullPath($p)
+    if ($full -match '^[A-Za-z]:\\?$') {
+        return ($full.Substring(0, 1).ToUpper() + ":\")
+    }
+    return $full.TrimEnd("\")
+}
+
+$script:MysqlToolCache = @{}
+
+function Find-MysqlTool {
+    param([string]$Name)
+    if ($script:MysqlToolCache.ContainsKey($Name)) { return $script:MysqlToolCache[$Name] }
+    $cmd = Get-Command $Name -ErrorAction SilentlyContinue
+    if ($cmd -and $cmd.Source) {
+        $script:MysqlToolCache[$Name] = $cmd.Source
+        return $cmd.Source
+    }
+    $candidates = New-Object System.Collections.Generic.List[string]
+    foreach ($root in @(
+        "C:\Program Files\MySQL",
+        "C:\Program Files (x86)\MySQL",
+        "C:\mysql",
+        "D:\MySQL",
+        "E:\MySQL",
+        "C:\Program Files\MariaDB"
+    )) {
+        if (-not (Test-Path $root)) { continue }
+        Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue | ForEach-Object {
+            $direct = Join-Path $_.FullName ("bin\{0}.exe" -f $Name)
+            $candidates.Add($direct)
+            Get-ChildItem -Path $_.FullName -Directory -ErrorAction SilentlyContinue | ForEach-Object {
+                $candidates.Add((Join-Path $_.FullName ("bin\{0}.exe" -f $Name)))
+            }
+        }
+    }
+    foreach ($p in $candidates) {
+        if (Test-Path -LiteralPath $p) {
+            $script:MysqlToolCache[$Name] = $p
+            return $p
+        }
+    }
+    return $null
+}
+
+function Get-DbSizeBytes {
+    try {
+        $cfg = Read-MesDbConfig
+        $mysql = Find-MysqlTool "mysql"
+        if (-not $mysql) { return 0 }
+        $cnf = Join-Path $env:TEMP ("mes_ui_size_{0}.cnf" -f $PID)
+        Write-MesMysqlCnf -Config $cfg -CnfPath $cnf
+        $sql = "SELECT IFNULL(SUM(data_length+index_length),0) FROM information_schema.tables WHERE table_schema='$($cfg.database)'"
+        $out = & $mysql "--defaults-extra-file=$cnf" "--connect-timeout=3" -N -s -e $sql 2>$null
+        Remove-Item $cnf -Force -ErrorAction SilentlyContinue
+        $n = 0
+        [void][int64]::TryParse(($out | Out-String).Trim(), [ref]$n)
+        return $n
+    } catch {
+        return 0
+    }
+}
+
+function Test-MesMysqlConn {
+    param($Config)
+    $cfg = ConvertTo-MesDbConfig -InputObject $Config
+    $mysql = Find-MysqlTool "mysql"
+    if (-not $mysql) { throw "找不到 mysql.exe,请安装 MySQL 客户端" }
+    $cnf = Join-Path $env:TEMP ("mes_ui_test_{0}.cnf" -f $PID)
+    Write-MesMysqlCnf -Config $cfg -CnfPath $cnf
+    try {
+        $out = & $mysql "--defaults-extra-file=$cnf" "--connect-timeout=5" -N -s -e "SELECT 1" 2>&1
+        if ($LASTEXITCODE -ne 0) {
+            throw ("连接失败: {0}" -f ($out | Out-String).Trim())
+        }
+    } finally {
+        Remove-Item $cnf -Force -ErrorAction SilentlyContinue
+    }
+    return $cfg
+}
+
+function Get-DriveList {
+    $list = New-Object System.Collections.Generic.List[object]
+    foreach ($root in [System.IO.Directory]::GetLogicalDrives()) {
+        if ($root -match '^[AB]:\\$') { continue }
+        $letter = $root.Substring(0, 2).ToUpper()
+        $list.Add([ordered]@{
+            letter     = $letter
+            path       = $root
+            name       = $letter
+            type       = "磁盘"
+            removable  = $false
+            ready      = $true
+            sizeGB     = 0
+            freeGB     = 0
+            freeBytes  = 0
+        })
+    }
+    return $list
+}
+
+function Get-FolderList {
+    param([string]$Path)
+    $full = Get-SafePath $Path
+    if (-not (Test-Path -LiteralPath $full)) { throw "路径不存在,请确认已插入磁盘" }
+    $parent = [System.IO.Path]::GetDirectoryName($full)
+    $folders = New-Object System.Collections.Generic.List[object]
+    Get-ChildItem -LiteralPath $full -Directory -Force -ErrorAction SilentlyContinue |
+        Select-Object -First 80 |
+        ForEach-Object {
+            $folders.Add([ordered]@{ name = $_.Name; path = $_.FullName })
+        }
+    $pathOut = $full
+    if (-not $pathOut.EndsWith("\")) { $pathOut += "\" }
+    return [ordered]@{
+        path    = $pathOut
+        parent  = $parent
+        folders = $folders
+    }
+}
+
+function Get-FreeBytes {
+    param([string]$Path)
+    try {
+        $full = Get-SafePath $Path
+        $root = [System.IO.Path]::GetPathRoot($full)
+        $di = New-Object System.IO.DriveInfo ($root)
+        if ($di.IsReady) { return [int64]$di.AvailableFreeSpace }
+    } catch { }
+    return 0
+}
+
+function Show-FolderPicker {
+    return [MesFolderPicker]::Pick("选择备份文件保存位置(可选手动插入的 U 盘或移动硬盘)")
+}
+
+function Get-WrittenBackup {
+    $info = [ordered]@{ bytes = 0; name = ""; doneBytes = 0 }
+    $dir = $State.Path
+    if (-not $dir -or -not (Test-Path -LiteralPath $dir)) { return $info }
+    $files = @(Get-ChildItem -LiteralPath $dir -File -ErrorAction SilentlyContinue |
+        Where-Object { $_.Name -like "mes_cloud_*.sql.gz*" } |
+        Sort-Object LastWriteTime -Descending)
+    if ($files.Count -gt 0) {
+        $final = @($files | Where-Object { $_.Name -notlike "*.tmp" }) | Select-Object -First 1
+        $pick = if ($final) { $final } else { $files[0] }
+        $info.name = $pick.Name
+        $info.bytes = [int64]$pick.Length
+        if ($pick.Name -notlike "*.tmp") {
+            $info.doneBytes = [int64]$pick.Length
+        }
+    }
+    return $info
+}
+
+function Read-JsonBody {
+    param($Request)
+    $reader = New-Object System.IO.StreamReader($Request.InputStream, [System.Text.Encoding]::UTF8)
+    try {
+        $raw = $reader.ReadToEnd()
+    } finally {
+        $reader.Dispose()
+    }
+    if ([string]::IsNullOrWhiteSpace($raw)) { return $null }
+    return $raw | ConvertFrom-Json
+}
+
+function Send-Bytes {
+    param($Response, [byte[]]$Bytes, [string]$ContentType, [int]$Status = 200)
+    $Response.StatusCode = $Status
+    $Response.ContentType = $ContentType
+    $Response.Headers["Cache-Control"] = "no-store"
+    $Response.ContentLength64 = $Bytes.Length
+    $Response.OutputStream.Write($Bytes, 0, $Bytes.Length)
+    $Response.OutputStream.Close()
+}
+
+function Send-Json {
+    param($Response, $Object, [int]$Status = 200)
+    $json = $Object | ConvertTo-Json -Depth 8 -Compress
+    $bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
+    Send-Bytes -Response $Response -Bytes $bytes -ContentType "application/json; charset=utf-8" -Status $Status
+}
+
+function Send-Html {
+    param($Response)
+    $bytes = [System.IO.File]::ReadAllBytes($HtmlFile)
+    Send-Bytes -Response $Response -Bytes $bytes -ContentType "text/html; charset=utf-8"
+}
+
+function Start-BackupJob {
+    param([string]$TargetDir, [int]$KeepDays)
+    if ($State.Running) { throw "已有备份正在进行" }
+    $dir = Get-SafePath $TargetDir
+    $free = Get-FreeBytes -Path $dir
+    $dbBytes = Get-DbSizeBytes
+    $State.DbBytes = $dbBytes
+    # gzip 后通常小于库文件,但 SQL 文本可能接近库大小;预留 1.2 倍库容量
+    if ($dbBytes -gt 0 -and $free -gt 0 -and $free -lt [int64]($dbBytes * 1.2)) {
+        $needGB = [math]::Round($dbBytes * 1.2 / 1GB, 2)
+        $freeGB = [math]::Round($free / 1GB, 2)
+        throw ("目标盘剩余空间不足:约需 {0} GB,当前剩余 {1} GB。请换更大的 U 盘/硬盘。" -f $needGB, $freeGB)
+    }
+    if (-not (Test-Path -LiteralPath $dir)) {
+        New-Item -ItemType Directory -Path $dir -Force | Out-Null
+    }
+    $outFile = Join-Path $env:TEMP ("mes_backup_ui_{0}.log" -f (Get-Date -Format "yyyyMMddHHmmss"))
+    $errFile = $outFile + ".err"
+    if (Test-Path $outFile) { Remove-Item $outFile -Force }
+    if (Test-Path $errFile) { Remove-Item $errFile -Force }
+
+    $arg = '-NoProfile -ExecutionPolicy Bypass -File "{0}" -BackupDir "{1}" -KeepDaysParam {2}' -f $BackupScript, $dir, $KeepDays
+    $proc = Start-Process -FilePath "powershell.exe" -ArgumentList $arg -PassThru -WindowStyle Hidden `
+        -RedirectStandardOutput $outFile -RedirectStandardError $errFile
+
+    $State.Running = $true
+    $State.Result = ""
+    $State.Error = ""
+    $State.Path = $dir
+    $State.StartedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
+    $State.EndedAt = ""
+    $State.Proc = $proc
+    $State.OutFile = $outFile
+    $State.Log.Clear()
+    Add-UiLog ("开始备份到: {0}" -f $dir)
+    if ($dbBytes -gt 0) {
+        Add-UiLog ("库数据约 {0} GB,采用边导出边压缩,请耐心等待,勿拔盘" -f ([math]::Round($dbBytes / 1GB, 2)))
+    }
+}
+
+function Read-LogFiles {
+    $lines = New-Object System.Collections.ArrayList
+    foreach ($f in @($State.OutFile, ($State.OutFile + ".err"))) {
+        if (-not $f -or -not (Test-Path $f)) { continue }
+        $text = $null
+        foreach ($encName in @("UTF8", "Default")) {
+            try {
+                $enc = [System.Text.Encoding]::$encName
+                $text = [System.IO.File]::ReadAllText($f, $enc)
+                if ($text -and $text.IndexOf([char]0xFFFD) -lt 0) { break }
+            } catch { }
+        }
+        if ($text) {
+            foreach ($line in ($text -split "`r?`n")) {
+                if ($line.Trim() -ne "") { [void]$lines.Add($line) }
+            }
+        }
+    }
+    return $lines
+}
+
+function Test-BackupFileOk {
+    $dir = $State.Path
+    if (-not $dir -or -not (Test-Path -LiteralPath $dir)) { return $false }
+    $started = $null
+    try {
+        if ($State.StartedAt) { $started = [datetime]::Parse($State.StartedAt) }
+    } catch { }
+    $gz = Get-ChildItem -LiteralPath $dir -File -Filter "mes_cloud_*.sql.gz" -ErrorAction SilentlyContinue |
+        Sort-Object LastWriteTime -Descending |
+        Select-Object -First 1
+    if ($null -eq $gz -or $gz.Length -le 100) { return $false }
+    if ($started -and $gz.LastWriteTime -lt $started.AddMinutes(-2)) { return $false }
+    return $true
+}
+
+function Update-BackupState {
+    if (-not $State.Running) { return }
+    $proc = $State.Proc
+    $lines = Read-LogFiles
+    if ($lines.Count -gt 0) {
+        $State.Log.Clear()
+        foreach ($line in $lines) { [void]$State.Log.Add($line) }
+    }
+    if ($null -eq $proc) { return }
+    try { $proc.Refresh() } catch { }
+    try { $exited = $proc.HasExited } catch { $exited = $true }
+    if (-not $exited) { return }
+    try { $proc.WaitForExit(1000) } catch { }
+    $code = $null
+    try { $code = $proc.ExitCode } catch { $code = $null }
+    $State.Running = $false
+    $State.EndedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
+    $State.Proc = $null
+    $fileOk = Test-BackupFileOk
+    # Start-Process 有时读不到 ExitCode(界面显示「退出码」后面空白);有新生成的 .gz 即视为成功
+    $success = ($code -eq 0) -or $fileOk
+    if ($success) {
+        $State.Result = "ok"
+        $State.Error = ""
+        Add-UiLog "备份完成"
+    } else {
+        $State.Result = "error"
+        $codeText = if ($null -eq $code) { "未知" } else { [string]$code }
+        $State.Error = "备份失败,退出码 $codeText"
+        Add-UiLog $State.Error
+    }
+}
+
+$listener = $null
+$started = $false
+$lastError = $null
+foreach ($tryPort in 18765..18785) {
+    $tryUrl = "http://127.0.0.1:{0}/" -f $tryPort
+    $tryListener = New-Object System.Net.HttpListener
+    try {
+        $tryListener.Prefixes.Add($tryUrl)
+        $tryListener.Start()
+        $listener = $tryListener
+        $Port = $tryPort
+        $Url = $tryUrl
+        $started = $true
+        break
+    } catch {
+        $lastError = $_.Exception.Message
+        try { $tryListener.Close() } catch { }
+    }
+}
+if (-not $started) {
+    throw ("无法启动本机页面,18765-18785 端口都被占用。请先关掉之前打开的备份窗口后再试。详情: {0}" -f $lastError)
+}
+
+Write-Host ""
+Write-Host ("数据库备份页面已启动(仅本机): {0}" -f $Url)
+if ($Port -ne 18765) {
+    Write-Host ("原端口 18765 已被占用,已自动改用 {0}。请先关掉旧的备份窗口,避免开两个。" -f $Port)
+}
+Write-Host "请在网页点「浏览文件夹」选择 U 盘/硬盘目录,不要手输路径。"
+Write-Host "关闭本窗口即停止页面。"
+Write-Host ""
+Start-Process $Url
+
+try {
+while ($listener.IsListening) {
+    $ctx = $null
+    try {
+        $ctx = $listener.GetContext()
+        $req = $ctx.Request
+        $res = $ctx.Response
+        $path = $req.Url.AbsolutePath.TrimEnd("/")
+        if ([string]::IsNullOrEmpty($path)) { $path = "/" }
+
+        if ($req.HttpMethod -eq "OPTIONS") {
+            $res.StatusCode = 204
+            $res.AddHeader("Access-Control-Allow-Origin", "*")
+            $res.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
+            $res.AddHeader("Access-Control-Allow-Headers", "Content-Type")
+            $res.ContentLength64 = 0
+            $res.Close()
+            continue
+        }
+        if ($req.HttpMethod -eq "GET" -and ($path -eq "/" -or $path -eq "/index.html")) {
+            Send-Html -Response $res
+            continue
+        }
+        if ($req.HttpMethod -eq "GET" -and $path -eq "/api/drives") {
+            Send-Json -Response $res -Object ([ordered]@{
+                ok     = $true
+                drives = @(Get-DriveList)
+            })
+            continue
+        }
+        if ($req.HttpMethod -eq "GET" -and $path -eq "/api/dbsize") {
+            Send-Json -Response $res -Object ([ordered]@{
+                ok      = $true
+                dbBytes = [int64](Get-DbSizeBytes)
+            })
+            continue
+        }
+        if ($req.HttpMethod -eq "GET" -and $path -eq "/api/folders") {
+            $folderPath = ""
+            $query = $req.Url.Query.TrimStart("?")
+            if ($query) {
+                foreach ($pair in $query.Split([char]38)) {
+                    $kv = $pair.Split("=", 2)
+                    if ($kv.Count -ge 2 -and [uri]::UnescapeDataString($kv[0]) -eq "path") {
+                        $folderPath = [uri]::UnescapeDataString(($kv[1] -replace "\+", " "))
+                    }
+                }
+            }
+            Send-Json -Response $res -Object ([ordered]@{ ok = $true; data = (Get-FolderList -Path $folderPath) })
+            continue
+        }
+        if ($req.HttpMethod -eq "POST" -and $path -eq "/api/browse") {
+            $picked = Show-FolderPicker
+            if ([string]::IsNullOrWhiteSpace($picked)) {
+                Send-Json -Response $res -Object ([ordered]@{ ok = $true; cancelled = $true })
+            } else {
+                Send-Json -Response $res -Object ([ordered]@{
+                    ok       = $true
+                    cancelled = $false
+                    path     = $picked
+                    freeBytes = [int64](Get-FreeBytes -Path $picked)
+                })
+            }
+            continue
+        }
+        if ($req.HttpMethod -eq "GET" -and $path -eq "/api/status") {
+            Update-BackupState
+            $written = Get-WrittenBackup
+            Send-Json -Response $res -Object ([ordered]@{
+                ok         = $true
+                running    = [bool]$State.Running
+                result     = $State.Result
+                error      = $State.Error
+                path       = $State.Path
+                startedAt  = $State.StartedAt
+                endedAt    = $State.EndedAt
+                log        = @($State.Log)
+                dbBytes    = [int64]$State.DbBytes
+                writtenBytes = [int64]$written.bytes
+                fileName   = $written.name
+            })
+            continue
+        }
+        if ($req.HttpMethod -eq "GET" -and $path -eq "/api/config") {
+            try {
+                $cfg = Read-MesDbConfig
+            } catch {
+                $cfg = Get-DefaultMesDbConfig
+            }
+            Send-Json -Response $res -Object ([ordered]@{
+                ok       = $true
+                engine   = "mysql"
+                host     = [string]$cfg.host
+                port     = [int]$cfg.port
+                database = [string]$cfg.database
+                user     = [string]$cfg.user
+                password = [string]$cfg.password
+            })
+            continue
+        }
+        if ($req.HttpMethod -eq "POST" -and $path -eq "/api/config") {
+            $body = Read-JsonBody -Request $req
+            $cfg = Save-MesDbConfig -Config $body
+            Send-Json -Response $res -Object ([ordered]@{
+                ok       = $true
+                message  = "已保存 MySQL 配置"
+                host     = [string]$cfg.host
+                port     = [int]$cfg.port
+                database = [string]$cfg.database
+                user     = [string]$cfg.user
+            })
+            continue
+        }
+        if (($req.HttpMethod -eq "GET" -or $req.HttpMethod -eq "POST") -and $path -eq "/api/test") {
+            $body = $null
+            if ($req.HttpMethod -eq "POST") {
+                $body = Read-JsonBody -Request $req
+            }
+            if ($null -eq $body) {
+                $q = $req.Url.Query.TrimStart("?")
+                $map = @{}
+                if ($q) {
+                    foreach ($pair in $q.Split([char]38)) {
+                        $kv = $pair.Split("=", 2)
+                        if ($kv.Count -ge 2) {
+                            $map[[uri]::UnescapeDataString($kv[0])] = [uri]::UnescapeDataString(($kv[1] -replace "\+", " "))
+                        }
+                    }
+                }
+                $body = [pscustomobject]@{
+                    engine   = "mysql"
+                    host     = $map["host"]
+                    port     = $map["port"]
+                    database = $map["database"]
+                    user     = $map["user"]
+                    password = $map["password"]
+                }
+            }
+            $cfg = Test-MesMysqlConn -Config $body
+            Send-Json -Response $res -Object ([ordered]@{
+                ok      = $true
+                message = ("MySQL 连接成功: {0}:{1} / {2}" -f $cfg.host, $cfg.port, $cfg.database)
+            })
+            continue
+        }
+        if ($req.HttpMethod -eq "POST" -and $path -eq "/api/backup") {
+            $body = Read-JsonBody -Request $req
+            if ($body -and $body.host) {
+                Save-MesDbConfig -Config $body | Out-Null
+            }
+            $keep = 0
+            if ($body -and $null -ne $body.keepDays) { $keep = [int]$body.keepDays }
+            Start-BackupJob -TargetDir $body.path -KeepDays $keep
+            Send-Json -Response $res -Object ([ordered]@{ ok = $true; message = "已开始备份" })
+            continue
+        }
+        Send-Json -Response $res -Object ([ordered]@{ ok = $false; message = "未找到接口" }) -Status 404
+    } catch {
+        if ($null -ne $ctx) {
+            try {
+                Send-Json -Response $ctx.Response -Object ([ordered]@{ ok = $false; message = $_.Exception.Message }) -Status 500
+            } catch { }
+        }
+    }
+}
+} finally {
+    if ($listener) {
+        try { $listener.Stop() } catch { }
+        try { $listener.Close() } catch { }
+    }
+}

+ 228 - 104
scripts/backup-ui.html

@@ -3,138 +3,262 @@
 <head>
   <meta charset="UTF-8"/>
   <meta name="viewport" content="width=device-width, initial-scale=1"/>
-  <title>MES 数据库手动备份</title>
+  <title>MES 智能备份控制台</title>
   <style>
     :root {
-      --bg: #0f172a; --card: #1e293b; --line: #334155; --text: #e2e8f0;
-      --muted: #94a3b8; --accent: #38bdf8; --ok: #34d399; --warn: #fbbf24; --bad: #f87171;
+      --bg: #07080d;
+      --bg2: #10131c;
+      --card: rgba(16, 20, 32, .72);
+      --line: rgba(94, 234, 212, .18);
+      --text: #eef4ff;
+      --muted: #8b95ab;
+      --cyan: #5eead4;
+      --cyan2: #22d3ee;
+      --violet: #818cf8;
+      --ok: #34d399;
+      --warn: #fbbf24;
+      --bad: #fb7185;
+      --shadow: 0 24px 60px rgba(0, 0, 0, .45);
     }
     * { box-sizing: border-box; }
+    html, body { height: 100%; }
     body {
       margin: 0;
-      font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
-      background: radial-gradient(1200px 600px at 10% -10%, #1e3a5f 0%, var(--bg) 45%);
-      color: var(--text); min-height: 100vh;
+      color: var(--text);
+      font-family: "Segoe UI Variable", "Microsoft YaHei UI", "Segoe UI", sans-serif;
+      background:
+        radial-gradient(900px 520px at 8% -10%, rgba(34, 211, 238, .18), transparent 55%),
+        radial-gradient(780px 480px at 108% 8%, rgba(129, 140, 248, .2), transparent 50%),
+        radial-gradient(640px 420px at 70% 110%, rgba(94, 234, 212, .1), transparent 50%),
+        var(--bg);
+      overflow-x: hidden;
     }
-    .wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 48px; }
-    h1 { font-size: 22px; margin: 0 0 6px; }
-    .sub { color: var(--muted); margin: 0 0 22px; font-size: 13px; line-height: 1.6; }
+    body::before {
+      content: "";
+      position: fixed; inset: 0; pointer-events: none; z-index: 0;
+      background-image:
+        linear-gradient(rgba(148, 163, 184, .05) 1px, transparent 1px),
+        linear-gradient(90deg, rgba(148, 163, 184, .05) 1px, transparent 1px);
+      background-size: 42px 42px;
+      mask-image: radial-gradient(ellipse at center, #000 40%, transparent 78%);
+    }
+    .orb {
+      position: fixed; border-radius: 50%; filter: blur(40px); pointer-events: none; z-index: 0;
+      animation: float 12s ease-in-out infinite;
+    }
+    .orb.a { width: 220px; height: 220px; left: -40px; top: 120px; background: rgba(34, 211, 238, .16); }
+    .orb.b { width: 280px; height: 280px; right: -80px; top: -40px; background: rgba(129, 140, 248, .18); animation-delay: -4s; }
+    @keyframes float {
+      0%, 100% { transform: translateY(0); }
+      50% { transform: translateY(18px); }
+    }
+    .wrap { position: relative; z-index: 1; max-width: 1080px; margin: 0 auto; padding: 28px 22px 48px; }
+    .hero {
+      display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
+      margin-bottom: 22px;
+    }
+    .mark {
+      width: 46px; height: 46px; border-radius: 14px; flex: 0 0 auto;
+      display: grid; place-items: center;
+      background: linear-gradient(145deg, rgba(94, 234, 212, .95), rgba(129, 140, 248, .85));
+      box-shadow: 0 0 28px rgba(94, 234, 212, .35);
+      color: #071018; font-weight: 800; font-size: 18px; letter-spacing: -.04em;
+    }
+    .brand { display: flex; gap: 14px; align-items: center; }
+    .kicker {
+      font-size: 11px; letter-spacing: .28em; text-transform: uppercase;
+      color: var(--cyan); margin: 0 0 4px;
+    }
+    h1 { font-size: 28px; margin: 0; letter-spacing: -.03em; font-weight: 700; }
+    .sub { color: var(--muted); margin: 6px 0 0; font-size: 13px; line-height: 1.6; }
+    .chips { display: flex; gap: 8px; flex-wrap: wrap; }
+    .chip {
+      font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
+      border: 1px solid var(--line); color: var(--muted);
+      padding: 6px 10px; border-radius: 999px; background: rgba(255,255,255,.03);
+    }
+    .chip.on { color: var(--cyan); border-color: rgba(94, 234, 212, .4); }
+    .layout { display: grid; grid-template-columns: 1.15fr .85fr; gap: 16px; }
+    @media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
+    .stack { display: flex; flex-direction: column; gap: 16px; }
     .card {
-      background: var(--card); border: 1px solid var(--line); border-radius: 14px;
-      padding: 18px; margin-bottom: 16px;
+      background: var(--card);
+      border: 1px solid var(--line);
+      border-radius: 20px;
+      padding: 20px 20px 18px;
+      backdrop-filter: blur(18px);
+      box-shadow: var(--shadow);
+      position: relative;
+      overflow: hidden;
     }
-    .row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
-    .row h2 { margin: 0; font-size: 15px; }
+    .card::after {
+      content: "";
+      position: absolute; inset: 0 auto auto 0; width: 100%; height: 1px;
+      background: linear-gradient(90deg, transparent, rgba(94,234,212,.55), transparent);
+    }
+    .row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
+    .row h2, .card > h2 { margin: 0; font-size: 15px; font-weight: 650; letter-spacing: .02em; }
+    .step {
+      width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center;
+      font-size: 11px; font-weight: 700; color: #071018;
+      background: linear-gradient(145deg, var(--cyan), var(--violet));
+    }
+    .head { display: flex; align-items: center; gap: 10px; }
+    .head .hint { color: var(--muted); font-size: 12px; margin-left: 4px; }
     .btn {
-      border: 0; border-radius: 8px; padding: 8px 14px; cursor: pointer;
-      font-size: 13px; color: #0f172a; background: var(--accent); font-weight: 600;
-    }
-    .btn:disabled { opacity: .45; cursor: not-allowed; }
-    .btn.ghost { background: #334155; color: var(--text); }
-    .btn.go { background: #22c55e; color: #052e16; padding: 12px 22px; font-size: 15px; }
-    .drives { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
-    .drive {
-      border: 1px solid var(--line); border-radius: 10px; padding: 12px;
-      cursor: pointer; background: #0f172a; text-align: left;
-    }
-    .drive:hover { border-color: var(--accent); }
-    .drive.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
-    .drive.usb { border-color: #6366f1; }
-    .drive .t { font-size: 12px; color: var(--muted); }
-    .drive .n { font-size: 16px; font-weight: 700; margin: 4px 0; }
-    .drive .s { font-size: 12px; color: var(--muted); }
-    .tag { display: inline-block; font-size: 11px; padding: 1px 6px; border-radius: 999px; background: #334155; }
-    .tag.usb { background: #3730a3; color: #c7d2fe; }
-    .picked {
-      background: #0f172a; border: 1px dashed var(--line); border-radius: 10px;
-      padding: 14px 14px; font-size: 15px; word-break: break-all; min-height: 48px;
+      border: 0; border-radius: 12px; padding: 9px 14px; cursor: pointer;
+      font-size: 13px; font-weight: 650; color: #071018;
+      background: linear-gradient(180deg, #7ff5e0, #5eead4);
+      box-shadow: 0 8px 22px rgba(94, 234, 212, .22);
+      transition: transform .15s ease, filter .15s ease;
     }
-    .picked.empty { color: var(--muted); }
-    .folders { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
-    .folder {
-      background: #0f172a; border: 1px solid var(--line); color: var(--text);
-      border-radius: 8px; padding: 6px 10px; cursor: pointer; font-size: 13px;
+    .btn:hover { transform: translateY(-1px); filter: brightness(1.05); }
+    .btn:disabled { opacity: .4; cursor: not-allowed; transform: none; }
+    .btn.ghost {
+      background: transparent; color: var(--text);
+      border: 1px solid rgba(255,255,255,.12);
+      box-shadow: none;
     }
-    .check { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 13px; margin: 12px 0; }
-    .hint { color: var(--muted); font-size: 12px; margin: 8px 0 0; }
-    .bar { height: 10px; background: #0f172a; border-radius: 999px; overflow: hidden; margin: 10px 0 6px; }
-    .bar > span { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #22c55e, #38bdf8); transition: width .4s; }
-    .bar.indeterminate > span { width: 40% !important; animation: slide 1.2s infinite; }
-    @keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(280%); } }
-    .log {
-      background: #020617; border: 1px solid var(--line); border-radius: 10px;
-      padding: 12px; height: 200px; overflow: auto; font-family: Consolas, monospace;
-      font-size: 12px; white-space: pre-wrap; color: #cbd5e1;
+    .btn.go {
+      width: 100%; padding: 14px 22px; font-size: 15px; letter-spacing: .12em;
+      text-transform: uppercase;
+      background: linear-gradient(90deg, #22d3ee, #818cf8 60%, #5eead4);
+      color: #061018;
     }
     .form {
       display: grid;
-      grid-template-columns: 72px 1fr 72px 1fr;
-      gap: 10px 12px;
-      align-items: center;
+      grid-template-columns: 1fr 1fr;
+      gap: 14px 18px;
     }
-    .form label { color: var(--muted); font-size: 13px; }
-    .form input, .form select {
-      background: #0f172a; border: 1px solid var(--line); color: var(--text);
-      border-radius: 8px; padding: 8px 10px; font-size: 14px; width: 100%;
+    .field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
+    .field label { color: var(--muted); font-size: 12px; letter-spacing: .08em; }
+    .field input {
+      background: rgba(7, 8, 13, .72);
+      border: 1px solid rgba(255,255,255,.08);
+      color: var(--text);
+      border-radius: 12px; padding: 10px 12px; font-size: 14px; width: 100%;
+      outline: none;
     }
-    .form .readonly {
-      background: #0b1220; color: var(--muted);
+    .field input:focus { border-color: rgba(94, 234, 212, .55); box-shadow: 0 0 0 3px rgba(94, 234, 212, .12); }
+    .field .readonly { color: var(--muted); }
+    .form-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
+    .cfg-msg { display: block; margin: 0; min-height: 22px; font-size: 13px; font-weight: 600; flex: 1; }
+    .picked {
+      background: rgba(7, 8, 13, .65);
+      border: 1px dashed rgba(94, 234, 212, .28);
+      border-radius: 14px;
+      padding: 16px 14px; font-size: 14px; word-break: break-all; min-height: 52px;
     }
-    .cfg-msg { display: block; margin-top: 10px; min-height: 22px; font-size: 14px; font-weight: 600; }
+    .picked.empty { color: var(--muted); }
+    .check { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 13px; margin: 12px 0; }
+    .hint { color: var(--muted); font-size: 12px; margin: 8px 0 0; }
+    .bar {
+      height: 8px; background: rgba(255,255,255,.06); border-radius: 999px;
+      overflow: hidden; margin: 12px 0 8px; position: relative;
+    }
+    .bar > span {
+      display: block; height: 100%; width: 0;
+      background: linear-gradient(90deg, #22d3ee, #818cf8, #5eead4);
+      box-shadow: 0 0 16px rgba(34, 211, 238, .65);
+      transition: width .4s;
+    }
+    .bar.indeterminate > span { width: 38% !important; animation: slide 1.2s infinite; }
+    @keyframes slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(280%); } }
+    .log {
+      background: #05070c;
+      border: 1px solid rgba(255,255,255,.06);
+      border-radius: 14px;
+      padding: 14px; height: 100%; min-height: 280px; overflow: auto;
+      font-family: Consolas, "Cascadia Mono", monospace;
+      font-size: 12px; white-space: pre-wrap; color: #9fe8dc; line-height: 1.55;
+    }
+    .status { font-size: 13px; font-weight: 650; }
     .ok { color: var(--ok); } .bad { color: var(--bad); } .run { color: var(--warn); }
   </style>
 </head>
 <body>
+  <div class="orb a"></div>
+  <div class="orb b"></div>
   <div class="wrap">
-    <h1>MES 数据库手动备份 · 选文件夹</h1>
-    <p class="sub">独立工具,仅支持 MySQL。填写连接后,点「浏览文件夹」选择 U 盘或硬盘目录(不用手输、打开时不扫描磁盘)。数据会边导出边压缩,完成前不要拔盘。</p>
-
-    <div class="card">
-      <div class="row">
-        <h2>1. MySQL 连接(可配置)</h2>
-      </div>
-      <div class="form">
-        <label>类型</label>
-        <input class="readonly" value="MySQL" readonly />
-        <label>端口</label>
-        <input id="dbPort" type="number" min="1" max="65535" value="3306" />
-        <label>IP/主机</label>
-        <input id="dbHost" type="text" placeholder="127.0.0.1 或 192.168.x.x" />
-        <label>库名</label>
-        <input id="dbName" type="text" placeholder="mes_cloud" />
-        <label>账号</label>
-        <input id="dbUser" type="text" autocomplete="off" />
-        <label>密码</label>
-        <input id="dbPass" type="password" autocomplete="new-password" />
+    <div class="hero">
+      <div class="brand">
+        <div class="mark">M</div>
+        <div>
+          <p class="kicker">Neural Archive</p>
+          <h1>MES 智能备份控制台</h1>
+          <p class="sub">独立工具,仅支持 MySQL。填写连接后选择 U 盘或硬盘目录。数据边导出边压缩,完成前不要拔盘。</p>
+        </div>
       </div>
-      <div class="form-actions">
-        <button class="btn ghost" id="btnTest" type="button">测试连接</button>
-        <button class="btn" id="btnSaveCfg" type="button">保存配置</button>
+      <div class="chips">
+        <span class="chip on">Local Only</span>
+        <span class="chip">MySQL</span>
+        <span class="chip">Air-gapped</span>
       </div>
-      <div class="cfg-msg" id="cfgStatus">填写 IP、账号、密码后点「测试连接」</div>
     </div>
 
-    <div class="card">
-      <div class="row">
-        <h2>2. 选择保存位置</h2>
-        <button class="btn" id="btnBrowse" type="button">浏览文件夹...</button>
+    <div class="layout">
+      <div class="stack">
+        <div class="card">
+          <div class="row">
+            <div class="head"><span class="step">01</span><h2>连接矩阵</h2><span class="hint">可先测试,再保存到本机</span></div>
+          </div>
+          <div class="form">
+            <div class="field">
+              <label>类型</label>
+              <input class="readonly" value="MySQL" readonly />
+            </div>
+            <div class="field">
+              <label>端口</label>
+              <input id="dbPort" type="number" min="1" max="65535" value="3306" />
+            </div>
+            <div class="field">
+              <label>IP / 主机</label>
+              <input id="dbHost" type="text" placeholder="127.0.0.1 或 192.168.x.x" />
+            </div>
+            <div class="field">
+              <label>库名</label>
+              <input id="dbName" type="text" placeholder="mes_cloud" />
+            </div>
+            <div class="field">
+              <label>账号</label>
+              <input id="dbUser" type="text" autocomplete="off" />
+            </div>
+            <div class="field">
+              <label>密码</label>
+              <input id="dbPass" type="password" autocomplete="new-password" />
+            </div>
+          </div>
+          <div class="form-actions">
+            <button class="btn ghost" id="btnTest" type="button">测试连接</button>
+            <button class="btn" id="btnSaveCfg" type="button">保存配置</button>
+            <div class="cfg-msg" id="cfgStatus">填写 IP、账号、密码后点「测试连接」</div>
+          </div>
+        </div>
+
+        <div class="card">
+          <div class="row">
+            <div class="head"><span class="step">02</span><h2>归档目标</h2></div>
+            <button class="btn" id="btnBrowse" type="button">浏览文件夹</button>
+          </div>
+          <div class="picked empty" id="picked">尚未选择保存位置</div>
+          <p class="hint" id="spaceHint">插入 U 盘或移动硬盘后选择目录,无需手输路径。</p>
+          <label class="check">
+            <input id="keepOld" type="checkbox"/>
+            同时删除该目录中超过 7 天的旧备份(U 盘建议不勾选)
+          </label>
+          <div class="bar" id="bar"><span id="barInner"></span></div>
+          <div class="hint" id="progHint"></div>
+          <button class="btn go" id="btnBackup" type="button">开始备份</button>
+          <span class="status" id="status"></span>
+        </div>
       </div>
-      <p class="hint">打开页面不会扫描磁盘。插入 U 盘后点「浏览文件夹」,在系统窗口里选目录即可。</p>
-      <div class="picked empty" id="picked">尚未选择。请点「浏览文件夹」。</div>
-      <p class="hint" id="spaceHint"></p>
-      <label class="check">
-        <input id="keepOld" type="checkbox"/>
-        同时删除该目录中超过 7 天的旧备份(U 盘建议不勾选)
-      </label>
-      <div class="bar" id="bar"><span id="barInner"></span></div>
-      <div class="hint" id="progHint"></div>
-      <button class="btn go" id="btnBackup" type="button">开始备份</button>
-      <span class="status" id="status"></span>
-    </div>
 
-    <div class="card">
-      <h2>运行日志</h2>
-      <div class="log" id="log">等待操作...</div>
+      <div class="card" style="min-height: 520px; display:flex; flex-direction:column;">
+        <div class="row">
+          <div class="head"><span class="step">03</span><h2>运行控制台</h2></div>
+        </div>
+        <div class="log" id="log">等待操作...</div>
+      </div>
     </div>
   </div>
   <script>
@@ -192,7 +316,7 @@
       var el = $("picked");
       if (!selectedPath) {
         el.className = "picked empty";
-        el.textContent = "尚未选择。请点「浏览文件夹」。";
+        el.textContent = "尚未选择保存位置";
       } else {
         el.className = "picked";
         el.textContent = selectedPath;

+ 0 - 0
scripts/db-config.json


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio