增加试算功能

This commit is contained in:
wuweihua
2026-09-07 14:42:27 +08:00
parent a083a0dece
commit ea2bc6f33e
17 changed files with 1060 additions and 0 deletions
@@ -0,0 +1,17 @@
package com.ruoyi.price.service;
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
/**
* TMS freight quote service.
*
* Match order: effective customer package (bind_priority ASC) FIXED override
* then DISCOUNT, else bms_product_country default freight/registration.
*
* @author ruoyi
*/
public interface ITmsPriceQuoteService
{
TmsPriceQuoteResult quote(TmsPriceQuoteRequest request);
}
@@ -0,0 +1,324 @@
package com.ruoyi.price.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.price.domain.BmsProduct;
import com.ruoyi.price.domain.BmsProductCountry;
import com.ruoyi.price.domain.PrcCustomerPriceSet;
import com.ruoyi.price.domain.PrcPriceSet;
import com.ruoyi.price.domain.PrcPriceSetDetail;
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
import com.ruoyi.price.mapper.BmsProductCountryMapper;
import com.ruoyi.price.mapper.BmsProductMapper;
import com.ruoyi.price.mapper.PrcCustomerPriceSetMapper;
import com.ruoyi.price.mapper.PrcPriceSetDetailMapper;
import com.ruoyi.price.mapper.PrcPriceSetMapper;
import com.ruoyi.price.service.ITmsPriceQuoteService;
/**
* TMS freight quote: customer package first, then product default.
*
* @author ruoyi
*/
@Service
public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
private static final String TYPE_FIXED = "FIXED";
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";
@Autowired
private PrcCustomerPriceSetMapper customerPriceSetMapper;
@Autowired
private PrcPriceSetMapper priceSetMapper;
@Autowired
private PrcPriceSetDetailMapper detailMapper;
@Autowired
private BmsProductCountryMapper productCountryMapper;
@Autowired
private BmsProductMapper productMapper;
@Override
public TmsPriceQuoteResult quote(TmsPriceQuoteRequest request)
{
validateRequest(request);
String productCode = request.getProductCode().trim();
String countryCode = emptyToNull(request.getCountryCode());
String countryName = emptyToNull(request.getCountryName());
if (countryCode != null)
{
countryCode = countryCode.toUpperCase();
}
BigDecimal weight = request.getWeight();
Date bizDate = resolveBizDate(request.getBizDate());
BmsProductCountry base = productCountryMapper.selectMatchByProductCountryWeight(
productCode, countryCode, countryName, weight);
if (base == null)
{
throw new ServiceException("PRICE_NOT_HIT: no bms_product_country band for product/country/weight");
}
String productName = resolveProductName(productCode);
List<PrcCustomerPriceSet> binds = customerPriceSetMapper.selectEffectiveByCustomer(
request.getCustomerId(), bizDate);
if (binds != null)
{
for (PrcCustomerPriceSet bind : binds)
{
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
if (set == null || !"E".equals(set.getStatus()))
{
continue;
}
String setType = StringUtils.isEmpty(set.getSetType()) ? TYPE_FIXED : set.getSetType();
if (TYPE_FIXED.equals(setType))
{
TmsPriceQuoteResult hit = tryFixed(bind, set, base, productCode, productName, weight);
if (hit != null)
{
return hit;
}
}
else if (TYPE_DISCOUNT.equals(setType))
{
TmsPriceQuoteResult hit = tryDiscount(bind, set, base, productCode, productName, weight);
if (hit != null)
{
return hit;
}
}
}
}
return buildDefault(request.getCustomerId(), base, productCode, productName, weight,
"no customer package override; use product default freight/registration");
}
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set,
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
{
int detailCount = detailMapper.countByPriceSetId(set.getPriceSetId());
if (detailCount == 0)
{
TmsPriceQuoteResult r = buildFromRates(SRC_DEFAULT, base.getFreightPrice(), null, null,
base.getRegistrationFee(), base, productCode, productName, weight);
fillPackage(r, bind, set);
r.setMatchMessage("FIXED package has no details; use bms_product_country default");
r.setCustomerId(bind.getCustomerId());
return r;
}
PrcPriceSetDetail detail = detailMapper.selectFixedMatch(
set.getPriceSetId(), productCode,
base.getCountryCode(), base.getCountryName(), base.getDetailId(),
formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
if (detail == null)
{
return null;
}
if (detail.getFvValue() == null || detail.getFvRegistrationFee() == null)
{
return null;
}
TmsPriceQuoteResult r = buildFromRates(SRC_FIXED, detail.getFvValue(), null, null,
detail.getFvRegistrationFee(), base, productCode, productName, weight);
fillPackage(r, bind, set);
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("hit customer FIXED package detail");
r.setCustomerId(bind.getCustomerId());
return r;
}
private TmsPriceQuoteResult tryDiscount(PrcCustomerPriceSet bind, PrcPriceSet set,
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
{
int detailCount = detailMapper.countByPriceSetId(set.getPriceSetId());
BigDecimal discount = set.getDefaultDiscount();
if (detailCount > 0)
{
PrcPriceSetDetail detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), productCode);
if (detail == null)
{
return null;
}
if (detail.getDiscountValue() != null)
{
discount = detail.getDiscountValue();
}
}
if (discount == null
|| discount.compareTo(BigDecimal.ONE) < 0
|| discount.compareTo(new BigDecimal("99")) > 0)
{
return null;
}
if (base.getFreightPrice() == null)
{
throw new ServiceException("FREIGHT_VALUE_MISSING: product default freight rate is null");
}
BigDecimal baseRate = base.getFreightPrice();
BigDecimal rate = baseRate.multiply(discount)
.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
TmsPriceQuoteResult r = buildFromRates(SRC_DISCOUNT, rate, baseRate, discount,
base.getRegistrationFee(), base, productCode, productName, weight);
fillPackage(r, bind, set);
r.setMatchMessage("hit customer DISCOUNT package; freight = default * discount%");
r.setCustomerId(bind.getCustomerId());
return r;
}
private TmsPriceQuoteResult buildDefault(Long customerId, BmsProductCountry base,
String productCode, String productName, BigDecimal weight, String message)
{
if (base.getFreightPrice() == null || base.getRegistrationFee() == null)
{
throw new ServiceException("FREIGHT_VALUE_MISSING: product default freight or registration is null");
}
TmsPriceQuoteResult r = buildFromRates(SRC_DEFAULT, base.getFreightPrice(), null, null,
base.getRegistrationFee(), base, productCode, productName, weight);
r.setCustomerId(customerId);
r.setMatchMessage(message);
return r;
}
private TmsPriceQuoteResult buildFromRates(String source, BigDecimal freightRate,
BigDecimal baseFreightRate, BigDecimal discountPercent, BigDecimal registrationFee,
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
{
if (freightRate == null)
{
throw new ServiceException("FREIGHT_VALUE_MISSING: freight rate is null");
}
if (registrationFee == null)
{
registrationFee = BigDecimal.ZERO;
}
BigDecimal freightAmount = freightRate.multiply(weight).setScale(2, RoundingMode.HALF_UP);
BigDecimal payable = freightAmount.add(registrationFee).setScale(2, RoundingMode.HALF_UP);
TmsPriceQuoteResult r = new TmsPriceQuoteResult();
r.setMatchSource(source);
r.setProductCode(productCode);
r.setProductName(productName);
r.setCountryCode(base.getCountryCode());
r.setCountryName(base.getCountryName());
r.setProductCountryId(base.getDetailId());
r.setWeightMin(base.getWeightMin());
r.setWeightMax(base.getWeightMax());
r.setWeightLabel(formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
r.setWeight(weight);
r.setChargeWeight(weight);
r.setFreightRate(freightRate);
r.setBaseFreightRate(baseFreightRate);
r.setDiscountPercent(discountPercent);
r.setRegistrationFee(registrationFee);
r.setFreightAmount(freightAmount);
r.setPayableTotal(payable);
r.setCurrency("RMB");
return r;
}
private void fillPackage(TmsPriceQuoteResult r, PrcCustomerPriceSet bind, PrcPriceSet set)
{
r.setPriceSetId(set.getPriceSetId());
r.setPriceSetCode(set.getPriceSetCode());
r.setPriceSetName(set.getPriceSetName());
r.setSetType(set.getSetType());
r.setBindPriority(bind.getBindPriority());
r.setCustomerPricesetId(bind.getCustomerPricesetId());
}
private void validateRequest(TmsPriceQuoteRequest request)
{
if (request == null)
{
throw new ServiceException("request body is required");
}
if (request.getCustomerId() == null)
{
throw new ServiceException("customerId is required");
}
if (StringUtils.isEmpty(request.getProductCode()))
{
throw new ServiceException("productCode is required");
}
if (StringUtils.isEmpty(request.getCountryCode()) && StringUtils.isEmpty(request.getCountryName()))
{
throw new ServiceException("countryCode or countryName is required");
}
if (request.getWeight() == null || request.getWeight().compareTo(BigDecimal.ZERO) <= 0)
{
throw new ServiceException("weight must be > 0");
}
}
private Date resolveBizDate(String bizDate)
{
if (StringUtils.isEmpty(bizDate))
{
return new Date();
}
try
{
return new SimpleDateFormat("yyyy-MM-dd").parse(bizDate.trim());
}
catch (ParseException e)
{
throw new ServiceException("bizDate format must be yyyy-MM-dd");
}
}
private String resolveProductName(String productCode)
{
BmsProduct p = productMapper.selectBmsProductByCode(productCode);
return p == null ? null : p.getProductName();
}
private static String emptyToNull(String s)
{
return StringUtils.isEmpty(s) ? null : s.trim();
}
private static String formatWeightLabel(BigDecimal min, BigDecimal max)
{
return strip(min) + "\u2264W\u2264" + strip(max);
}
private static String strip(BigDecimal v)
{
if (v == null)
{
return "";
}
return v.stripTrailingZeros().toPlainString();
}
}