This commit is contained in:
luojiaquan
2026-10-23 15:57:15 +08:00
14 changed files with 417 additions and 71 deletions
@@ -31,6 +31,8 @@ public class BatchAddIncomeBillFeeRequest implements Serializable
private List<String> productCodes; private List<String> productCodes;
private String countryCode; private String countryCode;
private String creditArrearsStatus; private String creditArrearsStatus;
/** Same as list filter billing_fail_status IN (...). */
private List<Integer> billingFailStatuses;
/** Billing time range (biz_date), inclusive; format yyyy-MM-dd. */ /** Billing time range (biz_date), inclusive; format yyyy-MM-dd. */
private String beginBizDate; private String beginBizDate;
@@ -25,6 +25,8 @@ public class RecalcBillPriceRequest implements Serializable
private List<String> productCodes; private List<String> productCodes;
private String countryCode; private String countryCode;
private String creditArrearsStatus; private String creditArrearsStatus;
/** Same as list filter billing_fail_status IN (...). */
private List<Integer> billingFailStatuses;
private String beginBizDate; private String beginBizDate;
private String endBizDate; private String endBizDate;
} }
@@ -38,6 +38,11 @@ public class TabOrderMonitorQuery implements Serializable
private List<String> orderIdentifiers; private List<String> orderIdentifiers;
/** 上游产品代码 LINE_CODE(产品比对点进订单号时使用) */ /** 上游产品代码 LINE_CODE(产品比对点进订单号时使用) */
private String productCode; private String productCode;
/**
* 订单状态 ORDER_STATUS 多选。
* 空则服务端回落默认 3,4,5,6,7,8,11(与历史硬编码一致)。
*/
private List<Integer> orderStatuses;
/** 分批同步计费每批条数 */ /** 分批同步计费每批条数 */
private Integer batchSize; private Integer batchSize;
} }
@@ -0,0 +1,49 @@
package com.bms.price.enums;
/**
* Snapshot of prc_order_bill.order_status (aligned with upstream TAB_ORDER).
* Fee refund sets {@link #REFUNDED} (9 / 已退件).
*/
public enum BillOrderStatus
{
/** Upstream: returned package; also set after billing-list fee refund. */
REFUNDED(9);
private final int code;
BillOrderStatus(int code)
{
this.code = code;
}
public int getCode()
{
return code;
}
public Integer getCodeBoxed()
{
return Integer.valueOf(code);
}
public boolean matches(Integer status)
{
return status != null && status.intValue() == code;
}
public static BillOrderStatus of(Integer status)
{
if (status == null)
{
return null;
}
for (BillOrderStatus value : values())
{
if (value.code == status.intValue())
{
return value;
}
}
return null;
}
}
@@ -3,6 +3,7 @@ package com.bms.price.oracle;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
@@ -71,6 +72,12 @@ public class TabOrderMonitorService
/** 单批 Oracle 查询最长等待(从库 socketTimeout 为 120s,留同样余量)。 */ /** 单批 Oracle 查询最长等待(从库 socketTimeout 为 120s,留同样余量)。 */
private static final long COLLECT_BATCH_TIMEOUT_SECONDS = 120L; private static final long COLLECT_BATCH_TIMEOUT_SECONDS = 120L;
/** Same default as historical hardcoded ORDER_STATUS IN (...). */
private static final List<Integer> DEFAULT_ORDER_STATUSES =
Collections.unmodifiableList(Arrays.asList(
Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(5), Integer.valueOf(6),
Integer.valueOf(7), Integer.valueOf(8), Integer.valueOf(11)));
@Autowired @Autowired
private OracleUpstreamOrderMapper oracleUpstreamOrderMapper; private OracleUpstreamOrderMapper oracleUpstreamOrderMapper;
@@ -994,7 +1001,8 @@ public class TabOrderMonitorService
appendSorted(sb, q.getCustomerCodes()); appendSorted(sb, q.getCustomerCodes());
sb.append('|').append(safe(q.getBeginTime())).append('|').append(safe(q.getEndTime())) sb.append('|').append(safe(q.getBeginTime())).append('|').append(safe(q.getEndTime()))
.append('|').append(safe(q.getOrderId())).append('|').append(safe(q.getOrgOrderId())) .append('|').append(safe(q.getOrderId())).append('|').append(safe(q.getOrgOrderId()))
.append('|').append(safe(q.getTransferOrderId())); .append('|').append(safe(q.getTransferOrderId())).append('|');
appendSortedInts(sb, q.getOrderStatuses());
return Integer.toHexString(sb.toString().hashCode()); return Integer.toHexString(sb.toString().hashCode());
} }
@@ -1012,6 +1020,20 @@ public class TabOrderMonitorService
} }
} }
private static void appendSortedInts(StringBuilder sb, List<Integer> values)
{
if (values == null || values.isEmpty())
{
return;
}
List<Integer> copy = new ArrayList<Integer>(values);
Collections.sort(copy);
for (Integer v : copy)
{
sb.append(v).append(',');
}
}
private static String safe(String s) private static String safe(String s)
{ {
return s == null ? "" : s; return s == null ? "" : s;
@@ -1152,6 +1174,25 @@ public class TabOrderMonitorService
query.setOrderId(trimToNull(query.getOrderId())); query.setOrderId(trimToNull(query.getOrderId()));
query.setOrgOrderId(trimToNull(query.getOrgOrderId())); query.setOrgOrderId(trimToNull(query.getOrgOrderId()));
query.setTransferOrderId(trimToNull(query.getTransferOrderId())); query.setTransferOrderId(trimToNull(query.getTransferOrderId()));
normalizeOrderStatuses(query);
}
/** Empty/missing -> default 3,4,5,6,7,8,11 so SQL always has a status IN list. */
private static void normalizeOrderStatuses(TabOrderMonitorQuery query)
{
List<Integer> statuses = query.getOrderStatuses();
List<Integer> cleaned = new ArrayList<Integer>();
if (statuses != null)
{
for (Integer s : statuses)
{
if (s != null)
{
cleaned.add(s);
}
}
}
query.setOrderStatuses(cleaned.isEmpty() ? new ArrayList<Integer>(DEFAULT_ORDER_STATUSES) : cleaned);
} }
/** /**
@@ -34,6 +34,7 @@ import com.bms.system.service.IBmsProductCodeMappingService;
public class OrderBillProductCheckService public class OrderBillProductCheckService
{ {
private static final int ORDER_DRILL_LIMIT = 500; private static final int ORDER_DRILL_LIMIT = 500;
private static final int MAX_ORDER_IDENTIFIERS = 1000;
@Autowired @Autowired
private ITmsPriceQuoteService tmsPriceQuoteService; private ITmsPriceQuoteService tmsPriceQuoteService;
@@ -225,6 +226,7 @@ public class OrderBillProductCheckService
throw new ServiceException("please select customer"); throw new ServiceException("please select customer");
} }
query.setCustomerCodes(codes); query.setCustomerCodes(codes);
query.setOrderIdentifiers(normalizeOrderIdentifiers(query.getOrderIdentifiers()));
Map<String, Object> params = query.getParams(); Map<String, Object> params = query.getParams();
String begin = params == null ? null : strObj(params.get("beginBizDate")); String begin = params == null ? null : strObj(params.get("beginBizDate"));
String end = params == null ? null : strObj(params.get("endBizDate")); String end = params == null ? null : strObj(params.get("endBizDate"));
@@ -251,6 +253,34 @@ public class OrderBillProductCheckService
return new ArrayList<String>(set); return new ArrayList<String>(set);
} }
private static List<String> normalizeOrderIdentifiers(List<String> identifiers)
{
LinkedHashSet<String> unique = new LinkedHashSet<String>();
if (identifiers != null)
{
for (String identifier : identifiers)
{
if (StringUtils.isEmpty(identifier))
{
continue;
}
for (String token : identifier.split("[,,;;\\s]+"))
{
String value = token.trim();
if (StringUtils.isNotEmpty(value))
{
unique.add(value);
}
}
}
}
if (unique.size() > MAX_ORDER_IDENTIFIERS)
{
throw new ServiceException("单号去重后最多输入 " + MAX_ORDER_IDENTIFIERS + " 个");
}
return new ArrayList<String>(unique);
}
private static Date resolveAsOf(PrcOrderBill query) private static Date resolveAsOf(PrcOrderBill query)
{ {
Map<String, Object> params = query.getParams(); Map<String, Object> params = query.getParams();
@@ -77,6 +77,7 @@ import com.bms.price.domain.vo.TmsPriceQuoteFeeLine;
import com.bms.price.domain.vo.TmsPriceQuoteResult; 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.BillOrderStatus;
import com.bms.price.enums.BillRefundStatus; import com.bms.price.enums.BillRefundStatus;
import com.bms.price.enums.BillBillingFailStatus; import com.bms.price.enums.BillBillingFailStatus;
import com.bms.price.enums.TmsOrderBillMatchType; import com.bms.price.enums.TmsOrderBillMatchType;
@@ -106,6 +107,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
private static final String DICT_FEE_TYPE = "bms_fee_type"; private static final String DICT_FEE_TYPE = "bms_fee_type";
private static final String FEE_TYPE_REFUND = "REFUND"; private static final String FEE_TYPE_REFUND = "REFUND";
private static final int MAX_ORDER_IDENTIFIERS = 1000;
@Autowired @Autowired
@Lazy @Lazy
@@ -299,6 +301,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
refundFee.setManualFlag("0"); refundFee.setManualFlag("0");
incomeBillFeeMapper.insertPrcIncomeBillFee(refundFee); incomeBillFeeMapper.insertPrcIncomeBillFee(refundFee);
bill.setRefundStatus(BillRefundStatus.REFUNDED.getCode()); bill.setRefundStatus(BillRefundStatus.REFUNDED.getCode());
// Align with upstream 已退件 after fee refund.
bill.setOrderStatus(BillOrderStatus.REFUNDED.getCodeBoxed());
if (incomeBill != null) if (incomeBill != null)
{ {
incomeBill.setRefundStatus(BillRefundStatus.REFUNDED.getCode()); incomeBill.setRefundStatus(BillRefundStatus.REFUNDED.getCode());
@@ -314,6 +318,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
req.put("refundAmount", feeSum.negate()); req.put("refundAmount", feeSum.negate());
req.put("feeId", refundFee.getFeeId()); req.put("feeId", refundFee.getFeeId());
req.put("refundStatus", bill.getRefundStatus()); req.put("refundStatus", bill.getRefundStatus());
req.put("orderStatus", bill.getOrderStatus());
if (billSearchStore != null) if (billSearchStore != null)
{ {
billSearchStore.save(EsLogType.BILL, billSearchStore.save(EsLogType.BILL,
@@ -343,6 +348,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setProductCodes(normalizeCodeList(query.getProductCodes())); query.setProductCodes(normalizeCodeList(query.getProductCodes()));
query.setTrackingNos(normalizeCodeList(query.getTrackingNos())); query.setTrackingNos(normalizeCodeList(query.getTrackingNos()));
query.setTransferNos(normalizeCodeList(query.getTransferNos())); query.setTransferNos(normalizeCodeList(query.getTransferNos()));
query.setOrderIdentifiers(normalizeOrderIdentifiers(query.getOrderIdentifiers()));
return orderBillMapper.selectPrcOrderBillList(query); return orderBillMapper.selectPrcOrderBillList(query);
} }
@@ -401,6 +407,17 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
return cleaned.isEmpty() ? null : cleaned; return cleaned.isEmpty() ? null : cleaned;
} }
/** 单号筛选按去重后的订单号/运单号/转单号数量限制,避免过大的 IN 参数。 */
private List<String> normalizeOrderIdentifiers(List<String> identifiers)
{
List<String> cleaned = normalizeCodeList(identifiers);
if (cleaned != null && cleaned.size() > MAX_ORDER_IDENTIFIERS)
{
throw new ServiceException("单号去重后最多输入 " + MAX_ORDER_IDENTIFIERS + " 个");
}
return cleaned;
}
@Override @Override
public TmsPriceQuoteResult getBill(String billId) public TmsPriceQuoteResult getBill(String billId)
{ {
@@ -653,7 +670,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
List<PrcOrderBill> bills = resolveListScopedBills(request.getScopeMode(), request.getBillIds(), List<PrcOrderBill> bills = resolveListScopedBills(request.getScopeMode(), request.getBillIds(),
request.getOrderNos(), request.getTrackingNos(), request.getTransferNos(), request.getOrderIdentifiers(), request.getOrderNos(), request.getTrackingNos(), request.getTransferNos(), request.getOrderIdentifiers(),
request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(), request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(),
request.getCreditArrearsStatus(), request.getBeginBizDate(), request.getEndBizDate(), request.getCreditArrearsStatus(), request.getBillingFailStatuses(),
request.getBeginBizDate(), request.getEndBizDate(),
"price.bill.recalc.filterRequired", "price.bill.recalc.scopeRequired", "price.bill.recalc.filterRequired", "price.bill.recalc.scopeRequired",
"price.bill.recalc.selectedRequired"); "price.bill.recalc.selectedRequired");
if (bills.isEmpty()) if (bills.isEmpty())
@@ -748,6 +766,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
{ {
throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired")); throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired"));
} }
assertOrderBillNotRefunded(bill);
TmsPriceBillRequest req = buildBillRequestFromBill(bill); TmsPriceBillRequest req = buildBillRequestFromBill(bill);
return billLocalWithDbRetry(billId.trim(), req); return billLocalWithDbRetry(billId.trim(), req);
} }
@@ -979,7 +998,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
List<String> orderNos = normalizeCodeList(request.getOrderNos()); List<String> orderNos = normalizeCodeList(request.getOrderNos());
List<String> trackingNos = normalizeCodeList(request.getTrackingNos()); List<String> trackingNos = normalizeCodeList(request.getTrackingNos());
List<String> transferNos = normalizeCodeList(request.getTransferNos()); List<String> transferNos = normalizeCodeList(request.getTransferNos());
List<String> orderIdentifiers = normalizeCodeList(request.getOrderIdentifiers()); List<String> orderIdentifiers = normalizeOrderIdentifiers(request.getOrderIdentifiers());
List<String> customerCodes = normalizeCodeList(request.getCustomerCodes()); List<String> customerCodes = normalizeCodeList(request.getCustomerCodes());
List<String> productCodes = normalizeCodeList(request.getProductCodes()); List<String> productCodes = normalizeCodeList(request.getProductCodes());
String creditArrearsStatus = StringUtils.isEmpty(request.getCreditArrearsStatus()) ? null String creditArrearsStatus = StringUtils.isEmpty(request.getCreditArrearsStatus()) ? null
@@ -1030,7 +1049,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setProductCodes(normalizeCodeList(query.getProductCodes())); query.setProductCodes(normalizeCodeList(query.getProductCodes()));
query.setTrackingNos(normalizeCodeList(query.getTrackingNos())); query.setTrackingNos(normalizeCodeList(query.getTrackingNos()));
query.setTransferNos(normalizeCodeList(query.getTransferNos())); query.setTransferNos(normalizeCodeList(query.getTransferNos()));
query.setOrderIdentifiers(normalizeCodeList(query.getOrderIdentifiers())); query.setOrderIdentifiers(normalizeOrderIdentifiers(query.getOrderIdentifiers()));
query.setBillIds(normalizeCodeList(query.getBillIds())); query.setBillIds(normalizeCodeList(query.getBillIds()));
} }
@@ -1126,7 +1145,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
return resolveListScopedBills(request.getScopeMode(), request.getBillIds(), return resolveListScopedBills(request.getScopeMode(), request.getBillIds(),
request.getOrderNos(), request.getTrackingNos(), request.getTransferNos(), request.getOrderIdentifiers(), request.getOrderNos(), request.getTrackingNos(), request.getTransferNos(), request.getOrderIdentifiers(),
request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(), request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(),
request.getCreditArrearsStatus(), request.getBeginBizDate(), request.getEndBizDate(), request.getCreditArrearsStatus(), request.getBillingFailStatuses(),
request.getBeginBizDate(), request.getEndBizDate(),
"price.bill.fee.batch.filterRequired", "price.bill.fee.batch.scopeRequired", "price.bill.fee.batch.filterRequired", "price.bill.fee.batch.scopeRequired",
"price.bill.fee.batch.selectedRequired"); "price.bill.fee.batch.selectedRequired");
} }
@@ -1138,7 +1158,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
private List<PrcOrderBill> resolveListScopedBills(String scopeModeRaw, List<String> billIdsRaw, private List<PrcOrderBill> resolveListScopedBills(String scopeModeRaw, List<String> billIdsRaw,
List<String> orderNosRaw, List<String> trackingNosRaw, List<String> transferNosRaw, List<String> orderIdentifiersRaw, List<String> orderNosRaw, List<String> trackingNosRaw, List<String> transferNosRaw, List<String> orderIdentifiersRaw,
List<String> customerCodesRaw, List<String> productCodesRaw, String countryCodeRaw, List<String> customerCodesRaw, List<String> productCodesRaw, String countryCodeRaw,
String creditArrearsStatusRaw, String beginBizDateRaw, String endBizDateRaw, String creditArrearsStatusRaw, List<Integer> billingFailStatusesRaw,
String beginBizDateRaw, String endBizDateRaw,
String msgFilter, String msgScope, String msgSelected) String msgFilter, String msgScope, String msgSelected)
{ {
String scopeMode = StringUtils.nvl(scopeModeRaw, "").trim().toUpperCase(); String scopeMode = StringUtils.nvl(scopeModeRaw, "").trim().toUpperCase();
@@ -1149,9 +1170,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
List<String> orderNos = normalizeCodeList(orderNosRaw); List<String> orderNos = normalizeCodeList(orderNosRaw);
List<String> trackingNos = normalizeCodeList(trackingNosRaw); List<String> trackingNos = normalizeCodeList(trackingNosRaw);
List<String> transferNos = normalizeCodeList(transferNosRaw); List<String> transferNos = normalizeCodeList(transferNosRaw);
List<String> orderIdentifiers = normalizeCodeList(orderIdentifiersRaw); List<String> orderIdentifiers = normalizeOrderIdentifiers(orderIdentifiersRaw);
List<String> customerCodes = normalizeCodeList(customerCodesRaw); List<String> customerCodes = normalizeCodeList(customerCodesRaw);
List<String> productCodes = normalizeCodeList(productCodesRaw); List<String> productCodes = normalizeCodeList(productCodesRaw);
List<Integer> billingFailStatuses = normalizeIntegerList(billingFailStatusesRaw);
String creditArrearsStatus = StringUtils.isEmpty(creditArrearsStatusRaw) ? null String creditArrearsStatus = StringUtils.isEmpty(creditArrearsStatusRaw) ? null
: creditArrearsStatusRaw.trim(); : creditArrearsStatusRaw.trim();
String countryCode = StringUtils.isEmpty(countryCodeRaw) ? null String countryCode = StringUtils.isEmpty(countryCodeRaw) ? null
@@ -1164,6 +1186,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|| (orderIdentifiers != null && !orderIdentifiers.isEmpty()) || (orderIdentifiers != null && !orderIdentifiers.isEmpty())
|| (customerCodes != null && !customerCodes.isEmpty()) || (customerCodes != null && !customerCodes.isEmpty())
|| (productCodes != null && !productCodes.isEmpty()) || (productCodes != null && !productCodes.isEmpty())
|| (billingFailStatuses != null && !billingFailStatuses.isEmpty())
|| StringUtils.isNotEmpty(creditArrearsStatus) || StringUtils.isNotEmpty(creditArrearsStatus)
|| StringUtils.isNotEmpty(countryCode) || StringUtils.isNotEmpty(countryCode)
|| StringUtils.isNotEmpty(beginBizDate) || StringUtils.isNotEmpty(beginBizDate)
@@ -1191,6 +1214,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setCustomerCodes(customerCodes); query.setCustomerCodes(customerCodes);
query.setProductCodes(productCodes); query.setProductCodes(productCodes);
query.setCreditArrearsStatus(creditArrearsStatus); query.setCreditArrearsStatus(creditArrearsStatus);
query.setBillingFailStatuses(billingFailStatuses);
query.setCountryCode(countryCode); query.setCountryCode(countryCode);
if (beginBizDate != null) if (beginBizDate != null)
{ {
@@ -1205,6 +1229,23 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
return list == null ? new ArrayList<PrcOrderBill>() : list; return list == null ? new ArrayList<PrcOrderBill>() : list;
} }
private List<Integer> normalizeIntegerList(List<Integer> values)
{
if (values == null || values.isEmpty())
{
return null;
}
List<Integer> out = new ArrayList<Integer>();
for (Integer v : values)
{
if (v != null)
{
out.add(v);
}
}
return out.isEmpty() ? null : out;
}
private PrcSurcharge requireActiveSurcharge(Long scId) private PrcSurcharge requireActiveSurcharge(Long scId)
{ {
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId); PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
@@ -1595,6 +1636,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
String billKey = Md5Utils.hash(orderNo); String billKey = Md5Utils.hash(orderNo);
PrcOrderBill existed = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo); PrcOrderBill existed = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo);
assertOrderBillEditable(existed); assertOrderBillEditable(existed);
assertOrderBillNotRefunded(existed);
BigDecimal previousIncome = existed == null ? BigDecimal.ZERO : nz(existed.getIncomeTotal()); BigDecimal previousIncome = existed == null ? BigDecimal.ZERO : nz(existed.getIncomeTotal());
PrcOrderBill bill = existed == null ? new PrcOrderBill() : existed; PrcOrderBill bill = existed == null ? new PrcOrderBill() : existed;
if (existed == null) if (existed == null)
@@ -2051,6 +2093,15 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
} }
} }
/** Block rebill when refundStatus=REFUNDED (order_status=9 alone is logistics return, not fee refund). */
private void assertOrderBillNotRefunded(PrcOrderBill bill)
{
if (bill != null && BillRefundStatus.REFUNDED.getCode().equalsIgnoreCase(bill.getRefundStatus()))
{
throw new ServiceException("\u8ba2\u5355\u5df2\u9000\u8d39\uff0c\u4e0d\u53ef\u91cd\u65b0\u8ba1\u8d39");
}
}
private static BigDecimal nz(BigDecimal v) private static BigDecimal nz(BigDecimal v)
{ {
return v == null ? BigDecimal.ZERO : v; return v == null ? BigDecimal.ZERO : v;
@@ -2577,7 +2628,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
boolean hasFilter = (orderNos != null && !orderNos.isEmpty()) boolean hasFilter = (orderNos != null && !orderNos.isEmpty())
|| (trackingNos != null && !trackingNos.isEmpty()) || (trackingNos != null && !trackingNos.isEmpty())
|| (transferNos != null && !transferNos.isEmpty()) || (transferNos != null && !transferNos.isEmpty())
|| (request.getOrderIdentifiers() != null && !normalizeCodeList(request.getOrderIdentifiers()).isEmpty()) || (request.getOrderIdentifiers() != null && !normalizeOrderIdentifiers(request.getOrderIdentifiers()).isEmpty())
|| (customerCodes != null && !customerCodes.isEmpty()) || (customerCodes != null && !customerCodes.isEmpty())
|| (productCodes != null && !productCodes.isEmpty()) || (productCodes != null && !productCodes.isEmpty())
|| StringUtils.isNotEmpty(creditArrearsStatus) || StringUtils.isNotEmpty(creditArrearsStatus)
@@ -2591,7 +2642,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setOrderNos(orderNos); query.setOrderNos(orderNos);
query.setTrackingNos(trackingNos); query.setTrackingNos(trackingNos);
query.setTransferNos(transferNos); query.setTransferNos(transferNos);
query.setOrderIdentifiers(normalizeCodeList(request.getOrderIdentifiers())); query.setOrderIdentifiers(normalizeOrderIdentifiers(request.getOrderIdentifiers()));
query.setCustomerCodes(customerCodes); query.setCustomerCodes(customerCodes);
query.setProductCodes(productCodes); query.setProductCodes(productCodes);
query.setCreditArrearsStatus(creditArrearsStatus); query.setCreditArrearsStatus(creditArrearsStatus);
@@ -2895,7 +2946,12 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
{ {
return; return;
} }
if (request.getOrderStatus() != null) // Fee refund marks order_status=9 (已退件); do not let Oracle sync overwrite it.
if (BillRefundStatus.REFUNDED.getCode().equalsIgnoreCase(bill.getRefundStatus()))
{
bill.setOrderStatus(BillOrderStatus.REFUNDED.getCodeBoxed());
}
else if (request.getOrderStatus() != null)
{ {
bill.setOrderStatus(request.getOrderStatus()); bill.setOrderStatus(request.getOrderStatus());
} }
@@ -27,7 +27,9 @@ import com.bms.price.domain.PrcBillingTaskRetryTask;
import com.bms.price.domain.vo.BillingTaskSubmitRequest; import com.bms.price.domain.vo.BillingTaskSubmitRequest;
import com.bms.price.domain.vo.TabOrderMonitorQuery; import com.bms.price.domain.vo.TabOrderMonitorQuery;
import com.bms.price.domain.vo.TabOrderMonitorVo; import com.bms.price.domain.vo.TabOrderMonitorVo;
import com.bms.price.enums.BillOrderStatus;
import com.bms.price.enums.BillTaskSuccessStatus; import com.bms.price.enums.BillTaskSuccessStatus;
import com.bms.price.enums.BillTaskType;
import com.bms.price.oracle.TabOrderMonitorService; import com.bms.price.oracle.TabOrderMonitorService;
import com.bms.price.search.IBillSearchStore; import com.bms.price.search.IBillSearchStore;
import com.bms.price.search.TabOrderSyncEsLog; import com.bms.price.search.TabOrderSyncEsLog;
@@ -200,7 +202,7 @@ public class TabOrderSyncAsyncRunner
/** /**
* Sync or refund selected rows. * Sync or refund selected rows.
* @param billType optional PENDING/REBILL/REFUND; null = auto PENDING/REBILL * @param billType optional PENDING/REBILL/REFUND; null = auto by orderStatus / bill existence
*/ */
public Map<String, Object> syncRowsParallel(List<TabOrderMonitorVo> rows, String billType) public Map<String, Object> syncRowsParallel(List<TabOrderMonitorVo> rows, String billType)
{ {
@@ -219,7 +221,7 @@ public class TabOrderSyncAsyncRunner
/** /**
* Sync or refund selected rows carrying the upstream Oracle full row (skip re-query). * Sync or refund selected rows carrying the upstream Oracle full row (skip re-query).
* @param billType optional PENDING/REBILL/REFUND; null = auto PENDING/REBILL * @param billType optional PENDING/REBILL/REFUND; null = auto by orderStatus / bill existence
*/ */
public Map<String, Object> syncRowsParallelRows(List<com.bms.price.oracle.TabOrderRow> rows, String billType) public Map<String, Object> syncRowsParallelRows(List<com.bms.price.oracle.TabOrderRow> rows, String billType)
{ {
@@ -509,6 +511,11 @@ public class TabOrderSyncAsyncRunner
{ {
req.setBillType(billType.trim()); req.setBillType(billType.trim());
} }
else if (BillOrderStatus.REFUNDED.matches(row.getOrderStatus()))
{
// Upstream 已退件 (ORDER_STATUS=9) -> fee refund task
req.setBillType(BillTaskType.REFUND.getCode());
}
return req; return req;
} }
} }
@@ -67,7 +67,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND o.PICKUP_DATE &lt;= TO_DATE(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS') AND o.PICKUP_DATE &lt;= TO_DATE(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS')
</if> </if>
AND o.BL_PICKUP = 1 AND o.BL_PICKUP = 1
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11) <if test="query.orderStatuses != null and query.orderStatuses.size() > 0">
AND o.ORDER_STATUS IN
<foreach collection="query.orderStatuses" item="st" open="(" separator="," close=")">
#{st}
</foreach>
</if>
<if test="query.orderStatuses == null or query.orderStatuses.size() == 0">
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11)
</if>
<if test="query.orderId != null and query.orderId != ''"> <if test="query.orderId != null and query.orderId != ''">
AND o.ORDER_ID = #{query.orderId} AND o.ORDER_ID = #{query.orderId}
</if> </if>
@@ -213,6 +213,7 @@ function submit(skipConfirm) {
orderId: q.orderId || undefined, orderId: q.orderId || undefined,
orgOrderId: q.orgOrderId || undefined, orgOrderId: q.orgOrderId || undefined,
transferOrderId: q.transferOrderId || undefined, transferOrderId: q.transferOrderId || undefined,
orderStatuses: Array.isArray(q.orderStatuses) && q.orderStatuses.length ? q.orderStatuses : undefined,
batchSize: 100 batchSize: 100
}).then(res => { }).then(res => {
const asyncTaskId = res.data?.asyncTaskId || res.data const asyncTaskId = res.data?.asyncTaskId || res.data
@@ -18,11 +18,11 @@
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:search-columns="searchColumns" :search-columns="searchColumns"
:default-params="defaultParams"
:query="queryList" :query="queryList"
row-key="orderId" row-key="orderId"
:auto-load="false" :auto-load="false"
selection selection
search-action-align="center"
:search-per-row="4" :search-per-row="4"
:page-sizes="[100, 200, 300, 400]" :page-sizes="[100, 200, 300, 400]"
:page-size="100" :page-size="100"
@@ -83,6 +83,31 @@
<template #col-country="{ row }"> <template #col-country="{ row }">
{{ row.countryName || '-' }} ({{ row.country || '-' }}) {{ row.countryName || '-' }} ({{ row.country || '-' }})
</template> </template>
<template #search-orderStatuses="{ model }">
<el-select
v-model="model.orderStatuses"
multiple
clearable
collapse-tags
collapse-tags-tooltip
placeholder="请选择"
style="width: 100%"
>
<template #header>
<el-checkbox
:model-value="isAllOrderStatusSelected(model)"
:indeterminate="isOrderStatusIndeterminate(model)"
@change="(checked) => toggleAllOrderStatus(model, checked)"
>全选</el-checkbox>
</template>
<el-option
v-for="opt in orderStatusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</template>
<template #col-orderStatus="{ row }"> <template #col-orderStatus="{ row }">
{{ orderStatusLabel(row.orderStatus) }} {{ orderStatusLabel(row.orderStatus) }}
</template> </template>
@@ -130,6 +155,27 @@ const syncFloat = ref(null)
/** 比对回填搜索模式:走专用转单号接口,不影响普通列表 */ /** 比对回填搜索模式:走专用转单号接口,不影响普通列表 */
const transferFillMode = ref(false) const transferFillMode = ref(false)
const orderStatusMap = {
0: '预报订单',
1: '确认订单',
2: '已中转',
3: '已收货',
4: '已装箱',
5: '已出库',
6: '转运中',
7: '已上网',
8: '已签收',
9: '已退件',
10: '已删除',
11: '已复核'
}
const orderStatusOptions = Object.keys(orderStatusMap).map(k => ({ label: orderStatusMap[k], value: Number(k) }))
const defaultParams = {
orderStatuses: [3, 4, 5, 6, 7, 8, 11]
}
const searchColumns = [ const searchColumns = [
{ {
// 客户名称或客户代码混合录入,一行一个 // 客户名称或客户代码混合录入,一行一个
@@ -164,7 +210,20 @@ const searchColumns = [
clearable: true, clearable: true,
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)] defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)]
} }
} },
{
label: '订单状态',
prop: 'orderStatuses',
component: 'select',
options: orderStatusOptions,
placeholder: '请选择',
props: {
multiple: true,
clearable: true,
collapseTags: true,
collapseTagsTooltip: true
}
},
] ]
const columns = [ const columns = [
@@ -186,21 +245,6 @@ const columns = [
// { label: '操作', prop: 'action', align: 'center', width: 120, fixed: 'right', slot: 'action', showInToolbar: false } // { label: '操作', prop: 'action', align: 'center', width: 120, fixed: 'right', slot: 'action', showInToolbar: false }
] ]
const orderStatusMap = {
0: '预报订单',
1: '确认订单',
2: '已中转',
3: '已收货',
4: '已装箱',
5: '已出库',
6: '转运中',
7: '已上网',
8: '已签收',
9: '已退件',
10: '已删除',
11: '已复核'
}
const syncFloatVisible = computed(() => { const syncFloatVisible = computed(() => {
const p = syncFloat.value const p = syncFloat.value
return !!(p && (p.loading || p.finished)) return !!(p && (p.loading || p.finished))
@@ -220,6 +264,30 @@ const syncFloatSummary = computed(() => {
}) })
const syncFloatTooltip = computed(() => syncFloat.value?.message || '点击查看详情') const syncFloatTooltip = computed(() => syncFloat.value?.message || '点击查看详情')
const orderStatusAllValues = computed(() => orderStatusOptions.map(o => o.value))
function selectedOrderStatuses(model) {
return Array.isArray(model?.orderStatuses) ? model.orderStatuses : []
}
function isAllOrderStatusSelected(model) {
const selected = selectedOrderStatuses(model)
const all = orderStatusAllValues.value
return all.length > 0 && selected.length === all.length
}
function isOrderStatusIndeterminate(model) {
const selected = selectedOrderStatuses(model)
const all = orderStatusAllValues.value
return selected.length > 0 && selected.length < all.length
}
function toggleAllOrderStatus(model, checked) {
if (!model) return
model.orderStatuses = checked ? [...orderStatusAllValues.value] : []
}
function orderStatusLabel(status) { function orderStatusLabel(status) {
if (status === null || status === undefined || status === '') return '-' if (status === null || status === undefined || status === '') return '-'
const key = Number(status) const key = Number(status)
@@ -388,7 +456,8 @@ function toSyncRow(row) {
orgOrderId: row.orgOrderId, orgOrderId: row.orgOrderId,
transferOrderId: row.transferOrderId, transferOrderId: row.transferOrderId,
customerCode: row.customerCode, customerCode: row.customerCode,
pickupDate: row.pickupDate pickupDate: row.pickupDate,
orderStatus: row.orderStatus
} }
} }
+54 -20
View File
@@ -14,12 +14,16 @@
virtual virtual
> >
<template #search-orderIdentifiers="{ model }"> <template #search-orderIdentifiers="{ model }">
<multi-value-input <div class="order-identifier-search">
v-model="model.orderIdentifiers" <el-input
placeholder="订单号/运单号/转单号" v-model="model.orderIdentifiers"
width="100%" type="textarea"
:search-method="searchOrderIdentifiers" :rows="1"
/> resize="none"
placeholder="请输入订单号、运单号或转单号"
clearable
/>
</div>
</template> </template>
<template #search-customerCodes="{ model }"> <template #search-customerCodes="{ model }">
<customer-select <customer-select
@@ -234,7 +238,7 @@
</template> </template>
<script setup name="BillingBill"> <script setup name="BillingBill">
import { listBill, getBill, refundBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions, updateCreditArrears, importChargeWeightAsync, downloadChargeWeightTemplate, recalcBillPriceAsync } from '@/api/price/billing' import { listBill, getBill, refundBill, updateCreditArrears, importChargeWeightAsync, downloadChargeWeightTemplate, recalcBillPriceAsync } from '@/api/price/billing'
import { exportAsyncFromTable } from '@/utils/excelExport' import { exportAsyncFromTable } from '@/utils/excelExport'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { useProducts } from '@/components/ProductSelect/useProducts' import { useProducts } from '@/components/ProductSelect/useProducts'
@@ -244,13 +248,27 @@ import EditChargeWeightDialog from './components/EditChargeWeightDialog.vue'
import ExcelImportDialog from '@/components/ExcelImportDialog' import ExcelImportDialog from '@/components/ExcelImportDialog'
import ChargeWeightImportAttachDrawer from './components/ChargeWeightImportAttachDrawer.vue' import ChargeWeightImportAttachDrawer from './components/ChargeWeightImportAttachDrawer.vue'
// 单号下拉:订单号、运单号、转单号合并候选 const MAX_ORDER_IDENTIFIERS = 1000
function searchOrderIdentifiers(keyword) {
return Promise.all([ function parseOrderIdentifiers(raw) {
listOrderNoOptions(keyword).then(res => res.data || []), const values = Array.isArray(raw) ? raw : [raw]
listTrackingNoOptions(keyword).then(res => res.data || []), const identifiers = values
listTransferNoOptions(keyword).then(res => res.data || []) .filter(v => v !== null && v !== undefined)
]).then(groups => [...new Set(groups.flat().map(v => String(v)))]) .flatMap(v => String(v).split(/[\s,,;;]+/))
.map(v => v.trim())
.filter(Boolean)
return [...new Set(identifiers)]
}
function getUniqueOrderIdentifierCount(raw) {
return parseOrderIdentifiers(raw).length
}
function validateOrderIdentifierLimit(raw) {
const count = getUniqueOrderIdentifierCount(raw)
if (count <= MAX_ORDER_IDENTIFIERS) return true
ElMessage.warning(`单号去重后最多输入 ${MAX_ORDER_IDENTIFIERS} 个,当前 ${count} 个`)
return false
} }
// 产品下拉选项:复用 ProductSelect 的模块级产品缓存(全应用只拉一次) // 产品下拉选项:复用 ProductSelect 的模块级产品缓存(全应用只拉一次)
@@ -402,8 +420,10 @@ const searchColumns = [
] ]
function buildBillQuery(params) { function buildBillQuery(params) {
const { bizDateRange, ...rest } = params || {} const { bizDateRange, orderIdentifiers: rawOrderIdentifiers, ...rest } = params || {}
const query = { ...rest } const query = { ...rest }
const orderIdentifiers = parseOrderIdentifiers(rawOrderIdentifiers)
if (orderIdentifiers.length) query.orderIdentifiers = orderIdentifiers
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) { if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
query.params = { query.params = {
...(query.params || {}), ...(query.params || {}),
@@ -415,11 +435,16 @@ function buildBillQuery(params) {
} }
function queryList(params) { function queryList(params) {
if (!validateOrderIdentifierLimit(params?.orderIdentifiers)) {
return Promise.reject(new Error('order identifier limit exceeded'))
}
const query = buildBillQuery(params) const query = buildBillQuery(params)
return listBill(query) return listBill(query)
} }
function handleExport() { function handleExport() {
const params = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(params.orderIdentifiers)) return
return exportAsyncFromTable({ return exportAsyncFromTable({
baseUrl: '/price/billing/bill/export', baseUrl: '/price/billing/bill/export',
tableRef: tableRef.value, tableRef: tableRef.value,
@@ -491,7 +516,7 @@ function onDetailUpdated(data) {
function hasBatchFeeQuery(q) { function hasBatchFeeQuery(q) {
const hasArr = (arr) => Array.isArray(arr) && arr.some(v => v != null && String(v).trim() !== '') const hasArr = (arr) => Array.isArray(arr) && arr.some(v => v != null && String(v).trim() !== '')
return hasArr(q.orderIdentifiers) return parseOrderIdentifiers(q.orderIdentifiers).length > 0
|| hasArr(q.orderNos) || hasArr(q.orderNos)
|| hasArr(q.trackingNos) || hasArr(q.trackingNos)
|| hasArr(q.transferNos) || hasArr(q.transferNos)
@@ -504,12 +529,16 @@ function hasBatchFeeQuery(q) {
function openBatchFee() { function openBatchFee() {
const q = tableRef.value?.getQueryParams?.() || {} const q = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return
const { pageNum, pageSize, bizDateRange, ...rest } = q const { pageNum, pageSize, bizDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange })) { if (!hasBatchFeeQuery({ ...rest, bizDateRange })) {
ElMessage.warning('请先设置列表查询条件') ElMessage.warning('请先设置列表查询条件')
return return
} }
const listQuery = { ...rest } const listQuery = {
...rest,
orderIdentifiers: parseOrderIdentifiers(rest.orderIdentifiers)
}
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) { if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
listQuery.beginBizDate = bizDateRange[0] || null listQuery.beginBizDate = bizDateRange[0] || null
listQuery.endBizDate = bizDateRange[1] || null listQuery.endBizDate = bizDateRange[1] || null
@@ -525,6 +554,7 @@ function onBatchFeeSuccess() {
function buildRecalcPayload() { function buildRecalcPayload() {
const q = tableRef.value?.getQueryParams?.() || {} const q = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return null
const { pageNum, pageSize, bizDateRange, ...rest } = q const { pageNum, pageSize, bizDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange })) { if (!hasBatchFeeQuery({ ...rest, bizDateRange })) {
ElMessage.warning('请先设置列表查询条件') ElMessage.warning('请先设置列表查询条件')
@@ -537,10 +567,11 @@ function buildRecalcPayload() {
orderNos: rest.orderNos || [], orderNos: rest.orderNos || [],
trackingNos: rest.trackingNos || [], trackingNos: rest.trackingNos || [],
transferNos: rest.transferNos || [], transferNos: rest.transferNos || [],
orderIdentifiers: rest.orderIdentifiers || [], orderIdentifiers: parseOrderIdentifiers(rest.orderIdentifiers),
customerCodes: rest.customerCodes || [], customerCodes: rest.customerCodes || [],
productCodes: rest.productCodes || [], productCodes: rest.productCodes || [],
creditArrearsStatus: rest.creditArrearsStatus || undefined creditArrearsStatus: rest.creditArrearsStatus || undefined,
billingFailStatuses: Array.isArray(rest.billingFailStatuses) ? rest.billingFailStatuses : []
} }
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) { if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
payload.beginBizDate = bizDateRange[0] || null payload.beginBizDate = bizDateRange[0] || null
@@ -628,7 +659,7 @@ function buildChargeWeightTemplateQuery() {
orderNos: rest.orderNos || [], orderNos: rest.orderNos || [],
trackingNos: rest.trackingNos || [], trackingNos: rest.trackingNos || [],
transferNos: rest.transferNos || [], transferNos: rest.transferNos || [],
orderIdentifiers: rest.orderIdentifiers || [], orderIdentifiers: parseOrderIdentifiers(rest.orderIdentifiers),
customerCodes: rest.customerCodes || [], customerCodes: rest.customerCodes || [],
productCodes: rest.productCodes || [], productCodes: rest.productCodes || [],
creditArrearsStatus: rest.creditArrearsStatus || undefined creditArrearsStatus: rest.creditArrearsStatus || undefined
@@ -641,6 +672,8 @@ function buildChargeWeightTemplateQuery() {
} }
async function downloadChargeWeightImportTemplate() { async function downloadChargeWeightImportTemplate() {
const currentQuery = tableRef.value?.getQueryParams?.() || {}
if (cwFillByFilter.value && !validateOrderIdentifierLimit(currentQuery.orderIdentifiers)) return
const payload = buildChargeWeightTemplateQuery() const payload = buildChargeWeightTemplateQuery()
// Checked: backend fills orderNos from list filters. Unchecked: OSS template CHARGE_WEIGHT. // Checked: backend fills orderNos from list filters. Unchecked: OSS template CHARGE_WEIGHT.
if (payload.fillByFilter) { if (payload.fillByFilter) {
@@ -842,4 +875,5 @@ function refundTagType(status) {
.billing-bill :deep(.basic-form .el-form-item .el-date-editor) { .billing-bill :deep(.basic-form .el-form-item .el-date-editor) {
width: 100%; width: 100%;
} }
.order-identifier-search { width: 100%; }
</style> </style>
@@ -211,12 +211,28 @@ function buildQueryTags(q) {
: (q.creditArrearsStatus === 'NORMAL' ? "正常" : q.creditArrearsStatus) : (q.creditArrearsStatus === 'NORMAL' ? "正常" : q.creditArrearsStatus)
tags.push("额度欠费" + ':' + label) tags.push("额度欠费" + ':' + label)
} }
const failStatuses = Array.isArray(q.billingFailStatuses) ? q.billingFailStatuses.filter(v => v != null) : []
if (failStatuses.length) {
const labels = failStatuses.map(v => billingFailStatusLabel(v))
tags.push("计费失败" + ':' + labels.join(', '))
}
if (q.beginBizDate || q.endBizDate) { if (q.beginBizDate || q.endBizDate) {
tags.push("计费时间:" + (q.beginBizDate || '…') + ' ~ ' + (q.endBizDate || '…')) tags.push("计费时间:" + (q.beginBizDate || '…') + ' ~ ' + (q.endBizDate || '…'))
} }
return tags return tags
} }
function billingFailStatusLabel(code) {
const map = {
0: '计费成功',
1: '未匹配产品',
2: '未匹配国家',
3: '未匹配重量段'
}
const key = Number(code)
return map[key] != null ? map[key] : String(code)
}
function loadSurcharges() { function loadSurcharges() {
return listSurchargeOptions({ ssCode: 'on' }).then(res => { return listSurchargeOptions({ ssCode: 'on' }).then(res => {
surchargeOptions.value = res.data || [] surchargeOptions.value = res.data || []
@@ -262,6 +278,7 @@ function buildPayload() {
customerCodes: q.customerCodes || [], customerCodes: q.customerCodes || [],
productCodes: q.productCodes || [], productCodes: q.productCodes || [],
creditArrearsStatus: q.creditArrearsStatus || undefined, creditArrearsStatus: q.creditArrearsStatus || undefined,
billingFailStatuses: Array.isArray(q.billingFailStatuses) ? q.billingFailStatuses : [],
beginBizDate: q.beginBizDate || undefined, beginBizDate: q.beginBizDate || undefined,
endBizDate: q.endBizDate || undefined endBizDate: q.endBizDate || undefined
} }
+45 -20
View File
@@ -60,12 +60,16 @@
</el-col> </el-col>
</template> </template>
<template #search-orderIdentifiers="{ model }"> <template #search-orderIdentifiers="{ model }">
<multi-value-input <div class="order-identifier-search">
v-model="model.orderIdentifiers" <el-input
placeholder="订单号/运单号/转单号" v-model="model.orderIdentifiers"
width="100%" type="textarea"
:search-method="searchOrderIdentifiers" :rows="1"
/> resize="none"
placeholder="请输入订单号、运单号或转单号"
clearable
/>
</div>
</template> </template>
<template #search-customerCodes="{ model }"> <template #search-customerCodes="{ model }">
<customer-select <customer-select
@@ -163,7 +167,7 @@
<script setup name="OrderBillQuery"> <script setup name="OrderBillQuery">
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { listOrderBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions, localRebillAsync } from '@/api/price/billing' import { listOrderBill, localRebillAsync } from '@/api/price/billing'
import { exportAsyncFromTable } from '@/utils/excelExport' import { exportAsyncFromTable } from '@/utils/excelExport'
import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue' import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue' import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
@@ -182,6 +186,7 @@ const rebillProgressSuccess = ref(0)
const rebillProgressFail = ref(0) const rebillProgressFail = ref(0)
const tableHeight = ref(480) const tableHeight = ref(480)
let tableResizeObserver = null let tableResizeObserver = null
const MAX_ORDER_IDENTIFIERS = 1000
const defaultParams = { const defaultParams = {
orderStatuses: [3, 4, 5, 6, 7, 8, 11], orderStatuses: [3, 4, 5, 6, 7, 8, 11],
@@ -330,8 +335,10 @@ const searchColumns = [
] ]
function applySearchRanges(params) { function applySearchRanges(params) {
const { bizDateRange, pickupDateRange, ...rest } = params const { bizDateRange, pickupDateRange, orderIdentifiers: rawOrderIdentifiers, ...rest } = params || {}
const query = { ...rest } const query = { ...rest }
const orderIdentifiers = parseOrderIdentifiers(rawOrderIdentifiers)
if (orderIdentifiers.length) query.orderIdentifiers = orderIdentifiers
const rangeParams = {} const rangeParams = {}
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) { if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
rangeParams.beginBizDate = bizDateRange[0] || null rangeParams.beginBizDate = bizDateRange[0] || null
@@ -347,11 +354,36 @@ function applySearchRanges(params) {
return query return query
} }
function parseOrderIdentifiers(raw) {
const values = Array.isArray(raw) ? raw : [raw]
const identifiers = values
.filter(v => v !== null && v !== undefined)
.flatMap(v => String(v).split(/[\s,,;;]+/))
.map(v => v.trim())
.filter(Boolean)
return [...new Set(identifiers)]
}
function getUniqueOrderIdentifierCount(raw) {
return parseOrderIdentifiers(raw).length
}
function validateOrderIdentifierLimit(raw) {
const count = getUniqueOrderIdentifierCount(raw)
if (count <= MAX_ORDER_IDENTIFIERS) return true
ElMessage.warning(`单号去重后最多输入 ${MAX_ORDER_IDENTIFIERS} 个,当前 ${count} 个`)
return false
}
function queryList(params) { function queryList(params) {
return listOrderBill(applySearchRanges(params)) const query = applySearchRanges(params)
if (!validateOrderIdentifierLimit(query.orderIdentifiers)) return Promise.reject(new Error('order identifier limit exceeded'))
return listOrderBill(query)
} }
function handleExport() { function handleExport() {
const params = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(params.orderIdentifiers)) return
return exportAsyncFromTable({ return exportAsyncFromTable({
baseUrl: '/price/billing/orderBill/export', baseUrl: '/price/billing/orderBill/export',
tableRef: tableRef.value, tableRef: tableRef.value,
@@ -365,9 +397,7 @@ const MAX_REBILL = 10000
function currentIdentifiers() { function currentIdentifiers() {
const q = tableRef.value?.getQueryParams?.() || {} const q = tableRef.value?.getQueryParams?.() || {}
return (Array.isArray(q.orderIdentifiers) ? q.orderIdentifiers : []) return parseOrderIdentifiers(q.orderIdentifiers)
.map(v => String(v || '').trim())
.filter(Boolean)
} }
function buildRebillFilter() { function buildRebillFilter() {
@@ -400,6 +430,7 @@ function buildCheckQuery() {
function handleCheckProducts() { function handleCheckProducts() {
const q = buildCheckQuery() const q = buildCheckQuery()
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return
const hasCustomer = Array.isArray(q.customerCodes) && q.customerCodes.length > 0 const hasCustomer = Array.isArray(q.customerCodes) && q.customerCodes.length > 0
const begin = q.params?.beginBizDate const begin = q.params?.beginBizDate
const end = q.params?.endBizDate const end = q.params?.endBizDate
@@ -474,6 +505,7 @@ async function runLocalRebill(payload, confirmText) {
function openFilterRebill() { function openFilterRebill() {
const filter = buildRebillFilter() const filter = buildRebillFilter()
const identifiers = filter.orderIdentifiers || [] const identifiers = filter.orderIdentifiers || []
if (!validateOrderIdentifierLimit(identifiers)) return
if (!hasRebillFilter(filter)) { if (!hasRebillFilter(filter)) {
ElMessage.warning('请输入单号,或选择客户并选择计费时间后再重新计费;也可勾选订单后点重新计费选中') ElMessage.warning('请输入单号,或选择客户并选择计费时间后再重新计费;也可勾选订单后点重新计费选中')
return return
@@ -514,14 +546,6 @@ function handleBatchRebill(selection) {
) )
} }
function searchOrderIdentifiers(keyword) {
return Promise.all([
listOrderNoOptions(keyword).then(res => res.data || []),
listTrackingNoOptions(keyword).then(res => res.data || []),
listTransferNoOptions(keyword).then(res => res.data || [])
]).then(groups => [...new Set(groups.flat().map(v => String(v)))])
}
function formatWeight(val) { function formatWeight(val) {
if (val == null || val === '') return '-' if (val == null || val === '') return '-'
const num = Number(val) const num = Number(val)
@@ -581,6 +605,7 @@ onBeforeUnmount(() => {
.order-bill-query :deep(.basic-form .el-form-item .el-date-editor) { .order-bill-query :deep(.basic-form .el-form-item .el-date-editor) {
width: 100%; width: 100%;
} }
.order-identifier-search { width: 100%; }
.rebill-progress__msg { margin-top: 12px; color: var(--el-text-color-regular); } .rebill-progress__msg { margin-top: 12px; color: var(--el-text-color-regular); }
.rebill-progress__text { margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary); } .rebill-progress__text { margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary); }
.rebill-progress__hint { margin-top: 12px; font-size: 12px; color: var(--el-color-warning); } .rebill-progress__hint { margin-top: 12px; font-size: 12px; color: var(--el-color-warning); }