修改订单同步
This commit is contained in:
+2
-1
@@ -607,7 +607,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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -21,6 +21,8 @@ public class PrcOrderBill extends BaseEntity
|
||||
/** 批量查询:多个订单号精确匹配(仅查询用,非表字段) */
|
||||
private List<String> 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<PrcIncomeBillFee> incomeFees;
|
||||
|
||||
+28
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+23
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
@@ -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<String, Object> selectByTransferNo(@Param("orderTable") String orderTable, @Param("transferNo") String transferNo);
|
||||
|
||||
Map<String, Object> selectByOrderId(@Param("orderTable") String orderTable, @Param("orderNo") String orderNo);
|
||||
|
||||
Map<String, Object> selectByOrgOrderId(@Param("orderTable") String orderTable, @Param("orderNo") String orderNo);
|
||||
|
||||
Map<String, Object> selectByTrackId(@Param("orderTable") String orderTable, @Param("trackingNo") String trackingNo);
|
||||
|
||||
Map<String, Object> selectByBarcode(@Param("orderTable") String orderTable, @Param("trackingNo") String trackingNo);
|
||||
|
||||
Map<String, Object> selectByCustomerTrackId(@Param("orderTable") String orderTable, @Param("trackingNo") String trackingNo);
|
||||
}
|
||||
@@ -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<PrcBillingTask> selectPrcBillingTaskList(PrcBillingTask query);
|
||||
|
||||
int insertPrcBillingTask(PrcBillingTask task);
|
||||
|
||||
int updatePrcBillingTask(PrcBillingTask task);
|
||||
|
||||
/** CAS: WAITING -> processing claim not needed; writeback SUCCESS/FAILED */
|
||||
int updateBillingResult(PrcBillingTask task);
|
||||
}
|
||||
@@ -11,6 +11,8 @@ public interface PrcOrderBillMapper
|
||||
{
|
||||
public PrcOrderBill selectPrcOrderBillByOrderNo(String orderNo);
|
||||
|
||||
public PrcOrderBill selectPrcOrderBillByTransferNo(String transferNo);
|
||||
|
||||
public PrcOrderBill selectPrcOrderBillById(String billId);
|
||||
|
||||
public List<PrcOrderBill> selectPrcOrderBillList(PrcOrderBill bill);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
+27
@@ -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));
|
||||
}
|
||||
}
|
||||
+38
@@ -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);
|
||||
}
|
||||
}
|
||||
+231
@@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> row, String... keys)
|
||||
{
|
||||
BigDecimal d = firstDecimal(row, keys);
|
||||
return d == null ? null : Integer.valueOf(d.intValue());
|
||||
}
|
||||
|
||||
private static Date firstDate(Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> e : row.entrySet())
|
||||
{
|
||||
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key))
|
||||
{
|
||||
return e.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+125
@@ -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<String, Object> 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<String, Object> byTransfer = query("TRANSFER_ORDER_ID",
|
||||
() -> oracleUpstreamOrderMapper.selectByTransferNo(table, tf));
|
||||
if (!isEmpty(byTransfer))
|
||||
{
|
||||
return byTransfer;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Fallback: order no
|
||||
Map<String, Object> 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<String, Object> query(String by, Supplier<Map<String, Object>> supplier)
|
||||
{
|
||||
long t0 = System.currentTimeMillis();
|
||||
Map<String, Object> row = supplier.get();
|
||||
log.info("oracle order lookup by={} hit={} costMs={}", by, !isEmpty(row), System.currentTimeMillis() - t0);
|
||||
return row;
|
||||
}
|
||||
|
||||
private static boolean isEmpty(Map<String, Object> row)
|
||||
{
|
||||
return row == null || row.isEmpty();
|
||||
}
|
||||
|
||||
private static String emptyToNull(String s)
|
||||
{
|
||||
return StringUtils.isEmpty(s) ? null : s.trim();
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.price.service;
|
||||
|
||||
import com.ruoyi.price.domain.PrcBillingTask;
|
||||
import com.ruoyi.price.domain.vo.BillingTaskSubmitRequest;
|
||||
|
||||
public interface IPrcBillingTaskService
|
||||
{
|
||||
/** Warehouse inbound: create task + publish MQ. */
|
||||
PrcBillingTask submitInbound(BillingTaskSubmitRequest request);
|
||||
|
||||
/** MQ consumer entry: Oracle query -> bill -> writeback success status. */
|
||||
void processTask(Long taskId);
|
||||
|
||||
PrcBillingTask getById(Long taskId);
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
package com.ruoyi.price.service.impl;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.price.config.BillingProperties;
|
||||
import com.ruoyi.price.domain.PrcBillingTask;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
import com.ruoyi.price.domain.vo.BillingTaskSubmitRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.enums.BillTaskSuccessStatus;
|
||||
import com.ruoyi.price.enums.BillTaskType;
|
||||
import com.ruoyi.price.mapper.PrcBillingTaskMapper;
|
||||
import com.ruoyi.price.mapper.PrcOrderBillMapper;
|
||||
import com.ruoyi.price.mq.BillingTaskMessage;
|
||||
import com.ruoyi.price.mq.IBillingTaskMqPublisher;
|
||||
import com.ruoyi.price.oracle.OracleOrderBillMapper;
|
||||
import com.ruoyi.price.oracle.OracleUpstreamOrderService;
|
||||
import com.ruoyi.price.service.IPrcBillingTaskService;
|
||||
import com.ruoyi.price.service.IPrcOrderBillService;
|
||||
|
||||
@Service
|
||||
public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(PrcBillingTaskServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private PrcBillingTaskMapper billingTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcOrderBillMapper orderBillMapper;
|
||||
|
||||
@Autowired
|
||||
private IBillingTaskMqPublisher billingTaskMqPublisher;
|
||||
|
||||
@Autowired
|
||||
private OracleUpstreamOrderService oracleUpstreamOrderService;
|
||||
|
||||
@Autowired
|
||||
private OracleOrderBillMapper oracleOrderBillMapper;
|
||||
|
||||
@Autowired
|
||||
private IPrcOrderBillService prcOrderBillService;
|
||||
|
||||
@Autowired
|
||||
private BillingProperties billingProperties;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PrcBillingTask submitInbound(BillingTaskSubmitRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ServiceException("billing task request required");
|
||||
}
|
||||
String transferNo = trim(request.getTransferNo());
|
||||
String trackingNo = trim(request.getTrackingNo());
|
||||
String orderNo = trim(request.getOrderNo());
|
||||
Date billTime = request.getBillTime();
|
||||
if (StringUtils.isEmpty(transferNo) && StringUtils.isEmpty(trackingNo) && StringUtils.isEmpty(orderNo))
|
||||
{
|
||||
throw new ServiceException("transferNo or trackingNo or orderNo required");
|
||||
}
|
||||
if (billTime == null)
|
||||
{
|
||||
throw new ServiceException("billTime required (billing time from mobile)");
|
||||
}
|
||||
|
||||
BillTaskType type = BillTaskType.fromCode(request.getBillType());
|
||||
if (type == null)
|
||||
{
|
||||
type = resolveAutoType(transferNo, trackingNo, orderNo);
|
||||
}
|
||||
|
||||
PrcBillingTask task = new PrcBillingTask();
|
||||
task.setTransferNo(transferNo);
|
||||
task.setTrackingNo(trackingNo);
|
||||
task.setOrderNo(orderNo);
|
||||
task.setBillTime(billTime);
|
||||
task.setBillType(type.getCode());
|
||||
task.setSuccessStatus(BillTaskSuccessStatus.WAITING.getCode());
|
||||
task.setCreateBy("tms");
|
||||
billingTaskMapper.insertPrcBillingTask(task);
|
||||
|
||||
billingTaskMqPublisher.publish(new BillingTaskMessage(task.getTaskId()));
|
||||
log.info("billing task submitted taskId={} type={} billTime={} transferNo={} trackingNo={} orderNo={}",
|
||||
task.getTaskId(), task.getBillType(), billTime, transferNo, trackingNo, orderNo);
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTask(Long taskId)
|
||||
{
|
||||
if (taskId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PrcBillingTask task = billingTaskMapper.selectPrcBillingTaskById(taskId);
|
||||
if (task == null)
|
||||
{
|
||||
log.warn("billing task not found taskId={}", taskId);
|
||||
return;
|
||||
}
|
||||
if (BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()))
|
||||
{
|
||||
log.info("billing task already success taskId={}", taskId);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Map<String, Object> row = oracleUpstreamOrderService.findMainOrder(
|
||||
task.getTransferNo(), task.getOrderNo(), task.getTrackingNo());
|
||||
if (row == null || row.isEmpty())
|
||||
{
|
||||
throw new ServiceException("Oracle main order not found");
|
||||
}
|
||||
TmsPriceBillRequest billReq = oracleOrderBillMapper.toBillRequest(row,
|
||||
billingProperties.getOracle().getDefaultProductCode());
|
||||
if (billReq == null || StringUtils.isEmpty(billReq.getOrderNo()))
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped orderNo empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(billReq.getCustomerCode()))
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped customerCode empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(billReq.getProductCode()))
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped productCode empty");
|
||||
}
|
||||
if (billReq.getWeight() == null || billReq.getWeight().signum() <= 0)
|
||||
{
|
||||
throw new ServiceException("Oracle order mapped weight invalid");
|
||||
}
|
||||
// Prefer task keys when Oracle mapping missed them
|
||||
if (StringUtils.isEmpty(billReq.getTransferNo()) && StringUtils.isNotEmpty(task.getTransferNo()))
|
||||
{
|
||||
billReq.setTransferNo(task.getTransferNo());
|
||||
}
|
||||
if (StringUtils.isEmpty(billReq.getTrackingNo()) && StringUtils.isNotEmpty(task.getTrackingNo()))
|
||||
{
|
||||
billReq.setTrackingNo(task.getTrackingNo());
|
||||
}
|
||||
// Billing date from task.bill_time (mobile), not Oracle CREATE_DATE.
|
||||
if (task.getBillTime() == null)
|
||||
{
|
||||
throw new ServiceException("task billTime empty (billing time from mobile required)");
|
||||
}
|
||||
billReq.setBizDate(new SimpleDateFormat("yyyy-MM-dd").format(task.getBillTime()));
|
||||
|
||||
BillTaskType type = BillTaskType.fromCode(task.getBillType());
|
||||
if (BillTaskType.REFUND == type)
|
||||
{
|
||||
// V1: refund still syncs latest Oracle snapshot into bill (business refine later).
|
||||
log.info("billing task REFUND path uses bill upsert taskId={}", taskId);
|
||||
}
|
||||
|
||||
TmsPriceQuoteResult result = prcOrderBillService.bill(billReq);
|
||||
writeback(task, BillTaskSuccessStatus.SUCCESS, result == null ? null : result.getBillId(),
|
||||
billReq, null);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
log.error("billing task failed taskId={}", taskId, e);
|
||||
writeback(task, BillTaskSuccessStatus.FAILED, null, null, cut(e.getMessage(), 900));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrcBillingTask getById(Long taskId)
|
||||
{
|
||||
return billingTaskMapper.selectPrcBillingTaskById(taskId);
|
||||
}
|
||||
|
||||
private BillTaskType resolveAutoType(String transferNo, String trackingNo, String orderNo)
|
||||
{
|
||||
PrcOrderBill existed = null;
|
||||
if (StringUtils.isNotEmpty(orderNo))
|
||||
{
|
||||
existed = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo);
|
||||
}
|
||||
if (existed == null && StringUtils.isNotEmpty(transferNo))
|
||||
{
|
||||
existed = orderBillMapper.selectPrcOrderBillByTransferNo(transferNo);
|
||||
}
|
||||
if (existed == null && StringUtils.isNotEmpty(trackingNo))
|
||||
{
|
||||
PrcOrderBill q = new PrcOrderBill();
|
||||
q.setTrackingNo(trackingNo);
|
||||
java.util.List<PrcOrderBill> list = orderBillMapper.selectPrcOrderBillList(q);
|
||||
if (list != null && !list.isEmpty())
|
||||
{
|
||||
existed = list.get(0);
|
||||
}
|
||||
}
|
||||
return existed == null ? BillTaskType.PENDING : BillTaskType.REBILL;
|
||||
}
|
||||
|
||||
private void writeback(PrcBillingTask task, BillTaskSuccessStatus status, String billId,
|
||||
TmsPriceBillRequest billReq, String errorMessage)
|
||||
{
|
||||
PrcBillingTask upd = new PrcBillingTask();
|
||||
upd.setTaskId(task.getTaskId());
|
||||
upd.setSuccessStatus(status.getCode());
|
||||
// Keep bill_time as mobile billing time; do not overwrite on writeback.
|
||||
upd.setBillId(billId);
|
||||
upd.setErrorMessage(errorMessage);
|
||||
if (billReq != null)
|
||||
{
|
||||
upd.setOrderNo(billReq.getOrderNo());
|
||||
upd.setTrackingNo(billReq.getTrackingNo());
|
||||
upd.setTransferNo(billReq.getTransferNo());
|
||||
}
|
||||
billingTaskMapper.updateBillingResult(upd);
|
||||
}
|
||||
|
||||
private static String trim(String s)
|
||||
{
|
||||
return s == null ? null : s.trim();
|
||||
}
|
||||
|
||||
private static String cut(String s, int max)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return s.length() <= max ? s : s.substring(0, max);
|
||||
}
|
||||
}
|
||||
+75
-10
@@ -27,6 +27,8 @@ import com.ruoyi.price.domain.PrcPayBillFee;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteFeeLine;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.enums.BillCreditArrearsStatus;
|
||||
import com.ruoyi.price.enums.BillCustomerActiveStatus;
|
||||
import com.ruoyi.price.mapper.PrcIncomeBillFeeMapper;
|
||||
import com.ruoyi.price.mapper.PrcOrderBillExtMapper;
|
||||
import com.ruoyi.price.mapper.PrcOrderBillMapper;
|
||||
@@ -37,6 +39,7 @@ import com.ruoyi.price.search.IBillSearchStore;
|
||||
import com.ruoyi.customer.customermoney.api.CreditUsageCommand;
|
||||
import com.ruoyi.customer.customermoney.api.CustomerFundBizType;
|
||||
import com.ruoyi.customer.customermoney.api.ICustomerAccountApi;
|
||||
import com.ruoyi.customer.customermoney.vo.CustomerAccountStatusVO;
|
||||
import com.ruoyi.price.service.IPrcOrderBillService;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
@@ -192,6 +195,13 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
bill.setCustomerCode(customerCode);
|
||||
bill.setCustomerName(request.getCustomerName());
|
||||
bill.setTrackingNo(request.getTrackingNo());
|
||||
bill.setTransferNo(request.getTransferNo());
|
||||
String customerActiveStatus = result == null ? null : result.getCustomerActiveStatus();
|
||||
if (StringUtils.isEmpty(customerActiveStatus))
|
||||
{
|
||||
customerActiveStatus = BillCustomerActiveStatus.ACTIVE.getCode();
|
||||
}
|
||||
bill.setCustomerActiveStatus(customerActiveStatus);
|
||||
bill.setPieces(request.getPieces());
|
||||
bill.setActualWeight(request.getActualWeight());
|
||||
bill.setVolume(request.getVolume());
|
||||
@@ -236,10 +246,16 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
bill.setPayTotal(result.getPayTotal() == null ? BigDecimal.ZERO : result.getPayTotal());
|
||||
bill.setCurrency(result.getCurrency());
|
||||
bill.setRoundMode(result.getRoundMode());
|
||||
// Credit soft-fail before persist so insert/update stores arrears flag in one write.
|
||||
String creditArrearsStatus = syncOrderCredit(bill, previousIncome, customerActiveStatus);
|
||||
bill.setCreditArrearsStatus(creditArrearsStatus);
|
||||
result.setBillId(bill.getBillId());
|
||||
result.setOrderNo(orderNo);
|
||||
result.setTransferNo(bill.getTransferNo());
|
||||
result.setCustomerId(bill.getCustomerId());
|
||||
result.setCustomerCode(bill.getCustomerCode());
|
||||
result.setCustomerActiveStatus(customerActiveStatus);
|
||||
result.setCreditArrearsStatus(creditArrearsStatus);
|
||||
result.setIncomeTotal(bill.getIncomeTotal());
|
||||
result.setPayTotal(bill.getPayTotal());
|
||||
result.setPayableTotal(bill.getIncomeTotal());
|
||||
@@ -263,33 +279,40 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
String syncJson = toRequestJson(request);
|
||||
persistBillExt(bill, syncJson);
|
||||
result.setRequestJson(syncJson);
|
||||
syncOrderCredit(bill, previousIncome);
|
||||
billSearchStore.save(EsLogType.BILL, orderNo, syncJson, toResponseJson(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Occupy/release credit by income total delta (bizType=ORDER).
|
||||
* Always write CREDIT log to ES (SUCCESS / FAIL), then rethrow on failure.
|
||||
* Missing/disabled customer: skip occupy, return NORMAL.
|
||||
* Insufficient credit: still occupy with checkAvailable=false, return ARREARS.
|
||||
* Always write CREDIT log to ES (SUCCESS / FAIL / SKIP).
|
||||
*/
|
||||
private void syncOrderCredit(PrcOrderBill bill, BigDecimal previousIncome)
|
||||
private String syncOrderCredit(PrcOrderBill bill, BigDecimal previousIncome, String customerActiveStatus)
|
||||
{
|
||||
String normal = BillCreditArrearsStatus.NORMAL.getCode();
|
||||
String arrears = BillCreditArrearsStatus.ARREARS.getCode();
|
||||
if (bill == null)
|
||||
{
|
||||
return;
|
||||
return normal;
|
||||
}
|
||||
if (!BillCustomerActiveStatus.ACTIVE.getCode().equals(customerActiveStatus))
|
||||
{
|
||||
saveCreditEsLog(bill, null, "SKIP", true, "customerActiveStatus=" + customerActiveStatus);
|
||||
return normal;
|
||||
}
|
||||
Long customerId = bill.getCustomerId();
|
||||
if (customerId == null || customerId.longValue() <= 0L)
|
||||
{
|
||||
ServiceException ex = new ServiceException(MessageUtils.message("price.bill.customerRequired"));
|
||||
saveCreditEsLog(bill, null, "SKIP", false, EsBizLogSupport.errorText(ex));
|
||||
throw ex;
|
||||
saveCreditEsLog(bill, null, "SKIP", true, "customerId missing");
|
||||
return normal;
|
||||
}
|
||||
BigDecimal oldAmt = nz(previousIncome);
|
||||
BigDecimal newAmt = nz(bill.getIncomeTotal());
|
||||
BigDecimal delta = newAmt.subtract(oldAmt);
|
||||
if (delta.compareTo(BigDecimal.ZERO) == 0)
|
||||
{
|
||||
return;
|
||||
return normal;
|
||||
}
|
||||
CreditUsageCommand cmd = new CreditUsageCommand();
|
||||
cmd.setCustomerId(customerId);
|
||||
@@ -297,8 +320,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
cmd.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency());
|
||||
cmd.setBizType(CustomerFundBizType.ORDER);
|
||||
cmd.setBizNo(bill.getOrderNo());
|
||||
cmd.setCheckAvailable(Boolean.TRUE);
|
||||
// Soft: allow used_credit to exceed available; mark ARREARS on the bill instead of failing.
|
||||
cmd.setCheckAvailable(Boolean.FALSE);
|
||||
String action = delta.compareTo(BigDecimal.ZERO) > 0 ? "OCCUPY" : "RELEASE";
|
||||
boolean markArrears = false;
|
||||
if (delta.compareTo(BigDecimal.ZERO) > 0)
|
||||
{
|
||||
markArrears = isCreditInsufficient(customerId, bill.getCustomerCode(), delta);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (delta.compareTo(BigDecimal.ZERO) > 0)
|
||||
@@ -311,15 +340,44 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
cmd.setAmount(delta.abs());
|
||||
customerAccountApi.releaseCredit(cmd);
|
||||
}
|
||||
saveCreditEsLog(bill, cmd, action, true, null);
|
||||
saveCreditEsLog(bill, cmd, action, true, markArrears ? "ARREARS" : null);
|
||||
return markArrears ? arrears : normal;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
// Unexpected occupy/release failure (lock conflict etc.): fail the bill txn.
|
||||
saveCreditEsLog(bill, cmd, action, false, EsBizLogSupport.errorText(e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/** True when available credit cannot cover this occupy delta (or already overdue). */
|
||||
private boolean isCreditInsufficient(Long customerId, String customerCode, BigDecimal delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
CustomerAccountStatusVO status = customerAccountApi.getAccountStatus(customerId, customerCode);
|
||||
if (status == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (status.isOverdue())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
BigDecimal available = status.getAvailableCredit();
|
||||
if (available == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return available.compareTo(nz(delta)) < 0;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** CREDIT ES log; does not set ATTR_FAIL_LOGGED so BILL fail can still be written. */
|
||||
private void saveCreditEsLog(PrcOrderBill bill, CreditUsageCommand cmd, String action, boolean success,
|
||||
String errorMessage)
|
||||
@@ -352,6 +410,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
resp.put("msg", errorMessage);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(errorMessage) && success)
|
||||
{
|
||||
resp.put("note", errorMessage);
|
||||
}
|
||||
String status = success ? EsBizLogSupport.STATUS_SUCCESS : EsBizLogSupport.STATUS_FAIL;
|
||||
String err = success ? null : EsBizLogSupport.cut(errorMessage);
|
||||
billSearchStore.save(EsLogType.CREDIT, bill.getOrderNo(), req.toJSONString(), resp.toJSONString(), status, err);
|
||||
@@ -529,7 +591,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
private void fillResultOrder(TmsPriceQuoteResult r, PrcOrderBill bill)
|
||||
{
|
||||
r.setTrackingNo(bill.getTrackingNo());
|
||||
r.setTransferNo(bill.getTransferNo());
|
||||
r.setCustomerName(bill.getCustomerName());
|
||||
r.setCustomerActiveStatus(bill.getCustomerActiveStatus());
|
||||
r.setCreditArrearsStatus(bill.getCreditArrearsStatus());
|
||||
r.setPieces(bill.getPieces());
|
||||
r.setActualWeight(bill.getActualWeight());
|
||||
r.setVolume(bill.getVolume());
|
||||
|
||||
+46
-1
@@ -39,6 +39,7 @@ import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteFeeLine;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.enums.BillCustomerActiveStatus;
|
||||
import com.ruoyi.price.mapper.BmsProductCountryMapper;
|
||||
import com.ruoyi.price.mapper.BmsProductMapper;
|
||||
import com.ruoyi.price.mapper.PrcCustomerPriceSetMapper;
|
||||
@@ -163,10 +164,12 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
public TmsPriceQuoteResult calculate(TmsPriceQuoteRequest request)
|
||||
{
|
||||
validateRequest(request);
|
||||
resolveCustomer(request);
|
||||
// Bill path: missing/disabled customer only marks status, pricing still continues.
|
||||
String customerActiveStatus = softResolveCustomer(request);
|
||||
Date bizDate = resolveBizDate(request.getBizDate());
|
||||
TmsPriceQuoteResult result = doQuote(request, bizDate);
|
||||
applySurchargesAndFees(result);
|
||||
result.setCustomerActiveStatus(customerActiveStatus);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -359,6 +362,48 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.customerDisabled"));
|
||||
}
|
||||
applyResolvedCustomer(request, hit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft resolve for billing: never throws on missing/disabled customer.
|
||||
* Returns ACTIVE / MISSING / DISABLED. Quote path still uses hard {@link #resolveCustomer}.
|
||||
*/
|
||||
private String softResolveCustomer(TmsPriceQuoteRequest request)
|
||||
{
|
||||
if (request == null || StringUtils.isEmpty(request.getCustomerCode()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.customerCodeRequired"));
|
||||
}
|
||||
String code = request.getCustomerCode().trim();
|
||||
if (StringUtils.isEmpty(code))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.customerCodeRequired"));
|
||||
}
|
||||
request.setCustomerCode(code);
|
||||
BmsCustomer query = new BmsCustomer();
|
||||
query.setCustomerCode(code);
|
||||
List<BmsCustomer> list = bmsCustomerMapper.selectBmsCustomerList(query);
|
||||
if (list == null || list.isEmpty())
|
||||
{
|
||||
request.setCustomerId(null);
|
||||
return BillCustomerActiveStatus.MISSING.getCode();
|
||||
}
|
||||
BmsCustomer hit = list.get(0);
|
||||
applyResolvedCustomer(request, hit);
|
||||
if ("1".equals(hit.getIsEnable()))
|
||||
{
|
||||
return BillCustomerActiveStatus.DISABLED.getCode();
|
||||
}
|
||||
return BillCustomerActiveStatus.ACTIVE.getCode();
|
||||
}
|
||||
|
||||
private void applyResolvedCustomer(TmsPriceQuoteRequest request, BmsCustomer hit)
|
||||
{
|
||||
if (request == null || hit == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
request.setCustomerId(hit.getCustomerId());
|
||||
if (StringUtils.isNotEmpty(hit.getCustomerCode()))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user