增加订单失败状态

This commit is contained in:
wuweihua
2026-10-14 16:50:11 +08:00
parent c1c5c6579c
commit c0c9c798b3
16 changed files with 455 additions and 28 deletions
@@ -206,3 +206,6 @@ price.set.detail.duplicate.fixed=产品{0} 国家{1} 重量段{2} {3}(共{4}
price.set.detail.duplicate.discount=产品{0}(共{1}条) price.set.detail.duplicate.discount=产品{0}(共{1}条)
price.set.detail.duplicate.hasZone=有分区({0}) price.set.detail.duplicate.hasZone=有分区({0})
price.set.detail.duplicate.noZone=无分区 price.set.detail.duplicate.noZone=无分区
price.bill.billingFail.product=计费失败:未匹配产品
price.bill.billingFail.country=计费失败:未匹配国家
price.bill.billingFail.weight=计费失败:未匹配重量段
@@ -116,6 +116,8 @@ public class PrcOrderBill extends BaseEntity
private String creditArrearsStatus; private String creditArrearsStatus;
/** NONE / REFUNDED */ /** NONE / REFUNDED */
private String refundStatus; private String refundStatus;
/** 计费失败状态:0成功/1未匹配产品/2未匹配国家/3未匹配重量段;软失败时金额为0且不落费用明细 */
private Integer billingFailStatus;
private String roundMode; private String roundMode;
private List<PrcIncomeBillFee> incomeFees; private List<PrcIncomeBillFee> incomeFees;
@@ -4,10 +4,8 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.bms.price.domain.PrcOrderBill; import com.bms.price.domain.PrcOrderBill;
import lombok.Data;
/** Preview matched bills before async batch add fee. */ /** Preview matched bills before async batch add fee. */
@Data
public class BatchAddIncomeBillFeePreview implements Serializable public class BatchAddIncomeBillFeePreview implements Serializable
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -15,4 +13,24 @@ public class BatchAddIncomeBillFeePreview implements Serializable
private int matchCount; private int matchCount;
/** First N rows for UI preview. */ /** First N rows for UI preview. */
private List<PrcOrderBill> samples = new ArrayList<PrcOrderBill>(); private List<PrcOrderBill> samples = new ArrayList<PrcOrderBill>();
public int getMatchCount()
{
return matchCount;
}
public void setMatchCount(int matchCount)
{
this.matchCount = matchCount;
}
public List<PrcOrderBill> getSamples()
{
return samples;
}
public void setSamples(List<PrcOrderBill> samples)
{
this.samples = samples;
}
} }
@@ -103,6 +103,8 @@ public class TmsPriceQuoteResult implements Serializable
private String creditArrearsStatus; private String creditArrearsStatus;
/** NONE / REFUNDED */ /** NONE / REFUNDED */
private String refundStatus; private String refundStatus;
/** 计费失败状态:0成功/1未匹配产品/2未匹配国家/3未匹配重量段(仅上游同步计费软失败) */
private Integer billingFailStatus;
private Integer pieces; private Integer pieces;
private BigDecimal actualWeight; private BigDecimal actualWeight;
/** Receive weight from upstream TAB_ORDER.WEIGHT */ /** Receive weight from upstream TAB_ORDER.WEIGHT */
@@ -0,0 +1,46 @@
package com.bms.price.enums;
/**
* prc_order_bill.billing_fail_status 计费失败状态。
* 0=成功;1=未匹配产品;2=未匹配国家;3=未匹配重量段。
*/
public enum BillBillingFailStatus
{
SUCCESS(0),
PRODUCT(1),
COUNTRY(2),
WEIGHT(3);
private final int code;
BillBillingFailStatus(int code)
{
this.code = code;
}
public int getCode()
{
return code;
}
public static BillBillingFailStatus of(Integer code)
{
if (code == null)
{
return SUCCESS;
}
for (BillBillingFailStatus status : values())
{
if (status.code == code.intValue())
{
return status;
}
}
return null;
}
public static boolean isFail(Integer code)
{
return code != null && code.intValue() > SUCCESS.code;
}
}
@@ -104,4 +104,20 @@ public interface BmsProductCountryMapper
@Param("countryCode") String countryCode, @Param("countryCode") String countryCode,
@Param("countryName") String countryName, @Param("countryName") String countryName,
@Param("bizDate") Date bizDate); @Param("bizDate") Date bizDate);
/** Count catalog rows by product (+ bizDate window), ignore country/zone/weight. */
public int countByProduct(
@Param("productCode") String productCode,
@Param("bizDate") Date bizDate);
/**
* Count catalog rows by product + country + zone (+ bizDate), ignore weight.
* Used to diagnose country miss vs weight miss.
*/
public int countByProductCountryZone(
@Param("productCode") String productCode,
@Param("countryCode") String countryCode,
@Param("countryName") String countryName,
@Param("zoneCode") String zoneCode,
@Param("bizDate") Date bizDate);
} }
@@ -82,6 +82,22 @@ public interface PrcPriceSetDetailMapper
@Param("countryCode") String countryCode, @Param("countryCode") String countryCode,
@Param("countryName") String countryName); @Param("countryName") String countryName);
/** Count FIXED details by version + product (ignore country/zone/weight). */
public int countByPsvProduct(
@Param("psvId") Long psvId,
@Param("productCode") String productCode);
/**
* Count FIXED details by version + product + country + zone (ignore weight).
* Used to diagnose country miss vs weight miss.
*/
public int countByPsvProductCountryZone(
@Param("psvId") Long psvId,
@Param("productCode") String productCode,
@Param("countryCode") String countryCode,
@Param("countryName") String countryName,
@Param("zoneCode") String zoneCode);
/** DISCOUNT by product; optional psvId scopes to version */ /** DISCOUNT by product; optional psvId scopes to version */
public PrcPriceSetDetail selectDiscountMatch( public PrcPriceSetDetail selectDiscountMatch(
@Param("priceSetId") Long priceSetId, @Param("priceSetId") Long priceSetId,
@@ -60,6 +60,7 @@ import com.bms.price.domain.vo.TmsPriceQuoteResult;
import com.bms.price.enums.BillCreditArrearsStatus; import com.bms.price.enums.BillCreditArrearsStatus;
import com.bms.price.enums.BillCustomerActiveStatus; import com.bms.price.enums.BillCustomerActiveStatus;
import com.bms.price.enums.BillRefundStatus; import com.bms.price.enums.BillRefundStatus;
import com.bms.price.enums.BillBillingFailStatus;
import com.bms.price.enums.TmsOrderBillMatchType; import com.bms.price.enums.TmsOrderBillMatchType;
import com.bms.price.mapper.PrcIncomeBillFeeMapper; import com.bms.price.mapper.PrcIncomeBillFeeMapper;
import com.bms.price.mapper.PrcIncomeBillMapper; import com.bms.price.mapper.PrcIncomeBillMapper;
@@ -1129,15 +1130,51 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
bill.setWeightLabel(result.getWeightLabel()); bill.setWeightLabel(result.getWeightLabel());
bill.setEffectiveDate(result.getEffectiveDate()); bill.setEffectiveDate(result.getEffectiveDate());
bill.setChargeWeight(resolvePersistChargeWeight(weight, result.getChargeWeight())); bill.setChargeWeight(resolvePersistChargeWeight(weight, result.getChargeWeight()));
bill.setFreightRate(result.getFreightRate()); Integer billingFailStatus = result.getBillingFailStatus();
bill.setBaseFreightRate(result.getBaseFreightRate()); if (billingFailStatus == null)
bill.setDiscountPercent(result.getDiscountPercent()); {
bill.setRegistrationFee(result.getRegistrationFee()); billingFailStatus = Integer.valueOf(BillBillingFailStatus.SUCCESS.getCode());
bill.setFreightAmount(result.getFreightAmount()); }
bill.setSurchargeAmount(result.getSurchargeAmount()); bill.setBillingFailStatus(billingFailStatus);
bill.setIncomeTotal(result.getPayableTotal()); boolean billingFailed = BillBillingFailStatus.isFail(billingFailStatus);
// 应付总额与产品目录价解耦:试算未产出 pay_total 时留空,不再用 product_amount 兜底 if (billingFailed)
bill.setPayTotal(result.getPayTotal()); {
// Soft-fail: persist order snapshot with zero amounts; skip fee detail rows.
bill.setFreightRate(BigDecimal.ZERO);
bill.setBaseFreightRate(BigDecimal.ZERO);
bill.setDiscountPercent(null);
bill.setRegistrationFee(BigDecimal.ZERO);
bill.setFreightAmount(BigDecimal.ZERO);
bill.setSurchargeAmount(BigDecimal.ZERO);
bill.setIncomeTotal(BigDecimal.ZERO);
bill.setPayTotal(BigDecimal.ZERO);
result.setFreightRate(BigDecimal.ZERO);
result.setBaseFreightRate(BigDecimal.ZERO);
result.setDiscountPercent(null);
result.setRegistrationFee(BigDecimal.ZERO);
result.setFreightAmount(BigDecimal.ZERO);
result.setSurchargeAmount(BigDecimal.ZERO);
result.setProductAmount(BigDecimal.ZERO);
result.setRedlineAmount(BigDecimal.ZERO);
result.setCostAmount(BigDecimal.ZERO);
result.setIncomeTotal(BigDecimal.ZERO);
result.setPayTotal(BigDecimal.ZERO);
result.setPayableTotal(BigDecimal.ZERO);
result.setFeeDetails(new ArrayList<TmsPriceQuoteFeeLine>());
result.setPayFeeDetails(new ArrayList<TmsPriceQuoteFeeLine>());
}
else
{
bill.setFreightRate(result.getFreightRate());
bill.setBaseFreightRate(result.getBaseFreightRate());
bill.setDiscountPercent(result.getDiscountPercent());
bill.setRegistrationFee(result.getRegistrationFee());
bill.setFreightAmount(result.getFreightAmount());
bill.setSurchargeAmount(result.getSurchargeAmount());
bill.setIncomeTotal(result.getPayableTotal());
// Payable total is decoupled from product catalog; leave null when quote has no pay_total.
bill.setPayTotal(result.getPayTotal());
}
bill.setCurrency(result.getCurrency()); bill.setCurrency(result.getCurrency());
bill.setRoundMode(result.getRoundMode()); bill.setRoundMode(result.getRoundMode());
// Credit soft-fail before persist so insert/update stores arrears flag in one write. // Credit soft-fail before persist so insert/update stores arrears flag in one write.
@@ -1155,6 +1192,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
result.setCustomerActiveStatus(customerActiveStatus); result.setCustomerActiveStatus(customerActiveStatus);
result.setCreditArrearsStatus(creditArrearsStatus); result.setCreditArrearsStatus(creditArrearsStatus);
result.setRefundStatus(bill.getRefundStatus()); result.setRefundStatus(bill.getRefundStatus());
result.setBillingFailStatus(billingFailStatus);
result.setIncomeTotal(bill.getIncomeTotal()); result.setIncomeTotal(bill.getIncomeTotal());
result.setPayTotal(bill.getPayTotal()); result.setPayTotal(bill.getPayTotal());
result.setPayableTotal(bill.getIncomeTotal()); result.setPayableTotal(bill.getIncomeTotal());
@@ -1169,23 +1207,34 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
else else
{ {
orderBillMapper.updatePrcOrderBill(bill); orderBillMapper.updatePrcOrderBill(bill);
// Keep manually added fees (manual_flag=1) across rebill if (billingFailed)
incomeBillFeeMapper.deleteAutoFeesByBillId(bill.getBillId()); {
// Soft-fail rebill: clear all fee details (no fee rows for failed billing).
incomeBillFeeMapper.deleteByBillId(bill.getBillId());
}
else
{
// Keep manually added fees (manual_flag=1) across rebill
incomeBillFeeMapper.deleteAutoFeesByBillId(bill.getBillId());
}
// Payable not in scope yet: skip prc_pay_bill_fee cleanup/write. // Payable not in scope yet: skip prc_pay_bill_fee cleanup/write.
// payBillFeeMapper.deleteByBillId(bill.getBillId()); // payBillFeeMapper.deleteByBillId(bill.getBillId());
} }
PrcIncomeBill incomeBill = persistIncomeBill(bill, result); PrcIncomeBill incomeBill = persistIncomeBill(bill, result);
List<PrcIncomeBillFee> fees = toFeeRows(bill.getBillId(), if (!billingFailed)
incomeBill == null ? null : incomeBill.getIncomeBillId(), result);
if (existed != null)
{ {
fees = filterAutoFeesAgainstManual(bill.getBillId(), fees); List<PrcIncomeBillFee> fees = toFeeRows(bill.getBillId(),
incomeBill == null ? null : incomeBill.getIncomeBillId(), result);
if (existed != null)
{
fees = filterAutoFeesAgainstManual(bill.getBillId(), fees);
}
if (!fees.isEmpty())
{
incomeBillFeeMapper.insertBatch(fees);
}
} }
if (!fees.isEmpty()) if (existed != null && !billingFailed)
{
incomeBillFeeMapper.insertBatch(fees);
}
if (existed != null)
{ {
// Quote totals were written above; recalc so retained manual fees join income/credit // Quote totals were written above; recalc so retained manual fees join income/credit
TmsPriceQuoteResult refreshed = refreshBillIncomeTotals(bill); TmsPriceQuoteResult refreshed = refreshBillIncomeTotals(bill);
@@ -1199,7 +1248,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
result.setFeeDetails(refreshed.getFeeDetails()); result.setFeeDetails(refreshed.getFeeDetails());
} }
} }
else else if (existed == null)
{ {
bill.setRefundStatus(BillRefundStatus.NONE.getCode()); bill.setRefundStatus(BillRefundStatus.NONE.getCode());
result.setRefundStatus(bill.getRefundStatus()); result.setRefundStatus(bill.getRefundStatus());
@@ -1619,6 +1668,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
r.setCustomerActiveStatus(bill.getCustomerActiveStatus()); r.setCustomerActiveStatus(bill.getCustomerActiveStatus());
r.setCreditArrearsStatus(bill.getCreditArrearsStatus()); r.setCreditArrearsStatus(bill.getCreditArrearsStatus());
r.setRefundStatus(bill.getRefundStatus()); r.setRefundStatus(bill.getRefundStatus());
r.setBillingFailStatus(bill.getBillingFailStatus() == null
? Integer.valueOf(BillBillingFailStatus.SUCCESS.getCode()) : bill.getBillingFailStatus());
r.setFeeDetails(toIncomeFeeLines(incomeFees)); r.setFeeDetails(toIncomeFeeLines(incomeFees));
r.setPayFeeDetails(toPayFeeLines(payFees)); r.setPayFeeDetails(toPayFeeLines(payFees));
PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId()); PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId());
@@ -42,6 +42,7 @@ import com.bms.price.domain.vo.TmsPriceBillRequest;
import com.bms.price.domain.vo.TmsPriceQuoteFeeLine; import com.bms.price.domain.vo.TmsPriceQuoteFeeLine;
import com.bms.price.domain.vo.TmsPriceQuoteRequest; import com.bms.price.domain.vo.TmsPriceQuoteRequest;
import com.bms.price.domain.vo.TmsPriceQuoteResult; import com.bms.price.domain.vo.TmsPriceQuoteResult;
import com.bms.price.enums.BillBillingFailStatus;
import com.bms.price.enums.BillCustomerActiveStatus; import com.bms.price.enums.BillCustomerActiveStatus;
import com.bms.price.mapper.BmsProductCountryMapper; import com.bms.price.mapper.BmsProductCountryMapper;
import com.bms.price.mapper.BmsProductMapper; import com.bms.price.mapper.BmsProductMapper;
@@ -64,7 +65,8 @@ import com.bms.system.service.IBmsProductCodeMappingService;
* Shared quote/bill calc: customer bind + package version by bizDate (version effective_time). * 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. * Bind pr_begindate/pr_enddate is not used for billing time gate.
* Sales price comes from package snapshot only (FIXED detail / DISCOUNT on catalog base). * 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). * No bind / no active version: quote and bill both fail hard (no PRODUCT_DEFAULT).
* Bill sync soft-fails for product/country/weight miss (billing_fail_status 1/2/3, zero amounts).
* product_country is only for product_amount/cost and DISCOUNT freight base. * product_country is only for product_amount/cost and DISCOUNT freight base.
* *
* @author bms * @author bms
@@ -207,7 +209,11 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
String customerActiveStatus = softResolveCustomer(request); String customerActiveStatus = softResolveCustomer(request);
Date bizDate = resolveBizDate(request.getBizDate()); Date bizDate = resolveBizDate(request.getBizDate());
TmsPriceQuoteResult result = doQuote(request, bizDate); TmsPriceQuoteResult result = doQuote(request, bizDate);
applySurchargesAndFees(result); if (!BillBillingFailStatus.isFail(result.getBillingFailStatus()))
{
applySurchargesAndFees(result);
result.setBillingFailStatus(BillBillingFailStatus.SUCCESS.getCode());
}
result.setCustomerActiveStatus(customerActiveStatus); result.setCustomerActiveStatus(customerActiveStatus);
return result; return result;
} }
@@ -405,6 +411,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
boolean productExcludedBySetProducts = false; boolean productExcludedBySetProducts = false;
boolean attemptedPriceMatch = false; boolean attemptedPriceMatch = false;
boolean anyZoneRequired = false; boolean anyZoneRequired = false;
// Highest specificity among soft-failable misses: WEIGHT(3) > COUNTRY(2) > PRODUCT(1).
int missCode = 0;
// Match version by exact bill datetime (effective_time / expire_time), not end-of-day. // Match version by exact bill datetime (effective_time / expire_time), not end-of-day.
Date asOf = bizDate != null ? bizDate : new Date(); Date asOf = bizDate != null ? bizDate : new Date();
for (PrcCustomerPriceSet bind : binds) for (PrcCustomerPriceSet bind : binds)
@@ -450,6 +458,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
if (anyProducts != null && !anyProducts.isEmpty()) if (anyProducts != null && !anyProducts.isEmpty())
{ {
productExcludedBySetProducts = true; productExcludedBySetProducts = true;
missCode = Math.max(missCode, BillBillingFailStatus.PRODUCT.getCode());
continue; continue;
} }
} }
@@ -482,11 +491,13 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
stampRequestMeta(request, hit); stampRequestMeta(request, hit);
return hit; return hit;
} }
missCode = Math.max(missCode, diagnoseFixedMiss(version.getPsvId(), productCode, cc, cn, zone.zoneCode));
} }
else if (TYPE_DISCOUNT.equals(setType)) else if (TYPE_DISCOUNT.equals(setType))
{ {
if (catalog == null) if (catalog == null)
{ {
missCode = Math.max(missCode, diagnoseCatalogMiss(productCode, cc, cn, zone.zoneCode, bizDate));
continue; continue;
} }
TmsPriceQuoteResult hit = tryDiscount(bind, set, version, catalog, productCode, productName, weight, bizDate); TmsPriceQuoteResult hit = tryDiscount(bind, set, version, catalog, productCode, productName, weight, bizDate);
@@ -495,6 +506,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
stampRequestMeta(request, hit); stampRequestMeta(request, hit);
return hit; return hit;
} }
// Discount detail missing for product (or invalid discount) after catalog hit.
missCode = Math.max(missCode, BillBillingFailStatus.PRODUCT.getCode());
} }
} }
@@ -506,6 +519,15 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
throw new ServiceException(MessageUtils.message(msgKey)); throw new ServiceException(MessageUtils.message(msgKey));
} }
if (!attemptedPriceMatch && productExcludedBySetProducts) if (!attemptedPriceMatch && productExcludedBySetProducts)
{
missCode = Math.max(missCode, BillBillingFailStatus.PRODUCT.getCode());
}
if (request instanceof TmsPriceBillRequest && BillBillingFailStatus.isFail(Integer.valueOf(missCode)))
{
return buildBillingMissResult(request, productCode, productName, countryCode, countryName,
weight, bizDate, missCode);
}
if (!attemptedPriceMatch && productExcludedBySetProducts)
{ {
String msgKey = request instanceof TmsPriceBillRequest String msgKey = request instanceof TmsPriceBillRequest
? "price.bill.productNotInPackage" ? "price.bill.productNotInPackage"
@@ -528,6 +550,118 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
throw new ServiceException(MessageUtils.message(missKey)); throw new ServiceException(MessageUtils.message(missKey));
} }
/**
* Diagnose FIXED miss: product rows absent -> 1; country/zone absent -> 2; else weight -> 3.
*/
private int diagnoseFixedMiss(Long psvId, String productCode, String countryCode,
String countryName, String zoneCode)
{
if (psvId == null || StringUtils.isEmpty(productCode))
{
return BillBillingFailStatus.PRODUCT.getCode();
}
int byProduct = detailMapper.countByPsvProduct(psvId, productCode);
if (byProduct <= 0)
{
return BillBillingFailStatus.PRODUCT.getCode();
}
int byCountry = detailMapper.countByPsvProductCountryZone(
psvId, productCode, countryCode, countryName, zoneCode);
if (byCountry <= 0)
{
return BillBillingFailStatus.COUNTRY.getCode();
}
return BillBillingFailStatus.WEIGHT.getCode();
}
/**
* Diagnose catalog miss for DISCOUNT base price.
*/
private int diagnoseCatalogMiss(String productCode, String countryCode, String countryName,
String zoneCode, Date bizDate)
{
if (StringUtils.isEmpty(productCode))
{
return BillBillingFailStatus.PRODUCT.getCode();
}
int byProduct = productCountryMapper.countByProduct(productCode, bizDate);
if (byProduct <= 0)
{
return BillBillingFailStatus.PRODUCT.getCode();
}
int byCountry = productCountryMapper.countByProductCountryZone(
productCode, countryCode, countryName, zoneCode, bizDate);
if (byCountry <= 0)
{
return BillBillingFailStatus.COUNTRY.getCode();
}
return BillBillingFailStatus.WEIGHT.getCode();
}
/**
* Soft-fail bill result: keep order snapshot fields, all amounts zero, set billingFailStatus.
*/
private TmsPriceQuoteResult buildBillingMissResult(TmsPriceQuoteRequest request, String productCode,
String productName, String countryCode, String countryName, BigDecimal weight, Date bizDate,
int missCode)
{
TmsPriceQuoteResult r = new TmsPriceQuoteResult();
r.setBillingFailStatus(Integer.valueOf(missCode));
r.setMatchSource("BILLING_FAIL");
r.setMatchMessage(billingFailMessage(missCode));
r.setProductCode(productCode);
r.setProductName(productName);
r.setCountryCode(countryCode);
r.setCountryName(countryName);
r.setWeight(weight);
r.setChargeWeight(weight);
r.setBizDate(bizDate);
r.setFreightRate(BigDecimal.ZERO);
r.setBaseFreightRate(BigDecimal.ZERO);
r.setDiscountPercent(null);
r.setRegistrationFee(BigDecimal.ZERO);
r.setFreightAmount(BigDecimal.ZERO);
r.setSurchargeAmount(BigDecimal.ZERO);
r.setProductAmount(BigDecimal.ZERO);
r.setRedlineAmount(BigDecimal.ZERO);
r.setCostAmount(BigDecimal.ZERO);
r.setIncomeTotal(BigDecimal.ZERO);
r.setPayTotal(BigDecimal.ZERO);
r.setPayableTotal(BigDecimal.ZERO);
r.setCurrency("CNY");
r.setRoundMode("HALF_UP");
r.setFromCache(Boolean.FALSE);
r.setFeeDetails(Collections.<TmsPriceQuoteFeeLine>emptyList());
r.setPayFeeDetails(Collections.<TmsPriceQuoteFeeLine>emptyList());
stampRequestMeta(request, r);
if (request instanceof TmsPriceBillRequest)
{
TmsPriceBillRequest billReq = (TmsPriceBillRequest) request;
if (StringUtils.isEmpty(r.getCustomerName()) && StringUtils.isNotEmpty(billReq.getCustomerName()))
{
r.setCustomerName(billReq.getCustomerName());
}
}
return r;
}
private String billingFailMessage(int missCode)
{
if (missCode == BillBillingFailStatus.PRODUCT.getCode())
{
return MessageUtils.message("price.bill.billingFail.product");
}
if (missCode == BillBillingFailStatus.COUNTRY.getCode())
{
return MessageUtils.message("price.bill.billingFail.country");
}
if (missCode == BillBillingFailStatus.WEIGHT.getCode())
{
return MessageUtils.message("price.bill.billingFail.weight");
}
return MessageUtils.message("price.bill.packagePriceNotHit");
}
/** /**
* Soft lookup of package product row; null when version has no such product. * Soft lookup of package product row; null when version has no such product.
*/ */
@@ -317,6 +317,53 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if> </if>
</select> </select>
<select id="countByProduct" resultType="int">
select count(1)
from product_country
where product_code = #{productCode}
and ifnull(is_del, '0') = '0'
<if test="bizDate != null">
and (effective_date is null or effective_date &lt;= #{bizDate})
and (expire_date is null or expire_date &gt; #{bizDate})
</if>
<if test="bizDate == null">
and expire_date is null
</if>
</select>
<select id="countByProductCountryZone" resultType="int">
select count(1)
from product_country
where product_code = #{productCode}
and ifnull(is_del, '0') = '0'
<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>
<choose>
<when test="zoneCode != null and zoneCode != ''">
and upper(ifnull(zone_code,'')) = upper(#{zoneCode})
</when>
<otherwise>
and (zone_code is null or zone_code = '')
</otherwise>
</choose>
<if test="bizDate != null">
and (effective_date is null or effective_date &lt;= #{bizDate})
and (expire_date is null or expire_date &gt; #{bizDate})
</if>
<if test="bizDate == null">
and expire_date is null
</if>
</select>
<select id="selectOpenByProductCode" resultMap="BmsProductCountryResult"> <select id="selectOpenByProductCode" resultMap="BmsProductCountryResult">
<include refid="selectVo"/> <include refid="selectVo"/>
where c.product_code = #{productCode} where c.product_code = #{productCode}
@@ -71,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="customerActiveStatus" column="customer_active_status" /> <result property="customerActiveStatus" column="customer_active_status" />
<result property="creditArrearsStatus" column="credit_arrears_status" /> <result property="creditArrearsStatus" column="credit_arrears_status" />
<result property="refundStatus" column="refund_status" /> <result property="refundStatus" column="refund_status" />
<result property="billingFailStatus" column="billing_fail_status" />
<result property="roundMode" column="round_mode" /> <result property="roundMode" column="round_mode" />
<result property="createBy" column="created_by" /> <result property="createBy" column="created_by" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
@@ -94,7 +95,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
weight_min, weight_max, weight_label, effective_date, charge_weight, weight_min, weight_max, weight_label, effective_date, charge_weight,
freight_rate, base_freight_rate, discount_percent, registration_fee, freight_rate, base_freight_rate, discount_percent, registration_fee,
freight_amount, surcharge_amount, income_total, pay_total, currency, ar_bill_status, freight_amount, surcharge_amount, income_total, pay_total, currency, ar_bill_status,
customer_active_status, credit_arrears_status, refund_status, round_mode, customer_active_status, credit_arrears_status, refund_status, billing_fail_status, round_mode,
created_by, create_time, updated_by, update_time created_by, create_time, updated_by, update_time
from prc_order_bill p from prc_order_bill p
</sql> </sql>
@@ -530,7 +531,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
weight_min, weight_max, weight_label, effective_date, charge_weight, weight_min, weight_max, weight_label, effective_date, charge_weight,
freight_rate, base_freight_rate, discount_percent, registration_fee, freight_rate, base_freight_rate, discount_percent, registration_fee,
freight_amount, surcharge_amount, income_total, pay_total, currency, ar_bill_status, freight_amount, surcharge_amount, income_total, pay_total, currency, ar_bill_status,
customer_active_status, credit_arrears_status, refund_status, round_mode, customer_active_status, credit_arrears_status, refund_status, billing_fail_status, round_mode,
create_time create_time
) values ( ) values (
#{billId}, #{billKey}, #{orderNo}, #{trackingNo}, #{transferNo}, #{billId}, #{billKey}, #{orderNo}, #{trackingNo}, #{transferNo},
@@ -544,7 +545,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight}, #{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight},
#{freightRate}, #{baseFreightRate}, #{discountPercent}, #{registrationFee}, #{freightRate}, #{baseFreightRate}, #{discountPercent}, #{registrationFee},
#{freightAmount}, #{surchargeAmount}, #{incomeTotal}, #{payTotal}, #{currency}, #{arBillStatus}, #{freightAmount}, #{surchargeAmount}, #{incomeTotal}, #{payTotal}, #{currency}, #{arBillStatus},
#{customerActiveStatus}, #{creditArrearsStatus}, #{refundStatus}, #{roundMode}, #{customerActiveStatus}, #{creditArrearsStatus}, #{refundStatus}, #{billingFailStatus}, #{roundMode},
sysdate() sysdate()
) )
</insert> </insert>
@@ -614,6 +615,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
customer_active_status = #{customerActiveStatus}, customer_active_status = #{customerActiveStatus},
credit_arrears_status = #{creditArrearsStatus}, credit_arrears_status = #{creditArrearsStatus},
refund_status = #{refundStatus}, refund_status = #{refundStatus},
billing_fail_status = #{billingFailStatus},
round_mode = #{roundMode}, round_mode = #{roundMode},
update_time = sysdate() update_time = sysdate()
where bill_id = #{billId} where bill_id = #{billId}
@@ -462,6 +462,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</choose> </choose>
</select> </select>
<select id="countByPsvProduct" resultType="int">
select count(1)
from prc_price_set_detail
where psv_id = #{psvId}
and product_code = #{productCode}
</select>
<select id="countByPsvProductCountryZone" resultType="int">
select count(1)
from prc_price_set_detail
where psv_id = #{psvId}
and product_code = #{productCode}
<choose>
<when test="countryCode != null and countryCode != ''">
and upper(ifnull(country_code,'')) = upper(#{countryCode})
</when>
<when test="countryName != null and countryName != ''">
and country_name = #{countryName}
</when>
<otherwise>
and 1 = 0
</otherwise>
</choose>
<choose>
<when test="zoneCode != null and zoneCode != ''">
and upper(ifnull(zone_code,'')) = upper(#{zoneCode})
</when>
<otherwise>
and (zone_code is null or zone_code = '')
</otherwise>
</choose>
</select>
<select id="selectDiscountMatch" resultMap="PrcPriceSetDetailResult"> <select id="selectDiscountMatch" resultMap="PrcPriceSetDetailResult">
<include refid="selectPrcPriceSetDetailVo"/> <include refid="selectPrcPriceSetDetailVo"/>
where price_set_id = #{priceSetId} where price_set_id = #{priceSetId}
+2
View File
@@ -1052,6 +1052,7 @@ CREATE TABLE `prc_order_bill` (
`customer_active_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '客户生效状态:ACTIVE正常 MISSING不存在 DISABLED停用', `customer_active_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '客户生效状态:ACTIVE正常 MISSING不存在 DISABLED停用',
`credit_arrears_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '额度欠费状态:NORMAL正常 ARREARS欠费', `credit_arrears_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '额度欠费状态:NORMAL正常 ARREARS欠费',
`refund_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NONE' COMMENT '退费状态:NONE未退费/REFUNDED已退费', `refund_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NONE' COMMENT '退费状态:NONE未退费/REFUNDED已退费',
`billing_fail_status` tinyint NOT NULL DEFAULT '0' COMMENT '计费失败状态:0成功/1未匹配产品/2未匹配国家/3未匹配重量段',
`round_mode` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'HALF_UP' COMMENT '取值方式', `round_mode` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'HALF_UP' COMMENT '取值方式',
`created_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建人', `created_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建人',
`create_time` datetime DEFAULT NULL, `create_time` datetime DEFAULT NULL,
@@ -1068,6 +1069,7 @@ CREATE TABLE `prc_order_bill` (
KEY `idx_order_bill_customer_active` (`customer_active_status`), KEY `idx_order_bill_customer_active` (`customer_active_status`),
KEY `idx_order_bill_credit_arrears` (`credit_arrears_status`), KEY `idx_order_bill_credit_arrears` (`credit_arrears_status`),
KEY `idx_order_bill_refund_status` (`refund_status`), KEY `idx_order_bill_refund_status` (`refund_status`),
KEY `idx_order_bill_billing_fail_status` (`billing_fail_status`),
KEY `idx_order_bill_psv` (`psv_id`), KEY `idx_order_bill_psv` (`psv_id`),
KEY `idx_order_bill_psd` (`psd_id`), KEY `idx_order_bill_psd` (`psd_id`),
KEY `idx_ar_order_bill_status_scope` (`customer_id`,`currency`,`ar_bill_status`,`biz_date`,`bill_id`) KEY `idx_ar_order_bill_status_scope` (`customer_id`,`currency`,`ar_bill_status`,`biz_date`,`bill_id`)
@@ -0,0 +1,16 @@
-- prc_order_bill.billing_fail_status: 0 success / 1 product / 2 country / 3 weight
SET NAMES utf8mb4;
SET @col := (
SELECT COLUMN_NAME FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'prc_order_bill'
AND COLUMN_NAME = 'billing_fail_status'
LIMIT 1
);
SET @sql := IF(
@col IS NULL,
'ALTER TABLE prc_order_bill ADD COLUMN billing_fail_status tinyint NOT NULL DEFAULT 0 COMMENT ''计费失败状态:0成功/1未匹配产品/2未匹配国家/3未匹配重量段'' AFTER refund_status, ADD KEY idx_order_bill_billing_fail_status (billing_fail_status)',
'SELECT 1'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -58,6 +58,9 @@
<template #col-matchSource="{ row }"> <template #col-matchSource="{ row }">
<el-tag size="small" :type="sourceTagType(row.matchSource)">{{ sourceLabel(row.matchSource) }}</el-tag> <el-tag size="small" :type="sourceTagType(row.matchSource)">{{ sourceLabel(row.matchSource) }}</el-tag>
</template> </template>
<template #col-billingFailStatus="{ row }">
<el-tag size="small" :type="billingFailTagType(row.billingFailStatus)">{{ billingFailLabel(row.billingFailStatus) }}</el-tag>
</template>
<template #col-customerActiveStatus="{ row }"> <template #col-customerActiveStatus="{ row }">
<el-tag size="small" :type="customerActiveTagType(row.customerActiveStatus)">{{ customerActiveLabel(row.customerActiveStatus) }}</el-tag> <el-tag size="small" :type="customerActiveTagType(row.customerActiveStatus)">{{ customerActiveLabel(row.customerActiveStatus) }}</el-tag>
</template> </template>
@@ -243,6 +246,7 @@ const columns = [
{ label: '应收总额', prop: 'incomeTotal', align: 'right', width: 120, slot: 'incomeTotal' }, { label: '应收总额', prop: 'incomeTotal', align: 'right', width: 120, slot: 'incomeTotal' },
{ label: '应付总额', prop: 'payTotal', align: 'right', width: 120, slot: 'payTotal' }, { label: '应付总额', prop: 'payTotal', align: 'right', width: 120, slot: 'payTotal' },
{ label: '计价来源', prop: 'matchSource', align: 'center', width: 130, slot: 'matchSource' }, { label: '计价来源', prop: 'matchSource', align: 'center', width: 130, slot: 'matchSource' },
{ label: '计费状态', prop: 'billingFailStatus', align: 'center', width: 120, slot: 'billingFailStatus' },
{ label: '客户状态', prop: 'customerActiveStatus', align: 'center', width: 100, slot: 'customerActiveStatus' }, { label: '客户状态', prop: 'customerActiveStatus', align: 'center', width: 100, slot: 'customerActiveStatus' },
{ label: '额度欠费', prop: 'creditArrearsStatus', align: 'center', width: 100, slot: 'creditArrearsStatus' }, { label: '额度欠费', prop: 'creditArrearsStatus', align: 'center', width: 100, slot: 'creditArrearsStatus' },
// { label: '退费状态', prop: 'refundStatus', align: 'center', width: 100, slot: 'refundStatus' }, // { label: '退费状态', prop: 'refundStatus', align: 'center', width: 100, slot: 'refundStatus' },
@@ -523,11 +527,26 @@ function formatWeight(val) {
return num.toFixed(3) return num.toFixed(3)
} }
function billingFailLabel(status) {
const code = status == null ? 0 : Number(status)
if (code === 1) return '未匹配产品'
if (code === 2) return '未匹配国家'
if (code === 3) return '未匹配重量段'
return '计费成功'
}
function billingFailTagType(status) {
const code = status == null ? 0 : Number(status)
if (code === 1 || code === 2 || code === 3) return 'danger'
return 'success'
}
function sourceLabel(source) { function sourceLabel(source) {
const map = { const map = {
PACKAGE_FIXED: '客户固定价套餐', PACKAGE_FIXED: '客户固定价套餐',
PACKAGE_DISCOUNT: '客户折扣套餐', PACKAGE_DISCOUNT: '客户折扣套餐',
PRODUCT_DEFAULT: '产品默认价', PRODUCT_DEFAULT: '产品默认价',
BILLING_FAIL: '计费失败',
FIXED: '客户固定价套餐', FIXED: '客户固定价套餐',
DISCOUNT: '客户折扣套餐' DISCOUNT: '客户折扣套餐'
} }
@@ -538,6 +557,7 @@ function sourceTagType(source) {
if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success' if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success'
if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning' if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning'
if (source === 'PRODUCT_DEFAULT') return 'info' if (source === 'PRODUCT_DEFAULT') return 'info'
if (source === 'BILLING_FAIL') return 'danger'
return '' return ''
} }
@@ -73,6 +73,9 @@
<el-descriptions-item label="计价来源"> <el-descriptions-item label="计价来源">
<el-tag size="small" :type="sourceTagType(detail.matchSource)">{{ sourceLabel(detail.matchSource) }}</el-tag> <el-tag size="small" :type="sourceTagType(detail.matchSource)">{{ sourceLabel(detail.matchSource) }}</el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="计费状态">
<el-tag size="small" :type="billingFailTagType(detail.billingFailStatus)">{{ billingFailLabel(detail.billingFailStatus) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="套餐">{{ priceSetLabel(detail) }}</el-descriptions-item> <el-descriptions-item label="套餐">{{ priceSetLabel(detail) }}</el-descriptions-item>
<!-- <el-descriptions-item label="套餐版本">{{ detail.versionNo || (detail.psvId ? ('psv=' + detail.psvId) : '-') }}</el-descriptions-item> --> <!-- <el-descriptions-item label="套餐版本">{{ detail.versionNo || (detail.psvId ? ('psv=' + detail.psvId) : '-') }}</el-descriptions-item> -->
<!-- <el-descriptions-item label="套餐明细ID">{{ detail.psdId != null ? detail.psdId : '-' }}</el-descriptions-item> --> <!-- <el-descriptions-item label="套餐明细ID">{{ detail.psdId != null ? detail.psdId : '-' }}</el-descriptions-item> -->
@@ -332,11 +335,26 @@ function formatDateTime(val) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
} }
function billingFailLabel(status) {
const code = status == null ? 0 : Number(status)
if (code === 1) return '未匹配产品'
if (code === 2) return '未匹配国家'
if (code === 3) return '未匹配重量段'
return '计费成功'
}
function billingFailTagType(status) {
const code = status == null ? 0 : Number(status)
if (code === 1 || code === 2 || code === 3) return 'danger'
return 'success'
}
function sourceLabel(source) { function sourceLabel(source) {
const map = { const map = {
PACKAGE_FIXED: '客户固定价套餐', PACKAGE_FIXED: '客户固定价套餐',
PACKAGE_DISCOUNT: '客户折扣套餐', PACKAGE_DISCOUNT: '客户折扣套餐',
PRODUCT_DEFAULT: '产品默认价', PRODUCT_DEFAULT: '产品默认价',
BILLING_FAIL: '计费失败',
FIXED: '客户固定价套餐', FIXED: '客户固定价套餐',
DISCOUNT: '客户折扣套餐' DISCOUNT: '客户折扣套餐'
} }
@@ -379,6 +397,7 @@ function sourceTagType(source) {
if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success' if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success'
if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning' if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning'
if (source === 'PRODUCT_DEFAULT') return 'info' if (source === 'PRODUCT_DEFAULT') return 'info'
if (source === 'BILLING_FAIL') return 'danger'
return '' return ''
} }