From 212c64e0ed968b2e572e314e02245d0d1bef8f73 Mon Sep 17 00:00:00 2001
From: wuweihua <88888888>
Date: Mon, 21 Sep 2026 10:05:16 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AE=A2=E5=8D=95=E5=90=8C?=
=?UTF-8?q?=E6=AD=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bms-back/ruoyi-admin/pom.xml | 8 +
.../controller/api/TmsPriceController.java | 35 +++
.../src/main/resources/application-druid.yml | 16 +-
.../src/main/resources/application.yml | 11 +
.../api/impl/CustomerAccountApiImpl.java | 3 +-
.../ruoyi/price/config/BillingProperties.java | 86 +++++++
.../ruoyi/price/domain/PrcBillingTask.java | 29 +++
.../com/ruoyi/price/domain/PrcOrderBill.java | 6 +
.../domain/vo/BillingTaskSubmitRequest.java | 28 +++
.../price/domain/vo/TmsPriceBillRequest.java | 2 +
.../price/domain/vo/TmsPriceQuoteResult.java | 6 +
.../price/enums/BillCreditArrearsStatus.java | 23 ++
.../price/enums/BillCustomerActiveStatus.java | 24 ++
.../price/enums/BillTaskSuccessStatus.java | 21 ++
.../com/ruoyi/price/enums/BillTaskType.java | 40 +++
.../mapper/OracleUpstreamOrderMapper.java | 20 ++
.../price/mapper/PrcBillingTaskMapper.java | 18 ++
.../price/mapper/PrcOrderBillMapper.java | 2 +
.../ruoyi/price/mq/BillingTaskMessage.java | 30 +++
.../ruoyi/price/mq/BillingTaskMqConsumer.java | 94 +++++++
.../ruoyi/price/mq/BillingTaskMqEvent.java | 21 ++
.../price/mq/IBillingTaskMqPublisher.java | 7 +
.../price/mq/LocalBillingTaskMqPublisher.java | 27 ++
.../price/mq/RedisBillingTaskMqPublisher.java | 38 +++
.../price/oracle/OracleOrderBillMapper.java | 231 +++++++++++++++++
.../oracle/OracleUpstreamOrderService.java | 125 +++++++++
.../price/service/IPrcBillingTaskService.java | 15 ++
.../impl/PrcBillingTaskServiceImpl.java | 238 ++++++++++++++++++
.../service/impl/PrcOrderBillServiceImpl.java | 85 ++++++-
.../impl/TmsPriceQuoteServiceImpl.java | 47 +++-
.../price/OracleUpstreamOrderMapper.xml | 47 ++++
.../mapper/price/PrcBillingTaskMapper.xml | 95 +++++++
.../mapper/price/PrcOrderBillMapper.xml | 37 ++-
bms-back/sql/price/billing_task.sql | 27 ++
.../sql/price/order_bill_status_alter.sql | 12 +
bms-front/src/views/price/billing/bill.vue | 33 +++
.../price/billing/components/BillDetail.vue | 13 +
37 files changed, 1576 insertions(+), 24 deletions(-)
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/config/BillingProperties.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcBillingTask.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/BillingTaskSubmitRequest.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCreditArrearsStatus.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCustomerActiveStatus.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskSuccessStatus.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskType.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/OracleUpstreamOrderMapper.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcBillingTaskMapper.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMessage.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqConsumer.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqEvent.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/IBillingTaskMqPublisher.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/LocalBillingTaskMqPublisher.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/RedisBillingTaskMqPublisher.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleOrderBillMapper.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleUpstreamOrderService.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IPrcBillingTaskService.java
create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcBillingTaskServiceImpl.java
create mode 100644 bms-back/ruoyi-system/src/main/resources/mapper/price/OracleUpstreamOrderMapper.xml
create mode 100644 bms-back/ruoyi-system/src/main/resources/mapper/price/PrcBillingTaskMapper.xml
create mode 100644 bms-back/sql/price/billing_task.sql
create mode 100644 bms-back/sql/price/order_bill_status_alter.sql
diff --git a/bms-back/ruoyi-admin/pom.xml b/bms-back/ruoyi-admin/pom.xml
index 4643a4d7..decfe985 100644
--- a/bms-back/ruoyi-admin/pom.xml
+++ b/bms-back/ruoyi-admin/pom.xml
@@ -36,6 +36,14 @@
mysql-connector-j
+
+
+ com.oracle.database.jdbc
+ ojdbc11
+ 23.5.0.24.07
+ runtime
+
+
com.ruoyi
diff --git a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TmsPriceController.java b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TmsPriceController.java
index 1bbcdb69..f2c21df2 100644
--- a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TmsPriceController.java
+++ b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TmsPriceController.java
@@ -16,12 +16,15 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.TmsApiProperties;
+import com.ruoyi.price.domain.vo.BillingTaskSubmitRequest;
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
+import com.ruoyi.price.domain.PrcBillingTask;
import com.ruoyi.price.search.EsBizLogSupport;
import com.ruoyi.price.search.EsLogType;
import com.ruoyi.price.search.IBillSearchStore;
+import com.ruoyi.price.service.IPrcBillingTaskService;
import com.ruoyi.price.service.IPrcOrderBillService;
import com.ruoyi.price.service.ITmsPriceQuoteService;
@@ -46,6 +49,9 @@ public class TmsPriceController extends BaseController
@Autowired
private IPrcOrderBillService prcOrderBillService;
+ @Autowired
+ private IPrcBillingTaskService prcBillingTaskService;
+
@Autowired
private TmsApiProperties tmsApiProperties;
@@ -99,6 +105,35 @@ public class TmsPriceController extends BaseController
return success(result);
}
+ /**
+ * Warehouse inbound: create billing task (PENDING/REBILL/REFUND) and publish MQ.
+ * Consumer queries Oracle by transferNo/orderNo then inserts prc_order_bill.
+ * POST /api/tms/price/billing-task
+ */
+ @PostMapping("/billing-task")
+ public AjaxResult submitBillingTask(
+ @RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
+ @RequestBody BillingTaskSubmitRequest request)
+ {
+ assertApiCode(headerApiCode, request == null ? null : request.getApiCode());
+ PrcBillingTask task = prcBillingTaskService.submitInbound(request);
+ return success(task);
+ }
+
+ /**
+ * Query billing task writeback status.
+ * GET /api/tms/price/billing-task/{taskId}
+ */
+ @GetMapping("/billing-task/{taskId}")
+ public AjaxResult getBillingTask(
+ @RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
+ @PathVariable("taskId") Long taskId,
+ @RequestParam(value = "apiCode", required = false) String apiCode)
+ {
+ assertApiCode(headerApiCode, apiCode);
+ return success(prcBillingTaskService.getById(taskId));
+ }
+
/**
* Query order bill by orderNo after sync.
*/
diff --git a/bms-back/ruoyi-admin/src/main/resources/application-druid.yml b/bms-back/ruoyi-admin/src/main/resources/application-druid.yml
index b48acc38..3148cebf 100644
--- a/bms-back/ruoyi-admin/src/main/resources/application-druid.yml
+++ b/bms-back/ruoyi-admin/src/main/resources/application-druid.yml
@@ -10,13 +10,17 @@ spring:
username: bms
password: D3wsHf2ZMfFtWtKX
connectionInitSqls: SET NAMES utf8mb4
- # 从库数据源
+ # Slave DS: Oracle upstream order (TAB_ORDER)
slave:
- # 从数据源开关/默认关闭
- enabled: false
- url:
- username:
- password:
+ enabled: true
+ driverClassName: oracle.jdbc.OracleDriver
+ url: jdbc:oracle:thin:@//120.79.189.203:15281/WSDB
+ username: readonly_report
+ password: k5x15WP1hE
+ # Oracle remote query may need longer read timeout than MySQL
+ connectTimeout: 30000
+ socketTimeout: 120000
+ validationQuery: SELECT 1 FROM DUAL
# 初始连接数
initialSize: 5
# 最小连接池数量
diff --git a/bms-back/ruoyi-admin/src/main/resources/application.yml b/bms-back/ruoyi-admin/src/main/resources/application.yml
index ee0b02e5..2aed60c7 100644
--- a/bms-back/ruoyi-admin/src/main/resources/application.yml
+++ b/bms-back/ruoyi-admin/src/main/resources/application.yml
@@ -151,6 +151,17 @@ bms:
tms:
# Agreed API identification code for TMS callers
api-code: BMS.TMS.ZYaOvlQzpcfau8TUaCFfO3Hp8ieHIqGw
+ billing:
+ # MQ mode: redis (default list queue) | local (Spring event)
+ mq-mode: redis
+ mq-queue-key: bms:billing:task
+ consumer-enabled: true
+ consumer-delay-ms: 1000
+ oracle:
+ # SLAVE datasource Oracle main-order table (schema.table allowed)
+ order-table: WEISHI_USER.TAB_ORDER
+ # Fallback product when Oracle row has no PRODUCT_CODE
+ default-product-code: ""
elasticsearch:
enabled: true
uris: http://127.0.0.1:9200
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/api/impl/CustomerAccountApiImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/api/impl/CustomerAccountApiImpl.java
index ccf2afe5..42929a56 100644
--- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/api/impl/CustomerAccountApiImpl.java
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/api/impl/CustomerAccountApiImpl.java
@@ -592,7 +592,8 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
flow.setEffectiveDate(effectiveDate);
flow.setExpireDate(expireDate);
flow.setReason("账户初始化");
- flow.setOperatorId(SecurityUtils.getUserId());
+ // No login (open API / MQ / job): operatorId null, same as occupyCredit
+ flow.setOperatorId(currentUserId());
flow.setCreateBy(username);
flow.setRemark(remark);
flow.setDelFlag("0");
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/config/BillingProperties.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/config/BillingProperties.java
new file mode 100644
index 00000000..80ad4f95
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/config/BillingProperties.java
@@ -0,0 +1,86 @@
+package com.ruoyi.price.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * Billing task MQ + Oracle upstream order settings.
+ */
+@Component
+@ConfigurationProperties(prefix = "bms.billing")
+public class BillingProperties
+{
+ /** local = Spring event; redis = Redis list queue */
+ private String mqMode = "redis";
+ private String mqQueueKey = "bms:billing:task";
+ private boolean consumerEnabled = true;
+ private Oracle oracle = new Oracle();
+
+ public String getMqMode()
+ {
+ return mqMode;
+ }
+
+ public void setMqMode(String mqMode)
+ {
+ this.mqMode = mqMode;
+ }
+
+ public String getMqQueueKey()
+ {
+ return mqQueueKey;
+ }
+
+ public void setMqQueueKey(String mqQueueKey)
+ {
+ this.mqQueueKey = mqQueueKey;
+ }
+
+ public boolean isConsumerEnabled()
+ {
+ return consumerEnabled;
+ }
+
+ public void setConsumerEnabled(boolean consumerEnabled)
+ {
+ this.consumerEnabled = consumerEnabled;
+ }
+
+ public Oracle getOracle()
+ {
+ return oracle;
+ }
+
+ public void setOracle(Oracle oracle)
+ {
+ this.oracle = oracle;
+ }
+
+ public static class Oracle
+ {
+ /** Upstream main-order table name on SLAVE/Oracle DS */
+ private String orderTable = "WEISHI_USER.TAB_ORDER";
+ /** Optional default product when Oracle row has no PRODUCT_CODE */
+ private String defaultProductCode = "";
+
+ public String getOrderTable()
+ {
+ return orderTable;
+ }
+
+ public void setOrderTable(String orderTable)
+ {
+ this.orderTable = orderTable;
+ }
+
+ public String getDefaultProductCode()
+ {
+ return defaultProductCode;
+ }
+
+ public void setDefaultProductCode(String defaultProductCode)
+ {
+ this.defaultProductCode = defaultProductCode;
+ }
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcBillingTask.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcBillingTask.java
new file mode 100644
index 00000000..e6fb3b12
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcBillingTask.java
@@ -0,0 +1,29 @@
+package com.ruoyi.price.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/** Billing task row prc_billing_task */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PrcBillingTask extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ private Long taskId;
+ private String transferNo;
+ private String trackingNo;
+ private String orderNo;
+ /** PENDING / REBILL / REFUND */
+ private String billType;
+ /** WAITING / SUCCESS / FAILED */
+ private String successStatus;
+ /** Billing time from mobile (not Oracle order time; not overwritten on writeback) */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date billTime;
+ private String billId;
+ private String errorMessage;
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java
index 354875aa..18eafda2 100644
--- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/PrcOrderBill.java
@@ -21,6 +21,8 @@ public class PrcOrderBill extends BaseEntity
/** 批量查询:多个订单号精确匹配(仅查询用,非表字段) */
private List orderNos;
private String trackingNo;
+ /** transfer order no */
+ private String transferNo;
private Long customerId;
private String customerCode;
private String customerName;
@@ -79,6 +81,10 @@ public class PrcOrderBill extends BaseEntity
private String currency;
/** 应收账单占用状态:UNBILLED/BILLED。 */
private String arBillStatus;
+ /** ACTIVE / MISSING / DISABLED */
+ private String customerActiveStatus;
+ /** NORMAL / ARREARS */
+ private String creditArrearsStatus;
private String roundMode;
private List incomeFees;
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/BillingTaskSubmitRequest.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/BillingTaskSubmitRequest.java
new file mode 100644
index 00000000..8086e000
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/BillingTaskSubmitRequest.java
@@ -0,0 +1,28 @@
+package com.ruoyi.price.domain.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+/**
+ * Warehouse inbound request: create billing task then MQ consume.
+ * Provide at least one of transferNo / trackingNo / orderNo.
+ * billTime is billing time from mobile (stored in prc_billing_task.bill_time).
+ */
+@Data
+public class BillingTaskSubmitRequest implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ private String transferNo;
+ private String trackingNo;
+ private String orderNo;
+ /** Billing time from mobile; persisted to prc_billing_task.bill_time */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date billTime;
+ /** Optional: PENDING / REBILL / REFUND. Empty = auto PENDING or REBILL. */
+ private String billType;
+ /** Optional body apiCode when header X-Api-Code missing */
+ private String apiCode;
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceBillRequest.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceBillRequest.java
index 9f2a1ba4..dec65633 100644
--- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceBillRequest.java
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceBillRequest.java
@@ -20,6 +20,8 @@ public class TmsPriceBillRequest extends TmsPriceQuoteRequest
private String orderNo;
private String trackingNo;
+ /** transfer order no */
+ private String transferNo;
private String customerName;
private Integer pieces;
private BigDecimal actualWeight;
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceQuoteResult.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceQuoteResult.java
index 715d8bba..5d11ebe0 100644
--- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceQuoteResult.java
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/vo/TmsPriceQuoteResult.java
@@ -73,8 +73,14 @@ public class TmsPriceQuoteResult implements Serializable
private String billId;
private String orderNo;
private String trackingNo;
+ /** transfer order no */
+ private String transferNo;
private String customerCode;
private String customerName;
+ /** ACTIVE / MISSING / DISABLED */
+ private String customerActiveStatus;
+ /** NORMAL / ARREARS */
+ private String creditArrearsStatus;
private Integer pieces;
private BigDecimal actualWeight;
private BigDecimal volume;
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCreditArrearsStatus.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCreditArrearsStatus.java
new file mode 100644
index 00000000..c7336f51
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCreditArrearsStatus.java
@@ -0,0 +1,23 @@
+package com.ruoyi.price.enums;
+
+/**
+ * Credit arrears status snapshot on prc_order_bill when billing.
+ * Billing continues even when ARREARS; occupy still runs with checkAvailable=false.
+ */
+public enum BillCreditArrearsStatus
+{
+ NORMAL("NORMAL"),
+ ARREARS("ARREARS");
+
+ private final String code;
+
+ BillCreditArrearsStatus(String code)
+ {
+ this.code = code;
+ }
+
+ public String getCode()
+ {
+ return code;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCustomerActiveStatus.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCustomerActiveStatus.java
new file mode 100644
index 00000000..9d4be226
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillCustomerActiveStatus.java
@@ -0,0 +1,24 @@
+package com.ruoyi.price.enums;
+
+/**
+ * Customer effective status snapshot on prc_order_bill when billing.
+ * Billing continues even when MISSING / DISABLED.
+ */
+public enum BillCustomerActiveStatus
+{
+ ACTIVE("ACTIVE"),
+ MISSING("MISSING"),
+ DISABLED("DISABLED");
+
+ private final String code;
+
+ BillCustomerActiveStatus(String code)
+ {
+ this.code = code;
+ }
+
+ public String getCode()
+ {
+ return code;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskSuccessStatus.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskSuccessStatus.java
new file mode 100644
index 00000000..ba8b7e49
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskSuccessStatus.java
@@ -0,0 +1,21 @@
+package com.ruoyi.price.enums;
+
+/** Billing task success flag on prc_billing_task.success_status */
+public enum BillTaskSuccessStatus
+{
+ WAITING("WAITING"),
+ SUCCESS("SUCCESS"),
+ FAILED("FAILED");
+
+ private final String code;
+
+ BillTaskSuccessStatus(String code)
+ {
+ this.code = code;
+ }
+
+ public String getCode()
+ {
+ return code;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskType.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskType.java
new file mode 100644
index 00000000..7867e5f0
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/enums/BillTaskType.java
@@ -0,0 +1,40 @@
+package com.ruoyi.price.enums;
+
+/** Billing task type on prc_billing_task.bill_type */
+public enum BillTaskType
+{
+ /** first-time billing */
+ PENDING("PENDING"),
+ /** same transfer/order already billed */
+ REBILL("REBILL"),
+ /** refund */
+ REFUND("REFUND");
+
+ private final String code;
+
+ BillTaskType(String code)
+ {
+ this.code = code;
+ }
+
+ public String getCode()
+ {
+ return code;
+ }
+
+ public static BillTaskType fromCode(String code)
+ {
+ if (code == null || code.isEmpty())
+ {
+ return null;
+ }
+ for (BillTaskType t : values())
+ {
+ if (t.code.equalsIgnoreCase(code.trim()))
+ {
+ return t;
+ }
+ }
+ return null;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/OracleUpstreamOrderMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/OracleUpstreamOrderMapper.java
new file mode 100644
index 00000000..96871b97
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/OracleUpstreamOrderMapper.java
@@ -0,0 +1,20 @@
+package com.ruoyi.price.mapper;
+
+import java.util.Map;
+import org.apache.ibatis.annotations.Param;
+
+/** Read upstream main order from Oracle (SLAVE datasource). */
+public interface OracleUpstreamOrderMapper
+{
+ Map selectByTransferNo(@Param("orderTable") String orderTable, @Param("transferNo") String transferNo);
+
+ Map selectByOrderId(@Param("orderTable") String orderTable, @Param("orderNo") String orderNo);
+
+ Map selectByOrgOrderId(@Param("orderTable") String orderTable, @Param("orderNo") String orderNo);
+
+ Map selectByTrackId(@Param("orderTable") String orderTable, @Param("trackingNo") String trackingNo);
+
+ Map selectByBarcode(@Param("orderTable") String orderTable, @Param("trackingNo") String trackingNo);
+
+ Map selectByCustomerTrackId(@Param("orderTable") String orderTable, @Param("trackingNo") String trackingNo);
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcBillingTaskMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcBillingTaskMapper.java
new file mode 100644
index 00000000..94a8a1a8
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcBillingTaskMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.price.mapper;
+
+import java.util.List;
+import com.ruoyi.price.domain.PrcBillingTask;
+
+public interface PrcBillingTaskMapper
+{
+ PrcBillingTask selectPrcBillingTaskById(Long taskId);
+
+ List selectPrcBillingTaskList(PrcBillingTask query);
+
+ int insertPrcBillingTask(PrcBillingTask task);
+
+ int updatePrcBillingTask(PrcBillingTask task);
+
+ /** CAS: WAITING -> processing claim not needed; writeback SUCCESS/FAILED */
+ int updateBillingResult(PrcBillingTask task);
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java
index c37804b5..6175630f 100644
--- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mapper/PrcOrderBillMapper.java
@@ -11,6 +11,8 @@ public interface PrcOrderBillMapper
{
public PrcOrderBill selectPrcOrderBillByOrderNo(String orderNo);
+ public PrcOrderBill selectPrcOrderBillByTransferNo(String transferNo);
+
public PrcOrderBill selectPrcOrderBillById(String billId);
public List selectPrcOrderBillList(PrcOrderBill bill);
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMessage.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMessage.java
new file mode 100644
index 00000000..d9f855c2
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMessage.java
@@ -0,0 +1,30 @@
+package com.ruoyi.price.mq;
+
+import java.io.Serializable;
+
+/** MQ payload: only taskId; consumer loads full row from MySQL. */
+public class BillingTaskMessage implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ private Long taskId;
+
+ public BillingTaskMessage()
+ {
+ }
+
+ public BillingTaskMessage(Long taskId)
+ {
+ this.taskId = taskId;
+ }
+
+ public Long getTaskId()
+ {
+ return taskId;
+ }
+
+ public void setTaskId(Long taskId)
+ {
+ this.taskId = taskId;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqConsumer.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqConsumer.java
new file mode 100644
index 00000000..b334e679
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqConsumer.java
@@ -0,0 +1,94 @@
+package com.ruoyi.price.mq;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.event.EventListener;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.price.config.BillingProperties;
+import com.ruoyi.price.service.IPrcBillingTaskService;
+
+/**
+ * Consume billing task MQ messages and call billing interface.
+ */
+@Component
+@ConditionalOnProperty(prefix = "bms.billing", name = "consumer-enabled", havingValue = "true", matchIfMissing = true)
+public class BillingTaskMqConsumer
+{
+ private static final Logger log = LoggerFactory.getLogger(BillingTaskMqConsumer.class);
+
+ @Autowired
+ private IPrcBillingTaskService billingTaskService;
+
+ @Autowired
+ private BillingProperties billingProperties;
+
+ @Autowired(required = false)
+ private StringRedisTemplate stringRedisTemplate;
+
+ /** Local MQ mode: async event listener. */
+ @Async
+ @EventListener
+ public void onLocalEvent(BillingTaskMqEvent event)
+ {
+ if (!isMode("local"))
+ {
+ return;
+ }
+ if (event == null || event.getMessage() == null)
+ {
+ return;
+ }
+ billingTaskService.processTask(event.getMessage().getTaskId());
+ }
+
+ /** Redis MQ mode: poll list every second. */
+ @Scheduled(fixedDelayString = "${bms.billing.consumer-delay-ms:1000}")
+ public void pollRedisQueue()
+ {
+ if (!isMode("redis"))
+ {
+ return;
+ }
+ if (stringRedisTemplate == null)
+ {
+ return;
+ }
+ String key = billingProperties.getMqQueueKey();
+ String payload = stringRedisTemplate.opsForList().rightPop(key);
+ if (payload == null || payload.isEmpty())
+ {
+ return;
+ }
+ try
+ {
+ BillingTaskMessage msg = JSON.parseObject(payload, BillingTaskMessage.class);
+ if (msg == null || msg.getTaskId() == null)
+ {
+ log.warn("invalid billing mq payload={}", payload);
+ return;
+ }
+ billingTaskService.processTask(msg.getTaskId());
+ }
+ catch (RuntimeException e)
+ {
+ log.error("billing mq consume failed payload={}", payload, e);
+ }
+ }
+
+ private boolean isMode(String expected)
+ {
+ String mode = billingProperties.getMqMode();
+ if (StringUtils.isEmpty(mode))
+ {
+ mode = "redis";
+ }
+ return expected.equalsIgnoreCase(mode.trim());
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqEvent.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqEvent.java
new file mode 100644
index 00000000..07b23f8e
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/BillingTaskMqEvent.java
@@ -0,0 +1,21 @@
+package com.ruoyi.price.mq;
+
+import org.springframework.context.ApplicationEvent;
+
+public class BillingTaskMqEvent extends ApplicationEvent
+{
+ private static final long serialVersionUID = 1L;
+
+ private final BillingTaskMessage message;
+
+ public BillingTaskMqEvent(Object source, BillingTaskMessage message)
+ {
+ super(source);
+ this.message = message;
+ }
+
+ public BillingTaskMessage getMessage()
+ {
+ return message;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/IBillingTaskMqPublisher.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/IBillingTaskMqPublisher.java
new file mode 100644
index 00000000..2c9be146
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/IBillingTaskMqPublisher.java
@@ -0,0 +1,7 @@
+package com.ruoyi.price.mq;
+
+/** Publish billing task id to MQ after warehouse inbound insert. */
+public interface IBillingTaskMqPublisher
+{
+ void publish(BillingTaskMessage message);
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/LocalBillingTaskMqPublisher.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/LocalBillingTaskMqPublisher.java
new file mode 100644
index 00000000..468d7752
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/LocalBillingTaskMqPublisher.java
@@ -0,0 +1,27 @@
+package com.ruoyi.price.mq;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+/**
+ * Local MQ: Spring application event (dev / no Redis broker).
+ * Enable with bms.billing.mq-mode=local
+ */
+@Component
+@ConditionalOnProperty(prefix = "bms.billing", name = "mq-mode", havingValue = "local")
+public class LocalBillingTaskMqPublisher implements IBillingTaskMqPublisher
+{
+ private final ApplicationEventPublisher eventPublisher;
+
+ public LocalBillingTaskMqPublisher(ApplicationEventPublisher eventPublisher)
+ {
+ this.eventPublisher = eventPublisher;
+ }
+
+ @Override
+ public void publish(BillingTaskMessage message)
+ {
+ eventPublisher.publishEvent(new BillingTaskMqEvent(this, message));
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/RedisBillingTaskMqPublisher.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/RedisBillingTaskMqPublisher.java
new file mode 100644
index 00000000..3ad0f1fb
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/mq/RedisBillingTaskMqPublisher.java
@@ -0,0 +1,38 @@
+package com.ruoyi.price.mq;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.price.config.BillingProperties;
+
+/**
+ * Redis list as MQ queue (default). LPUSH on publish; consumer BRPOP/RPOP.
+ * Enable with bms.billing.mq-mode=redis (default).
+ */
+@Component
+@ConditionalOnProperty(prefix = "bms.billing", name = "mq-mode", havingValue = "redis", matchIfMissing = true)
+public class RedisBillingTaskMqPublisher implements IBillingTaskMqPublisher
+{
+ private static final Logger log = LoggerFactory.getLogger(RedisBillingTaskMqPublisher.class);
+
+ private final StringRedisTemplate stringRedisTemplate;
+ private final BillingProperties billingProperties;
+
+ public RedisBillingTaskMqPublisher(StringRedisTemplate stringRedisTemplate, BillingProperties billingProperties)
+ {
+ this.stringRedisTemplate = stringRedisTemplate;
+ this.billingProperties = billingProperties;
+ }
+
+ @Override
+ public void publish(BillingTaskMessage message)
+ {
+ String key = billingProperties.getMqQueueKey();
+ String payload = JSON.toJSONString(message);
+ stringRedisTemplate.opsForList().leftPush(key, payload);
+ log.info("billing task mq published taskId={} queue={}", message.getTaskId(), key);
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleOrderBillMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleOrderBillMapper.java
new file mode 100644
index 00000000..69afffe2
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleOrderBillMapper.java
@@ -0,0 +1,231 @@
+package com.ruoyi.price.oracle;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
+
+/**
+ * Map Oracle main-order row (FIELD_MAP style columns) to TmsPriceBillRequest.
+ */
+@Component
+public class OracleOrderBillMapper
+{
+ public TmsPriceBillRequest toBillRequest(Map row, String defaultProductCode)
+ {
+ if (row == null || row.isEmpty())
+ {
+ return null;
+ }
+ TmsPriceBillRequest req = new TmsPriceBillRequest();
+ req.setOrderNo(firstStr(row, "ORG_ORDER_ID", "ORDER_ID", "TRANSFER_ORDER_ID"));
+ req.setTrackingNo(firstStr(row, "TRANSFER_ORDER_ID", "TRACK_ID", "BARCODE_NUMBER", "CUSTOMER_TRACK_ID"));
+ req.setTransferNo(firstStr(row, "TRANSFER_ORDER_ID"));
+ req.setCustomerCode(firstStr(row, "CUSTOMER_CODE"));
+ req.setCustomerName(firstStr(row, "CUSTOMER_NAME"));
+ String product = firstStr(row, "PRODUCT_CODE", "GOODS_CODE", "CHANNEL_CODE");
+ if (StringUtils.isEmpty(product))
+ {
+ product = defaultProductCode;
+ }
+ req.setProductCode(product);
+ req.setCountryCode(firstStr(row, "COUNTRY"));
+ String countryName = firstStr(row, "COUNTRY_NAME", "COUNTRY_EINGTH_NAME", "COUNTRY");
+ req.setCountryName(countryName);
+ req.setDestPostcode(firstStr(row, "CONSIGNEE_POSTCODE"));
+ // Prefer positive weight: Oracle WEIGHT is often 0 while ACTUAL_WEIGHT/PUSH_WEIGHT holds real value.
+ BigDecimal weight = firstPositiveDecimal(row, "ACTUAL_WEIGHT", "PUSH_WEIGHT", "WEIGHT", "VOLUME_WEIGHT");
+ req.setWeight(weight);
+ req.setActualWeight(firstPositiveDecimal(row, "ACTUAL_WEIGHT", "PUSH_WEIGHT", "WEIGHT"));
+ req.setPieces(firstInt(row, "ORDER_PIECE", "PACKAGE_NUM", "PRODUCT_SUM"));
+ req.setVolume(toVolumeM3(firstDecimal(row, "VOLUME")));
+ req.setVolumeWeight(firstDecimal(row, "VOLUME_WEIGHT"));
+ req.setPkgLength(firstDecimal(row, "LENGTH"));
+ req.setPkgWidth(firstDecimal(row, "WIDTH"));
+ req.setPkgHeight(firstDecimal(row, "HEIGHT"));
+ req.setShipperName(firstStr(row, "SHIPPER_NAME", "SHIPPER_COMPANYNAME"));
+ req.setConsigneeName(firstStr(row, "CONSIGNEE_NAME"));
+ req.setWarehouseCode(firstStr(row, "REGISTER_SITE_CODE"));
+ req.setOrderRemark(firstStr(row, "TRACK_REMARK", "EXCEPTION_REMARK", "ORDER_CUSTOMNOTE"));
+ // CREATE_DATE is order place time only; bizDate (billing date) comes from mobile inbound task.
+ Date createDate = firstDate(row, "CREATE_DATE");
+ if (createDate != null)
+ {
+ req.setOrderTime(createDate);
+ }
+ return req;
+ }
+
+ private static String firstStr(Map row, String... keys)
+ {
+ Object v = firstValue(row, keys);
+ if (v == null)
+ {
+ return null;
+ }
+ String s = String.valueOf(v).trim();
+ return s.isEmpty() ? null : s;
+ }
+
+ private static BigDecimal firstDecimal(Map row, String... keys)
+ {
+ Object v = firstValue(row, keys);
+ if (v == null || "".equals(String.valueOf(v).trim()))
+ {
+ return null;
+ }
+ if (v instanceof BigDecimal)
+ {
+ return (BigDecimal) v;
+ }
+ if (v instanceof Number)
+ {
+ return BigDecimal.valueOf(((Number) v).doubleValue());
+ }
+ try
+ {
+ return new BigDecimal(String.valueOf(v).trim());
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ }
+
+ /** First decimal that is strictly > 0 among keys (skip Oracle zero placeholders). */
+ private static BigDecimal firstPositiveDecimal(Map row, String... keys)
+ {
+ if (row == null || keys == null)
+ {
+ return null;
+ }
+ for (String key : keys)
+ {
+ Object raw = getIgnoreCase(row, key);
+ if (raw == null || "".equals(String.valueOf(raw).trim()))
+ {
+ continue;
+ }
+ BigDecimal d;
+ if (raw instanceof BigDecimal)
+ {
+ d = (BigDecimal) raw;
+ }
+ else if (raw instanceof Number)
+ {
+ d = BigDecimal.valueOf(((Number) raw).doubleValue());
+ }
+ else
+ {
+ try
+ {
+ d = new BigDecimal(String.valueOf(raw).trim());
+ }
+ catch (Exception e)
+ {
+ continue;
+ }
+ }
+ if (d != null && d.signum() > 0)
+ {
+ return d;
+ }
+ }
+ return null;
+ }
+
+ private static Integer firstInt(Map row, String... keys)
+ {
+ BigDecimal d = firstDecimal(row, keys);
+ return d == null ? null : Integer.valueOf(d.intValue());
+ }
+
+ private static Date firstDate(Map row, String... keys)
+ {
+ Object v = firstValue(row, keys);
+ if (v == null)
+ {
+ return null;
+ }
+ if (v instanceof Date)
+ {
+ return (Date) v;
+ }
+ String s = String.valueOf(v).trim();
+ String[] formats = {
+ "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy HH:mm", "yyyy-MM-dd HH:mm:ss",
+ "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd"
+ };
+ for (String fmt : formats)
+ {
+ try
+ {
+ return new SimpleDateFormat(fmt, Locale.ENGLISH).parse(s);
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ return null;
+ }
+
+ private static BigDecimal toVolumeM3(BigDecimal v)
+ {
+ if (v == null)
+ {
+ return null;
+ }
+ if (v.doubleValue() > 10d)
+ {
+ return v.divide(BigDecimal.valueOf(1_000_000L), 6, java.math.RoundingMode.HALF_UP);
+ }
+ return v;
+ }
+
+ private static Object firstValue(Map row, String... keys)
+ {
+ if (row == null || keys == null)
+ {
+ return null;
+ }
+ for (String key : keys)
+ {
+ Object v = getIgnoreCase(row, key);
+ if (v != null && !"".equals(String.valueOf(v).trim()))
+ {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ private static Object getIgnoreCase(Map row, String key)
+ {
+ if (row.containsKey(key))
+ {
+ return row.get(key);
+ }
+ String upper = key.toUpperCase(Locale.ROOT);
+ if (row.containsKey(upper))
+ {
+ return row.get(upper);
+ }
+ String lower = key.toLowerCase(Locale.ROOT);
+ if (row.containsKey(lower))
+ {
+ return row.get(lower);
+ }
+ for (Map.Entry e : row.entrySet())
+ {
+ if (e.getKey() != null && e.getKey().equalsIgnoreCase(key))
+ {
+ return e.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleUpstreamOrderService.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleUpstreamOrderService.java
new file mode 100644
index 00000000..0b433869
--- /dev/null
+++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/oracle/OracleUpstreamOrderService.java
@@ -0,0 +1,125 @@
+package com.ruoyi.price.oracle;
+
+import java.util.Map;
+import java.util.function.Supplier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.github.pagehelper.PageHelper;
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.price.config.BillingProperties;
+import com.ruoyi.price.mapper.OracleUpstreamOrderMapper;
+
+/**
+ * Query Oracle main order via SLAVE datasource.
+ * Requires spring.datasource.druid.slave.enabled=true and Oracle URL.
+ */
+@Service
+public class OracleUpstreamOrderService
+{
+ private static final Logger log = LoggerFactory.getLogger(OracleUpstreamOrderService.class);
+
+ @Autowired
+ private OracleUpstreamOrderMapper oracleUpstreamOrderMapper;
+
+ @Autowired
+ private BillingProperties billingProperties;
+
+ @DataSource(DataSourceType.SLAVE)
+ public Map findMainOrder(String transferNo, String orderNo, String trackingNo)
+ {
+ if (StringUtils.isEmpty(transferNo) && StringUtils.isEmpty(orderNo) && StringUtils.isEmpty(trackingNo))
+ {
+ throw new ServiceException("transferNo / orderNo / trackingNo required for Oracle order query");
+ }
+ String table = billingProperties.getOracle().getOrderTable();
+ if (StringUtils.isEmpty(table))
+ {
+ throw new ServiceException("bms.billing.oracle.order-table is empty");
+ }
+ if (!table.matches("[A-Za-z0-9_\\.]+"))
+ {
+ throw new ServiceException("invalid oracle order table name");
+ }
+
+ String tf = emptyToNull(transferNo);
+ String ono = emptyToNull(orderNo);
+ String track = emptyToNull(trackingNo);
+ try
+ {
+ // Avoid PageHelper wrapping this Oracle query (can slow / break SQL).
+ PageHelper.clearPage();
+
+ // 1) Prefer exact match by transfer no (TRANSFER_ORDER_ID)
+ if (tf != null)
+ {
+ Map byTransfer = query("TRANSFER_ORDER_ID",
+ () -> oracleUpstreamOrderMapper.selectByTransferNo(table, tf));
+ if (!isEmpty(byTransfer))
+ {
+ return byTransfer;
+ }
+ }
+
+ // 2) Fallback: order no
+ Map row = null;
+ if (ono != null)
+ {
+ row = query("ORDER_ID", () -> oracleUpstreamOrderMapper.selectByOrderId(table, ono));
+ }
+ if (isEmpty(row) && ono != null)
+ {
+ row = query("ORG_ORDER_ID", () -> oracleUpstreamOrderMapper.selectByOrgOrderId(table, ono));
+ }
+
+ // 3) Fallback: tracking no
+ if (isEmpty(row) && track != null)
+ {
+ row = query("TRACK_ID", () -> oracleUpstreamOrderMapper.selectByTrackId(table, track));
+ }
+ if (isEmpty(row) && track != null)
+ {
+ row = query("BARCODE_NUMBER", () -> oracleUpstreamOrderMapper.selectByBarcode(table, track));
+ }
+ if (isEmpty(row) && track != null)
+ {
+ row = query("CUSTOMER_TRACK_ID", () -> oracleUpstreamOrderMapper.selectByCustomerTrackId(table, track));
+ }
+ return row;
+ }
+ catch (ServiceException e)
+ {
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ throw new ServiceException("Oracle order query failed: " + e.getMessage());
+ }
+ finally
+ {
+ PageHelper.clearPage();
+ }
+ }
+
+ private Map query(String by, Supplier