From 071e7f0c55bc45893e5b5e6457ea9354f3f3544d Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Thu, 15 Oct 2026 16:09:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=A7=E5=93=81=E4=BB=B7?= =?UTF-8?q?=E5=92=8C=E7=BA=A2=E7=BA=BF=E4=BB=B7=E9=87=8D=E8=AE=A1=E8=B4=B9?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/price/BillingController.java | 48 ++++ .../main/resources/i18n/messages.properties | 8 + .../domain/vo/RecalcBillPriceRequest.java | 28 +++ .../price/service/IPrcOrderBillService.java | 18 ++ .../service/impl/PrcOrderBillServiceImpl.java | 225 ++++++++++++++++-- .../task/PrcRecalcBillPriceAsyncRunner.java | 114 +++++++++ .../sql/price/billing_recalc_price_menu.sql | 27 +++ bms-front/src/api/price/billing.js | 45 ++++ bms-front/src/views/price/billing/bill.vue | 127 +++++++++- 9 files changed, 615 insertions(+), 25 deletions(-) create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/domain/vo/RecalcBillPriceRequest.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/task/PrcRecalcBillPriceAsyncRunner.java create mode 100644 bms-back/sql/price/billing_recalc_price_menu.sql 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 20d584ef..07bcbdeb 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 @@ -41,6 +41,7 @@ import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest; import com.bms.price.domain.vo.BillingTaskSubmitRequest; import com.bms.price.domain.vo.ChargeWeightImportRow; import com.bms.price.domain.vo.ChargeWeightTemplateRequest; +import com.bms.price.domain.vo.RecalcBillPriceRequest; import com.bms.price.domain.vo.UpdateChargeWeightRequest; import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest; import com.bms.price.domain.vo.TmsPriceBillRequest; @@ -303,6 +304,53 @@ public class BillingController extends BaseController return success(task); } + /** Async recalc product_amount only. Poll /bill/recalcPrice/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:billing:recalc')") + @Log(title = "Billing recalc product amount", businessType = BusinessType.UPDATE) + @PostMapping("/bill/recalcProduct") + public AjaxResult submitRecalcProductAmount(@RequestBody RecalcBillPriceRequest request) + { + if (request == null) + { + request = new RecalcBillPriceRequest(); + } + request.setMode("PRODUCT"); + return successTask(prcOrderBillService.submitRecalcBillPrice(request)); + } + + /** Async recalc redline_amount only. Poll /bill/recalcPrice/task/{taskId}. */ + @PreAuthorize("@ss.hasPermi('price:billing:recalc')") + @Log(title = "Billing recalc redline amount", businessType = BusinessType.UPDATE) + @PostMapping("/bill/recalcRedline") + public AjaxResult submitRecalcRedlineAmount(@RequestBody RecalcBillPriceRequest request) + { + if (request == null) + { + request = new RecalcBillPriceRequest(); + } + request.setMode("REDLINE"); + return successTask(prcOrderBillService.submitRecalcBillPrice(request)); + } + + @PreAuthorize("@ss.hasPermi('price:billing:recalc')") + @GetMapping("/bill/recalcPrice/task/{taskId}") + public AjaxResult getRecalcBillPriceTask(@PathVariable String taskId) + { + BatchAddIncomeBillFeeTask task = prcOrderBillService.getRecalcBillPriceTask(taskId); + if (task == null) + { + return error(MessageUtils.message("price.bill.recalc.taskNotExists")); + } + return success(task); + } + + private AjaxResult successTask(String taskId) + { + Map payload = new HashMap(); + payload.put("taskId", taskId); + return success(payload); + } + @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 02b50f2f..8852450b 100644 --- a/bms-back/bms-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/bms-admin/src/main/resources/i18n/messages.properties @@ -187,6 +187,14 @@ price.bill.fee.batch.noMatch=未查询到匹配的计费订单,无法批量增 price.bill.fee.batch.filterRequired=请先在计费列表设置查询条件 price.bill.fee.batch.selectedRequired=请勾选要增加费用的订单 price.bill.fee.batch.scopeRequired=请选择添加范围 +price.bill.recalc.taskNotExists=重算价格任务不存在或已过期 +price.bill.recalc.tooMany=匹配订单超过上限 {0} 条,请缩小筛选范围 +price.bill.recalc.noMatch=未查询到已计费订单,无法重算产品价/红线价 +price.bill.recalc.filterRequired=请先在计费列表设置查询条件 +price.bill.recalc.selectedRequired=请勾选要重算的订单 +price.bill.recalc.modeRequired=请指定重算类型(PRODUCT/产品价或 REDLINE/红线价) +price.bill.recalc.scopeRequired=请选择重算范围 +price.bill.recalc.quoteEmpty=计价结果为空,无法更新产品价/红线价 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/vo/RecalcBillPriceRequest.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/RecalcBillPriceRequest.java new file mode 100644 index 00000000..9f65da31 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/RecalcBillPriceRequest.java @@ -0,0 +1,28 @@ +package com.bms.price.domain.vo; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** Batch recalc product_amount or redline_amount only (existing bills). */ +@Data +public class RecalcBillPriceRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** PRODUCT | REDLINE */ + private String mode; + + /** ALL = filters; SELECTED = billIds. */ + private String scopeMode; + private List billIds; + private List orderNos; + private List trackingNos; + private List transferNos; + private List customerCodes; + private List productCodes; + private String countryCode; + private String creditArrearsStatus; + private String beginBizDate; + private String endBizDate; +} 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 e15cc023..98142456 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 @@ -10,6 +10,7 @@ import com.bms.price.domain.vo.BatchAddIncomeBillFeePreview; import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest; import com.bms.price.domain.vo.ChargeWeightImportRow; import com.bms.price.domain.vo.ChargeWeightTemplateRequest; +import com.bms.price.domain.vo.RecalcBillPriceRequest; import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest; import com.bms.price.domain.vo.TmsPriceBillRequest; import com.bms.price.domain.vo.TmsPriceQuoteResult; @@ -111,4 +112,21 @@ public interface IPrcOrderBillService * otherwise fill orderNo/customer from list filters (data-scoped). */ List buildChargeWeightTemplateRows(ChargeWeightTemplateRequest request); + + /** + * Submit async recalc of product_amount or redline_amount (request.mode). + * Existing bills only; no AR restriction; sales amounts untouched. + * + * @return taskId + */ + String submitRecalcBillPrice(RecalcBillPriceRequest request); + + /** Poll Redis progress for product/redline recalc task. */ + BatchAddIncomeBillFeeTask getRecalcBillPriceTask(String taskId); + + /** + * Recalc and persist product_amount or redline_amount by mode (PRODUCT/REDLINE). + * Requires existing prc_order_bill; skips AR editable guard. + */ + void recalcBillPriceByBillId(String billId, String mode); } 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 9a26b925..b8335fc2 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 @@ -49,10 +49,12 @@ import com.bms.price.domain.vo.BatchAddIncomeBillFeePreview; import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest; import com.bms.price.domain.vo.ChargeWeightImportRow; import com.bms.price.domain.vo.ChargeWeightTemplateRequest; +import com.bms.price.domain.vo.RecalcBillPriceRequest; import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest; import com.bms.price.task.PrcBatchAddIncomeFeeAsyncRunner; import com.bms.price.task.PrcChargeWeightImportAsyncRunner; import com.bms.price.task.PrcChargeWeightImportAsyncRunner.ChargeWeightImportWorkItem; +import com.bms.price.task.PrcRecalcBillPriceAsyncRunner; import com.bms.price.domain.vo.TmsPriceBillRequest; import com.bms.price.domain.vo.TmsOrderBillStatusResult; import com.bms.price.domain.vo.TmsPriceQuoteFeeLine; @@ -137,9 +139,13 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService @Autowired private PrcChargeWeightImportAsyncRunner chargeWeightImportAsyncRunner; + @Autowired + private PrcRecalcBillPriceAsyncRunner recalcBillPriceAsyncRunner; + private static final int BATCH_ADD_FEE_MAX = 5000; private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20; private static final int CHARGE_WEIGHT_IMPORT_MAX = 5000; + private static final int RECALC_PRICE_MAX = 5000; @Override @Transactional(rollbackFor = Exception.class) @@ -591,34 +597,208 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService return batchAddIncomeFeeAsyncRunner.get(taskId); } - /** - * Resolve matched bills with data scope. - * scopeMode required: SELECTED uses billIds; ALL uses list filters (at least one required). - */ + @Override + public String submitRecalcBillPrice(RecalcBillPriceRequest request) + { + if (request == null) + { + throw new ServiceException(MessageUtils.message("price.bill.recalc.filterRequired")); + } + String mode = normalizeRecalcMode(request.getMode()); + List bills = resolveListScopedBills(request.getScopeMode(), request.getBillIds(), + request.getOrderNos(), request.getTrackingNos(), request.getTransferNos(), + request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(), + request.getCreditArrearsStatus(), request.getBeginBizDate(), request.getEndBizDate(), + "price.bill.recalc.filterRequired", "price.bill.recalc.scopeRequired", + "price.bill.recalc.selectedRequired"); + if (bills.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.bill.recalc.noMatch")); + } + if (bills.size() > RECALC_PRICE_MAX) + { + throw new ServiceException(MessageUtils.message("price.bill.recalc.tooMany", + Integer.valueOf(RECALC_PRICE_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.recalc.noMatch")); + } + String taskId = IdUtils.fastSimpleUUID(); + recalcBillPriceAsyncRunner.put(taskId, BatchAddIncomeBillFeeTask.STATUS_PENDING, 0, "queued", null, + billIds.size(), 0, 0); + recalcBillPriceAsyncRunner.run(taskId, mode, billIds); + return taskId; + } + + @Override + public BatchAddIncomeBillFeeTask getRecalcBillPriceTask(String taskId) + { + if (StringUtils.isEmpty(taskId) || recalcBillPriceAsyncRunner == null) + { + return null; + } + return recalcBillPriceAsyncRunner.get(taskId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void recalcBillPriceByBillId(String billId, String mode) + { + boolean productMode = "PRODUCT".equals(normalizeRecalcMode(mode)); + if (StringUtils.isEmpty(billId)) + { + throw new ServiceException(MessageUtils.message("price.bill.billIdRequired")); + } + PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(billId.trim()); + if (bill == null) + { + throw new ServiceException(MessageUtils.message("price.bill.notFound")); + } + TmsPriceBillRequest req = buildBillRequestFromBill(bill); + if (StringUtils.isEmpty(req.getBizDate())) + { + throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired")); + } + TmsPriceQuoteResult result = tmsPriceQuoteService.calculate(req); + if (result == null) + { + throw new ServiceException(MessageUtils.message("price.bill.recalc.quoteEmpty")); + } + persistProductOrRedlineOnly(bill, result, productMode); + } + + private static String normalizeRecalcMode(String mode) + { + String m = StringUtils.nvl(mode, "").trim().toUpperCase(); + if (!"PRODUCT".equals(m) && !"REDLINE".equals(m)) + { + throw new ServiceException(MessageUtils.message("price.bill.recalc.modeRequired")); + } + return m; + } + + /** Update income header + fee snapshots for product or redline only. */ + private void persistProductOrRedlineOnly(PrcOrderBill bill, TmsPriceQuoteResult result, boolean productMode) + { + PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId()); + boolean insert = incomeBill == null; + if (insert) + { + incomeBill = new PrcIncomeBill(); + incomeBill.setIncomeBillId(UUID.randomUUID().toString()); + incomeBill.setBillId(bill.getBillId()); + incomeBill.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency()); + incomeBill.setSalesAmount(nz(bill.getIncomeTotal())); + incomeBill.setIncomeTotal(nz(bill.getIncomeTotal())); + incomeBill.setBelongSaler(bill.getBelongSaler()); + incomeBill.setBelongSalerCode(bill.getBelongSalerCode()); + incomeBill.setSettlementMan(bill.getSettlementMan()); + incomeBill.setSettlementManCode(bill.getSettlementManCode()); + incomeBill.setCreateBy(bill.getCreateBy()); + } + if (productMode) + { + incomeBill.setProductAmount(result.getProductAmount()); + } + else + { + incomeBill.setRedlineAmount(result.getRedlineAmount()); + } + if (insert) + { + incomeBillMapper.insertPrcIncomeBill(incomeBill); + } + else + { + incomeBill.setUpdateBy(bill.getUpdateBy()); + incomeBillMapper.updatePrcIncomeBill(incomeBill); + } + + List fees = incomeBillFeeMapper.selectByBillId(bill.getBillId()); + if (fees == null || fees.isEmpty() || result.getFeeDetails() == null) + { + return; + } + java.util.Map quoteByKey = new java.util.HashMap(); + for (TmsPriceQuoteFeeLine line : result.getFeeDetails()) + { + if (line != null && StringUtils.isNotEmpty(line.getFeeType())) + { + quoteByKey.put(feeUniqueKey(line.getFeeType(), line.getScId()), line); + } + } + for (PrcIncomeBillFee fee : fees) + { + if (fee == null || StringUtils.isEmpty(fee.getFeeType())) + { + continue; + } + TmsPriceQuoteFeeLine line = quoteByKey.get(feeUniqueKey(fee.getFeeType(), fee.getScId())); + if (line == null) + { + continue; + } + if (productMode) + { + fee.setProductAmount(line.getProductAmount()); + } + else + { + fee.setRedlineAmount(line.getRedlineAmount()); + } + incomeBillFeeMapper.updatePrcIncomeBillFee(fee); + } + } + 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(); + return resolveListScopedBills(request.getScopeMode(), request.getBillIds(), + request.getOrderNos(), request.getTrackingNos(), request.getTransferNos(), + request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(), + request.getCreditArrearsStatus(), request.getBeginBizDate(), request.getEndBizDate(), + "price.bill.fee.batch.filterRequired", "price.bill.fee.batch.scopeRequired", + "price.bill.fee.batch.selectedRequired"); + } + + /** + * Shared list-scope resolve for batch fee / recalc price. + * scopeMode: SELECTED uses billIds; ALL uses filters (at least one required). + */ + private List resolveListScopedBills(String scopeModeRaw, List billIdsRaw, + List orderNosRaw, List trackingNosRaw, List transferNosRaw, + List customerCodesRaw, List productCodesRaw, String countryCodeRaw, + String creditArrearsStatusRaw, String beginBizDateRaw, String endBizDateRaw, + String msgFilter, String msgScope, String msgSelected) + { + String scopeMode = StringUtils.nvl(scopeModeRaw, "").trim().toUpperCase(); if (!"ALL".equals(scopeMode) && !"SELECTED".equals(scopeMode)) { - throw new ServiceException(MessageUtils.message("price.bill.fee.batch.scopeRequired")); + throw new ServiceException(MessageUtils.message(msgScope)); } - 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(); - String beginBizDate = StringUtils.isEmpty(request.getBeginBizDate()) ? null - : request.getBeginBizDate().trim(); - String endBizDate = StringUtils.isEmpty(request.getEndBizDate()) ? null - : request.getEndBizDate().trim(); + List orderNos = normalizeCodeList(orderNosRaw); + List trackingNos = normalizeCodeList(trackingNosRaw); + List transferNos = normalizeCodeList(transferNosRaw); + List customerCodes = normalizeCodeList(customerCodesRaw); + List productCodes = normalizeCodeList(productCodesRaw); + String creditArrearsStatus = StringUtils.isEmpty(creditArrearsStatusRaw) ? null + : creditArrearsStatusRaw.trim(); + String countryCode = StringUtils.isEmpty(countryCodeRaw) ? null + : countryCodeRaw.trim().toUpperCase(); + String beginBizDate = StringUtils.isEmpty(beginBizDateRaw) ? null : beginBizDateRaw.trim(); + String endBizDate = StringUtils.isEmpty(endBizDateRaw) ? null : endBizDateRaw.trim(); boolean hasFilter = (orderNos != null && !orderNos.isEmpty()) || (trackingNos != null && !trackingNos.isEmpty()) || (transferNos != null && !transferNos.isEmpty()) @@ -630,15 +810,15 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService || StringUtils.isNotEmpty(endBizDate); if (!hasFilter) { - throw new ServiceException(MessageUtils.message("price.bill.fee.batch.filterRequired")); + throw new ServiceException(MessageUtils.message(msgFilter)); } PrcOrderBill query = new PrcOrderBill(); if ("SELECTED".equals(scopeMode)) { - List billIds = normalizeCodeList(request.getBillIds()); + List billIds = normalizeCodeList(billIdsRaw); if (billIds == null || billIds.isEmpty()) { - throw new ServiceException(MessageUtils.message("price.bill.fee.batch.selectedRequired")); + throw new ServiceException(MessageUtils.message(msgSelected)); } query.setBillIds(billIds); } @@ -660,7 +840,6 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService query.getParams().put("endBizDate", endBizDate); } } - // Use self proxy so @DataScope applies List list = self.selectBillList(query); return list == null ? new ArrayList() : list; } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcRecalcBillPriceAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcRecalcBillPriceAsyncRunner.java new file mode 100644 index 00000000..519cb87d --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcRecalcBillPriceAsyncRunner.java @@ -0,0 +1,114 @@ +package com.bms.price.task; + +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.service.IPrcOrderBillService; + +/** Async: recalc product_amount or redline_amount only (no AR guard). */ +@Component +public class PrcRecalcBillPriceAsyncRunner +{ + private static final Logger log = LoggerFactory.getLogger(PrcRecalcBillPriceAsyncRunner.class); + public static final String CACHE_KEY_PREFIX = "price:billing:recalcPrice:task:"; + public static final int TTL_MINUTES = 120; + + @Autowired + @Lazy + private IPrcOrderBillService orderBillService; + + @Autowired + private RedisCache redisCache; + + @Async("threadPoolTaskExecutor") + public void run(String taskId, String mode, List billIds) + { + 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 + { + orderBillService.recalcBillPriceByBillId(billId, mode); + success++; + } + catch (Exception e) + { + fail++; + appendFail(failTips, billId, e); + log.warn("recalc price failed taskId={} mode={} billId={}", taskId, mode, 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; + put(taskId, BatchAddIncomeBillFeeTask.STATUS_SUCCESS, 100, msg, + fail > 0 && failTips.length() > 0 ? failTips.toString() : null, total, success, fail); + log.info("recalc price done taskId={} mode={} total={} success={} fail={}", + taskId, mode, total, success, fail); + } + catch (Exception e) + { + log.error("recalc price aborted taskId={} mode={}", taskId, mode, 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 void appendFail(StringBuilder failTips, String billId, Exception e) + { + if (failTips.length() >= 800) + { + return; + } + if (failTips.length() > 0) + { + failTips.append("; "); + } + failTips.append(billId).append(":").append(errorMessage(e)); + } + + private static String errorMessage(Exception e) + { + return StringUtils.isEmpty(e.getMessage()) ? e.getClass().getSimpleName() : e.getMessage(); + } +} diff --git a/bms-back/sql/price/billing_recalc_price_menu.sql b/bms-back/sql/price/billing_recalc_price_menu.sql new file mode 100644 index 00000000..7f3f64c5 --- /dev/null +++ b/bms-back/sql/price/billing_recalc_price_menu.sql @@ -0,0 +1,27 @@ +-- billing list: recalc product/redline amount (async; no AR restriction) +SET NAMES utf8mb4; + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT 2332, N'重计费', m.menu_id, 22, '', NULL, 1, 0, 'F', '0', '0', 'price:billing:recalc', '#', 'admin', sysdate(), N'计费列表异步重算产品价/红线价(已出账也可)' +FROM ( + SELECT menu_id FROM sys_menu + WHERE perms = 'price:billing:bill' AND menu_type = 'C' + UNION ALL + SELECT menu_id FROM sys_menu + WHERE component = 'price/billing/bill' AND menu_type = 'C' +) m +WHERE NOT EXISTS (SELECT 1 FROM sys_menu x WHERE x.menu_id = 2332 OR x.perms = 'price:billing:recalc') +LIMIT 1; + +INSERT INTO sys_role_menu (role_id, menu_id) +SELECT 1, 2332 +FROM DUAL +WHERE EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2332) + AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = 2332); + +-- cleanup old split permissions if previously inserted +DELETE FROM sys_role_menu WHERE menu_id = 2333; +DELETE FROM sys_menu WHERE menu_id = 2333 OR perms = 'price:billing:recalcRedline'; +UPDATE sys_menu SET menu_name = N'重计费', perms = 'price:billing:recalc', + remark = N'计费列表异步重算产品价/红线价(已出账也可)' +WHERE menu_id = 2332 OR perms IN ('price:billing:recalcProduct', 'price:billing:recalc'); diff --git a/bms-front/src/api/price/billing.js b/bms-front/src/api/price/billing.js index 91249fd1..aa25bd27 100644 --- a/bms-front/src/api/price/billing.js +++ b/bms-front/src/api/price/billing.js @@ -286,3 +286,48 @@ export function listChargeWeightImportAttachments(query) { params: query }) } + +/** Submit async recalc (PRODUCT / REDLINE) */ +export function submitRecalcBillPrice(mode, data) { + const path = mode === 'REDLINE' ? 'recalcRedline' : 'recalcProduct' + return request({ + url: '/price/billing/bill/' + path, + method: 'post', + data + }) +} + +export function getRecalcBillPriceTask(taskId) { + return request({ + url: '/price/billing/bill/recalcPrice/task/' + taskId, + method: 'get' + }) +} + +/** + * Async recalc product/redline amount; poll until done. + * @param {'PRODUCT'|'REDLINE'} mode + * @param {object} data + * @param {(task: object) => void} [onProgress] + */ +export async function recalcBillPriceAsync(mode, data, onProgress) { + const submitRes = await submitRecalcBillPrice(mode, data) + const d = submitRes && submitRes.data + const taskId = d && (d.taskId || d) + if (!taskId) throw new Error('missing recalc taskId') + const started = Date.now() + const maxMs = 30 * 60 * 1000 + while (Date.now() - started < maxMs) { + const res = await getRecalcBillPriceTask(String(taskId)) + const task = res.data || {} + if (typeof onProgress === 'function') onProgress(task) + if (task.status === 'SUCCESS') return { task } + if (task.status === 'FAIL') { + const err = new Error(task.error || task.message || 'recalc price failed') + err.task = task + throw err + } + await new Promise(resolve => setTimeout(resolve, 800)) + } + throw new Error('recalc price timeout') +} diff --git a/bms-front/src/views/price/billing/bill.vue b/bms-front/src/views/price/billing/bill.vue index d61f3229..97cd8722 100644 --- a/bms-front/src/views/price/billing/bill.vue +++ b/bms-front/src/views/price/billing/bill.vue @@ -8,6 +8,7 @@ search-action-align="center" :search-per-row="4" row-key="billId" + selection > + + + + + + 产品价 + 红线价 + + + + + +