# -*- coding: utf-8 -*-
"""Patch wms-v3-blueprint-prototype.html for blueprint alignment checklist."""
from pathlib import Path

PATH = Path(__file__).resolve().parent / "wms-v3-blueprint-prototype.html"
text = PATH.read_text(encoding="utf-8")

def must_replace(old: str, new: str, label: str):
    global text
    if old not in text:
        raise SystemExit(f"MISSING anchor: {label}")
    text = text.replace(old, new, 1)

# --- 1. Warehousing sections ---
must_replace(
    """  const WH_FEAT_NAV = {
    23: { section: "purchase", tab: "order" },
    24: { section: "purchase", tab: "arrival" },
    25: { section: "purchase", tab: "hint" },
    26: { section: "purchase", tab: "visual" },
    27: { section: "purchase", tab: "receive" },
    28: { section: "purchase", tab: "loc" },
    29: { section: "purchase", tab: "iqc" },
    30: { section: "purchase", tab: "label" },
    31: { section: "purchase", tab: "reject" },
    32: { section: "return", tab: "mes" },
    33: { section: "return", tab: "lims" },
    34: { section: "return", tab: "review" },
    35: { section: "finish", tab: "mes" },
    36: { section: "finish", tab: "instant" },
    37: { section: "finish", tab: "wcs" },
    38: { section: "finish", tab: "post" },
    39: { section: "fg", tab: "order" },
    40: { section: "fg", tab: "scan" },
  };""",
    """  const WH_FEAT_NAV = {
    23: { section: "purchase", tab: "order" },
    24: { section: "purchase", tab: "arrival" },
    25: { section: "purchase", tab: "hint" },
    26: { section: "purchase", tab: "visual" },
    27: { section: "purchase", tab: "receive" },
    28: { section: "purchase", tab: "loc" },
    29: { section: "purchase", tab: "iqc" },
    30: { section: "purchase", tab: "label" },
    31: { section: "purchase", tab: "reject" },
    32: { section: "return", tab: "mes" },
    33: { section: "return", tab: "lims" },
    34: { section: "return", tab: "review" },
    35: { section: "finish", tab: "mes" },
    36: { section: "finish", tab: "instant" },
    37: { section: "finish", tab: "wcs" },
    38: { section: "finish", tab: "post" },
    39: { section: "fg", tab: "order" },
    40: { section: "fg", tab: "scan" },
    113: { section: "semi", tab: "order" },
    114: { section: "semi", tab: "scan" },
    115: { section: "rtn", tab: "order" },
    116: { section: "rtn", tab: "scan" },
  };""",
    "WH_FEAT_NAV",
)

must_replace(
    """  const WH_SECTION_META = {
    purchase: { title: "采购入库", count: 9, level: "L" },
    return: { title: "退料入库", count: 3, level: "S" },
    finish: { title: "完工入库", count: 3, level: "M" },
    fg: { title: "成品入库", count: 2, level: "M" },
  };""",
    """  const WH_SECTION_META = {
    purchase: { title: "采购入库", count: 9, level: "L" },
    return: { title: "退料入库", count: 3, level: "S" },
    finish: { title: "完工入库", count: 4, level: "M" },
    fg: { title: "成品入库", count: 2, level: "M" },
    semi: { title: "半成品入库", count: 2, level: "M" },
    rtn: { title: "退货入库", count: 2, level: "S" },
  };""",
    "WH_SECTION_META",
)

must_replace(
    """    fg: [
      ["order", "入库单"],
      ["scan", "扫码入库"],
    ],
  };""",
    """    fg: [
      ["order", "入库单"],
      ["scan", "扫码入库"],
    ],
    semi: [
      ["order", "MES入库单"],
      ["scan", "PDA扫码入库"],
    ],
    rtn: [
      ["order", "退货单协同"],
      ["scan", "PDA复核入库"],
    ],
  };""",
    "WH_TAB_LABELS",
)

