更新价格计算逻辑,添加套餐价格匹配和计费相关的国际化消息,优化数据库查询和删除操作
This commit is contained in:
@@ -153,6 +153,9 @@ price.quote.noCustomerPackage=客户未绑定套餐,无法试算
|
||||
price.quote.noActivePackageVersion=计费日无生效套餐版本,无法试算
|
||||
price.quote.quoteIdRequired=报价ID为空
|
||||
price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细
|
||||
price.quote.packageZonePriceNotHit=已匹配分区,但套餐版本无该分区/重量段价格
|
||||
price.quote.packagePriceNotHit=套餐版本无匹配的国家/重量段价格
|
||||
price.quote.productNotInPackage=产品不在客户套餐版本中,无法试算
|
||||
|
||||
# receivable bill
|
||||
price.bill.orderNoRequired=订单号为空
|
||||
@@ -163,6 +166,9 @@ price.bill.alreadyInArBill=订单{0}已生成应收账单,作废后方可重
|
||||
price.bill.bizDateRequired=业务日期为空
|
||||
price.bill.noCustomerPackage=客户未绑定套餐,无法计费
|
||||
price.bill.noActivePackageVersion=计费日无生效套餐版本,无法计费
|
||||
price.bill.packageZonePriceNotHit=已匹配分区,但套餐版本无该分区/重量段价格
|
||||
price.bill.packagePriceNotHit=套餐版本无匹配的国家/重量段价格
|
||||
price.bill.productNotInPackage=产品不在客户套餐版本中,无法计费
|
||||
price.bill.fee.scIdRequired=附加费不能为空
|
||||
price.bill.fee.surchargeNotFound=附加费配置不存在或已停用
|
||||
price.bill.fee.notFound=费用明细不存在
|
||||
|
||||
@@ -8,7 +8,7 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* TMS freight quote response.
|
||||
* matchSource: PACKAGE_FIXED / PACKAGE_DISCOUNT / PRODUCT_DEFAULT
|
||||
* matchSource: PACKAGE_FIXED / PACKAGE_DISCOUNT
|
||||
*/
|
||||
@Data
|
||||
public class TmsPriceQuoteResult implements Serializable
|
||||
|
||||
+12
-3
@@ -54,7 +54,10 @@ public interface PrcPriceSetDetailMapper
|
||||
@Param("priceSetId") Long priceSetId,
|
||||
@Param("psvId") Long psvId);
|
||||
|
||||
/** FIXED: product + country + zone + productCountryId / weight; optional psvId scopes to version */
|
||||
/**
|
||||
* FIXED match on package snapshot: product + country + zone + weight band.
|
||||
* Does not depend on live product_country.detail_id.
|
||||
*/
|
||||
public PrcPriceSetDetail selectFixedMatch(
|
||||
@Param("priceSetId") Long priceSetId,
|
||||
@Param("psvId") Long psvId,
|
||||
@@ -62,8 +65,14 @@ public interface PrcPriceSetDetailMapper
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("zoneCode") String zoneCode,
|
||||
@Param("productCountryId") Long productCountryId,
|
||||
@Param("weightLabel") String weightLabel);
|
||||
@Param("weight") BigDecimal weight);
|
||||
|
||||
/** Count version details that have zone for product + country (postcode gate). */
|
||||
public int countZonedByPsvProductCountry(
|
||||
@Param("psvId") Long psvId,
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName);
|
||||
|
||||
/** DISCOUNT by product; optional psvId scopes to version */
|
||||
public PrcPriceSetDetail selectDiscountMatch(
|
||||
|
||||
@@ -16,6 +16,8 @@ public interface PrcQuoteMapper
|
||||
|
||||
public int insertPrcQuote(PrcQuote quote);
|
||||
|
||||
public int deletePrcQuoteById(String quoteId);
|
||||
|
||||
public int updateQuoteAmounts(@Param("quoteId") String quoteId,
|
||||
@Param("surchargeAmount") BigDecimal surchargeAmount,
|
||||
@Param("payableTotal") BigDecimal payableTotal,
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.bms.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.bms.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.bms.price.domain.vo.TmsOrderBillStatusResult;
|
||||
|
||||
/** 订单计费:落库 prc_order_bill + prc_income_bill/+fee + prc_pay_bill/+fee。 */
|
||||
/** Order billing: persist prc_order_bill + prc_income_bill/+fee. Payable (prc_pay_bill/+fee) skipped for now. */
|
||||
public interface IPrcOrderBillService
|
||||
{
|
||||
TmsPriceQuoteResult bill(TmsPriceBillRequest request);
|
||||
|
||||
+10
-8
@@ -890,7 +890,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
orderBillMapper.updatePrcOrderBill(bill);
|
||||
incomeBillFeeMapper.deleteByBillId(bill.getBillId());
|
||||
payBillFeeMapper.deleteByBillId(bill.getBillId());
|
||||
// Payable not in scope yet: skip prc_pay_bill_fee cleanup/write.
|
||||
// payBillFeeMapper.deleteByBillId(bill.getBillId());
|
||||
}
|
||||
PrcIncomeBill incomeBill = persistIncomeBill(bill, result);
|
||||
List<PrcIncomeBillFee> fees = toFeeRows(bill.getBillId(),
|
||||
@@ -900,13 +901,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
incomeBillFeeMapper.insertBatch(fees);
|
||||
}
|
||||
orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId()));
|
||||
PrcPayBill payBill = persistPayBill(bill, result);
|
||||
List<PrcPayBillFee> payFees = toPayFeeRows(bill.getBillId(),
|
||||
payBill == null ? null : payBill.getPayBillId(), result);
|
||||
if (!payFees.isEmpty())
|
||||
{
|
||||
payBillFeeMapper.insertBatch(payFees);
|
||||
}
|
||||
// Payable not in scope yet: do not write prc_pay_bill / prc_pay_bill_fee.
|
||||
// PrcPayBill payBill = persistPayBill(bill, result);
|
||||
// List<PrcPayBillFee> payFees = toPayFeeRows(bill.getBillId(),
|
||||
// payBill == null ? null : payBill.getPayBillId(), result);
|
||||
// if (!payFees.isEmpty())
|
||||
// {
|
||||
// payBillFeeMapper.insertBatch(payFees);
|
||||
// }
|
||||
String requestJson = toRequestJson(request);
|
||||
persistBillExt(bill, request.getUpstreamRow());
|
||||
result.setRequestJson(requestJson);
|
||||
|
||||
+341
-145
@@ -60,7 +60,9 @@ import com.bms.price.service.ITmsPriceQuoteService;
|
||||
/**
|
||||
* Shared quote/bill calc: customer bind + package version by bizDate (version effective_time).
|
||||
* Bind pr_begindate/pr_enddate is not used for billing time gate.
|
||||
* No bind / no active version at bill time: quote and bill both fail hard (no PRODUCT_DEFAULT).
|
||||
* Sales price comes from package snapshot only (FIXED detail / DISCOUNT on catalog base).
|
||||
* No bind / no active version / package miss: quote and bill both fail hard (no PRODUCT_DEFAULT).
|
||||
* product_country is only for product_amount/cost and DISCOUNT freight base.
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@@ -71,7 +73,6 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
private static final String TYPE_DISCOUNT = "DISCOUNT";
|
||||
private static final String SRC_FIXED = "PACKAGE_FIXED";
|
||||
private static final String SRC_DISCOUNT = "PACKAGE_DISCOUNT";
|
||||
private static final String SRC_DEFAULT = "PRODUCT_DEFAULT";
|
||||
|
||||
private static final String FEE_BASIC = "BASIC_FREIGHT";
|
||||
private static final String FEE_REG = "REGISTRATION_FEE";
|
||||
@@ -146,18 +147,27 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
PrcQuote cached = quoteMapper.selectPrcQuoteByKey(quoteKey);
|
||||
if (cached != null)
|
||||
{
|
||||
List<PrcQuoteFee> fees = quoteFeeMapper.selectByQuoteId(cached.getQuoteId());
|
||||
if (!isStaleSurchargeFees(fees))
|
||||
// Legacy PRODUCT_DEFAULT (or no package stamp) must be recomputed after package-snapshot pricing.
|
||||
if (isStaleCachedQuote(cached))
|
||||
{
|
||||
return toResult(cached, fees, true);
|
||||
quoteFeeMapper.deleteByQuoteId(cached.getQuoteId());
|
||||
quoteMapper.deletePrcQuoteById(cached.getQuoteId());
|
||||
}
|
||||
else
|
||||
{
|
||||
List<PrcQuoteFee> fees = quoteFeeMapper.selectByQuoteId(cached.getQuoteId());
|
||||
if (!isStaleSurchargeFees(fees))
|
||||
{
|
||||
return toResult(cached, fees, true);
|
||||
}
|
||||
TmsPriceQuoteResult refreshed = doQuote(request, bizDate);
|
||||
applySurchargesAndFees(refreshed);
|
||||
replaceQuoteFees(cached.getQuoteId(), refreshed);
|
||||
refreshed.setQuoteId(cached.getQuoteId());
|
||||
refreshed.setFromCache(false);
|
||||
billSearchStore.save(EsLogType.QUOTE, cached.getQuoteId(), toEsRequest(request), toEsResponse(refreshed));
|
||||
return refreshed;
|
||||
}
|
||||
TmsPriceQuoteResult refreshed = doQuote(request, bizDate);
|
||||
applySurchargesAndFees(refreshed);
|
||||
replaceQuoteFees(cached.getQuoteId(), refreshed);
|
||||
refreshed.setQuoteId(cached.getQuoteId());
|
||||
refreshed.setFromCache(false);
|
||||
billSearchStore.save(EsLogType.QUOTE, cached.getQuoteId(), toEsRequest(request), toEsResponse(refreshed));
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
TmsPriceQuoteResult result = doQuote(request, bizDate);
|
||||
@@ -166,6 +176,23 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached quotes from pre-package-snapshot logic (PRODUCT_DEFAULT / missing package id)
|
||||
* must not be reused after billing rules changed.
|
||||
*/
|
||||
private boolean isStaleCachedQuote(PrcQuote cached)
|
||||
{
|
||||
if (cached == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if ("PRODUCT_DEFAULT".equalsIgnoreCase(cached.getMatchSource()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return cached.getPriceSetId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TmsPriceQuoteResult calculate(TmsPriceQuoteRequest request)
|
||||
{
|
||||
@@ -303,41 +330,201 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
}
|
||||
// Charge weight keeps 3 decimals; freight unit price keeps 2 (see buildFromRates).
|
||||
BigDecimal weight = request.getWeight().setScale(3, RoundingMode.HALF_UP);
|
||||
|
||||
String destPostcode = emptyToNull(request.getDestPostcode());
|
||||
String zoneCode = null;
|
||||
PrcZone resolvedZone = null;
|
||||
int zonedCount = productCountryMapper.countZonedByProductCountry(
|
||||
productCode, countryCode, countryName, bizDate);
|
||||
if (zonedCount > 0)
|
||||
String productName = resolveProductName(productCode, bizDate);
|
||||
|
||||
List<PrcCustomerPriceSet> binds;
|
||||
if (StringUtils.isNotEmpty(request.getCustomerCode()))
|
||||
{
|
||||
if (StringUtils.isEmpty(destPostcode))
|
||||
binds = customerPriceSetMapper.selectEffectiveByCustomerCode(request.getCustomerCode());
|
||||
}
|
||||
else
|
||||
{
|
||||
binds = customerPriceSetMapper.selectEffectiveByCustomer(request.getCustomerId());
|
||||
}
|
||||
if (binds == null || binds.isEmpty())
|
||||
{
|
||||
String msgKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.noCustomerPackage"
|
||||
: "price.quote.noCustomerPackage";
|
||||
throw new ServiceException(MessageUtils.message(msgKey));
|
||||
}
|
||||
|
||||
boolean anyActiveVersion = false;
|
||||
boolean productExcludedBySetProducts = false;
|
||||
boolean attemptedPriceMatch = false;
|
||||
boolean anyZoneRequired = false;
|
||||
Date asOf = endOfDay(bizDate);
|
||||
for (PrcCustomerPriceSet bind : binds)
|
||||
{
|
||||
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
|
||||
if (set == null || !"E".equals(set.getStatus()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.postcodeRequired"));
|
||||
continue;
|
||||
}
|
||||
String iso2 = resolveCountryIso2(productCode, countryCode, countryName);
|
||||
if (StringUtils.isEmpty(iso2))
|
||||
// Temporal gate: prc_price_set_version.effective_time / expire_time (not bind pr_begindate)
|
||||
PrcPriceSetVersion version = versionMapper.selectActiveByPriceSetAt(set.getPriceSetId(), asOf);
|
||||
if (version == null || version.getPsvId() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.countryRequired"));
|
||||
continue;
|
||||
}
|
||||
resolvedZone = zoneDetailMapper.selectZoneByCountryPostcode(iso2, normalizePostcode(destPostcode));
|
||||
if (resolvedZone == null || StringUtils.isEmpty(resolvedZone.getZoneCode()))
|
||||
anyActiveVersion = true;
|
||||
|
||||
PrcPriceSetProduct setProduct = resolveSetProduct(version.getPsvId(), productCode);
|
||||
if (setProduct == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.postcodeZoneNotFound"));
|
||||
PrcPriceSetProduct probe = new PrcPriceSetProduct();
|
||||
probe.setPsvId(version.getPsvId());
|
||||
List<PrcPriceSetProduct> anyProducts = setProductMapper.selectPrcPriceSetProductList(probe);
|
||||
if (anyProducts != null && !anyProducts.isEmpty())
|
||||
{
|
||||
productExcludedBySetProducts = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
zoneCode = resolvedZone.getZoneCode();
|
||||
if (countryCode == null)
|
||||
attemptedPriceMatch = true;
|
||||
if (setProduct != null && StringUtils.isNotEmpty(setProduct.getProductName()))
|
||||
{
|
||||
countryCode = iso2;
|
||||
productName = setProduct.getProductName();
|
||||
}
|
||||
|
||||
String setType = StringUtils.isEmpty(set.getSetType()) ? TYPE_FIXED : set.getSetType();
|
||||
String cc = countryCode;
|
||||
String cn = countryName;
|
||||
ZoneResolve zone = resolveZone(setType, version.getPsvId(), productCode, cc, cn, destPostcode, bizDate);
|
||||
if (zone.required)
|
||||
{
|
||||
anyZoneRequired = true;
|
||||
}
|
||||
if (zone.countryCode != null)
|
||||
{
|
||||
cc = zone.countryCode;
|
||||
}
|
||||
BmsProductCountry catalog = softMatchCatalog(productCode, cc, cn, zone.zoneCode, weight, bizDate, zone.zone);
|
||||
|
||||
if (TYPE_FIXED.equals(setType))
|
||||
{
|
||||
TmsPriceQuoteResult hit = tryFixed(bind, set, version, catalog, zone.zone,
|
||||
productCode, productName, cc, cn, zone.zoneCode, weight);
|
||||
if (hit != null)
|
||||
{
|
||||
stampRequestMeta(request, hit);
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
else if (TYPE_DISCOUNT.equals(setType))
|
||||
{
|
||||
if (catalog == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TmsPriceQuoteResult hit = tryDiscount(bind, set, version, catalog, productCode, productName, weight);
|
||||
if (hit != null)
|
||||
{
|
||||
stampRequestMeta(request, hit);
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyActiveVersion)
|
||||
{
|
||||
String msgKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.noActivePackageVersion"
|
||||
: "price.quote.noActivePackageVersion";
|
||||
throw new ServiceException(MessageUtils.message(msgKey));
|
||||
}
|
||||
if (!attemptedPriceMatch && productExcludedBySetProducts)
|
||||
{
|
||||
String msgKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.productNotInPackage"
|
||||
: "price.quote.productNotInPackage";
|
||||
throw new ServiceException(MessageUtils.message(msgKey));
|
||||
}
|
||||
String missKey;
|
||||
if (anyZoneRequired)
|
||||
{
|
||||
missKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.packageZonePriceNotHit"
|
||||
: "price.quote.packageZonePriceNotHit";
|
||||
}
|
||||
else
|
||||
{
|
||||
missKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.packagePriceNotHit"
|
||||
: "price.quote.packagePriceNotHit";
|
||||
}
|
||||
throw new ServiceException(MessageUtils.message(missKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft lookup of package product row; null when version has no such product.
|
||||
*/
|
||||
private PrcPriceSetProduct resolveSetProduct(Long psvId, String productCode)
|
||||
{
|
||||
if (psvId == null || StringUtils.isEmpty(productCode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return setProductMapper.selectByPsvIdAndCode(psvId, productCode);
|
||||
}
|
||||
|
||||
private static final class ZoneResolve
|
||||
{
|
||||
private boolean required;
|
||||
private String zoneCode;
|
||||
private String countryCode;
|
||||
private PrcZone zone;
|
||||
}
|
||||
|
||||
private ZoneResolve resolveZone(String setType, Long psvId, String productCode,
|
||||
String countryCode, String countryName, String destPostcode, Date bizDate)
|
||||
{
|
||||
ZoneResolve out = new ZoneResolve();
|
||||
int zonedCount;
|
||||
if (TYPE_FIXED.equals(setType))
|
||||
{
|
||||
zonedCount = detailMapper.countZonedByPsvProductCountry(psvId, productCode, countryCode, countryName);
|
||||
}
|
||||
else
|
||||
{
|
||||
zonedCount = productCountryMapper.countZonedByProductCountry(
|
||||
productCode, countryCode, countryName, bizDate);
|
||||
}
|
||||
if (zonedCount <= 0)
|
||||
{
|
||||
return out;
|
||||
}
|
||||
out.required = true;
|
||||
if (StringUtils.isEmpty(destPostcode))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.postcodeRequired"));
|
||||
}
|
||||
String iso2 = resolveCountryIso2(productCode, countryCode, countryName);
|
||||
if (StringUtils.isEmpty(iso2))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.countryRequired"));
|
||||
}
|
||||
PrcZone resolvedZone = zoneDetailMapper.selectZoneByCountryPostcode(iso2, normalizePostcode(destPostcode));
|
||||
if (resolvedZone == null || StringUtils.isEmpty(resolvedZone.getZoneCode()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.postcodeZoneNotFound"));
|
||||
}
|
||||
out.zone = resolvedZone;
|
||||
out.zoneCode = resolvedZone.getZoneCode();
|
||||
out.countryCode = iso2;
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Catalog match for product_amount / DISCOUNT base; null is allowed for FIXED sales. */
|
||||
private BmsProductCountry softMatchCatalog(String productCode, String countryCode, String countryName,
|
||||
String zoneCode, BigDecimal weight, Date bizDate, PrcZone resolvedZone)
|
||||
{
|
||||
BmsProductCountry base = productCountryMapper.selectMatchByProductCountryWeight(
|
||||
productCode, countryCode, countryName, zoneCode, weight, bizDate);
|
||||
if (base == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message(
|
||||
zonedCount > 0 ? "price.quote.zonePriceNotHit" : "price.quote.priceNotHit"));
|
||||
return null;
|
||||
}
|
||||
if (resolvedZone != null)
|
||||
{
|
||||
@@ -354,81 +541,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
base.setZoneName(resolvedZone.getZoneName());
|
||||
}
|
||||
}
|
||||
|
||||
String productName = resolveProductName(productCode, bizDate);
|
||||
List<PrcCustomerPriceSet> binds;
|
||||
if (StringUtils.isNotEmpty(request.getCustomerCode()))
|
||||
{
|
||||
binds = customerPriceSetMapper.selectEffectiveByCustomerCode(request.getCustomerCode());
|
||||
}
|
||||
else
|
||||
{
|
||||
binds = customerPriceSetMapper.selectEffectiveByCustomer(request.getCustomerId());
|
||||
}
|
||||
|
||||
boolean anyActiveVersion = false;
|
||||
if (binds != null)
|
||||
{
|
||||
Date asOf = endOfDay(bizDate);
|
||||
for (PrcCustomerPriceSet bind : binds)
|
||||
{
|
||||
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
|
||||
if (set == null || !"E".equals(set.getStatus()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Temporal gate: prc_price_set_version.effective_time / expire_time (not bind pr_begindate)
|
||||
PrcPriceSetVersion version = versionMapper.selectActiveByPriceSetAt(set.getPriceSetId(), asOf);
|
||||
if (version == null || version.getPsvId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
anyActiveVersion = true;
|
||||
String setType = StringUtils.isEmpty(set.getSetType()) ? TYPE_FIXED : set.getSetType();
|
||||
if (TYPE_FIXED.equals(setType))
|
||||
{
|
||||
TmsPriceQuoteResult hit = tryFixed(bind, set, version, base, productCode, productName, weight);
|
||||
if (hit != null)
|
||||
{
|
||||
stampRequestMeta(request, hit);
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
else if (TYPE_DISCOUNT.equals(setType))
|
||||
{
|
||||
TmsPriceQuoteResult hit = tryDiscount(bind, set, version, base, productCode, productName, weight);
|
||||
if (hit != null)
|
||||
{
|
||||
stampRequestMeta(request, hit);
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No bind, or bind exists but no version effective at bill time.
|
||||
if (binds == null || binds.isEmpty() || !anyActiveVersion)
|
||||
{
|
||||
String msgKey;
|
||||
if (binds == null || binds.isEmpty())
|
||||
{
|
||||
msgKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.noCustomerPackage"
|
||||
: "price.quote.noCustomerPackage";
|
||||
}
|
||||
else
|
||||
{
|
||||
msgKey = request instanceof TmsPriceBillRequest
|
||||
? "price.bill.noActivePackageVersion"
|
||||
: "price.quote.noActivePackageVersion";
|
||||
}
|
||||
throw new ServiceException(MessageUtils.message(msgKey));
|
||||
}
|
||||
|
||||
TmsPriceQuoteResult fallback = buildDefault(request.getCustomerId(), base, productCode, productName, weight,
|
||||
MessageUtils.message("price.quote.noCustomerPackageOverride"));
|
||||
stampRequestMeta(request, fallback);
|
||||
return fallback;
|
||||
return base;
|
||||
}
|
||||
|
||||
private void stampRequestMeta(TmsPriceQuoteRequest request, TmsPriceQuoteResult result)
|
||||
@@ -576,7 +689,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
|
||||
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
|
||||
BmsProductCountry catalog, PrcZone resolvedZone, String productCode, String productName,
|
||||
String countryCode, String countryName, String zoneCode, BigDecimal weight)
|
||||
{
|
||||
Long psvId = version.getPsvId();
|
||||
int detailCount = detailMapper.countByPsvId(psvId);
|
||||
@@ -588,9 +702,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
|
||||
PrcPriceSetDetail detail = detailMapper.selectFixedMatch(
|
||||
set.getPriceSetId(), psvId, productCode,
|
||||
base.getCountryCode(), base.getCountryName(), base.getZoneCode(),
|
||||
base.getDetailId(),
|
||||
formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
|
||||
countryCode, countryName, zoneCode, weight);
|
||||
if (detail == null)
|
||||
{
|
||||
return null;
|
||||
@@ -599,22 +711,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
{
|
||||
return null;
|
||||
}
|
||||
TmsPriceQuoteResult r = buildFromRates(SRC_FIXED, detail.getFvValue(), null, null,
|
||||
detail.getFvRegistrationFee(), base, productCode, productName, weight, detail.getRoundMode());
|
||||
TmsPriceQuoteResult r = buildFromPackageDetail(SRC_FIXED, detail, catalog, resolvedZone,
|
||||
productCode, productName, weight, detail.getFvValue(), null, null, detail.getFvRegistrationFee());
|
||||
fillPackage(r, bind, set);
|
||||
stampVersion(r, version, detail.getPsdId());
|
||||
if (StringUtils.isNotEmpty(detail.getProductName()))
|
||||
{
|
||||
r.setProductName(detail.getProductName());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(detail.getWeightLabel()))
|
||||
{
|
||||
r.setWeightLabel(detail.getWeightLabel());
|
||||
}
|
||||
if (detail.getProductCountryId() != null)
|
||||
{
|
||||
r.setProductCountryId(detail.getProductCountryId());
|
||||
}
|
||||
r.setMatchMessage(MessageUtils.message("price.quote.hitCustomerFixedPackageDetail")
|
||||
+ " [" + (version.getVersionNo() == null ? ("psv=" + psvId) : version.getVersionNo()) + "]");
|
||||
r.setCustomerId(bind.getCustomerId());
|
||||
@@ -629,9 +729,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
BigDecimal discount = set.getDefaultDiscount();
|
||||
String roundMode = null;
|
||||
Long psdId = null;
|
||||
PrcPriceSetDetail detail = null;
|
||||
if (detailCount > 0)
|
||||
{
|
||||
PrcPriceSetDetail detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), psvId, productCode);
|
||||
detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), psvId, productCode);
|
||||
if (detail == null)
|
||||
{
|
||||
return null;
|
||||
@@ -679,17 +780,105 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
result.setPsdId(psdId);
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult buildDefault(Long customerId, BmsProductCountry base,
|
||||
String productCode, String productName, BigDecimal weight, String message)
|
||||
/**
|
||||
* FIXED sales from package detail snapshot; catalog only fills product_amount / cost.
|
||||
*/
|
||||
private TmsPriceQuoteResult buildFromPackageDetail(String source, PrcPriceSetDetail detail,
|
||||
BmsProductCountry catalog, PrcZone resolvedZone, String productCode, String productName,
|
||||
BigDecimal weight, BigDecimal freightRate, BigDecimal baseFreightRate, BigDecimal discountPercent,
|
||||
BigDecimal registrationFee)
|
||||
{
|
||||
if (base.getFreightPrice() == null || base.getRegistrationFee() == null)
|
||||
if (freightRate == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.quote.freightMissing2"));
|
||||
throw new ServiceException(MessageUtils.message("price.quote.freightMissing3"));
|
||||
}
|
||||
TmsPriceQuoteResult r = buildFromRates(SRC_DEFAULT, base.getFreightPrice(), null, null,
|
||||
base.getRegistrationFee(), base, productCode, productName, weight, null);
|
||||
r.setCustomerId(customerId);
|
||||
r.setMatchMessage(message);
|
||||
if (registrationFee == null)
|
||||
{
|
||||
registrationFee = BigDecimal.ZERO;
|
||||
}
|
||||
String roundMode = detail == null ? null : detail.getRoundMode();
|
||||
RoundingMode rm = resolveRoundingMode(roundMode);
|
||||
String mode = normalizeRoundMode(roundMode);
|
||||
weight = weight.setScale(3, RoundingMode.HALF_UP);
|
||||
freightRate = freightRate.setScale(2, rm);
|
||||
if (baseFreightRate != null)
|
||||
{
|
||||
baseFreightRate = baseFreightRate.setScale(2, rm);
|
||||
}
|
||||
registrationFee = registrationFee.setScale(2, rm);
|
||||
BigDecimal freightAmount = freightRate.multiply(weight).setScale(2, rm);
|
||||
BigDecimal payable = freightAmount.add(registrationFee).setScale(2, rm);
|
||||
|
||||
TmsPriceQuoteResult r = new TmsPriceQuoteResult();
|
||||
r.setMatchSource(source);
|
||||
r.setProductCode(productCode);
|
||||
if (detail != null && StringUtils.isNotEmpty(detail.getProductName()))
|
||||
{
|
||||
r.setProductName(detail.getProductName());
|
||||
}
|
||||
else
|
||||
{
|
||||
r.setProductName(productName);
|
||||
}
|
||||
if (detail != null)
|
||||
{
|
||||
r.setCountryCode(detail.getCountryCode());
|
||||
r.setCountryName(detail.getCountryName());
|
||||
r.setZoneId(detail.getZoneId());
|
||||
r.setZoneCode(detail.getZoneCode());
|
||||
r.setZoneName(detail.getZoneName());
|
||||
r.setWeightMin(detail.getWeightMin());
|
||||
r.setWeightMax(detail.getWeightMax());
|
||||
if (StringUtils.isNotEmpty(detail.getWeightLabel()))
|
||||
{
|
||||
r.setWeightLabel(detail.getWeightLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
r.setWeightLabel(formatWeightLabel(detail.getWeightMin(), detail.getWeightMax()));
|
||||
}
|
||||
r.setProductCountryId(detail.getProductCountryId());
|
||||
r.setEffectiveDate(detail.getEffectiveDate());
|
||||
}
|
||||
if (resolvedZone != null)
|
||||
{
|
||||
if (r.getZoneId() == null)
|
||||
{
|
||||
r.setZoneId(resolvedZone.getZoneId());
|
||||
}
|
||||
if (StringUtils.isEmpty(r.getZoneCode()))
|
||||
{
|
||||
r.setZoneCode(resolvedZone.getZoneCode());
|
||||
}
|
||||
if (StringUtils.isEmpty(r.getZoneName()))
|
||||
{
|
||||
r.setZoneName(resolvedZone.getZoneName());
|
||||
}
|
||||
}
|
||||
if (catalog != null)
|
||||
{
|
||||
if (r.getProductCountryId() == null)
|
||||
{
|
||||
r.setProductCountryId(catalog.getDetailId());
|
||||
}
|
||||
if (r.getEffectiveDate() == null)
|
||||
{
|
||||
r.setEffectiveDate(catalog.getEffectiveDate());
|
||||
}
|
||||
}
|
||||
r.setWeight(weight);
|
||||
r.setChargeWeight(weight);
|
||||
r.setFreightRate(freightRate);
|
||||
r.setBaseFreightRate(baseFreightRate);
|
||||
r.setDiscountPercent(discountPercent);
|
||||
r.setRegistrationFee(registrationFee);
|
||||
r.setFreightAmount(freightAmount);
|
||||
r.setSurchargeAmount(BigDecimal.ZERO);
|
||||
r.setPayableTotal(payable);
|
||||
r.setCurrency("CNY");
|
||||
r.setRoundMode(mode);
|
||||
r.setFromCache(false);
|
||||
fillProductAmountAndPay(r, catalog, weight, rm, mode);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -827,22 +1016,29 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
{
|
||||
return matched;
|
||||
}
|
||||
if (result.getPsdId() != null)
|
||||
boolean packageHit = SRC_FIXED.equals(result.getMatchSource()) || SRC_DISCOUNT.equals(result.getMatchSource());
|
||||
// Package sales use version surcharge snapshot only (empty snapshot = no surcharge).
|
||||
if (packageHit)
|
||||
{
|
||||
List<PrcPriceSetVersionSurcharge> snaps =
|
||||
versionSurchargeMapper.selectByPsdIds(Collections.singletonList(result.getPsdId()));
|
||||
if (snaps != null && !snaps.isEmpty())
|
||||
if (result.getPsdId() == null)
|
||||
{
|
||||
for (PrcPriceSetVersionSurcharge snap : snaps)
|
||||
{
|
||||
PrcSurchargeRel rel = toRelFromVersionSnap(snap);
|
||||
if (surchargeApplies(rel, result))
|
||||
{
|
||||
matched.add(rel);
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
List<PrcPriceSetVersionSurcharge> snaps =
|
||||
versionSurchargeMapper.selectByPsdIds(Collections.singletonList(result.getPsdId()));
|
||||
if (snaps == null || snaps.isEmpty())
|
||||
{
|
||||
return matched;
|
||||
}
|
||||
for (PrcPriceSetVersionSurcharge snap : snaps)
|
||||
{
|
||||
PrcSurchargeRel rel = toRelFromVersionSnap(snap);
|
||||
if (surchargeApplies(rel, result))
|
||||
{
|
||||
matched.add(rel);
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
PrcSurchargeRel query = new PrcSurchargeRel();
|
||||
query.setProductCode(result.getProductCode());
|
||||
|
||||
@@ -328,39 +328,52 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and psv_id = #{psvId}
|
||||
</if>
|
||||
and product_code = #{productCode}
|
||||
and (
|
||||
<if test="productCountryId != null">
|
||||
product_country_id = #{productCountryId}
|
||||
or
|
||||
</if>
|
||||
(
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="zoneCode != null and zoneCode != ''">
|
||||
and (zone_code is null or upper(zone_code) = upper(#{zoneCode}))
|
||||
</if>
|
||||
<if test="weightLabel != null and weightLabel != ''">
|
||||
and (weight_label is null or weight_label = #{weightLabel} or weight_label like concat('%', #{weightLabel}, '%'))
|
||||
</if>
|
||||
)
|
||||
)
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
and country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
<choose>
|
||||
<when test="zoneCode != null and zoneCode != ''">
|
||||
and upper(ifnull(zone_code,'')) = upper(#{zoneCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and (zone_code is null or zone_code = '')
|
||||
</otherwise>
|
||||
</choose>
|
||||
and weight_min <= #{weight}
|
||||
and weight_max >= #{weight}
|
||||
order by
|
||||
case when #{productCountryId} is not null and product_country_id = #{productCountryId} then 0 else 1 end,
|
||||
case when #{zoneCode} is not null and upper(ifnull(zone_code,'')) = upper(#{zoneCode}) then 0 else 1 end,
|
||||
case when zone_code is null then 1 else 0 end,
|
||||
weight_min asc,
|
||||
psd_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="countZonedByPsvProductCountry" resultType="int">
|
||||
select count(1)
|
||||
from prc_price_set_detail
|
||||
where psv_id = #{psvId}
|
||||
and product_code = #{productCode}
|
||||
and zone_code is not null and zone_code <> ''
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
and country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
</select>
|
||||
|
||||
<select id="selectDiscountMatch" resultMap="PrcPriceSetDetailResult">
|
||||
<include refid="selectPrcPriceSetDetailVo"/>
|
||||
where price_set_id = #{priceSetId}
|
||||
|
||||
@@ -134,6 +134,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</insert>
|
||||
|
||||
<delete id="deletePrcQuoteById" parameterType="String">
|
||||
delete from prc_quote where quote_id = #{quoteId}
|
||||
</delete>
|
||||
|
||||
<update id="updateQuoteAmounts">
|
||||
update prc_quote
|
||||
set surcharge_amount = #{surchargeAmount},
|
||||
|
||||
Reference in New Issue
Block a user