修改计费逻辑

This commit is contained in:
wuweihua
2026-10-12 14:44:20 +08:00
parent 4d41b0fe12
commit a115926e9f
6 changed files with 39 additions and 44 deletions
@@ -26,7 +26,8 @@ public class PrcQuote extends BaseEntity
private String countryName;
private String destPostcode;
private BigDecimal weight;
@JsonFormat(pattern = "yyyy-MM-dd")
/** 计费时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bizDate;
private Long tmsId;
@@ -12,7 +12,7 @@ import java.util.List;
*
* Match order:
* 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
* 4) Else product default only when a version existed but detail missed
* Package hit uses version surcharge snapshot when present; else product library.
@@ -240,8 +240,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
return Collections.emptyList();
}
Date day = bizDate != null ? bizDate : new Date();
Date asOf = endOfDay(day);
Date asOf = bizDate != null ? bizDate : new Date();
for (PrcCustomerPriceSet bind : binds)
{
if (bind == null || bind.getPriceSetId() == null)
@@ -355,7 +354,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
boolean productExcludedBySetProducts = false;
boolean attemptedPriceMatch = 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)
{
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
@@ -406,7 +406,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
if (TYPE_FIXED.equals(setType))
{
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)
{
stampRequestMeta(request, hit);
@@ -419,7 +419,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
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)
{
stampRequestMeta(request, hit);
@@ -691,7 +691,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
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();
int detailCount = detailMapper.countByPsvId(psvId);
@@ -713,7 +713,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return null;
}
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);
stampVersion(r, version, detail.getPsdId());
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,
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
BmsProductCountry base, String productCode, String productName, BigDecimal weight, Date bizDate)
{
Long psvId = version.getPsvId();
int detailCount = detailMapper.countByPsvId(psvId);
@@ -760,7 +760,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
BigDecimal rate = baseRate.multiply(discount)
.divide(new BigDecimal("100"), 2, rm);
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);
stampVersion(r, version, psdId);
r.setMatchMessage("hit customer DISCOUNT package version "
@@ -787,7 +787,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
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)
BigDecimal registrationFee, Date bizDate)
{
if (freightRate == null)
{
@@ -879,15 +879,16 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
r.setCurrency("CNY");
r.setRoundMode(mode);
r.setFromCache(false);
r.setBizDate(bizDate);
fillProductAmountAndPay(r, catalog, weight, rm, mode);
fillRedlineAmount(r, weight, rm);
fillRedlineAmount(r, weight, rm, bizDate);
return r;
}
private TmsPriceQuoteResult buildFromRates(String source, BigDecimal freightRate,
BigDecimal baseFreightRate, BigDecimal discountPercent, BigDecimal registrationFee,
BmsProductCountry base, String productCode, String productName, BigDecimal weight,
String roundMode)
String roundMode, Date bizDate)
{
if (freightRate == null)
{
@@ -935,8 +936,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
r.setCurrency("CNY");
r.setRoundMode(mode);
r.setFromCache(false);
r.setBizDate(bizDate);
fillProductAmountAndPay(r, base, weight, rm, mode);
fillRedlineAmount(r, weight, rm);
fillRedlineAmount(r, weight, rm, bizDate);
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()))
{
return;
}
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)
{
return;
@@ -1469,7 +1472,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
countryCode = countryCode.toUpperCase();
}
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() + "|"
+ request.getProductCode().trim() + "|"
+ nvlStr(countryCode) + "|"
@@ -1639,24 +1642,6 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
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)
{
BmsProduct p = productMapper.selectEffectiveBmsProductByCode(productCode, bizDate);
+2 -2
View File
@@ -1427,7 +1427,7 @@ CREATE TABLE `prc_price_set_product` (
-- ----------------------------
CREATE TABLE `prc_quote` (
`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_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'customer code',
`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',
`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',
`biz_date` date NOT NULL COMMENT '计费日期',
`biz_date` datetime NOT NULL COMMENT '计费时间',
`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_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)';
+10 -5
View File
@@ -68,8 +68,8 @@
<el-form-item label="计费时间" prop="bizDate">
<el-date-picker
v-model="form.bizDate"
type="date"
value-format="YYYY-MM-DD"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="默认今天,用于筛选已生效的产品明细"
style="width: 100%"
/>
@@ -288,7 +288,7 @@ const historyColumns = [
{ label: '产品', prop: 'product', minWidth: 160, slot: 'product' },
{ label: '国家', prop: 'country', minWidth: 130, slot: 'country' },
{ 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: 'priceSetName', minWidth: 120, showOverflowTooltip: true },
{ label: '产品价', prop: 'productAmount', align: 'right', width: 110 },
@@ -527,10 +527,15 @@ function formatRate(val) {
function formatDate(val) {
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)
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) {