WH_PANEL_INSERT = r"""    } else if (sec === "semi" && tab === "order") {
      const semiRows = (state.semiFinishedInbound || [])
        .map(
          (s) =>
            `<tr data-feat-id="113" data-crud-store="semiFinishedInbound" data-crud-id="${s.id}"><td>${s.id}</td><td>${s.wo}</td><td>${s.sku}</td><td>${s.batch}</td><td>${s.qty}</td><td>${tagStatus(s.status === "done" ? "done" : s.status === "putaway" ? "putaway" : "pending")}</td><td>${renderCrudRowOps("semiFinishedInbound", s.id, '<button type="button" class="btn btn-sm btn-link btn-semi-pda">PDA入库</button>')}</td></tr>`,
        )
        .join("");
      body = `<p class="base-panel-hint" data-feat-id="113">MES 半成品完工入库申请同步 WMS（区别于成品 #39）；目标仓库为<strong>半成品仓</strong>。支持<strong>新增 / 编辑 / 删除</strong>。</p>
        ${crudToolbarHtml("semiFinishedInbound", "新建半成品入库单")}
        ${gcTableCard(
          "<tr><th>入库单</th><th>工单</th><th>SKU</th><th>批次</th><th>数量</th><th>状态</th><th>操作</th></tr>",
          semiRows || `<tr><td colspan="7" class="empty-hint">暂无半成品入库单</td></tr>`,
          (state.semiFinishedInbound || []).length,
        )}`;
    } else if (sec === "semi" && tab === "scan") {
      body = `<p class="base-panel-hint" data-feat-id="114">PDA 扫描半成品条码、WMS 推荐库位（WH-03 半成品区），入库后推送 K3。</p>
        <div class="toolbar admin-toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary btn-semi-pda">PDA 半成品扫码入库</button></div>
        ${gcTableCard(
          "<tr><th>入库单</th><th>推荐库位</th><th>序列号绑定</th><th>操作</th></tr>",
          (state.semiFinishedInbound || []).map((s) => `<tr data-feat-id="114"><td>${s.id}</td><td>S-03-02-01</td><td>可选 SN</td><td><button type="button" class="btn btn-sm btn-link btn-semi-pda">PDA</button></td></tr>`).join(""),
          (state.semiFinishedInbound || []).length,
        )}`;
    } else if (sec === "rtn" && tab === "order") {
      const rtnRows = (state.salesReturnInbound || [])
        .map(
          (r) =>
            `<tr data-feat-id="115" data-crud-store="salesReturnInbound" data-crud-id="${r.id}"><td>${r.id}</td><td>${r.returnType}</td><td>${r.sourceNo}</td><td>${r.sku}</td><td>${r.qty}</td><td>${tagStatus(r.status === "done" ? "done" : r.status === "iqc" ? "iqc" : "pending")}</td><td>${renderCrudRowOps("salesReturnInbound", r.id)}</td></tr>`,
        )
        .join("");
      body = `<p class="base-panel-hint" data-feat-id="115">销售/采购<strong>退货入库</strong>（区别于 IQC 不合格退回 #31）：接收 K3 退货单或 SRM 退供单，生成 WMS 收货任务。</p>
        ${crudToolbarHtml("salesReturnInbound", "新建退货入库单")}
        ${gcTableCard(
          "<tr><th>退货入库单</th><th>类型</th><th>来源单号</th><th>SKU</th><th>数量</th><th>状态</th><th>操作</th></tr>",
          rtnRows || `<tr><td colspan="7" class="empty-hint">暂无退货单</td></tr>`,
          (state.salesReturnInbound || []).length,
        )}`;
    } else if (sec === "rtn" && tab === "scan") {
      body = `<p class="base-panel-hint" data-feat-id="116">PDA 复核退货实物与单据，更新库存并回传 K3（销退/采退）。</p>
        <div class="toolbar admin-toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary btn-rtn-pda">PDA 退货复核入库</button></div>
        ${gcTableCard(
          "<tr><th>退货单</th><th>SKU</th><th>质检</th><th>操作</th></tr>",
          (state.salesReturnInbound || []).map((r) => `<tr data-feat-id="116"><td>${r.id}</td><td>${r.sku}</td><td>${r.status === "iqc" ? "待检" : "可入库"}</td><td><button type="button" class="btn btn-sm btn-link btn-rtn-pda">PDA</button></td></tr>`).join(""),
          (state.salesReturnInbound || []).length,
        )}`;

    body = ensurePanelBody(body, {
      sectionTitle: WH_SECTION_META[sec]?.title,
      tabTitle: tabLabelFrom(WH_TAB_LABELS, sec, tab),
      featId: Number(Object.entries(WH_FEAT_NAV).find(([, v]) => v.section === sec && v.tab === tab)?.[0]) || undefined,
    });

    const sectionTabs = Object.entries(WH_SECTION_META)"""

must_replace(
    """    } else if (sec === "fg" && tab === "scan") {
      body = `<p class="base-panel-hint" data-feat-id="40">PDA 扫描校验、WMS 推荐库位、入库推送 K3，附带质检报告。</p>
        <div class="toolbar admin-toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary btn-fg-pda">PDA 扫码入库</button></div>
        ${gcTableCard(
          "<tr><th>入库单号</th><th>工单</th><th>推荐库位</th><th>质检报告</th><th>操作</th></tr>",
          state.finishedInbound.map((f) => `<tr><td>${f.id}</td><td>${f.wo}</td><td>${f.loc}</td><td>已关联 LIMS</td><td><button type="button" class="btn btn-sm btn-link btn-fg-pda">PDA</button></td></tr>`).join(""),
          state.finishedInbound.length,
        )}`;
    }

    body = ensurePanelBody(body, {
      sectionTitle: WH_SECTION_META[sec]?.title,
      tabTitle: tabLabelFrom(WH_TAB_LABELS, sec, tab),
      featId: Number(Object.entries(WH_FEAT_NAV).find(([, v]) => v.section === sec && v.tab === tab)?.[0]) || undefined,
    });

    const sectionTabs = Object.entries(WH_SECTION_META)""",
    """    } else if (sec === "fg" && tab === "scan") {
      body = `<p class="base-panel-hint" data-feat-id="40">PDA 扫描校验、WMS 推荐库位、入库推送 K3，附带质检报告。</p>
        <div class="toolbar admin-toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary btn-fg-pda">PDA 扫码入库</button></div>
        ${gcTableCard(
          "<tr><th>入库单号</th><th>工单</th><th>推荐库位</th><th>质检报告</th><th>操作</th></tr>",
          state.finishedInbound.map((f) => `<tr><td>${f.id}</td><td>${f.wo}</td><td>${f.loc}</td><td>已关联 LIMS</td><td><button type="button" class="btn btn-sm btn-link btn-fg-pda">PDA</button></td></tr>`).join(""),
          state.finishedInbound.length,
        )}`;
""" + WH_PANEL_INSERT,
    "warehousing semi/rtn panels",
)

# --- 2. Outbound delivery signoff ---
must_replace(
    """  const OUT_FEAT_NAV = {
    72: { section: "strategy", tab: "fifo" },
    73: { section: "strategy", tab: "manual" },
    74: { section: "strategy", tab: "custom" },
    75: { section: "transfer", tab: "order" },
    76: { section: "transfer", tab: "pickShip" },
    77: { section: "transfer", tab: "close" },
    78: { section: "fg", tab: "plan" },
    79: { section: "fg", tab: "task" },
    80: { section: "fg", tab: "pdaShip" },
    81: { section: "other", tab: "special" },
    82: { section: "other", tab: "finance" },
    83: { section: "other", tab: "compliance" },
  };""",
    """  const OUT_FEAT_NAV = {
    72: { section: "strategy", tab: "fifo" },
    73: { section: "strategy", tab: "manual" },
    74: { section: "strategy", tab: "custom" },
    75: { section: "transfer", tab: "order" },
    76: { section: "transfer", tab: "pickShip" },
    77: { section: "transfer", tab: "close" },
    78: { section: "fg", tab: "plan" },
    79: { section: "fg", tab: "task" },
    80: { section: "fg", tab: "pdaShip" },
    125: { section: "delivery", tab: "signConfirm" },
    126: { section: "delivery", tab: "signSync" },
    81: { section: "other", tab: "special" },
    82: { section: "other", tab: "finance" },
    83: { section: "other", tab: "compliance" },
  };""",
    "OUT_FEAT_NAV",
)

