﻿N-SRM PO 订单工具 —— 打包与使用说明
==========================================

版本：v3.0（等待查询结果提速：双通道并行竞速）

【v3.0 更新】解决"每次等查询结果都特别久"：
  - 现象：浏览器窗口里结果早就出来了，工具还在干等，日志一直停在"等待查询结果"。
  - 原因：旧逻辑是串行单通道——死等查询接口返回，最多等 45 秒，
    超时才回退去读页面网格。但页面网格其实早就渲染出结果了。
  - 修复：改为【接口抓包 + 页面网格】两条通道同时跑（并行竞速）：
      · 通道A：接口一返回数据立即用（原来的方式，保留）
      · 通道B：每 1.5 秒读一次页面网格，网格里有数据立即用（新增）
    → 谁先出数据用谁的，实测浏览器网格先出时，等待从 45 秒降到 1~2 秒。
  - 兜底提前：两条通道 15 秒都还没数据（多半是按钮被浮层挡了），
    自动清浮层重试搜索一次（原来要干等 45 秒才重试）。
  - 数据来源无差别：无论走接口还是网格，抓到后都同样进入筛选与导出，
    行数、字段完全一致，不会多也不会漏。

【v2.9 更新】修复复用档案后导出报 NoneType 的 bug：
  - 现象：无头被拦 → 自动降级有头 → 登录成功 → 抓到 15 行，
    但最后导出时失败：'NoneType' object has no attribute 'close'
  - 原因：改用持久化浏览器档案（launch_persistent_context）后，只把 context 存进
    browser_holder["context"]，没把 browser 变量指向它。收尾的 browser.close()
    对 None 调用就炸了。
  - 修复：把 persistent context 直接赋给 browser 变量（它自带 close()），
    并让 browser_holder["browser"] 也指向它 —— 顺带修复"停止"按钮关不掉浏览器的问题。
  - 加固：收尾关闭浏览器改为 try/except 包裹。数据抓到手之后，
    "关浏览器失败"绝不能再把整个导出搞挂（已经白跑过一次了）。

【v2.8 更新】关键点：用户反馈"之前无头模式一直可以"——说明不是代码问题，
而是 Cloudflare 近期升级了检测规则（或 Chrome 更新后 headless 指纹变了）。
既然代码层面已尽力，本版重点做**兜底，不让工具卡死**：
  - **自动降级**：无头模式被 Cloudflare 拦截 → 自动切成有头模式重试一次。
    你不用手动改设置，工具自己兜底（代价是会弹出浏览器窗口）。
  - **复用浏览器档案**：用固定目录保存浏览器 profile，Cloudflare 放行后下发的
    cf_clearance cookie 会留存，后续无头访问带上这个"已放行"凭证，
    能显著提升无头成功率（第一次通常要有头跑通一次来拿凭证）。
  - **快速失败**：每 2 秒检测一次 Cloudflare 页面，一旦识别立刻失败并降级，
    不用再干等 45 秒超时（实测 0.1 秒就能识别）。
  - GUI 无头勾选框文案已更新，说明"被拦会自动改为有头重试"。

【v2.7 更新】识别到根本原因：N-SRM（nsrm.secbuy.com）启用了
Cloudflare 反爬，无头 Chrome 因 navigator.webdriver=true 等特征被识别为爬虫，
直接显示"Sorry, you have been blocked"，根本到不了登录页。已做：
  - 启动参数改用 Chrome 119+ 的新版 headless（--headless=new）+
    --disable-blink-features=AutomationControlled，减少自动化痕迹
  - 每个新 context 注入 stealth JS（修补 navigator.webdriver=false、
    window.chrome.runtime、navigator.plugins 长度、languages、webGL vendor）
  - 自动诊断新增 Cloudflare 检测，识别到会明确告诉你"被反爬拦了，
    建议改用有头模式"，而不是只说"找不到输入框"

