From 1c9022cc5e78952d27ba33920afd2bbffe5b5e76 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 11:13:36 +0800 Subject: [PATCH 1/7] =?UTF-8?q?[FIX]=20=E4=BF=AE=E6=AD=A3=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=A0=BC=E7=B7=9A=E5=8F=B3=E7=B7=A3=E8=A7=B8=E6=8E=A7?= =?UTF-8?q?=E5=A4=B1=E6=95=88=E8=88=87=E7=9B=B4=E6=8E=A5=E8=B3=BC=E8=B2=B7?= =?UTF-8?q?=E6=9C=AA=E8=B7=B3=E8=B3=BC=E7=89=A9=E8=BB=8A=E9=A0=81=EF=BC=9B?= =?UTF-8?q?=E5=8D=87=E7=89=88=20v88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修正 fragment_vmc.xml 商品格線 recyclerBuy 在 ConstraintLayout 缺水平約束(原 match_parent 僅綁上下)導致右側佈局/觸控異常:改為 0dp + start/end 綁 parent 確保全寬與右緣可點。 2. RecyclerVmcBuyListAdpter 讓整張商品卡片皆可點開詳情頁(原僅圖片可點),避免點到名稱/價格區無反應。 3. 商品詳情頁「直接購買」(購物車版)改為先切換至購物車品項頁、再關閉詳情頁,避免關閉對話框與 fragment 切換時序互相干擾導致未跳頁。 4. 升版號 verPatch 87 → 88。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle | 2 +- .../smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java | 3 +++ .../com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java | 4 +++- app/src/main/res/layout/fragment_vmc.xml | 4 +++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 56d87a4..68796d3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 87 +def verPatch = 88 diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java index 0276229..ff04fdd 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java @@ -114,8 +114,11 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter { if (cartOn) { addSelectedQuantityToCart(); - cancel(); + // 先切到購物車品項頁(於詳情頁視窗後方),再關閉詳情頁顯示出來, + // 避免「關閉對話框」與「fragment 切換」時序互相干擾導致沒跳頁。 openCartList(context); + cancel(); } else { MyApp.getInstance().getBuyList().clear(); MyApp.getInstance().getBuyList().add( diff --git a/app/src/main/res/layout/fragment_vmc.xml b/app/src/main/res/layout/fragment_vmc.xml index 0700956..63f1ebf 100644 --- a/app/src/main/res/layout/fragment_vmc.xml +++ b/app/src/main/res/layout/fragment_vmc.xml @@ -9,10 +9,12 @@ From d7bef7b827a421ed01f17320dc8729774684ae72 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 11:24:28 +0800 Subject: [PATCH 2/7] =?UTF-8?q?[FIX]=20=E4=BF=AE=E6=AD=A3=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E8=B3=BC=E8=B2=B7(=E8=B3=BC=E7=89=A9=E8=BB=8A?= =?UTF-8?q?=E7=89=88)=E6=9C=AA=E8=B7=B3=E8=B3=BC=E7=89=A9=E8=BB=8A?= =?UTF-8?q?=E5=93=81=E9=A0=85=E9=A0=81=E7=9A=84=E7=9C=9F=E5=9B=A0=EF=BC=9B?= =?UTF-8?q?=E5=8D=87=E7=89=88=20v89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 真因:DialogProductDetail 取 SaleFlowHandler 時用 onCreate 的 getContext(),那是 AlertDialog 包裝過的 ContextThemeWrapper,instanceof FontendActivity 為 false,導致 openCartList 靜默 return(一般版因 requestBuy 有 new BuyDialog 後路才照常開支付頁,故僅購物車版受影響)。 2. 修法:建構子保存原始傳入的 Context(真正的 FontendActivity)為 hostContext,openCartList/requestBuy 改用 hostContext 做 instanceof 與取 SaleFlowHandler。 3. 升版號 verPatch 88 → 89。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle | 2 +- .../ui/dialog/DialogProductDetail.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 68796d3..68f123d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 88 +def verPatch = 89 diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java index aae5b49..ab8002d 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java @@ -28,10 +28,15 @@ public class DialogProductDetail extends DialogAbstract { private DialogProductDetailBinding binding; private final SlotStructure slot; + // 建構子傳入的原始 Context(真正的 FontendActivity)。 + // 不可用 onCreate 的 getContext(),那是 AlertDialog 包裝過的 ContextThemeWrapper, + // instanceof FontendActivity 會是 false,導致購物車頁/結帳入口取不到 SaleFlowHandler。 + private final Context hostContext; private int quantity = 1; public DialogProductDetail(Context context, HandlerMain srcHandlerMain, SlotStructure slot) { super(context, srcHandlerMain); + this.hostContext = context; this.slot = slot; } @@ -121,14 +126,14 @@ public class DialogProductDetail extends DialogAbstract { addSelectedQuantityToCart(); // 先切到購物車品項頁(於詳情頁視窗後方),再關閉詳情頁顯示出來, // 避免「關閉對話框」與「fragment 切換」時序互相干擾導致沒跳頁。 - openCartList(context); + openCartList(); cancel(); } else { MyApp.getInstance().getBuyList().clear(); MyApp.getInstance().getBuyList().add( new BuyStructure(slot.getField(), slot.getSlot(), slot.getProduct(), quantity)); cancel(); - requestBuy(context); + requestBuy(); } }); } @@ -141,9 +146,9 @@ public class DialogProductDetail extends DialogAbstract { } /** 前往購物車列表頁(沿用各 flavor 的購物車入口)。 */ - private void openCartList(Context context) { - if (context instanceof FontendActivity) { - SaleFlowHandler handler = ((FontendActivity) context).getSaleFlowHandler(); + private void openCartList() { + if (hostContext instanceof FontendActivity) { + SaleFlowHandler handler = ((FontendActivity) hostContext).getSaleFlowHandler(); if (handler != null) { handler.onShoppingCartRequested(); } @@ -157,14 +162,14 @@ public class DialogProductDetail extends DialogAbstract { } /** 沿用各 flavor 的結帳入口(basic→BuyDialog);非 FontendActivity 時直接開 BuyDialog。 */ - private void requestBuy(Context context) { - if (context instanceof FontendActivity) { - SaleFlowHandler handler = ((FontendActivity) context).getSaleFlowHandler(); + private void requestBuy() { + if (hostContext instanceof FontendActivity) { + SaleFlowHandler handler = ((FontendActivity) hostContext).getSaleFlowHandler(); if (handler != null) { handler.onBuyRequested(); return; } } - new BuyDialog(context, getSrcHandlerMain()).show(); + new BuyDialog(hostContext, getSrcHandlerMain()).show(); } } From 7cb9039a501a0a41998de64e795103e1ade726e1 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 11:50:00 +0800 Subject: [PATCH 3/7] =?UTF-8?q?[FEAT]=20=E5=95=86=E5=93=81=E8=A9=B3?= =?UTF-8?q?=E6=83=85=E9=A0=81=E6=8E=A5=20B012=20t060v03=5Fi18n=20=E8=A6=8F?= =?UTF-8?q?=E6=A0=BC=E5=A4=9A=E8=AA=9E=EF=BC=88=E7=B4=94=E6=96=87=E5=AD=97?= =?UTF-8?q?=E5=90=AB=E6=8F=9B=E8=A1=8C=E5=8E=9F=E6=A8=A3=E5=91=88=E7=8F=BE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .claude/settings.json | 9 ++++ .../java/com/unibuy/smartdevice/MyApp.java | 35 ++++++++++++++++ .../smartdevice/external/StarCloudAPI.java | 14 +++++++ .../structure/ProductStructure.java | 41 +++++++++++++++++++ .../ui/dialog/DialogProductDetail.java | 9 ++++ .../main/res/layout/dialog_product_detail.xml | 3 +- 6 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..233387b --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(git fetch *)", + "Bash(git switch *)", + "Bash(grep -n 'is_spring_slot\\\\|protected $fillable\\\\|protected $casts\\\\|'\\\\''settings'\\\\''' app/Models/Machine/Machine.php)" + ] + } +} diff --git a/app/src/main/java/com/unibuy/smartdevice/MyApp.java b/app/src/main/java/com/unibuy/smartdevice/MyApp.java index 24ad351..c316d6e 100644 --- a/app/src/main/java/com/unibuy/smartdevice/MyApp.java +++ b/app/src/main/java/com/unibuy/smartdevice/MyApp.java @@ -120,6 +120,7 @@ public class MyApp extends Application { private String defaultLanguage = ""; // 商品名稱多語 map(B012 t060v01_i18n),key=商品ID,value=底線代碼→名稱 的 JSONObject。 private JSONObject productNameI18nBlob = new JSONObject(); + private JSONObject productSpecI18nBlob = new JSONObject(); private GreenInvoiceStructure greenInvoice; private GreenpaySettingStructure greenpaySetting; private GreenpayMerchantStructure greenpayMerchant; @@ -408,6 +409,11 @@ public class MyApp extends Application { MyApp.getInstance().setProductNameI18nBlob(memoDao.getOnlyOne("ProductNameI18n").getData()); } + // 商品規格多語 blob(B012 t060v03_i18n),離線重啟時供 fallback + if (memoDao.existOnlyOne("ProductSpecI18n")) { + MyApp.getInstance().setProductSpecI18nBlob(memoDao.getOnlyOne("ProductSpecI18n").getData()); + } + if (memoDao.existOnlyOne(MyApp.getInstance().getCashSetStructure().getClass().getSimpleName())) { JSONObject jsonObject = memoDao.getOnlyOne(MyApp.getInstance().getCashSetStructure().getClass().getSimpleName()).getData(); logs.debug("initialSystemSet:" + jsonObject.toString()); @@ -535,6 +541,20 @@ public class MyApp extends Application { } } + // 持久化商品規格多語 blob(不動 products 表 schema,存於 MemoDao),供離線重啟 fallback + public static void saveProductSpecI18n(Context context) { + MemoDao memoDao = new MemoDao(context); + try { + memoDao.insertOnlyOne(new MemoStructure( + "ProductSpecI18n", + MyApp.getInstance().getProductSpecI18nBlob(), + new JSONObject())); + memoDao.mapUpdate(); + } catch (Exception e) { + new Logs(MyApp.class).warning(e); + } + } + public static void updateLog(Context context) { OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(UploadLogOneTimeWorker.class) .setInitialDelay(5, TimeUnit.SECONDS) @@ -1047,6 +1067,21 @@ public class MyApp extends Application { return (obj == null) ? null : obj.toString(); } + public void setProductSpecI18nBlob(JSONObject blob) { + this.productSpecI18nBlob = (blob == null) ? new JSONObject() : blob; + } + + public JSONObject getProductSpecI18nBlob() { + return productSpecI18nBlob; + } + + // 取某商品的規格多語 JSON 字串(底線代碼 key),無則回 null。供 ProductStructure 離線時 fallback。 + public String getProductSpecI18nJson(String productId) { + if (productId == null || productSpecI18nBlob == null) return null; + JSONObject obj = productSpecI18nBlob.optJSONObject(productId); + return (obj == null) ? null : obj.toString(); + } + public ReportAccessCodeStructure getReportAccessCodeStructure() { return reportAccessCodeStructure; diff --git a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java index a5100f8..f579926 100644 --- a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java +++ b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java @@ -1169,6 +1169,8 @@ public class StarCloudAPI { MyApp.getInstance().getProductList().clear(); // 商品名稱多語 blob:商品ID → t060v01_i18n(底線代碼 key),存於 MemoDao 持久化(不動 products 表) JSONObject nameI18nBlob = new JSONObject(); + // 商品規格多語 blob:商品ID → t060v03_i18n(純文字含換行) + JSONObject specI18nBlob = new JSONObject(); for (int i = 0; i < products.length(); i++) { JSONObject p = products.getJSONObject(i); @@ -1195,11 +1197,23 @@ public class StarCloudAPI { try { nameI18nBlob.put(pid, nameI18n); } catch (JSONException ignore) {} } } + + // 新增:商品規格多語 map(B012 t060v03_i18n,純文字含換行) + JSONObject specI18n = p.optJSONObject("t060v03_i18n"); + if (specI18n != null) { + product.setSpecI18nJson(specI18n.toString()); + String pid = p.optString("t060v00", ""); + if (!pid.isEmpty()) { + try { specI18nBlob.put(pid, specI18n); } catch (JSONException ignore) {} + } + } MyApp.getInstance().getProductList().add(product); } // 持久化商品多語 blob,供離線重啟時 fallback MyApp.getInstance().setProductNameI18nBlob(nameI18nBlob); MyApp.saveProductNameI18n(handlerMain.getContext()); + MyApp.getInstance().setProductSpecI18nBlob(specI18nBlob); + MyApp.saveProductSpecI18n(handlerMain.getContext()); // 將商品同步至資料庫 ProductsDao productsDao = new ProductsDao(handlerMain.getContext()); diff --git a/app/src/main/java/com/unibuy/smartdevice/structure/ProductStructure.java b/app/src/main/java/com/unibuy/smartdevice/structure/ProductStructure.java index 4b956c5..90b0a32 100644 --- a/app/src/main/java/com/unibuy/smartdevice/structure/ProductStructure.java +++ b/app/src/main/java/com/unibuy/smartdevice/structure/ProductStructure.java @@ -171,6 +171,47 @@ public class ProductStructure { } } + // B012 t060v03_i18n 原始 JSON 字串(底線代碼 key,如 {"zh_TW":..,"en":..})。 + // 內容為純文字含換行(\n),顯示時以 setText 原樣呈現(保留換行/空格/縮排)。 + // 由 B012 解析時設入;離線從 DB 重新載入商品時此欄為空,改向 MyApp 持久化 blob 取。 + private String specI18nJson; + + public String getSpecI18nJson() { + return specI18nJson; + } + + public void setSpecI18nJson(String specI18nJson) { + this.specI18nJson = specI18nJson; + } + + // 依目前語言取商品規格/描述。順序:對應語系 → zh_TW → 空字串(無描述)。 + public String getLocalizedSpec(Context context) { + String canonical = LanguageHelper.getSavedLanguage(context); + String backendLocale = LanguageHelper.toBackendLocale(canonical); + + String fromMap = lookupSpecI18n(backendLocale); + if (notBlank(fromMap)) return fromMap; + + if (!"zh_TW".equals(backendLocale)) { + String zh = lookupSpecI18n("zh_TW"); + if (notBlank(zh)) return zh; + } + return ""; + } + + private String lookupSpecI18n(String backendLocale) { + String json = specI18nJson; + if (json == null || json.isEmpty()) { + json = com.unibuy.smartdevice.MyApp.getInstance().getProductSpecI18nJson(productID); + } + if (json == null || json.isEmpty()) return null; + try { + return new org.json.JSONObject(json).optString(backendLocale, null); + } catch (org.json.JSONException e) { + return null; + } + } + private static boolean notBlank(String s) { return s != null && !s.trim().isEmpty(); } diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java index ab8002d..bc33b53 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/DialogProductDetail.java @@ -77,6 +77,15 @@ public class DialogProductDetail extends DialogAbstract { binding.slotNumber.setText("貨道" + slot.getSlot()); binding.inventoryNumber.setText("庫存" + slot.getCount()); + // 商品規格/描述(多語,純文字含換行;後台空白則隱藏整塊)。 + // setText 原樣呈現換行與空格/縮排;版面已設 gravity=start,故縮排不會被置中打亂。 + String spec = slot.getProduct().getLocalizedSpec(context); + if (spec == null || spec.trim().isEmpty()) { + binding.textProductDescription.setVisibility(android.view.View.GONE); + } else { + binding.textProductDescription.setText(spec); + } + // 依購物車開關決定按鈕呈現 boolean cartOn = MyApp.getInstance().getDevSet().isShoppingCar(); if (!cartOn) { diff --git a/app/src/main/res/layout/dialog_product_detail.xml b/app/src/main/res/layout/dialog_product_detail.xml index 4c73592..1701d59 100644 --- a/app/src/main/res/layout/dialog_product_detail.xml +++ b/app/src/main/res/layout/dialog_product_detail.xml @@ -138,8 +138,7 @@ android:ellipsize="end" android:gravity="start" android:lineSpacingExtra="4dp" - android:maxLines="3" - android:text="無" + android:maxLines="8" android:textColor="#757575" android:textSize="28sp" /> From a99f7b0572138d3d6220c7721546c97690cac41a Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 13:56:32 +0800 Subject: [PATCH 4/7] =?UTF-8?q?[FEAT]=20=E9=9B=B2=E7=AB=AF=E6=97=A5?= =?UTF-8?q?=E8=AA=8C=E5=88=86=E7=B4=9A(LogLevel)=E8=88=87=E9=99=A4?= =?UTF-8?q?=E9=8C=AF=E9=9B=9C=E8=A8=8A=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. MyApp 新增 logLevel 欄位(預設 INFO),由雲端 B014 LogLevel 下發並回送,現場除錯硬體可設 DEBUG。 2. Logs.debug()/ignore() 改為僅在 LogLevel=DEBUG 時寫入 debug.txt;logcat 一律照印不受影響。 3. Logs 新增單行寫檔上限 MAX_LINE(1000),超長 JSON/payload dump 截斷並保留原始長度,避免淹沒 debug.txt。 4. 將 HttpAPI/HttpTapPay/DevXinYuan/DevXinYuanController/DevNFCPayController/BuyDialog/DispatchDialog 大量 "AAAA…/bbbb…" 開發雜訊 logs.info 改為 logs.debug 並補上具體訊息。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../java/com/unibuy/smartdevice/MyApp.java | 15 ++++++++++- .../controller/DevNFCPayController.java | 2 +- .../controller/DevXinYuanController.java | 20 +++++++------- .../smartdevice/devices/DevXinYuan.java | 26 +++++++++---------- .../unibuy/smartdevice/exception/Logs.java | 22 ++++++++++++++-- .../unibuy/smartdevice/external/HttpAPI.java | 13 +++------- .../smartdevice/external/HttpTapPay.java | 10 +++---- .../smartdevice/ui/dialog/BuyDialog.java | 2 +- .../smartdevice/ui/dialog/DispatchDialog.java | 6 ++--- 9 files changed, 71 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/unibuy/smartdevice/MyApp.java b/app/src/main/java/com/unibuy/smartdevice/MyApp.java index c316d6e..b32e764 100644 --- a/app/src/main/java/com/unibuy/smartdevice/MyApp.java +++ b/app/src/main/java/com/unibuy/smartdevice/MyApp.java @@ -115,6 +115,9 @@ public class MyApp extends Application { // 購買流程模式:basic(多品項購物車)/ employee_card(員工卡)/ pickup_sheet(取貨單), // 由雲端 B014 ShoppingMode 下發,取代舊 project flavor 分家。預設 basic。 private String shoppingMode = "basic"; + // 上傳 log 的寫檔門檻等級。預設 INFO:debug.txt 只留 INFO/WARNING/ERROR/UPDATE,過濾掉 DEBUG/IGNORE 雜訊。 + // 工程師現場除錯硬體時可設為 DEBUG,讓 hex/協議細節重新寫檔(logcat 一律照印,不受此值影響)。 + private String logLevel = "INFO"; // 機台顯示語系(由雲端 B014 LangSet 下發)。標準標籤(zh-TW/en/...),有序,第 0 個為預設。 private java.util.List machineLanguages = new java.util.ArrayList<>(); private String defaultLanguage = ""; @@ -388,6 +391,7 @@ public class MyApp extends Application { MyApp.getInstance().getDevSet().setMemberSystem(jsonObject.optBoolean("MemberSystem", false)); MyApp.getInstance().getDevSet().setAmbientTemp(jsonObject.optBoolean("AmbientTemp", false)); MyApp.getInstance().setShoppingMode(jsonObject.optString("ShoppingMode", "basic")); + MyApp.getInstance().setLogLevel(jsonObject.optString("LogLevel", "INFO")); // 機台顯示語系(B014 LangSet) java.util.List langs = new java.util.ArrayList<>(); JSONArray langArr = jsonObject.optJSONArray("MachineLanguages"); @@ -476,6 +480,7 @@ public class MyApp extends Application { systemSetByJsonObject.put("MemberSystem", MyApp.getInstance().getDevSet().isMemberSystem()); systemSetByJsonObject.put("AmbientTemp", MyApp.getInstance().getDevSet().isAmbientTemp()); systemSetByJsonObject.put("ShoppingMode", MyApp.getInstance().getShoppingMode()); + systemSetByJsonObject.put("LogLevel", MyApp.getInstance().getLogLevel()); systemSetByJsonObject.put("MachineLanguages", new JSONArray(MyApp.getInstance().getMachineLanguages())); systemSetByJsonObject.put("DefaultLanguage", MyApp.getInstance().getDefaultLanguage()); @@ -836,7 +841,7 @@ public class MyApp extends Application { int i = 0; for (SlotStructure slotData: getSlotList(field)) { i += 1; - logs.info("AAAAAAAAAAAAAAAAAAAAAAAAAAAA0"+i); + logs.debug("slot index:" + i); if (slotData.getProduct().getProductID().equals(String.valueOf(pid))) { if(slotData.getCount() > 0) { return slotData; @@ -1036,6 +1041,14 @@ public class MyApp extends Application { this.shoppingMode = (shoppingMode == null || shoppingMode.isEmpty()) ? "basic" : shoppingMode; } + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = (logLevel == null || logLevel.isEmpty()) ? "INFO" : logLevel.toUpperCase(java.util.Locale.US); + } + public java.util.List getMachineLanguages() { return machineLanguages; } diff --git a/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java b/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java index 2471eef..d47afb2 100644 --- a/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java +++ b/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java @@ -155,7 +155,7 @@ public class DevNFCPayController extends HandlerMainScheduler { byte[] buffer = comPort2.readListen(30, 200); if (buffer.length > 0) { - getLogs().info("NFC 收到資料: " + PortTools.showHex(buffer)); + getLogs().debug("NFC 收到資料: " + PortTools.showHex(buffer)); String rxData = new String(buffer, StandardCharsets.UTF_8); if (rxData.contains(String.valueOf((char) 0x03))) { // ETX diff --git a/app/src/main/java/com/unibuy/smartdevice/controller/DevXinYuanController.java b/app/src/main/java/com/unibuy/smartdevice/controller/DevXinYuanController.java index fc73d2c..0f2d7a6 100644 --- a/app/src/main/java/com/unibuy/smartdevice/controller/DevXinYuanController.java +++ b/app/src/main/java/com/unibuy/smartdevice/controller/DevXinYuanController.java @@ -56,7 +56,7 @@ public class DevXinYuanController extends DevController { try { this.rs232Dev = new Rs232Dev(MyApp.getInstance().getComPort1ByXinYuan()); devXinYuan.setRun(true); - logs.debug("下位機連接"); + logs.info("下位機連接"); } catch (LogsUnsupportedOperationException | LogsIOException e) { // getLogs().error(ErrorCode.DEV_ERROR, "@下位機連接錯誤", handlerMain); getLogs().warning(e); @@ -123,9 +123,9 @@ public class DevXinYuanController extends DevController { Byte key = Byte.valueOf(vmcCmd[0]); if (!vmcMap.containsKey(key)) { - getLogs().info("XinYuan vmcCmd:" + PortTools.showHex(vmcCmd)); - getLogs().info("XinYuan vmcData:" + PortTools.showHex(vmcData)); - getLogs().info("XinYuan vmcXor:" + PortTools.showHex(vmcXor)); + getLogs().debug("XinYuan vmcCmd:" + PortTools.showHex(vmcCmd)); + getLogs().debug("XinYuan vmcData:" + PortTools.showHex(vmcData)); + getLogs().debug("XinYuan vmcXor:" + PortTools.showHex(vmcXor)); getLogs().info("XinYuan vmc not found"); continue; } @@ -133,9 +133,9 @@ public class DevXinYuanController extends DevController { DevXinYuan.VMC vmc = vmcMap.get(key); if (vmc != DevXinYuan.VMC.VMC_41_POLL) { - getLogs().info("XinYuan vmcCmd:" + PortTools.showHex(vmcCmd)); - getLogs().info("XinYuan vmcData:" + PortTools.showHex(vmcData)); - getLogs().info("XinYuan vmcXor:" + PortTools.showHex(vmcXor)); + getLogs().debug("XinYuan vmcCmd:" + PortTools.showHex(vmcCmd)); + getLogs().debug("XinYuan vmcData:" + PortTools.showHex(vmcData)); + getLogs().debug("XinYuan vmcXor:" + PortTools.showHex(vmcXor)); getLogs().info("XinYuan vmc:" + vmc.name()); } @@ -143,12 +143,12 @@ public class DevXinYuanController extends DevController { case VMC_41_POLL: if (getCountBySendBuffer() > 0) { byte[] cmdBytes = getSendBufferByFirst(); - getLogs().info("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"+bytesToHex(cmdBytes)); + getLogs().debug("XinYuan cmd hex:" + bytesToHex(cmdBytes)); if (cmdBytes.length == (4+cmdBytes[3]+1)) { for (int sendCount=1; sendCount <2; sendCount++) { rs232Dev.send(cmdBytes); - getLogs().info("XinYuan Send:" + PortTools.showHex(cmdBytes)); + getLogs().debug("XinYuan Send:" + PortTools.showHex(cmdBytes)); byte[] vmcBuffer = rs232Dev.readListen(0, 1); if (vmcBuffer.length > 2) { @@ -247,7 +247,7 @@ public class DevXinYuanController extends DevController { break; case VMC_52_CHECK_VMC_STATUS: //溫度:vmcData[7] - getLogs().info("下位機訊號"+PortTools.showHex(vmcData)); + getLogs().debug("下位機訊號"+PortTools.showHex(vmcData)); if (PortTools.showHex(vmcData).isEmpty()) { getLogs().info("下位機失連,重啟APP"); diff --git a/app/src/main/java/com/unibuy/smartdevice/devices/DevXinYuan.java b/app/src/main/java/com/unibuy/smartdevice/devices/DevXinYuan.java index 7ecb081..565ce5b 100644 --- a/app/src/main/java/com/unibuy/smartdevice/devices/DevXinYuan.java +++ b/app/src/main/java/com/unibuy/smartdevice/devices/DevXinYuan.java @@ -267,7 +267,7 @@ public class DevXinYuan { proBytes = PortTools.concatByteArrays(proBytes, XORByte); if (isShow) { - logs.info("XinYuan ipcCmd:" + cmd.name() + " " + PortTools.showHex(proBytes)); + logs.debug("XinYuan ipcCmd:" + cmd.name() + " " + PortTools.showHex(proBytes)); } return proBytes; @@ -371,18 +371,18 @@ public class DevXinYuan { } public CMD readCMD(byte[] buffer) throws LogsEmptyException { - logs.info(PortTools.showHex(buffer)); + logs.debug(PortTools.showHex(buffer)); for (CMD cmd : CMD.values()) { if (cmd.getBytes().length >= 2 && buffer.length >= 2) { if (cmd.getBytes()[0] == buffer[0]) { if (cmd.equals(CMD.CMD_FAFB_STX)) { if (cmd.getBytes()[1] == buffer[1]) { - logs.info("CMD:" + cmd.name() + " " + PortTools.showHex(cmd.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug("CMD:" + cmd.name() + " " + PortTools.showHex(cmd.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return cmd; } } else { - logs.info("CMD:" + cmd.name() + " " + PortTools.showHex(cmd.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug("CMD:" + cmd.name() + " " + PortTools.showHex(cmd.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return cmd; } } @@ -393,18 +393,18 @@ public class DevXinYuan { } public VMC readVMC(byte[] buffer) throws LogsEmptyException { - logs.info(PortTools.showHex(buffer)); + logs.debug(PortTools.showHex(buffer)); for (VMC vmc : VMC.values()) { if (vmc.getBytes().length >= 2 && buffer.length >= 2) { if (vmc.getBytes()[0] == buffer[0]) { if (vmc.equals(VMC.VMC_FAFB_STX)) { if (vmc.getBytes()[1] == buffer[1]) { - logs.info("VMC:" + vmc.name() + " " + PortTools.showHex(vmc.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug("VMC:" + vmc.name() + " " + PortTools.showHex(vmc.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return vmc; } } else { - logs.info("VMC:" + vmc.name() + " " + PortTools.showHex(vmc.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug("VMC:" + vmc.name() + " " + PortTools.showHex(vmc.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return vmc; } } @@ -416,7 +416,7 @@ public class DevXinYuan { public byte readPackNo(byte[] buffer) { byte packNo = buffer[0]; - logs.info("Read packNo:" + PortTools.showHex(packNo)); + logs.debug("Read packNo:" + PortTools.showHex(packNo)); return packNo; } @@ -424,7 +424,7 @@ public class DevXinYuan { for (BUY_STATUS_04 buyStatus : BUY_STATUS_04.values()) { if (buyStatus.getBytes().length == 1 && buffer.length > 0) { if (buyStatus.getBytes()[0] == buffer[1]) { - logs.info(BUY_STATUS_04.class.getSimpleName()+":" + buyStatus.name() + " " + PortTools.showHex(buyStatus.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug(BUY_STATUS_04.class.getSimpleName()+":" + buyStatus.name() + " " + PortTools.showHex(buyStatus.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return buyStatus; } } @@ -437,7 +437,7 @@ public class DevXinYuan { for (SLOT_STATUS_02 slotStatus : SLOT_STATUS_02.values()) { if (slotStatus.getBytes().length == 1 && buffer.length > 0) { if (slotStatus.getBytes()[0] == buffer[1]) { - logs.info(SLOT_STATUS_02.class.getSimpleName()+":" + slotStatus.name() + " " + PortTools.showHex(slotStatus.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug(SLOT_STATUS_02.class.getSimpleName()+":" + slotStatus.name() + " " + PortTools.showHex(slotStatus.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return slotStatus; } } @@ -451,7 +451,7 @@ public class DevXinYuan { for (GIVE_MONEY_26 giveMoney : GIVE_MONEY_26.values()) { if (giveMoney.getBytes().length == 1 && buffer.length > 0) { if (giveMoney.getBytes()[0] == buffer[1]) { - logs.info(GIVE_MONEY_26.class.getSimpleName()+":" + giveMoney.name() + " " + PortTools.showHex(giveMoney.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); + logs.debug(GIVE_MONEY_26.class.getSimpleName()+":" + giveMoney.name() + " " + PortTools.showHex(giveMoney.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]})); return giveMoney; } } @@ -475,10 +475,10 @@ public class DevXinYuan { byte XORByte = buffer[buffer.length -1]; proBytes = PortTools.concatByteArrays(proBytes, cmdLenData); byte XORByte2 = getXOR(proBytes); - logs.info("Length:"+length+" XOR1:" + PortTools.showHex(new byte[] {XORByte}) + " XOR2:" + PortTools.showHex(new byte[] {XORByte2})); + logs.debug("Length:"+length+" XOR1:" + PortTools.showHex(new byte[] {XORByte}) + " XOR2:" + PortTools.showHex(new byte[] {XORByte2})); if (buffer.length > (2+length)) { byte[] dataBytes = PortTools.catByteArrays(buffer, 2, length); - logs.info("data:"+PortTools.showHex(dataBytes)); + logs.debug("data:"+PortTools.showHex(dataBytes)); return dataBytes; } else { throw new LogsEmptyException(logs, "data array length insufficient"); diff --git a/app/src/main/java/com/unibuy/smartdevice/exception/Logs.java b/app/src/main/java/com/unibuy/smartdevice/exception/Logs.java index 913288b..c3e50b7 100644 --- a/app/src/main/java/com/unibuy/smartdevice/exception/Logs.java +++ b/app/src/main/java/com/unibuy/smartdevice/exception/Logs.java @@ -18,6 +18,10 @@ import java.util.Date; import java.util.Locale; public class Logs { + // 單筆寫檔上限:超過即截斷,避免整包 JSON/payload dump 把 debug.txt 撐爆、淹沒有用事件。 + // 只影響「寫入 debug.txt」那份;logcat(Log.d/i/...)維持完整不截斷。 + private static final int MAX_LINE = 1000; + private String message; private String className; private String methodName; @@ -46,6 +50,10 @@ public class Logs { } String dateTime = Tools.getTimeStampByNow("yyyy-MM-dd HH:mm:ss.SSS"); + // 招 1:超長行截斷(止血整包 JSON/payload dump),保留事件本身與原始長度資訊。 + if (data.length() > MAX_LINE) { + data = data.substring(0, MAX_LINE) + " …[truncated " + data.length() + " chars]"; + } data = dateTime + " " + data; try (FileOutputStream fos = new FileOutputStream(txtDebug, true)) { // try-with-resources 自動 close() @@ -61,10 +69,20 @@ public class Logs { return this.className+"."+this.methodName+"("+this.lineNumber+")"; } + // 招 2:是否把 DEBUG/IGNORE 寫進 debug.txt。預設(門檻 INFO)不寫,過濾掉開發雜訊; + // 雲端/現場把 LogLevel 設為 DEBUG 時才寫檔。MyApp 尚未就緒時一律不寫,維持乾淨預設。 + private static boolean writeDebugToFile() { + try { + return "DEBUG".equals(com.unibuy.smartdevice.MyApp.getInstance().getLogLevel()); + } catch (Exception e) { + return false; + } + } + public void debug(String message) { this.message = message; Log.d(this.className, "DEBUG ["+getClassName()+"]"+this.message); - writeFile("DEBUG ["+getClassName()+"]"+this.message); + if (writeDebugToFile()) writeFile("DEBUG ["+getClassName()+"]"+this.message); } public void info(String message) { @@ -87,7 +105,7 @@ public class Logs { public void ignore(String message) { this.message = message; Log.w(this.className, "IGNORE ["+getClassName()+"/"+getInfo()+"]"+this.message); - writeFile("IGNORE ["+getClassName()+"/"+getInfo()+"]"+this.message); + if (writeDebugToFile()) writeFile("IGNORE ["+getClassName()+"/"+getInfo()+"]"+this.message); } public void warning(Exception e) { diff --git a/app/src/main/java/com/unibuy/smartdevice/external/HttpAPI.java b/app/src/main/java/com/unibuy/smartdevice/external/HttpAPI.java index ca950be..e751227 100644 --- a/app/src/main/java/com/unibuy/smartdevice/external/HttpAPI.java +++ b/app/src/main/java/com/unibuy/smartdevice/external/HttpAPI.java @@ -493,7 +493,6 @@ public class HttpAPI { public void response(int responseCode, String receive, HandlerMain handlerMain) { if (responseCode == 200) { try { - getLogs().info("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); MyApp.getInstance().getMachine().setMachineID(""); MyApp.getInstance().getEsunpay().setStoreID(""); @@ -656,9 +655,7 @@ public class HttpAPI { MyApp.getInstance().getGreenpayMerchant().getAtomeMerchantId() })); - getLogs().info("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa0"); SettingActivity.updateProgressUI(10, "下載中..."); - getLogs().info("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa10"); settingsDao.close(); } catch (JSONException e) { getLogs().warning(e); @@ -720,7 +717,7 @@ public class HttpAPI { * t060v06 "https://unibuy.com.tw/Unibuy/dist/img/products/53317506.png", */ - getLogs().info("AAAAAAAAAAAAAAAAAAAAAAA" + productData); + getLogs().debug("productData:" + productData); if (productData.has("t060v00") && productData.has("t060v06") && @@ -783,7 +780,6 @@ public class HttpAPI { productsDao.close(); SettingActivity.updateProgressUI(10, "下載中..."); - getLogs().info("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa30"); handlerMain.start(getClass().getSimpleName(), Option.DOWNLOAD_PRODUCT_RESPONSE.getOption(), "成功載入" + MyApp.getInstance().getProductList().size() + "筆商品資料"); @@ -1053,9 +1049,8 @@ public class HttpAPI { JSONObject receiveByJsonObject = new JSONObject(receive); String workApi = receiveByJsonObject.getString("workApi"); int status = receiveByJsonObject.getInt("status"); - getLogs().info("bbbbbbbbbbbbbbbbbbbbbb status" + status); String message = receiveByJsonObject.getString("message"); - getLogs().info("bbbbbbbbbbbbbbbbbbbbbb message" + message); + getLogs().debug("reportFlow status:" + status + " message:" + message); if (status == 0) { handlerMain.start(getClass().getSimpleName(), Option.REPORT_FLOW_RESPONSE.getOption(), @@ -2142,7 +2137,7 @@ public class HttpAPI { */ jsonObject2.put("data", jsonArray); - getLogs().info("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=" + jsonObject2.toString()); + getLogs().debug("B009 supplementary:" + jsonObject2.toString()); formPutRunMain.start(domainNameByUnibuy + "products/supplementary/B009", jsonObject2.toString(), null, null, HttpConnect.ContentType.JSON); @@ -2228,7 +2223,7 @@ public class HttpAPI { int slotid = Integer.parseInt(deliveryData.getString("tid")); int slotnum = Integer.parseInt(deliveryData.getString("num")); - getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + slotid + " " + slotnum); + getLogs().info("dispatch slotid:" + slotid + " num:" + slotnum); slotid = slotid; diff --git a/app/src/main/java/com/unibuy/smartdevice/external/HttpTapPay.java b/app/src/main/java/com/unibuy/smartdevice/external/HttpTapPay.java index 1ac2131..6d571aa 100644 --- a/app/src/main/java/com/unibuy/smartdevice/external/HttpTapPay.java +++ b/app/src/main/java/com/unibuy/smartdevice/external/HttpTapPay.java @@ -174,11 +174,11 @@ public class HttpTapPay { int payType = MyApp.getInstance().getReportFlowInfoData().getFlowType(); GreenpaySettingStructure greenpaySetting = MyApp.getInstance().getGreenpaySetting(); String greenpayMerchantId = MyApp.getInstance().getMachine().getMachineID(); - logs.info("aaaaaaaaaaaaaaaaaaaaaa1"+ MyApp.getInstance().getGreenpayMerchant().getLineMerchantId()); - logs.info("aaaaaaaaaaaaaaaaaaaaaa2"+ MyApp.getInstance().getGreenpayMerchant().getJkoMerchantId()); - logs.info("aaaaaaaaaaaaaaaaaaaaaa3"+ MyApp.getInstance().getGreenpayMerchant().getPiMerchantId()); - logs.info("aaaaaaaaaaaaaaaaaaaaaa4"+ MyApp.getInstance().getGreenpayMerchant().getPsMerchantId()); - logs.info("aaaaaaaaaaaaaaaaaaaaaa5"+ MyApp.getInstance().getGreenpayMerchant().getEasyMerchantId()); + logs.debug("merchantId line:" + MyApp.getInstance().getGreenpayMerchant().getLineMerchantId() + + " jko:" + MyApp.getInstance().getGreenpayMerchant().getJkoMerchantId() + + " pi:" + MyApp.getInstance().getGreenpayMerchant().getPiMerchantId() + + " ps:" + MyApp.getInstance().getGreenpayMerchant().getPsMerchantId() + + " easy:" + MyApp.getInstance().getGreenpayMerchant().getEasyMerchantId()); switch (payType) { case 30: if (MyApp.getInstance().getGreenpayMerchant() != null && !MyApp.getInstance().getGreenpayMerchant().getLineMerchantId().isEmpty()) { diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java index e1e25f2..9997f3b 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java @@ -387,7 +387,7 @@ public class BuyDialog extends DialogAbstract { public void gotoDialog() { closeDialogCountdown.shutdown(); int flowType = MyApp.getInstance().getReportFlowInfoData().getFlowType(); - getLogs().info("aaaaaaaaaaaaaaaaaaaaaaaa"+flowType); + getLogs().debug("buy flowType:" + flowType); // if (flowType == 5 || flowType == 7 || flowType == 30) { if (flowType == 5 || flowType == 7) { changeCheckout(); 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 67cb6d0..dc34eb9 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 @@ -494,7 +494,7 @@ public class DispatchDialog extends DialogAbstract { public void readBufferOnScheduler(DevController devController, HandlerMain handlerMain) { if (isCheckSlotByVMC) return; - getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA006"); + getLogs().debug("dispatch flow 006"); isCheckSlotByVMC = true; // 進來立即設定true,可以判斷掉重複出貨 boolean isCMD = false; @@ -669,7 +669,7 @@ public class DispatchDialog extends DialogAbstract { getSrcHandlerMain().send(getClass().getSimpleName(), Option.NEXT_PROCESS_GOODS.getOption(), "next process goods"); } else { - getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA002"); + getLogs().debug("dispatch flow 002"); MyApp.getInstance().getDevXinYuanController() .getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000L); } @@ -682,7 +682,7 @@ public class DispatchDialog extends DialogAbstract { getCtx().getString(R.string.product_delivery_issues_contact_the_counter_window)); } } else { - getLogs().info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA003"); + getLogs().debug("dispatch flow 003"); MyApp.getInstance().getDevXinYuanController() .getReadBufferByNow(new DevGetProductByVMC(handlerMain, slot), 5000L); MyApp.getInstance().getDevXinYuanController() From 3fd34becb66578b7c3252d193cdfcb9b1abdf756 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 13:56:56 +0800 Subject: [PATCH 5/7] =?UTF-8?q?[FIX]=20=E6=94=AF=E4=BB=98=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E6=94=B9=E8=B5=B0=20MQTT=20finalize=E3=80=81=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E5=8D=A1=E5=9C=A8=E5=80=92=E6=95=B8=E4=B8=8D=E5=87=BA?= =?UTF-8?q?=E8=B2=A8=EF=BC=9B=E5=8D=87=E7=89=88=20v90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真因:各支付 Dialog 成功後呼叫已停用的 B600(reportFlowInfo),再靠 B600 的 HTTP 回應回呼 REPORT_FLOW 才進 DispatchDialog;B600 已失敗 → 永遠不出貨,卡到倒數 COUNTDOWN_CANCEL(症狀:刷卡機顯示成功但流程不繼續)。 1. CheckoutNfcCardDialog / CheckoutNfcRechargeDialog:支付成功(0000)不再呼叫 B600,直接 changeDispatch() 進 DispatchDialog,由 MQTT publishTransactionFinalize 統一上報(payload 已含 invoice_info,B600/B650 皆不需要)。 2. CheckoutNfcPhoneDialog:同上,且進 dispatch 前覆寫 payment_type=10(手機支付);刷卡機 NCCC 電文無法區分手機錢包/實體卡,依使用者所選按鈕分類。 3. CheckoutEsunPayDialog / CheckoutLinePayDialog / CheckoutTapPayDialog:交易成功直接 changeDispatch;交易失敗改為提示「交易失敗」並返回購買頁(changeBuy),不再卡住或誤出貨。 4. LinePay 維持 payment_type=70(官方直連),後台另補定義;TapPay 子項 30~34 沿用。 5. 舊 CheckoutCashDialog/CashPayDialog 為 legacy,basic shopping_mode 不使用(現金走 CashPayDialogGeneral),本次未動。 6. build.gradle 版號 verPatch 89 → 90。 注意:後台 star-cloud 需同步新增 payment_type 10(手機支付)/70(LINE Pay 官方) 與翻譯(另一 repo,另行提交)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle | 2 +- .../ui/dialog/CheckoutEsunPayDialog.java | 19 +++++++------------ .../ui/dialog/CheckoutLinePayDialog.java | 19 +++++++------------ .../ui/dialog/CheckoutNfcCardDialog.java | 9 ++++----- .../ui/dialog/CheckoutNfcPhoneDialog.java | 12 +++++++----- .../ui/dialog/CheckoutNfcRechargeDialog.java | 9 ++++----- .../ui/dialog/CheckoutTapPayDialog.java | 17 +++++++---------- 7 files changed, 37 insertions(+), 50 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 68f123d..725e539 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 89 +def verPatch = 90 diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutEsunPayDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutEsunPayDialog.java index 4aa55c0..299c3d4 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutEsunPayDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutEsunPayDialog.java @@ -100,25 +100,20 @@ public class CheckoutEsunPayDialog extends DialogAbstract { case TRANSACTION_SUCCESSFUL: if (isTransaction) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(1); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { -// exception(context, e.getErrorCode(), e.getLocalizedMessage()); - getLogs().warning(e); - } + // [MQTT Finalize 重構] 支付成功不再呼叫舊 B600(reportFlowInfo); + // 直接進出貨流程,由 DispatchDialog 以 MQTT Finalize 統一上報結案(含發票/扣庫存)。 + // payment_type=3(掃碼支付)已於 BuyDialog 設定。 isTransaction = false; + changeDispatch(); } break; case TRANSACTION_FAILED: if (isTransaction) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { -// exception(context, e.getErrorCode(), e.getLocalizedMessage()); - getLogs().warning(e); - } + // [MQTT Finalize 重構] 交易失敗不再經 B600;直接提示並返回購買頁,不進出貨。 isTransaction = false; + start(getSrcClassName(), "@" + getContext().getString(R.string.transaction_failure)); + changeBuy(); } break; case CHANGE_DISPATCH: diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutLinePayDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutLinePayDialog.java index 8883ca6..a46d5b3 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutLinePayDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutLinePayDialog.java @@ -101,25 +101,20 @@ public class CheckoutLinePayDialog extends DialogAbstract { case TRANSACTION_SUCCESSFUL: if (isTransaction) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(1); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { -// exception(context, e.getErrorCode(), e.getLocalizedMessage()); - getLogs().warning(e); - } + // [MQTT Finalize 重構] 支付成功不再呼叫舊 B600(reportFlowInfo); + // 直接進出貨流程,由 DispatchDialog 以 MQTT Finalize 統一上報結案(含發票/扣庫存)。 + // payment_type=70(LINE Pay 官方)已於 BuyDialog 設定。 isTransaction = false; + changeDispatch(); } break; case TRANSACTION_FAILED: if (isTransaction) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { -// exception(context, e.getErrorCode(), e.getLocalizedMessage()); - getLogs().warning(e); - } + // [MQTT Finalize 重構] 交易失敗不再經 B600;直接提示並返回購買頁,不進出貨。 isTransaction = false; + start(getSrcClassName(), "@" + getContext().getString(R.string.transaction_failure)); + changeBuy(); } break; case CHANGE_DISPATCH: diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java index 4c42bae..460fb00 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java @@ -109,11 +109,10 @@ public class CheckoutNfcCardDialog extends DialogAbstract { if (message.equals("0000")) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(1); binding.textStatus.setText(DevNFCPay.recordMessage(message)); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { - getLogs().warning(e); - } + // [MQTT Finalize 重構] 支付成功不再呼叫舊 B600(reportFlowInfo); + // 直接進出貨流程,由 DispatchDialog 以 MQTT Finalize 統一上報結案(含發票/扣庫存)。 + // payment_type=1(信用卡)已於 BuyDialog 設定。 + changeDispatch(); } else { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0); binding.textStatus.setText(DevNFCPay.recordMessage(message)); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java index ad7919b..131cc43 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java @@ -108,11 +108,13 @@ public class CheckoutNfcPhoneDialog extends DialogAbstract { if (message.equals("0000")) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(1); binding.textStatus.setText(DevNFCPay.recordMessage(message)); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { - getLogs().warning(e); - } + // NfcPhone(手機支付):刷卡機 NCCC 電文無法區分手機錢包/實體卡, + // 依使用者所選付款鈕歸類為 payment_type=10(手機支付)。 + // 路由鍵 flowType=4 僅用於 BuyDialog 開啟本 Dialog,這裡覆寫為實際上報值。 + MyApp.getInstance().getReportFlowInfoData().setFlowType(10); + // [MQTT Finalize 重構] 支付成功不再呼叫舊 B600(reportFlowInfo); + // 直接進出貨流程,由 DispatchDialog 以 MQTT Finalize 統一上報結案(含發票/扣庫存)。 + changeDispatch(); } else { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0); binding.textStatus.setText(DevNFCPay.recordMessage(message)); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java index bbe9675..748079f 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java @@ -108,11 +108,10 @@ public class CheckoutNfcRechargeDialog extends DialogAbstract { if (message.equals("0000")) { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(1); binding.textStatus.setText(DevNFCPay.recordMessage(message)); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { - getLogs().warning(e); - } + // [MQTT Finalize 重構] 支付成功不再呼叫舊 B600(reportFlowInfo); + // 直接進出貨流程,由 DispatchDialog 以 MQTT Finalize 統一上報結案(含發票/扣庫存)。 + // payment_type=2(悠遊卡/一卡通)已於 BuyDialog 設定。 + changeDispatch(); } else { MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0); binding.textStatus.setText(DevNFCPay.recordMessage(message)); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutTapPayDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutTapPayDialog.java index d6a3e51..10c3d3c 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutTapPayDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutTapPayDialog.java @@ -123,22 +123,19 @@ public class CheckoutTapPayDialog extends DialogAbstract { if (isTransaction) { isTransaction = false; // 🚨 馬上關閉,避免送出第二筆 MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(1); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { - getLogs().warning(e); - } + // [MQTT Finalize 重構] 支付成功不再呼叫舊 B600(reportFlowInfo); + // 直接進出貨流程,由 DispatchDialog 以 MQTT Finalize 統一上報結案(含發票/扣庫存)。 + // payment_type=30~34(TapPay 子項)已於本 Dialog 選擇時設定。 + changeDispatch(); } break; case TRANSACTION_FAILED: if (isTransaction) { isTransaction = false; // 🚨 馬上關閉 MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0); - try { - httpAPI.reportFlowInfo(MyApp.getInstance().getReportFlowInfoData()); - } catch (LogsSettingEmptyException | LogsParseException e) { - getLogs().warning(e); - } + // [MQTT Finalize 重構] 交易失敗不再經 B600;直接提示並返回購買頁,不進出貨。 + start(getSrcClassName(), "@" + getContext().getString(R.string.transaction_failure)); + changeBuy(); } break; case TRANSACTION_AGAIN: From 7875db4c7d9c8315aff9a870137c7d005cd336cc Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 14:03:17 +0800 Subject: [PATCH 6/7] =?UTF-8?q?[FIX]=20=E5=8D=87=E7=89=88=20v91=EF=BC=88OT?= =?UTF-8?q?A=20=E7=89=88=E8=99=9F=E9=81=9E=E5=A2=9E=EF=BC=8C=E5=85=A7?= =?UTF-8?q?=E5=AE=B9=E5=90=8C=20v90=20=E6=94=AF=E4=BB=98=20MQTT=20finalize?= =?UTF-8?q?=20=E4=BF=AE=E6=AD=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. build.gradle verPatch 90 → 91,供實體機台 OTA 升級。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 725e539..aab448f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 90 +def verPatch = 91 From ff65559b4c93a4a36305019b9b28e0f6ec303b9d Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 15 Jun 2026 16:18:11 +0800 Subject: [PATCH 7/7] =?UTF-8?q?[FEAT]=20=E9=9B=BB=E5=AD=90=E7=99=BC?= =?UTF-8?q?=E7=A5=A8=E6=94=B9=E7=94=B1=E5=BE=8C=E5=8F=B0=E9=96=8B=E7=AB=8B?= =?UTF-8?q?=EF=BC=9A=E6=A9=9F=E5=8F=B0=E5=8F=AA=E9=9A=A8=20finalize=20?= =?UTF-8?q?=E4=B8=8A=E5=A0=B1=E7=99=BC=E7=A5=A8=E8=BC=B8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. TransactionFinalizePayload 新增 InvoicePayload 物件(status/amount/carrier_id/business_tax_id/love_code 等),讓 finalize 一併帶上發票開立輸入。 2. TransactionFinalizeBuilder:有發票需求(invoiceInfo 非空)時,從 InvoiceData 組發票輸入並固定 status=pending 帶進 finalize;機台不再判定開立結果。 3. DispatchDialog:移除機台直連綠界開票,改為進出貨流程僅上報;發票由後台收到 finalize 後建 pending 並去綠界開立(避免機台網路不確定性與等待)。 4. ReportInvoiceInfoStructure:保留 relateNumber 欄位(冪等鍵相關)。 5. 開票邏輯不放在即將淘汰的 HttpAPI(先前暫放 StarCloudAPI 的機台開票方法亦已移除)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../structure/ReportInvoiceInfoStructure.java | 11 +++ .../mqtt/TransactionFinalizePayload.java | 76 +++++++++++++++++++ .../tools/TransactionFinalizeBuilder.java | 23 ++++++ .../smartdevice/ui/dialog/DispatchDialog.java | 4 + 4 files changed, 114 insertions(+) diff --git a/app/src/main/java/com/unibuy/smartdevice/structure/ReportInvoiceInfoStructure.java b/app/src/main/java/com/unibuy/smartdevice/structure/ReportInvoiceInfoStructure.java index 188cdff..90e5a70 100644 --- a/app/src/main/java/com/unibuy/smartdevice/structure/ReportInvoiceInfoStructure.java +++ b/app/src/main/java/com/unibuy/smartdevice/structure/ReportInvoiceInfoStructure.java @@ -8,6 +8,9 @@ public class ReportInvoiceInfoStructure { String invoiceDate; String randomNumber; String loveCode; + // 綠界 RelateNumber(冪等鍵,= machineID + flow_id)。由 DispatchDialog 開票前產生, + // 開票請求與 finalize 上報共用同一值,後台據此查詢/補開避免重複開立。 + String relateNumber = ""; public ReportInvoiceInfoStructure(String reportFlowId, String rtnCode, String rtnMsg, String invoiceNo, String invoiceDate, String randomNumber, String loveCode) { this.reportFlowId = reportFlowId; @@ -75,6 +78,14 @@ public class ReportInvoiceInfoStructure { this.loveCode = loveCode; } + public String getRelateNumber() { + return relateNumber; + } + + public void setRelateNumber(String relateNumber) { + this.relateNumber = relateNumber; + } + @Override public String toString() { return "ReportInvoiceInfoStructure{" + diff --git a/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java b/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java index f3d10bf..df517f7 100644 --- a/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java +++ b/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java @@ -35,6 +35,14 @@ public class TransactionFinalizePayload { /** 每個貨道的出貨結果列表 */ private List dispense; + /** + * 電子發票開立結果(選填)。 + * 機台在進入出貨流程時平行向綠界開立,結果折進此物件隨 finalize 一起上報; + * 若開票尚未回應則 status=pending,後台依 relate_number 去綠界查詢/補開。 + * 無發票需求(未帶發票資訊)時此欄為 null,後台不建立發票。 + */ + private InvoicePayload invoice; + // =========== Getters & Setters =========== public String getAction() { @@ -82,6 +90,74 @@ public class TransactionFinalizePayload { this.dispense = dispense; } + public InvoicePayload getInvoice() { + return invoice; + } + + public void setInvoice(InvoicePayload invoice) { + this.invoice = invoice; + } + + // =========== 內部類別:電子發票開立結果 =========== + + public static class InvoicePayload { + /** 開立狀態:issued(已開)/ pending(已送出未回應)/ failed(綠界回失敗) */ + private String status; + /** 綠界 RelateNumber(= machineID + flow_id),後台查詢/補開用 */ + private String relate_number; + /** 發票號碼(pending/failed 時為空) */ + private String invoice_no; + /** 綠界回應碼(1=成功) */ + private String rtn_code; + /** 綠界回應訊息 */ + private String rtn_msg; + /** 發票開立日期 */ + private String invoice_date; + /** 隨機碼 */ + private String random_number; + /** 愛心碼(捐贈時) */ + private String love_code; + /** 載具號碼 */ + private String carrier_id; + /** 統一編號(B2B 統編,有打統編時) */ + private String business_tax_id; + /** 發票金額 */ + private int amount; + + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + + public String getRelate_number() { return relate_number; } + public void setRelate_number(String relate_number) { this.relate_number = relate_number; } + + public String getInvoice_no() { return invoice_no; } + public void setInvoice_no(String invoice_no) { this.invoice_no = invoice_no; } + + public String getRtn_code() { return rtn_code; } + public void setRtn_code(String rtn_code) { this.rtn_code = rtn_code; } + + public String getRtn_msg() { return rtn_msg; } + public void setRtn_msg(String rtn_msg) { this.rtn_msg = rtn_msg; } + + public String getInvoice_date() { return invoice_date; } + public void setInvoice_date(String invoice_date) { this.invoice_date = invoice_date; } + + public String getRandom_number() { return random_number; } + public void setRandom_number(String random_number) { this.random_number = random_number; } + + public String getLove_code() { return love_code; } + public void setLove_code(String love_code) { this.love_code = love_code; } + + public String getCarrier_id() { return carrier_id; } + public void setCarrier_id(String carrier_id) { this.carrier_id = carrier_id; } + + public String getBusiness_tax_id() { return business_tax_id; } + public void setBusiness_tax_id(String business_tax_id) { this.business_tax_id = business_tax_id; } + + public int getAmount() { return amount; } + public void setAmount(int amount) { this.amount = amount; } + } + // =========== 內部類別:訂單資訊 =========== public static class OrderPayload { 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 71885d6..fab5ca3 100644 --- a/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java +++ b/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java @@ -5,6 +5,7 @@ import android.util.Log; import com.unibuy.smartdevice.MyApp; import com.unibuy.smartdevice.structure.BuyStructure; +import com.unibuy.smartdevice.structure.InvoiceStructure; import com.unibuy.smartdevice.structure.ReportFlowInfoStructure; import com.unibuy.smartdevice.structure.mqtt.TransactionFinalizePayload; @@ -59,6 +60,10 @@ public class TransactionFinalizeBuilder { this.flowId = flowId; } + public String getFlowId() { + return flowId; + } + public void setOrderNo(String orderNo) { this.orderNo = orderNo; } @@ -133,6 +138,24 @@ public class TransactionFinalizeBuilder { // --- 出貨記錄 --- payload.setDispense(new ArrayList<>(dispenseRecords)); + // --- 電子發票(改由後台開立)--- + // 機台不開票,只把「開立輸入」(載具/愛心碼/統編/金額)帶上去,狀態固定 pending; + // 後台建 pending 發票後派 Job 去綠界開立。無發票需求(invoiceInfo 為空)時不帶,後台不建立發票。 + if (flowInfo.getInvoiceInfo() != null && !flowInfo.getInvoiceInfo().isEmpty()) { + TransactionFinalizePayload.InvoicePayload invoicePayload = + new TransactionFinalizePayload.InvoicePayload(); + invoicePayload.setStatus("pending"); + invoicePayload.setAmount(flowInfo.getTotalPrice()); + + InvoiceStructure invoiceData = MyApp.getInstance().getInvoiceData(); + if (invoiceData != null) { + invoicePayload.setCarrier_id(invoiceData.getCarrier()); + invoicePayload.setBusiness_tax_id(invoiceData.getCustomerIdentifier()); + invoicePayload.setLove_code(invoiceData.getLoveCode()); + } + payload.setInvoice(invoicePayload); + } + Log.i(TAG, "TransactionFinalizePayload built: flow_id=" + flowId + ", order_no=" + order.getOrder_no() + ", items=" + order.getItems().size() 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 dc34eb9..6ddb946 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 @@ -262,6 +262,10 @@ public class DispatchDialog extends DialogAbstract { MyApp.getInstance().getReportFlowInfoData().setFlowType(42); } + // [MQTT Finalize 重構] 電子發票改由「後台」開立:機台不再直連綠界, + // 僅把發票輸入(載具/愛心碼/統編/金額)隨 finalize 的 invoice 物件上報(TransactionFinalizeBuilder 組裝)。 + // 後台收到 finalize 後建 pending 發票並派 Job 去綠界開立,避免機台網路不確定性與等待。 + MyApp.getInstance().setReportFreeGiftCodeStructure(new ReportFreeGiftCodeStructure("", 0, "", 0, 0, "")); getLogs().info("BuyList size:" + MyApp.getInstance().getBuyList().size());