must_replace(
    """  const OUT_SECTION_META = {
    strategy: { title: "出库策略", count: 3, level: "M" },
    transfer: { title: "产品调拨", count: 3, level: "M" },
    fg: { title: "成品出库", count: 3, level: "M" },
    other: { title: "其他出库", count: 3, level: "S" },
  };""",
    """  const OUT_SECTION_META = {
    strategy: { title: "出库策略", count: 3, level: "M" },
    transfer: { title: "产品调拨", count: 3, level: "M" },
    fg: { title: "成品出库", count: 3, level: "M" },
    delivery: { title: "发运交付", count: 2, level: "S" },
    other: { title: "其他出库", count: 3, level: "S" },
  };""",
    "OUT_SECTION_META",
)

must_replace(
    """    fg: [
      ["plan", "出货计划同步"],
      ["task", "出库任务推送"],
      ["pdaShip", "PDA 分拣与发运"],
    ],
    other: [""",
    """    fg: [
      ["plan", "出货计划同步"],
      ["task", "出库任务推送"],
      ["pdaShip", "PDA 分拣与发运"],
    ],
    delivery: [
      ["signConfirm", "签收/交付确认"],
      ["signSync", "状态回写 WMS/K3"],
    ],
    other: [""",
    "OUT_TAB_LABELS",
)

must_replace(
    """    } else if (sec === "fg" && tab === "pdaShip") {
      body = `<p class="base-panel-hint" data-feat-id="80">PDA 选择出货任务、指引库位分拣、扫描条码关联出库单发运。</p>
        ${gcQuery([{ label: "出库单号", key: "ck", ph: "CK20260928003" }, { label: "客户", key: "cus", ph: "客户名称" }])}
        ${gcTableCard(
          "<tr><th>出库单号</th><th>单据类型</th><th>客户</th><th>计划出库</th><th>实际出库</th><th>状态</th><th>操作</th></tr>",
          outRows,
          state.outbound.length,
        )}`;
    } else if (sec === "other" && tab === "special") {
      body = `<p class="base-panel-hint" data-feat-id="81">库间调拨、样品留样、杂项其他出库、物料报废四大特殊消耗业务。支持<strong>新增 / 编辑 / 删除</strong>。</p>""",
    """    } else if (sec === "fg" && tab === "pdaShip") {
      body = `<p class="base-panel-hint" data-feat-id="80">PDA 选择出货任务、指引库位分拣、扫描条码关联出库单发运；发运后进入<strong>签收确认 #125</strong>。</p>
        ${gcQuery([{ label: "出库单号", key: "ck", ph: "CK20260928003" }, { label: "客户", key: "cus", ph: "客户名称" }])}
        ${gcTableCard(
          "<tr><th>出库单号</th><th>单据类型</th><th>客户</th><th>计划出库</th><th>实际出库</th><th>状态</th><th>操作</th></tr>",
          outRows,
          state.outbound.length,
        )}`;
    } else if (sec === "delivery" && tab === "signConfirm") {
      const rows = (state.deliverySignoffs || [])
        .map(
          (d) =>
            `<tr data-feat-id="125" data-crud-store="deliverySignoffs" data-crud-id="${d.outboundId}"><td>${d.outboundId}</td><td>${d.customer}</td><td>${d.shipStatus}</td><td>${d.signStatus}</td><td>${d.signedBy || "—"}</td><td>${d.signedAt || "—"}</td><td>${d.signStatus === "待签收" ? `<button type="button" class="btn btn-sm btn-link btn-delivery-sign-pda">PDA签收</button> <button type="button" class="btn btn-sm btn-link btn-delivery-sign" data-oid="${d.outboundId}">PC确认签收</button>` : "—"}</td></tr>`,
        )
        .join("");
      body = `<p class="base-panel-hint" data-feat-id="125">货物到达目的地后，收货方（客户/承运）扫码或 PC 录入<strong>签收/交付确认</strong>，作为订单交付闭环。</p>
        ${gcTableCard(
          "<tr><th>出库单</th><th>客户</th><th>发运状态</th><th>签收状态</th><th>签收人</th><th>签收时间</th><th>操作</th></tr>",
          rows || `<tr><td colspan="7" class="empty-hint">暂无待签收发运单</td></tr>`,
          (state.deliverySignoffs || []).length,
        )}`;
    } else if (sec === "delivery" && tab === "signSync") {
      body = `<p class="base-panel-hint" data-feat-id="126">签收结果回写 WMS 出库单状态，并同步金蝶 K3 销售出库/发运确认。</p>
        <div class="toolbar admin-toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary" id="btn-delivery-sync-k3">同步 K3 签收状态</button></div>
        ${gcTableCard(
          "<tr><th>出库单</th><th>WMS状态</th><th>K3回写</th><th>最近同步</th></tr>",
          (state.deliverySignoffs || []).map((d) => `<tr data-feat-id="126"><td>${d.outboundId}</td><td>${d.signStatus}</td><td>${d.k3Synced ? "已回写" : "待回写"}</td><td>${d.k3SyncedAt || "—"}</td></tr>`).join(""),
          (state.deliverySignoffs || []).length,
        )}`;
    } else if (sec === "other" && tab === "special") {
      body = `<p class="base-panel-hint" data-feat-id="81">库间调拨、样品留样、杂项其他出库、物料报废；蓝图还要求<strong>取样、内销</strong>消耗（已纳入类型）。<span class="tag tag-gray">捐赠=非标演示项</span>。支持<strong>新增 / 编辑 / 删除</strong>。</p>""",
    "outbound delivery + special hint",
)

# --- 3. Trace serial ---
must_replace(
    """    64: { section: "trace", tab: "ledger" },
    65: { section: "trace", tab: "integration" },
    66: { section: "count", tab: "strategy" },""",
    """    64: { section: "trace", tab: "ledger" },
    65: { section: "trace", tab: "integration" },
    121: { section: "trace", tab: "serialQuery" },
    122: { section: "trace", tab: "serialBind" },
    66: { section: "count", tab: "strategy" },""",
    "INV_FEAT_NAV serial",
)