【v2.6 更新】修复「调试截图找不到」+ 新增登录页自动诊断：
  - 问题：之前失败时截图默认存到系统临时目录，弹窗只说"请查看 nsrm_debug"，
    但用户根本不知道这个目录在哪。
  - 解法：截图存到**导出目录旁的 nsrm_debug**（路径已知），错误弹窗里直接
    显示完整绝对路径，并自动询问【是否打开文件夹】。
  - 新增 login_form_missing_*.diag.txt：失败时同时输出页面诊断文本
    （URL / 标题 / readyState / input 数 / shadowRoot 数 / 页面文字摘要），
    即使不会看截图，也能把这份 .diag.txt 发给维护者直接定位问题。
  - 错误信息末尾会包含完整 dump位置路径，**不要再说"找不到截图"**。

【v2.5 更新】修复无头模式登录页面定位账号/密码框超时：
  - 原因：有头模式浏览器渲染完整，Polymer 组件立即就位；
    无头模式下组件要 lazy-load 一会儿，单纯 `wait_for_selector 20s` 等不到。
  - 解法：先等 networkidle 让 JS 跑完；用 CSS 轮询查找（穿透 open Shadow DOM）；
    仍找不到再用注入的 __nsrmWalk 遍历器兜底（处理封闭 Shadow DOM）；
    兜底全部失败时自动保存截图和页面源码到 `nsrm_debug/` 目录。
  - 登录结果校验（仍停在登录页吗）也改成走遍历器，避免误判。
  - 超时放宽到 45 秒（默认），适应慢速网络。

【v2.0 更新】客户门户界面改版，抓取流程失效，已做以下修复：
  1. 新增「Seller 自动选择」
     改版后进入订单页必须先在网页上选定 Seller，否则查询返回 0 条。
     现在工具会自动把 Seller 条件（如 E0XB）填进网页的 Seller 输入框；
     遇到“放大镜/搜索窗”形式的 Seller 选择器，会自动打开 → 输入编码 →
     搜索 → 选中匹配行 → 确认带回。若当前界面没有 Seller 控件则自动跳过，
     不影响旧版界面使用。
  2. 新增「弹窗自动清理」
     登录后、进入订单页后、等待查询期间，都会循环关闭遮挡搜索面板的浮层
     （公告 / 帮助手册 / 通知 / Dashboard）。同一浮层连点两次仍不消失时，
     自动升级为直接移除，避免空转。
  3. 数据读取改为「接口抓包 + 页面网格」双通道
     优先直接捕获查询接口的返回数据（最可靠），取不到再回退读页面网格；
     并且网格读取与组件定位都支持穿透 Shadow DOM，不会误判成 0 条。
  4. 日期写入双保险
     同时用「输入框事件」和「页面组件参数」两种方式写入，避免 Polymer
     日期控件对键盘输入绑定失效导致日期没生效。
  5. 失败自动存证
     登录失败或查不到数据时，自动把截图、页面源码、诊断文本保存到
     **导出目录旁的 `nsrm_debug` 文件夹**。错误弹窗会直接给出绝对路径
     并询问是否打开文件夹，方便直接看清新界面长什么样、快速定位。

零、不想等 Windows 重打 EXE？想先试用新代码？
-------------------------------------------
你电脑上有 Python 的话，直接命令行 cd 到解压目录跑：
    pip install -r requirements.txt          # 一次性安装依赖
    playwright install chromium              # 一次性下载 Chromium
    python po_tool.py --url <门户地址> --account <账号> --password <密码> ^
                      --from 2026-08-01 --to 2026-08-31 --out D:\导出.xlsx ^
                      --headless --seller E0XB
