From a5add7d8ae87a8ea7e2037c7d85d6b0986b69a42 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Tue, 2 Jun 2026 16:00:58 +0800 Subject: [PATCH] =?UTF-8?q?RELEASE:=20=E4=BF=AE=E6=AD=A3=E5=A4=9A=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=8F=96=E8=B2=A8=20UI=20=E5=80=92=E6=95=B8=E9=96=83?= =?UTF-8?q?=E8=B7=B3=E8=88=87=203=20=E5=88=86=E9=90=98=E9=96=92=E7=BD=AE?= =?UTF-8?q?=E5=B4=A9=E6=BD=B0=E5=95=8F=E9=A1=8C=EF=BC=8C=E7=99=BC=E4=BD=88?= =?UTF-8?q?=E7=89=88=E6=9C=AC=2065?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- .../smartdevice/ui/FontendActivity.java | 14 +- .../smartdevice/ui/dialog/DispatchDialog.java | 395 +++++++++++------- 3 files changed, 259 insertions(+), 152 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a85360a..6507b9b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 56 +def verPatch = 65 android { diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java index 1b46301..0d16ca5 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java @@ -238,7 +238,11 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl ElectricFragment.Option.SHIPPED_PRODUCT.getOption(), "shipped product"); } saveData(); - setBottomNavigation();// 這一個方法是在消費完後,它會呼叫本方法,更新銷售平台的商品列長fragment + + // 🚩 安全生命週期防禦:如果 Activity 已在銷毀流程中,絕不執行 Fragment 提交,避免崩潰 + if (!isFinishing() && !isDestroyed()) { + setBottomNavigation();// 這一個方法是在消費完後,它會呼叫本方法,更新銷售平台的商品列長fragment + } break; case REPORT_AND_SHOT_TIME: String ver = ""; @@ -523,6 +527,14 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl resetIdleTimer(); return; } + + // 🚩 若當前機台正處於忙碌/出貨狀態,則忽略閒置返回待機頁,並重新計時,防止出貨中 Activity 被銷毀 + if (MyApp.getInstance().isMachineBusy()) { + getLogs().info("ℹ️ 偵測到當前機台正處於忙碌/出貨狀態,忽略閒置返回待機頁。原因: " + MyApp.getInstance().getBusyReason()); + resetIdleTimer(); + return; + } + getLogs().info("⚠️ 無操作 3 分鐘,自動返回初始畫面(清空購物車)"); MyApp.getInstance().getBuyList().clear(); // 🚩 逾時返回首頁時,務必清空購物車,避免機台進入永久忙碌狀態 Intent intent = new Intent(FontendActivity.this, AppEntry.initialActivity()); 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 030fe17..7bb704c 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 @@ -135,22 +135,24 @@ public class DispatchDialog extends DialogAbstract { cancelOnThreadCountdown(); break; case SET_COUNTDOWN_TEXT: - if (isFinalizing) break; // 結案倒數中,忽略舊的倒數事件 + if (isFinalizing) + break; // 結案倒數中,忽略舊的倒數事件 setButtonOkText(Integer.parseInt(message)); - if (Integer.parseInt(message) <= 15) { - setTextViewCountdown(Integer.parseInt(message)); - } + // 💡 徹底閹割背景防護計時器對 TextView 倒數文字的修改,防堵假倒數與 14 秒閃跳 Bug! break; case NEXT_PRODUCT: - if (isFinalizing) break; // 結案倒數中,不再重新啟動出貨 + if (isFinalizing) + break; // 結案倒數中,不再重新啟動出貨 currentlyProcessingGoods(); break; case ENABLED_TRUE: - if (isFinalizing) break; // 結案倒數中,不改變按鈕狀態 + if (isFinalizing) + break; // 結案倒數中,不改變按鈕狀態 openButtonOk(); break; case ENABLED_FALSE: - if (isFinalizing) break; // 結案倒數中,不改變按鈕狀態 + if (isFinalizing) + break; // 結案倒數中,不改變按鈕狀態 closeButtonOk(); break; case REPORT_DISPATCH: @@ -162,15 +164,18 @@ public class DispatchDialog extends DialogAbstract { setTextViewSmall(message); break; case CURRENTLY_PROCESSING_GOODS: - if (isFinalizing) break; // 結案倒數中,不再處理 + if (isFinalizing) + break; // 結案倒數中,不再處理 currentlyProcessingGoods(); break; case SHIPPED_PROCESSING_GOODS: - if (isFinalizing) break; // 結案倒數中,不再處理 + if (isFinalizing) + break; // 結案倒數中,不再處理 shippedProcessingGoods(); break; case NEXT_PROCESS_GOODS: - if (isFinalizing) break; // 結案倒數中,不再處理 + if (isFinalizing) + break; // 結案倒數中,不再處理 nextProcessGoods(); break; case ERROR_PROCESS_GOODS: @@ -191,7 +196,7 @@ public class DispatchDialog extends DialogAbstract { startCountdown(); getLogs().info("✅ 出貨到一半失敗"); - deliverylot(0);//出貨失敗,寫入log + deliverylot(0);// 出貨失敗,寫入log getLogs().info("📦 出貨狀況總結:"); for (String logItem : deliveryLogs) { getLogs().info(logItem); @@ -268,26 +273,30 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("✅ BuyList 有多筆,將依序連續出貨。"); } - for (BuyStructure buyStructure: buyList) { + for (BuyStructure buyStructure : buyList) { getLogs().info(buyStructure.toString()); - if (!buyStructure.getProduct().getMaterialCode().isEmpty() && buyStructure.getProduct().getMaterialCode().contains(":")) { + if (!buyStructure.getProduct().getMaterialCode().isEmpty() + && buyStructure.getProduct().getMaterialCode().contains(":")) { String[] materialCodes = buyStructure.getProduct().getMaterialCode().split(":"); String productId = materialCodes[0]; int count = Integer.parseInt(materialCodes[1]); try { - SlotStructure slotData = MyApp.getInstance().getSlotData(buyStructure.getField(), buyStructure.getSlot()); + SlotStructure slotData = MyApp.getInstance().getSlotData(buyStructure.getField(), + buyStructure.getSlot()); if (slotData.getProduct().getProductID().equals(productId)) { - DispatchStructure dispatchData = new DispatchStructure(buyStructure.getField(), buyStructure.getSlot(), slotData.getProduct(), count*buyStructure.getCount()); + DispatchStructure dispatchData = new DispatchStructure(buyStructure.getField(), + buyStructure.getSlot(), slotData.getProduct(), count * buyStructure.getCount()); dispatchData.setFreeGift(buyStructure.isFreeGift()); // ✅ 這裡的 buyStructure.getCount() 現在會是正確的 QTY this.dispatchList.add(dispatchData); } } catch (LogsEmptyException e) { throw new RuntimeException(e); - //很嚴重的錯誤 + // 很嚴重的錯誤 } } else { - DispatchStructure dispatchData = new DispatchStructure(buyStructure.getField(), buyStructure.getSlot(), buyStructure.getProduct(), buyStructure.getCount()); + DispatchStructure dispatchData = new DispatchStructure(buyStructure.getField(), buyStructure.getSlot(), + buyStructure.getProduct(), buyStructure.getCount()); dispatchData.setFreeGift(buyStructure.isFreeGift()); // ✅ 這裡的 buyStructure.getCount() 現在會是正確的 QTY this.dispatchList.add(dispatchData); @@ -313,10 +322,13 @@ public class DispatchDialog extends DialogAbstract { if (decryptedStr != null && !decryptedStr.isEmpty()) { JSONObject fullJson = new JSONObject(decryptedStr); patName = fullJson.optString("NAME", ""); - if (patName.isEmpty()) patName = fullJson.optString("PATNAME", ""); - if (patName.isEmpty()) patName = fullJson.optString("patName", ""); - if (patName.isEmpty()) patName = "未知患者"; - + if (patName.isEmpty()) + patName = fullJson.optString("PATNAME", ""); + if (patName.isEmpty()) + patName = fullJson.optString("patName", ""); + if (patName.isEmpty()) + patName = "未知患者"; + sn = fullJson.optString("SN", ""); drgNo = fullJson.optString("DRGNO", ""); } @@ -335,11 +347,12 @@ public class DispatchDialog extends DialogAbstract { // 按照最新要求:[患者姓名]([藥單號]) 用括號 String barcodeInfo = patName + "(" + drgNo + ")"; - getLogs().info("✅ 中國醫 QR Code 取物單資訊打包上報:姓名 " + patName + ", 藥單號 " + drgNo + " -> barcodeInfo: " + barcodeInfo); - + getLogs().info( + "✅ 中國醫 QR Code 取物單資訊打包上報:姓名 " + patName + ", 藥單號 " + drgNo + " -> barcodeInfo: " + barcodeInfo); + // 設定到 flowBarCode (會連動到後台 member_barcode 欄位) MyApp.getInstance().getReportFlowInfoData().setFlowBarCode(barcodeInfo); - + // CMUH: flow_id 保留 App 機台流水號;order_no 使用取物單序號 SN 方便後台對藥單。 if (sn != null && !sn.isEmpty()) { finalizeBuilder.setOrderNo(sn); @@ -351,7 +364,8 @@ public class DispatchDialog extends DialogAbstract { } } - this.recyclerDialogDispatchListAdpter = new RecyclerDialogDispatchListAdpter(getCtx(), this.dispatchList, getHandlerMain()); + this.recyclerDialogDispatchListAdpter = new RecyclerDialogDispatchListAdpter(getCtx(), this.dispatchList, + getHandlerMain()); this.binding.recyclerDialogDispatchList.setAdapter(recyclerDialogDispatchListAdpter); this.binding.recyclerDialogDispatchList.setLayoutManager(new LinearLayoutManager(context)); @@ -469,15 +483,19 @@ public class DispatchDialog extends DialogAbstract { @Override public void readBufferOnScheduler(DevController devController, HandlerMain handlerMain) { - if (isCheckSlotByVMC) return; + if (isCheckSlotByVMC) + return; getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA006"); - isCheckSlotByVMC = true; //進來立即設定true,可以判斷掉重複出貨 + isCheckSlotByVMC = true; // 進來立即設定true,可以判斷掉重複出貨 boolean isCMD = false; boolean isStatus = false; String errorDetail = ""; - for (byte[] buffer: devController.getReadBufferByMax()) { + for (byte[] buffer : devController.getReadBufferByMax()) { + getLogs().info("🔍 checkSlot 收到原始 Buffer: " + PortTools.showHex(buffer)); + deliveryLogs.add("🔍 checkSlot 收到: " + PortTools.showHex(buffer)); + if (buffer[0] == 0x02) { getLogs().info("XinYuan Read:" + PortTools.showHex(buffer)); @@ -495,16 +513,20 @@ public class DispatchDialog extends DialogAbstract { if (isCMD) { if (isStatus) { - getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA001"); - setTextViewCountdown("商品出貨中",65); - MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000); - MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().getProduct(slot)); + getLogs().info("-----------------商品出貨中-----------------"); + setTextViewCountdown("商品出貨中", 65); + MyApp.getInstance().getDevXinYuanController() + .getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000); + MyApp.getInstance().getDevXinYuanController().addSendBuffer( + MyApp.getInstance().getDevXinYuanController().getDevXinYuan().getProduct(slot)); } else { if (!errorDetail.isEmpty()) { MqttManager.getInstance().publishError(slot, errorDetail); } buttonControlCountdown.shutdown(); - getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), getCtx().getString(R.string.check_whether_goods_have_been_taken_out_before_confirming_collection));//請檢查商品有無取出再確認取貨 + getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), + getCtx().getString( + R.string.check_whether_goods_have_been_taken_out_before_confirming_collection));// 請檢查商品有無取出再確認取貨 } } else { boolean isCmuh = "Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project); @@ -517,19 +539,24 @@ public class DispatchDialog extends DialogAbstract { if (!MyApp.checkAndReconnectDev()) { getLogs().info("無法連線到下位機,出貨作業中止"); } - getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), "領藥有問題 請等3秒後再重掃"); + getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), + "領藥有問題 請等3秒後再重掃"); } else { - getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), getCtx().getString(R.string.slave_computer_did_not_respond_wait_3_seconds_try_again)); + getSrcHandlerMain().start(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), + getCtx().getString(R.string.slave_computer_did_not_respond_wait_3_seconds_try_again)); MyApp.getInstance().getDevXinYuanController().start(); } checkSlotRunCount = 0; } else { - getLogs().info("機台未回應,第 " + (checkSlotRunCount + 1) + " 次重試 checkSlot..." + (isCmuh ? " (溫和模式,不發送指令)" : "")); + getLogs().info("機台未回應,第 " + (checkSlotRunCount + 1) + " 次重試 checkSlot..." + + (isCmuh ? " (溫和模式,不發送指令)" : "")); isCheckSlotByVMC = false; // 重置旗標,允許下次回調進入 checkSlotRunCount++; - MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevCheckSlotByVMC(handlerMain, slot)); + MyApp.getInstance().getDevXinYuanController() + .getReadBufferByNow(new DevCheckSlotByVMC(handlerMain, slot)); if (!isCmuh) { - MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); + MyApp.getInstance().getDevXinYuanController().addSendBuffer( + MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); } } } @@ -538,6 +565,7 @@ public class DispatchDialog extends DialogAbstract { private boolean isGetProductByVMC = false; private int getProductRunCount = 0; + public class DevGetProductByVMC extends DevController.ReadBufferOnScheduler { private int slot; @@ -559,24 +587,28 @@ public class DispatchDialog extends DialogAbstract { @Override public void readBufferOnScheduler(DevController devController, HandlerMain handlerMain) { - if (isGetProductByVMC) return; + if (isGetProductByVMC) + return; boolean isCMD = false; boolean inTheMiddle = false; boolean isSuccess = false; String errorDetail = ""; - for (byte[] buffer: devController.getReadBufferByMax()) { + for (byte[] buffer : devController.getReadBufferByMax()) { + getLogs().info("🔍 getProduct 收到原始 Buffer: " + PortTools.showHex(buffer)); + deliveryLogs.add("🔍 getProduct 收到: " + PortTools.showHex(buffer)); + if (buffer[0] == 0x04) { getLogs().info("XinYuan Read:" + PortTools.showHex(buffer)); isCMD = true; -// if (buffer[1] == 0x01) { -// inTheMiddle = true; -// isSuccess = true; -// break; -// } + // if (buffer[1] == 0x01) { + // inTheMiddle = true; + // isSuccess = true; + // break; + // } if (buffer[1] == 0x01 || buffer[1] == 0x10 || buffer[1] == 0x11) { inTheMiddle = true; @@ -624,23 +656,28 @@ public class DispatchDialog extends DialogAbstract { } } getLogs().info("✅ 出貨成功,觸發 nextProcessGoods"); - deliverylot(1);//出貨成功,寫入出貨狀況的LOG - getSrcHandlerMain().send(getClass().getSimpleName(), Option.NEXT_PROCESS_GOODS.getOption(), "next process goods"); + deliverylot(1);// 出貨成功,寫入出貨狀況的LOG + getSrcHandlerMain().send(getClass().getSimpleName(), Option.NEXT_PROCESS_GOODS.getOption(), + "next process goods"); } else { getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA002"); - MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot),5000L); + MyApp.getInstance().getDevXinYuanController() + .getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000L); } } else { if (!errorDetail.isEmpty()) { MqttManager.getInstance().publishError(slot, errorDetail); } buttonControlCountdown.shutdown(); - getSrcHandlerMain().send(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), getCtx().getString(R.string.product_delivery_issues_contact_the_counter_window)); + getSrcHandlerMain().send(getClass().getSimpleName(), Option.ERROR_PROCESS_GOODS.getOption(), + getCtx().getString(R.string.product_delivery_issues_contact_the_counter_window)); } } else { getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA003"); - MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot),5000L); - MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); + MyApp.getInstance().getDevXinYuanController() + .getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000L); + MyApp.getInstance().getDevXinYuanController() + .addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); } } } @@ -650,13 +687,13 @@ public class DispatchDialog extends DialogAbstract { slotsDao.deleteAll(); slotsDao.insertAll(MyApp.getInstance().getSlotMap()); slotsDao.close(); - getLogs().info("save count:"+ MyApp.getInstance().getSlotList(SlotField.VMC.getField()).size()); + getLogs().info("save count:" + MyApp.getInstance().getSlotList(SlotField.VMC.getField()).size()); } - public void currentlyProcessingGoods() { //出貨前動作,UI與資料變更 + public void currentlyProcessingGoods() { // 出貨前動作,UI與資料變更 getLogs().info("----- currentlyProcessingGoods -----"); - if (this.dispatchIndex == -1) { //最後一次確定完後沒有貨要出就關閉視窗 + if (this.dispatchIndex == -1) { // 最後一次確定完後沒有貨要出就關閉視窗 dialogCancel(); } @@ -666,11 +703,11 @@ public class DispatchDialog extends DialogAbstract { int slot = dispatch.getSlot(); int count = dispatch.getCount(); int quantity = dispatch.getQuantity(); - boolean isShipped = dispatch.isShipped(); //表記是否是正在出貨 - int increase = 1; //機台要出幾個貨? + boolean isShipped = dispatch.isShipped(); // 表記是否是正在出貨 + int increase = 1; // 機台要出幾個貨? try { - //出貨數量 + // 出貨數量 if (SlotField.getSlotField(field).isCanOneShipped()) { increase = 1; } else { @@ -690,11 +727,11 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("isShipped:" + isShipped); getLogs().info("dispatchIndex:" + dispatchIndex + " dispatchList:" + dispatchList.size()); - getLogs().info("count:" + count +" quantity:" + quantity); + getLogs().info("count:" + count + " quantity:" + quantity); - if (isShipped) { //正在要出貨的話 - if (count < quantity) { //count 目前出了多少貨 Quantity 總貨物數量 - //設定商品名稱和圖片 + if (isShipped) { // 正在要出貨的話 + if (count < quantity) { // count 目前出了多少貨 Quantity 總貨物數量 + // 設定商品名稱和圖片 binding.textProductName.setText(product.getProductName()); try { @@ -710,14 +747,15 @@ public class DispatchDialog extends DialogAbstract { getProductRunCount = 0; isChangeStatus = true; - getHandlerMain().start(getClass().getSimpleName(), Option.SHIPPED_PROCESSING_GOODS.getOption(), "shipped processing goods"); + getHandlerMain().start(getClass().getSimpleName(), Option.SHIPPED_PROCESSING_GOODS.getOption(), + "shipped processing goods"); } } } } - //devShippedProduct(field, slot, increase); - //出貨中動作 + // devShippedProduct(field, slot, increase); + // 出貨中動作 public void shippedProcessingGoods() { getLogs().info("----- shippedProcessingGoods -----"); @@ -727,11 +765,11 @@ public class DispatchDialog extends DialogAbstract { int slot = dispatch.getSlot(); int count = dispatch.getCount(); int quantity = dispatch.getQuantity(); - boolean isShipped = dispatch.isShipped(); //表記是否是正在出貨 - int increase = 1; //機台要出幾個貨? + boolean isShipped = dispatch.isShipped(); // 表記是否是正在出貨 + int increase = 1; // 機台要出幾個貨? try { - //出貨數量 + // 出貨數量 if (SlotField.getSlotField(field).isCanOneShipped()) { increase = 1; } else { @@ -745,10 +783,10 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("isShipped:" + isShipped); getLogs().info("dispatchIndex:" + dispatchIndex + " dispatchList:" + dispatchList.size()); - getLogs().info("count:" + count +" quantity:" + quantity); + getLogs().info("count:" + count + " quantity:" + quantity); - if (isShipped) { //正在要出貨的話 - //控制倒數計時,先倒數再出貨 + if (isShipped) { // 正在要出貨的話 + // 控制倒數計時,先倒數再出貨 try { SlotField slotField = SlotField.getSlotField(field); if (slotField.isAutoNextProduct()) { @@ -768,25 +806,26 @@ public class DispatchDialog extends DialogAbstract { } closeButtonOk(); devRunVMC(field, slot, increase); -// try { -// closeButtonOk(); -// SlotField slotField = SlotField.getSlotField(field); -// switch (slotField) { -// case VMC: -// devRunVMC(field, slot, increase); -// break; -// case ELECTRIC: -// try { -// devRunElectric(field, slot, increase); -// } catch (LogsSecurityException | LogsUnsupportedOperationException | LogsIOException e) { -// getLogs().warning(e); -// } -// -// break; -// } -// } catch (LogsEmptyException e) { -// getLogs().warning(e); -// } + // try { + // closeButtonOk(); + // SlotField slotField = SlotField.getSlotField(field); + // switch (slotField) { + // case VMC: + // devRunVMC(field, slot, increase); + // break; + // case ELECTRIC: + // try { + // devRunElectric(field, slot, increase); + // } catch (LogsSecurityException | LogsUnsupportedOperationException | + // LogsIOException e) { + // getLogs().warning(e); + // } + // + // break; + // } + // } catch (LogsEmptyException e) { + // getLogs().warning(e); + // } } } else { dialogCancel(); @@ -795,12 +834,14 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("index:" + this.dispatchIndex); } - private void nextProcessGoods() { //出貨後動作,索引變更和上傳資料 + private void nextProcessGoods() { // 出貨後動作,索引變更和上傳資料 getLogs().info("----- nextProcessGoods -----"); // ✅ 在方法開頭檢查 dispatchIndex 的有效性 - if (this.dispatchList == null || this.dispatchList.isEmpty() || this.dispatchIndex < 0 || this.dispatchIndex >= this.dispatchList.size()) { - getLogs().info("⚠ nextProcessGoods: dispatchList 為空或 dispatchIndex 越界,直接返回。dispatchIndex=" + this.dispatchIndex); + if (this.dispatchList == null || this.dispatchList.isEmpty() || this.dispatchIndex < 0 + || this.dispatchIndex >= this.dispatchList.size()) { + getLogs().info( + "⚠ nextProcessGoods: dispatchList 為空或 dispatchIndex 越界,直接返回。dispatchIndex=" + this.dispatchIndex); isDispatchingNext = false; // 確保重置鎖 isGetProductByVMC = false; // 確保重置鎖 return; @@ -817,9 +858,9 @@ public class DispatchDialog extends DialogAbstract { int slot = dispatch.getSlot(); int count = dispatch.getCount(); int quantity = dispatch.getQuantity(); - boolean isShipped = dispatch.isShipped(); //表記是否是正在出貨 - int increase = 1; //機台要出幾個貨? - int remaining = 0;//計算貨道剩餘數量 + boolean isShipped = dispatch.isShipped(); // 表記是否是正在出貨 + int increase = 1; // 機台要出幾個貨? + int remaining = 0;// 計算貨道剩餘數量 // ✅ 防呆:如果這筆出貨已完成,不應再進入出貨流程 if (count >= quantity) { @@ -831,7 +872,7 @@ public class DispatchDialog extends DialogAbstract { // 🔽 保持原本 nextProcessGoods 原樣繼續執行... try { - //出貨數量 + // 出貨數量 if (SlotField.getSlotField(field).isCanOneShipped()) { increase = 1; } else { @@ -844,15 +885,15 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("isShipped:" + isShipped); getLogs().info("dispatchIndex:" + dispatchIndex + " dispatchList:" + dispatchList.size()); - getLogs().info("count:" + count +" quantity:" + quantity); + getLogs().info("count:" + count + " quantity:" + quantity); - if (isShipped) { //正在要出貨的話,出貨完成後 - if (count < quantity) { //count 目前出了多少貨 Quantity 總貨物數量 + if (isShipped) { // 正在要出貨的話,出貨完成後 + if (count < quantity) { // count 目前出了多少貨 Quantity 總貨物數量 try { - if (SlotField.getSlotField(field).isCanOneShipped()) { //一個出貨還是一次出貨 - count += 1; //目前出貨量+將要出1個 + if (SlotField.getSlotField(field).isCanOneShipped()) { // 一個出貨還是一次出貨 + count += 1; // 目前出貨量+將要出1個 } else { - count = quantity; //要出全部 + count = quantity; // 要出全部 } } catch (LogsEmptyException e) { count += 1; @@ -864,7 +905,7 @@ public class DispatchDialog extends DialogAbstract { try { int slotCount = MyApp.getInstance().getSlotData(field, slot).getCount(); - remaining = slotCount-increase; //計算剩餘數量 + remaining = slotCount - increase; // 計算剩餘數量 MyApp.getInstance().getSlotData(field, slot).setCount(remaining); MyApp.getInstance().getReportDispatchInfoData().setRemaining(remaining); } catch (LogsEmptyException e) { @@ -876,30 +917,36 @@ public class DispatchDialog extends DialogAbstract { * 1)將原來的ProductId欄位,由醫令代碼修改為原單的ProductId代碼 * 2)flowtype 直接固定寫入42,代表QRCode取貨,中國醫專用 * 3)dispatchStatusType欄位寫入0,代表成功 - * */ + */ int price = product.getRealPrice() * count; // [MQTT Finalize 模式] 累積出貨成功記錄,最後統一送出 int productIdInt = 0; - try { productIdInt = Integer.parseInt(product.getProductID()); } catch (Exception ignored) {} + try { + productIdInt = Integer.parseInt(product.getProductID()); + } catch (Exception ignored) { + } finalizeBuilder.addDispenseRecord(slot, productIdInt, price * increase, 0, true, remaining); - getLogs().info("[Finalize] 已累積出貨記錄: slot=" + slot + ", product=" + productIdInt + ", remaining=" + remaining); + getLogs().info( + "[Finalize] 已累積出貨記錄: slot=" + slot + ", product=" + productIdInt + ", remaining=" + remaining); - getLogs().info("count:" + count +" quantity:" + quantity); + getLogs().info("count:" + count + " quantity:" + quantity); - if (count == quantity) { //貨物是否已經出貨完畢 - //偵測是否還有下個出貨商品 - if (this.dispatchIndex >= this.dispatchList.size()-1) { //沒有貨 + if (count == quantity) { // 貨物是否已經出貨完畢 + // 偵測是否還有下個出貨商品 + if (this.dispatchIndex >= this.dispatchList.size() - 1) { // 沒有貨 // ✅ 所有商品出貨完畢,發送 MQTT Finalize 結案訊息 - if (isFinalizing) return; // 防呆:如果已經在結案倒數了,直接跳出 + if (isFinalizing) + return; // 防呆:如果已經在結案倒數了,直接跳出 isFinalizing = true; - getLogs().info("所有商品出貨完畢,發送 MQTT Finalize 結案訊息。"); - if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project)) { - insertDrgnoToMemo(); - } - MqttManager.getInstance().publishTransactionFinalize(finalizeBuilder.build()); - getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(), "update data"); - saveData(); + getLogs().info("所有商品出貨完畢,發送 MQTT Finalize 結案訊息。"); + if ("Cmuh".equalsIgnoreCase(com.unibuy.smartdevice.BuildConfig.FLAVOR_project)) { + insertDrgnoToMemo(); + } + MqttManager.getInstance().publishTransactionFinalize(finalizeBuilder.build()); + getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(), + "update data"); + saveData(); getLogs().info("📦 出貨狀況總結:"); for (String logItem : deliveryLogs) { @@ -926,12 +973,17 @@ public class DispatchDialog extends DialogAbstract { // 顯示取貨倒數 15 秒後自動關閉對話框 isChangeStatus = true; - setTextViewCountdown(getCtx().getString(R.string.pick_up_goods_within_15_seconds_after_pick_up_port_opened), 65); + // 💡 統一都有括號,消除初始與倒數的格式不一致閃爍 + setTextViewCountdown( + getCtx().getString(R.string.pickup_port_open) + "(15)" + + getCtx().getString(R.string.pick_up_within), + 65); countDownTimer = new CountDownTimer(15_000, 1_000) { public void onTick(long millisUntilFinished) { int seconds = (int) (millisUntilFinished / 1000); setTextViewCountdown(seconds); } + public void onFinish() { dialogCancel(); } @@ -941,17 +993,50 @@ public class DispatchDialog extends DialogAbstract { isDispatchingNext = false; isGetProductByVMC = false; return; // 結束方法 - } else { //有貨要出了 - this.dispatchIndex += 1; //表記下一個商品要出貨 + } else { // 有下一個商品要出了 + getLogs().info("📦 還有商品要出,啟動精準 15 秒取貨時間後再出下一個商品"); + + // 1. 停止原本在出貨中啟動的 40 秒防護計時器,防堵背景干擾 + closeDialogCountdown.shutdown(); + if (!buttonControlCountdown.isShutdown()) { + buttonControlCountdown.shutdown(); + } + if (countDownTimer != null) { + countDownTimer.cancel(); + } + + // 2. 表記下一個商品要出貨 + this.dispatchIndex += 1; + + // 3. 啟動精準的 15 秒取貨倒數計時,時間到了之後再自動出下一個商品! + isChangeStatus = true; + // 💡 統一都有括號,消除初始與倒數的格式不一致閃爍 + setTextViewCountdown( + getCtx().getString(R.string.pickup_port_open) + "(15)" + + getCtx().getString(R.string.pick_up_within), + 65); + countDownTimer = new CountDownTimer(15_000, 1_000) { + public void onTick(long millisUntilFinished) { + int seconds = (int) (millisUntilFinished / 1000); + setTextViewCountdown(seconds); + } + + public void onFinish() { + getLogs().info("📢 15 秒取貨時間結束,自動啟動下一個商品出貨。Index=" + dispatchIndex); + if (!isFinalizing) { + currentlyProcessingGoods(); + } + } + }.start(); } } } - getLogs().info("確保重置出貨控制狀態 dispatchIndex="+dispatchIndex); + getLogs().info("確保重置出貨控制狀態 dispatchIndex=" + dispatchIndex); // ✅ 在這裡確保重置出貨控制狀態 dispatch.setGetProductComplete(false); // 重置出貨旗標 - isDispatchingNext = false; // 解鎖流程控制 - isGetProductByVMC = false; // 解鎖出貨成功判定 + isDispatchingNext = false; // 解鎖流程控制 + isGetProductByVMC = false; // 解鎖出貨成功判定 } private void finalizeFailedDispatch(String message) { @@ -975,7 +1060,8 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("出貨失敗,發送 MQTT Finalize 結案訊息: " + message); MqttManager.getInstance().publishTransactionFinalize(finalizeBuilder.build()); - getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(), "update data"); + getSrcHandlerMain().send(getClass().getSimpleName(), FontendActivity.Option.RESET_DATA.getOption(), + "update data"); saveData(); } @@ -1028,15 +1114,16 @@ public class DispatchDialog extends DialogAbstract { return false; } - private void deliverylot(int msgflag){ + private void deliverylot(int msgflag) { String productId = dispatchList.get(dispatchIndex).getProduct().getProductID(); String productName = dispatchList.get(dispatchIndex).getProduct().getProductName(); - if(msgflag == 1){ + if (msgflag == 1) { deliveryLogs.add("✔ 出貨成功 - " + productId + " / " + productName); - }else if(msgflag == 0) { + } else if (msgflag == 0) { deliveryLogs.add("✖ 出貨失敗(貨道無實體商品或商品未取出等問題)- " + productId + " / " + productName); } } + private void devRunVMC(int field, int slot, int increase) { if (buttonControlCountdown.isShutdown()) { @@ -1048,11 +1135,13 @@ public class DispatchDialog extends DialogAbstract { getLogs().info("11111"); MyApp.getInstance().getDevXinYuanController().getReadBufferByNow(new DevCheckSlotByVMC(getHandlerMain(), slot)); getLogs().info("22222"); - MyApp.getInstance().getDevXinYuanController().addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); + MyApp.getInstance().getDevXinYuanController() + .addSendBuffer(MyApp.getInstance().getDevXinYuanController().getDevXinYuan().checkSlot(slot)); getLogs().info("33333"); } - private void devRunElectric(int field, int slot, int increase) throws LogsEmptyException, LogsSecurityException, LogsUnsupportedOperationException, LogsIOException { + private void devRunElectric(int field, int slot, int increase) + throws LogsEmptyException, LogsSecurityException, LogsUnsupportedOperationException, LogsIOException { if (buttonControlCountdown.isShutdown()) { buttonControlCountdown.start(5); } else { @@ -1063,7 +1152,7 @@ public class DispatchDialog extends DialogAbstract { isChangeStatus = false; SlotStructure slotData = MyApp.getInstance().getSlotData(field, slot); -// slotData.setLock(true); + // slotData.setLock(true); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.TAIWAN); Date timeEpiredDate = new Date(); if (slotData.getOtherData().has("timeExpired")) { @@ -1095,18 +1184,18 @@ public class DispatchDialog extends DialogAbstract { long diffInMillis = timeEpiredDate.getTime() - now.getTime(); long diffInSeconds = diffInMillis / 1000; // 總秒數 - int hours = (int) (diffInSeconds / 3600); // 小時 + int hours = (int) (diffInSeconds / 3600); // 小時 int minutes = (int) ((diffInSeconds % 3600) / 60); // 剩下的分鐘 - int seconds = (int) (diffInSeconds % 60); // 剩下的秒數 - int hoursByDay = (int) (diffInSeconds / 3600 % 24); // 小時 - int day = (int) (diffInSeconds / 3600 / 24); // 小時 + int seconds = (int) (diffInSeconds % 60); // 剩下的秒數 + int hoursByDay = (int) (diffInSeconds / 3600 % 24); // 小時 + int day = (int) (diffInSeconds / 3600 / 24); // 小時 getLogs().info("hours:" + hours + " minutes:" + minutes + " seconds:" + seconds); DevElectricController devElectricController = new DevElectricController(getHandlerMain()); DevDigitalDisplayController devDigitalDisplayController = new DevDigitalDisplayController(getHandlerMain()); - devElectricController.devSwitch(slot-1, hours, minutes+2, seconds); + devElectricController.devSwitch(slot - 1, hours, minutes + 2, seconds); devDigitalDisplayController.devPut(slot, hours, minutes, seconds); getHandlerMain().getHandler().postDelayed(new Runnable() { @@ -1127,7 +1216,7 @@ public class DispatchDialog extends DialogAbstract { getHandlerMain().start(getClass().getSimpleName(), Option.NEXT_PROCESS_GOODS.getOption(), "next process goods"); } - public void ReSetAPPDispatch(Context context){ + public void ReSetAPPDispatch(Context context) { MyApp.getInstance().getBuyList().clear(); if (context == null) { getLogs().info("ReSetAPP -- Context 為 null,無法重啟"); @@ -1135,7 +1224,8 @@ public class DispatchDialog extends DialogAbstract { } Intent intent = new Intent(context, FontendActivity.class); // 清空所有 Task,重新啟動 - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); // 建議搭配 finishAffinity(),確保所有舊的 Activity 都被清掉 @@ -1148,23 +1238,26 @@ public class DispatchDialog extends DialogAbstract { public void setButtonOkText(long countdown) { if (countdown <= 0) { - binding.buttonOk.setText(getContext().getString(R.string.pickup_confirmation));//取貨確認 + binding.buttonOk.setText(getContext().getString(R.string.pickup_confirmation));// 取貨確認 } else { - binding.buttonOk.setText(getContext().getString(R.string.pickup_confirmation)+"(" + countdown + ")"); + binding.buttonOk.setText(getContext().getString(R.string.pickup_confirmation) + "(" + countdown + ")"); } } - public void setTextViewCountdown(long countdown){ + public void setTextViewCountdown(long countdown) { if (countdown <= 0) { - setTextViewCountdown(getCtx().getString(R.string.pick_up_goods_within_15_seconds_after_pick_up_port_opened), 65);//取貨口開啟15秒內取貨 + setTextViewCountdown(getCtx().getString(R.string.pick_up_goods_within_15_seconds_after_pick_up_port_opened), + 65);// 取貨口開啟15秒內取貨 } else { - setTextViewCountdown(getCtx().getString(R.string.pickup_port_open)+"(" + countdown + ")"+getCtx().getString(R.string.pick_up_within), 65);//取貨口開啟,秒內取貨 + setTextViewCountdown(getCtx().getString(R.string.pickup_port_open) + "(" + countdown + ")" + + getCtx().getString(R.string.pick_up_within), 65);// 取貨口開啟,秒內取貨 } } public void setTextViewSmall(String message) { setTextViewCountdown(message, 60); } + public void setTextViewCountdown(String message, int size) { if (isChangeStatus) { binding.dispatchtips.setText(message); @@ -1189,7 +1282,8 @@ public class DispatchDialog extends DialogAbstract { @Override protected void execute(long countdown, HandlerMain handlerMain) { - handlerMain.start(getClass().getSimpleName(), Option.SET_COUNTDOWN_TEXT.getOption(), String.valueOf(countdown)); + handlerMain.start(getClass().getSimpleName(), Option.SET_COUNTDOWN_TEXT.getOption(), + String.valueOf(countdown)); } @Override @@ -1200,14 +1294,14 @@ public class DispatchDialog extends DialogAbstract { public void insertDrgnoToMemo() { try { - /*刷藥單解碼後進來的json資料*/ + /* 刷藥單解碼後進來的json資料 */ JSONObject jsonObject = new JSONObject(dispatchList.get(0).getProduct().getMarketingPlan()); // **要新增的 ITEM 資料** JSONObject newItem = new JSONObject(); - newItem.put("SN", jsonObject.optString("SN")); // **領藥人藥單序號** - newItem.put("DRGNO", jsonObject.optString("DRGNO")); // **領藥人醫令** + newItem.put("SN", jsonObject.optString("SN")); // **領藥人藥單序號** + newItem.put("DRGNO", jsonObject.optString("DRGNO")); // **領藥人醫令** - /*從Memo的JSON資料中,讀取SN及DRGNO的全部資料*/ + /* 從Memo的JSON資料中,讀取SN及DRGNO的全部資料 */ String jsonStr = MyApp.getInstance().getMemoMap().get("CMUH").get(0).getData().toString(); JSONObject jsonObject2 = new JSONObject(jsonStr); JSONArray itemArray = jsonObject2.getJSONArray("ITEM"); @@ -1216,9 +1310,10 @@ public class DispatchDialog extends DialogAbstract { boolean exists = false; for (int i = 0; i < itemArray.length(); i++) { JSONObject item = itemArray.getJSONObject(i); - if (item.getString("SN").equals(newItem.getString("SN")) && item.getString("DRGNO").equals(newItem.getString("DRGNO"))) { + if (item.getString("SN").equals(newItem.getString("SN")) + && item.getString("DRGNO").equals(newItem.getString("DRGNO"))) { exists = true; - break; // **如果已存在,就結束迴圈** + break; // **如果已存在,就結束迴圈** } }