增加产品价和红线价重计费功能
This commit is contained in:
@@ -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<String> billIds;
|
||||
private List<String> orderNos;
|
||||
private List<String> trackingNos;
|
||||
private List<String> transferNos;
|
||||
private List<String> customerCodes;
|
||||
private List<String> productCodes;
|
||||
private String countryCode;
|
||||
private String creditArrearsStatus;
|
||||
private String beginBizDate;
|
||||
private String endBizDate;
|
||||
}
|
||||
@@ -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<ChargeWeightImportRow> 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);
|
||||
}
|
||||
|
||||
+202
-23
@@ -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<PrcOrderBill> 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<String> billIds = new ArrayList<String>();
|
||||
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<PrcIncomeBillFee> fees = incomeBillFeeMapper.selectByBillId(bill.getBillId());
|
||||
if (fees == null || fees.isEmpty() || result.getFeeDetails() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
java.util.Map<String, TmsPriceQuoteFeeLine> quoteByKey = new java.util.HashMap<String, TmsPriceQuoteFeeLine>();
|
||||
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<PrcOrderBill> 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<PrcOrderBill> resolveListScopedBills(String scopeModeRaw, List<String> billIdsRaw,
|
||||
List<String> orderNosRaw, List<String> trackingNosRaw, List<String> transferNosRaw,
|
||||
List<String> customerCodesRaw, List<String> 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<String> orderNos = normalizeCodeList(request.getOrderNos());
|
||||
List<String> trackingNos = normalizeCodeList(request.getTrackingNos());
|
||||
List<String> transferNos = normalizeCodeList(request.getTransferNos());
|
||||
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();
|
||||
List<String> orderNos = normalizeCodeList(orderNosRaw);
|
||||
List<String> trackingNos = normalizeCodeList(trackingNosRaw);
|
||||
List<String> transferNos = normalizeCodeList(transferNosRaw);
|
||||
List<String> customerCodes = normalizeCodeList(customerCodesRaw);
|
||||
List<String> 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<String> billIds = normalizeCodeList(request.getBillIds());
|
||||
List<String> 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<PrcOrderBill> list = self.selectBillList(query);
|
||||
return list == null ? new ArrayList<PrcOrderBill>() : list;
|
||||
}
|
||||
|
||||
+114
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user