增加失败码功能

This commit is contained in:
wuweihua
2026-09-28 11:07:09 +08:00
parent 0408b6c35e
commit cc802d1292
13 changed files with 247 additions and 31 deletions
@@ -86,6 +86,7 @@ public class BillingTaskMonitorController extends BaseController
{
PrcBillingTask task = billingTaskService.retryTask(taskId);
row.put("successStatus", task == null ? null : task.getSuccessStatus());
row.put("failCode", task == null ? null : task.getFailCode());
row.put("errorMessage", task == null ? null : task.getErrorMessage());
if (task != null && BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()))
{
@@ -144,6 +144,7 @@ price.quote.bizDateInvalid=业务日期格式必须为yyyy-MM-dd
price.quote.fixedPackageNoDetails=套餐固定价格没有明细,使用产品默认价格
price.quote.noCustomerPackageOverride=没有客户套餐覆盖,使用产品默认运费/挂号费
price.quote.noCustomerPackage=客户未绑定套餐,无法试算
price.quote.noActivePackageVersion=计费日无生效套餐版本,无法试算
price.quote.quoteIdRequired=报价ID为空
price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细
@@ -155,6 +156,7 @@ price.bill.customerRequired=订单同步缺少有效客户
price.bill.alreadyInArBill=订单{0}已生成应收账单,作废后方可重新计费
price.bill.bizDateRequired=业务日期为空
price.bill.noCustomerPackage=客户未绑定套餐,无法计费
price.bill.noActivePackageVersion=计费日无生效套餐版本,无法计费
price.bill.fee.scIdRequired=附加费不能为空
price.bill.fee.surchargeNotFound=附加费配置不存在或已停用
price.bill.fee.notFound=费用明细不存在
@@ -25,5 +25,7 @@ public class PrcBillingTask extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date billTime;
private String billId;
/** Stable fail code for search, e.g. NO_CUSTOMER_PACKAGE */
private String failCode;
private String errorMessage;
}
@@ -0,0 +1,143 @@
package com.bms.price.enums;
import com.bms.common.utils.StringUtils;
/**
* Stable fail codes for prc_billing_task.fail_code (search / ops).
* Resolved from exception message when task writeback is FAILED.
*/
public enum BillTaskFailCode
{
NO_CUSTOMER_PACKAGE("NO_CUSTOMER_PACKAGE"),
NO_ACTIVE_PACKAGE_VERSION("NO_ACTIVE_PACKAGE_VERSION"),
ALREADY_IN_AR_BILL("ALREADY_IN_AR_BILL"),
PRODUCT_ARCHIVE_MISSING("PRODUCT_ARCHIVE_MISSING"),
PRICE_NOT_HIT("PRICE_NOT_HIT"),
ZONE_PRICE_NOT_HIT("ZONE_PRICE_NOT_HIT"),
POSTCODE_REQUIRED("POSTCODE_REQUIRED"),
POSTCODE_ZONE_NOT_FOUND("POSTCODE_ZONE_NOT_FOUND"),
ORACLE_ORDER_NOT_FOUND("ORACLE_ORDER_NOT_FOUND"),
ORACLE_ORDERNO_EMPTY("ORACLE_ORDERNO_EMPTY"),
ORACLE_CUSTOMER_EMPTY("ORACLE_CUSTOMER_EMPTY"),
ORACLE_PRODUCT_EMPTY("ORACLE_PRODUCT_EMPTY"),
ORACLE_WEIGHT_INVALID("ORACLE_WEIGHT_INVALID"),
BILL_TIME_EMPTY("BILL_TIME_EMPTY"),
REFUND_ORDER_UNRESOLVED("REFUND_ORDER_UNRESOLVED"),
CUSTOMER_CODE_REQUIRED("CUSTOMER_CODE_REQUIRED"),
CUSTOMER_DISABLED("CUSTOMER_DISABLED"),
FREIGHT_MISSING("FREIGHT_MISSING"),
UNKNOWN("UNKNOWN");
private final String code;
BillTaskFailCode(String code)
{
this.code = code;
}
public String getCode()
{
return code;
}
/**
* Map free-text error message to a stable fail code for search.
*/
public static String resolveCode(String errorMessage)
{
if (StringUtils.isEmpty(errorMessage))
{
return null;
}
String msg = errorMessage.trim();
if (containsAny(msg, "\u672a\u7ed1\u5b9a\u5957\u9910", "noCustomerPackage", "NO_CUSTOMER_PACKAGE"))
{
return NO_CUSTOMER_PACKAGE.code;
}
if (containsAny(msg, "\u65e0\u751f\u6548\u5957\u9910\u7248\u672c", "noActivePackageVersion", "NO_ACTIVE_PACKAGE_VERSION"))
{
return NO_ACTIVE_PACKAGE_VERSION.code;
}
if (containsAny(msg, "\u5df2\u751f\u6210\u5e94\u6536\u8d26\u5355", "alreadyInArBill", "ALREADY_IN_AR_BILL"))
{
return ALREADY_IN_AR_BILL.code;
}
if (containsAny(msg, "\u672a\u627e\u5230\u6709\u6548\u4ea7\u54c1\u6863\u6848", "PRODUCT_ARCHIVE_MISSING"))
{
return PRODUCT_ARCHIVE_MISSING.code;
}
if (containsAny(msg, "\u8be5\u5206\u533a\u65e0\u5bf9\u5e94\u4ea7\u54c1\u91cd\u91cf\u6bb5", "zonePriceNotHit", "ZONE_PRICE_NOT_HIT"))
{
return ZONE_PRICE_NOT_HIT.code;
}
if (containsAny(msg, "\u6ca1\u6709\u4ea7\u54c1/\u56fd\u5bb6/\u91cd\u91cf\u6bb5", "priceNotHit", "PRICE_NOT_HIT"))
{
return PRICE_NOT_HIT.code;
}
if (containsAny(msg, "\u8bf7\u586b\u5199\u76ee\u7684\u90ae\u7f16", "postcodeRequired", "POSTCODE_REQUIRED"))
{
return POSTCODE_REQUIRED.code;
}
if (containsAny(msg, "\u90ae\u7f16\u672a\u5339\u914d\u5230\u5206\u533a", "postcodeZoneNotFound", "POSTCODE_ZONE_NOT_FOUND"))
{
return POSTCODE_ZONE_NOT_FOUND.code;
}
if (containsAny(msg, "Oracle main order not found", "ORACLE_ORDER_NOT_FOUND"))
{
return ORACLE_ORDER_NOT_FOUND.code;
}
if (containsAny(msg, "mapped orderNo empty", "ORACLE_ORDERNO_EMPTY"))
{
return ORACLE_ORDERNO_EMPTY.code;
}
if (containsAny(msg, "mapped customerCode empty", "ORACLE_CUSTOMER_EMPTY"))
{
return ORACLE_CUSTOMER_EMPTY.code;
}
if (containsAny(msg, "mapped productCode empty", "ORACLE_PRODUCT_EMPTY"))
{
return ORACLE_PRODUCT_EMPTY.code;
}
if (containsAny(msg, "mapped weight invalid", "ORACLE_WEIGHT_INVALID"))
{
return ORACLE_WEIGHT_INVALID.code;
}
if (containsAny(msg, "billTime empty", "billTime required", "BILL_TIME_EMPTY"))
{
return BILL_TIME_EMPTY.code;
}
if (containsAny(msg, "\u9000\u8d39\u8ba2\u5355\u53f7\u65e0\u6cd5\u89e3\u6790", "REFUND_ORDER_UNRESOLVED"))
{
return REFUND_ORDER_UNRESOLVED.code;
}
if (containsAny(msg, "\u5ba2\u6237\u4ee3\u7801\u4e0d\u80fd\u4e3a\u7a7a", "customerCodeRequired", "CUSTOMER_CODE_REQUIRED"))
{
return CUSTOMER_CODE_REQUIRED.code;
}
if (containsAny(msg, "\u5ba2\u6237\u5df2\u505c\u7528", "customerDisabled", "CUSTOMER_DISABLED"))
{
return CUSTOMER_DISABLED.code;
}
if (containsAny(msg, "\u8fd0\u8d39\u4e3a\u7a7a", "freightMissing", "FREIGHT_MISSING"))
{
return FREIGHT_MISSING.code;
}
return UNKNOWN.code;
}
private static boolean containsAny(String msg, String... markers)
{
if (msg == null || markers == null)
{
return false;
}
for (String marker : markers)
{
if (StringUtils.isNotEmpty(marker) && msg.indexOf(marker) >= 0)
{
return true;
}
}
return false;
}
}
@@ -40,13 +40,11 @@ public interface PrcCustomerPriceSetMapper
@Param("endDate") java.util.Date endDate);
/**
* Customer binds valid on bizDate; package not stopped. Version match is done in quote service.
* Customer package binds (package not stopped). Temporal match uses version effective_time in quote/bill.
*/
public java.util.List<PrcCustomerPriceSet> selectEffectiveByCustomer(
@Param("customerId") Long customerId,
@Param("bizDate") java.util.Date bizDate);
@Param("customerId") Long customerId);
public java.util.List<PrcCustomerPriceSet> selectEffectiveByCustomerCode(
@Param("customerCode") String customerCode,
@Param("bizDate") java.util.Date bizDate);
@Param("customerCode") String customerCode);
}
@@ -9,10 +9,10 @@ import java.util.List;
* TMS freight quote / bill calc service (shared by console trial and upstream bill).
*
* Match order:
* 1) Customer binds (not stopped), bind_priority ASC
* 2) Active package version at bill-time end-of-day (effective_time / expire_time)
* 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)
* 3) FIXED detail override, else DISCOUNT on that version
* 4) Else bms_product_country default
* 4) Else product default only when a version existed but detail missed
* Package hit uses version surcharge snapshot when present; else product library.
*
* @author bms
@@ -17,6 +17,7 @@ import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.vo.BillingTaskSubmitRequest;
import com.bms.price.domain.vo.TmsPriceBillRequest;
import com.bms.price.domain.vo.TmsPriceQuoteResult;
import com.bms.price.enums.BillTaskFailCode;
import com.bms.price.enums.BillTaskSuccessStatus;
import com.bms.price.enums.BillTaskType;
import com.bms.price.mapper.PrcBillingTaskMapper;
@@ -351,6 +352,7 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
reset.setTaskId(taskId);
reset.setSuccessStatus(BillTaskSuccessStatus.WAITING.getCode());
reset.setBillId(null);
reset.setFailCode(null);
reset.setErrorMessage(null);
billingTaskMapper.updateBillingResult(reset);
@@ -382,7 +384,7 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
return existed == null ? BillTaskType.PENDING : BillTaskType.REBILL;
}
/** Write back status/billId/error only; keep inbound transfer/tracking/order/billTime untouched. */
/** Write back status/billId/failCode/error only; keep inbound transfer/tracking/order/billTime untouched. */
private void writeback(PrcBillingTask task, BillTaskSuccessStatus status, String billId,
String errorMessage)
{
@@ -390,6 +392,14 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
upd.setTaskId(task.getTaskId());
upd.setSuccessStatus(status.getCode());
upd.setBillId(billId);
if (BillTaskSuccessStatus.FAILED == status)
{
upd.setFailCode(BillTaskFailCode.resolveCode(errorMessage));
}
else
{
upd.setFailCode(null);
}
upd.setErrorMessage(errorMessage);
billingTaskMapper.updateBillingResult(upd);
}
@@ -56,9 +56,9 @@ import com.bms.price.search.IBillSearchStore;
import com.bms.price.service.ITmsPriceQuoteService;
/**
* Shared quote/bill calc: bound set + active version by bizDate.
* No customer package bind: quote and bill both fail hard (no PRODUCT_DEFAULT).
* Bill path: no persist; billing task marked FAILED (abnormal order).
* 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.
* No bind / no active version at bill time: quote and bill both fail hard (no PRODUCT_DEFAULT).
*
* @author bms
*/
@@ -261,15 +261,14 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
List<PrcCustomerPriceSet> binds;
if (StringUtils.isNotEmpty(request.getCustomerCode()))
{
binds = customerPriceSetMapper.selectEffectiveByCustomerCode(
request.getCustomerCode(), bizDate);
binds = customerPriceSetMapper.selectEffectiveByCustomerCode(request.getCustomerCode());
}
else
{
binds = customerPriceSetMapper.selectEffectiveByCustomer(
request.getCustomerId(), bizDate);
binds = customerPriceSetMapper.selectEffectiveByCustomer(request.getCustomerId());
}
boolean anyActiveVersion = false;
if (binds != null)
{
Date asOf = endOfDay(bizDate);
@@ -280,12 +279,13 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
continue;
}
// Temporal gate: prc_price_set_version.effective_time / expire_time (not bind pr_begindate)
PrcPriceSetVersion version = versionMapper.selectActiveByPriceSetAt(set.getPriceSetId(), asOf);
if (version == null || version.getPsvId() == null)
{
// Bound package but no version effective at bill time -> try next bind / default
continue;
}
anyActiveVersion = true;
String setType = StringUtils.isEmpty(set.getSetType()) ? TYPE_FIXED : set.getSetType();
if (TYPE_FIXED.equals(setType))
{
@@ -308,13 +308,22 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
}
// No customer package bind: do not use PRODUCT_DEFAULT (quote and bill).
// Bill: no persist; billing task catches and writes success_status=FAILED.
if (binds == null || binds.isEmpty())
// No bind, or bind exists but no version effective at bill time.
if (binds == null || binds.isEmpty() || !anyActiveVersion)
{
String msgKey = request instanceof TmsPriceBillRequest
? "price.bill.noCustomerPackage"
: "price.quote.noCustomerPackage";
String msgKey;
if (binds == null || binds.isEmpty())
{
msgKey = request instanceof TmsPriceBillRequest
? "price.bill.noCustomerPackage"
: "price.quote.noCustomerPackage";
}
else
{
msgKey = request instanceof TmsPriceBillRequest
? "price.bill.noActivePackageVersion"
: "price.quote.noActivePackageVersion";
}
throw new ServiceException(MessageUtils.message(msgKey));
}
@@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="successStatus" column="success_status" />
<result property="billTime" column="bill_time" />
<result property="billId" column="bill_id" />
<result property="failCode" column="fail_code" />
<result property="errorMessage" column="error_message" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
@@ -22,7 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectPrcBillingTaskVo">
select task_id, transfer_no, tracking_no, order_no, bill_type, success_status,
bill_time, bill_id, error_message, create_by, create_time, update_by, update_time
bill_time, bill_id, fail_code, error_message, create_by, create_time, update_by, update_time
from prc_billing_task
</sql>
@@ -49,6 +50,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="successStatus != null and successStatus != ''">
AND success_status = #{successStatus}
</if>
<if test="failCode != null and failCode != ''">
AND fail_code = #{failCode}
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND create_time &gt;= #{params.beginTime}
</if>
@@ -62,10 +66,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertPrcBillingTask" parameterType="PrcBillingTask" useGeneratedKeys="true" keyProperty="taskId">
insert into prc_billing_task (
transfer_no, tracking_no, order_no, bill_type, success_status,
bill_time, bill_id, error_message, create_by, create_time
bill_time, bill_id, fail_code, error_message, create_by, create_time
) values (
#{transferNo}, #{trackingNo}, #{orderNo}, #{billType}, #{successStatus},
#{billTime}, #{billId}, #{errorMessage}, #{createBy}, sysdate()
#{billTime}, #{billId}, #{failCode}, #{errorMessage}, #{createBy}, sysdate()
)
</insert>
@@ -79,6 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="successStatus != null">success_status = #{successStatus},</if>
<if test="billTime != null">bill_time = #{billTime},</if>
<if test="billId != null">bill_id = #{billId},</if>
<if test="failCode != null">fail_code = #{failCode},</if>
<if test="errorMessage != null">error_message = #{errorMessage},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
update_time = sysdate()
@@ -91,6 +96,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update prc_billing_task
set success_status = #{successStatus},
bill_id = #{billId},
fail_code = #{failCode},
error_message = #{errorMessage},
update_time = sysdate()
where task_id = #{taskId}
@@ -98,8 +98,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
inner join prc_price_set s on s.price_set_id = c.price_set_id
where c.customer_id = #{customerId}
and ifnull(s.status, 'D') &lt;&gt; 'S'
and (c.pr_begindate is null or c.pr_begindate &lt;= #{bizDate})
and ifnull(c.pr_enddate, '9999-12-31') &gt;= #{bizDate}
order by c.bind_priority asc, c.pr_begindate desc, c.customer_priceset_id asc
</select>
@@ -111,8 +109,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
inner join prc_price_set s on s.price_set_id = c.price_set_id
where c.customer_code = #{customerCode}
and ifnull(s.status, 'D') &lt;&gt; 'S'
and (c.pr_begindate is null or c.pr_begindate &lt;= #{bizDate})
and ifnull(c.pr_enddate, '9999-12-31') &gt;= #{bizDate}
order by c.bind_priority asc, c.pr_begindate desc, c.customer_priceset_id asc
</select>
+3 -1
View File
@@ -814,6 +814,7 @@ CREATE TABLE `prc_billing_task` (
`success_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'WAITING' COMMENT '是否计费成功:WAITING待处理 SUCCESS成功 FAILED失败',
`bill_time` datetime DEFAULT NULL COMMENT '计费时间',
`bill_id` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '计费订单ID prc_order_bill.bill_id',
`fail_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '失败码',
`error_message` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '失败原因',
`create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL COMMENT '创建时间',
@@ -824,7 +825,8 @@ CREATE TABLE `prc_billing_task` (
KEY `idx_billing_task_tracking` (`tracking_no`),
KEY `idx_billing_task_order` (`order_no`),
KEY `idx_billing_task_status` (`success_status`,`create_time`),
KEY `idx_billing_task_type` (`bill_type`)
KEY `idx_billing_task_type` (`bill_type`),
KEY `idx_billing_task_fail_code` (`fail_code`)
) ENGINE=InnoDB AUTO_INCREMENT=7885 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='计费任务表';
-- ----------------------------
@@ -0,0 +1,23 @@
-- prc_billing_task add fail_code for abnormal-task search
ALTER TABLE prc_billing_task
ADD COLUMN fail_code varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '失败码' AFTER bill_id,
ADD KEY idx_billing_task_fail_code (fail_code);
-- backfill known historical FAILED rows
UPDATE prc_billing_task SET fail_code = 'NO_CUSTOMER_PACKAGE' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%未绑定套餐%';
UPDATE prc_billing_task SET fail_code = 'ALREADY_IN_AR_BILL' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%已生成应收账单%';
UPDATE prc_billing_task SET fail_code = 'PRODUCT_ARCHIVE_MISSING' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%未找到有效产品档案%';
UPDATE prc_billing_task SET fail_code = 'ZONE_PRICE_NOT_HIT' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%该分区无对应产品重量段%';
UPDATE prc_billing_task SET fail_code = 'PRICE_NOT_HIT' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%没有产品/国家/重量段%';
UPDATE prc_billing_task SET fail_code = 'POSTCODE_REQUIRED' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%请填写目的邮编%';
UPDATE prc_billing_task SET fail_code = 'POSTCODE_ZONE_NOT_FOUND' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%邮编未匹配到分区%';
UPDATE prc_billing_task SET fail_code = 'ORACLE_ORDER_NOT_FOUND' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%Oracle main order not found%';
UPDATE prc_billing_task SET fail_code = 'ORACLE_ORDERNO_EMPTY' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%mapped orderNo empty%';
UPDATE prc_billing_task SET fail_code = 'ORACLE_CUSTOMER_EMPTY' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%mapped customerCode empty%';
UPDATE prc_billing_task SET fail_code = 'ORACLE_PRODUCT_EMPTY' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%mapped productCode empty%';
UPDATE prc_billing_task SET fail_code = 'ORACLE_WEIGHT_INVALID' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%mapped weight invalid%';
UPDATE prc_billing_task SET fail_code = 'BILL_TIME_EMPTY' WHERE success_status = 'FAILED' AND fail_code IS NULL AND (error_message LIKE '%billTime empty%' OR error_message LIKE '%billTime required%');
UPDATE prc_billing_task SET fail_code = 'REFUND_ORDER_UNRESOLVED' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%退费订单号无法解析%';
UPDATE prc_billing_task SET fail_code = 'CUSTOMER_DISABLED' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%客户已停用%';
UPDATE prc_billing_task SET fail_code = 'FREIGHT_MISSING' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message LIKE '%运费为空%';
UPDATE prc_billing_task SET fail_code = 'UNKNOWN' WHERE success_status = 'FAILED' AND fail_code IS NULL AND error_message IS NOT NULL AND error_message <> '';