增加试算功能
This commit is contained in:
+76
@@ -0,0 +1,76 @@
|
||||
package com.ruoyi.web.controller.api;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.framework.config.properties.TmsApiProperties;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
/**
|
||||
* TMS open API: freight quote.
|
||||
*
|
||||
* POST /api/tms/price/quote
|
||||
* Header X-Api-Code must equal bms.tms.api-code
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Anonymous
|
||||
@RestController
|
||||
@RequestMapping("/api/tms/price")
|
||||
public class TmsPriceController extends BaseController
|
||||
{
|
||||
public static final String HEADER_API_CODE = "X-Api-Code";
|
||||
|
||||
@Autowired
|
||||
private ITmsPriceQuoteService tmsPriceQuoteService;
|
||||
|
||||
@Autowired
|
||||
private TmsApiProperties tmsApiProperties;
|
||||
|
||||
/**
|
||||
* Quote by customerId + productCode + country + weight.
|
||||
* Match: customer package FIXED > DISCOUNT > product default.
|
||||
*/
|
||||
@PostMapping("/quote")
|
||||
public AjaxResult quote(
|
||||
@RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
|
||||
@Validated @RequestBody TmsPriceQuoteRequest request)
|
||||
{
|
||||
assertApiCode(headerApiCode, request == null ? null : request.getApiCode());
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.quote(request);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
private void assertApiCode(String headerApiCode, String bodyApiCode)
|
||||
{
|
||||
String expected = tmsApiProperties.getApiCode();
|
||||
if (StringUtils.isEmpty(expected))
|
||||
{
|
||||
throw new ServiceException("API_CODE_NOT_CONFIGURED: bms.tms.api-code is empty");
|
||||
}
|
||||
String actual = StringUtils.isNotEmpty(headerApiCode) ? headerApiCode.trim() : null;
|
||||
if (StringUtils.isEmpty(actual) && StringUtils.isNotEmpty(bodyApiCode))
|
||||
{
|
||||
actual = bodyApiCode.trim();
|
||||
}
|
||||
if (StringUtils.isEmpty(actual))
|
||||
{
|
||||
throw new ServiceException("API_CODE_REQUIRED: missing header X-Api-Code (or body apiCode)");
|
||||
}
|
||||
if (!expected.equals(actual))
|
||||
{
|
||||
throw new ServiceException("API_CODE_INVALID: api identification code mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.web.controller.price;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
/**
|
||||
* Billing center: order quote trial (authenticated).
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/price/billing")
|
||||
public class BillingController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ITmsPriceQuoteService tmsPriceQuoteService;
|
||||
|
||||
/**
|
||||
* Same matching logic as TMS open API, for BMS console trial.
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:quote')")
|
||||
@PostMapping("/quote")
|
||||
public AjaxResult quote(@Validated @RequestBody TmsPriceQuoteRequest request)
|
||||
{
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.quote(request);
|
||||
return success(result);
|
||||
}
|
||||
}
|
||||
@@ -145,3 +145,9 @@ xss:
|
||||
excludes: /system/notice
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
|
||||
# BMS TMS open API
|
||||
bms:
|
||||
tms:
|
||||
# Agreed API identification code for TMS callers
|
||||
api-code: BMS.TMS._Q2WnPXiXwGW2Dl-iSGNzLfrhUgQogNo
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.framework.config.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* TMS open API properties.
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "bms.tms")
|
||||
public class TmsApiProperties
|
||||
{
|
||||
/**
|
||||
* Agreed API identification code (header X-Api-Code or body apiCode).
|
||||
*/
|
||||
private String apiCode = "BMS.TMS._Q2WnPXiXwGW2Dl-iSGNzLfrhUgQogNo";
|
||||
|
||||
public String getApiCode()
|
||||
{
|
||||
return apiCode;
|
||||
}
|
||||
|
||||
public void setApiCode(String apiCode)
|
||||
{
|
||||
this.apiCode = apiCode;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.price.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* TMS freight quote request.
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class TmsPriceQuoteRequest implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull(message = "customerId is required")
|
||||
private Long customerId;
|
||||
|
||||
@NotBlank(message = "productCode is required")
|
||||
private String productCode;
|
||||
|
||||
/** ISO2 country code; provide countryCode or countryName */
|
||||
private String countryCode;
|
||||
|
||||
/** Country CN name; provide countryCode or countryName */
|
||||
private String countryName;
|
||||
|
||||
@NotNull(message = "weight is required")
|
||||
private BigDecimal weight;
|
||||
|
||||
/** Optional business date yyyy-MM-dd; default today */
|
||||
private String bizDate;
|
||||
|
||||
/** Optional TMS / tenant id */
|
||||
private Integer tmsId;
|
||||
|
||||
/**
|
||||
* Agreed API identification code (same as header X-Api-Code).
|
||||
* Prefer header; body is fallback.
|
||||
*/
|
||||
private String apiCode;
|
||||
|
||||
public Long getCustomerId() { return customerId; }
|
||||
public void setCustomerId(Long customerId) { this.customerId = customerId; }
|
||||
public String getProductCode() { return productCode; }
|
||||
public void setProductCode(String productCode) { this.productCode = productCode; }
|
||||
public String getCountryCode() { return countryCode; }
|
||||
public void setCountryCode(String countryCode) { this.countryCode = countryCode; }
|
||||
public String getCountryName() { return countryName; }
|
||||
public void setCountryName(String countryName) { this.countryName = countryName; }
|
||||
public BigDecimal getWeight() { return weight; }
|
||||
public void setWeight(BigDecimal weight) { this.weight = weight; }
|
||||
public String getBizDate() { return bizDate; }
|
||||
public void setBizDate(String bizDate) { this.bizDate = bizDate; }
|
||||
public Integer getTmsId() { return tmsId; }
|
||||
public void setTmsId(Integer tmsId) { this.tmsId = tmsId; }
|
||||
public String getApiCode() { return apiCode; }
|
||||
public void setApiCode(String apiCode) { this.apiCode = apiCode; }
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package com.ruoyi.price.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* TMS freight quote response.
|
||||
*
|
||||
* matchSource: PACKAGE_FIXED / PACKAGE_DISCOUNT / PRODUCT_DEFAULT
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class TmsPriceQuoteResult implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String matchSource;
|
||||
private String matchMessage;
|
||||
|
||||
private Long customerId;
|
||||
private Long priceSetId;
|
||||
private String priceSetCode;
|
||||
private String priceSetName;
|
||||
private String setType;
|
||||
private Integer bindPriority;
|
||||
private Long customerPricesetId;
|
||||
|
||||
private String productCode;
|
||||
private String productName;
|
||||
private String countryCode;
|
||||
private String countryName;
|
||||
private Long productCountryId;
|
||||
private BigDecimal weightMin;
|
||||
private BigDecimal weightMax;
|
||||
private String weightLabel;
|
||||
|
||||
private BigDecimal weight;
|
||||
private BigDecimal chargeWeight;
|
||||
/** Freight unit price RMB/KG (after discount if any) */
|
||||
private BigDecimal freightRate;
|
||||
/** Base freight before discount (DISCOUNT only) */
|
||||
private BigDecimal baseFreightRate;
|
||||
/** Discount percent e.g. 88 */
|
||||
private BigDecimal discountPercent;
|
||||
/** Registration fee RMB/pcs */
|
||||
private BigDecimal registrationFee;
|
||||
/** freightRate * chargeWeight */
|
||||
private BigDecimal freightAmount;
|
||||
/** freightAmount + registrationFee */
|
||||
private BigDecimal payableTotal;
|
||||
private String currency = "RMB";
|
||||
|
||||
public String getMatchSource() { return matchSource; }
|
||||
public void setMatchSource(String matchSource) { this.matchSource = matchSource; }
|
||||
public String getMatchMessage() { return matchMessage; }
|
||||
public void setMatchMessage(String matchMessage) { this.matchMessage = matchMessage; }
|
||||
public Long getCustomerId() { return customerId; }
|
||||
public void setCustomerId(Long customerId) { this.customerId = customerId; }
|
||||
public Long getPriceSetId() { return priceSetId; }
|
||||
public void setPriceSetId(Long priceSetId) { this.priceSetId = priceSetId; }
|
||||
public String getPriceSetCode() { return priceSetCode; }
|
||||
public void setPriceSetCode(String priceSetCode) { this.priceSetCode = priceSetCode; }
|
||||
public String getPriceSetName() { return priceSetName; }
|
||||
public void setPriceSetName(String priceSetName) { this.priceSetName = priceSetName; }
|
||||
public String getSetType() { return setType; }
|
||||
public void setSetType(String setType) { this.setType = setType; }
|
||||
public Integer getBindPriority() { return bindPriority; }
|
||||
public void setBindPriority(Integer bindPriority) { this.bindPriority = bindPriority; }
|
||||
public Long getCustomerPricesetId() { return customerPricesetId; }
|
||||
public void setCustomerPricesetId(Long customerPricesetId) { this.customerPricesetId = customerPricesetId; }
|
||||
public String getProductCode() { return productCode; }
|
||||
public void setProductCode(String productCode) { this.productCode = productCode; }
|
||||
public String getProductName() { return productName; }
|
||||
public void setProductName(String productName) { this.productName = productName; }
|
||||
public String getCountryCode() { return countryCode; }
|
||||
public void setCountryCode(String countryCode) { this.countryCode = countryCode; }
|
||||
public String getCountryName() { return countryName; }
|
||||
public void setCountryName(String countryName) { this.countryName = countryName; }
|
||||
public Long getProductCountryId() { return productCountryId; }
|
||||
public void setProductCountryId(Long productCountryId) { this.productCountryId = productCountryId; }
|
||||
public BigDecimal getWeightMin() { return weightMin; }
|
||||
public void setWeightMin(BigDecimal weightMin) { this.weightMin = weightMin; }
|
||||
public BigDecimal getWeightMax() { return weightMax; }
|
||||
public void setWeightMax(BigDecimal weightMax) { this.weightMax = weightMax; }
|
||||
public String getWeightLabel() { return weightLabel; }
|
||||
public void setWeightLabel(String weightLabel) { this.weightLabel = weightLabel; }
|
||||
public BigDecimal getWeight() { return weight; }
|
||||
public void setWeight(BigDecimal weight) { this.weight = weight; }
|
||||
public BigDecimal getChargeWeight() { return chargeWeight; }
|
||||
public void setChargeWeight(BigDecimal chargeWeight) { this.chargeWeight = chargeWeight; }
|
||||
public BigDecimal getFreightRate() { return freightRate; }
|
||||
public void setFreightRate(BigDecimal freightRate) { this.freightRate = freightRate; }
|
||||
public BigDecimal getBaseFreightRate() { return baseFreightRate; }
|
||||
public void setBaseFreightRate(BigDecimal baseFreightRate) { this.baseFreightRate = baseFreightRate; }
|
||||
public BigDecimal getDiscountPercent() { return discountPercent; }
|
||||
public void setDiscountPercent(BigDecimal discountPercent) { this.discountPercent = discountPercent; }
|
||||
public BigDecimal getRegistrationFee() { return registrationFee; }
|
||||
public void setRegistrationFee(BigDecimal registrationFee) { this.registrationFee = registrationFee; }
|
||||
public BigDecimal getFreightAmount() { return freightAmount; }
|
||||
public void setFreightAmount(BigDecimal freightAmount) { this.freightAmount = freightAmount; }
|
||||
public BigDecimal getPayableTotal() { return payableTotal; }
|
||||
public void setPayableTotal(BigDecimal payableTotal) { this.payableTotal = payableTotal; }
|
||||
public String getCurrency() { return currency; }
|
||||
public void setCurrency(String currency) { this.currency = currency; }
|
||||
}
|
||||
+11
@@ -1,6 +1,8 @@
|
||||
package com.ruoyi.price.mapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.price.domain.BmsProductCountry;
|
||||
|
||||
/**
|
||||
@@ -17,4 +19,13 @@ public interface BmsProductCountryMapper
|
||||
public int deleteByProductCode(String productCode);
|
||||
|
||||
public int deleteByProductCodes(String[] productCodes);
|
||||
|
||||
/**
|
||||
* 按产品 + 国家 + 重量落入重量段匹配一条默认价
|
||||
*/
|
||||
public BmsProductCountry selectMatchByProductCountryWeight(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("weight") BigDecimal weight);
|
||||
}
|
||||
|
||||
+7
@@ -29,4 +29,11 @@ public interface PrcCustomerPriceSetMapper
|
||||
@Param("priceSetId") Long priceSetId,
|
||||
@Param("beginDate") java.util.Date beginDate,
|
||||
@Param("endDate") java.util.Date endDate);
|
||||
|
||||
/**
|
||||
* 客户在业务日有效、且套餐状态为生效(E) 的绑定,按优先级升序
|
||||
*/
|
||||
public java.util.List<PrcCustomerPriceSet> selectEffectiveByCustomer(
|
||||
@Param("customerId") Long customerId,
|
||||
@Param("bizDate") java.util.Date bizDate);
|
||||
}
|
||||
|
||||
+15
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.price.domain.PrcPriceSetDetail;
|
||||
|
||||
/**
|
||||
@@ -25,4 +26,18 @@ public interface PrcPriceSetDetailMapper
|
||||
public int countProductByPriceSetId(Long priceSetId);
|
||||
|
||||
public int countByPriceSetId(Long priceSetId);
|
||||
|
||||
/** FIXED:按产品+国家+关联产品国家明细/重量段命中 */
|
||||
public PrcPriceSetDetail selectFixedMatch(
|
||||
@Param("priceSetId") Long priceSetId,
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("productCountryId") Long productCountryId,
|
||||
@Param("weightLabel") String weightLabel);
|
||||
|
||||
/** DISCOUNT:按产品命中明细折扣(可空) */
|
||||
public PrcPriceSetDetail selectDiscountMatch(
|
||||
@Param("priceSetId") Long priceSetId,
|
||||
@Param("productCode") String productCode);
|
||||
}
|
||||
|
||||
+17
@@ -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);
|
||||
}
|
||||
+324
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="selectMatchByProductCountryWeight" resultMap="BmsProductCountryResult">
|
||||
select detail_id, product_code, country_name, country_code, weight_min, weight_max,
|
||||
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||
effective_date, created_at, updated_at
|
||||
from bms_product_country
|
||||
where product_code = #{productCode}
|
||||
and weight_min <= #{weight}
|
||||
and weight_max >= #{weight}
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(country_code) = upper(#{countryCode})
|
||||
</when>
|
||||
<when test="countryName != null and countryName != ''">
|
||||
and country_name = #{countryName}
|
||||
</when>
|
||||
<otherwise>
|
||||
and 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
order by weight_min asc, detail_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -64,6 +64,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and ifnull(c.pr_enddate, '9999-12-31') >= #{beginDate}
|
||||
</select>
|
||||
|
||||
<select id="selectEffectiveByCustomer" resultMap="PrcCustomerPriceSetResult">
|
||||
select c.customer_priceset_id, c.price_set_id, c.customer_id, c.customer_name, c.bind_priority,
|
||||
c.pr_begindate, c.pr_enddate, c.price_set_note,
|
||||
c.created_by, c.created_at, c.updated_by, c.updated_at
|
||||
from prc_customer_price_set c
|
||||
inner join prc_price_set s on s.price_set_id = c.price_set_id
|
||||
where c.customer_id = #{customerId}
|
||||
and s.status = 'E'
|
||||
and c.pr_begindate <= #{bizDate}
|
||||
and ifnull(c.pr_enddate, '9999-12-31') >= #{bizDate}
|
||||
order by c.bind_priority asc, c.pr_begindate desc, c.customer_priceset_id asc
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcCustomerPriceSet" parameterType="PrcCustomerPriceSet" useGeneratedKeys="true" keyProperty="customerPricesetId">
|
||||
insert into prc_customer_price_set (
|
||||
<if test="priceSetId != null">price_set_id,</if>
|
||||
|
||||
@@ -124,4 +124,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
delete from prc_price_set_detail where price_set_id = #{priceSetId}
|
||||
</delete>
|
||||
|
||||
<select id="selectFixedMatch" resultMap="PrcPriceSetDetailResult">
|
||||
<include refid="selectPrcPriceSetDetailVo"/>
|
||||
where price_set_id = #{priceSetId}
|
||||
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="weightLabel != null and weightLabel != ''">
|
||||
and (weight_label is null or weight_label = #{weightLabel} or weight_label like concat('%', #{weightLabel}, '%'))
|
||||
</if>
|
||||
)
|
||||
)
|
||||
order by
|
||||
case when #{productCountryId} is not null and product_country_id = #{productCountryId} then 0 else 1 end,
|
||||
psd_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectDiscountMatch" resultMap="PrcPriceSetDetailResult">
|
||||
<include refid="selectPrcPriceSetDetailVo"/>
|
||||
where price_set_id = #{priceSetId}
|
||||
and product_code = #{productCode}
|
||||
order by psd_id asc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Billing center menus
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2300, N'计费中心', 0, 9, 'billing', NULL, '', '', 1, 0, 'M', '0', '0', '', 'money', 'admin', sysdate(), N'计费中心目录'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2300);
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2301, N'下单计费试算', 2300, 1, 'quote', 'price/billing/quote', '', 'BillingQuote', 1, 0, 'C', '0', '0', 'price:billing:quote', 'edit', 'admin', sysdate(), N'下单计费试算'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2301);
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2302, N'试算执行', 2301, 1, '', NULL, 1, 0, 'F', '0', '0', 'price:billing:quote', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2302);
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, m.menu_id FROM (SELECT 2300 AS menu_id UNION SELECT 2301 UNION SELECT 2302) m
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id);
|
||||
Reference in New Issue
Block a user