diff --git a/bms-back/bms-admin/src/main/resources/i18n/messages.properties b/bms-back/bms-admin/src/main/resources/i18n/messages.properties index 14ecda1e..a9d029e2 100644 --- a/bms-back/bms-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/bms-admin/src/main/resources/i18n/messages.properties @@ -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=费用明细不存在 diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TmsPriceQuoteResult.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TmsPriceQuoteResult.java index 3b0cde48..6ac2818d 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TmsPriceQuoteResult.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TmsPriceQuoteResult.java @@ -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 diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java index 808dd931..015a4edb 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java @@ -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( diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcQuoteMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcQuoteMapper.java index 2fea3249..22d81c03 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcQuoteMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcQuoteMapper.java @@ -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, diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java index 74b74860..e7cfaaff 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcOrderBillService.java @@ -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); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java index 1de8a0c0..941092d2 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcOrderBillServiceImpl.java @@ -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 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 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 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); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java index a21c181b..e2b5e1c2 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java @@ -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 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 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 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 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 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 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 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()); diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml index 6457acc9..9cc331ab 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml @@ -328,39 +328,52 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and psv_id = #{psvId} and product_code = #{productCode} - and ( - - product_country_id = #{productCountryId} - or - - ( - - - upper(ifnull(country_code,'')) = upper(#{countryCode}) - - - country_name = #{countryName} - - - 1 = 0 - - - - and (zone_code is null or upper(zone_code) = upper(#{zoneCode})) - - - and (weight_label is null or weight_label = #{weightLabel} or weight_label like concat('%', #{weightLabel}, '%')) - - ) - ) + + + and upper(ifnull(country_code,'')) = upper(#{countryCode}) + + + and country_name = #{countryName} + + + and 1 = 0 + + + + + and upper(ifnull(zone_code,'')) = upper(#{zoneCode}) + + + and (zone_code is null or zone_code = '') + + + 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 + +