From ca36da92f28deb766532b584b77378e29f792eee Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 14 Sep 2026 15:02:01 +0800 Subject: [PATCH] =?UTF-8?q?Liquibase=2020260910=EF=BC=9A#1=20stockUomId?= =?UTF-8?q?=E3=80=81#2=E2=80=933=20Option=20A=20trigger=E3=80=81#35=20inde?= =?UTF-8?q?x=20=E4=B8=8A=E7=B7=9A=20app=EF=BC=9A#5=20entity=E3=80=81#7=20a?= =?UTF-8?q?vailable=20=3D=20onHand=20=E2=88=92=20unavailable=E3=80=81#8=20?= =?UTF-8?q?=E6=90=9C=E5=B0=8B=E5=85=A8=20bucket=E3=80=81#34=20Resolver?= =?UTF-8?q?=EF=BC=88#10=EF=BC=8F#11=20=E5=B7=B2=E6=8E=A5=EF=BC=89=20Admin?= =?UTF-8?q?=201.0=EF=BC=9A=E8=A3=9C=20stockUomId=E3=80=81uomId=3Dbase?= =?UTF-8?q?=EF=BC=88=E9=80=99=E5=B0=B1=E6=98=AF=20#1=20=E7=9A=84=20data=20?= =?UTF-8?q?patch=EF=BC=89=202.3=20=E6=8A=8A=20ledger=20=E7=B6=81=E5=88=B0?= =?UTF-8?q?=E8=A9=B2=20bucket=202.7=EF=BC=9A=E5=85=88=E8=B6=85=E7=99=BC=20?= =?UTF-8?q?remain=E2=86=920=EF=BC=88=E6=94=B9=20lot=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E5=86=8D=20miss=EF=BC=9B=E5=85=B1=E7=94=A8=E4=B8=80=E5=BC=B5?= =?UTF-8?q?=E5=85=A5=E5=80=89=E5=96=AE=E3=80=81=E4=B8=80=E5=BC=B5=E5=87=BA?= =?UTF-8?q?=E5=80=89=E5=96=AE=202.6=20=E6=97=A5=E7=B5=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/stockLedgerFix/client.ts | 7 ++++ .../StockLedgerFixPageClient.tsx | 36 +++++++++++-------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/app/api/stockLedgerFix/client.ts b/src/app/api/stockLedgerFix/client.ts index 5c72dd55..675ec990 100644 --- a/src/app/api/stockLedgerFix/client.ts +++ b/src/app/api/stockLedgerFix/client.ts @@ -52,12 +52,15 @@ export type StockLedgerFixInventoryPreview = { inventoryRows: number; lotUomPairs: number; missingUomPairs: number; + nullStockUomId: number; }; export type StockLedgerFixInventoryResponse = { inserted: number; updated: number; missingUomPairsAfter: number; + patchedStockUomId: number; + nullStockUomIdAfter: number; }; export type StockLedgerFixSearchInventoryHit = { @@ -101,6 +104,7 @@ export type StockLedgerFixAdjRow = { ledgerOut: string; missIn: string; missOut: string; + overIssue: string; }; export type StockLedgerFixAdjPreview = { @@ -112,6 +116,8 @@ export type StockLedgerFixAdjPreview = { sumMissIn: string; sumMissOut: string; skuNet: string; + overIssueCount: number; + sumOverIssue: string; rows: StockLedgerFixAdjRow[]; }; @@ -119,6 +125,7 @@ export type StockLedgerFixAdjResponse = { adjDate: string; insertedIn: number; insertedOut: number; + overIssuePatched: number; filledLotQty: number; filledBalance: number; dayRowsWritten: number; diff --git a/src/components/StockLedgerFix/StockLedgerFixPageClient.tsx b/src/components/StockLedgerFix/StockLedgerFixPageClient.tsx index 60d5f9e7..78c15865 100644 --- a/src/components/StockLedgerFix/StockLedgerFixPageClient.tsx +++ b/src/components/StockLedgerFix/StockLedgerFixPageClient.tsx @@ -93,7 +93,7 @@ const EXPORT_PART_LABEL: Record = { "2.3": "2.3 inventoryId", ledger: "ledger lot/uom/lotQty/balance", "2.6": "2.6 日結", - "2.7": "2.7 ADJ INSERT", + "2.7": "2.7 ADJ+入出倉單", }; function selectedExportParts(flags: Record): ExportPart[] { @@ -426,7 +426,7 @@ const StockLedgerFixPageClient: React.FC = () => { const onInventory = async () => { if (inventoryRunInFlight.current) return; const ok = window.confirm( - "方案 A:保留現有 inventory.id,補缺 (itemId, uomId),並用 lot 的 in−out 重算全部數量。沒有 lot 的 UOM 會變成 0。不會改 inventory_lot_line。確定執行?", + "方案 A:保留現有 inventory.id;從 lot 回填 stockUomId(舊 uomId 是 stock 則改回 base);補缺 (itemId, stockUomId);用 lot 的 in−out 重算 onHand/unavailable。不寫 onHoldQty、不改 inventory_lot_line。確定執行?", ); if (!ok) return; inventoryRunInFlight.current = true; @@ -436,7 +436,7 @@ const StockLedgerFixPageClient: React.FC = () => { try { const res = await runStockLedgerFixInventory(); setInventoryMessage( - `inventory 1.0 完成:新增 ${res.inserted} 列、重算 ${res.updated} 列、之後仍缺 UOM ${res.missingUomPairsAfter}`, + `inventory 1.0 完成:回填 stockUomId ${res.patchedStockUomId} 列、新增 ${res.inserted} 列、重算 ${res.updated} 列、之後仍缺 UOM ${res.missingUomPairsAfter}、仍 NULL stockUomId ${res.nullStockUomIdAfter}`, ); await loadInventory(); } catch (e) { @@ -456,7 +456,7 @@ const StockLedgerFixPageClient: React.FC = () => { return; } const ok = window.confirm( - `會在 ${d} 新增 ADJ:入向 ${adjPreview?.adjInCount ?? 0} 筆、出向 ${adjPreview?.adjOutCount ?? 0} 筆(含反向調整)。缺入合計 ${adjPreview?.sumMissIn ?? "?"}、缺出合計 ${adjPreview?.sumMissOut ?? "?"}。目標:每 lot 最後 lotQtyAfter = line remain。已對齊的 lot 不會再動。確定?`, + `會在 ${d} 先修超發 remain→0(${adjPreview?.overIssueCount ?? 0} 條 lot,合計 ${adjPreview?.sumOverIssue ?? "?"},會改 lot inQty)。再補 miss ADJ:入向 ${adjPreview?.adjInCount ?? 0} 筆、出向 ${adjPreview?.adjOutCount ?? 0} 筆。入向全部掛同一張入倉單、出向全部掛同一張出倉單(miss 不改 lot 數量)。缺入合計 ${adjPreview?.sumMissIn ?? "?"}、缺出合計 ${adjPreview?.sumMissOut ?? "?"}。確定?`, ); if (!ok) return; adjRunInFlight.current = true; @@ -466,7 +466,7 @@ const StockLedgerFixPageClient: React.FC = () => { try { const res = await runStockLedgerFixAdj(d); setAdjMessage( - `ADJ ${res.adjDate}:入 ${res.insertedIn}、出 ${res.insertedOut}、lotQty ${res.filledLotQty}、balance ${res.filledBalance}、日結 ${res.dayRowsWritten}`, + `ADJ ${res.adjDate}:超發 remain→0 ${res.overIssuePatched}、入 ${res.insertedIn}、出 ${res.insertedOut}、lotQty ${res.filledLotQty}、balance ${res.filledBalance}、日結 ${res.dayRowsWritten}`, ); await loadAdjPreview(); } catch (e) { @@ -631,7 +631,7 @@ const StockLedgerFixPageClient: React.FC = () => { const steps = stepsPayload(daySteps); const stepLabel = steps?.join("、") ?? "全部 2.1–2.6"; const ok = window.confirm( - `一次修 ${from} → ${to}(步驟:${stepLabel})。2.1–2.5 整段一次算、2.6 仍依日寫日結。過程中無法中停。確定?`, + `一次修 ${from} → ${to}(步驟:${stepLabel})。改為逐日跑(每天 commit)。中途失敗時已跑完的天會留下。確定?`, ); if (!ok) return; @@ -724,7 +724,8 @@ const StockLedgerFixPageClient: React.FC = () => { 現有 inventory {inventoryPreview.inventoryRows} 列;lot 的 item+UOM {inventoryPreview.lotUomPairs} 組;缺{" "} - {inventoryPreview.missingUomPairs} 組。 + {inventoryPreview.missingUomPairs} 組;stockUomId 仍 NULL{" "} + {inventoryPreview.nullStockUomId} 列。 )} @@ -742,11 +743,13 @@ const StockLedgerFixPageClient: React.FC = () => { - 2.7 ADJ:line in/out 對齊 ledger + 2.7 ADJ:超發 remain→0,再對齊 ledger 請先選 ADJ 日期再預覽。預設昨天;freeze dump 夜可選今天(例如 31/8 23:17 寫 ADJ 到 - 31/8)。比對每 lot 的 line in/out 與 ledger 加總;有差就 ADJ。目標:最後一筆 - lotQtyAfter = line remain。明細最多 20 列。 + 31/8)。先處理超發(line 入少於出):把該 lot 的 inQty 補到等於 outQty(remain=0,trigger + 重算 inventory)。再處理 miss:line 與 ledger 有差就 ADJ,掛既有 lot 的入/出倉行(不改 lot + 數量)。一次 Apply 共用一張入倉單、一張出倉單,下面多行;同一 ADJ 日期再跑會沿用這兩張單。 + 明細最多 20 列。 { {adjLoading && !adjPreview && } {adjPreview && ( - ADJ 日期 {adjPreview.adjDate};可補 {adjPreview.lotCount} 條 - lot(入 {adjPreview.adjInCount}、出 {adjPreview.adjOutCount});缺入合計{" "} + ADJ 日期 {adjPreview.adjDate};可處理 {adjPreview.lotCount} 條 + lot(超發 remain→0 {adjPreview.overIssueCount}、合計 {adjPreview.sumOverIssue};miss 入{" "} + {adjPreview.adjInCount}、出 {adjPreview.adjOutCount});缺入合計{" "} {adjPreview.sumMissIn}、缺出合計 {adjPreview.sumMissOut};SKU 淨額{" "} {adjPreview.skuNet} {adjPreview.skuNet !== "0" ? "(非 0 表示有缺入沒有對應缺出)" : ""} {adjPreview.skippedNegCount > 0 ? `;含反向 ADJ ${adjPreview.skippedNegCount} 條(ledger 多記)` : ""} - 。下列最多 20 列。 + 。下列最多 20 列(超發優先)。 )} {adjPreview && adjPreview.rows.length > 0 && ( @@ -810,6 +814,7 @@ const StockLedgerFixPageClient: React.FC = () => { ledger in/out 缺入 缺出 + 超發 @@ -825,6 +830,7 @@ const StockLedgerFixPageClient: React.FC = () => { {r.missIn} {r.missOut} + {r.overIssue} ))} @@ -949,7 +955,7 @@ const StockLedgerFixPageClient: React.FC = () => { 區間修(2.1–2.6 一次) - 使用上面勾的步驟,整段日期一次 API。2.1–2.5 用同一視窗算完;2.6 日結仍依日寫入。可含今天(freeze dump 夜)。最長約 20 分鐘。 + 使用上面勾的步驟,一次 API 但改為逐日(與單日修相同 SQL,每天 commit)。可含今天(freeze dump 夜)。前端最長約 20 分鐘;大區間仍建議匯出 SQL。 { 匯出已修 SQL(新庫用) - 勾選要匯出的段落(同日修步驟)。套檔順序:1.0 → ledger(±2.3)→ 2.7 INSERT → + 勾選要匯出的段落(同日修步驟)。套檔順序:1.0 → ledger(±2.3)→ 2.7 入出倉單+ADJ INSERT → 2.6。完整檔(含 1.0+2.3)且正式庫 freeze/id 同源時,可跳過正式庫 1.0/2.3。預設只匯 ledger+日結(舊行為)。