must_replace(
    """    trace: { title: "质量追溯", count: 2, level: "M" },""",
    """    trace: { title: "质量追溯", count: 4, level: "M" },""",
    "INV trace count",
)

must_replace(
    """    trace: [
      ["ledger", "全周期追溯台账"],
      ["integration", "多系统追溯集成"],
    ],""",
    """    trace: [
      ["ledger", "全周期追溯台账"],
      ["integration", "多系统追溯集成"],
      ["serialQuery", "序列号查询"],
      ["serialBind", "序列号绑定"],
    ],""",
    "INV trace tabs",
)

must_replace(
    """    } else if (sec === "trace" && tab === "integration") {
      const logHint = (state.integrationSyncLogs || [])[0];
      body = `<p class="base-panel-hint" data-feat-id="65">与金蝶 K3、MES、LIMS 集成；入库标记订单来源，出库标记生产批次。最近接口：${logHint ? logHint.code + " · " + logHint.trigger : "—"}</p>${gcTableCard(
        "<tr><th>系统</th><th>方向</th><th>标记字段</th><th>最近同步</th><th>状态</th></tr>",
        `<tr><td>金蝶 K3</td><td>双向</td><td>订单来源 / 货主 / PO#</td><td>2026-09-28 09:00</td><td>${tagStatus("done")}</td></tr>
        <tr><td>MES</td><td>WMS→MES</td><td>生产批次 / WO#</td><td>2026-09-28 10:15</td><td>${tagStatus("done")}</td></tr>
        <tr><td>LIMS</td><td>双向</td><td>质检放行 / 报检单</td><td>2026-09-28 08:30</td><td>${tagStatus("done")}</td></tr>`,
        3,
      )}`;
    } else if (sec === "count" && tab === "strategy") {""",
    """    } else if (sec === "trace" && tab === "integration") {
      const logHint = (state.integrationSyncLogs || [])[0];
      body = `<p class="base-panel-hint" data-feat-id="65">与金蝶 K3、MES、LIMS 集成；入库标记订单来源，出库标记生产批次。最近接口：${logHint ? logHint.code + " · " + logHint.trigger : "—"}</p>${gcTableCard(
        "<tr><th>系统</th><th>方向</th><th>标记字段</th><th>最近同步</th><th>状态</th></tr>",
        `<tr><td>金蝶 K3</td><td>双向</td><td>订单来源 / 货主 / PO#</td><td>2026-09-28 09:00</td><td>${tagStatus("done")}</td></tr>
        <tr><td>MES</td><td>WMS→MES</td><td>生产批次 / WO#</td><td>2026-09-28 10:15</td><td>${tagStatus("done")}</td></tr>
        <tr><td>LIMS</td><td>双向</td><td>质检放行 / 报检单</td><td>2026-09-28 08:30</td><td>${tagStatus("done")}</td></tr>`,
        3,
      )}`;
    } else if (sec === "trace" && tab === "serialQuery") {
      body = `<p class="base-panel-hint" data-feat-id="121">按<strong>成品序列号</strong>正反向追溯（补充最小包装条码 #64）；联动成品入库/出库单。</p>
        ${gcQuery([{ label: "序列号 SN", key: "sn", ph: "SN20260928001" }])}
        <div class="toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary" id="btn-serial-trace">查询序列号链路</button></div>
        ${gcTableCard(
          "<tr><th>序列号</th><th>SKU</th><th>批次</th><th>状态</th><th>绑定出库单</th><th>绑定时间</th></tr>",
          (state.serialNumbers || []).map((s) => `<tr data-feat-id="121"><td>${s.serial}</td><td>${s.sku}</td><td>${s.batch}</td><td>${s.status}</td><td>${s.outboundId}</td><td>${s.boundAt}</td></tr>`).join(""),
          (state.serialNumbers || []).length,
        )}`;
    } else if (sec === "trace" && tab === "serialBind") {
      body = `<p class="base-panel-hint" data-feat-id="122">成品/半成品入库 PDA 扫码时绑定序列号；出库发运时校验 SN 与出库单一致。</p>
        <div class="toolbar admin-toolbar gc-toolbar"><button type="button" class="btn btn-sm btn-primary btn-serial-bind-pda">PDA 绑定序列号</button></div>
        ${gcTableCard(
          "<tr><th>序列号</th><th>来源入库单</th><th>SKU</th><th>操作</th></tr>",
          (state.serialNumbers || []).map((s) => `<tr data-feat-id="122"><td>${s.serial}</td><td>${s.inboundId || "SF/FG"}</td><td>${s.sku}</td><td><button type="button" class="btn btn-sm btn-link btn-serial-bind-pda">PDA</button></td></tr>`).join(""),
          (state.serialNumbers || []).length,
        )}`;
    } else if (sec === "count" && tab === "strategy") {""",
    "trace serial panels",
)

# --- 4. Integration SRM / APS ---
must_replace(
    """  const INT_SECTION_META = {
    k3: { title: "金蝶 K3", count: 13, level: "L" },
    lims: { title: "LIMS", count: 1, level: "—" },
    wcs: { title: "WCS", count: 1, level: "M" },
    zsl: { title: "浙食链", count: 1, level: "—" },
    aixi: { title: "艾希赋码", count: 1, level: "—" },
    ext: { title: "扩展接口", count: 1, level: "—" },
  };""",
    """  const INT_SECTION_META = {
    k3: { title: "金蝶 K3", count: 13, level: "L" },
    srm: { title: "SRM", count: 2, level: "M" },
    aps: { title: "APS", count: 2, level: "M" },
    lims: { title: "LIMS", count: 1, level: "—" },
    wcs: { title: "WCS", count: 1, level: "M" },
    zsl: { title: "浙食链", count: 1, level: "—" },
    aixi: { title: "艾希赋码", count: 1, level: "—" },
    ext: { title: "扩展接口", count: 1, level: "—" },
  };""",
    "INT_SECTION_META",
)

