新增订单计费失败原因的价格周期字段,并优化前端展示,支持查看价格周期详情。
This commit is contained in:
@@ -35,6 +35,12 @@ public class OrderBillFailReasonVo implements Serializable
|
||||
private String setType;
|
||||
private Long psvId;
|
||||
private String versionNo;
|
||||
/** Price-cycle version effective time. */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date effectiveTime;
|
||||
/** Price-cycle version expire time; null = open. */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date expireTime;
|
||||
private Long psdId;
|
||||
|
||||
private BigDecimal weight;
|
||||
|
||||
+153
-8
@@ -46,6 +46,7 @@ import com.bms.customer.customerinfo.mapper.BmsCustomerMapper;
|
||||
import com.bms.system.mapper.SysUserMapper;
|
||||
import com.bms.system.service.IBmsBizRuleService;
|
||||
import com.bms.system.service.IBmsProductCodeMappingService;
|
||||
import com.bms.price.domain.PrcCustomerPriceSet;
|
||||
import com.bms.price.domain.PrcIncomeBill;
|
||||
import com.bms.price.domain.PrcIncomeBillFee;
|
||||
import com.bms.price.domain.PrcOrderBill;
|
||||
@@ -53,6 +54,7 @@ import com.bms.price.domain.PrcOrderBillExportTask;
|
||||
import com.bms.price.domain.PrcOrderBillExt;
|
||||
import com.bms.price.domain.PrcPayBill;
|
||||
import com.bms.price.domain.PrcPayBillFee;
|
||||
import com.bms.price.domain.PrcPriceSet;
|
||||
import com.bms.price.domain.PrcSurcharge;
|
||||
import com.bms.price.domain.BatchAddIncomeBillFeeTask;
|
||||
import com.bms.price.domain.ChargeWeightImportTask;
|
||||
@@ -83,11 +85,13 @@ import com.bms.price.enums.BillOrderStatus;
|
||||
import com.bms.price.enums.BillRefundStatus;
|
||||
import com.bms.price.enums.BillBillingFailStatus;
|
||||
import com.bms.price.enums.TmsOrderBillMatchType;
|
||||
import com.bms.price.mapper.PrcCustomerPriceSetMapper;
|
||||
import com.bms.price.mapper.PrcIncomeBillFeeMapper;
|
||||
import com.bms.price.mapper.PrcIncomeBillMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillExtMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetDetailMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetMapper;
|
||||
import com.bms.price.mapper.PrcPriceSetVersionMapper;
|
||||
import com.bms.price.domain.PrcPriceSetDetail;
|
||||
import com.bms.price.domain.PrcPriceSetVersion;
|
||||
@@ -132,6 +136,12 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
@Autowired
|
||||
private PrcPriceSetDetailMapper priceSetDetailMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcCustomerPriceSetMapper customerPriceSetMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcPriceSetMapper priceSetMapper;
|
||||
|
||||
/** TMS 实时状态查询使用客户主档案,不读取订单上的历史状态快照。 */
|
||||
@Autowired
|
||||
private BmsCustomerMapper bmsCustomerMapper;
|
||||
@@ -498,9 +508,11 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
PrcPriceSetVersion ver = priceSetVersionMapper.selectPrcPriceSetVersionById(bill.getPsvId());
|
||||
if (ver != null)
|
||||
{
|
||||
vo.setVersionNo(ver.getVersionNo());
|
||||
applyFailReasonVersion(vo, ver);
|
||||
}
|
||||
}
|
||||
// Soft-fail rows often leave package/version empty on the bill; resolve from customer bind.
|
||||
fillFailReasonPackageVersionFromBind(vo, bill);
|
||||
if (bill.getPsdId() != null && priceSetDetailMapper != null)
|
||||
{
|
||||
PrcPriceSetDetail detail = priceSetDetailMapper.selectPrcPriceSetDetailById(bill.getPsdId());
|
||||
@@ -512,10 +524,6 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
}
|
||||
String reason = bill.getErrorMessage();
|
||||
if (StringUtils.isEmpty(reason))
|
||||
{
|
||||
reason = bill.getMatchMessage();
|
||||
}
|
||||
if (StringUtils.isEmpty(reason))
|
||||
{
|
||||
reason = vo.getBillingFailStatusLabel();
|
||||
}
|
||||
@@ -524,6 +532,141 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* When order-bill package / version is empty, do not keep empty snapshot fields.
|
||||
* Resolve package from customer bind; match version by pickupDate then bizDate.
|
||||
* Leave empty when still not found.
|
||||
*/
|
||||
private void fillFailReasonPackageVersionFromBind(OrderBillFailReasonVo vo, PrcOrderBill bill)
|
||||
{
|
||||
if (vo == null || bill == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
boolean needPackage = bill.getPriceSetId() == null
|
||||
&& StringUtils.isEmpty(bill.getPriceSetCode())
|
||||
&& StringUtils.isEmpty(bill.getPriceSetName());
|
||||
boolean needVersion = bill.getPsvId() == null && StringUtils.isEmpty(vo.getVersionNo());
|
||||
if (!needPackage && !needVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (needPackage)
|
||||
{
|
||||
vo.setPriceSetId(null);
|
||||
vo.setPriceSetCode(null);
|
||||
vo.setPriceSetName(null);
|
||||
vo.setSetType(null);
|
||||
}
|
||||
if (needVersion)
|
||||
{
|
||||
vo.setPsvId(null);
|
||||
vo.setVersionNo(null);
|
||||
vo.setEffectiveTime(null);
|
||||
vo.setExpireTime(null);
|
||||
}
|
||||
Long priceSetId = needPackage ? null : bill.getPriceSetId();
|
||||
PrcPriceSet set = null;
|
||||
PrcPriceSetVersion version = null;
|
||||
if (priceSetId != null)
|
||||
{
|
||||
set = priceSetMapper == null ? null : priceSetMapper.selectPrcPriceSetById(priceSetId);
|
||||
version = needVersion ? selectVersionByPickupOrBiz(priceSetId, bill) : null;
|
||||
}
|
||||
else if (needPackage || needVersion)
|
||||
{
|
||||
if (StringUtils.isEmpty(bill.getCustomerCode()) || customerPriceSetMapper == null
|
||||
|| priceSetMapper == null || priceSetVersionMapper == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<PrcCustomerPriceSet> binds =
|
||||
customerPriceSetMapper.selectEffectiveByCustomerCode(bill.getCustomerCode().trim());
|
||||
if (binds == null || binds.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (PrcCustomerPriceSet bind : binds)
|
||||
{
|
||||
if (bind == null || bind.getPriceSetId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
PrcPriceSet candidate = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
|
||||
if (candidate == null || !"E".equals(candidate.getStatus()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String category = StringUtils.trim(candidate.getSetCategory());
|
||||
if (PrcPriceSet.CATEGORY_LABEL.equalsIgnoreCase(category))
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
set = candidate;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
PrcPriceSetVersion hit = selectVersionByPickupOrBiz(candidate.getPriceSetId(), bill);
|
||||
if (set == null)
|
||||
{
|
||||
set = candidate;
|
||||
}
|
||||
if (hit != null)
|
||||
{
|
||||
set = candidate;
|
||||
version = hit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needPackage && set != null)
|
||||
{
|
||||
vo.setPriceSetId(set.getPriceSetId());
|
||||
vo.setPriceSetCode(set.getPriceSetCode());
|
||||
vo.setPriceSetName(set.getPriceSetName());
|
||||
vo.setSetType(set.getSetType());
|
||||
}
|
||||
if (needVersion && version != null)
|
||||
{
|
||||
applyFailReasonVersion(vo, version);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyFailReasonVersion(OrderBillFailReasonVo vo, PrcPriceSetVersion ver)
|
||||
{
|
||||
if (vo == null || ver == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
vo.setPsvId(ver.getPsvId());
|
||||
vo.setVersionNo(ver.getVersionNo());
|
||||
vo.setEffectiveTime(ver.getEffectiveTime());
|
||||
vo.setExpireTime(ver.getExpireTime());
|
||||
}
|
||||
|
||||
/** Prefer pickupDate, then bizDate, for sales package version window match. */
|
||||
private PrcPriceSetVersion selectVersionByPickupOrBiz(Long priceSetId, PrcOrderBill bill)
|
||||
{
|
||||
if (priceSetId == null || priceSetVersionMapper == null || bill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Date[] asOfList = new Date[] { bill.getPickupDate(), bill.getBizDate() };
|
||||
for (Date asOf : asOfList)
|
||||
{
|
||||
if (asOf == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
PrcPriceSetVersion ver = priceSetVersionMapper.selectActiveByPriceSetAt(priceSetId, asOf);
|
||||
if (ver != null)
|
||||
{
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TmsPriceQuoteResult getBillByOrderNo(String orderNo)
|
||||
{
|
||||
@@ -1918,8 +2061,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
bill.setWeight(weight);
|
||||
bill.setBizDate(bizDate);
|
||||
bill.setTmsId(request.getTmsId() == null ? null : Long.valueOf(request.getTmsId()));
|
||||
bill.setMatchSource(result.getMatchSource());
|
||||
bill.setMatchMessage(result.getMatchMessage());
|
||||
// Do not persist match_source / match_message on order bill.
|
||||
bill.setMatchSource(null);
|
||||
bill.setMatchMessage(null);
|
||||
bill.setPriceSetId(result.getPriceSetId());
|
||||
bill.setPriceSetCode(result.getPriceSetCode());
|
||||
bill.setPriceSetName(result.getPriceSetName());
|
||||
@@ -3512,6 +3656,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
failText = failText.substring(0, 1000);
|
||||
}
|
||||
bill.setErrorMessage(failText);
|
||||
bill.setMatchMessage(failText);
|
||||
bill.setMatchMessage(null);
|
||||
bill.setMatchSource(null);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-17
@@ -19,14 +19,14 @@ public final class BillingFailReasonSupport
|
||||
|
||||
static
|
||||
{
|
||||
TEMPLATES.put(Integer.valueOf(1), "\u3010\u9500\u552e\u4ef7\u672a\u5339\u914d\u4ea7\u54c1\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u5ba2\u6237 {customer}\uff0c\u4ea7\u54c1 {product}\uff0c\u56fd\u5bb6 {country}\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u5728\u5ba2\u6237\u9500\u552e\u4ef7\u5957\u9910\u300c{priceSet}\u300d\u4e2d\u672a\u627e\u5230\u8be5\u4ea7\u54c1\uff0c\u8bf7\u68c0\u67e5\u5957\u9910\u4ea7\u54c1\u8303\u56f4\u6216\u4ea7\u54c1\u4ee3\u7801\u6620\u5c04\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(2), "\u3010\u9500\u552e\u4ef7\u672a\u5339\u914d\u56fd\u5bb6\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u5ba2\u6237 {customer}\uff0c\u4ea7\u54c1 {product}\uff0c\u76ee\u7684\u56fd\u5bb6 {country}\uff0c\u90ae\u7f16 {postcode}\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u5957\u9910\u300c{priceSet}\u300d\u7248\u672c {version}\u5df2\u547d\u4e2d\u4ea7\u54c1\uff0c\u4f46\u672a\u547d\u4e2d\u56fd\u5bb6/\u5206\u533a\uff0c\u8bf7\u8865\u9f50\u56fd\u5bb6\u4e0e\u5206\u533a\u4ef7\u683c\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(3), "\u3010\u9500\u552e\u4ef7\u672a\u5339\u914d\u91cd\u91cf\u6bb5\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u5ba2\u6237 {customer}\uff0c\u4ea7\u54c1 {product}\uff0c\u56fd\u5bb6 {country}\uff0c\u5206\u533a {zone}\uff0c\u8ba1\u8d39\u91cd {chargeWeight} kg\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u5957\u9910\u300c{priceSet}\u300d\u7248\u672c {version}\u5df2\u547d\u4e2d\u4ea7\u54c1\u4e0e\u56fd\u5bb6\uff0c\u4f46\u8ba1\u8d39\u91cd\u672a\u843d\u5728\u4ef7\u683c\u91cd\u91cf\u6bb5\u5185\uff0c\u8bf7\u68c0\u67e5\u91cd\u91cf\u6bb5\u8bbe\u7f6e\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(23), "\u3010\u4ea7\u54c1\u4ef7\u672a\u5339\u914d\u4ea7\u54c1\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u5ba2\u6237 {customer}\uff0c\u4ea7\u54c1 {product}\uff0c\u56fd\u5bb6 {country}\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u4ea7\u54c1\u4ef7\u76ee\u5f55\u4e2d\u672a\u627e\u5230\u8be5\u4ea7\u54c1\uff0c\u8bf7\u68c0\u67e5\u4ea7\u54c1\u4ef7\u6863\u6848\u662f\u5426\u5df2\u7ef4\u62a4\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(24), "\u3010\u4ea7\u54c1\u4ef7\u672a\u5339\u914d\u56fd\u5bb6\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u4ea7\u54c1 {product}\uff0c\u76ee\u7684\u56fd\u5bb6 {country}\uff0c\u90ae\u7f16 {postcode}\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u4ea7\u54c1\u4ef7\u5df2\u547d\u4e2d\u4ea7\u54c1\uff0c\u4f46\u672a\u547d\u4e2d\u56fd\u5bb6/\u5206\u533a\uff0c\u8bf7\u8865\u9f50\u4ea7\u54c1\u4ef7\u56fd\u5bb6\u4ef7\u683c\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(25), "\u3010\u4ea7\u54c1\u4ef7\u672a\u5339\u914d\u91cd\u91cf\u6bb5\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u4ea7\u54c1 {product}\uff0c\u56fd\u5bb6 {country}\uff0c\u5206\u533a {zone}\uff0c\u8ba1\u8d39\u91cd {chargeWeight} kg\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u4ea7\u54c1\u4ef7\u5df2\u547d\u4e2d\u4ea7\u54c1\u4e0e\u56fd\u5bb6\uff0c\u4f46\u8ba1\u8d39\u91cd\u672a\u843d\u5728\u91cd\u91cf\u6bb5\u5185\uff0c\u8bf7\u68c0\u67e5\u4ea7\u54c1\u4ef7\u91cd\u91cf\u6bb5\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(4), "\u3010\u672a\u7ed1\u5b9a\u5957\u9910\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u5ba2\u6237 {customer}\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u8be5\u5ba2\u6237\u672a\u7ed1\u5b9a\u9500\u552e\u4ef7\u5957\u9910\uff0c\u65e0\u6cd5\u6309\u5957\u9910\u53d6\u4ef7\uff0c\u8bf7\u5148\u5728\u5ba2\u6237/\u5957\u9910\u7ed1\u5b9a\u540e\u518d\u8ba1\u8d39\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(5), "\u3010\u5957\u9910\u672a\u542f\u7528/\u65e0\u751f\u6548\u7248\u672c\u3011\n\u8ba2\u5355 {orderNo}\uff08\u8f6c\u5355\u53f7 {transferNo}\uff09\uff0c\u5ba2\u6237 {customer}\uff0c\u5957\u9910\u300c{priceSet}\u300d\uff0c\u8ba1\u8d39\u65f6\u95f4 {bizDate}\u3002\n\u8ba1\u8d39\u65e5\u6ca1\u6709\u53ef\u7528\u7684\u5957\u9910\u751f\u6548\u7248\u672c\uff0c\u8bf7\u68c0\u67e5\u5957\u9910\u542f\u7528\u72b6\u6001\u4e0e\u7248\u672c\u751f\u6548\u671f\u3002");
|
||||
TEMPLATES.put(Integer.valueOf(1), "【销售价未匹配产品】\n订单 {orderNo}(转单号 {transferNo}),客户 {customer},产品 {product},国家 {country},计费时间 {bizDate}。\n在客户销售价套餐「{priceSet}」中未找到该产品,请检查套餐产品范围或产品代码映射。");
|
||||
TEMPLATES.put(Integer.valueOf(2), "【销售价未匹配国家】\n订单 {orderNo}(转单号 {transferNo}),客户 {customer},产品 {product},目的国家 {country},邮编 {postcode},计费时间 {bizDate}。\n套餐「{priceSet}」版本 {version}已命中产品,但未命中国家/分区,请补齐国家与分区价格。");
|
||||
TEMPLATES.put(Integer.valueOf(3), "【销售价未匹配重量段】\n订单 {orderNo}(转单号 {transferNo}),客户 {customer},产品 {product},国家 {country},分区 {zone},计费重 {chargeWeight} kg,计费时间 {bizDate}。\n套餐「{priceSet}」版本 {version}已命中产品与国家,但计费重未落在价格重量段内,请检查重量段设置。");
|
||||
TEMPLATES.put(Integer.valueOf(23), "【产品价未匹配产品】\n订单 {orderNo}(转单号 {transferNo}),客户 {customer},产品 {product},国家 {country},计费时间 {bizDate}。\n产品价目录中未找到该产品,请检查产品价档案是否已维护。");
|
||||
TEMPLATES.put(Integer.valueOf(24), "【产品价未匹配国家】\n订单 {orderNo}(转单号 {transferNo}),产品 {product},目的国家 {country},邮编 {postcode},计费时间 {bizDate}。\n产品价已命中产品,但未命中国家/分区,请补齐产品价国家价格。");
|
||||
TEMPLATES.put(Integer.valueOf(25), "【产品价未匹配重量段】\n订单 {orderNo}(转单号 {transferNo}),产品 {product},国家 {country},分区 {zone},计费重 {chargeWeight} kg,计费时间 {bizDate}。\n产品价已命中产品与国家,但计费重未落在重量段内,请检查产品价重量段。");
|
||||
TEMPLATES.put(Integer.valueOf(4), "【未绑定套餐】\n订单 {orderNo}(转单号 {transferNo}),客户 {customer},计费时间 {bizDate}。\n该客户未绑定销售价套餐,无法按套餐取价,请先在客户/套餐绑定后再计费。");
|
||||
TEMPLATES.put(Integer.valueOf(5), "【套餐未启用/无生效版本】\n订单 {orderNo}(转单号 {transferNo}),客户 {customer},套餐「{priceSet}」,计费时间 {bizDate}。\n计费日没有可用的套餐生效版本,请检查套餐启用状态与版本生效期。");
|
||||
}
|
||||
|
||||
public static String buildDetail(OrderBillFailReasonVo vo, String fallback)
|
||||
@@ -40,13 +40,13 @@ public final class BillingFailReasonSupport
|
||||
if (StringUtils.isEmpty(tpl))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\u3010").append(nz(vo.getBillingFailStatusLabel(), "\u8ba1\u8d39\u5931\u8d25")).append("\u3011\n");
|
||||
sb.append("\u8ba2\u5355 ").append(nz(vo.getOrderNo(), "-"));
|
||||
sb.append("\uff08\u8f6c\u5355\u53f7 ").append(nz(vo.getTransferNo(), "-")).append("\uff09");
|
||||
sb.append("\uff0c\u5ba2\u6237 ").append(joinCodeName(vo.getCustomerCode(), vo.getCustomerName()));
|
||||
sb.append("\uff0c\u4ea7\u54c1 ").append(joinCodeName(vo.getProductCode(), vo.getProductName()));
|
||||
sb.append("\uff0c\u56fd\u5bb6 ").append(joinCodeName(vo.getCountryCode(), vo.getCountryName()));
|
||||
sb.append("\uff0c\u8ba1\u8d39\u65f6\u95f4 ").append(fmtDate(vo.getBizDate())).append("\u3002\n");
|
||||
sb.append("【").append(nz(vo.getBillingFailStatusLabel(), "计费失败")).append("】\n");
|
||||
sb.append("订单 ").append(nz(vo.getOrderNo(), "-"));
|
||||
sb.append("(转单号 ").append(nz(vo.getTransferNo(), "-")).append(")");
|
||||
sb.append(",客户 ").append(joinCodeName(vo.getCustomerCode(), vo.getCustomerName()));
|
||||
sb.append(",产品 ").append(joinCodeName(vo.getProductCode(), vo.getProductName()));
|
||||
sb.append(",国家 ").append(joinCodeName(vo.getCountryCode(), vo.getCountryName()));
|
||||
sb.append(",计费时间 ").append(fmtDate(vo.getBizDate())).append("。\n");
|
||||
sb.append(nz(fallback, nz(vo.getErrorMessage(), vo.getBillingFailStatusLabel())));
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public final class BillingFailReasonSupport
|
||||
ctx.put("zone", joinCodeName(vo.getZoneCode(), vo.getZoneName()));
|
||||
ctx.put("postcode", nz(vo.getDestPostcode(), "-"));
|
||||
ctx.put("priceSet", joinCodeName(vo.getPriceSetCode(), vo.getPriceSetName()));
|
||||
ctx.put("version", nz(vo.getVersionNo(), "-"));
|
||||
ctx.put("version", formatPriceCycle(vo.getEffectiveTime(), vo.getExpireTime(), vo.getVersionNo()));
|
||||
ctx.put("chargeWeight", fmtNum(vo.getChargeWeight()));
|
||||
ctx.put("bizDate", fmtDate(vo.getBizDate()));
|
||||
ctx.put("pickupDate", fmtDate(vo.getPickupDate()));
|
||||
@@ -71,7 +71,7 @@ public final class BillingFailReasonSupport
|
||||
}
|
||||
if (StringUtils.isNotEmpty(fallback) && !fallback.equals(vo.getBillingFailStatusLabel()))
|
||||
{
|
||||
out = out + "\n\u8be6\u60c5\uff1a" + fallback;
|
||||
out = out + "\n详情:" + fallback;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -93,6 +93,15 @@ public final class BillingFailReasonSupport
|
||||
return code + "/" + name;
|
||||
}
|
||||
|
||||
private static String formatPriceCycle(Date effectiveTime, Date expireTime, String versionNo)
|
||||
{
|
||||
if (effectiveTime == null && expireTime == null)
|
||||
{
|
||||
return nz(versionNo, "-");
|
||||
}
|
||||
return fmtDate(effectiveTime) + " ~ " + fmtDate(expireTime);
|
||||
}
|
||||
|
||||
private static String fmtDate(Date d)
|
||||
{
|
||||
if (d == null)
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:model-value="modelValue"
|
||||
:title="drawerTitle"
|
||||
size="720px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="close"
|
||||
>
|
||||
<div v-loading="loading" class="cycle-body">
|
||||
<el-empty v-if="!loading && !versions.length" :description="emptyText" :image-size="72" />
|
||||
<el-table v-else :data="versions" border height="calc(100vh - 220px)">
|
||||
<el-table-column :label="colVer" prop="versionNo" width="90" align="center" />
|
||||
<el-table-column :label="colEff" min-width="170">
|
||||
<template #default="{ row }">{{ row.effectiveTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="colExp" min-width="170">
|
||||
<template #default="{ row }">{{ row.expireTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="colStatus" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="versionStatusTag(row.status)" size="small">{{ versionStatusLabel(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="colRows" width="90" align="center">
|
||||
<template #default="{ row }">{{ row.detailCount != null ? row.detailCount : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="colAction" width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="close">关闭</el-button>
|
||||
</template>
|
||||
|
||||
<set-version-detail-drawer
|
||||
v-model="detailVisible"
|
||||
:version="detailVersion"
|
||||
:set-type="setType || 'FIXED'"
|
||||
/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup name="OrderBillPriceCycleListDrawer">
|
||||
import { priceSetApi } from '@/api/price/set'
|
||||
import { exclusiveQuoteApi } from '@/api/price/exclusive'
|
||||
import { versionStatusLabel, versionStatusTag } from '../../set/utils/priceSet'
|
||||
import SetVersionDetailDrawer from '../../set/components/detail/SetVersionDetailDrawer.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
priceSetId: { type: [Number, String], default: null },
|
||||
priceSetCode: { type: String, default: '' },
|
||||
priceSetName: { type: String, default: '' },
|
||||
setType: { type: String, default: 'FIXED' }
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const loading = ref(false)
|
||||
const versions = ref([])
|
||||
const detailVisible = ref(false)
|
||||
const detailVersion = ref(null)
|
||||
|
||||
const emptyText = '暂无价格周期'
|
||||
const colVer = '版本号'
|
||||
const colEff = '生效时间'
|
||||
const colExp = '失效时间'
|
||||
const colStatus = '状态'
|
||||
const colRows = '明细行数'
|
||||
const colAction = '操作'
|
||||
|
||||
const drawerTitle = computed(() => {
|
||||
const code = (props.priceSetCode || '').trim()
|
||||
const name = (props.priceSetName || '').trim()
|
||||
let pkg = '-'
|
||||
if (code && name && code !== name) pkg = code + ' / ' + name
|
||||
else if (code || name) pkg = code || name
|
||||
return '价格周期列表 · ' + pkg
|
||||
})
|
||||
|
||||
function close() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
function toEffMs(v) {
|
||||
if (!v || !v.effectiveTime) return NaN
|
||||
const t = new Date(String(v.effectiveTime).replace(/-/g, '/')).getTime()
|
||||
return Number.isNaN(t) ? NaN : t
|
||||
}
|
||||
|
||||
function statusRank(v) {
|
||||
const s = String(v && v.status != null ? v.status : '')
|
||||
if (s === '0') return 0
|
||||
if (s === '3') return 1
|
||||
if (s === '1') return 2
|
||||
if (s === '2') return 3
|
||||
return 9
|
||||
}
|
||||
|
||||
function sortVersions(list) {
|
||||
return (list || []).slice().sort((a, b) => {
|
||||
const ra = statusRank(a)
|
||||
const rb = statusRank(b)
|
||||
if (ra !== rb) return ra - rb
|
||||
const ta = toEffMs(a)
|
||||
const tb = toEffMs(b)
|
||||
const aNull = Number.isNaN(ta)
|
||||
const bNull = Number.isNaN(tb)
|
||||
if (aNull && bNull) return (Number(b.versionSeq) || 0) - (Number(a.versionSeq) || 0)
|
||||
if (aNull) return 1
|
||||
if (bNull) return -1
|
||||
if (tb !== ta) return tb - ta
|
||||
return (Number(b.versionSeq) || 0) - (Number(a.versionSeq) || 0)
|
||||
})
|
||||
}
|
||||
|
||||
async function loadVersions() {
|
||||
versions.value = []
|
||||
if (!props.priceSetId) return
|
||||
loading.value = true
|
||||
try {
|
||||
let data = null
|
||||
try {
|
||||
const res = await priceSetApi.getPriceSet(props.priceSetId)
|
||||
data = (res && res.data) || null
|
||||
} catch (e1) {
|
||||
const res2 = await exclusiveQuoteApi.getPriceSet(props.priceSetId)
|
||||
data = (res2 && res2.data) || null
|
||||
}
|
||||
const list = (data && data.versions) || []
|
||||
versions.value = sortVersions(list.filter(v => v && String(v.isDel) !== '1'))
|
||||
} catch (e) {
|
||||
versions.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDetail(row) {
|
||||
if (!row || row.psvId == null) return
|
||||
detailVersion.value = {
|
||||
psvId: row.psvId,
|
||||
versionNo: row.versionNo,
|
||||
effectiveTime: row.effectiveTime || null,
|
||||
expireTime: row.expireTime || null
|
||||
}
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.modelValue, props.priceSetId],
|
||||
([vis]) => {
|
||||
if (vis) loadVersions()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cycle-body {
|
||||
min-height: 240px;
|
||||
}
|
||||
</style>
|
||||
@@ -342,10 +342,28 @@
|
||||
<span :class="frValueClass(failReason.destPostcode || '-')">{{ failReason.destPostcode || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label-class-name="fr-label-key" class-name="fr-cell-key" label="套餐">
|
||||
<span :class="frValueClass(joinCodeName(failReason.priceSetCode, failReason.priceSetName), true)">{{ joinCodeName(failReason.priceSetCode, failReason.priceSetName) }}</span>
|
||||
<div class="fr-cycle-row">
|
||||
<span :class="frValueClass(joinCodeName(failReason.priceSetCode, failReason.priceSetName), true)">{{ joinCodeName(failReason.priceSetCode, failReason.priceSetName) }}</span>
|
||||
<el-button
|
||||
v-if="failReason.priceSetId"
|
||||
link
|
||||
type="primary"
|
||||
class="fr-cycle-btn"
|
||||
@click="openPackagePriceCycles"
|
||||
>查看</el-button>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="套餐版本">
|
||||
<span :class="frValueClass(failReason.versionNo || '-')">{{ failReason.versionNo || '-' }}</span>
|
||||
<el-descriptions-item label-class-name="fr-label-key" class-name="fr-cell-key" label="价格周期">
|
||||
<div class="fr-cycle-row">
|
||||
<span :class="frValueClass(formatPriceCycle(failReason), true)">{{ formatPriceCycle(failReason) }}</span>
|
||||
<el-button
|
||||
v-if="failReason.psvId"
|
||||
link
|
||||
type="primary"
|
||||
class="fr-cycle-btn"
|
||||
@click="openPriceCycleDetail"
|
||||
>查看</el-button>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重量段">
|
||||
<span :class="frValueClass(failReason.weightLabel || formatWeightRange(failReason.weightMin, failReason.weightMax))">{{ failReason.weightLabel || formatWeightRange(failReason.weightMin, failReason.weightMax) }}</span>
|
||||
@@ -380,6 +398,20 @@
|
||||
<el-button type="primary" @click="failReasonVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<set-version-detail-drawer
|
||||
v-model="priceCycleDrawerVisible"
|
||||
:version="priceCycleVersion"
|
||||
:set-type="failReason.setType || 'FIXED'"
|
||||
/>
|
||||
|
||||
<order-bill-price-cycle-list-drawer
|
||||
v-model="packageCycleDrawerVisible"
|
||||
:price-set-id="failReason.priceSetId"
|
||||
:price-set-code="failReason.priceSetCode"
|
||||
:price-set-name="failReason.priceSetName"
|
||||
:set-type="failReason.setType || 'FIXED'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -391,6 +423,8 @@ import ExcelExportFieldDialog from '@/components/ExcelExportFieldDialog/index.vu
|
||||
import { ORDER_BILL_EXPORT_FIELDS } from '@/utils/exportFieldDefs'
|
||||
import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
|
||||
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
|
||||
import SetVersionDetailDrawer from '../set/components/detail/SetVersionDetailDrawer.vue'
|
||||
import OrderBillPriceCycleListDrawer from './components/OrderBillPriceCycleListDrawer.vue'
|
||||
|
||||
const tableRef = ref()
|
||||
const exportFieldRef = ref()
|
||||
@@ -1171,9 +1205,14 @@ const failReason = reactive({
|
||||
zoneCode: '',
|
||||
zoneName: '',
|
||||
destPostcode: '',
|
||||
priceSetId: null,
|
||||
priceSetCode: '',
|
||||
priceSetName: '',
|
||||
setType: '',
|
||||
psvId: null,
|
||||
versionNo: '',
|
||||
effectiveTime: null,
|
||||
expireTime: null,
|
||||
weightLabel: '',
|
||||
weightMin: null,
|
||||
weightMax: null,
|
||||
@@ -1225,6 +1264,37 @@ function formatDateTime(val) {
|
||||
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
|
||||
}
|
||||
|
||||
function formatPriceCycle(row) {
|
||||
const r = row || {}
|
||||
const eff = formatDateTime(r.effectiveTime)
|
||||
const exp = formatDateTime(r.expireTime)
|
||||
if (eff === '-' && exp === '-') {
|
||||
return r.versionNo || '-'
|
||||
}
|
||||
return eff + ' ~ ' + exp
|
||||
}
|
||||
|
||||
const priceCycleDrawerVisible = ref(false)
|
||||
const priceCycleVersion = ref(null)
|
||||
|
||||
function openPriceCycleDetail() {
|
||||
if (!failReason.psvId) return
|
||||
priceCycleVersion.value = {
|
||||
psvId: failReason.psvId,
|
||||
versionNo: failReason.versionNo,
|
||||
effectiveTime: failReason.effectiveTime ? formatDateTime(failReason.effectiveTime) : null,
|
||||
expireTime: failReason.expireTime ? formatDateTime(failReason.expireTime) : null
|
||||
}
|
||||
priceCycleDrawerVisible.value = true
|
||||
}
|
||||
|
||||
const packageCycleDrawerVisible = ref(false)
|
||||
|
||||
function openPackagePriceCycles() {
|
||||
if (!failReason.priceSetId) return
|
||||
packageCycleDrawerVisible.value = true
|
||||
}
|
||||
|
||||
function applyFailReason(data, row) {
|
||||
const src = data || {}
|
||||
const base = row || {}
|
||||
@@ -1241,9 +1311,14 @@ function applyFailReason(data, row) {
|
||||
failReason.zoneCode = src.zoneCode || ''
|
||||
failReason.zoneName = src.zoneName || ''
|
||||
failReason.destPostcode = src.destPostcode || base.destPostcode || ''
|
||||
failReason.priceSetId = src.priceSetId != null ? src.priceSetId : (base.priceSetId != null ? base.priceSetId : null)
|
||||
failReason.priceSetCode = src.priceSetCode || base.priceSetCode || ''
|
||||
failReason.priceSetName = src.priceSetName || base.priceSetName || ''
|
||||
failReason.setType = src.setType || base.setType || ''
|
||||
failReason.psvId = src.psvId != null ? src.psvId : (base.psvId != null ? base.psvId : null)
|
||||
failReason.versionNo = src.versionNo || ''
|
||||
failReason.effectiveTime = src.effectiveTime || null
|
||||
failReason.expireTime = src.expireTime || null
|
||||
failReason.weightLabel = src.weightLabel || base.weightLabel || ''
|
||||
failReason.weightMin = src.weightMin != null ? src.weightMin : base.weightMin
|
||||
failReason.weightMax = src.weightMax != null ? src.weightMax : base.weightMax
|
||||
@@ -1487,6 +1562,16 @@ onBeforeUnmount(() => {
|
||||
color: #0f172a;
|
||||
font-weight: 500;
|
||||
}
|
||||
.fr-cycle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.fr-cycle-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.fr-val-key {
|
||||
color: #c2410c;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
:model-value="modelValue"
|
||||
:title="title"
|
||||
size="72%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="close"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user