From c7bc554ecae3b3de95e43a8874e8f0f7e51fa616 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Tue, 16 Jun 2026 13:13:46 +0800 Subject: [PATCH] =?UTF-8?q?[FIX]=20=E4=BF=AE=E5=BE=A9=E6=8E=83=E7=A2=BC?= =?UTF-8?q?=E6=94=AF=E4=BB=98(=E7=8E=89=E5=B1=B1)=E7=B5=90=E6=A1=88?= =?UTF-8?q?=E5=89=8D=E8=B3=BC=E7=89=A9=E8=BB=8A=E8=A2=AB=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E5=B0=8E=E8=87=B4=E3=80=8C=E7=84=A1=E5=95=86=E5=93=81=E7=B4=80?= =?UTF-8?q?=E9=8C=84=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 根因:order.items 在結案 build() 當下才即時讀 live buyList,掃碼支付(玉山)流程較慢(掃碼+玉山API約20s),結案前 buyList 會被取消/倒數路徑清空,造成 order.items 變空陣列(無商品紀錄),但出貨其實成功(dispense 有商品)。 2. TransactionFinalizeBuilder 新增 itemsSnapshot 商品明細快照欄位與 captureItems() 方法;buyList 為空時不覆寫既有快照。 3. 抽出共用 buildItemRecords() helper,captureItems() 與 build() fallback 共用。 4. build()/buildOrderPayload() 改為優先採用快照,無快照時才 fallback 讀 live buyList。 5. DispatchDialog onCreate 建立 finalizeBuilder 後立即 captureItems(),此時 buyList 必完整。 6. 升版 verPatch 95 → 96 (S9XYU versionName 21_01_96_R / versionCode 210196)。 正常情況快照==live,逐字等價;僅在 buyList 被清空的 bug 情境修正。純 App 端,不影響後台與線上 main。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle | 2 +- .../tools/TransactionFinalizeBuilder.java | 65 +++++++++++++++---- .../smartdevice/ui/dialog/DispatchDialog.java | 4 ++ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6f8ad82..f78d58e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 94 +def verPatch = 96 diff --git a/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java b/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java index af93964..536b8d2 100644 --- a/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java +++ b/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java @@ -47,6 +47,14 @@ public class TransactionFinalizeBuilder { /** 累積的出貨結果清單 */ private final List dispenseRecords = new ArrayList<>(); + /** + * 商品明細快照。在「buyList 仍完整」時擷取(例如進入出貨流程當下); + * 之後 build() 會優先採用此快照,不再即時讀 live buyList。 + * 用以修正掃碼支付(玉山)等較慢流程:結案前 buyList 可能被取消/倒數路徑清空, + * 導致 order.items 變成空陣列(出現「無商品紀錄」),但出貨其實成功。 + */ + private List itemsSnapshot = null; + private String codeId = "0"; /** 交易生命週期狀態(pending/completed/failed/abandoned);預設 completed 維持成功路徑原行為。 */ @@ -151,6 +159,42 @@ public class TransactionFinalizeBuilder { } + /** + * 擷取目前 buyList 的商品明細快照。 + * 應在「buyList 仍完整」時呼叫(例如 DispatchDialog 進入出貨流程當下); + * 之後 {@link #build()} 會優先採用此快照,不再即時讀 live buyList, + * 避免結案前 buyList 被取消/倒數等其他執行緒清空,導致 order.items 變成空陣列。 + * buyList 為空時不覆寫既有快照(避免把有效快照洗成空)。 + */ + public void captureItems() { + List buyList = MyApp.getInstance().getBuyList(); + if (buyList == null || buyList.isEmpty()) { + return; + } + this.itemsSnapshot = buildItemRecords(buyList); + Log.i(TAG, "captureItems: snapshot " + this.itemsSnapshot.size() + + " item(s), flow_id=" + flowId); + } + + /** 由 buyList 組出 items 明細(captureItems 與 build() fallback 共用)。 */ + private List buildItemRecords(List buyList) { + List items = new ArrayList<>(); + if (buyList != null) { + for (BuyStructure buy : buyList) { + int productId = 0; + try { + productId = Integer.parseInt(buy.getProduct().getProductID()); + } catch (NumberFormatException e) { + Log.w(TAG, "Cannot parse productID to int: " + buy.getProduct().getProductID()); + } + int price = buy.getProduct().getRealPrice(); + int quantity = buy.getCount() > 0 ? buy.getCount() : 1; + items.add(new TransactionFinalizePayload.ItemRecord(productId, price, quantity)); + } + } + return items; + } + /** * 加入一筆出貨結果記錄(每個貨道出貨後呼叫一次)。 * @@ -309,20 +353,13 @@ public class TransactionFinalizeBuilder { order.setMachine_time(orderMachineTime); // --- 組裝購買商品清單 (items) --- - List items = new ArrayList<>(); - List buyList = MyApp.getInstance().getBuyList(); - if (buyList != null) { - for (BuyStructure buy : buyList) { - int productId = 0; - try { - productId = Integer.parseInt(buy.getProduct().getProductID()); - } catch (NumberFormatException e) { - Log.w(TAG, "Cannot parse productID to int: " + buy.getProduct().getProductID()); - } - int price = buy.getProduct().getRealPrice(); - int quantity = buy.getCount() > 0 ? buy.getCount() : 1; - items.add(new TransactionFinalizePayload.ItemRecord(productId, price, quantity)); - } + // 優先採用 captureItems() 在出貨流程開始時擷取的快照;無快照時才即時讀 live buyList。 + // 快照可避免結案前 buyList 被其他執行緒清空(掃碼支付等慢流程的競態),造成「無商品紀錄」。 + List items; + if (itemsSnapshot != null && !itemsSnapshot.isEmpty()) { + items = new ArrayList<>(itemsSnapshot); + } else { + items = buildItemRecords(MyApp.getInstance().getBuyList()); } order.setItems(items); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DispatchDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DispatchDialog.java index 6ddb946..f621596 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DispatchDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DispatchDialog.java @@ -258,6 +258,10 @@ public class DispatchDialog extends DialogAbstract { httpAPI = new HttpAPI(getHandlerMain()); // 初始化 Finalize Builder(在支付完成、進入出貨流程時建立) finalizeBuilder = new TransactionFinalizeBuilder(); + // 進入出貨流程當下 buyList 必為完整(下方即用它組 dispatchList),先擷取商品明細快照。 + // 之後結案 build() 會優先採用此快照,避免掃碼支付(玉山)等慢流程在結案前 buyList + // 被取消/倒數路徑清空,導致 order.items 變空(無商品紀錄)但出貨其實成功。 + finalizeBuilder.captureItems(); if (com.unibuy.smartdevice.AppEntry.isCmuh()) { MyApp.getInstance().getReportFlowInfoData().setFlowType(42); }