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

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.noActivePackageVersion=计费日无生效套餐版本,无法试算
price.quote.quoteIdRequired=报价ID为空 price.quote.quoteIdRequired=报价ID为空
price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细 price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细
price.quote.packageZonePriceNotHit=已匹配分区,但套餐版本无该分区/重量段价格
price.quote.packagePriceNotHit=套餐版本无匹配的国家/重量段价格
price.quote.productNotInPackage=产品不在客户套餐版本中,无法试算
# receivable bill # receivable bill
price.bill.orderNoRequired=订单号为空 price.bill.orderNoRequired=订单号为空
@@ -163,6 +166,9 @@ price.bill.alreadyInArBill=订单{0}已生成应收账单,作废后方可重
price.bill.bizDateRequired=业务日期为空 price.bill.bizDateRequired=业务日期为空
price.bill.noCustomerPackage=客户未绑定套餐,无法计费 price.bill.noCustomerPackage=客户未绑定套餐,无法计费
price.bill.noActivePackageVersion=计费日无生效套餐版本,无法计费 price.bill.noActivePackageVersion=计费日无生效套餐版本,无法计费
price.bill.packageZonePriceNotHit=已匹配分区,但套餐版本无该分区/重量段价格
price.bill.packagePriceNotHit=套餐版本无匹配的国家/重量段价格
price.bill.productNotInPackage=产品不在客户套餐版本中,无法计费
price.bill.fee.scIdRequired=附加费不能为空 price.bill.fee.scIdRequired=附加费不能为空
price.bill.fee.surchargeNotFound=附加费配置不存在或已停用 price.bill.fee.surchargeNotFound=附加费配置不存在或已停用
price.bill.fee.notFound=费用明细不存在 price.bill.fee.notFound=费用明细不存在
@@ -8,7 +8,7 @@ import lombok.Data;
/** /**
* TMS freight quote response. * TMS freight quote response.
* matchSource: PACKAGE_FIXED / PACKAGE_DISCOUNT / PRODUCT_DEFAULT * matchSource: PACKAGE_FIXED / PACKAGE_DISCOUNT
*/ */
@Data @Data
public class TmsPriceQuoteResult implements Serializable public class TmsPriceQuoteResult implements Serializable
@@ -54,7 +54,10 @@ public interface PrcPriceSetDetailMapper
@Param("priceSetId") Long priceSetId, @Param("priceSetId") Long priceSetId,
@Param("psvId") Long psvId); @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( public PrcPriceSetDetail selectFixedMatch(
@Param("priceSetId") Long priceSetId, @Param("priceSetId") Long priceSetId,
@Param("psvId") Long psvId, @Param("psvId") Long psvId,
@@ -62,8 +65,14 @@ public interface PrcPriceSetDetailMapper
@Param("countryCode") String countryCode, @Param("countryCode") String countryCode,
@Param("countryName") String countryName, @Param("countryName") String countryName,
@Param("zoneCode") String zoneCode, @Param("zoneCode") String zoneCode,
@Param("productCountryId") Long productCountryId, @Param("weight") BigDecimal weight);
@Param("weightLabel") String weightLabel);
/** 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 */ /** DISCOUNT by product; optional psvId scopes to version */
public PrcPriceSetDetail selectDiscountMatch( public PrcPriceSetDetail selectDiscountMatch(
@@ -16,6 +16,8 @@ public interface PrcQuoteMapper
public int insertPrcQuote(PrcQuote quote); public int insertPrcQuote(PrcQuote quote);
public int deletePrcQuoteById(String quoteId);
public int updateQuoteAmounts(@Param("quoteId") String quoteId, public int updateQuoteAmounts(@Param("quoteId") String quoteId,
@Param("surchargeAmount") BigDecimal surchargeAmount, @Param("surchargeAmount") BigDecimal surchargeAmount,
@Param("payableTotal") BigDecimal payableTotal, @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.TmsPriceQuoteResult;
import com.bms.price.domain.vo.TmsOrderBillStatusResult; 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 public interface IPrcOrderBillService
{ {
TmsPriceQuoteResult bill(TmsPriceBillRequest request); TmsPriceQuoteResult bill(TmsPriceBillRequest request);
@@ -890,7 +890,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
{ {
orderBillMapper.updatePrcOrderBill(bill); orderBillMapper.updatePrcOrderBill(bill);
incomeBillFeeMapper.deleteByBillId(bill.getBillId()); 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); PrcIncomeBill incomeBill = persistIncomeBill(bill, result);
List<PrcIncomeBillFee> fees = toFeeRows(bill.getBillId(), List<PrcIncomeBillFee> fees = toFeeRows(bill.getBillId(),
@@ -900,13 +901,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
incomeBillFeeMapper.insertBatch(fees); incomeBillFeeMapper.insertBatch(fees);
} }
orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId())); orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId()));
PrcPayBill payBill = persistPayBill(bill, result); // Payable not in scope yet: do not write prc_pay_bill / prc_pay_bill_fee.
List<PrcPayBillFee> payFees = toPayFeeRows(bill.getBillId(), // PrcPayBill payBill = persistPayBill(bill, result);
payBill == null ? null : payBill.getPayBillId(), result); // List<PrcPayBillFee> payFees = toPayFeeRows(bill.getBillId(),
if (!payFees.isEmpty()) // payBill == null ? null : payBill.getPayBillId(), result);
{ // if (!payFees.isEmpty())
payBillFeeMapper.insertBatch(payFees); // {
} // payBillFeeMapper.insertBatch(payFees);
// }
String requestJson = toRequestJson(request); String requestJson = toRequestJson(request);
persistBillExt(bill, request.getUpstreamRow()); persistBillExt(bill, request.getUpstreamRow());
result.setRequestJson(requestJson); 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). * 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. * 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 * @author bms
*/ */
@@ -71,7 +73,6 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
private static final String TYPE_DISCOUNT = "DISCOUNT"; private static final String TYPE_DISCOUNT = "DISCOUNT";
private static final String SRC_FIXED = "PACKAGE_FIXED"; private static final String SRC_FIXED = "PACKAGE_FIXED";
private static final String SRC_DISCOUNT = "PACKAGE_DISCOUNT"; 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_BASIC = "BASIC_FREIGHT";
private static final String FEE_REG = "REGISTRATION_FEE"; private static final String FEE_REG = "REGISTRATION_FEE";
@@ -145,6 +146,14 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
PrcQuote cached = quoteMapper.selectPrcQuoteByKey(quoteKey); PrcQuote cached = quoteMapper.selectPrcQuoteByKey(quoteKey);
if (cached != null) if (cached != null)
{
// Legacy PRODUCT_DEFAULT (or no package stamp) must be recomputed after package-snapshot pricing.
if (isStaleCachedQuote(cached))
{
quoteFeeMapper.deleteByQuoteId(cached.getQuoteId());
quoteMapper.deletePrcQuoteById(cached.getQuoteId());
}
else
{ {
List<PrcQuoteFee> fees = quoteFeeMapper.selectByQuoteId(cached.getQuoteId()); List<PrcQuoteFee> fees = quoteFeeMapper.selectByQuoteId(cached.getQuoteId());
if (!isStaleSurchargeFees(fees)) if (!isStaleSurchargeFees(fees))
@@ -159,6 +168,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
billSearchStore.save(EsLogType.QUOTE, cached.getQuoteId(), toEsRequest(request), toEsResponse(refreshed)); billSearchStore.save(EsLogType.QUOTE, cached.getQuoteId(), toEsRequest(request), toEsResponse(refreshed));
return refreshed; return refreshed;
} }
}
TmsPriceQuoteResult result = doQuote(request, bizDate); TmsPriceQuoteResult result = doQuote(request, bizDate);
applySurchargesAndFees(result); applySurchargesAndFees(result);
@@ -166,6 +176,23 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return result; 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 @Override
public TmsPriceQuoteResult calculate(TmsPriceQuoteRequest request) public TmsPriceQuoteResult calculate(TmsPriceQuoteRequest request)
{ {
@@ -303,14 +330,172 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
} }
// Charge weight keeps 3 decimals; freight unit price keeps 2 (see buildFromRates). // Charge weight keeps 3 decimals; freight unit price keeps 2 (see buildFromRates).
BigDecimal weight = request.getWeight().setScale(3, RoundingMode.HALF_UP); BigDecimal weight = request.getWeight().setScale(3, RoundingMode.HALF_UP);
String destPostcode = emptyToNull(request.getDestPostcode()); String destPostcode = emptyToNull(request.getDestPostcode());
String zoneCode = null; String productName = resolveProductName(productCode, bizDate);
PrcZone resolvedZone = null;
int zonedCount = productCountryMapper.countZonedByProductCountry( List<PrcCustomerPriceSet> binds;
productCode, countryCode, countryName, bizDate); if (StringUtils.isNotEmpty(request.getCustomerCode()))
if (zonedCount > 0)
{ {
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()))
{
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;
PrcPriceSetProduct setProduct = resolveSetProduct(version.getPsvId(), productCode);
if (setProduct == null)
{
PrcPriceSetProduct probe = new PrcPriceSetProduct();
probe.setPsvId(version.getPsvId());
List<PrcPriceSetProduct> anyProducts = setProductMapper.selectPrcPriceSetProductList(probe);
if (anyProducts != null && !anyProducts.isEmpty())
{
productExcludedBySetProducts = true;
continue;
}
}
attemptedPriceMatch = true;
if (setProduct != null && StringUtils.isNotEmpty(setProduct.getProductName()))
{
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)) if (StringUtils.isEmpty(destPostcode))
{ {
throw new ServiceException(MessageUtils.message("price.quote.postcodeRequired")); throw new ServiceException(MessageUtils.message("price.quote.postcodeRequired"));
@@ -320,24 +505,26 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{ {
throw new ServiceException(MessageUtils.message("price.quote.countryRequired")); throw new ServiceException(MessageUtils.message("price.quote.countryRequired"));
} }
resolvedZone = zoneDetailMapper.selectZoneByCountryPostcode(iso2, normalizePostcode(destPostcode)); PrcZone resolvedZone = zoneDetailMapper.selectZoneByCountryPostcode(iso2, normalizePostcode(destPostcode));
if (resolvedZone == null || StringUtils.isEmpty(resolvedZone.getZoneCode())) if (resolvedZone == null || StringUtils.isEmpty(resolvedZone.getZoneCode()))
{ {
throw new ServiceException(MessageUtils.message("price.quote.postcodeZoneNotFound")); throw new ServiceException(MessageUtils.message("price.quote.postcodeZoneNotFound"));
} }
zoneCode = resolvedZone.getZoneCode(); out.zone = resolvedZone;
if (countryCode == null) out.zoneCode = resolvedZone.getZoneCode();
{ out.countryCode = iso2;
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( BmsProductCountry base = productCountryMapper.selectMatchByProductCountryWeight(
productCode, countryCode, countryName, zoneCode, weight, bizDate); productCode, countryCode, countryName, zoneCode, weight, bizDate);
if (base == null) if (base == null)
{ {
throw new ServiceException(MessageUtils.message( return null;
zonedCount > 0 ? "price.quote.zonePriceNotHit" : "price.quote.priceNotHit"));
} }
if (resolvedZone != null) if (resolvedZone != null)
{ {
@@ -354,81 +541,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
base.setZoneName(resolvedZone.getZoneName()); base.setZoneName(resolvedZone.getZoneName());
} }
} }
return base;
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;
} }
private void stampRequestMeta(TmsPriceQuoteRequest request, TmsPriceQuoteResult result) 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, 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(); Long psvId = version.getPsvId();
int detailCount = detailMapper.countByPsvId(psvId); int detailCount = detailMapper.countByPsvId(psvId);
@@ -588,9 +702,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
PrcPriceSetDetail detail = detailMapper.selectFixedMatch( PrcPriceSetDetail detail = detailMapper.selectFixedMatch(
set.getPriceSetId(), psvId, productCode, set.getPriceSetId(), psvId, productCode,
base.getCountryCode(), base.getCountryName(), base.getZoneCode(), countryCode, countryName, zoneCode, weight);
base.getDetailId(),
formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
if (detail == null) if (detail == null)
{ {
return null; return null;
@@ -599,22 +711,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{ {
return null; return null;
} }
TmsPriceQuoteResult r = buildFromRates(SRC_FIXED, detail.getFvValue(), null, null, TmsPriceQuoteResult r = buildFromPackageDetail(SRC_FIXED, detail, catalog, resolvedZone,
detail.getFvRegistrationFee(), base, productCode, productName, weight, detail.getRoundMode()); productCode, productName, weight, detail.getFvValue(), null, null, detail.getFvRegistrationFee());
fillPackage(r, bind, set); fillPackage(r, bind, set);
stampVersion(r, version, detail.getPsdId()); 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") r.setMatchMessage(MessageUtils.message("price.quote.hitCustomerFixedPackageDetail")
+ " [" + (version.getVersionNo() == null ? ("psv=" + psvId) : version.getVersionNo()) + "]"); + " [" + (version.getVersionNo() == null ? ("psv=" + psvId) : version.getVersionNo()) + "]");
r.setCustomerId(bind.getCustomerId()); r.setCustomerId(bind.getCustomerId());
@@ -629,9 +729,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
BigDecimal discount = set.getDefaultDiscount(); BigDecimal discount = set.getDefaultDiscount();
String roundMode = null; String roundMode = null;
Long psdId = null; Long psdId = null;
PrcPriceSetDetail detail = null;
if (detailCount > 0) if (detailCount > 0)
{ {
PrcPriceSetDetail detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), psvId, productCode); detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), psvId, productCode);
if (detail == null) if (detail == null)
{ {
return null; return null;
@@ -679,17 +780,105 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
result.setPsdId(psdId); 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, if (registrationFee == null)
base.getRegistrationFee(), base, productCode, productName, weight, null); {
r.setCustomerId(customerId); registrationFee = BigDecimal.ZERO;
r.setMatchMessage(message); }
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; return r;
} }
@@ -827,12 +1016,20 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{ {
return matched; 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)
{ {
if (result.getPsdId() == null)
{
return matched;
}
List<PrcPriceSetVersionSurcharge> snaps = List<PrcPriceSetVersionSurcharge> snaps =
versionSurchargeMapper.selectByPsdIds(Collections.singletonList(result.getPsdId())); versionSurchargeMapper.selectByPsdIds(Collections.singletonList(result.getPsdId()));
if (snaps != null && !snaps.isEmpty()) if (snaps == null || snaps.isEmpty())
{ {
return matched;
}
for (PrcPriceSetVersionSurcharge snap : snaps) for (PrcPriceSetVersionSurcharge snap : snaps)
{ {
PrcSurchargeRel rel = toRelFromVersionSnap(snap); PrcSurchargeRel rel = toRelFromVersionSnap(snap);
@@ -843,7 +1040,6 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
} }
return matched; return matched;
} }
}
PrcSurchargeRel query = new PrcSurchargeRel(); PrcSurchargeRel query = new PrcSurchargeRel();
query.setProductCode(result.getProductCode()); query.setProductCode(result.getProductCode());
List<PrcSurchargeRel> rels = surchargeRelMapper.selectPrcSurchargeRelList(query); List<PrcSurchargeRel> rels = surchargeRelMapper.selectPrcSurchargeRelList(query);
@@ -328,39 +328,52 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and psv_id = #{psvId} and psv_id = #{psvId}
</if> </if>
and product_code = #{productCode} and product_code = #{productCode}
and (
<if test="productCountryId != null">
product_country_id = #{productCountryId}
or
</if>
(
<choose> <choose>
<when test="countryCode != null and countryCode != ''"> <when test="countryCode != null and countryCode != ''">
upper(ifnull(country_code,'')) = upper(#{countryCode}) and upper(ifnull(country_code,'')) = upper(#{countryCode})
</when> </when>
<when test="countryName != null and countryName != ''"> <when test="countryName != null and countryName != ''">
country_name = #{countryName} and country_name = #{countryName}
</when> </when>
<otherwise> <otherwise>
1 = 0 and 1 = 0
</otherwise> </otherwise>
</choose> </choose>
<if test="zoneCode != null and zoneCode != ''"> <choose>
and (zone_code is null or upper(zone_code) = upper(#{zoneCode})) <when test="zoneCode != null and zoneCode != ''">
</if> and upper(ifnull(zone_code,'')) = upper(#{zoneCode})
<if test="weightLabel != null and weightLabel != ''"> </when>
and (weight_label is null or weight_label = #{weightLabel} or weight_label like concat('%', #{weightLabel}, '%')) <otherwise>
</if> and (zone_code is null or zone_code = '')
) </otherwise>
) </choose>
and weight_min &lt;= #{weight}
and weight_max &gt;= #{weight}
order by order by
case when #{productCountryId} is not null and product_country_id = #{productCountryId} then 0 else 1 end, weight_min asc,
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,
psd_id asc psd_id asc
limit 1 limit 1
</select> </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"> <select id="selectDiscountMatch" resultMap="PrcPriceSetDetailResult">
<include refid="selectPrcPriceSetDetailVo"/> <include refid="selectPrcPriceSetDetailVo"/>
where price_set_id = #{priceSetId} where price_set_id = #{priceSetId}
@@ -134,6 +134,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) )
</insert> </insert>
<delete id="deletePrcQuoteById" parameterType="String">
delete from prc_quote where quote_id = #{quoteId}
</delete>
<update id="updateQuoteAmounts"> <update id="updateQuoteAmounts">
update prc_quote update prc_quote
set surcharge_amount = #{surchargeAmount}, set surcharge_amount = #{surchargeAmount},