[FIX] 整合中國醫功能優化、連續出貨修復與多國語系字串調整
1. 遞增 app/build.gradle 修補版本號為 1.30。 2. 優化 StarCloudAPI 登入追蹤日誌並調整日誌前綴。 3. 修正 DispatchDialog 出貨邏輯,移除單筆出貨防呆,恢復支援多筆連續出貨。 4. 新增與調整多國語系 strings.xml (繁中、英文、日文) 中國醫無庫存與未設定貨道的提示訊息。
This commit is contained in:
parent
71a059183a
commit
47acd44e61
@ -9,7 +9,7 @@ plugins {
|
||||
// 避免不同尺寸機台間版本號產生衝突。
|
||||
// =========================================================================
|
||||
def verMinor = 1
|
||||
def verPatch = 24
|
||||
def verPatch = 30
|
||||
|
||||
|
||||
android {
|
||||
|
||||
@ -47,6 +47,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
public class SaleFlowHandler {
|
||||
private static final String ORDC_EMPTY = "ORDC_EMPTY";
|
||||
private static final String MATERIAL_NOT_FOUND = "MATERIAL_NOT_FOUND";
|
||||
private static final String STOCK_EMPTY = "STOCK_EMPTY";
|
||||
private static final String STOCK_NOT_ENOUGH = "STOCK_NOT_ENOUGH";
|
||||
|
||||
protected final Context context;
|
||||
@ -270,7 +271,13 @@ public class SaleFlowHandler {
|
||||
}
|
||||
int totalStock = 0;
|
||||
for (SlotStructure slotData : matchedSlots) {
|
||||
totalStock += slotData.getCount();
|
||||
if (isAvailableCmuhStock(slotData)) {
|
||||
totalStock += slotData.getCount();
|
||||
}
|
||||
}
|
||||
if (totalStock <= 0) {
|
||||
failWithCode(STOCK_EMPTY, context.getString(R.string.cmuh_stock_empty, ordc));
|
||||
return;
|
||||
}
|
||||
if (requestQty <= 0 || totalStock < requestQty) {
|
||||
failWithCode(STOCK_NOT_ENOUGH, context.getString(R.string.cmuh_stock_not_enough, ordc, requestQty, totalStock));
|
||||
@ -293,6 +300,7 @@ public class SaleFlowHandler {
|
||||
MemoSnDrgno = "";
|
||||
List<SlotStructure> matchedSlots = materialSlotMap.get(ordc);
|
||||
for (SlotStructure matchedSlot : matchedSlots) {
|
||||
if (!isAvailableCmuhStock(matchedSlot)) continue;
|
||||
MedicineCount += matchedSlot.getCount();
|
||||
MedicineSlot += matchedSlot.getSlot() + "-" + matchedSlot.getCount() + ",";
|
||||
if (MedicineCount >= requestQty) {
|
||||
@ -363,12 +371,19 @@ public class SaleFlowHandler {
|
||||
String materialCode = slotData.getProduct().getMaterialCode();
|
||||
if (productId == null || productId.isEmpty() || "?".equals(productId)) continue;
|
||||
if (materialCode == null || materialCode.trim().isEmpty()) continue;
|
||||
if (slotData.getCount() <= 0 || slotData.getCount() > 20) continue;
|
||||
if (slotData.getSlot() <= 0) {
|
||||
MyApp.getInstance().getLogs().info("Skip invalid VMC slot for CMUH pickup: " + slotData.getSlot());
|
||||
continue;
|
||||
}
|
||||
materialSlotMap.computeIfAbsent(materialCode.trim(), k -> new ArrayList<>()).add(slotData);
|
||||
}
|
||||
return materialSlotMap;
|
||||
}
|
||||
|
||||
private boolean isAvailableCmuhStock(SlotStructure slotData) {
|
||||
return slotData != null && slotData.getCount() > 0 && slotData.getCount() <= 20;
|
||||
}
|
||||
|
||||
private int safeParseInt(String value, int defaultValue) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
@ -378,17 +393,21 @@ public class SaleFlowHandler {
|
||||
}
|
||||
|
||||
private void failWithCode(String code, String message) {
|
||||
String finalMessage = "@" + code + " " + message;
|
||||
MyApp.getInstance().getLogs().info("[" + code + "] " + message);
|
||||
MyApp.getInstance().setCmuhPickupErrorMessage(finalMessage.substring(1));
|
||||
MyApp.getInstance().setCmuhPickupErrorMessage(message);
|
||||
haveValuestate = false;
|
||||
openBarcode = false;
|
||||
MyApp.getInstance().getBuyList().clear();
|
||||
}
|
||||
|
||||
public void addMedicineToBuyList(String MedicinePidStr, String MedicineNameStr, int SlotNumber, String MedicineImg, String MemoSnDrgno, int count) {
|
||||
if (SlotNumber <= 0) {
|
||||
failWithCode(MATERIAL_NOT_FOUND, context.getString(R.string.cmuh_material_not_found, MedicinePidStr));
|
||||
MyApp.getInstance().getLogs().info("CMUH pickup blocked invalid slot: slot=" + SlotNumber + ", pid=" + MedicinePidStr + ", name=" + MedicineNameStr);
|
||||
return;
|
||||
}
|
||||
ProductStructure productStructure = new ProductStructure(MedicinePidStr, MedicineImg, MedicineNameStr, 0, 0, 0, 0, MemoSnDrgno, "", "", "");
|
||||
MyApp.getInstance().getLogs().info("aaaaaa " + MedicinePidStr + " " + MemoSnDrgno + " " + MedicineNameStr);
|
||||
MyApp.getInstance().getLogs().info("CMUH pickup add buy item: slot=" + SlotNumber + ", pid=" + MedicinePidStr + ", name=" + MedicineNameStr);
|
||||
BuyStructure buyProduct = new BuyStructure(0, SlotNumber, productStructure, count);
|
||||
MyApp.getInstance().getBuyList().add(buyProduct);
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ public class RecyclerDialogPickupCodeBuyListAdpter extends RecyclerView.Adapter<
|
||||
logs.info("position:" + position);
|
||||
|
||||
BuyStructure buyProduct = buyList.get(position);
|
||||
holder.binding.textPosition.setText(String.valueOf(position));
|
||||
holder.binding.textPosition.setText(String.valueOf(position + 1));
|
||||
String productID = buyProduct.getProduct().getProductID();
|
||||
if (!productID.isEmpty() && !productID.equals("?")) {
|
||||
holder.binding.textProductName.setText(buyProduct.getProduct().getProductName());
|
||||
|
||||
@ -54,7 +54,7 @@ public class RecyclerDialogTakeMedicineListAdpter extends RecyclerView.Adapter<R
|
||||
logs.info("position:" + position);
|
||||
|
||||
BuyStructure buyProduct = buyList.get(position);
|
||||
holder.binding.textPosition.setText(String.valueOf(position));
|
||||
holder.binding.textPosition.setText(String.valueOf(buyProduct.getSlot()));
|
||||
String productID = buyProduct.getProduct().getProductID();
|
||||
if (!productID.isEmpty() && !productID.equals("?")) {
|
||||
holder.binding.textProductName.setText(buyProduct.getProduct().getProductName());
|
||||
|
||||
@ -13,7 +13,6 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@ -64,7 +63,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MainActivity extends AppCompatActivityAbstract {
|
||||
private static final String LOGIN_TRACE_TAG = "LoginTrace";
|
||||
@Override
|
||||
protected Context setCtx() {
|
||||
return this;
|
||||
@ -86,7 +84,7 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
if ("登入成功".equals(message)) {
|
||||
if (pendingLoginTraceId != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
Log.i(LOGIN_TRACE_TAG, "[" + pendingLoginTraceId + "] callback=success totalMs="
|
||||
getLogs().info("[LoginTrace][" + pendingLoginTraceId + "] callback=success totalMs="
|
||||
+ (now - pendingLoginStartMs) + " msg=" + message);
|
||||
}
|
||||
updateLoginStatus();
|
||||
@ -103,7 +101,7 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
} else if (pendingLoginTraceId != null && message != null
|
||||
&& (message.contains("登入失敗") || message.contains("網路錯誤") || message.contains("解析失敗"))) {
|
||||
long now = System.currentTimeMillis();
|
||||
Log.w(LOGIN_TRACE_TAG, "[" + pendingLoginTraceId + "] callback=failure totalMs="
|
||||
getLogs().info("[LoginTrace][" + pendingLoginTraceId + "] callback=failure totalMs="
|
||||
+ (now - pendingLoginStartMs) + " msg=" + message + " code=" + commandCode);
|
||||
pendingLoginTraceId = null;
|
||||
pendingLoginStartMs = 0L;
|
||||
@ -586,10 +584,10 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
String access = accessInput.getText().toString().trim();
|
||||
String password = passwordInput.getText().toString().trim();
|
||||
String traceId = String.format(Locale.US, "L%tH%<tM%<tS-%03d", clickStartMs, clickStartMs % 1000);
|
||||
Log.i(LOGIN_TRACE_TAG, "[" + traceId + "] click_login accountLen=" + access.length());
|
||||
getLogs().info("[LoginTrace][" + traceId + "] click_login accountLen=" + access.length());
|
||||
|
||||
if (access.isEmpty() || password.isEmpty()) {
|
||||
Log.w(LOGIN_TRACE_TAG, "[" + traceId + "] blocked_empty_input");
|
||||
getLogs().info("[LoginTrace][" + traceId + "] blocked_empty_input");
|
||||
Toast.makeText(context, "請輸入帳號和密碼", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(context, "正在驗證登入...", Toast.LENGTH_SHORT).show();
|
||||
@ -599,15 +597,15 @@ public class MainActivity extends AppCompatActivityAbstract {
|
||||
pendingAction = onSuccess;
|
||||
pendingLoginTraceId = traceId;
|
||||
pendingLoginStartMs = clickStartMs;
|
||||
Log.i(LOGIN_TRACE_TAG, "[" + traceId + "] dispatch_starcloud_login");
|
||||
getLogs().info("[LoginTrace][" + traceId + "] dispatch_starcloud_login");
|
||||
starCloudAPI.login(access, password, traceId, clickStartMs);
|
||||
|
||||
if (rememberAccountCb.isChecked()) {
|
||||
prefs.edit().putString("saved_account", access).apply();
|
||||
Log.i(LOGIN_TRACE_TAG, "[" + traceId + "] remember_account=save");
|
||||
getLogs().info("[LoginTrace][" + traceId + "] remember_account=save");
|
||||
} else {
|
||||
prefs.edit().remove("saved_account").apply();
|
||||
Log.i(LOGIN_TRACE_TAG, "[" + traceId + "] remember_account=clear");
|
||||
getLogs().info("[LoginTrace][" + traceId + "] remember_account=clear");
|
||||
}
|
||||
// 🛠️ 關鍵優化:不再此處立即 dismiss(),改由 API 回報成功時關閉
|
||||
}
|
||||
|
||||
@ -55,6 +55,14 @@ public class StarCloudAPI {
|
||||
this.srcHandlerMain = srcHandlerMain;
|
||||
}
|
||||
|
||||
private void traceLogin(String message) {
|
||||
if (handlerMain != null && handlerMain.getLogs() != null) {
|
||||
handlerMain.getLogs().info("[LoginTrace]" + message);
|
||||
} else {
|
||||
Log.i("LoginTrace", message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* B000: 管理員登入驗證
|
||||
*/
|
||||
@ -66,7 +74,7 @@ public class StarCloudAPI {
|
||||
Log.i(TAG, "login() called for account: " + account);
|
||||
final String url = getBaseUrl() + "admin/login/B000";
|
||||
Log.i(TAG, "Starting B000 request to: " + url);
|
||||
Log.i("LoginTrace", "[" + traceId + "] b000_start fromUiMs=" + (System.currentTimeMillis() - startMs));
|
||||
traceLogin("[" + traceId + "] b000_start fromUiMs=" + (System.currentTimeMillis() - startMs));
|
||||
|
||||
// 不依賴 FormPostMainScheduler (會靜默吞掉 IO exception)
|
||||
// 直接用 Thread + HttpURLConnection 確保錯誤一定看得到
|
||||
@ -103,11 +111,11 @@ public class StarCloudAPI {
|
||||
long writeStartMs = System.currentTimeMillis();
|
||||
conn.getOutputStream().write(bodyBytes);
|
||||
conn.getOutputStream().flush();
|
||||
Log.i("LoginTrace", "[" + traceId + "] b000_write_done tMs=" + (System.currentTimeMillis() - writeStartMs));
|
||||
traceLogin("[" + traceId + "] b000_write_done tMs=" + (System.currentTimeMillis() - writeStartMs));
|
||||
|
||||
long responseStartMs = System.currentTimeMillis();
|
||||
responseCode = conn.getResponseCode();
|
||||
Log.i("LoginTrace", "[" + traceId + "] b000_response_code=" + responseCode
|
||||
traceLogin("[" + traceId + "] b000_response_code=" + responseCode
|
||||
+ " waitMs=" + (System.currentTimeMillis() - responseStartMs));
|
||||
java.io.InputStream is = (responseCode >= 200 && responseCode < 300)
|
||||
? conn.getInputStream() : conn.getErrorStream();
|
||||
@ -120,11 +128,11 @@ public class StarCloudAPI {
|
||||
conn.disconnect();
|
||||
|
||||
Log.d(TAG, "B000 Response (" + responseCode + "): " + receive);
|
||||
Log.i("LoginTrace", "[" + traceId + "] b000_network_done totalNetMs="
|
||||
traceLogin("[" + traceId + "] b000_network_done totalNetMs="
|
||||
+ (System.currentTimeMillis() - threadStartMs));
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "B000 Network Error", e);
|
||||
Log.e("LoginTrace", "[" + traceId + "] b000_network_error afterMs="
|
||||
traceLogin("[" + traceId + "] b000_network_error afterMs="
|
||||
+ (System.currentTimeMillis() - threadStartMs) + " err=" + e.getMessage());
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
@ -149,12 +157,12 @@ public class StarCloudAPI {
|
||||
MyApp.getInstance().setSuAccess(res.optString("Su_Account", account));
|
||||
// 移除覆蓋 ApiKey 的邏輯,因為 B000 回傳的可能是 Admin Token,但我們需要保留 Machine Token
|
||||
Log.i(TAG, "Login success for user: " + account);
|
||||
Log.i("LoginTrace", "[" + traceId + "] b000_parse_success parseMs="
|
||||
traceLogin("[" + traceId + "] b000_parse_success parseMs="
|
||||
+ (System.currentTimeMillis() - mainStartMs)
|
||||
+ " totalMs=" + (System.currentTimeMillis() - startMs));
|
||||
handlerMain.start("StarCloudAPI", 200, "登入成功");
|
||||
} else {
|
||||
Log.w("LoginTrace", "[" + traceId + "] b000_business_fail code=401 parseMs="
|
||||
traceLogin("[" + traceId + "] b000_business_fail code=401 parseMs="
|
||||
+ (System.currentTimeMillis() - mainStartMs)
|
||||
+ " totalMs=" + (System.currentTimeMillis() - startMs));
|
||||
handlerMain.start("StarCloudAPI", 401,
|
||||
@ -162,14 +170,14 @@ public class StarCloudAPI {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "B000 Parse Error: " + e.getMessage());
|
||||
Log.e("LoginTrace", "[" + traceId + "] b000_parse_error parseMs="
|
||||
traceLogin("[" + traceId + "] b000_parse_error parseMs="
|
||||
+ (System.currentTimeMillis() - mainStartMs)
|
||||
+ " totalMs=" + (System.currentTimeMillis() - startMs)
|
||||
+ " err=" + e.getMessage());
|
||||
handlerMain.start("StarCloudAPI", 500, "登入解析失敗: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
Log.w("LoginTrace", "[" + traceId + "] b000_http_fail code=" + finalCode
|
||||
traceLogin("[" + traceId + "] b000_http_fail code=" + finalCode
|
||||
+ " totalMs=" + (System.currentTimeMillis() - startMs));
|
||||
handlerMain.start("StarCloudAPI", finalCode,
|
||||
"網路錯誤 (" + finalCode + "): " + finalReceive);
|
||||
|
||||
@ -264,7 +264,7 @@ public class DispatchDialog extends DialogAbstract {
|
||||
|
||||
List<BuyStructure> buyList = MyApp.getInstance().getBuyList();
|
||||
if (buyList.size() > 1) {
|
||||
getLogs().info("⚠️ BuyList 有多筆,只處理第一筆,其餘略過。");
|
||||
getLogs().info("✅ BuyList 有多筆,將依序連續出貨。");
|
||||
}
|
||||
|
||||
for (BuyStructure buyStructure: buyList) {
|
||||
@ -297,7 +297,6 @@ public class DispatchDialog extends DialogAbstract {
|
||||
int pcount = buyStructure.getCount();
|
||||
deliveryLogs.add("▲ 應出貨明細 - " + ptId + " / " + pName + " / 數量 " + pcount);
|
||||
|
||||
break; // ✅ 加入這行防呆:只處理一筆後跳出
|
||||
}
|
||||
|
||||
getLogs().info("DispatchList size:" + this.dispatchList.size());
|
||||
|
||||
@ -394,7 +394,8 @@
|
||||
<string name="sync_product_warning">This will download all products from the cloud and overwrite any unsaved local inventory changes. Are you sure you want to continue?</string>
|
||||
<string name="downloading_product_data">Downloading product data...</string>
|
||||
<string name="cmuh_ordc_empty">Missing ORDC in prescription data. Please scan again or contact pharmacy staff.</string>
|
||||
<string name="cmuh_material_not_found">Item [%1$s] has no stock or no assigned slot. Please verify configuration.</string>
|
||||
<string name="cmuh_material_not_found">Item [%1$s] has no assigned slot. Please contact pharmacy staff.</string>
|
||||
<string name="cmuh_stock_empty">Item [%1$s] is currently out of stock. Please contact pharmacy staff.</string>
|
||||
<string name="cmuh_stock_not_enough">Item [%1$s] has insufficient stock. Required: %2$d, Available: %3$d</string>
|
||||
<string name="vmc_material_code_empty">MATERIAL_CODE_EMPTY Slot %1$d product %2$s is missing material code. Please fix product master data first.</string>
|
||||
<string name="cmuh_pickup_failed_contact_counter">Pickup failed\nPlease contact the counter</string>
|
||||
|
||||
@ -396,7 +396,8 @@
|
||||
<string name="syncing">同期中... </string>
|
||||
<string name="sync_completed">商品データの同期が完了しました</string>
|
||||
<string name="cmuh_ordc_empty">処方データに ORDC がありません。再度スキャンするか薬局窓口へお問い合わせください。</string>
|
||||
<string name="cmuh_material_not_found">商品 [%1$s] は在庫切れ、またはスロット未設定です。設定を確認してください。</string>
|
||||
<string name="cmuh_material_not_found">商品 [%1$s] はスロット未設定です。薬局窓口へお問い合わせください。</string>
|
||||
<string name="cmuh_stock_empty">商品 [%1$s] は現在在庫切れです。薬局窓口へお問い合わせください。</string>
|
||||
<string name="cmuh_stock_not_enough">商品 [%1$s] の在庫が不足しています。必要数: %2$d、在庫: %3$d</string>
|
||||
<string name="vmc_material_code_empty">MATERIAL_CODE_EMPTY スロット %1$d の商品 %2$s に物料コードがありません。商品マスタを修正してください。</string>
|
||||
<string name="cmuh_pickup_failed_contact_counter">受け取り失敗\nカウンターへお問い合わせください</string>
|
||||
|
||||
@ -313,7 +313,8 @@
|
||||
<string name="upload_failed">資料上傳失敗</string>
|
||||
<string name="upload_to_backend_and_return">上傳後台並返回</string>
|
||||
<string name="cmuh_ordc_empty">領藥單缺少 ORDC,請重新掃碼或洽藥局窗口。</string>
|
||||
<string name="cmuh_material_not_found">商品 [%1$s] 無庫存或未設定貨道,請確認。</string>
|
||||
<string name="cmuh_material_not_found">商品 [%1$s] 未設定貨道,請洽藥局窗口。</string>
|
||||
<string name="cmuh_stock_empty">商品 [%1$s] 目前無庫存,請洽藥局窗口。</string>
|
||||
<string name="cmuh_stock_not_enough">商品 [%1$s] 庫存不足,需求:%2$d,可用:%3$d</string>
|
||||
<string name="vmc_material_code_empty">MATERIAL_CODE_EMPTY 貨道 %1$d 商品 %2$s 缺少物料編號,請先修正商品主檔。</string>
|
||||
<string name="cmuh_pickup_failed_contact_counter">取物失敗\n請洽櫃台</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user