diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java index 7be4dd32..785904fa 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java @@ -1,7 +1,9 @@ package com.bms.web.controller.price; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -20,10 +22,13 @@ import com.bms.common.core.domain.AjaxResult; import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.common.utils.DateUtils; +import com.bms.common.utils.MessageUtils; import com.bms.common.utils.StringUtils; +import com.bms.price.domain.BatchAddIncomeBillFeeTask; import com.bms.price.domain.PrcOrderBill; import com.bms.price.domain.PrcQuote; import com.bms.price.domain.vo.AddIncomeBillFeeRequest; +import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest; import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest; import com.bms.price.domain.vo.TmsPriceBillRequest; import com.bms.price.domain.vo.TmsPriceQuoteRequest; @@ -207,6 +212,38 @@ public class BillingController extends BaseController return success(prcOrderBillService.addIncomeFee(request)); } + /** Preview matched bills for batch add fee (filter: customer / transferNo / country). */ + @PreAuthorize("@ss.hasPermi('price:billing:feeAdd')") + @PostMapping("/bill/fee/batch/preview") + public AjaxResult previewBatchAddIncomeFee(@RequestBody BatchAddIncomeBillFeeRequest request) + { + return success(prcOrderBillService.previewBatchAddIncomeFee(request)); + } + + /** Submit async batch add fee; poll /bill/fee/batch/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:billing:feeAdd')") + @Log(title = "Billing batch income fee", businessType = BusinessType.INSERT) + @PostMapping("/bill/fee/batch") + public AjaxResult submitBatchAddIncomeFee(@Validated @RequestBody BatchAddIncomeBillFeeRequest request) + { + String taskId = prcOrderBillService.submitBatchAddIncomeFee(request); + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } + + @PreAuthorize("@ss.hasPermi('price:billing:feeAdd')") + @GetMapping("/bill/fee/batch/task/{taskId}") + public AjaxResult getBatchAddIncomeFeeTask(@PathVariable String taskId) + { + BatchAddIncomeBillFeeTask task = prcOrderBillService.getBatchAddIncomeFeeTask(taskId); + if (task == null) + { + return error(MessageUtils.message("price.bill.fee.batch.taskNotExists")); + } + return success(task); + } + @PreAuthorize("@ss.hasPermi('price:billing:feeEdit')") @Log(title = "Billing income fee", businessType = BusinessType.UPDATE) @PutMapping("/bill/fee") diff --git a/bms-back/bms-admin/src/main/resources/i18n/messages.properties b/bms-back/bms-admin/src/main/resources/i18n/messages.properties index 1c284c33..2c3d7804 100644 --- a/bms-back/bms-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/bms-admin/src/main/resources/i18n/messages.properties @@ -175,6 +175,12 @@ price.bill.packageZonePriceNotHit=已匹配分区,但套餐版本无该分区/ price.bill.packagePriceNotHit=套餐版本无匹配的国家/重量段价格 price.bill.productNotInPackage=产品不在客户套餐版本中,无法计费 price.bill.fee.scIdRequired=附加费不能为空 +price.bill.fee.batch.taskNotExists=批量增加费用任务不存在或已过期 +price.bill.fee.batch.tooMany=匹配订单超过上限 {0} 条,请缩小筛选范围 +price.bill.fee.batch.noMatch=未查询到匹配的计费订单,无法批量增加费用 +price.bill.fee.batch.filterRequired=请先在计费列表设置查询条件 +price.bill.fee.batch.selectedRequired=请勾选要增加费用的订单 +price.bill.fee.batch.scopeRequired=请选择添加范围 price.bill.fee.surchargeNotFound=附加费配置不存在或已停用 price.bill.fee.notFound=费用明细不存在 price.bill.fee.notManual=仅手动增加的费用可编辑或删除 diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BatchAddIncomeBillFeeTask.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BatchAddIncomeBillFeeTask.java new file mode 100644 index 00000000..1246d8c2 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BatchAddIncomeBillFeeTask.java @@ -0,0 +1,103 @@ +package com.bms.price.domain; + +import java.io.Serializable; + +/** Async batch add income fee progress (Redis). */ +public class BatchAddIncomeBillFeeTask implements Serializable +{ + private static final long serialVersionUID = 1L; + + public static final String STATUS_PENDING = "PENDING"; + public static final String STATUS_RUNNING = "RUNNING"; + public static final String STATUS_SUCCESS = "SUCCESS"; + public static final String STATUS_FAIL = "FAIL"; + + private String taskId; + private String status; + private int percent; + private String message; + private String error; + private int total; + private int success; + private int fail; + + public String getTaskId() + { + return taskId; + } + + public void setTaskId(String taskId) + { + this.taskId = taskId; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public int getPercent() + { + return percent; + } + + public void setPercent(int percent) + { + this.percent = percent; + } + + public String getMessage() + { + return message; + } + + public void setMessage(String message) + { + this.message = message; + } + + public String getError() + { + return error; + } + + public void setError(String error) + { + this.error = error; + } + + public int getTotal() + { + return total; + } + + public void setTotal(int total) + { + this.total = total; + } + + public int getSuccess() + { + return success; + } + + public void setSuccess(int success) + { + this.success = success; + } + + public int getFail() + { + return fail; + } + + public void setFail(int fail) + { + this.fail = fail; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcOrderBill.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcOrderBill.java index 4b4f3dad..d0d6d445 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcOrderBill.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcOrderBill.java @@ -16,6 +16,8 @@ public class PrcOrderBill extends BaseEntity private static final long serialVersionUID = 1L; private String billId; + /** 批量查询:多个账单ID精确匹配(仅查询用,非表字段) */ + private List billIds; private String billKey; private String orderNo; /** 批量查询:多个订单号精确匹配(仅查询用,非表字段) */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/BatchAddIncomeBillFeePreview.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/BatchAddIncomeBillFeePreview.java new file mode 100644 index 00000000..8ef72f0b --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/BatchAddIncomeBillFeePreview.java @@ -0,0 +1,18 @@ +package com.bms.price.domain.vo; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import com.bms.price.domain.PrcOrderBill; +import lombok.Data; + +/** Preview matched bills before async batch add fee. */ +@Data +public class BatchAddIncomeBillFeePreview implements Serializable +{ + private static final long serialVersionUID = 1L; + + private int matchCount; + /** First N rows for UI preview. */ + private List samples = new ArrayList(); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/BatchAddIncomeBillFeeRequest.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/BatchAddIncomeBillFeeRequest.java new file mode 100644 index 00000000..66358f69 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/BatchAddIncomeBillFeeRequest.java @@ -0,0 +1,50 @@ +package com.bms.price.domain.vo; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Batch add receivable fee lines. + * scopeMode=SELECTED uses billIds; scopeMode=ALL uses list query filters. + */ +@Data +public class BatchAddIncomeBillFeeRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** ALL = list filters; SELECTED = billIds only. */ + private String scopeMode; + + /** Selected bill ids when scopeMode=SELECTED. */ + private List billIds; + + /** Same filters as billing list search. */ + private List orderNos; + private List trackingNos; + private List transferNos; + private List customerCodes; + private List productCodes; + private String countryCode; + private String creditArrearsStatus; + + /** Surcharge config id (prc_surcharge.sc_id). */ + @NotNull(message = "scId required") + private Long scId; + + /** Dict bms_fee_type; empty = use surcharge.feeType. */ + private String feeType; + + /** + * Fixed amount for every matched bill. + * Empty = calculate per bill from surcharge chargeType / weight / freight. + */ + private BigDecimal amount; + + /** Default CNY. */ + private String currency; + + private String remark; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java index e94f7809..736c2c8d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcIncomeBillFeeMapper.java @@ -17,6 +17,10 @@ public interface PrcIncomeBillFeeMapper public PrcIncomeBillFee selectByFeeId(String feeId); + /** Lookup by unique key uk_bill_fee_item (bill_id, fee_type, sc_id). */ + public PrcIncomeBillFee selectByBillIdAndFeeTypeAndScId(@Param("billId") String billId, + @Param("feeType") String feeType, @Param("scId") Long scId); + /** Count fees of a bill under given ar_bill_status and fee_type (e.g. BILLED + BASIC_FREIGHT). */ public int countByBillIdAndArBillStatusAndFeeType(@Param("billId") String billId, @Param("arBillStatus") String arBillStatus, @Param("feeType") String feeType); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java index 6edec522..25ff2294 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java @@ -3,7 +3,10 @@ package com.bms.price.service; import java.math.BigDecimal; import java.util.List; import com.bms.price.domain.PrcOrderBill; +import com.bms.price.domain.BatchAddIncomeBillFeeTask; import com.bms.price.domain.vo.AddIncomeBillFeeRequest; +import com.bms.price.domain.vo.BatchAddIncomeBillFeePreview; +import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest; import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest; import com.bms.price.domain.vo.TmsPriceBillRequest; import com.bms.price.domain.vo.TmsPriceQuoteResult; @@ -45,6 +48,15 @@ public interface IPrcOrderBillService /** Delete manual UNBILLED income fee; refresh receivable totals. */ TmsPriceQuoteResult deleteIncomeFee(String feeId); + /** Preview matched bills for batch add fee (data-scoped). */ + BatchAddIncomeBillFeePreview previewBatchAddIncomeFee(BatchAddIncomeBillFeeRequest request); + + /** Submit async batch add fee; returns taskId. */ + String submitBatchAddIncomeFee(BatchAddIncomeBillFeeRequest request); + + /** Poll Redis progress for batch add fee task. */ + BatchAddIncomeBillFeeTask getBatchAddIncomeFeeTask(String taskId); + /** 提供给 TMS 判断订单是否可以继续处理的客户状态查询。 */ TmsOrderBillStatusResult queryTmsOrderBillStatus(String identifier); 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 c297b1ba..9ef6a685 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 @@ -30,6 +30,7 @@ import com.bms.common.utils.MessageUtils; import com.bms.common.utils.SecurityUtils; import com.bms.common.utils.StringUtils; import com.bms.common.utils.sign.Md5Utils; +import com.bms.common.utils.uuid.IdUtils; import com.bms.customer.customerinfo.domain.BmsCustomer; import com.bms.customer.customerinfo.mapper.BmsCustomerMapper; import com.bms.system.mapper.SysUserMapper; @@ -40,8 +41,12 @@ import com.bms.price.domain.PrcOrderBillExt; import com.bms.price.domain.PrcPayBill; import com.bms.price.domain.PrcPayBillFee; import com.bms.price.domain.PrcSurcharge; +import com.bms.price.domain.BatchAddIncomeBillFeeTask; import com.bms.price.domain.vo.AddIncomeBillFeeRequest; +import com.bms.price.domain.vo.BatchAddIncomeBillFeePreview; +import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest; import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest; +import com.bms.price.task.PrcBatchAddIncomeFeeAsyncRunner; import com.bms.price.domain.vo.TmsPriceBillRequest; import com.bms.price.domain.vo.TmsOrderBillStatusResult; import com.bms.price.domain.vo.TmsPriceQuoteFeeLine; @@ -118,6 +123,12 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService @Autowired private PrcCreditArrearsClearAsyncRunner creditArrearsClearAsyncRunner; + @Autowired + private PrcBatchAddIncomeFeeAsyncRunner batchAddIncomeFeeAsyncRunner; + + private static final int BATCH_ADD_FEE_MAX = 5000; + private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20; + @Override @Transactional(rollbackFor = Exception.class) public TmsPriceQuoteResult bill(TmsPriceBillRequest request) @@ -387,20 +398,48 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService amount = BigDecimal.ZERO; } - PrcIncomeBillFee fee = new PrcIncomeBillFee(); - fee.setFeeId(UUID.randomUUID().toString()); - fee.setBillId(bill.getBillId()); - fee.setIncomeBillId(incomeBill == null ? null : incomeBill.getIncomeBillId()); - fee.setFeeType(feeType); - fee.setFeeName(surcharge.getScName()); - fee.setScId(surcharge.getScId()); - fee.setAmount(amount); - fee.setCurrency(currency); - fee.setRoundMode(StringUtils.isEmpty(surcharge.getRoundMode()) ? bill.getRoundMode() : surcharge.getRoundMode()); - fee.setRemark(request.getRemark()); - fee.setArBillStatus(ArIncomeBillFeeStatus.UNBILLED.getCode()); - fee.setManualFlag("1"); - incomeBillFeeMapper.insertPrcIncomeBillFee(fee); + // uk_bill_fee_item (bill_id, fee_type, sc_id): overwrite when exists + PrcIncomeBillFee existing = incomeBillFeeMapper.selectByBillIdAndFeeTypeAndScId( + bill.getBillId(), feeType, surcharge.getScId()); + if (existing != null) + { + if (ArIncomeBillFeeStatus.BILLED.getCode().equals(existing.getArBillStatus())) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.alreadyBilled")); + } + existing.setFeeType(feeType); + existing.setFeeName(surcharge.getScName()); + existing.setScId(surcharge.getScId()); + existing.setAmount(amount); + existing.setCurrency(currency); + existing.setRoundMode(StringUtils.isEmpty(surcharge.getRoundMode()) ? bill.getRoundMode() + : surcharge.getRoundMode()); + existing.setRemark(request.getRemark()); + existing.setManualFlag("1"); + if (incomeBill != null && StringUtils.isEmpty(existing.getIncomeBillId())) + { + existing.setIncomeBillId(incomeBill.getIncomeBillId()); + } + incomeBillFeeMapper.updatePrcIncomeBillFee(existing); + } + else + { + PrcIncomeBillFee fee = new PrcIncomeBillFee(); + fee.setFeeId(UUID.randomUUID().toString()); + fee.setBillId(bill.getBillId()); + fee.setIncomeBillId(incomeBill == null ? null : incomeBill.getIncomeBillId()); + fee.setFeeType(feeType); + fee.setFeeName(surcharge.getScName()); + fee.setScId(surcharge.getScId()); + fee.setAmount(amount); + fee.setCurrency(currency); + fee.setRoundMode(StringUtils.isEmpty(surcharge.getRoundMode()) ? bill.getRoundMode() + : surcharge.getRoundMode()); + fee.setRemark(request.getRemark()); + fee.setArBillStatus(ArIncomeBillFeeStatus.UNBILLED.getCode()); + fee.setManualFlag("1"); + incomeBillFeeMapper.insertPrcIncomeBillFee(fee); + } return refreshBillIncomeTotals(bill); } @@ -472,6 +511,125 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService return refreshBillIncomeTotals(bill); } + @Override + public BatchAddIncomeBillFeePreview previewBatchAddIncomeFee(BatchAddIncomeBillFeeRequest request) + { + List bills = resolveBatchAddFeeBills(request); + BatchAddIncomeBillFeePreview preview = new BatchAddIncomeBillFeePreview(); + preview.setMatchCount(bills.size()); + int limit = Math.min(bills.size(), BATCH_ADD_FEE_PREVIEW_LIMIT); + preview.setSamples(bills.subList(0, limit)); + return preview; + } + + @Override + public String submitBatchAddIncomeFee(BatchAddIncomeBillFeeRequest request) + { + if (request == null || request.getScId() == null) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.scIdRequired")); + } + requireActiveSurcharge(request.getScId()); + List bills = resolveBatchAddFeeBills(request); + if (bills.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.noMatch")); + } + if (bills.size() > BATCH_ADD_FEE_MAX) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.tooMany", + Integer.valueOf(BATCH_ADD_FEE_MAX))); + } + List billIds = new ArrayList(); + for (PrcOrderBill bill : bills) + { + if (bill != null && StringUtils.isNotEmpty(bill.getBillId())) + { + billIds.add(bill.getBillId()); + } + } + if (billIds.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.noMatch")); + } + String taskId = IdUtils.fastSimpleUUID(); + batchAddIncomeFeeAsyncRunner.put(taskId, BatchAddIncomeBillFeeTask.STATUS_PENDING, 0, "queued", null, + billIds.size(), 0, 0); + batchAddIncomeFeeAsyncRunner.run(taskId, billIds, request.getScId(), request.getFeeType(), + request.getAmount(), request.getCurrency(), request.getRemark()); + return taskId; + } + + @Override + public BatchAddIncomeBillFeeTask getBatchAddIncomeFeeTask(String taskId) + { + if (StringUtils.isEmpty(taskId) || batchAddIncomeFeeAsyncRunner == null) + { + return null; + } + return batchAddIncomeFeeAsyncRunner.get(taskId); + } + + /** + * Resolve matched bills with data scope. + * scopeMode required: SELECTED uses billIds; ALL uses list filters (at least one required). + */ + private List resolveBatchAddFeeBills(BatchAddIncomeBillFeeRequest request) + { + if (request == null) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.filterRequired")); + } + String scopeMode = StringUtils.nvl(request.getScopeMode(), "").trim().toUpperCase(); + if (!"ALL".equals(scopeMode) && !"SELECTED".equals(scopeMode)) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.scopeRequired")); + } + List orderNos = normalizeCodeList(request.getOrderNos()); + List trackingNos = normalizeCodeList(request.getTrackingNos()); + List transferNos = normalizeCodeList(request.getTransferNos()); + List customerCodes = normalizeCodeList(request.getCustomerCodes()); + List productCodes = normalizeCodeList(request.getProductCodes()); + String creditArrearsStatus = StringUtils.isEmpty(request.getCreditArrearsStatus()) ? null + : request.getCreditArrearsStatus().trim(); + String countryCode = StringUtils.isEmpty(request.getCountryCode()) ? null + : request.getCountryCode().trim().toUpperCase(); + boolean hasFilter = (orderNos != null && !orderNos.isEmpty()) + || (trackingNos != null && !trackingNos.isEmpty()) + || (transferNos != null && !transferNos.isEmpty()) + || (customerCodes != null && !customerCodes.isEmpty()) + || (productCodes != null && !productCodes.isEmpty()) + || StringUtils.isNotEmpty(creditArrearsStatus) + || StringUtils.isNotEmpty(countryCode); + if (!hasFilter) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.filterRequired")); + } + PrcOrderBill query = new PrcOrderBill(); + if ("SELECTED".equals(scopeMode)) + { + List billIds = normalizeCodeList(request.getBillIds()); + if (billIds == null || billIds.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.bill.fee.batch.selectedRequired")); + } + query.setBillIds(billIds); + } + else + { + query.setOrderNos(orderNos); + query.setTrackingNos(trackingNos); + query.setTransferNos(transferNos); + query.setCustomerCodes(customerCodes); + query.setProductCodes(productCodes); + query.setCreditArrearsStatus(creditArrearsStatus); + query.setCountryCode(countryCode); + } + // Use self proxy so @DataScope applies + List list = self.selectBillList(query); + return list == null ? new ArrayList() : list; + } + private PrcSurcharge requireActiveSurcharge(Long scId) { PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcBatchAddIncomeFeeAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcBatchAddIncomeFeeAsyncRunner.java new file mode 100644 index 00000000..200ac020 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcBatchAddIncomeFeeAsyncRunner.java @@ -0,0 +1,137 @@ +package com.bms.price.task; + +import java.math.BigDecimal; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import com.bms.common.core.redis.RedisCache; +import com.bms.common.utils.StringUtils; +import com.bms.price.domain.BatchAddIncomeBillFeeTask; +import com.bms.price.domain.vo.AddIncomeBillFeeRequest; +import com.bms.price.service.IPrcOrderBillService; + +/** + * Async runner: add the same manual income fee to many order bills. + */ +@Component +public class PrcBatchAddIncomeFeeAsyncRunner +{ + private static final Logger log = LoggerFactory.getLogger(PrcBatchAddIncomeFeeAsyncRunner.class); + + public static final String CACHE_KEY_PREFIX = "price:billing:fee:batch:task:"; + public static final int TTL_MINUTES = 60; + + @Autowired + @Lazy + private IPrcOrderBillService orderBillService; + + @Autowired + private RedisCache redisCache; + + @Async("threadPoolTaskExecutor") + public void run(String taskId, List billIds, Long scId, String feeType, BigDecimal amount, + String currency, String remark) + { + int total = billIds == null ? 0 : billIds.size(); + int success = 0; + int fail = 0; + StringBuilder failTips = new StringBuilder(); + try + { + put(taskId, BatchAddIncomeBillFeeTask.STATUS_RUNNING, 1, "running", null, total, 0, 0); + if (total == 0) + { + put(taskId, BatchAddIncomeBillFeeTask.STATUS_FAIL, 100, "failed", "no bills", 0, 0, 0); + return; + } + for (int i = 0; i < total; i++) + { + String billId = billIds.get(i); + try + { + AddIncomeBillFeeRequest req = new AddIncomeBillFeeRequest(); + req.setBillId(billId); + req.setScId(scId); + req.setFeeType(feeType); + req.setAmount(amount); + req.setCurrency(currency); + req.setRemark(remark); + orderBillService.addIncomeFee(req); + success++; + } + catch (Exception e) + { + fail++; + if (failTips.length() < 800) + { + String tip = e.getMessage(); + if (StringUtils.isEmpty(tip)) + { + tip = e.getClass().getSimpleName(); + } + if (failTips.length() > 0) + { + failTips.append("; "); + } + failTips.append(billId).append(":").append(tip); + } + log.warn("batch add fee failed taskId={} billId={}", taskId, billId, e); + } + int percent = Math.min(99, (int) Math.floor(100.0 * (i + 1) / total)); + put(taskId, BatchAddIncomeBillFeeTask.STATUS_RUNNING, percent, + "processing " + (i + 1) + "/" + total, null, total, success, fail); + } + String msg = "success=" + success + ", fail=" + fail; + if (fail > 0 && failTips.length() > 0) + { + put(taskId, BatchAddIncomeBillFeeTask.STATUS_SUCCESS, 100, msg, failTips.toString(), + total, success, fail); + } + else + { + put(taskId, BatchAddIncomeBillFeeTask.STATUS_SUCCESS, 100, msg, null, total, success, fail); + } + log.info("batch add fee done taskId={} total={} success={} fail={}", taskId, total, success, fail); + } + catch (Exception e) + { + log.error("batch add fee aborted taskId={}", taskId, e); + put(taskId, BatchAddIncomeBillFeeTask.STATUS_FAIL, 100, "failed", errorMessage(e), total, success, fail); + } + } + + public void put(String taskId, String status, int percent, String message, String error, + int total, int success, int fail) + { + BatchAddIncomeBillFeeTask task = new BatchAddIncomeBillFeeTask(); + task.setTaskId(taskId); + task.setStatus(status); + task.setPercent(percent); + task.setMessage(message); + task.setError(error); + task.setTotal(total); + task.setSuccess(success); + task.setFail(fail); + redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES); + } + + public BatchAddIncomeBillFeeTask get(String taskId) + { + return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId); + } + + private static String errorMessage(Exception e) + { + String msg = e.getMessage(); + if (StringUtils.isEmpty(msg)) + { + msg = e.getClass().getSimpleName(); + } + return msg; + } +} diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml index 3bee21c7..208c858d 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcIncomeBillFeeMapper.xml @@ -60,6 +60,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where fee_id = #{feeId} + +