From dd2efef456c194a348b4a43a3e2e3ecc62611e26 Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Wed, 21 Oct 2026 10:34:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=AD=BB=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../price/domain/vo/LocalRebillRequest.java | 2 + .../service/impl/PrcOrderBillServiceImpl.java | 82 ++++++++++++++++++- bms-front/src/views/price/orderBill/index.vue | 44 ++++++---- 3 files changed, 109 insertions(+), 19 deletions(-) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/LocalRebillRequest.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/LocalRebillRequest.java index 8b6cdeeb..ad5f756a 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/LocalRebillRequest.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/LocalRebillRequest.java @@ -22,6 +22,8 @@ public class LocalRebillRequest implements Serializable private List orderIdentifiers; private List customerCodes; private List productCodes; + private List orderStatuses; + private List billingFailStatuses; private String countryCode; private String creditArrearsStatus; private String beginBizDate; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java index cb280f2b..9685c0e1 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -89,6 +91,8 @@ import com.bms.price.task.PrcCreditArrearsClearAsyncRunner; @Service public class PrcOrderBillServiceImpl implements IPrcOrderBillService { + private static final Logger log = LoggerFactory.getLogger(PrcOrderBillServiceImpl.class); + private static final String DICT_FEE_TYPE = "bms_fee_type"; private static final String FEE_TYPE_REFUND = "REFUND"; @@ -701,8 +705,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService persistProductOrRedlineOnly(bill, result, productMode); } + private static final int LOCAL_REBILL_DB_RETRY_MAX = 3; + private static final long LOCAL_REBILL_DB_RETRY_BASE_MS = 80L; + + /** + * Local rebill without outer txn: each {@link #bill} starts its own transaction so + * MySQL deadlock can be retried cleanly (same idea as billing-task billWithDbRetry). + */ @Override - @Transactional(rollbackFor = Exception.class) public TmsPriceQuoteResult rebillLocalByBillId(String billId) { if (StringUtils.isEmpty(billId)) @@ -723,8 +733,72 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired")); } TmsPriceBillRequest req = buildBillRequestFromBill(bill); - // Full bill() path: AR guard, weight rules, quote, persist (local snapshot only). - return self.bill(req); + return billLocalWithDbRetry(billId.trim(), req); + } + + private TmsPriceQuoteResult billLocalWithDbRetry(String billId, TmsPriceBillRequest billReq) + { + RuntimeException last = null; + for (int attempt = 1; attempt <= LOCAL_REBILL_DB_RETRY_MAX; attempt++) + { + try + { + return self.bill(billReq); + } + catch (RuntimeException e) + { + last = e; + if (attempt < LOCAL_REBILL_DB_RETRY_MAX && isRetryableDbConflict(e)) + { + long sleepMs = LOCAL_REBILL_DB_RETRY_BASE_MS * attempt + + (long) (Math.random() * LOCAL_REBILL_DB_RETRY_BASE_MS); + log.warn("local rebill db conflict, retry billId={} attempt={}/{} sleepMs={} msg={}", + billId, Integer.valueOf(attempt), Integer.valueOf(LOCAL_REBILL_DB_RETRY_MAX), + Long.valueOf(sleepMs), StringUtils.substring(e.getMessage(), 0, 200)); + try + { + Thread.sleep(sleepMs); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + throw e; + } + continue; + } + throw e; + } + } + throw last != null ? last : new ServiceException("local rebill retry exhausted"); + } + + /** Deadlock / lock wait — safe to retry whole bill txn (parallel local rebill). */ + private static boolean isRetryableDbConflict(Throwable e) + { + Throwable t = e; + while (t != null) + { + String name = t.getClass().getName(); + if (name.indexOf("DeadlockLoser") >= 0 + || name.indexOf("CannotAcquireLock") >= 0 + || name.indexOf("MySQLTransactionRollback") >= 0) + { + return true; + } + String msg = t.getMessage(); + if (msg != null) + { + String m = msg.toLowerCase(); + if (m.indexOf("deadlock") >= 0 + || m.indexOf("try restarting transaction") >= 0 + || m.indexOf("lock wait timeout") >= 0) + { + return true; + } + } + t = t.getCause(); + } + return false; } @Override @@ -839,6 +913,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService query.setOrderIdentifiers(orderIdentifiers); query.setCustomerCodes(customerCodes); query.setProductCodes(productCodes); + query.setOrderStatuses(request.getOrderStatuses()); + query.setBillingFailStatuses(request.getBillingFailStatuses()); query.setCreditArrearsStatus(creditArrearsStatus); query.setCountryCode(countryCode); if (beginBizDate != null) diff --git a/bms-front/src/views/price/orderBill/index.vue b/bms-front/src/views/price/orderBill/index.vue index 30265726..0d191a8c 100644 --- a/bms-front/src/views/price/orderBill/index.vue +++ b/bms-front/src/views/price/orderBill/index.vue @@ -295,14 +295,27 @@ function currentIdentifiers() { } function buildRebillFilter() { - const q = tableRef.value?.getQueryParams?.() || {} - const filter = { orderIdentifiers: currentIdentifiers() } - if (Array.isArray(q.customerCodes) && q.customerCodes.length) { - filter.customerCodes = q.customerCodes + const q = applySearchRanges(tableRef.value?.getQueryParams?.() || {}) + const filter = { + scopeMode: 'ALL', + orderIdentifiers: currentIdentifiers(), + customerCodes: Array.isArray(q.customerCodes) ? q.customerCodes : [], + productCodes: Array.isArray(q.productCodes) ? q.productCodes : [], + orderStatuses: Array.isArray(q.orderStatuses) ? q.orderStatuses : [], + billingFailStatuses: Array.isArray(q.billingFailStatuses) ? q.billingFailStatuses : [], + beginBizDate: q.params?.beginBizDate || null, + endBizDate: q.params?.endBizDate || null } return filter } +function hasRebillFilter(filter) { + const ids = filter.orderIdentifiers || [] + if (ids.length) return true + const hasCustomer = Array.isArray(filter.customerCodes) && filter.customerCodes.length > 0 + const hasBizDate = !!(filter.beginBizDate && filter.endBizDate) + return hasCustomer && hasBizDate +} function buildCheckQuery() { const q = tableRef.value?.getQueryParams?.() || {} @@ -383,24 +396,23 @@ async function runLocalRebill(payload, confirmText) { } function openFilterRebill() { - const identifiers = currentIdentifiers() - if (!identifiers.length) { - ElMessage.warning('请输入单号后再重新计费,或勾选订单后点重新计费选中') + const filter = buildRebillFilter() + const identifiers = filter.orderIdentifiers || [] + if (!hasRebillFilter(filter)) { + ElMessage.warning('请输入单号,或选择客户并选择计费时间后再重新计费;也可勾选订单后点重新计费选中') return } if (identifiers.length > MAX_REBILL) { ElMessage.warning('单次最多重新计费 ' + MAX_REBILL + ' 个单号') return } - const filter = buildRebillFilter() - runLocalRebill( - { - scopeMode: 'ALL', - orderIdentifiers: filter.orderIdentifiers || identifiers, - customerCodes: filter.customerCodes || [] - }, - '将按 ' + identifiers.length + ' 个单号从本地订单表重新计费(不查上游),确认继续?' - ) + let confirmText + if (identifiers.length) { + confirmText = '将按 ' + identifiers.length + ' 个单号从本地订单表重新计费(不查上游),确认继续?' + } else { + confirmText = '将按当前列表筛选(客户 + 计费时间等)从本地订单表重新计费(不查上游),确认继续?' + } + runLocalRebill(filter, confirmText) } function handleBatchRebill(selection) {