✨ 单号筛选改为多行输入并限制1000个

This commit is contained in:
caojia
2026-10-23 10:06:59 +08:00
parent 4ec28de073
commit fc94a15f76
4 changed files with 145 additions and 44 deletions
@@ -34,6 +34,7 @@ import com.bms.system.service.IBmsProductCodeMappingService;
public class OrderBillProductCheckService
{
private static final int ORDER_DRILL_LIMIT = 500;
private static final int MAX_ORDER_IDENTIFIERS = 1000;
@Autowired
private ITmsPriceQuoteService tmsPriceQuoteService;
@@ -225,6 +226,7 @@ public class OrderBillProductCheckService
throw new ServiceException("please select customer");
}
query.setCustomerCodes(codes);
query.setOrderIdentifiers(normalizeOrderIdentifiers(query.getOrderIdentifiers()));
Map<String, Object> params = query.getParams();
String begin = params == null ? null : strObj(params.get("beginBizDate"));
String end = params == null ? null : strObj(params.get("endBizDate"));
@@ -251,6 +253,34 @@ public class OrderBillProductCheckService
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)
{
Map<String, Object> params = query.getParams();
@@ -106,6 +106,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
private static final String DICT_FEE_TYPE = "bms_fee_type";
private static final String FEE_TYPE_REFUND = "REFUND";
private static final int MAX_ORDER_IDENTIFIERS = 1000;
@Autowired
@Lazy
@@ -343,6 +344,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setProductCodes(normalizeCodeList(query.getProductCodes()));
query.setTrackingNos(normalizeCodeList(query.getTrackingNos()));
query.setTransferNos(normalizeCodeList(query.getTransferNos()));
query.setOrderIdentifiers(normalizeOrderIdentifiers(query.getOrderIdentifiers()));
return orderBillMapper.selectPrcOrderBillList(query);
}
@@ -401,6 +403,17 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
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
public TmsPriceQuoteResult getBill(String billId)
{
@@ -979,7 +992,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
List<String> orderNos = normalizeCodeList(request.getOrderNos());
List<String> trackingNos = normalizeCodeList(request.getTrackingNos());
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> productCodes = normalizeCodeList(request.getProductCodes());
String creditArrearsStatus = StringUtils.isEmpty(request.getCreditArrearsStatus()) ? null
@@ -1030,7 +1043,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setProductCodes(normalizeCodeList(query.getProductCodes()));
query.setTrackingNos(normalizeCodeList(query.getTrackingNos()));
query.setTransferNos(normalizeCodeList(query.getTransferNos()));
query.setOrderIdentifiers(normalizeCodeList(query.getOrderIdentifiers()));
query.setOrderIdentifiers(normalizeOrderIdentifiers(query.getOrderIdentifiers()));
query.setBillIds(normalizeCodeList(query.getBillIds()));
}
@@ -1149,7 +1162,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
List<String> orderNos = normalizeCodeList(orderNosRaw);
List<String> trackingNos = normalizeCodeList(trackingNosRaw);
List<String> transferNos = normalizeCodeList(transferNosRaw);
List<String> orderIdentifiers = normalizeCodeList(orderIdentifiersRaw);
List<String> orderIdentifiers = normalizeOrderIdentifiers(orderIdentifiersRaw);
List<String> customerCodes = normalizeCodeList(customerCodesRaw);
List<String> productCodes = normalizeCodeList(productCodesRaw);
String creditArrearsStatus = StringUtils.isEmpty(creditArrearsStatusRaw) ? null
@@ -2577,7 +2590,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
boolean hasFilter = (orderNos != null && !orderNos.isEmpty())
|| (trackingNos != null && !trackingNos.isEmpty())
|| (transferNos != null && !transferNos.isEmpty())
|| (request.getOrderIdentifiers() != null && !normalizeCodeList(request.getOrderIdentifiers()).isEmpty())
|| (request.getOrderIdentifiers() != null && !normalizeOrderIdentifiers(request.getOrderIdentifiers()).isEmpty())
|| (customerCodes != null && !customerCodes.isEmpty())
|| (productCodes != null && !productCodes.isEmpty())
|| StringUtils.isNotEmpty(creditArrearsStatus)
@@ -2591,7 +2604,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
query.setOrderNos(orderNos);
query.setTrackingNos(trackingNos);
query.setTransferNos(transferNos);
query.setOrderIdentifiers(normalizeCodeList(request.getOrderIdentifiers()));
query.setOrderIdentifiers(normalizeOrderIdentifiers(request.getOrderIdentifiers()));
query.setCustomerCodes(customerCodes);
query.setProductCodes(productCodes);
query.setCreditArrearsStatus(creditArrearsStatus);
+52 -19
View File
@@ -14,12 +14,16 @@
virtual
>
<template #search-orderIdentifiers="{ model }">
<multi-value-input
v-model="model.orderIdentifiers"
placeholder="订单号/运单号/转单号"
width="100%"
:search-method="searchOrderIdentifiers"
/>
<div class="order-identifier-search">
<el-input
v-model="model.orderIdentifiers"
type="textarea"
:rows="1"
resize="none"
placeholder="请输入订单号、运单号或转单号"
clearable
/>
</div>
</template>
<template #search-customerCodes="{ model }">
<customer-select
@@ -234,7 +238,7 @@
</template>
<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 { ElMessage, ElMessageBox } from 'element-plus'
import { useProducts } from '@/components/ProductSelect/useProducts'
@@ -244,13 +248,27 @@ import EditChargeWeightDialog from './components/EditChargeWeightDialog.vue'
import ExcelImportDialog from '@/components/ExcelImportDialog'
import ChargeWeightImportAttachDrawer from './components/ChargeWeightImportAttachDrawer.vue'
// 单号下拉:订单号、运单号、转单号合并候选
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)))])
const MAX_ORDER_IDENTIFIERS = 1000
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
}
// 产品下拉选项:复用 ProductSelect 的模块级产品缓存(全应用只拉一次)
@@ -402,8 +420,10 @@ const searchColumns = [
]
function buildBillQuery(params) {
const { bizDateRange, ...rest } = params || {}
const { bizDateRange, orderIdentifiers: rawOrderIdentifiers, ...rest } = params || {}
const query = { ...rest }
const orderIdentifiers = parseOrderIdentifiers(rawOrderIdentifiers)
if (orderIdentifiers.length) query.orderIdentifiers = orderIdentifiers
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
query.params = {
...(query.params || {}),
@@ -415,11 +435,16 @@ function buildBillQuery(params) {
}
function queryList(params) {
if (!validateOrderIdentifierLimit(params?.orderIdentifiers)) {
return Promise.reject(new Error('order identifier limit exceeded'))
}
const query = buildBillQuery(params)
return listBill(query)
}
function handleExport() {
const params = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(params.orderIdentifiers)) return
return exportAsyncFromTable({
baseUrl: '/price/billing/bill/export',
tableRef: tableRef.value,
@@ -491,7 +516,7 @@ function onDetailUpdated(data) {
function hasBatchFeeQuery(q) {
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.trackingNos)
|| hasArr(q.transferNos)
@@ -504,12 +529,16 @@ function hasBatchFeeQuery(q) {
function openBatchFee() {
const q = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return
const { pageNum, pageSize, bizDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange })) {
ElMessage.warning('请先设置列表查询条件')
return
}
const listQuery = { ...rest }
const listQuery = {
...rest,
orderIdentifiers: parseOrderIdentifiers(rest.orderIdentifiers)
}
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
listQuery.beginBizDate = bizDateRange[0] || null
listQuery.endBizDate = bizDateRange[1] || null
@@ -525,6 +554,7 @@ function onBatchFeeSuccess() {
function buildRecalcPayload() {
const q = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return null
const { pageNum, pageSize, bizDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange })) {
ElMessage.warning('请先设置列表查询条件')
@@ -537,7 +567,7 @@ function buildRecalcPayload() {
orderNos: rest.orderNos || [],
trackingNos: rest.trackingNos || [],
transferNos: rest.transferNos || [],
orderIdentifiers: rest.orderIdentifiers || [],
orderIdentifiers: parseOrderIdentifiers(rest.orderIdentifiers),
customerCodes: rest.customerCodes || [],
productCodes: rest.productCodes || [],
creditArrearsStatus: rest.creditArrearsStatus || undefined
@@ -628,7 +658,7 @@ function buildChargeWeightTemplateQuery() {
orderNos: rest.orderNos || [],
trackingNos: rest.trackingNos || [],
transferNos: rest.transferNos || [],
orderIdentifiers: rest.orderIdentifiers || [],
orderIdentifiers: parseOrderIdentifiers(rest.orderIdentifiers),
customerCodes: rest.customerCodes || [],
productCodes: rest.productCodes || [],
creditArrearsStatus: rest.creditArrearsStatus || undefined
@@ -641,6 +671,8 @@ function buildChargeWeightTemplateQuery() {
}
async function downloadChargeWeightImportTemplate() {
const currentQuery = tableRef.value?.getQueryParams?.() || {}
if (cwFillByFilter.value && !validateOrderIdentifierLimit(currentQuery.orderIdentifiers)) return
const payload = buildChargeWeightTemplateQuery()
// Checked: backend fills orderNos from list filters. Unchecked: OSS template CHARGE_WEIGHT.
if (payload.fillByFilter) {
@@ -842,4 +874,5 @@ function refundTagType(status) {
.billing-bill :deep(.basic-form .el-form-item .el-date-editor) {
width: 100%;
}
.order-identifier-search { width: 100%; }
</style>
+45 -20
View File
@@ -60,12 +60,16 @@
</el-col>
</template>
<template #search-orderIdentifiers="{ model }">
<multi-value-input
v-model="model.orderIdentifiers"
placeholder="订单号/运单号/转单号"
width="100%"
:search-method="searchOrderIdentifiers"
/>
<div class="order-identifier-search">
<el-input
v-model="model.orderIdentifiers"
type="textarea"
:rows="1"
resize="none"
placeholder="请输入订单号、运单号或转单号"
clearable
/>
</div>
</template>
<template #search-customerCodes="{ model }">
<customer-select
@@ -163,7 +167,7 @@
<script setup name="OrderBillQuery">
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 OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
@@ -182,6 +186,7 @@ const rebillProgressSuccess = ref(0)
const rebillProgressFail = ref(0)
const tableHeight = ref(480)
let tableResizeObserver = null
const MAX_ORDER_IDENTIFIERS = 1000
const defaultParams = {
orderStatuses: [3, 4, 5, 6, 7, 8, 11],
@@ -330,8 +335,10 @@ const searchColumns = [
]
function applySearchRanges(params) {
const { bizDateRange, pickupDateRange, ...rest } = params
const { bizDateRange, pickupDateRange, orderIdentifiers: rawOrderIdentifiers, ...rest } = params || {}
const query = { ...rest }
const orderIdentifiers = parseOrderIdentifiers(rawOrderIdentifiers)
if (orderIdentifiers.length) query.orderIdentifiers = orderIdentifiers
const rangeParams = {}
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
rangeParams.beginBizDate = bizDateRange[0] || null
@@ -347,11 +354,36 @@ function applySearchRanges(params) {
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) {
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() {
const params = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(params.orderIdentifiers)) return
return exportAsyncFromTable({
baseUrl: '/price/billing/orderBill/export',
tableRef: tableRef.value,
@@ -365,9 +397,7 @@ const MAX_REBILL = 10000
function currentIdentifiers() {
const q = tableRef.value?.getQueryParams?.() || {}
return (Array.isArray(q.orderIdentifiers) ? q.orderIdentifiers : [])
.map(v => String(v || '').trim())
.filter(Boolean)
return parseOrderIdentifiers(q.orderIdentifiers)
}
function buildRebillFilter() {
@@ -400,6 +430,7 @@ function buildCheckQuery() {
function handleCheckProducts() {
const q = buildCheckQuery()
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return
const hasCustomer = Array.isArray(q.customerCodes) && q.customerCodes.length > 0
const begin = q.params?.beginBizDate
const end = q.params?.endBizDate
@@ -474,6 +505,7 @@ async function runLocalRebill(payload, confirmText) {
function openFilterRebill() {
const filter = buildRebillFilter()
const identifiers = filter.orderIdentifiers || []
if (!validateOrderIdentifierLimit(identifiers)) return
if (!hasRebillFilter(filter)) {
ElMessage.warning('请输入单号,或选择客户并选择计费时间后再重新计费;也可勾选订单后点重新计费选中')
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) {
if (val == null || val === '') return '-'
const num = Number(val)
@@ -581,6 +605,7 @@ onBeforeUnmount(() => {
.order-bill-query :deep(.basic-form .el-form-item .el-date-editor) {
width: 100%;
}
.order-identifier-search { width: 100%; }
.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__hint { margin-top: 12px; font-size: 12px; color: var(--el-color-warning); }