diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java index 5ebd8c6e..fa0c4291 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java @@ -27,6 +27,7 @@ import com.bms.price.domain.PrcBillingTaskRetryTask; import com.bms.price.domain.vo.TabOrderMonitorQuery; import com.bms.price.domain.vo.TabOrderMonitorVo; import com.bms.price.domain.vo.TabOrderProductCheckResult; +import com.bms.price.oracle.CustomerKeywordResolver; import com.bms.price.oracle.TabOrderMonitorService; import com.bms.price.oracle.TabOrderProductCheckService; import com.bms.price.search.BillSearchPage; @@ -54,6 +55,9 @@ public class TabOrderMonitorController extends BaseController @Autowired private IBillSearchStore billSearchStore; + @Autowired + private CustomerKeywordResolver customerKeywordResolver; + @PreAuthorize("@ss.hasPermi('monitor:orderSource:list')") @GetMapping("/list") public TableDataInfo list(TabOrderMonitorQuery query) @@ -61,12 +65,14 @@ public class TabOrderMonitorController extends BaseController TableDataInfo rsp = new TableDataInfo(); rsp.setCode(HttpStatus.SUCCESS); rsp.setMsg("\u67e5\u8be2\u6210\u529f"); - if (!hasRequiredFilters(query)) + if (!hasListFilters(query)) { rsp.setRows(Collections.emptyList()); rsp.setTotal(0L); return rsp; } + // 在切换 Oracle 数据源之前,把客户名称解析为客户代码 + customerKeywordResolver.resolve(query); PageDomain pageDomain = TableSupport.buildPageRequest(); int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue(); int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue(); @@ -87,6 +93,7 @@ public class TabOrderMonitorController extends BaseController { return error("customerCodes and pickup time required"); } + customerKeywordResolver.resolve(query); TabOrderProductCheckResult data = tabOrderProductCheckService.check(query); return success(data); } @@ -103,6 +110,7 @@ public class TabOrderMonitorController extends BaseController { return error("productCode required"); } + customerKeywordResolver.resolve(query); return success(tabOrderMonitorService.selectOrderIdsByProduct(query)); } @@ -115,6 +123,7 @@ public class TabOrderMonitorController extends BaseController { return error("customerCodes and pickup time required"); } + customerKeywordResolver.resolve(query); tabOrderMonitorService.count(query); String asyncTaskId = UUID.randomUUID().toString().replace("-", ""); tabOrderSyncAsyncRunner.put(asyncTaskId, PrcBillingTaskRetryTask.STATUS_PENDING, 0, "queued", @@ -211,25 +220,39 @@ public class TabOrderMonitorController extends BaseController return prefix + "_" + System.currentTimeMillis(); } + /** + * 列表查询:有任一单号即可查询;无单号时必须客户(名称或代码)+提货时间。 + */ + private static boolean hasListFilters(TabOrderMonitorQuery query) + { + return hasRequiredFilters(query) || hasNonBlankItem(query.getOrderIdentifiers()); + } + private static boolean hasRequiredFilters(TabOrderMonitorQuery query) { if (query == null) { return false; } - boolean hasCustomer = false; - if (query.getCustomerCodes() != null) - { - for (String code : query.getCustomerCodes()) - { - if (StringUtils.isNotEmpty(code)) - { - hasCustomer = true; - break; - } - } - } + boolean hasCustomer = hasNonBlankItem(query.getCustomerCodes()) + || hasNonBlankItem(query.getCustomerKeywords()); boolean hasTime = StringUtils.isNotEmpty(query.getBeginTime()) && StringUtils.isNotEmpty(query.getEndTime()); return hasCustomer && hasTime; } + + private static boolean hasNonBlankItem(List list) + { + if (list == null) + { + return false; + } + for (String item : list) + { + if (StringUtils.isNotEmpty(item)) + { + return true; + } + } + return false; + } } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderMonitorQuery.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderMonitorQuery.java index 06a628ed..f66089d2 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderMonitorQuery.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderMonitorQuery.java @@ -15,6 +15,11 @@ public class TabOrderMonitorQuery implements Serializable /** 客户代码列表 */ private List customerCodes; + /** + * 客户名称或客户代码混合录入,每行一个, + * CUSTOMER_CODE / CUSTOMER_NAME 任一列精确命中即可。 + */ + private List customerKeywords; /** 提货时间起(yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) */ private String beginTime; /** 提货时间止(yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) */ @@ -25,6 +30,12 @@ public class TabOrderMonitorQuery implements Serializable private String orgOrderId; /** 转单号 TRANSFER_ORDER_ID */ private String transferOrderId; + /** + * 订单号/运单号/转单号混合录入,每行一个, + * ORDER_ID / ORG_ORDER_ID / TRANSFER_ORDER_ID 任一列精确命中即可。 + * 有单号时不再强制要求客户+提货时间。 + */ + private List orderIdentifiers; /** 上游产品代码 PRODUCT_CODE(产品比对点进订单号时使用) */ private String productCode; /** 分批同步计费每批条数 */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/CustomerCodeResolveMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/CustomerCodeResolveMapper.java new file mode 100644 index 00000000..50312054 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/CustomerCodeResolveMapper.java @@ -0,0 +1,17 @@ +package com.bms.price.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; + +/** + * 订单监控查询专用:把用户混合录入的客户名称/客户代码解析为客户代码集合。 + * 走默认(MASTER/MySQL)数据源;Oracle TAB_ORDER 的 CUSTOMER_NAME 无索引, + * 不能在 Oracle 侧做名称 OR,否则会全表扫描。 + */ +public interface CustomerCodeResolveMapper +{ + /** + * 按客户代码或客户名称精确匹配,返回命中的客户代码(同名客户可能对应多个代码)。 + */ + List selectCustomerCodesByKeywords(@Param("keywords") List keywords); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/oracle/CustomerKeywordResolver.java b/bms-back/bms-system/src/main/java/com/bms/price/oracle/CustomerKeywordResolver.java new file mode 100644 index 00000000..cdbe1e2b --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/oracle/CustomerKeywordResolver.java @@ -0,0 +1,99 @@ +package com.bms.price.oracle; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.bms.common.utils.StringUtils; +import com.bms.price.domain.vo.TabOrderMonitorQuery; +import com.bms.price.mapper.CustomerCodeResolveMapper; + +/** + * 把客户长文本(客户名称/客户代码混合,一行一条)解析为客户代码集合, + * 并入查询条件。必须在切换到 Oracle(SLAVE)数据源之前调用。 + * Oracle TAB_ORDER.CUSTOMER_NAME 无索引,名称匹配只能先在 MySQL 主数据解析成代码, + * 否则跨列 OR 会导致 Oracle 全表扫描。 + */ +@Component +public class CustomerKeywordResolver +{ + @Autowired + private CustomerCodeResolveMapper customerCodeResolveMapper; + + /** + * 原地解析:query.customerKeywords 归一化后查主数据转成 customerCodes, + * 解析完成清空 keywords(Oracle 侧不再使用该字段)。 + */ + public void resolve(TabOrderMonitorQuery query) + { + if (query == null) + { + return; + } + List keywords = normalizeLineList(query.getCustomerKeywords()); + query.setCustomerKeywords(keywords); + if (keywords == null || keywords.isEmpty()) + { + return; + } + List resolved = customerCodeResolveMapper.selectCustomerCodesByKeywords(keywords); + Set codes = new LinkedHashSet(); + if (query.getCustomerCodes() != null) + { + for (String c : query.getCustomerCodes()) + { + if (StringUtils.isNotEmpty(c)) + { + codes.add(c.trim()); + } + } + } + if (resolved != null) + { + for (String c : resolved) + { + if (StringUtils.isNotEmpty(c)) + { + codes.add(c.trim()); + } + } + } + query.setCustomerCodes(codes.isEmpty() ? null : new ArrayList(codes)); + // 名称已全部解析为代码;Oracle 侧不保留跨列 OR 条件 + query.setCustomerKeywords(null); + } + + /** + * 多行文本归一化:按换行/逗号/分号/顿号拆分(不按空格拆,客户名称可能含空格),去空去重。 + */ + private static List normalizeLineList(List lines) + { + if (lines == null || lines.isEmpty()) + { + return null; + } + Set set = new LinkedHashSet(); + for (String raw : lines) + { + if (StringUtils.isEmpty(raw)) + { + continue; + } + for (String part : Arrays.asList(raw.split("[,,;;、\\r\\n]+"))) + { + if (StringUtils.isNotEmpty(part)) + { + String t = part.trim(); + if (!t.isEmpty()) + { + set.add(t); + } + } + } + } + return set.isEmpty() ? null : new ArrayList(set); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderMonitorService.java b/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderMonitorService.java index 31a33376..a3b2c3d3 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderMonitorService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderMonitorService.java @@ -160,39 +160,75 @@ public class TabOrderMonitorService { if (query == null) { - throw new ServiceException("请先选择客户和提货时间"); + throw new ServiceException("请输入单号,或选择客户和提货时间"); } + // 单号长文本:一行一条(兼容逗号/分号/顿号,不按空格拆)。 + // 客户名称/代码混合条件由 CustomerKeywordResolver 在切库前解析为 customerCodes。 + List identifiers = normalizeLineList(query.getOrderIdentifiers()); + query.setOrderIdentifiers(identifiers); + List codes = query.getCustomerCodes(); - if (codes == null || codes.isEmpty()) + List cleanedCodes = new ArrayList(); + if (codes != null) { - throw new ServiceException("请选择客户"); - } - List cleaned = new ArrayList(); - for (String c : codes) - { - if (StringUtils.isNotEmpty(c)) + for (String c : codes) { - cleaned.add(c.trim()); + if (StringUtils.isNotEmpty(c)) + { + cleanedCodes.add(c.trim()); + } } } - if (cleaned.isEmpty()) - { - throw new ServiceException("请选择客户"); - } - query.setCustomerCodes(cleaned); + query.setCustomerCodes(cleanedCodes.isEmpty() ? null : cleanedCodes); + String begin = normalizeTime(query.getBeginTime(), true); String end = normalizeTime(query.getEndTime(), false); - if (begin == null || end == null) - { - throw new ServiceException("请选择提货时间范围"); - } query.setBeginTime(begin); query.setEndTime(end); + boolean hasTime = begin != null && end != null; + boolean hasCustomer = !cleanedCodes.isEmpty(); + boolean hasIdentifier = identifiers != null && !identifiers.isEmpty(); + // 有单号即可查询;无单号时必须客户+提货时间,避免全表扫描(同步/产品检测在入口另有校验) + if (!hasIdentifier && !(hasCustomer && hasTime)) + { + throw new ServiceException("请输入单号,或选择客户和提货时间"); + } query.setOrderId(trimToNull(query.getOrderId())); query.setOrgOrderId(trimToNull(query.getOrgOrderId())); query.setTransferOrderId(trimToNull(query.getTransferOrderId())); } + /** + * 多行文本归一化:按换行/逗号/分号/顿号拆分(不按空格拆),trim、去空去重。 + */ + private static List normalizeLineList(List lines) + { + if (lines == null || lines.isEmpty()) + { + return null; + } + java.util.LinkedHashSet set = new java.util.LinkedHashSet(); + for (String raw : lines) + { + if (StringUtils.isEmpty(raw)) + { + continue; + } + for (String part : raw.split("[,,;;、\\r\\n]+")) + { + if (StringUtils.isNotEmpty(part)) + { + String t = part.trim(); + if (!t.isEmpty()) + { + set.add(t); + } + } + } + } + return set.isEmpty() ? null : new ArrayList(set); + } + private static String trimToNull(String s) { if (StringUtils.isEmpty(s)) diff --git a/bms-back/bms-system/src/main/resources/mapper/price/CustomerCodeResolveMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/CustomerCodeResolveMapper.xml new file mode 100644 index 00000000..4b8ebed9 --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/price/CustomerCodeResolveMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/bms-back/bms-system/src/main/resources/mapper/price/OracleUpstreamOrderMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/OracleUpstreamOrderMapper.xml index 54d8b8b5..f3a7bac5 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/OracleUpstreamOrderMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/OracleUpstreamOrderMapper.xml @@ -48,12 +48,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" o.BL_PICKUP = 1 AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11) - AND o.CUSTOMER_CODE IN - - #{code} - - AND o.PICKUP_DATE >= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS') - AND o.PICKUP_DATE <= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS') + + AND o.CUSTOMER_CODE IN + + #{code} + + + + AND o.PICKUP_DATE >= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS') + + + AND o.PICKUP_DATE <= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS') + AND o.ORDER_ID = #{query.orderId} @@ -63,6 +69,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND o.TRANSFER_ORDER_ID = #{query.transferOrderId} + + AND ( + o.ORDER_ID IN + + #{oid} + + OR o.ORG_ORDER_ID IN + + #{oid} + + OR o.TRANSFER_ORDER_ID IN + + #{oid} + + ) + diff --git a/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue b/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue index 544f482a..ff3ef3fa 100644 --- a/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue +++ b/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue @@ -138,6 +138,8 @@ function openOrders(row) { } const q = { ...lastQuery.value, + // 下钻到具体客户代码时,去掉客户名称/代码混合条件,避免 OR 扩大范围 + customerKeywords: undefined, customerCodes: row.customerCode ? [row.customerCode] : lastQuery.value.customerCodes, productCode: row.upstreamCode } @@ -156,8 +158,12 @@ function openOrders(row) { } function open(query) { - if (!query || !query.customerCodes || !query.beginTime || !query.endTime) { - ElMessage.warning('请先选择客户和提货时间') + const hasCustomer = !!(query && ( + (Array.isArray(query.customerKeywords) && query.customerKeywords.length) + || (Array.isArray(query.customerCodes) && query.customerCodes.length) + )) + if (!query || !hasCustomer || !query.beginTime || !query.endTime) { + ElMessage.warning('请先输入客户并选择提货时间') return } lastQuery.value = { ...query } diff --git a/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncDialog.vue b/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncDialog.vue index e9a02653..96888c3f 100644 --- a/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncDialog.vue +++ b/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncDialog.vue @@ -86,12 +86,15 @@ const progress = ref({ const filterText = computed(() => { const q = query.value || {} const codes = Array.isArray(q.customerCodes) ? q.customerCodes.filter(Boolean) : [] + const keywords = Array.isArray(q.customerKeywords) ? q.customerKeywords.filter(Boolean) : [] + const identifiers = Array.isArray(q.orderIdentifiers) ? q.orderIdentifiers.filter(Boolean) : [] const nos = [] + if (identifiers.length) nos.push('单号 ' + identifiers.join(', ')) if (q.orderId) nos.push('订单号 ' + q.orderId) if (q.orgOrderId) nos.push('运单号 ' + q.orgOrderId) if (q.transferOrderId) nos.push('转单号 ' + q.transferOrderId) return { - customers: codes.join(', ') || '-', + customers: keywords.join(', ') || codes.join(', ') || '-', time: (q.beginTime || '-') + ' ~ ' + (q.endTime || '-'), nos: nos.join(' / ') } @@ -178,8 +181,9 @@ function onClosed() { function submit(skipConfirm) { const q = query.value || {} const codes = Array.isArray(q.customerCodes) ? q.customerCodes.filter(v => v != null && String(v).trim() !== '') : [] - if (!codes.length || !q.beginTime || !q.endTime) { - proxy.$modal.msgError('请先选择客户和提货时间') + const keywords = Array.isArray(q.customerKeywords) ? q.customerKeywords.filter(v => v != null && String(v).trim() !== '') : [] + if ((!codes.length && !keywords.length) || !q.beginTime || !q.endTime) { + proxy.$modal.msgError('请先输入客户并选择提货时间') return } const start = () => { @@ -197,7 +201,9 @@ function submit(skipConfirm) { } emitProgress() return syncOrderSourceByFilter({ - customerCodes: codes, + customerCodes: codes.length ? codes : undefined, + customerKeywords: keywords.length ? keywords : undefined, + orderIdentifiers: Array.isArray(q.orderIdentifiers) && q.orderIdentifiers.length ? q.orderIdentifiers : undefined, beginTime: q.beginTime, endTime: q.endTime, orderId: q.orderId || undefined, diff --git a/bms-front/src/views/monitor/orderSource/index.vue b/bms-front/src/views/monitor/orderSource/index.vue index 575d13e9..2430a98d 100644 --- a/bms-front/src/views/monitor/orderSource/index.vue +++ b/bms-front/src/views/monitor/orderSource/index.vue @@ -25,19 +25,6 @@ search-action-align="center" :search-per-row="4" > -