订单列表重新计费优化
This commit is contained in:
@@ -43,6 +43,7 @@ import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest;
|
|||||||
import com.bms.price.domain.vo.BillingTaskSubmitRequest;
|
import com.bms.price.domain.vo.BillingTaskSubmitRequest;
|
||||||
import com.bms.price.domain.vo.ChargeWeightImportRow;
|
import com.bms.price.domain.vo.ChargeWeightImportRow;
|
||||||
import com.bms.price.domain.vo.ChargeWeightTemplateRequest;
|
import com.bms.price.domain.vo.ChargeWeightTemplateRequest;
|
||||||
|
import com.bms.price.domain.vo.LocalRebillRequest;
|
||||||
import com.bms.price.domain.vo.RecalcBillPriceRequest;
|
import com.bms.price.domain.vo.RecalcBillPriceRequest;
|
||||||
import com.bms.price.domain.vo.UpdateChargeWeightRequest;
|
import com.bms.price.domain.vo.UpdateChargeWeightRequest;
|
||||||
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||||
@@ -197,6 +198,30 @@ public class BillingController extends BaseController
|
|||||||
return success(orderBillProductCheckService.listOrdersByProduct(query));
|
return success(orderBillProductCheckService.listOrdersByProduct(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order-list rebill from local prc_order_bill only (no Oracle TAB_ORDER).
|
||||||
|
* Poll {@code GET /orderBill/rebill/task/{taskId}}.
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('price:orderBill:rebill')")
|
||||||
|
@Log(title = "Order bill local rebill", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/orderBill/rebill")
|
||||||
|
public AjaxResult submitLocalRebill(@RequestBody LocalRebillRequest request)
|
||||||
|
{
|
||||||
|
return successTask(prcOrderBillService.submitLocalRebill(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('price:orderBill:rebill')")
|
||||||
|
@GetMapping("/orderBill/rebill/task/{taskId}")
|
||||||
|
public AjaxResult getLocalRebillTask(@PathVariable("taskId") String taskId)
|
||||||
|
{
|
||||||
|
BatchAddIncomeBillFeeTask task = prcOrderBillService.getLocalRebillTask(taskId);
|
||||||
|
if (task == null)
|
||||||
|
{
|
||||||
|
return error(MessageUtils.message("price.bill.recalc.taskNotExists"));
|
||||||
|
}
|
||||||
|
return success(task);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order no options for remote-search dropdown (bill list multi search).
|
* Order no options for remote-search dropdown (bill list multi search).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.bms.price.domain.vo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order-list local rebill: use prc_order_bill only (no Oracle TAB_ORDER).
|
||||||
|
* scopeMode SELECTED = billIds; ALL = filters (orderIdentifiers / customer / bizDate...).
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LocalRebillRequest implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** ALL | SELECTED */
|
||||||
|
private String scopeMode;
|
||||||
|
private List<String> billIds;
|
||||||
|
private List<String> orderNos;
|
||||||
|
private List<String> trackingNos;
|
||||||
|
private List<String> transferNos;
|
||||||
|
private List<String> orderIdentifiers;
|
||||||
|
private List<String> customerCodes;
|
||||||
|
private List<String> productCodes;
|
||||||
|
private String countryCode;
|
||||||
|
private String creditArrearsStatus;
|
||||||
|
private String beginBizDate;
|
||||||
|
private String endBizDate;
|
||||||
|
}
|
||||||
@@ -21,6 +21,12 @@ public interface PrcOrderBillMapper
|
|||||||
|
|
||||||
public List<PrcOrderBill> selectPrcOrderBillList(PrcOrderBill bill);
|
public List<PrcOrderBill> selectPrcOrderBillList(PrcOrderBill bill);
|
||||||
|
|
||||||
|
/** bill_id only (same filters / data-scope as list), for large local rebill. */
|
||||||
|
List<String> selectPrcOrderBillIds(PrcOrderBill bill);
|
||||||
|
|
||||||
|
/** Count matching bills (same filters / data-scope as list). */
|
||||||
|
int countPrcOrderBillIds(PrcOrderBill bill);
|
||||||
|
|
||||||
/** Distinct customer+product groups for product-check (order counts in filter window). */
|
/** Distinct customer+product groups for product-check (order counts in filter window). */
|
||||||
List<Map<String, Object>> selectDistinctProductGroups(PrcOrderBill bill);
|
List<Map<String, Object>> selectDistinctProductGroups(PrcOrderBill bill);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.bms.price.domain.vo.BatchAddIncomeBillFeePreview;
|
|||||||
import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest;
|
import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest;
|
||||||
import com.bms.price.domain.vo.ChargeWeightImportRow;
|
import com.bms.price.domain.vo.ChargeWeightImportRow;
|
||||||
import com.bms.price.domain.vo.ChargeWeightTemplateRequest;
|
import com.bms.price.domain.vo.ChargeWeightTemplateRequest;
|
||||||
|
import com.bms.price.domain.vo.LocalRebillRequest;
|
||||||
import com.bms.price.domain.vo.RecalcBillPriceRequest;
|
import com.bms.price.domain.vo.RecalcBillPriceRequest;
|
||||||
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||||
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
||||||
@@ -135,4 +136,25 @@ public interface IPrcOrderBillService
|
|||||||
* Requires existing prc_order_bill; skips AR editable guard.
|
* Requires existing prc_order_bill; skips AR editable guard.
|
||||||
*/
|
*/
|
||||||
void recalcBillPriceByBillId(String billId, String mode);
|
void recalcBillPriceByBillId(String billId, String mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full rebill from local prc_order_bill only (no Oracle upstream).
|
||||||
|
* Rebuilds TmsPriceBillRequest from the bill row then calls {@link #bill}.
|
||||||
|
*/
|
||||||
|
TmsPriceQuoteResult rebillLocalByBillId(String billId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async local rebill by selected billIds or list filters. Returns taskId for progress poll.
|
||||||
|
* Large sets: only bill_id loaded, then processed in pages of 500 with parallel workers.
|
||||||
|
*/
|
||||||
|
String submitLocalRebill(LocalRebillRequest request);
|
||||||
|
|
||||||
|
/** Poll Redis progress for local rebill task. */
|
||||||
|
BatchAddIncomeBillFeeTask getLocalRebillTask(String taskId);
|
||||||
|
|
||||||
|
/** Data-scoped bill_id list for local rebill (same filters as order-bill list). */
|
||||||
|
List<String> selectBillIdsForLocalRebill(PrcOrderBill query);
|
||||||
|
|
||||||
|
/** Data-scoped count for local rebill. */
|
||||||
|
int countBillIdsForLocalRebill(PrcOrderBill query);
|
||||||
}
|
}
|
||||||
|
|||||||
+169
@@ -50,11 +50,13 @@ import com.bms.price.domain.vo.BatchAddIncomeBillFeePreview;
|
|||||||
import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest;
|
import com.bms.price.domain.vo.BatchAddIncomeBillFeeRequest;
|
||||||
import com.bms.price.domain.vo.ChargeWeightImportRow;
|
import com.bms.price.domain.vo.ChargeWeightImportRow;
|
||||||
import com.bms.price.domain.vo.ChargeWeightTemplateRequest;
|
import com.bms.price.domain.vo.ChargeWeightTemplateRequest;
|
||||||
|
import com.bms.price.domain.vo.LocalRebillRequest;
|
||||||
import com.bms.price.domain.vo.RecalcBillPriceRequest;
|
import com.bms.price.domain.vo.RecalcBillPriceRequest;
|
||||||
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||||
import com.bms.price.task.PrcBatchAddIncomeFeeAsyncRunner;
|
import com.bms.price.task.PrcBatchAddIncomeFeeAsyncRunner;
|
||||||
import com.bms.price.task.PrcChargeWeightImportAsyncRunner;
|
import com.bms.price.task.PrcChargeWeightImportAsyncRunner;
|
||||||
import com.bms.price.task.PrcChargeWeightImportAsyncRunner.ChargeWeightImportWorkItem;
|
import com.bms.price.task.PrcChargeWeightImportAsyncRunner.ChargeWeightImportWorkItem;
|
||||||
|
import com.bms.price.task.PrcOrderBillLocalRebillAsyncRunner;
|
||||||
import com.bms.price.task.PrcRecalcBillPriceAsyncRunner;
|
import com.bms.price.task.PrcRecalcBillPriceAsyncRunner;
|
||||||
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
import com.bms.price.domain.vo.TmsPriceBillRequest;
|
||||||
import com.bms.price.domain.vo.TmsOrderBillStatusResult;
|
import com.bms.price.domain.vo.TmsOrderBillStatusResult;
|
||||||
@@ -143,6 +145,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PrcRecalcBillPriceAsyncRunner recalcBillPriceAsyncRunner;
|
private PrcRecalcBillPriceAsyncRunner recalcBillPriceAsyncRunner;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PrcOrderBillLocalRebillAsyncRunner localRebillAsyncRunner;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IBmsBizRuleService bizRuleService;
|
private IBmsBizRuleService bizRuleService;
|
||||||
|
|
||||||
@@ -150,6 +155,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
|||||||
private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20;
|
private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20;
|
||||||
private static final int CHARGE_WEIGHT_IMPORT_MAX = 5000;
|
private static final int CHARGE_WEIGHT_IMPORT_MAX = 5000;
|
||||||
private static final int RECALC_PRICE_MAX = 5000;
|
private static final int RECALC_PRICE_MAX = 5000;
|
||||||
|
/** Soft max for one local-rebill job (id-only load + paged parallel). */
|
||||||
|
private static final int LOCAL_REBILL_MAX = 100000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -694,6 +701,168 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
|||||||
persistProductOrRedlineOnly(bill, result, productMode);
|
persistProductOrRedlineOnly(bill, result, productMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public TmsPriceQuoteResult rebillLocalByBillId(String billId)
|
||||||
|
{
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(bill.getOrderNo()))
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.orderNoRequired"));
|
||||||
|
}
|
||||||
|
if (bill.getBizDate() == null)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String submitLocalRebill(LocalRebillRequest request)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.recalc.filterRequired"));
|
||||||
|
}
|
||||||
|
PrcOrderBill query = buildLocalRebillQuery(request);
|
||||||
|
int total = self.countBillIdsForLocalRebill(query);
|
||||||
|
if (total <= 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.recalc.noMatch"));
|
||||||
|
}
|
||||||
|
if (total > LOCAL_REBILL_MAX)
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.recalc.tooMany",
|
||||||
|
Integer.valueOf(LOCAL_REBILL_MAX)));
|
||||||
|
}
|
||||||
|
// Id-only: avoid loading full prc_order_bill rows for large jobs.
|
||||||
|
List<String> billIds = self.selectBillIdsForLocalRebill(query);
|
||||||
|
if (billIds == null || billIds.isEmpty())
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.recalc.noMatch"));
|
||||||
|
}
|
||||||
|
String taskId = IdUtils.fastSimpleUUID();
|
||||||
|
localRebillAsyncRunner.put(taskId, BatchAddIncomeBillFeeTask.STATUS_PENDING, 0, "queued", null,
|
||||||
|
billIds.size(), 0, 0);
|
||||||
|
localRebillAsyncRunner.run(taskId, billIds);
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatchAddIncomeBillFeeTask getLocalRebillTask(String taskId)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(taskId) || localRebillAsyncRunner == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return localRebillAsyncRunner.get(taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
|
||||||
|
public List<String> selectBillIdsForLocalRebill(PrcOrderBill query)
|
||||||
|
{
|
||||||
|
if (query == null)
|
||||||
|
{
|
||||||
|
return new ArrayList<String>();
|
||||||
|
}
|
||||||
|
normalizeLocalRebillQuery(query);
|
||||||
|
List<String> ids = orderBillMapper.selectPrcOrderBillIds(query);
|
||||||
|
return ids == null ? new ArrayList<String>() : ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
|
||||||
|
public int countBillIdsForLocalRebill(PrcOrderBill query)
|
||||||
|
{
|
||||||
|
if (query == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
normalizeLocalRebillQuery(query);
|
||||||
|
return orderBillMapper.countPrcOrderBillIds(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrcOrderBill buildLocalRebillQuery(LocalRebillRequest request)
|
||||||
|
{
|
||||||
|
String scopeMode = StringUtils.nvl(request.getScopeMode(), "").trim().toUpperCase();
|
||||||
|
PrcOrderBill query = new PrcOrderBill();
|
||||||
|
if ("SELECTED".equals(scopeMode))
|
||||||
|
{
|
||||||
|
List<String> billIds = normalizeCodeList(request.getBillIds());
|
||||||
|
if (billIds == null || billIds.isEmpty())
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.recalc.selectedRequired"));
|
||||||
|
}
|
||||||
|
query.setBillIds(billIds);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
List<String> orderNos = normalizeCodeList(request.getOrderNos());
|
||||||
|
List<String> trackingNos = normalizeCodeList(request.getTrackingNos());
|
||||||
|
List<String> transferNos = normalizeCodeList(request.getTransferNos());
|
||||||
|
List<String> orderIdentifiers = normalizeCodeList(request.getOrderIdentifiers());
|
||||||
|
List<String> customerCodes = normalizeCodeList(request.getCustomerCodes());
|
||||||
|
List<String> 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();
|
||||||
|
boolean hasFilter = (orderNos != null && !orderNos.isEmpty())
|
||||||
|
|| (trackingNos != null && !trackingNos.isEmpty())
|
||||||
|
|| (transferNos != null && !transferNos.isEmpty())
|
||||||
|
|| (orderIdentifiers != null && !orderIdentifiers.isEmpty())
|
||||||
|
|| (customerCodes != null && !customerCodes.isEmpty())
|
||||||
|
|| (productCodes != null && !productCodes.isEmpty())
|
||||||
|
|| StringUtils.isNotEmpty(creditArrearsStatus)
|
||||||
|
|| StringUtils.isNotEmpty(countryCode)
|
||||||
|
|| StringUtils.isNotEmpty(beginBizDate)
|
||||||
|
|| StringUtils.isNotEmpty(endBizDate);
|
||||||
|
if (!hasFilter)
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.bill.recalc.filterRequired"));
|
||||||
|
}
|
||||||
|
query.setOrderNos(orderNos);
|
||||||
|
query.setTrackingNos(trackingNos);
|
||||||
|
query.setTransferNos(transferNos);
|
||||||
|
query.setOrderIdentifiers(orderIdentifiers);
|
||||||
|
query.setCustomerCodes(customerCodes);
|
||||||
|
query.setProductCodes(productCodes);
|
||||||
|
query.setCreditArrearsStatus(creditArrearsStatus);
|
||||||
|
query.setCountryCode(countryCode);
|
||||||
|
if (beginBizDate != null)
|
||||||
|
{
|
||||||
|
query.getParams().put("beginBizDate", beginBizDate);
|
||||||
|
}
|
||||||
|
if (endBizDate != null)
|
||||||
|
{
|
||||||
|
query.getParams().put("endBizDate", endBizDate);
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeLocalRebillQuery(PrcOrderBill query)
|
||||||
|
{
|
||||||
|
query.setOrderNos(normalizeCodeList(query.getOrderNos()));
|
||||||
|
query.setCustomerCodes(normalizeCodeList(query.getCustomerCodes()));
|
||||||
|
query.setProductCodes(normalizeCodeList(query.getProductCodes()));
|
||||||
|
query.setTrackingNos(normalizeCodeList(query.getTrackingNos()));
|
||||||
|
query.setTransferNos(normalizeCodeList(query.getTransferNos()));
|
||||||
|
query.setOrderIdentifiers(normalizeCodeList(query.getOrderIdentifiers()));
|
||||||
|
query.setBillIds(normalizeCodeList(query.getBillIds()));
|
||||||
|
}
|
||||||
|
|
||||||
private static String normalizeRecalcMode(String mode)
|
private static String normalizeRecalcMode(String mode)
|
||||||
{
|
{
|
||||||
String m = StringUtils.nvl(mode, "").trim().toUpperCase();
|
String m = StringUtils.nvl(mode, "").trim().toUpperCase();
|
||||||
|
|||||||
+222
@@ -0,0 +1,222 @@
|
|||||||
|
package com.bms.price.task;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
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.config.BillingProperties;
|
||||||
|
import com.bms.price.domain.BatchAddIncomeBillFeeTask;
|
||||||
|
import com.bms.price.service.IPrcOrderBillService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async local rebill from prc_order_bill only (no Oracle / no MQ).
|
||||||
|
* Large jobs: process in pages of {@link #PAGE_SIZE}; each page runs in a short-lived worker pool.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class PrcOrderBillLocalRebillAsyncRunner
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PrcOrderBillLocalRebillAsyncRunner.class);
|
||||||
|
|
||||||
|
public static final String CACHE_KEY_PREFIX = "price:billing:localRebill:task:";
|
||||||
|
public static final int TTL_MINUTES = 120;
|
||||||
|
/** Bill this many, wait for page done, then next page. */
|
||||||
|
public static final int PAGE_SIZE = 500;
|
||||||
|
public static final int MIN_THREADS = 2;
|
||||||
|
public static final int MAX_THREADS = 16;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private IPrcOrderBillService orderBillService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BillingProperties billingProperties;
|
||||||
|
|
||||||
|
@Async("threadPoolTaskExecutor")
|
||||||
|
public void run(String taskId, List<String> billIds)
|
||||||
|
{
|
||||||
|
int total = billIds == null ? 0 : billIds.size();
|
||||||
|
AtomicInteger success = new AtomicInteger(0);
|
||||||
|
AtomicInteger fail = new AtomicInteger(0);
|
||||||
|
StringBuilder failTips = new StringBuilder();
|
||||||
|
int threads = clampThreads(billingProperties == null ? null : billingProperties.getOrderSourceSyncThreads());
|
||||||
|
ExecutorService pool = newWorkerPool(threads, "local-rebill");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
int processed = 0;
|
||||||
|
for (int from = 0; from < total; from += PAGE_SIZE)
|
||||||
|
{
|
||||||
|
int to = Math.min(from + PAGE_SIZE, total);
|
||||||
|
List<String> page = billIds.subList(from, to);
|
||||||
|
List<Callable<Boolean>> jobs = new ArrayList<Callable<Boolean>>(page.size());
|
||||||
|
for (String billId : page)
|
||||||
|
{
|
||||||
|
final String one = billId;
|
||||||
|
jobs.add(() -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
orderBillService.rebillLocalByBillId(one);
|
||||||
|
success.incrementAndGet();
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
fail.incrementAndGet();
|
||||||
|
appendFail(failTips, one, e);
|
||||||
|
log.warn("local rebill failed taskId={} billId={}", taskId, one, e);
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
invokeAllQuiet(pool, jobs);
|
||||||
|
processed = to;
|
||||||
|
int percent = Math.min(99, (int) Math.floor(100.0 * processed / total));
|
||||||
|
put(taskId, BatchAddIncomeBillFeeTask.STATUS_RUNNING, percent,
|
||||||
|
"processing " + processed + "/" + total + " page=" + ((from / PAGE_SIZE) + 1),
|
||||||
|
null, total, success.get(), fail.get());
|
||||||
|
}
|
||||||
|
String msg = "success=" + success.get() + ", fail=" + fail.get();
|
||||||
|
put(taskId, BatchAddIncomeBillFeeTask.STATUS_SUCCESS, 100, msg,
|
||||||
|
fail.get() > 0 && failTips.length() > 0 ? failTips.toString() : null,
|
||||||
|
total, success.get(), fail.get());
|
||||||
|
log.info("local rebill done taskId={} total={} success={} fail={} threads={} pageSize={}",
|
||||||
|
taskId, total, success.get(), fail.get(), threads, PAGE_SIZE);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.error("local rebill aborted taskId={}", taskId, e);
|
||||||
|
put(taskId, BatchAddIncomeBillFeeTask.STATUS_FAIL, 100, "failed", errorMessage(e),
|
||||||
|
total, success.get(), fail.get());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
shutdownPool(pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 invokeAllQuiet(ExecutorService pool, List<Callable<Boolean>> jobs) throws InterruptedException
|
||||||
|
{
|
||||||
|
List<Future<Boolean>> futures = pool.invokeAll(jobs);
|
||||||
|
for (Future<Boolean> f : futures)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
f.get();
|
||||||
|
}
|
||||||
|
catch (ExecutionException e)
|
||||||
|
{
|
||||||
|
// counted in job body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExecutorService newWorkerPool(int threads, String namePrefix)
|
||||||
|
{
|
||||||
|
AtomicInteger seq = new AtomicInteger(0);
|
||||||
|
ThreadFactory factory = r -> {
|
||||||
|
Thread t = new Thread(r);
|
||||||
|
t.setName(namePrefix + "-" + seq.incrementAndGet());
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
return Executors.newFixedThreadPool(Math.max(1, threads), factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void shutdownPool(ExecutorService pool)
|
||||||
|
{
|
||||||
|
if (pool == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pool.shutdown();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
|
||||||
|
{
|
||||||
|
pool.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
pool.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clampThreads(Integer n)
|
||||||
|
{
|
||||||
|
int v = n == null ? 8 : n.intValue();
|
||||||
|
if (v < MIN_THREADS)
|
||||||
|
{
|
||||||
|
return MIN_THREADS;
|
||||||
|
}
|
||||||
|
if (v > MAX_THREADS)
|
||||||
|
{
|
||||||
|
return MAX_THREADS;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendFail(StringBuilder failTips, String billId, Exception e)
|
||||||
|
{
|
||||||
|
synchronized (failTips)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -409,9 +409,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
limit #{limit}
|
limit #{limit}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectPrcOrderBillList" parameterType="PrcOrderBill" resultMap="PrcOrderBillResult">
|
<!-- Shared list filters for full row / id-only / count queries. -->
|
||||||
<include refid="selectPrcOrderBillVo"/>
|
<sql id="prcOrderBillListWhere">
|
||||||
<where>
|
|
||||||
<if test="billId != null and billId != ''">
|
<if test="billId != null and billId != ''">
|
||||||
AND p.bill_id = #{billId}
|
AND p.bill_id = #{billId}
|
||||||
</if>
|
</if>
|
||||||
@@ -540,10 +539,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
AND date_format(p.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
|
AND date_format(p.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
|
||||||
</if>
|
</if>
|
||||||
<include refid="orderBillDataScopeFilter"/>
|
<include refid="orderBillDataScopeFilter"/>
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectPrcOrderBillList" parameterType="PrcOrderBill" resultMap="PrcOrderBillResult">
|
||||||
|
<include refid="selectPrcOrderBillVo"/>
|
||||||
|
<where>
|
||||||
|
<include refid="prcOrderBillListWhere"/>
|
||||||
</where>
|
</where>
|
||||||
order by p.create_time desc, p.bill_id desc
|
order by p.create_time desc, p.bill_id desc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- Id-only for large local rebill (avoid loading full rows into memory). -->
|
||||||
|
<select id="selectPrcOrderBillIds" parameterType="PrcOrderBill" resultType="string">
|
||||||
|
select p.bill_id
|
||||||
|
from prc_order_bill p
|
||||||
|
<where>
|
||||||
|
<include refid="prcOrderBillListWhere"/>
|
||||||
|
</where>
|
||||||
|
order by p.bill_id asc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countPrcOrderBillIds" parameterType="PrcOrderBill" resultType="int">
|
||||||
|
select count(1)
|
||||||
|
from prc_order_bill p
|
||||||
|
<where>
|
||||||
|
<include refid="prcOrderBillListWhere"/>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- rename_bill_tables.sql 已将需求文档中的 prc_bill 映射为本项目的 prc_order_bill。 -->
|
<!-- rename_bill_tables.sql 已将需求文档中的 prc_bill 映射为本项目的 prc_order_bill。 -->
|
||||||
<!-- 生成弹窗预览:按客户、截止日、币种和订单号/运单号/转单号 OR 条件分页查询未入账订单。 -->
|
<!-- 生成弹窗预览:按客户、截止日、币种和订单号/运单号/转单号 OR 条件分页查询未入账订单。 -->
|
||||||
<sql id="orderIdentifierCondition">
|
<sql id="orderIdentifierCondition">
|
||||||
|
|||||||
@@ -85,6 +85,50 @@ export function listOrderBillsByProduct(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Order-list local rebill (prc_order_bill only, no Oracle). Returns { taskId }. */
|
||||||
|
export function submitLocalRebill(data) {
|
||||||
|
return request({
|
||||||
|
url: '/price/billing/orderBill/rebill',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
timeout: 120000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalRebillTask(taskId) {
|
||||||
|
return request({
|
||||||
|
url: '/price/billing/orderBill/rebill/task/' + taskId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async local rebill; poll until done.
|
||||||
|
* @param {object} data LocalRebillRequest
|
||||||
|
* @param {(task: object) => void} [onProgress]
|
||||||
|
*/
|
||||||
|
export async function localRebillAsync(data, onProgress) {
|
||||||
|
const submitRes = await submitLocalRebill(data)
|
||||||
|
const d = submitRes && submitRes.data
|
||||||
|
const taskId = d && (d.taskId || d)
|
||||||
|
if (!taskId) throw new Error('missing local rebill taskId')
|
||||||
|
const started = Date.now()
|
||||||
|
const maxMs = 30 * 60 * 1000
|
||||||
|
while (Date.now() - started < maxMs) {
|
||||||
|
const res = await getLocalRebillTask(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 || 'local rebill failed')
|
||||||
|
err.task = task
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800))
|
||||||
|
}
|
||||||
|
throw new Error('local rebill timeout')
|
||||||
|
}
|
||||||
|
|
||||||
/** Receivable bill detail */
|
/** Receivable bill detail */
|
||||||
export function getBill(billId) {
|
export function getBill(billId) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
icon="Refresh"
|
icon="Refresh"
|
||||||
|
:loading="batchLoading"
|
||||||
v-hasPermi="['price:orderBill:rebill']"
|
v-hasPermi="['price:orderBill:rebill']"
|
||||||
@click="openFilterRebill()"
|
@click="openFilterRebill()"
|
||||||
>重新计费</el-button>
|
>重新计费</el-button>
|
||||||
@@ -91,7 +92,35 @@
|
|||||||
<el-tag size="small" :type="billingFailTagType(row.billingFailStatus)">{{ billingFailLabel(row.billingFailStatus) }}</el-tag>
|
<el-tag size="small" :type="billingFailTagType(row.billingFailStatus)">{{ billingFailLabel(row.billingFailStatus) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</basic-table>
|
</basic-table>
|
||||||
<order-source-sync-dialog ref="syncDialogRef" @success="onRebillSuccess" />
|
<el-dialog
|
||||||
|
v-model="rebillProgressVisible"
|
||||||
|
title="本地重新计费"
|
||||||
|
width="480px"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="!batchLoading"
|
||||||
|
:close-on-press-escape="!batchLoading"
|
||||||
|
:show-close="!batchLoading"
|
||||||
|
>
|
||||||
|
<el-progress
|
||||||
|
:percentage="rebillProgressPercent"
|
||||||
|
:status="rebillProgressStatus"
|
||||||
|
:stroke-width="14"
|
||||||
|
striped
|
||||||
|
:striped-flow="batchLoading"
|
||||||
|
/>
|
||||||
|
<div class="rebill-progress__msg">{{ rebillProgressMessage }}</div>
|
||||||
|
<div class="rebill-progress__text">
|
||||||
|
总数 {{ rebillProgressTotal }}
|
||||||
|
/ 成功 {{ rebillProgressSuccess }}
|
||||||
|
/ 失败 {{ rebillProgressFail }}
|
||||||
|
</div>
|
||||||
|
<p v-if="batchLoading" class="rebill-progress__hint">请勿关闭页面,大量订单重新计费可能需要一些时间</p>
|
||||||
|
<template #footer>
|
||||||
|
<el-button v-if="!batchLoading" type="primary" @click="rebillProgressVisible = false">关闭</el-button>
|
||||||
|
<el-button v-else disabled>执行中...</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
<order-source-sync-log-drawer ref="logDrawerRef" />
|
<order-source-sync-log-drawer ref="logDrawerRef" />
|
||||||
<order-bill-product-check-drawer ref="productCheckDrawerRef" />
|
<order-bill-product-check-drawer ref="productCheckDrawerRef" />
|
||||||
</div>
|
</div>
|
||||||
@@ -99,18 +128,22 @@
|
|||||||
|
|
||||||
<script setup name="OrderBillQuery">
|
<script setup name="OrderBillQuery">
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { listOrderBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions } from '@/api/price/billing'
|
import { listOrderBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions, localRebillAsync } from '@/api/price/billing'
|
||||||
import { syncOrderSourceSelected } from '@/api/monitor/orderSource'
|
|
||||||
import OrderSourceSyncDialog from '../../monitor/orderSource/components/OrderSourceSyncDialog.vue'
|
|
||||||
import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
|
import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
|
||||||
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
|
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
|
||||||
|
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const syncDialogRef = ref()
|
|
||||||
const logDrawerRef = ref()
|
const logDrawerRef = ref()
|
||||||
const productCheckDrawerRef = ref()
|
const productCheckDrawerRef = ref()
|
||||||
const checkLoading = ref(false)
|
const checkLoading = ref(false)
|
||||||
const batchLoading = ref(false)
|
const batchLoading = ref(false)
|
||||||
|
const rebillProgressVisible = ref(false)
|
||||||
|
const rebillProgressPercent = ref(0)
|
||||||
|
const rebillProgressMessage = ref('')
|
||||||
|
const rebillProgressStatus = ref(undefined)
|
||||||
|
const rebillProgressTotal = ref(0)
|
||||||
|
const rebillProgressSuccess = ref(0)
|
||||||
|
const rebillProgressFail = ref(0)
|
||||||
const tableHeight = ref(480)
|
const tableHeight = ref(480)
|
||||||
let tableResizeObserver = null
|
let tableResizeObserver = null
|
||||||
|
|
||||||
@@ -293,6 +326,62 @@ function handleCheckProducts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runLocalRebill(payload, confirmText) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(confirmText, '二次确认', { type: 'warning' })
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batchLoading.value = true
|
||||||
|
rebillProgressVisible.value = true
|
||||||
|
rebillProgressPercent.value = 0
|
||||||
|
rebillProgressStatus.value = undefined
|
||||||
|
rebillProgressMessage.value = '任务提交中…'
|
||||||
|
rebillProgressTotal.value = 0
|
||||||
|
rebillProgressSuccess.value = 0
|
||||||
|
rebillProgressFail.value = 0
|
||||||
|
try {
|
||||||
|
const { task } = await localRebillAsync(payload, (t) => {
|
||||||
|
const total = Number(t.total) || 0
|
||||||
|
const success = Number(t.success) || 0
|
||||||
|
const fail = Number(t.fail) || 0
|
||||||
|
rebillProgressPercent.value = Math.min(100, Number(t.percent) || 0)
|
||||||
|
rebillProgressTotal.value = total
|
||||||
|
rebillProgressSuccess.value = success
|
||||||
|
rebillProgressFail.value = fail
|
||||||
|
if (t.status === 'PENDING') {
|
||||||
|
rebillProgressMessage.value = '排队中…'
|
||||||
|
} else if (t.status === 'RUNNING') {
|
||||||
|
rebillProgressMessage.value = '正在重新计费 ' + success + '/' + total +
|
||||||
|
(fail ? ('(失败 ' + fail + ')') : '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const success = Number(task && task.success) || 0
|
||||||
|
const fail = Number(task && task.fail) || 0
|
||||||
|
rebillProgressPercent.value = 100
|
||||||
|
rebillProgressStatus.value = fail > 0 ? 'warning' : 'success'
|
||||||
|
rebillProgressTotal.value = Number(task && task.total) || rebillProgressTotal.value
|
||||||
|
rebillProgressSuccess.value = success
|
||||||
|
rebillProgressFail.value = fail
|
||||||
|
rebillProgressMessage.value = '完成:成功 ' + success + ' / 失败 ' + fail +
|
||||||
|
(fail && task.error ? (';' + task.error) : '')
|
||||||
|
tableRef.value?.refresh?.()
|
||||||
|
} catch (e) {
|
||||||
|
const task = e && e.task
|
||||||
|
rebillProgressStatus.value = 'exception'
|
||||||
|
rebillProgressMessage.value = (e && e.message) || '重新计费失败'
|
||||||
|
if (task) {
|
||||||
|
rebillProgressPercent.value = Math.min(100, Number(task.percent) || 100)
|
||||||
|
rebillProgressTotal.value = Number(task.total) || 0
|
||||||
|
rebillProgressSuccess.value = Number(task.success) || 0
|
||||||
|
rebillProgressFail.value = Number(task.fail) || 0
|
||||||
|
tableRef.value?.refresh?.()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
batchLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openFilterRebill() {
|
function openFilterRebill() {
|
||||||
const identifiers = currentIdentifiers()
|
const identifiers = currentIdentifiers()
|
||||||
if (!identifiers.length) {
|
if (!identifiers.length) {
|
||||||
@@ -303,30 +392,21 @@ function openFilterRebill() {
|
|||||||
ElMessage.warning('单次最多重新计费 ' + MAX_REBILL + ' 个单号')
|
ElMessage.warning('单次最多重新计费 ' + MAX_REBILL + ' 个单号')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ElMessageBox.confirm(
|
const filter = buildRebillFilter()
|
||||||
'将按 ' + identifiers.length + ' 个单号查上游订单并重新计费,确认继续?',
|
runLocalRebill(
|
||||||
'二次确认',
|
{
|
||||||
{ type: 'warning' }
|
scopeMode: 'ALL',
|
||||||
).then(() => {
|
orderIdentifiers: filter.orderIdentifiers || identifiers,
|
||||||
syncDialogRef.value?.open(buildRebillFilter(), true)
|
customerCodes: filter.customerCodes || []
|
||||||
}).catch(() => {})
|
},
|
||||||
|
'将按 ' + identifiers.length + ' 个单号从本地订单表重新计费(不查上游),确认继续?'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toSyncRow(row) {
|
|
||||||
return {
|
|
||||||
orderId: row.orderNo,
|
|
||||||
orgOrderId: row.trackingNo,
|
|
||||||
transferOrderId: row.transferNo,
|
|
||||||
customerCode: row.customerCode,
|
|
||||||
pickupDate: row.bizDate || row.pickupDate || row.orderTime || null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function handleBatchRebill(selection) {
|
function handleBatchRebill(selection) {
|
||||||
const rows = selection || []
|
const rows = selection || []
|
||||||
if (!rows.length) return
|
if (!rows.length) return
|
||||||
const missingTime = rows.filter(row => !toSyncRow(row).pickupDate).length
|
const missingTime = rows.filter(row => !(row.bizDate || row.pickupDate || row.orderTime)).length
|
||||||
if (missingTime) {
|
if (missingTime) {
|
||||||
ElMessage.warning(missingTime + ' 条没有计费时间,无法重新计费')
|
ElMessage.warning(missingTime + ' 条没有计费时间,无法重新计费')
|
||||||
return
|
return
|
||||||
@@ -335,20 +415,15 @@ function handleBatchRebill(selection) {
|
|||||||
ElMessage.warning('选中重新计费最多 200 条')
|
ElMessage.warning('选中重新计费最多 200 条')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ElMessageBox.confirm('确认重新计费选中的 ' + rows.length + ' 条订单?', '二次确认', { type: 'warning' }).then(() => {
|
const billIds = rows.map(r => r.billId).filter(Boolean)
|
||||||
batchLoading.value = true
|
if (!billIds.length) {
|
||||||
return syncOrderSourceSelected(rows.map(toSyncRow))
|
ElMessage.warning('选中行缺少 billId,无法重新计费')
|
||||||
}).then(res => {
|
return
|
||||||
const data = (res && res.data) || {}
|
}
|
||||||
ElMessage.success('完成:成功 ' + (data.ok || 0) + ' / 失败 ' + (data.fail || 0))
|
runLocalRebill(
|
||||||
tableRef.value?.refresh?.()
|
{ scopeMode: 'SELECTED', billIds },
|
||||||
}).catch(() => {}).finally(() => {
|
'确认用本地订单数据重新计费选中的 ' + rows.length + ' 条订单?'
|
||||||
batchLoading.value = false
|
)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRebillSuccess() {
|
|
||||||
tableRef.value?.refresh?.()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchOrderIdentifiers(keyword) {
|
function searchOrderIdentifiers(keyword) {
|
||||||
@@ -418,4 +493,7 @@ onBeforeUnmount(() => {
|
|||||||
.order-bill-query :deep(.basic-form .el-form-item .el-date-editor) {
|
.order-bill-query :deep(.basic-form .el-form-item .el-date-editor) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.rebill-progress__msg { margin-top: 12px; color: var(--el-text-color-regular); }
|
||||||
|
.rebill-progress__text { margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary); }
|
||||||
|
.rebill-progress__hint { margin-top: 12px; font-size: 12px; color: var(--el-color-warning); }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user