must_replace(
    """    k3: [
      ["mdm", "物料主数据"],
      ["pickReq", "领料申请单"],
      ["mesReturn", "车间退料"],
      ["rtResult", "退料结果回传"],
      ["fgIn", "成品完工入库"],
      ["fgOut", "成品出库单"],
      ["fgOutRet", "出库结果返回"],
      ["invQuery", "ERP 库存查询"],
      ["loc", "仓库库位"],
      ["rmIn", "原辅料入库"],
      ["countInv", "盘点库存"],
      ["otherOut", "其他出库"],
    ],
    lims: [["release", "产品放行同步"]],""",
    """    k3: [
      ["mdm", "物料主数据"],
      ["pickReq", "领料申请单"],
      ["mesReturn", "车间退料"],
      ["rtResult", "退料结果回传"],
      ["fgIn", "成品完工入库"],
      ["fgOut", "成品出库单"],
      ["fgOutRet", "出库结果返回"],
      ["invQuery", "ERP 库存查询"],
      ["loc", "仓库库位"],
      ["rmIn", "原辅料入库"],
      ["countInv", "盘点库存"],
      ["otherOut", "其他出库"],
    ],
    srm: [
      ["appt", "供应商送货预约"],
      ["dn", "送货单同步"],
    ],
    aps: [
      ["materialPlan", "工单用料计划"],
      ["prepTask", "备料任务下发"],
    ],
    lims: [["release", "产品放行同步"]],""",
    "INT_TAB_LABELS",
)

must_replace(
    """    105: { section: "zsl", tab: "outerPack" },
    106: { section: "aixi", tab: "boxCode" },
    107: { section: "ext", tab: "reserve" },
  };""",
    """    105: { section: "zsl", tab: "outerPack" },
    106: { section: "aixi", tab: "boxCode" },
    107: { section: "ext", tab: "reserve" },
    127: { section: "srm", tab: "appt" },
    128: { section: "srm", tab: "dn" },
    129: { section: "aps", tab: "materialPlan" },
    130: { section: "aps", tab: "prepTask" },
  };""",
    "INT_FEAT_NAV",
)

must_replace(
    """      {
        code: "EXT-RES",
        name: "预留扩展接口",
        dir: "待定",
        freq: "—",
        provider: "—",
        status: "ok",
        last: "—",
        feat: 107,
        desc: "预留 MES 生产执行、WCS/AGV 等后期联动接口。",
      },
    ],""",
    """      {
        code: "SRM-APPT",
        name: "供应商送货预约",
        dir: "SRM → WMS",
        freq: "实时",
        provider: "SRM",
        status: "ok",
        last: "09:20",
        feat: 127,
        desc: "供应商在 SRM 预约到仓时段与月台，WMS 生成收货任务。",
      },
      {
        code: "SRM-DN",
        name: "送货单同步",
        dir: "SRM/K3 → WMS",
        freq: "实时",
        provider: "SRM",
        status: "ok",
        last: "09:25",
        feat: 128,
        desc: "送货单与采购订单行下发 WMS，驱动 PDA 收货 #27。",
      },
      {
        code: "APS-PLAN",
        name: "工单用料计划",
        dir: "APS → WMS",
        freq: "实时",
        provider: "APS",
        status: "ok",
        last: "08:50",
        feat: 129,
        desc: "APS 锁定发布后的工单用料计划，作为备料依据。",
      },
      {
        code: "APS-PREP",
        name: "备料任务下发",
        dir: "APS → WMS",
        freq: "实时",
        provider: "APS",
        status: "ok",
        last: "08:55",
        feat: 130,
        desc: "APS 备料任务进入 WMS 分拣/移库池（#41）。",
      },
      {
        code: "EXT-RES",
        name: "预留扩展接口",
        dir: "待定",
        freq: "—",
        provider: "—",
        status: "ok",
        last: "—",
        feat: 107,
        desc: "预留 MES 生产执行、WCS/AGV 等后期联动接口（SRM/APS 已单列 Tab）。",
      },
    ],""",
    "integrations srm aps",
)

# --- 5. Initial state ---
must_replace(
    """    otherOutbound: [
      { id: "OB-SAMPLE", type: "留样", status: "pending" },
      { id: "OB-SCRAP", type: "报废", status: "pending" },
      { id: "OB-DONATE", type: "捐赠", status: "pending" },
      { id: "OB-LOSS", type: "盘亏出库", status: "pending" },
    ],""",
    """    otherOutbound: [
      { id: "OB-SAMPLE", type: "留样", status: "pending" },
      { id: "OB-SCRAP", type: "报废", status: "pending" },
      { id: "OB-QSAMPLE", type: "取样", status: "pending" },
      { id: "OB-DOMESTIC", type: "内销", status: "pending" },
      { id: "OB-MISC", type: "杂项领用", status: "pending" },
      { id: "OB-DONATE", type: "捐赠", status: "pending", nonStandard: true },
      { id: "OB-LOSS", type: "盘亏出库", status: "pending" },
    ],
    semiFinishedInbound: [
      { id: "SF-9920", wo: "WO-S8820", sku: "SF-2001", batch: "SFP20260928", qty: "120 件", loc: "—", status: "mes_ok" },
    ],
    salesReturnInbound: [
      { id: "RTN-S001", returnType: "销售退货", sourceNo: "SO-RET-20260928", sku: "FG-1002", qty: "20 箱", status: "pending" },
      { id: "RTN-P001", returnType: "采购退货", sourceNo: "PO-RET-018", sku: "RM-8821", qty: "10 KG", status: "iqc" },
    ],
    serialNumbers: [
      { serial: "SN20260928001", sku: "FG-1002", batch: "BP202609", status: "在库", boundAt: "2026-09-28", outboundId: "—", inboundId: "IN-9921" },
      { serial: "SN20260928002", sku: "SF-2001", batch: "SFP20260928", status: "在库", boundAt: "2026-09-28", outboundId: "—", inboundId: "SF-9920" },
    ],
    deliverySignoffs: [
      { outboundId: "CK20260928002", customer: "客户 B", shipStatus: "已发运", signStatus: "待签收", signedAt: "—", signedBy: "—", k3Synced: false, k3SyncedAt: "—" },
    ],""",
    "initial state",
)

