From c038621b3c4852a5b4699355cd7a606f0153960e Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Wed, 23 Sep 2026 11:37:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9B=B4=E6=96=B0=E6=AC=A0?= =?UTF-8?q?=E8=B4=B9=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CustomerMoneyServiceImpl.java | 12 ++ .../price/mapper/PrcOrderBillMapper.java | 10 ++ .../com/ruoyi/price/search/EsLogType.java | 2 + .../price/service/IPrcOrderBillService.java | 21 +++ .../service/impl/PrcOrderBillServiceImpl.java | 148 ++++++++++++++++++ .../PrcCreditArrearsClearAsyncRunner.java | 44 ++++++ .../mapper/price/PrcOrderBillMapper.xml | 16 ++ .../product/components/ProductFormDialog.vue | 20 +-- .../src/views/price/basic/product/index.vue | 20 +-- 9 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/PrcCreditArrearsClearAsyncRunner.java diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java index 9fee29f5..87868983 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java @@ -32,6 +32,7 @@ import com.ruoyi.customer.customermoney.mapper.CustomerCreditApplyMapper; import com.ruoyi.customer.customermoney.mapper.CustomerMoneyMapper; import com.ruoyi.customer.customermoney.service.ICreditQuotaService; import com.ruoyi.customer.customermoney.service.ICustomerMoneyService; +import com.ruoyi.price.service.IPrcOrderBillService; import com.ruoyi.system.domain.BmsExchangeRate; import com.ruoyi.system.service.IBmsExchangeRateService; @@ -60,6 +61,9 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService @Autowired private BmsCustomerMapper bmsCustomerMapper; + @Autowired + private IPrcOrderBillService orderBillService; + @Autowired private IBmsExchangeRateService exchangeRateService; @@ -321,6 +325,14 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService fundCmd.setRemark("收款单审核通过:" + receipt.getReceiptNo()); customerAccountApi.refundBalance(fundCmd); + // 余额入账后:按 FIFO 清除订单欠费状态(ARREARS -> NORMAL) + BmsCustomer customer = bmsCustomerMapper.selectBmsCustomerByCustomerId(receipt.getCustomerId()); + if (customer != null && StringUtils.hasText(customer.getCustomerCode()) + && baseAmount != null && baseAmount.compareTo(BigDecimal.ZERO) > 0) + { + orderBillService.clearCreditArrearsByAmount(customer.getCustomerCode().trim(), baseAmount); + } + // 2. 收款单置为已确认 receipt.setStatus(CustomerReceipt.STATUS_CONFIRMED); receipt.setConfirmedAt(new Date()); 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 e7c9edc5..e959f08b 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 @@ -88,6 +88,16 @@ public interface PrcOrderBillMapper public int updatePrcOrderBill(PrcOrderBill bill); + + /** 根据客户代码+欠费状态查询订单,按下单时间升序(核销清欠用) */ + List selectArrearsByCustomerCode(@Param("customerCode") String customerCode, + @Param("creditArrearsStatus") String creditArrearsStatus); + + /** 更新订单额度欠费状态 */ + int updateCreditArrearsStatus(@Param("billId") String billId, + @Param("creditArrearsStatus") String creditArrearsStatus, + @Param("updateBy") String updateBy); + public int updateOrderBillAmounts(@Param("billId") String billId, @Param("surchargeAmount") BigDecimal surchargeAmount, @Param("incomeTotal") BigDecimal incomeTotal, diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/search/EsLogType.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/search/EsLogType.java index 83491128..4a1522ff 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/search/EsLogType.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/search/EsLogType.java @@ -10,6 +10,8 @@ public final class EsLogType public static final String QUOTE = "QUOTE"; /** Order credit occupy/release */ public static final String CREDIT = "CREDIT"; + /** Clear order credit arrears after balance in */ + public static final String ARREARS = "ARREARS"; private EsLogType() { diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IPrcOrderBillService.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IPrcOrderBillService.java index ea0f94e4..1678f4f2 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IPrcOrderBillService.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IPrcOrderBillService.java @@ -1,5 +1,6 @@ package com.ruoyi.price.service; +import java.math.BigDecimal; import java.util.List; import com.ruoyi.price.domain.PrcOrderBill; import com.ruoyi.price.domain.vo.AddIncomeBillFeeRequest; @@ -39,4 +40,24 @@ public interface IPrcOrderBillService /** 提供给 TMS 判断订单是否可以继续处理的客户状态查询。 */ TmsOrderBillStatusResult queryTmsOrderBillStatus(String identifier); + + /** + * 余额充值等审核入账后调用(异步):按客户代码查询欠费(ARREARS)订单, + * 按下单时间升序依次更为 NORMAL;更新日志写入 ES(logType=ARREARS)。 + * + * @param customerCode 客户代码 + * @param amount 入账金额(依次抵扣订单 income_total,通常为本位币CNY) + */ + void clearCreditArrearsByAmount(String customerCode, BigDecimal amount); + + /** + * 异步执行体:实际清欠并写 ES日志(供异步 Runner 调用)。 + * + * @return 已更新为 NORMAL 的订单数 + */ + int processClearCreditArrearsByAmount(String customerCode, BigDecimal amount); + + /** 异步清欠失败时写 ES 摘要日志 */ + void logClearCreditArrearsFail(String customerCode, BigDecimal amount, String errorMessage); } + diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java index 67fa1392..74a1fa42 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcOrderBillServiceImpl.java @@ -14,6 +14,8 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.ar.enums.ArIncomeBillFeeStatus; @@ -55,6 +57,7 @@ import com.ruoyi.customer.customermoney.vo.CustomerAccountStatusVO; import com.ruoyi.price.service.IPrcOrderBillService; import com.ruoyi.price.service.IPrcSurchargeService; import com.ruoyi.price.service.ITmsPriceQuoteService; +import com.ruoyi.price.task.PrcCreditArrearsClearAsyncRunner; @Service public class PrcOrderBillServiceImpl implements IPrcOrderBillService @@ -89,6 +92,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService @Autowired private IPrcSurchargeService surchargeService; + @Autowired + private PrcCreditArrearsClearAsyncRunner creditArrearsClearAsyncRunner; + @Override @Transactional(rollbackFor = Exception.class) public TmsPriceQuoteResult bill(TmsPriceBillRequest request) @@ -489,6 +495,148 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService return result; } + /** + * 余额充值等审核入账后调用:校验参数后投递异步清欠(数据量大时避免阻塞审核事务)。 + */ + @Override + public void clearCreditArrearsByAmount(String customerCode, BigDecimal amount) + { + String code = StringUtils.trim(customerCode); + if (StringUtils.isEmpty(code)) + { + throw new ServiceException("客户代码不能为空"); + } + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) + { + throw new ServiceException("入账金额必须大于0"); + } + // 审核事务提交后再投递异步,避免回滚后误清欠 + if (TransactionSynchronizationManager.isSynchronizationActive()) + { + final String asyncCode = code; + final BigDecimal asyncAmount = amount; + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() + { + @Override + public void afterCommit() + { + creditArrearsClearAsyncRunner.run(asyncCode, asyncAmount); + } + }); + } + else + { + creditArrearsClearAsyncRunner.run(code, amount); + } + } + + /** + * 异步执行:按下单时间升序 FIFO 清欠,逐单与摘要日志写入 ES。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int processClearCreditArrearsByAmount(String customerCode, BigDecimal amount) + { + String code = StringUtils.trim(customerCode); + if (StringUtils.isEmpty(code)) + { + throw new ServiceException("客户代码不能为空"); + } + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) + { + throw new ServiceException("入账金额必须大于0"); + } + BigDecimal remaining = amount; + List bills = orderBillMapper.selectArrearsByCustomerCode(code, + BillCreditArrearsStatus.ARREARS.getCode()); + if (bills == null || bills.isEmpty()) + { + saveArrearsClearSummaryEsLog(code, amount, 0, remaining, true, "no arrears orders"); + return 0; + } + String normal = BillCreditArrearsStatus.NORMAL.getCode(); + int updated = 0; + for (PrcOrderBill bill : bills) + { + BigDecimal orderAmt = nz(bill.getIncomeTotal()); + // 当前订单额度大于剩余金额:停止更新 + if (orderAmt.compareTo(remaining) > 0) + { + break; + } + orderBillMapper.updateCreditArrearsStatus(bill.getBillId(), normal, "receipt"); + remaining = remaining.subtract(orderAmt); + updated++; + saveArrearsClearOrderEsLog(bill, amount, orderAmt, remaining, true, null); + if (remaining.compareTo(BigDecimal.ZERO) <= 0) + { + break; + } + } + saveArrearsClearSummaryEsLog(code, amount, updated, remaining, true, null); + return updated; + } + + @Override + public void logClearCreditArrearsFail(String customerCode, BigDecimal amount, String errorMessage) + { + saveArrearsClearSummaryEsLog(StringUtils.trim(customerCode), amount, 0, amount, false, errorMessage); + } + + private void saveArrearsClearSummaryEsLog(String customerCode, BigDecimal amount, int updated, + BigDecimal remaining, boolean success, String note) + { + if (billSearchStore == null || StringUtils.isEmpty(customerCode)) + { + return; + } + JSONObject req = new JSONObject(); + req.put("action", "CLEAR_ARREARS"); + req.put("scope", "SUMMARY"); + req.put("customerCode", customerCode); + req.put("amount", amount); + JSONObject resp = new JSONObject(); + resp.put("success", Boolean.valueOf(success)); + resp.put("updatedCount", Integer.valueOf(updated)); + resp.put("remainingAmount", remaining); + if (StringUtils.isNotEmpty(note)) + { + resp.put("msg", note); + } + String status = success ? EsBizLogSupport.STATUS_SUCCESS : EsBizLogSupport.STATUS_FAIL; + String err = success ? null : EsBizLogSupport.cut(note); + billSearchStore.save(EsLogType.ARREARS, customerCode, req.toJSONString(), resp.toJSONString(), status, err); + } + + private void saveArrearsClearOrderEsLog(PrcOrderBill bill, BigDecimal batchAmount, BigDecimal orderAmt, + BigDecimal remaining, boolean success, String errorMessage) + { + if (billSearchStore == null || bill == null || StringUtils.isEmpty(bill.getOrderNo())) + { + return; + } + JSONObject req = new JSONObject(); + req.put("action", "CLEAR_ARREARS"); + req.put("scope", "ORDER"); + req.put("orderNo", bill.getOrderNo()); + req.put("billId", bill.getBillId()); + req.put("customerCode", bill.getCustomerCode()); + req.put("batchAmount", batchAmount); + req.put("orderIncomeTotal", orderAmt); + req.put("fromStatus", BillCreditArrearsStatus.ARREARS.getCode()); + req.put("toStatus", BillCreditArrearsStatus.NORMAL.getCode()); + JSONObject resp = new JSONObject(); + resp.put("success", Boolean.valueOf(success)); + resp.put("remainingAmount", remaining); + if (StringUtils.isNotEmpty(errorMessage)) + { + resp.put("msg", errorMessage); + } + String status = success ? EsBizLogSupport.STATUS_SUCCESS : EsBizLogSupport.STATUS_FAIL; + String err = success ? null : EsBizLogSupport.cut(errorMessage); + billSearchStore.save(EsLogType.ARREARS, bill.getOrderNo(), req.toJSONString(), resp.toJSONString(), status, err); + } + private TmsOrderBillMatchType resolveTmsMatchType(String identifier, PrcOrderBill bill) { if (identifier.equalsIgnoreCase(StringUtils.trim(bill.getOrderNo()))) diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/PrcCreditArrearsClearAsyncRunner.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/PrcCreditArrearsClearAsyncRunner.java new file mode 100644 index 00000000..a57a9bfb --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/task/PrcCreditArrearsClearAsyncRunner.java @@ -0,0 +1,44 @@ +package com.ruoyi.price.task; + +import java.math.BigDecimal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.price.service.IPrcOrderBillService; + +/** + * 异步清除订单额度欠费状态(余额充值等审核入账后)。 + */ +@Component +public class PrcCreditArrearsClearAsyncRunner +{ + private static final Logger log = LoggerFactory.getLogger(PrcCreditArrearsClearAsyncRunner.class); + + @Autowired + private IPrcOrderBillService orderBillService; + + @Async("threadPoolTaskExecutor") + public void run(String customerCode, BigDecimal amount) + { + try + { + int updated = orderBillService.processClearCreditArrearsByAmount(customerCode, amount); + log.info("async clear credit arrears done, customerCode={}, amount={}, updated={}", + customerCode, amount, Integer.valueOf(updated)); + } + catch (Exception e) + { + log.error("async clear credit arrears failed, customerCode={}, amount={}", + customerCode, amount, e); + String msg = e.getMessage(); + if (StringUtils.isEmpty(msg)) + { + msg = e.getClass().getSimpleName(); + } + orderBillService.logClearCreditArrearsFail(customerCode, amount, msg); + } + } +} diff --git a/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcOrderBillMapper.xml b/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcOrderBillMapper.xml index eaef285d..97291997 100644 --- a/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcOrderBillMapper.xml +++ b/bms-back/ruoyi-system/src/main/resources/mapper/price/PrcOrderBillMapper.xml @@ -621,4 +621,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" end, o.update_time = sysdate() + + + + + + update prc_order_bill + set credit_arrears_status = #{creditArrearsStatus}, + updated_by = #{updateBy}, + update_time = sysdate() + where bill_id = #{billId} + diff --git a/bms-front/src/views/price/basic/product/components/ProductFormDialog.vue b/bms-front/src/views/price/basic/product/components/ProductFormDialog.vue index 2a34bf4a..2b63336d 100644 --- a/bms-front/src/views/price/basic/product/components/ProductFormDialog.vue +++ b/bms-front/src/views/price/basic/product/components/ProductFormDialog.vue @@ -69,16 +69,16 @@ const schemas = computed(() => [ width: '100%', props: { maxlength: 50 } }, - { - label: '税模式', - prop: 'taxMode', - component: 'select', - placeholder: '请选择', - dict: 'bms_tax_mode', - span: 12, - width: '100%', - rules: [{ required: true, message: '请选择税模式', trigger: 'change' }] - }, + // { + // label: '税模式', + // prop: 'taxMode', + // component: 'select', + // placeholder: '请选择', + // dict: 'bms_tax_mode', + // span: 12, + // width: '100%', + // rules: [{ required: true, message: '请选择税模式', trigger: 'change' }] + // }, { label: '生效日期', prop: 'effectiveDate', diff --git a/bms-front/src/views/price/basic/product/index.vue b/bms-front/src/views/price/basic/product/index.vue index 5232c939..be6ef634 100644 --- a/bms-front/src/views/price/basic/product/index.vue +++ b/bms-front/src/views/price/basic/product/index.vue @@ -113,7 +113,7 @@ const columns = [ { prop: 'serviceLevel', label: '时效等级', width: 90, align: 'center' }, { prop: 'cargoAttr', label: '走货属性', minWidth: 120, slot: 'cargoAttr' }, { prop: 'destRegion', label: '目的区域', width: 90, align: 'center' }, - { prop: 'taxMode', label: '税模式', width: 90, align: 'center', dict: 'bms_tax_mode' }, + // { prop: 'taxMode', label: '税模式', width: 90, align: 'center', dict: 'bms_tax_mode' }, { prop: 'effectiveDate', label: '生效日期', width: 120, align: 'center' }, { prop: 'expireDate', label: '失效日期', width: 120, align: 'center' }, { prop: 'countryCount', label: '国家数', width: 80, align: 'center' }, @@ -125,15 +125,15 @@ const columns = [ const searchColumns = [ { prop: 'productCode', label: '产品代码', component: 'input', placeholder: '产品代码' }, - { prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' }, - { - prop: 'taxMode', - label: '税模式', - component: 'select', - dict: 'bms_tax_mode', - placeholder: '税模式', - clearable: true - } + { prop: 'productName', label: '产品名称', component: 'input', placeholder: '产品名称' } + // { + // prop: 'taxMode', + // label: '税模式', + // component: 'select', + // dict: 'bms_tax_mode', + // placeholder: '税模式', + // clearable: true + // } ] function queryList(params) {