（命令行里的 ^ 是换行连接符，CMD 用 ^、PowerShell 用 ` 行尾；详细参数见末尾"命令行用法"段）

或者直接启动 GUI：
    python po_tool.py

这样跑的就是最新的源码修复，EXE 之后想打再打不迟。
**注意**：PyInstaller 打包必须在 Windows 上做；Mac / Linux 上 `pyinstaller` 出来的是
对应平台的二进制，拿到 Windows 上跑不起来。

一、Windows 打包步骤
--------------------
1. 安装 Python 3.10 或以上版本（安装时务必勾选 "Add Python to PATH"，这是最常见报错原因）。
2. 把本文件夹整体复制到 Windows 电脑。
3. 双击运行 build.bat。
4. 等待几十秒，脚本会自动安装依赖并用 PyInstaller 打包。
5. 打包完成后，生成的是【单个 EXE 文件】：dist\POOrderTool.exe
   这是一个完整的可执行文件，不需要任何配套文件即可运行。
6. 直接把 POOrderTool.exe（需要改名可改成“PO订单工具.exe”）发给最终用户即可。
   第一次运行并点“保存配置”后，会在 EXE 所在的同一个文件夹里自动生成
   配置文件 .po_tool_config.json；以后每次打开都能读回，配置始终跟着 EXE 走。

【重要提醒】
- 最终用户电脑必须安装 Google Chrome（工具用本机 Chrome 驱动网页）。
- build.bat 必须保持纯英文 ASCII 内容；请勿用 Windows 记事本打开并保存 build.bat，否则会破坏编码/换行符，再次运行就会像乱码一样把命令拆碎。
- 配置文件（含混淆后的密码）位于 EXE 同一个文件夹：
    （EXE 所在目录）\.po_tool_config.json
  请勿把该配置文件随 EXE 一起发给外人，否则可能泄露客户系统密码。
- 建议把 EXE 放在普通目录（如桌面/下载/工作目录），不要放在
  C:\Program Files 等受系统保护的目录，否则系统禁止写入、配置会自动回退到用户目录。

二、最终用户使用说明（图形界面）
--------------------------------
1. 双击 POOrderTool.exe（或改名后的 PO订单工具.exe）启动，无需安装 Python。
2. 顶部“网站链接”已默认填好客户系统地址，一般不用改。
3. 填写“网站账号”和“密码”。
4. 如需下次免填密码，勾选“记住密码”。
5. 填写“抓取日期范围” From / To（格式 YYYY-MM-DD，例如 2026-08-10）。
6. 需要筛选就在下面四个框粘贴条件：
     Seller / PO NO / Material / PO Seq
     可从表格复制多个值，自动按 换行 / 逗号 / 分号 / Tab 拆分；
     同一框内多值 = “或”，多个字段之间 = “且”。

   【Seller 的特殊说明 —— 与旧版不同，请留意】
   旧版里 Seller 只在“导出前”用 Python 过滤，网页查询不限 Seller。
   客户门户改版后，不先在网页上选定 Seller 就查不到数据，所以现在改为：

     · 只填一个值（如 E0XB，界面默认已填）
       → 工具先在网页上选中 E0XB 再查询，导出前再按同样条件复筛一次。
         两道筛的是同一个条件，结果不会变多也不会漏。

     · 填了多个值（如 E0XB, E0XC）
       → 网页的 Seller 选择器只接受单个编码，表达不了“或”。
         若只选第一个值，网页就不会再返回其余值的数据，本地过滤想留也留不住，
         会静默漏数据。因此多值时工具【不在网页选择】，改为按账号默认范围
         全量查询，再由本地“或”过滤，保证不漏。日志里会写明走了哪种方式。

     · 留空
       → 不在网页选择，按账号默认范围查询（与旧版一致）。
7. 设置“导出文件保存目录”（点浏览选择文件夹；文件名每次运行自动生成，不会互相覆盖）。
8. 勾选“后台无头模式（不显示浏览器窗口）”——勾上后抓取时不会弹出
   Chrome 窗口，适合挂计划任务后台跑；不勾则正常弹出浏览器。
9. 点“从网站抓取并导出”，下方日志窗口会显示进度。
10. 完成后弹出结果，Excel 已保存到指定地址。

三、本地 Excel 筛选模式（不联网）
----------------------------------
点“选择本地 Excel 并筛选导出”，选择一个已导出的报表，
填好筛选条件，点按钮即可。此模式不需要登录网站。

四、保存配置
------------
填好账号/密码/日期/路径/筛选条件后，点“保存配置”，
下次打开会自动加载，无需重填。

五、后台无头 / 命令行模式（可挂计划任务，完全无界面）
----------------------------------------------------
除了图形界面，工具还支持纯命令行运行：不弹任何窗口、不显示浏览器，
一条命令直接产出 Excel，非常适合用系统计划任务定时执行。

用法（在 EXE 所在目录打开命令行，或配 Windows 任务计划程序）：
  POOrderTool.exe --url https://nsrm.secbuy.com/spDxLogin.do ^
      --account 你的账号 --password 你的密码 ^
      --from 2026-08-10 --to 2026-08-16 ^
      --out D:\PO_List.xlsx ^
      --material "MAT001,MAT002" --pono "PO123"

参数说明：
  --url       登录页地址（默认客户系统，可省略）
  --account   网站账号
  --password  网站密码（明文传参；计划任务请用文件权限保护好本机）
  --from/--to 日期，格式 YYYY-MM-DD
  --out       导出 Excel 的完整路径
  --seller/--pono/--material/--poseq  筛选值，多个用逗号分隔；省略则不筛选
所有进度会打印到命令行，可重定向到日志文件，例如 >> run.log 2>&1。

定时示例（Windows 任务计划程序）：
  触发器设“每天 08:00”，操作启动程序 POOrderTool.exe，
  参数填上面的命令行（把账号密码和日期换成实际值）。

六、常见问题
------------
Q1: 双击 build.bat 后报错 "'python' 不是内部或外部命令" 或 "'pip' 不是内部或外部命令"？
A: 说明 Python 没有加入系统 PATH。请重新运行 Python 安装程序，在安装界面
   勾选 "Add Python to PATH"（勾选后点 Install Now），然后打开一个全新的
   命令提示符窗口，再双击 build.bat。不要重复用旧的 cmd 窗口。

Q2: build.bat 运行后显示很多碎片命令，比如 'Building'、'stall'、'orlevel' 等？
A: 说明 build.bat 在传输/编辑过程中编码或换行符被破坏了。典型原因：用记事本
   打开并保存过 build.bat，或解压工具把 LF 换行当成了 CRLF。解决办法：重新从
   po_tool_package.zip 里解压一份 build.bat，不要再用记事本改它；中文说明请
   只看 README.txt。

Q3: 打包出来的 EXE 在最终用户机上打开中文乱码？
A: 工具已经按平台指定了中文字体。如果还乱码，通常是 Windows 系统缺少中文字体，
   或 EXE 在传输中被破坏。可在另一台 Win10/Win11 机器上重新打包测试。

Q4: 提示“查询已执行但没有取到数据”怎么办？
A: 按下面顺序排查：
   1. 先看日志里是否出现「🏷 Seller 选择」——如果显示“页面上未找到 Seller
      输入控件”，说明当前界面不需要选 Seller，属正常；如果显示异常，请把
      Seller 填成 E0XB 再跑一次。
   2. 去导出目录旁的 nsrm_debug 文件夹（或系统临时目录 %TEMP%\nsrm_debug）
      查看自动保存的截图 debug_empty_*.png，能直接看到当时界面卡在哪一步。
   3. 缩小日期范围（例如只查一周），确认该区间确实有订单。
   4. 仍不行时，把日志文字和截图一起发给工具维护者。


==========================================
七、2026-11-06 更新：导出报表新增 5 列
------------------------------------------
从网站抓取并导出时，Excel 由原来的 20 列变为 25 列，
新增的 5 列追加在最后，原 20 列的顺序和内容完全不变：

  显示列名          取自客户系统字段   页面全称
  PC Qty            pcr_qty           P/O Change Request Quantity（页面缩写 PCR Qty）
  Invoice Qty       invoice_qty       Invoice Quantity
  GR Qty            gr_qty            Goods Receipt Quantity
  Manual GR Qty     manual_gr_qty     Manual Goods Receipt Quantity
  Balance           balance           Balance

说明：
- 这 5 列本来就在客户系统 PO List 页面和接口返回的数据里，
  旧版只是没有映射出来，不是新算出来的字段，数值与页面一致。
- 只有「从网站抓取并导出」会带这 5 列；
  「选择本地 Excel 并筛选导出」读的是旧报表，原样保留旧文件的列。
- 若要把列名 PC Qty 改成 PCR Qty，或把 5 列插到 PO Qty 后面，
  改 po_tool.py 里的 MAPPING 顺序即可。