must_replace(
    """    outFinanceRules: [
      { id: "FR-01", outType: "留样", logic: "锁定库存", glAccount: "1403", costCenter: "CC-QA" },
      { id: "FR-02", outType: "报废", logic: "资产核销", glAccount: "6711", costCenter: "CC-PLANT" },
      { id: "FR-03", outType: "杂项领用", logic: "部门费用", glAccount: "6602", costCenter: "CC-DEPT" },
    ],""",
    """    outFinanceRules: [
      { id: "FR-01", outType: "留样", logic: "锁定库存", glAccount: "1403", costCenter: "CC-QA" },
      { id: "FR-02", outType: "报废", logic: "资产核销", glAccount: "6711", costCenter: "CC-PLANT" },
      { id: "FR-03", outType: "杂项领用", logic: "部门费用", glAccount: "6602", costCenter: "CC-DEPT" },
      { id: "FR-04", outType: "取样", logic: "质检消耗", glAccount: "6602", costCenter: "CC-QA" },
      { id: "FR-05", outType: "内销", logic: "内部销售消耗", glAccount: "6001", costCenter: "CC-SALES" },
    ],""",
    "finance rules",
)

# --- 6. Permission #9 gap note ---
must_replace(
    """      body = `<p class="base-panel-hint" data-feat-id="9">菜单/数据权限演示 CRUD（上线接 RuoYi）。当前演示角色：<strong>${state.demoRole || "admin"}</strong></p>""",
    """      body = `<p class="base-panel-hint" data-feat-id="9"><strong>差距说明（验收必读）</strong>：本页为<strong>原型演示</strong>，非生产权限中心。上线后须对接企业统一身份与权限（如 RuoYi/若依、LDAP、OAuth2），实现菜单权限、按钮权限、数据范围（仓库/库区/货主）、字段脱敏与审批流绑定。原型仅模拟角色切换与权限项 CRUD，<strong>不可作为安全合规验收依据</strong>。详见蓝图中心 → <a href="#" id="link-gap-acceptance">需求对齐与差距说明</a>。当前演示角色：<strong>${state.demoRole || "admin"}</strong></p>""",
    "perm #9",
)

# --- 7. Hub acceptance tab ---
must_replace(
    """  const HUB_LABELS = Object.fromEntries(
    [
      ["integration", "系统集成"],
      ["strategy", "策略配置"],
      ["backup", "系统备份"],
    ].map(([k, v]) => [k, v]),
  );""",
    """  const HUB_LABELS = Object.fromEntries(
    [
      ["integration", "系统集成"],
      ["strategy", "策略配置"],
      ["backup", "系统备份"],
      ["acceptance", "需求对齐与差距"],
    ].map(([k, v]) => [k, v]),
  );""",
    "HUB_LABELS",
)

must_replace(
    """      if (h === "strategy") return "hub-strategy";
      if (h === "backup") return "hub-backup";
      return "hub-integration";""",
    """      if (h === "strategy") return "hub-strategy";
      if (h === "backup") return "hub-backup";
      if (h === "acceptance") return "hub-acceptance";
      return "hub-integration";""",
    "moduleCatalogKey acceptance",
)

must_replace(
    """    "hub-backup": { name: "系统备份" },
  };""",
    """    "hub-backup": { name: "系统备份" },
    "hub-acceptance": { name: "需求对齐与差距" },
  };""",
    "MODULE_CATALOG acceptance",
)

must_replace(
    """    "hub-backup": "数据库、文件备份与高可用",
  };""",
    """    "hub-backup": "数据库、文件备份与高可用",
    "hub-acceptance": "WMS功能清单.xlsx / 蓝图1.2 与原型验收对齐说明",
  };""",
    "PAGE_DESC acceptance",
)

ACCEPTANCE_FN = '''
  function renderAcceptanceGapPanel() {
    const rows = [
      ["入库", "半成品入库、退货入库", "warehousing → 半成品/退货 Tab #113-116", "已补原型页面 + PDA semiIn/rtnIn", "P0", "生产须接 MES/K3 真单"],
      ["出库", "取样、内销等特殊出库", "outbound → 其他出库 #81", "类型已含取样/内销；捐赠标为非标", "P0", "财务规则见 #82"],
      ["集成", "SRM 预约/送货单、APS 用料/备料", "蓝图中心 → 系统集成 SRM/APS", "Tab #127-130 + 接口监控", "P0", "报文/重试待联调"],
      ["追溯", "成品序列号", "库内 → 质量追溯", "序列号查询/绑定 #121-122", "P0", "与成品/半成品入库联动"],
      ["发运", "签收/交付确认回写", "出库 → 发运交付", "签收 #125 · K3回写 #126 · PDA deliverySign", "P0", "客户/承运签收"],
      ["权限", "分级账号与审计", "基础管理 → 权限 #9", "原型演示 only；生产用统一权限中心", "P0", "不可作为安全验收"],
      ["平台", "MySQL/双机/RAID 备份", "蓝图中心 → 系统备份", "方案展示 #108-111", "—", "属 MOM 平台非 WMS 业务"],
      ["扩展", "ABC 分类报表", "可视化 #112", "蓝图未单列", "P2", "可选增值"],
    ];
    const table = gcTableCard(
      "<tr><th>域</th><th>蓝图要求</th><th>原型入口</th><th>对齐状态</th><th>优先级</th><th>备注</th></tr>",
      rows.map((r) => `<tr><td>${r[0]}</td><td>${r[1]}</td><td>${r[2]}</td><td>${r[3]}</td><td>${r[4]}</td><td>${r[5]}</td></tr>`).join(""),
      rows.length,
    );
    return `<div class="gap-panel" style="margin-bottom:16px"><strong>与《WMS功能清单.xlsx》《WMS蓝图设计方案 1.2》验收对齐</strong>
      <ul style="margin-top:8px;line-height:1.85">
        <li><strong>已覆盖</strong>：本原型 FEAT #1–#111 主链路 + 本次补齐 #113–#130、#112 扩展。</li>
        <li><strong>演示级</strong>：sessionStorage、接口「演示同步」、权限 #9 — 立项开发时需替换为真实服务。</li>
        <li><strong>非标项</strong>：其他出库「捐赠」仅演示，蓝图未要求，验收时可忽略。</li>
      </ul></div>
      ${table}
      <div class="card" style="margin-top:12px"><div class="card-head"><h3>权限 #9 生产差距（固定说明）</h3></div><div class="card-body" style="font-size:13px;line-height:1.9">
        <p>原型：角色下拉 + 权限项 CRUD + 菜单 dataScope 演示。</p>
        <p>生产：对接<strong>统一权限中心</strong>（用户/角色/组织/岗位、菜单与按钮授权、仓库数据权限、操作审计、双因素/密码策略、与 K3 主数据组织一致）。</p>
        <p>验收标准：以企业信息安全制度与 RuoYi（或选定 IAM）配置清单为准，<strong>不以本 HTML 为准</strong>。</p>
      </div></div>`;
  }
'''

