[PROMOTE] 晉升 dev 變更至 demo 環境
1. [FIX] 修復中國醫多貨道庫存分配異常,引進動態多貨道輪替分配機制,徹底解決一筆單多商品時貨道重複分配的 Bug。 2. [RELEASE] 遞增修補版號 verPatch 至 66,完成發版前置準備。 3. [RELEASE] 修正多商品取貨 UI 倒數閃跳與 3 分鐘閒置崩潰問題,發佈版本 65。 4. [FIX] 修正 OTA 連線超時與首頁執行緒併發競態導致 UI 異常問題。 5. [FEAT] 優化 OTA 靜默安裝邏輯,支援降版本覆蓋安裝。 6. [STYLE] 優化 CMUH 專屬佈局元件比例與樣式排版。 7. [FEAT] 新增 CMUH 客製初始畫面跳轉與多處頁面重啟邏輯優化。 8. [DOCS] 修正 release-apk 工作流中寫死 S12XYU 的問題。
This commit is contained in:
commit
3db92cb3fb
@ -157,6 +157,7 @@ if [ -n "$APK_SOURCE" ]; then
|
||||
echo "* 版本名稱 (versionName):$VERSION_NAME"
|
||||
echo "* 版本代碼 (versionCode):$VERSION_CODE"
|
||||
echo "* APK 檔案名稱:$APK_NAME"
|
||||
echo "* 更新說明:[請在此處附上口語化的一句話更新說明]"
|
||||
echo "=================================================="
|
||||
else
|
||||
echo "❌ 找不到編譯產出的 APK 檔案,複製失敗!"
|
||||
|
||||
@ -42,10 +42,16 @@ if [ "$TARGET_FLAVOR" != "General" ] && [ "$TARGET_FLAVOR" != "Chengwai" ] && [
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📦 [Release] 開始編譯正式版變體: ${TARGET_FLAVOR}S12XYURelease"
|
||||
# 根據專案 Flavor 判定硬體 Flavor(中國醫 Cmuh 使用 S9XYU,其餘使用 S12XYU)
|
||||
HW_FLAVOR="S12XYU"
|
||||
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
|
||||
HW_FLAVOR="S9XYU"
|
||||
fi
|
||||
|
||||
echo "📦 [Release] 開始編譯正式版變體: ${TARGET_FLAVOR}${HW_FLAVOR}Release"
|
||||
|
||||
# 使用 clean 確保完整重新編譯,避開 incremental 快取問題
|
||||
./gradlew clean :app:assemble${TARGET_FLAVOR}S12XYURelease --no-daemon
|
||||
./gradlew clean :app:assemble${TARGET_FLAVOR}${HW_FLAVOR}Release --no-daemon
|
||||
```
|
||||
|
||||
## 步驟二:導出正式 APK 並產出後台 Keyin 派送參考資料
|
||||
@ -62,8 +68,13 @@ if [ -n "$1" ]; then
|
||||
TARGET_FLAVOR=$(echo "$1" | awk '{print toupper(substr($0,1,1))tolower(substr($0,2))}')
|
||||
fi
|
||||
|
||||
HW_FLAVOR="S12XYU"
|
||||
if [ "$TARGET_FLAVOR" = "Cmuh" ]; then
|
||||
HW_FLAVOR="S9XYU"
|
||||
fi
|
||||
|
||||
# 動態尋找編譯產出的最新正式版 APK 檔案
|
||||
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_FLAVOR}S12XYU/release/ -name "app-S_12_XY_U_${TARGET_FLAVOR}-*-release.apk" | head -n 1)
|
||||
APK_SOURCE=$(find app/build/outputs/apk/${TARGET_FLAVOR}${HW_FLAVOR}/release/ -name "app-S_*_${TARGET_FLAVOR}-*-release.apk" | head -n 1)
|
||||
|
||||
if [ -n "$APK_SOURCE" ]; then
|
||||
APK_NAME=$(basename "$APK_SOURCE")
|
||||
@ -74,12 +85,19 @@ if [ -n "$APK_SOURCE" ]; then
|
||||
echo "=================================================="
|
||||
|
||||
# 提取與計算版本資訊
|
||||
MAJOR=$(grep "def verMajor" app/build.gradle | awk '{print $4}')
|
||||
MINOR=$(grep "def verMinor" app/build.gradle | awk '{print $4}')
|
||||
PATCH=$(grep "def verPatch" app/build.gradle | awk '{print $4}')
|
||||
|
||||
# 根據硬體規格決定 Major (S9XYU 為 21 吋,S12XYU 為 10 吋)
|
||||
if [ "$HW_FLAVOR" = "S9XYU" ]; then
|
||||
MAJOR=21
|
||||
else
|
||||
MAJOR=10
|
||||
fi
|
||||
|
||||
VERSION_NAME="${MAJOR}_0${MINOR}_${PATCH}_R"
|
||||
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
|
||||
FLAVOR="${TARGET_FLAVOR}S12XYU"
|
||||
FLAVOR="${TARGET_FLAVOR}${HW_FLAVOR}"
|
||||
|
||||
echo "🚀 實體販賣機 OTA 後台新增版本 Keyin 參考資料:"
|
||||
echo "--------------------------------------------------"
|
||||
|
||||
@ -9,7 +9,7 @@ plugins {
|
||||
// 避免不同尺寸機台間版本號產生衝突。
|
||||
// =========================================================================
|
||||
def verMinor = 1
|
||||
def verPatch = 33
|
||||
def verPatch = 66
|
||||
|
||||
|
||||
android {
|
||||
|
||||
@ -322,14 +322,36 @@ public class SaleFlowHandler {
|
||||
haveValuestate = true;
|
||||
|
||||
if (requestQty > 1) {
|
||||
int checkcount = 0;
|
||||
int slotnum = 0;
|
||||
class TempSlot {
|
||||
int slotNo;
|
||||
int stock;
|
||||
TempSlot(int slotNo, int stock) {
|
||||
this.slotNo = slotNo;
|
||||
this.stock = stock;
|
||||
}
|
||||
}
|
||||
String[] slotPairs = medicineList.get(i).getMedicineSlot().split(",");
|
||||
java.util.List<TempSlot> tempSlots = new java.util.ArrayList<>();
|
||||
for (String pair : slotPairs) {
|
||||
if (pair.trim().isEmpty()) continue;
|
||||
String[] parts = pair.split("-");
|
||||
if (parts.length == 2) {
|
||||
tempSlots.add(new TempSlot(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])));
|
||||
}
|
||||
}
|
||||
|
||||
int slotIdx = 0;
|
||||
for (int j = 0; j < requestQty; j++) {
|
||||
checkcount += 1;
|
||||
if (checkcount > Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[0].split("-")[1])) {
|
||||
slotnum = Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[1].split("-")[0]);
|
||||
while (slotIdx < tempSlots.size() && tempSlots.get(slotIdx).stock <= 0) {
|
||||
slotIdx++;
|
||||
}
|
||||
int slotnum;
|
||||
if (slotIdx < tempSlots.size()) {
|
||||
TempSlot activeSlot = tempSlots.get(slotIdx);
|
||||
slotnum = activeSlot.slotNo;
|
||||
activeSlot.stock--;
|
||||
} else {
|
||||
slotnum = Integer.valueOf(medicineList.get(i).getMedicineSlot().split(",")[0].split("-")[0]);
|
||||
slotnum = tempSlots.get(tempSlots.size() - 1).slotNo;
|
||||
}
|
||||
addMedicineToBuyList(
|
||||
medicineList.get(i).getProductId(),
|
||||
|
||||
271
app/src/Cmuh/res/layout/activity_fontend.xml
Normal file
271
app/src/Cmuh/res/layout/activity_fontend.xml
Normal file
@ -0,0 +1,271 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/light_gray"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:background="@color/red"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textVersion"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:text="20250108ver1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textThreadCount"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:text="TC:0"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/btnswitchlanTW"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:text="中"
|
||||
android:visibility="gone"
|
||||
android:textSize="30sp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnswitchlanEn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:text="En"
|
||||
android:visibility="gone"
|
||||
android:textSize="30sp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnswitchlanJa"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:text="日"
|
||||
android:visibility="gone"
|
||||
android:textSize="30sp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textTemperature"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:text="0 ℃"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textTime"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:text="1911/01/01 01:01:01"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textNetwork"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical|end"
|
||||
android:text="@string/no_internet"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/relativeLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:background="@color/black"
|
||||
app:layout_constraintDimensionRatio="18:21"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:show_subtitle_button="true"
|
||||
app:use_artwork="true"
|
||||
app:use_controller="false"
|
||||
app:resize_mode="fit"
|
||||
app:surface_type="texture_view" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageAd"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/relativeLayout" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_navigation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/buttonVmc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/drinksale"
|
||||
app:cornerRadius="12dp"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:strokeColor="@color/gray"
|
||||
android:textSize="38sp"
|
||||
app:strokeWidth="2dp"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="4dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/buttonElectric"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/board_games"
|
||||
app:cornerRadius="12dp"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:strokeColor="@color/gray"
|
||||
android:textSize="38sp"
|
||||
android:visibility="gone"
|
||||
app:strokeWidth="2dp"
|
||||
android:layout_margin="4dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/buttonOther"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:text="@string/other"
|
||||
app:cornerRadius="12dp"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:strokeColor="@color/gray"
|
||||
android:textSize="38sp"
|
||||
app:strokeWidth="2dp"
|
||||
android:layout_margin="4dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/floatButtonPickupCode"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="150dp"
|
||||
android:text="取\n貨\n碼"
|
||||
android:textSize="28sp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:backgroundTint="@color/gray"
|
||||
android:textColor="@color/black"
|
||||
android:alpha="0.8"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/floatButtonShoppingCart"
|
||||
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/floatButtonShoppingCart"
|
||||
android:layout_width="58dp"
|
||||
android:layout_height="51dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:contentDescription="TODO"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/gray"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/fragment_container"
|
||||
app:layout_constraintEnd_toEndOf="@+id/bottom_navigation"
|
||||
app:maxImageSize="60dp"
|
||||
app:srcCompat="@drawable/baseline_add_shopping_cart_24" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTopRight"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="100dp"
|
||||
android:text="進入後台"
|
||||
android:textSize="24sp"
|
||||
android:backgroundTint="@color/red"
|
||||
android:textColor="@android:color/white"
|
||||
android:alpha="1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="60dp"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -18,23 +18,22 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="500dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
>
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/scanbarcodebtn"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="400dp"
|
||||
android:layout_marginTop="-50dp"
|
||||
android:padding="10dp"
|
||||
android:text="掃描單據\n按鈕"
|
||||
android:textSize="70sp"
|
||||
android:textColor="@android:color/white"
|
||||
app:strokeWidth="5dp"
|
||||
android:textSize="70sp"
|
||||
app:strokeColor="@color/gray"
|
||||
android:layout_marginTop="-50dp"
|
||||
/>
|
||||
app:strokeWidth="5dp" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
16
app/src/main/java/com/unibuy/smartdevice/AppEntry.java
Normal file
16
app/src/main/java/com/unibuy/smartdevice/AppEntry.java
Normal file
@ -0,0 +1,16 @@
|
||||
package com.unibuy.smartdevice;
|
||||
|
||||
import com.unibuy.smartdevice.ui.FontendActivity;
|
||||
|
||||
public final class AppEntry {
|
||||
private AppEntry() {
|
||||
}
|
||||
|
||||
public static boolean isCmuh() {
|
||||
return "Cmuh".equalsIgnoreCase(BuildConfig.FLAVOR_project);
|
||||
}
|
||||
|
||||
public static Class<?> initialActivity() {
|
||||
return isCmuh() ? FontendActivity.class : HomeActivity.class;
|
||||
}
|
||||
}
|
||||
@ -239,6 +239,14 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
}
|
||||
settingsDao.close();
|
||||
|
||||
if (AppEntry.isCmuh()) {
|
||||
Intent intent = new Intent(this, FontendActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
binding = ActivityHomeBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
@ -312,7 +320,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
getLogs().info("🔄HomeActivity - Scheduled Hard App restarting via gracefulDisconnect...");
|
||||
com.unibuy.smartdevice.external.mqtt.MqttManager.getInstance().gracefulDisconnect(() -> {
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
|
||||
Intent intent = new Intent(getApplicationContext(), AppEntry.initialActivity());
|
||||
// 清空所有 Task,重新啟動
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
|
||||
@ -649,7 +649,7 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
getLogs().info("🔄MainActivity - Manual App restarting via gracefulDisconnect...");
|
||||
com.unibuy.smartdevice.external.mqtt.MqttManager.getInstance().gracefulDisconnect(() -> {
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
|
||||
Intent intent = new Intent(getApplicationContext(), AppEntry.initialActivity());
|
||||
// 清空所有 Task,重新啟動
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
|
||||
@ -15,7 +15,7 @@ import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.unibuy.smartdevice.HomeActivity;
|
||||
import com.unibuy.smartdevice.AppEntry;
|
||||
import com.unibuy.smartdevice.R;
|
||||
import com.unibuy.smartdevice.MyApp;
|
||||
import com.unibuy.smartdevice.devices.SlotField;
|
||||
@ -57,7 +57,7 @@ public class MqttService extends Service {
|
||||
createNotificationChannel();
|
||||
|
||||
// 2. 啟動為前台服務(確保不被系統殺掉)
|
||||
Intent notificationIntent = new Intent(this, HomeActivity.class);
|
||||
Intent notificationIntent = new Intent(this, AppEntry.initialActivity());
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this,
|
||||
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
@ -118,7 +118,7 @@ public class MqttService extends Service {
|
||||
.postDelayed(() -> mqttManager.gracefulDisconnect(() -> {
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
Intent restartIntent = new Intent(getApplicationContext(),
|
||||
com.unibuy.smartdevice.HomeActivity.class);
|
||||
AppEntry.initialActivity());
|
||||
restartIntent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(restartIntent);
|
||||
@ -160,12 +160,12 @@ public class MqttService extends Service {
|
||||
mqttManager.publishCommandAck(cmdId, "success", "Machine unlocked, restarting...");
|
||||
|
||||
// 🚩 優雅重啟:先 gracefulDisconnect(不觸發 LWT),再 killProcess
|
||||
// 解鎖後重啟到初始畫面 (HomeActivity)
|
||||
// 解鎖後重啟到目前版本的初始畫面
|
||||
new android.os.Handler(android.os.Looper.getMainLooper())
|
||||
.postDelayed(() -> mqttManager.gracefulDisconnect(() -> {
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
Intent restartIntent = new Intent(getApplicationContext(),
|
||||
com.unibuy.smartdevice.HomeActivity.class);
|
||||
AppEntry.initialActivity());
|
||||
restartIntent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(restartIntent);
|
||||
|
||||
@ -17,6 +17,8 @@ import java.net.URL;
|
||||
public class OtaManager {
|
||||
private static final String TAG = "OtaManager";
|
||||
private static final Logs logs = new Logs(OtaManager.class);
|
||||
private static final int DOWNLOAD_CONNECT_TIMEOUT_MS = 30000;
|
||||
private static final int DOWNLOAD_READ_TIMEOUT_MS = 120000;
|
||||
|
||||
/**
|
||||
* 啟動背景執行緒執行 APP OTA 更新
|
||||
@ -67,8 +69,8 @@ public class OtaManager {
|
||||
private static File downloadApk(Context context, String apkUrl) throws Exception {
|
||||
URL url = new URL(apkUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
connection.setConnectTimeout(DOWNLOAD_CONNECT_TIMEOUT_MS);
|
||||
connection.setReadTimeout(DOWNLOAD_READ_TIMEOUT_MS);
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
@ -108,10 +110,10 @@ public class OtaManager {
|
||||
Process process = null;
|
||||
DataOutputStream os = null;
|
||||
try {
|
||||
logs.info("Executing silent install command via su: pm install -r " + apkPath);
|
||||
logs.info("Executing silent install command via su: pm install -r -d " + apkPath);
|
||||
process = Runtime.getRuntime().exec("su");
|
||||
os = new DataOutputStream(process.getOutputStream());
|
||||
os.writeBytes("pm install -r " + apkPath + "\n");
|
||||
os.writeBytes("pm install -r -d " + apkPath + "\n");
|
||||
os.writeBytes("exit\n");
|
||||
os.flush();
|
||||
|
||||
|
||||
@ -33,8 +33,8 @@ import android.widget.Toast;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.unibuy.smartdevice.AppCompatActivityAbstract;
|
||||
import com.unibuy.smartdevice.AppEntry;
|
||||
import com.unibuy.smartdevice.FragmentAbstract;
|
||||
import com.unibuy.smartdevice.HomeActivity;
|
||||
import com.unibuy.smartdevice.MainActivity;
|
||||
import com.unibuy.smartdevice.MyApp;
|
||||
|
||||
@ -118,11 +118,11 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
private Fragment activeFragment;
|
||||
private Fragment vmcFragment;
|
||||
private Fragment electricFragment;
|
||||
private static ReportShotTimeByScheduler reportOnScheduler;
|
||||
private ReportShotTimeByScheduler reportOnScheduler;
|
||||
private ConnectivityManager connectivityManager;
|
||||
private ConnectivityManager.NetworkCallback networkCallback;
|
||||
|
||||
private static final long IDLE_TIMEOUT = 5 * 60 * 1000; // 3 分鐘
|
||||
private static final long IDLE_TIMEOUT = 5 * 60 * 1000; // 5 分鐘
|
||||
private final long TIME_LIMIT = 200000;
|
||||
private int clickCount = 0;
|
||||
private long firstClickTime = 0;
|
||||
@ -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 = "";
|
||||
@ -263,7 +267,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
// 【第一層】定時重啟:到了整點就啟動等待模式(RestartAPP2 自己會等閒置 >= 90 秒才真重啟)
|
||||
if (timeByM.equals("04:00") || timeByM.equals("08:00") || timeByM.equals("12:00")
|
||||
|| timeByM.equals("16:00") || timeByM.equals("20:00") || timeByM.equals("23:59")) {
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("AppRestartPrefs", Context.MODE_PRIVATE);
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("AppRestartPrefs",
|
||||
Context.MODE_PRIVATE);
|
||||
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||
String currentSlot = today + " " + timeByM;
|
||||
String lastRestartSlot = prefs.getString("last_restart_slot", "");
|
||||
@ -280,11 +285,11 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
|
||||
// 【第二層】閒置深度刷新:任何時間閒置超過 30 分鐘就重啟 (暫時關閉,改依賴定時重啟)
|
||||
/*
|
||||
if (MyApp.getInstance().getIdleSeconds() >= 30 * 60) {
|
||||
getLogs().info("閒置超過 30 分鐘,執行深度排毒重啟...");
|
||||
new RestartAPP2(this).start();
|
||||
}
|
||||
*/
|
||||
* if (MyApp.getInstance().getIdleSeconds() >= 30 * 60) {
|
||||
* getLogs().info("閒置超過 30 分鐘,執行深度排毒重啟...");
|
||||
* new RestartAPP2(this).start();
|
||||
* }
|
||||
*/
|
||||
|
||||
// 這裡判斷每5分鐘去跑連線下位機的狀態
|
||||
// String minutePart = timeByM.split(":")[1]; // 取得 "mm" 部分
|
||||
@ -305,24 +310,24 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
// new CMUHAutoRun(this).start();
|
||||
}
|
||||
|
||||
getLogs().update("ThreadCount", String.valueOf(Tools.threadCount()));
|
||||
String tc = getLogs().getMessage();
|
||||
getLogs().update("ViceoCount", String.valueOf(VideoEoxPlayer.getExoPlayerTotal()));
|
||||
String vc = getLogs().getMessage();
|
||||
binding.textThreadCount.setText("TC:" + tc + " VC:" + vc);
|
||||
getLogs().clearMessage();
|
||||
// 直接使用區域變數更新 UI,避免多執行緒 Race Condition 導致 getLogs().getMessage() 被改寫或清空而使 UI 變空
|
||||
String threadCountStr = String.valueOf(Tools.threadCount());
|
||||
String videoCountStr = String.valueOf(VideoEoxPlayer.getExoPlayerTotal());
|
||||
binding.textThreadCount.setText("TC:" + threadCountStr + " VC:" + videoCountStr);
|
||||
|
||||
getLogs().update("ThreadCount", threadCountStr);
|
||||
getLogs().update("ViceoCount", videoCountStr);
|
||||
|
||||
Tools.threadInfos();
|
||||
|
||||
String networkDevice = getNetworkDevice();
|
||||
binding.textNetwork.setText(getString(R.string.network) + ":" + networkDevice);
|
||||
getLogs().update("Network", networkDevice);
|
||||
binding.textNetwork.setText(getString(R.string.network) + ":" + getLogs().getMessage());
|
||||
getLogs().clearMessage();
|
||||
|
||||
DevXinYuanController.setVersion(ver);
|
||||
String tempStr = DevXinYuanController.getTemperature() + "℃";
|
||||
binding.textTemperature.setText(getString(R.string.temp) + ":" + tempStr);
|
||||
getLogs().update("Temperature", String.valueOf(DevXinYuanController.getTemperature()), "℃");
|
||||
binding.textTemperature.setText(getString(R.string.temp) + ":" + getLogs().getMessage());
|
||||
getLogs().clearMessage();
|
||||
|
||||
try {
|
||||
// httpAPI.detectHost();
|
||||
@ -436,7 +441,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
}
|
||||
|
||||
public void ReSetAPP() {
|
||||
Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
|
||||
Intent intent = new Intent(getApplicationContext(), AppEntry.initialActivity());
|
||||
// 清空所有 Task,重新啟動
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
@ -449,7 +454,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
}
|
||||
|
||||
public void ReSetAPP2() {
|
||||
Intent intent = new Intent(getApplicationContext(), HomeActivity.class);
|
||||
Intent intent = new Intent(getApplicationContext(), AppEntry.initialActivity());
|
||||
// 清空所有 Task,重新啟動
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
@ -467,7 +472,6 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
return saleFlowHandler;
|
||||
}
|
||||
|
||||
|
||||
public void startOnSwitchFragment() {
|
||||
try {
|
||||
stopOnSwitchFragment(); // ✅ 先安全釋放舊播放器
|
||||
@ -487,7 +491,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
videoEoxPlayer = new VideoEoxPlayer(getCtx()); // ✅ 用新版建構子
|
||||
|
||||
getHandlerMain().getHandler().postDelayed(() -> {
|
||||
if (isFinishing() || isDestroyed()) return;
|
||||
if (isFinishing() || isDestroyed())
|
||||
return;
|
||||
if (videoEoxPlayer != null) {
|
||||
// videoEoxPlayer.playMedia(binding.videoView, binding.imageAd); // ✅ 傳入 view
|
||||
// 開始播放
|
||||
@ -522,9 +527,17 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
resetIdleTimer();
|
||||
return;
|
||||
}
|
||||
getLogs().info("⚠️ 無操作 3 分鐘,自動返回首頁(清空購物車)");
|
||||
|
||||
// 🚩 若當前機台正處於忙碌/出貨狀態,則忽略閒置返回待機頁,並重新計時,防止出貨中 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, HomeActivity.class);
|
||||
Intent intent = new Intent(FontendActivity.this, AppEntry.initialActivity());
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
@ -700,12 +713,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
binding.floatButtonShoppingCart.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (reportOnScheduler != null) {
|
||||
reportOnScheduler.shutdown();
|
||||
}
|
||||
|
||||
reportOnScheduler = new ReportShotTimeByScheduler(getHandlerMain());
|
||||
reportOnScheduler.start(1L, 10L, TimeUnit.SECONDS);
|
||||
startHeaderReportScheduler();
|
||||
|
||||
// binding.btnswitchlanTW.setOnClickListener(v -> switchLanguage("tw"));
|
||||
//
|
||||
@ -753,6 +761,20 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
startOnSwitchFragment();
|
||||
}
|
||||
|
||||
private void startHeaderReportScheduler() {
|
||||
stopHeaderReportScheduler();
|
||||
reportOnScheduler = new ReportShotTimeByScheduler(getHandlerMain());
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.REPORT_AND_SHOT_TIME.getOption(), "start");
|
||||
reportOnScheduler.start(1L, 10L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void stopHeaderReportScheduler() {
|
||||
if (reportOnScheduler != null) {
|
||||
reportOnScheduler.shutdown();
|
||||
reportOnScheduler = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void hiddenMultiLanguageButton() {
|
||||
binding.btnswitchlanTW.setVisibility(View.GONE);
|
||||
binding.btnswitchlanEn.setVisibility(View.GONE);
|
||||
@ -840,7 +862,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
|
||||
/**
|
||||
* 【現代化重啟】不再使用 sleep 等待,直接確認閒置秒數後執行真重啟。
|
||||
* - 只要閒置 >= 90 秒,立刻啟動 HomeActivity 並 killProcess。
|
||||
* - 只要閒置 >= 90 秒,立刻啟動目前版本的初始畫面並 killProcess。
|
||||
* - 若有人正在使用(閒置 < 90 秒),則等待 5 秒後再重新判斷,最多等待 10 分鐘。
|
||||
*/
|
||||
private class RestartAPP2 extends HandlerMainScheduler {
|
||||
@ -864,7 +886,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
getLogs().info("RestartAPP2:執行真重啟 (MQTT 優雅斷線模式)");
|
||||
com.unibuy.smartdevice.external.mqtt.MqttManager.getInstance().gracefulDisconnect(() -> {
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
|
||||
Intent intent = new Intent(getApplicationContext(), com.unibuy.smartdevice.HomeActivity.class);
|
||||
Intent intent = new Intent(getApplicationContext(), AppEntry.initialActivity());
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
|
||||
@ -1230,9 +1252,6 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
}
|
||||
}
|
||||
|
||||
if (reportOnScheduler != null) {
|
||||
reportOnScheduler.shutdown();
|
||||
reportOnScheduler = null;
|
||||
}
|
||||
stopHeaderReportScheduler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; // **如果已存在,就結束迴圈**
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user