增加退费功能
This commit is contained in:
+7
-1
@@ -14,6 +14,13 @@ public interface IPrcOrderBillService
|
||||
{
|
||||
TmsPriceQuoteResult bill(TmsPriceBillRequest request);
|
||||
|
||||
/**
|
||||
* 退费:保留原费用明细,在 prc_income_bill_fee 增加一条负金额(feeType=REFUND),重算应收并回补余额。
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
*/
|
||||
TmsPriceQuoteResult refund(String orderNo);
|
||||
|
||||
List<PrcOrderBill> selectBillList(PrcOrderBill query);
|
||||
|
||||
/** 订单号下拉搜索选项(模糊匹配,去重限量) */
|
||||
@@ -59,4 +66,3 @@ public interface IPrcOrderBillService
|
||||
*/
|
||||
TmsPriceQuoteResult updateCreditArrearsStatus(String billId);
|
||||
}
|
||||
|
||||
|
||||
+78
-11
@@ -120,6 +120,21 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
|
||||
}
|
||||
try
|
||||
{
|
||||
if (task.getBillTime() == null)
|
||||
{
|
||||
throw new ServiceException("task billTime empty (billing time from mobile required)");
|
||||
}
|
||||
BillTaskType type = BillTaskType.fromCode(task.getBillType());
|
||||
if (BillTaskType.REFUND == type)
|
||||
{
|
||||
String refundOrderNo = resolveRefundOrderNo(task);
|
||||
log.info("billing task REFUND path taskId={} orderNo={}", taskId, refundOrderNo);
|
||||
TmsPriceQuoteResult result = refundWithDbRetry(taskId, refundOrderNo);
|
||||
writeback(task, BillTaskSuccessStatus.SUCCESS, result == null ? null : result.getBillId(),
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> row = oracleUpstreamOrderService.findMainOrder(
|
||||
task.getTransferNo(), task.getOrderNo(), task.getTrackingNo());
|
||||
if (row == null || row.isEmpty())
|
||||
@@ -158,19 +173,8 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
|
||||
billReq.setOrderNo(task.getOrderNo());
|
||||
}
|
||||
// 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 = billWithDbRetry(taskId, billReq);
|
||||
writeback(task, BillTaskSuccessStatus.SUCCESS, result == null ? null : result.getBillId(),
|
||||
null);
|
||||
@@ -182,6 +186,69 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve orderNo for REFUND from task keys or existing bill (no Oracle quote required). */
|
||||
private String resolveRefundOrderNo(PrcBillingTask task)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(task.getOrderNo()))
|
||||
{
|
||||
return task.getOrderNo().trim();
|
||||
}
|
||||
if (StringUtils.isNotEmpty(task.getTransferNo()))
|
||||
{
|
||||
PrcOrderBill byTransfer = orderBillMapper.selectPrcOrderBillByTransferNo(task.getTransferNo().trim());
|
||||
if (byTransfer != null && StringUtils.isNotEmpty(byTransfer.getOrderNo()))
|
||||
{
|
||||
return byTransfer.getOrderNo();
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(task.getTrackingNo()))
|
||||
{
|
||||
PrcOrderBill q = new PrcOrderBill();
|
||||
q.setTrackingNo(task.getTrackingNo().trim());
|
||||
List<PrcOrderBill> list = orderBillMapper.selectPrcOrderBillList(q);
|
||||
if (list != null && !list.isEmpty() && StringUtils.isNotEmpty(list.get(0).getOrderNo()))
|
||||
{
|
||||
return list.get(0).getOrderNo();
|
||||
}
|
||||
}
|
||||
throw new ServiceException("退费订单号无法解析");
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult refundWithDbRetry(Long taskId, String orderNo)
|
||||
{
|
||||
RuntimeException last = null;
|
||||
for (int attempt = 1; attempt <= BILL_DB_RETRY_MAX; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return prcOrderBillService.refund(orderNo);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
last = e;
|
||||
if (attempt < BILL_DB_RETRY_MAX && isRetryableDbConflict(e))
|
||||
{
|
||||
long sleepMs = BILL_DB_RETRY_BASE_MS * attempt
|
||||
+ (long) (Math.random() * BILL_DB_RETRY_BASE_MS);
|
||||
log.warn("billing refund db conflict, retry taskId={} attempt={}/{} sleepMs={} msg={}",
|
||||
taskId, attempt, BILL_DB_RETRY_MAX, sleepMs, cut(e.getMessage(), 200));
|
||||
try
|
||||
{
|
||||
Thread.sleep(sleepMs);
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw e;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw last != null ? last : new ServiceException("billing refund retry exhausted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry bill() on MySQL deadlock / lock wait (common when many tasks share one customer_account).
|
||||
*/
|
||||
|
||||
+103
-1
@@ -5,6 +5,7 @@ import java.math.RoundingMode;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -21,6 +22,7 @@ import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.ar.enums.ArIncomeBillFeeStatus;
|
||||
import com.ruoyi.ar.enums.ArOrderBillStatus;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DictUtils;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
@@ -65,6 +67,9 @@ import com.ruoyi.price.task.PrcCreditArrearsClearAsyncRunner;
|
||||
@Service
|
||||
public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
private static final String DICT_FEE_TYPE = "bms_fee_type";
|
||||
private static final String FEE_TYPE_REFUND = "REFUND";
|
||||
|
||||
@Autowired
|
||||
private ITmsPriceQuoteService tmsPriceQuoteService;
|
||||
|
||||
@@ -131,6 +136,102 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退费:不删除原费用明细;在 prc_income_bill_fee 增加一条负金额(feeType=REFUND),
|
||||
* 重算应收合计并按差额回补账户余额。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult refund(String orderNo)
|
||||
{
|
||||
TmsPriceBillRequest failReq = new TmsPriceBillRequest();
|
||||
try
|
||||
{
|
||||
if (StringUtils.isEmpty(orderNo))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.orderNoRequired"));
|
||||
}
|
||||
orderNo = orderNo.trim();
|
||||
failReq.setOrderNo(orderNo);
|
||||
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo);
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.notFound"));
|
||||
}
|
||||
assertOrderBillEditable(bill);
|
||||
|
||||
List<PrcIncomeBillFee> fees = incomeBillFeeMapper.selectByBillId(bill.getBillId());
|
||||
if (fees == null)
|
||||
{
|
||||
fees = new ArrayList<PrcIncomeBillFee>();
|
||||
}
|
||||
for (PrcIncomeBillFee row : fees)
|
||||
{
|
||||
if (row != null && FEE_TYPE_REFUND.equalsIgnoreCase(row.getFeeType()))
|
||||
{
|
||||
throw new ServiceException("订单已退费,不可重复退费");
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal feeSum = BigDecimal.ZERO;
|
||||
for (PrcIncomeBillFee row : fees)
|
||||
{
|
||||
feeSum = feeSum.add(nz(row.getAmount()));
|
||||
}
|
||||
if (feeSum.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
throw new ServiceException("应收总额已为 0,无需退费");
|
||||
}
|
||||
|
||||
PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId());
|
||||
if (incomeBill == null)
|
||||
{
|
||||
incomeBill = persistIncomeBill(bill);
|
||||
}
|
||||
|
||||
PrcIncomeBillFee refundFee = new PrcIncomeBillFee();
|
||||
refundFee.setFeeId(UUID.randomUUID().toString());
|
||||
refundFee.setBillId(bill.getBillId());
|
||||
refundFee.setIncomeBillId(incomeBill == null ? null : incomeBill.getIncomeBillId());
|
||||
String refundFeeType = FEE_TYPE_REFUND;
|
||||
String refundFeeName = DictUtils.getDictLabel(DICT_FEE_TYPE, refundFeeType);
|
||||
if (StringUtils.isEmpty(refundFeeName))
|
||||
{
|
||||
refundFeeName = "退费";
|
||||
}
|
||||
refundFee.setFeeType(refundFeeType);
|
||||
refundFee.setFeeName(refundFeeName);
|
||||
refundFee.setScId(Long.valueOf(0L));
|
||||
refundFee.setAmount(feeSum.negate());
|
||||
refundFee.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency());
|
||||
refundFee.setRoundMode(bill.getRoundMode());
|
||||
refundFee.setRemark("计费任务 REFUND 回补");
|
||||
refundFee.setArBillStatus(ArIncomeBillFeeStatus.UNBILLED.getCode());
|
||||
refundFee.setManualFlag("0");
|
||||
incomeBillFeeMapper.insertPrcIncomeBillFee(refundFee);
|
||||
|
||||
TmsPriceQuoteResult result = refreshBillIncomeTotals(bill);
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "REFUND");
|
||||
req.put("orderNo", orderNo);
|
||||
req.put("billId", bill.getBillId());
|
||||
req.put("refundAmount", feeSum.negate());
|
||||
req.put("feeId", refundFee.getFeeId());
|
||||
if (billSearchStore != null)
|
||||
{
|
||||
billSearchStore.save(EsLogType.BILL, orderNo, req.toJSONString(), toResponseJson(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.BILL,
|
||||
EsBizLogSupport.billFailBizId(failReq), failReq, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrcOrderBill> selectBillList(PrcOrderBill query)
|
||||
{
|
||||
@@ -392,7 +493,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
BigDecimal amt = nz(row.getAmount());
|
||||
incomeTotal = incomeTotal.add(amt);
|
||||
if (!"BASIC_FREIGHT".equalsIgnoreCase(row.getFeeType())
|
||||
&& !"REGISTRATION_FEE".equalsIgnoreCase(row.getFeeType()))
|
||||
&& !"REGISTRATION_FEE".equalsIgnoreCase(row.getFeeType())
|
||||
&& !FEE_TYPE_REFUND.equalsIgnoreCase(row.getFeeType()))
|
||||
{
|
||||
surchargeTotal = surchargeTotal.add(amt);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
ar_bill_status, manual_flag, create_time
|
||||
from prc_income_bill_fee
|
||||
where bill_id = #{billId}
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectByBillIds" resultMap="PrcIncomeBillFeeResult">
|
||||
@@ -36,7 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectUnbilledByBillIds" resultMap="PrcIncomeBillFeeResult">
|
||||
@@ -48,7 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectByFeeId" parameterType="String" resultMap="PrcIncomeBillFeeResult">
|
||||
|
||||
@@ -22,7 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
from prc_pay_bill_fee
|
||||
where bill_id = #{billId}
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcPayBillFee" parameterType="PrcPayBillFee">
|
||||
|
||||
@@ -21,7 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select fee_id, quote_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
from prc_quote_fee
|
||||
where quote_id = #{quoteId}
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcQuoteFee" parameterType="PrcQuoteFee">
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- bms_fee_type: add REFUND (refund fee type)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
SELECT N'费用类型', 'bms_fee_type', '0', 'admin', sysdate(), N'计费/附加费费用类型'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'bms_fee_type');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 1, N'基础运费', 'BASIC_FREIGHT', 'bms_fee_type', '', 'primary', 'N', '0', 'admin', sysdate(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'bms_fee_type' AND dict_value = 'BASIC_FREIGHT');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 2, N'挂号费', 'REGISTRATION_FEE', 'bms_fee_type', '', 'info', 'N', '0', 'admin', sysdate(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'bms_fee_type' AND dict_value = 'REGISTRATION_FEE');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 3, N'附加费', 'SURCHARGE', 'bms_fee_type', '', 'warning', 'N', '0', 'admin', sysdate(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'bms_fee_type' AND dict_value = 'SURCHARGE');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 4, N'退费', 'REFUND', 'bms_fee_type', '', 'danger', 'N', '0', 'admin', sysdate(), N'计费任务 billType=REFUND 负金额明细'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'bms_fee_type' AND dict_value = 'REFUND');
|
||||
Reference in New Issue
Block a user