must_replace(
    """  function renderBlueprintHubPage() {
    const tab = state.hubTab || "integration";
    if (!["strategy", "integration", "backup"].includes(tab)) {
      state.hubTab = "integration";
    }""",
    ACCEPTANCE_FN + """
  function renderBlueprintHubPage() {
    const tab = state.hubTab || "integration";
    if (!["strategy", "integration", "backup", "acceptance"].includes(tab)) {
      state.hubTab = "integration";
    }""",
    "acceptance function",
)

must_replace(
    """    } else if (hubTab === "backup") {
      body = renderBackupPanel();
    }
    return adminPage(""",
    """    } else if (hubTab === "backup") {
      body = renderBackupPanel();
    } else if (hubTab === "acceptance") {
      body = renderAcceptanceGapPanel();
    }
    return adminPage(""",
    "hub render acceptance",
)

must_replace(
    """        { label: "系统备份", page: "blueprint-hub", hub: "backup" },
      ],
    },
  ];""",
    """        { label: "系统备份", page: "blueprint-hub", hub: "backup" },
        { label: "需求对齐与差距", page: "blueprint-hub", hub: "acceptance" },
      ],
    },
  ];""",
    "FUNCTION_NAV",
)

must_replace(
    """      { id: "backup", page: "blueprint-hub", hub: "backup", label: "系统备份", icon: "folder", sectionMeta: BACKUP_SECTION_META, tabLabels: BACKUP_TAB_LABELS, getNav: () => ({ section: state.backupSection, tab: state.backupTab }), apply: (s, t) => { state.backupSection = s; state.backupTab = t; go("blueprint-hub", { hub: "backup" }); } },
    ];""",
    """      { id: "backup", page: "blueprint-hub", hub: "backup", label: "系统备份", icon: "folder", sectionMeta: BACKUP_SECTION_META, tabLabels: BACKUP_TAB_LABELS, getNav: () => ({ section: state.backupSection, tab: state.backupTab }), apply: (s, t) => { state.backupSection = s; state.backupTab = t; go("blueprint-hub", { hub: "backup" }); } },
      { id: "acceptance", page: "blueprint-hub", hub: "acceptance", label: "需求对齐与差距", icon: "guide", sectionMeta: { doc: { title: "验收说明", count: 1 } }, tabLabels: { doc: [["main", "对齐清单"]] }, getNav: () => ({ section: "doc", tab: "main" }), apply: () => go("blueprint-hub", { hub: "acceptance" }) },
    ];""",
    "sidebar acceptance",
)

# FEAT rows + scenes
must_replace(
    """  [112, "func-visual", "", "", "仓库 ABC 分类分析"],
  [91, "blueprint-hub", "integration", "", "K3 物料主数据"],""",
    """  [112, "func-visual", "", "", "仓库 ABC 分类分析"],
  [113, "warehousing", "", "semiIn", "半成品入库单"],
  [114, "warehousing", "", "semiIn", "PDA 半成品扫码入库"],
  [115, "warehousing", "", "rtnIn", "退货入库单协同"],
  [116, "warehousing", "", "rtnIn", "PDA 退货复核入库"],
  [121, "inventory", "", "", "序列号追溯查询"],
  [122, "inventory", "", "", "序列号绑定"],
  [125, "outbound", "", "deliverySign", "签收/交付确认"],
  [126, "outbound", "", "", "签收状态回写 K3"],
  [127, "blueprint-hub", "integration", "", "SRM 送货预约"],
  [128, "blueprint-hub", "integration", "", "SRM 送货单"],
  [129, "blueprint-hub", "integration", "", "APS 工单用料"],
  [130, "blueprint-hub", "integration", "", "APS 备料任务"],
  [91, "blueprint-hub", "integration", "", "K3 物料主数据"],""",
    "FEAT_ROUTE_ROWS",
)

must_replace(
    """    111: { page: "blueprint-hub", hub: "backup", backupSection: "core", backupTab: "raid" },
  };""",
    """    111: { page: "blueprint-hub", hub: "backup", backupSection: "core", backupTab: "raid" },
    113: { page: "warehousing", whSection: "semi", whTab: "order" },
    114: { page: "warehousing", whSection: "semi", whTab: "scan" },
    115: { page: "warehousing", whSection: "rtn", whTab: "order" },
    116: { page: "warehousing", whSection: "rtn", whTab: "scan" },
    121: { page: "inventory", invSection: "trace", invTab: "serialQuery" },
    122: { page: "inventory", invSection: "trace", invTab: "serialBind" },
    125: { page: "outbound", outSection: "delivery", outTab: "signConfirm" },
    126: { page: "outbound", outSection: "delivery", outTab: "signSync" },
    127: { page: "blueprint-hub", hub: "integration", intSection: "srm", intTab: "appt" },
    128: { page: "blueprint-hub", hub: "integration", intSection: "srm", intTab: "dn" },
    129: { page: "blueprint-hub", hub: "integration", intSection: "aps", intTab: "materialPlan" },
    130: { page: "blueprint-hub", hub: "integration", intSection: "aps", intTab: "prepTask" },
    131: { page: "blueprint-hub", hub: "acceptance", acceptanceSection: "doc", acceptanceTab: "main" },
  };""",
    "FEAT_SCENE",
)

