修改计费逻辑
This commit is contained in:
@@ -26,7 +26,8 @@ public class PrcQuote extends BaseEntity
|
|||||||
private String countryName;
|
private String countryName;
|
||||||
private String destPostcode;
|
private String destPostcode;
|
||||||
private BigDecimal weight;
|
private BigDecimal weight;
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
/** 计费时间 */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date bizDate;
|
private Date bizDate;
|
||||||
private Long tmsId;
|
private Long tmsId;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* Match order:
|
* Match order:
|
||||||
* 1) Customer binds (package not stopped), bind_priority ASC; bind date range is not used for billing
|
* 1) Customer binds (package not stopped), bind_priority ASC; bind date range is not used for billing
|
||||||
* 2) Active package version at bill-time end-of-day (version effective_time / expire_time)
|
* 2) Active package version at bill datetime (version effective_time / expire_time)
|
||||||
* 3) FIXED detail override, else DISCOUNT on that version
|
* 3) FIXED detail override, else DISCOUNT on that version
|
||||||
* 4) Else product default only when a version existed but detail missed
|
* 4) Else product default only when a version existed but detail missed
|
||||||
* Package hit uses version surcharge snapshot when present; else product library.
|
* Package hit uses version surcharge snapshot when present; else product library.
|
||||||
|
|||||||
+20
-35
@@ -240,8 +240,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
{
|
{
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
Date day = bizDate != null ? bizDate : new Date();
|
Date asOf = bizDate != null ? bizDate : new Date();
|
||||||
Date asOf = endOfDay(day);
|
|
||||||
for (PrcCustomerPriceSet bind : binds)
|
for (PrcCustomerPriceSet bind : binds)
|
||||||
{
|
{
|
||||||
if (bind == null || bind.getPriceSetId() == null)
|
if (bind == null || bind.getPriceSetId() == null)
|
||||||
@@ -355,7 +354,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
boolean productExcludedBySetProducts = false;
|
boolean productExcludedBySetProducts = false;
|
||||||
boolean attemptedPriceMatch = false;
|
boolean attemptedPriceMatch = false;
|
||||||
boolean anyZoneRequired = false;
|
boolean anyZoneRequired = false;
|
||||||
Date asOf = endOfDay(bizDate);
|
// Match version by exact bill datetime (effective_time / expire_time), not end-of-day.
|
||||||
|
Date asOf = bizDate != null ? bizDate : new Date();
|
||||||
for (PrcCustomerPriceSet bind : binds)
|
for (PrcCustomerPriceSet bind : binds)
|
||||||
{
|
{
|
||||||
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
|
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
|
||||||
@@ -406,7 +406,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
if (TYPE_FIXED.equals(setType))
|
if (TYPE_FIXED.equals(setType))
|
||||||
{
|
{
|
||||||
TmsPriceQuoteResult hit = tryFixed(bind, set, version, catalog, zone.zone,
|
TmsPriceQuoteResult hit = tryFixed(bind, set, version, catalog, zone.zone,
|
||||||
productCode, productName, cc, cn, zone.zoneCode, weight);
|
productCode, productName, cc, cn, zone.zoneCode, weight, bizDate);
|
||||||
if (hit != null)
|
if (hit != null)
|
||||||
{
|
{
|
||||||
stampRequestMeta(request, hit);
|
stampRequestMeta(request, hit);
|
||||||
@@ -419,7 +419,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
TmsPriceQuoteResult hit = tryDiscount(bind, set, version, catalog, productCode, productName, weight);
|
TmsPriceQuoteResult hit = tryDiscount(bind, set, version, catalog, productCode, productName, weight, bizDate);
|
||||||
if (hit != null)
|
if (hit != null)
|
||||||
{
|
{
|
||||||
stampRequestMeta(request, hit);
|
stampRequestMeta(request, hit);
|
||||||
@@ -691,7 +691,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
|
|
||||||
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
|
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
|
||||||
BmsProductCountry catalog, PrcZone resolvedZone, String productCode, String productName,
|
BmsProductCountry catalog, PrcZone resolvedZone, String productCode, String productName,
|
||||||
String countryCode, String countryName, String zoneCode, BigDecimal weight)
|
String countryCode, String countryName, String zoneCode, BigDecimal weight, Date bizDate)
|
||||||
{
|
{
|
||||||
Long psvId = version.getPsvId();
|
Long psvId = version.getPsvId();
|
||||||
int detailCount = detailMapper.countByPsvId(psvId);
|
int detailCount = detailMapper.countByPsvId(psvId);
|
||||||
@@ -713,7 +713,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TmsPriceQuoteResult r = buildFromPackageDetail(SRC_FIXED, detail, catalog, resolvedZone,
|
TmsPriceQuoteResult r = buildFromPackageDetail(SRC_FIXED, detail, catalog, resolvedZone,
|
||||||
productCode, productName, weight, detail.getFvValue(), null, null, detail.getFvRegistrationFee());
|
productCode, productName, weight, detail.getFvValue(), null, null, detail.getFvRegistrationFee(), bizDate);
|
||||||
fillPackage(r, bind, set);
|
fillPackage(r, bind, set);
|
||||||
stampVersion(r, version, detail.getPsdId());
|
stampVersion(r, version, detail.getPsdId());
|
||||||
r.setMatchMessage(MessageUtils.message("price.quote.hitCustomerFixedPackageDetail")
|
r.setMatchMessage(MessageUtils.message("price.quote.hitCustomerFixedPackageDetail")
|
||||||
@@ -723,7 +723,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TmsPriceQuoteResult tryDiscount(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
|
private TmsPriceQuoteResult tryDiscount(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
|
||||||
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
|
BmsProductCountry base, String productCode, String productName, BigDecimal weight, Date bizDate)
|
||||||
{
|
{
|
||||||
Long psvId = version.getPsvId();
|
Long psvId = version.getPsvId();
|
||||||
int detailCount = detailMapper.countByPsvId(psvId);
|
int detailCount = detailMapper.countByPsvId(psvId);
|
||||||
@@ -760,7 +760,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
BigDecimal rate = baseRate.multiply(discount)
|
BigDecimal rate = baseRate.multiply(discount)
|
||||||
.divide(new BigDecimal("100"), 2, rm);
|
.divide(new BigDecimal("100"), 2, rm);
|
||||||
TmsPriceQuoteResult r = buildFromRates(SRC_DISCOUNT, rate, baseRate, discount,
|
TmsPriceQuoteResult r = buildFromRates(SRC_DISCOUNT, rate, baseRate, discount,
|
||||||
base.getRegistrationFee(), base, productCode, productName, weight, roundMode);
|
base.getRegistrationFee(), base, productCode, productName, weight, roundMode, bizDate);
|
||||||
fillPackage(r, bind, set);
|
fillPackage(r, bind, set);
|
||||||
stampVersion(r, version, psdId);
|
stampVersion(r, version, psdId);
|
||||||
r.setMatchMessage("hit customer DISCOUNT package version "
|
r.setMatchMessage("hit customer DISCOUNT package version "
|
||||||
@@ -787,7 +787,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
private TmsPriceQuoteResult buildFromPackageDetail(String source, PrcPriceSetDetail detail,
|
private TmsPriceQuoteResult buildFromPackageDetail(String source, PrcPriceSetDetail detail,
|
||||||
BmsProductCountry catalog, PrcZone resolvedZone, String productCode, String productName,
|
BmsProductCountry catalog, PrcZone resolvedZone, String productCode, String productName,
|
||||||
BigDecimal weight, BigDecimal freightRate, BigDecimal baseFreightRate, BigDecimal discountPercent,
|
BigDecimal weight, BigDecimal freightRate, BigDecimal baseFreightRate, BigDecimal discountPercent,
|
||||||
BigDecimal registrationFee)
|
BigDecimal registrationFee, Date bizDate)
|
||||||
{
|
{
|
||||||
if (freightRate == null)
|
if (freightRate == null)
|
||||||
{
|
{
|
||||||
@@ -879,15 +879,16 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
r.setCurrency("CNY");
|
r.setCurrency("CNY");
|
||||||
r.setRoundMode(mode);
|
r.setRoundMode(mode);
|
||||||
r.setFromCache(false);
|
r.setFromCache(false);
|
||||||
|
r.setBizDate(bizDate);
|
||||||
fillProductAmountAndPay(r, catalog, weight, rm, mode);
|
fillProductAmountAndPay(r, catalog, weight, rm, mode);
|
||||||
fillRedlineAmount(r, weight, rm);
|
fillRedlineAmount(r, weight, rm, bizDate);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TmsPriceQuoteResult buildFromRates(String source, BigDecimal freightRate,
|
private TmsPriceQuoteResult buildFromRates(String source, BigDecimal freightRate,
|
||||||
BigDecimal baseFreightRate, BigDecimal discountPercent, BigDecimal registrationFee,
|
BigDecimal baseFreightRate, BigDecimal discountPercent, BigDecimal registrationFee,
|
||||||
BmsProductCountry base, String productCode, String productName, BigDecimal weight,
|
BmsProductCountry base, String productCode, String productName, BigDecimal weight,
|
||||||
String roundMode)
|
String roundMode, Date bizDate)
|
||||||
{
|
{
|
||||||
if (freightRate == null)
|
if (freightRate == null)
|
||||||
{
|
{
|
||||||
@@ -935,8 +936,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
r.setCurrency("CNY");
|
r.setCurrency("CNY");
|
||||||
r.setRoundMode(mode);
|
r.setRoundMode(mode);
|
||||||
r.setFromCache(false);
|
r.setFromCache(false);
|
||||||
|
r.setBizDate(bizDate);
|
||||||
fillProductAmountAndPay(r, base, weight, rm, mode);
|
fillProductAmountAndPay(r, base, weight, rm, mode);
|
||||||
fillRedlineAmount(r, weight, rm);
|
fillRedlineAmount(r, weight, rm, bizDate);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,16 +973,17 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redline catalog amount from redline_product_country (same match keys as product catalog).
|
* Redline catalog amount from redline_product_country
|
||||||
|
* (same match keys as product catalog, including bill datetime).
|
||||||
*/
|
*/
|
||||||
private void fillRedlineAmount(TmsPriceQuoteResult r, BigDecimal weight, RoundingMode rm)
|
private void fillRedlineAmount(TmsPriceQuoteResult r, BigDecimal weight, RoundingMode rm, Date bizDate)
|
||||||
{
|
{
|
||||||
if (r == null || weight == null || StringUtils.isEmpty(r.getProductCode()))
|
if (r == null || weight == null || StringUtils.isEmpty(r.getProductCode()))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BmsRedlineProductCountry base = redlineProductCountryMapper.selectMatchByProductCountryWeight(
|
BmsRedlineProductCountry base = redlineProductCountryMapper.selectMatchByProductCountryWeight(
|
||||||
r.getProductCode(), r.getCountryCode(), r.getCountryName(), r.getZoneCode(), weight, null);
|
r.getProductCode(), r.getCountryCode(), r.getCountryName(), r.getZoneCode(), weight, bizDate);
|
||||||
if (base == null || base.getFreightPrice() == null)
|
if (base == null || base.getFreightPrice() == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -1469,7 +1472,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
countryCode = countryCode.toUpperCase();
|
countryCode = countryCode.toUpperCase();
|
||||||
}
|
}
|
||||||
String countryName = emptyToNull(request.getCountryName());
|
String countryName = emptyToNull(request.getCountryName());
|
||||||
String biz = new SimpleDateFormat("yyyy-MM-dd").format(bizDate);
|
String biz = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(bizDate);
|
||||||
String raw = request.getCustomerCode().trim() + "|"
|
String raw = request.getCustomerCode().trim() + "|"
|
||||||
+ request.getProductCode().trim() + "|"
|
+ request.getProductCode().trim() + "|"
|
||||||
+ nvlStr(countryCode) + "|"
|
+ nvlStr(countryCode) + "|"
|
||||||
@@ -1639,24 +1642,6 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Use end of bill day so same-day version activate still matches that day's quote. */
|
|
||||||
private static Date endOfDay(Date day)
|
|
||||||
{
|
|
||||||
if (day == null)
|
|
||||||
{
|
|
||||||
return new Date();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String d = new SimpleDateFormat("yyyy-MM-dd").format(day);
|
|
||||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(d + " 23:59:59");
|
|
||||||
}
|
|
||||||
catch (ParseException e)
|
|
||||||
{
|
|
||||||
return day;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveProductName(String productCode, Date bizDate)
|
private String resolveProductName(String productCode, Date bizDate)
|
||||||
{
|
{
|
||||||
BmsProduct p = productMapper.selectEffectiveBmsProductByCode(productCode, bizDate);
|
BmsProduct p = productMapper.selectEffectiveBmsProductByCode(productCode, bizDate);
|
||||||
|
|||||||
@@ -1427,7 +1427,7 @@ CREATE TABLE `prc_price_set_product` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
CREATE TABLE `prc_quote` (
|
CREATE TABLE `prc_quote` (
|
||||||
`quote_id` varchar(36) COLLATE utf8mb4_unicode_ci NOT NULL,
|
`quote_id` varchar(36) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
`quote_key` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '试算指纹 MD5(客户|产品|国家|重量|计费日期)',
|
`quote_key` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '试算指纹 MD5(客户|产品|国家|重量|计费时间)',
|
||||||
`customer_id` bigint(20) NOT NULL COMMENT '客户ID',
|
`customer_id` bigint(20) NOT NULL COMMENT '客户ID',
|
||||||
`customer_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'customer code',
|
`customer_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'customer code',
|
||||||
`product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码',
|
`product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码',
|
||||||
@@ -1437,7 +1437,7 @@ CREATE TABLE `prc_quote` (
|
|||||||
`dest_postcode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'dest postcode',
|
`dest_postcode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'dest postcode',
|
||||||
`quote_postcode_key` varchar(20) COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS (ifnull(`dest_postcode`,'')) STORED COMMENT 'dest_postcode for unique key',
|
`quote_postcode_key` varchar(20) COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS (ifnull(`dest_postcode`,'')) STORED COMMENT 'dest_postcode for unique key',
|
||||||
`weight` decimal(10,3) NOT NULL COMMENT '计费重量KG',
|
`weight` decimal(10,3) NOT NULL COMMENT '计费重量KG',
|
||||||
`biz_date` date NOT NULL COMMENT '计费日期',
|
`biz_date` datetime NOT NULL COMMENT '计费时间',
|
||||||
`tms_id` bigint(20) DEFAULT NULL COMMENT '多租户系统ID',
|
`tms_id` bigint(20) DEFAULT NULL COMMENT '多租户系统ID',
|
||||||
`match_source` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中来源 PACKAGE_FIXED/套餐固定价 PACKAGE_DISCOUNT/套餐折扣 PRODUCT_DEFAULT/产品默认价',
|
`match_source` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中来源 PACKAGE_FIXED/套餐固定价 PACKAGE_DISCOUNT/套餐折扣 PRODUCT_DEFAULT/产品默认价',
|
||||||
`match_message` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中说明',
|
`match_message` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中说明',
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- prc_quote.biz_date: date -> datetime (same-day different bill times)
|
||||||
|
ALTER TABLE prc_quote
|
||||||
|
MODIFY COLUMN `biz_date` datetime NOT NULL COMMENT '计费时间',
|
||||||
|
MODIFY COLUMN `quote_key` char(32) NOT NULL COMMENT 'MD5(customer|product|country|weight|biz_datetime)';
|
||||||
@@ -68,8 +68,8 @@
|
|||||||
<el-form-item label="计费时间" prop="bizDate">
|
<el-form-item label="计费时间" prop="bizDate">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="form.bizDate"
|
v-model="form.bizDate"
|
||||||
type="date"
|
type="datetime"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
placeholder="默认今天,用于筛选已生效的产品明细"
|
placeholder="默认今天,用于筛选已生效的产品明细"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -288,7 +288,7 @@ const historyColumns = [
|
|||||||
{ label: '产品', prop: 'product', minWidth: 160, slot: 'product' },
|
{ label: '产品', prop: 'product', minWidth: 160, slot: 'product' },
|
||||||
{ label: '国家', prop: 'country', minWidth: 130, slot: 'country' },
|
{ label: '国家', prop: 'country', minWidth: 130, slot: 'country' },
|
||||||
{ label: '计费重量', prop: 'weight', align: 'center', width: 100, slot: 'weight' },
|
{ label: '计费重量', prop: 'weight', align: 'center', width: 100, slot: 'weight' },
|
||||||
{ label: '计费日期', prop: 'bizDate', align: 'center', width: 120 },
|
{ label: '计费时间', prop: 'bizDate', align: 'center', width: 170, parseTime: true },
|
||||||
{ label: '计价来源', prop: 'matchSource', align: 'center', width: 140, slot: 'matchSource' },
|
{ label: '计价来源', prop: 'matchSource', align: 'center', width: 140, slot: 'matchSource' },
|
||||||
{ label: '套餐', prop: 'priceSetName', minWidth: 120, showOverflowTooltip: true },
|
{ label: '套餐', prop: 'priceSetName', minWidth: 120, showOverflowTooltip: true },
|
||||||
{ label: '产品价', prop: 'productAmount', align: 'right', width: 110 },
|
{ label: '产品价', prop: 'productAmount', align: 'right', width: 110 },
|
||||||
@@ -527,10 +527,15 @@ function formatRate(val) {
|
|||||||
|
|
||||||
function formatDate(val) {
|
function formatDate(val) {
|
||||||
if (!val) return '-'
|
if (!val) return '-'
|
||||||
// 兼容后端 java.util.Date 转 ISO 字符串
|
const raw = String(val).replace('T', ' ')
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}/.test(raw)) {
|
||||||
|
return raw.length >= 19 ? raw.slice(0, 19) : raw.slice(0, 10)
|
||||||
|
}
|
||||||
const d = new Date(val)
|
const d = new Date(val)
|
||||||
if (Number.isNaN(d.getTime())) return val
|
if (Number.isNaN(d.getTime())) return val
|
||||||
return d.toISOString().slice(0, 10)
|
const pad = (n) => String(n).padStart(2, '0')
|
||||||
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||||||
|
+ ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
function sourceLabel(source) {
|
function sourceLabel(source) {
|
||||||
|
|||||||
Reference in New Issue
Block a user