更新价格计算逻辑,添加套餐价格匹配和计费相关的国际化消息,优化数据库查询和删除操作

This commit is contained in:
wuweihua
2026-09-29 23:52:37 +08:00
parent 52d0a13d83
commit 4f5959a2f9
9 changed files with 418 additions and 186 deletions
@@ -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
@@ -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);
@@ -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);
@@ -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 &lt;= #{weight}
and weight_max &gt;= #{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 &lt;&gt; ''
<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},