must_replace(
    """      outbound: {
        name: "销售出库",
        steps: ["K3 出货单", "分配拣货", "PDA 拣货", "PDA 发运", "K3 确认"],""",
    """      outbound: {
        name: "销售出库",
        steps: ["K3 出货单", "分配拣货", "PDA 拣货", "PDA 发运", "签收确认", "K3 确认"],""",
    "flow outbound sign",
)

# PDA labels + menu
must_replace(
    """    countRecap: "差异复盘",
  };""",
    """    countRecap: "差异复盘",
    semiIn: "半成品入库",
    rtnIn: "退货入库",
    deliverySign: "签收确认",
    serialBind: "序列号绑定",
  };""",
    "PDA_TYPE_LABEL",
)

must_replace(
    """    state.finishedInbound.filter((f) => f.status === "putaway").forEach((f) => add("fg-" + f.id, "fg", "成品入 " + f.id, false));
    if (state.returns[0].status === "putaway") add("ret-1", "return", "退料 " + state.returns[0].id, false);""",
    """    state.finishedInbound.filter((f) => f.status === "putaway").forEach((f) => add("fg-" + f.id, "fg", "成品入 " + f.id, false));
    (state.semiFinishedInbound || []).filter((s) => s.status !== "done").forEach((s) => add("semi-" + s.id, "semiIn", "半成品入 " + s.id, false));
    (state.salesReturnInbound || []).filter((r) => r.status !== "done").forEach((r) => add("rtn-" + r.id, "rtnIn", "退货入 " + r.id, true));
    (state.deliverySignoffs || []).filter((d) => d.signStatus === "待签收").forEach((d) => add("sign-" + d.outboundId, "deliverySign", "签收 " + d.outboundId, true));
    if (state.returns[0].status === "putaway") add("ret-1", "return", "退料 " + state.returns[0].id, false);""",
    "refreshPdaInbox",
)

PDA_MENU_OLD = (
    '          ${[\n'
    '            ["receive", "入库收货", c.receive],\n'
    '            ["putaway", "上架入库", c.putaway],\n'
    '            ["pick", "出库拣货", c.pick],\n'
    '            ["ship", "出库发运", c.ship],\n'
    '            ["fg", "成品入库", c.fg],\n'
    '            ["count", "库存盘点", c.count],\n'
    '            ["move", "移库移位", c.move],\n'
    '            ["return", "退料复核", c.ret],\n'
    '            ["query", "物资库存", 0],\n'
    '            ["material", "生产分拣", c.material],\n'
    '            ["workshop", "车间收料", c.workshop],\n'
    "          ]\n"
    '            .map(([t, lb, n]) => pdaMenuItemHtml(t, lb, n))\n'
    '            .join("")}\n'
    "        </div>"
)
PDA_MENU_NEW = (
    '          ${[\n'
    '            ["receive", "入库收货", c.receive],\n'
    '            ["putaway", "上架入库", c.putaway],\n'
    '            ["pick", "出库拣货", c.pick],\n'
    '            ["ship", "出库发运", c.ship],\n'
    '            ["fg", "成品入库", c.fg],\n'
    '            ["semiIn", "半成品入库", (state.semiFinishedInbound || []).filter((s) => s.status !== "done").length],\n'
    '            ["rtnIn", "退货入库", (state.salesReturnInbound || []).filter((r) => r.status !== "done").length],\n'
    '            ["count", "库存盘点", c.count],\n'
    '            ["move", "移库移位", c.move],\n'
    '            ["return", "退料复核", c.ret],\n'
    '            ["query", "物资库存", 0],\n'
    '            ["material", "生产分拣", c.material],\n'
    '            ["workshop", "车间收料", c.workshop],\n'
    '            ["deliverySign", "签收确认", (state.deliverySignoffs || []).filter((d) => d.signStatus === "待签收").length],\n'
    "          ]\n"
    '            .map(([t, lb, n]) => pdaMenuItemHtml(t, lb, n))\n'
    '            .join("")}\n'
    "        </div>"
)
must_replace(PDA_MENU_OLD, PDA_MENU_NEW, "pda menu grid")

# Ship -> create delivery signoff
must_replace(
    """        o.status = "done";
        o.actualQty = o.planQty || o.actualQty || "480 箱";
      }
      const fg = state.inventory.find((i) => i.sku === "FG-1002");""",
    """        o.status = "done";
        o.actualQty = o.planQty || o.actualQty || "480 箱";
        const ds = (state.deliverySignoffs || []).find((d) => d.outboundId === o.id);
        if (!ds) {
          if (!state.deliverySignoffs) state.deliverySignoffs = [];
          state.deliverySignoffs.push({
            outboundId: o.id,
            customer: o.customer || "—",
            shipStatus: "已发运",
            signStatus: "待签收",
            signedAt: "—",
            signedBy: "—",
            k3Synced: false,
            k3SyncedAt: "—",
          });
        } else {
          ds.shipStatus = "已发运";
          ds.signStatus = "待签收";
        }
      }
      const fg = state.inventory.find((i) => i.sku === "FG-1002");""",
    "ship creates signoff",
)

must_replace(
    """      toast("发运完成 · 出库闭环 · K3", "success");""",
    """      toast("发运完成 · 请继续签收确认 #125", "success");""",
    "ship toast",
)

# PDA workflow defs - add to stepDefs
must_replace(
    """      fg: ["扫箱码", "确认库位", "过账"],
      splitPallet: ["扫托标", "拆分数", "新箱码", "打印提交"],""",
    """      fg: ["扫箱码", "确认库位", "过账"],
      semiIn: ["扫半成品单", "扫条码", "确认库位"],
      rtnIn: ["扫退货单", "扫物料", "复核入库"],
      deliverySign: ["扫出库单", "扫签收码/人", "确认签收"],
      serialBind: ["扫 SN", "扫箱码", "提交绑定"],
      splitPallet: ["扫托标", "拆分数", "新箱码", "打印提交"],""",
    "pda stepDefs",
)

must_replace(
    """    warehousing: "采购、退料、完工与成品入库",""",
    """    warehousing: "采购、退料、完工、成品/半成品/退货入库",""",
    "PAGE_DESC warehousing",
)

PATH.write_text(text, encoding="utf-8")
print("Patched OK:", PATH)
