From 52816ad61868414387a9240156f1d7efd02f9da7 Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Tue, 13 Oct 2026 13:39:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=A0=81=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=EF=BC=8C=E5=92=8C=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/BmsProductCodeMappingController.java | 73 +++++ .../monitor/TabOrderMonitorController.java | 17 ++ .../domain/vo/TabOrderProductCheckResult.java | 277 +++++++++++++++++ .../mapper/OracleUpstreamOrderMapper.java | 5 + .../price/oracle/OracleOrderBillMapper.java | 1 + .../price/oracle/TabOrderMonitorService.java | 26 ++ .../oracle/TabOrderProductCheckService.java | 284 ++++++++++++++++++ .../price/service/ITmsPriceQuoteService.java | 7 + .../impl/TmsPriceQuoteServiceImpl.java | 87 ++++++ .../system/domain/BmsProductCodeMapping.java | 92 ++++++ .../mapper/BmsProductCodeMappingMapper.java | 23 ++ .../IBmsProductCodeMappingService.java | 24 ++ .../BmsProductCodeMappingServiceImpl.java | 107 +++++++ .../data/BmsProductCodeMappingMapper.xml | 92 ++++++ .../price/OracleUpstreamOrderMapper.xml | 14 + bms-back/sql/data/product_code_mapping.sql | 18 ++ .../sql/data/product_code_mapping_menu.sql | 37 +++ bms-front/src/api/data/productCodeMap.js | 39 +++ bms-front/src/api/monitor/orderSource.js | 9 + .../src/views/data/productCodeMap/index.vue | 177 +++++++++++ .../OrderSourceProductCheckDrawer.vue | 132 ++++++++ .../src/views/monitor/orderSource/index.vue | 32 ++ 22 files changed, 1573 insertions(+) create mode 100644 bms-back/bms-admin/src/main/java/com/bms/web/controller/data/BmsProductCodeMappingController.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderProductCheckResult.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderProductCheckService.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/system/domain/BmsProductCodeMapping.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsProductCodeMappingMapper.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/system/service/IBmsProductCodeMappingService.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsProductCodeMappingServiceImpl.java create mode 100644 bms-back/bms-system/src/main/resources/mapper/data/BmsProductCodeMappingMapper.xml create mode 100644 bms-back/sql/data/product_code_mapping.sql create mode 100644 bms-back/sql/data/product_code_mapping_menu.sql create mode 100644 bms-front/src/api/data/productCodeMap.js create mode 100644 bms-front/src/views/data/productCodeMap/index.vue create mode 100644 bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/data/BmsProductCodeMappingController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/data/BmsProductCodeMappingController.java new file mode 100644 index 00000000..957b8c38 --- /dev/null +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/data/BmsProductCodeMappingController.java @@ -0,0 +1,73 @@ +package com.bms.web.controller.data; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.bms.common.annotation.Log; +import com.bms.common.core.controller.BaseController; +import com.bms.common.core.domain.AjaxResult; +import com.bms.common.core.page.TableDataInfo; +import com.bms.common.enums.BusinessType; +import com.bms.system.domain.BmsProductCodeMapping; +import com.bms.system.service.IBmsProductCodeMappingService; + +/** + * 代码映射:上游产品代码 -> BMS产品代码 + */ +@RestController +@RequestMapping("/data/productCodeMap") +public class BmsProductCodeMappingController extends BaseController +{ + @Autowired + private IBmsProductCodeMappingService mappingService; + + @PreAuthorize("@ss.hasPermi('data:productCodeMap:list')") + @GetMapping("/list") + public TableDataInfo list(BmsProductCodeMapping query) + { + startPage(); + List list = mappingService.selectList(query); + return getDataTable(list); + } + + @PreAuthorize("@ss.hasPermi('data:productCodeMap:query')") + @GetMapping("/{mapId}") + public AjaxResult getInfo(@PathVariable Long mapId) + { + return success(mappingService.selectById(mapId)); + } + + @PreAuthorize("@ss.hasPermi('data:productCodeMap:add')") + @Log(title = "product code mapping", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody BmsProductCodeMapping row) + { + row.setCreateBy(getUsername()); + return toAjax(mappingService.insert(row)); + } + + @PreAuthorize("@ss.hasPermi('data:productCodeMap:edit')") + @Log(title = "product code mapping", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody BmsProductCodeMapping row) + { + row.setUpdateBy(getUsername()); + return toAjax(mappingService.update(row)); + } + + @PreAuthorize("@ss.hasPermi('data:productCodeMap:remove')") + @Log(title = "product code mapping", businessType = BusinessType.DELETE) + @DeleteMapping("/{mapIds}") + public AjaxResult remove(@PathVariable Long[] mapIds) + { + return toAjax(mappingService.deleteByIds(mapIds)); + } +} 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 877e7ca7..32f96d30 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 @@ -26,7 +26,9 @@ import com.bms.common.utils.StringUtils; 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.TabOrderMonitorService; +import com.bms.price.oracle.TabOrderProductCheckService; import com.bms.price.search.BillSearchPage; import com.bms.price.search.EsLogType; import com.bms.price.search.IBillSearchStore; @@ -43,6 +45,9 @@ public class TabOrderMonitorController extends BaseController @Autowired private TabOrderMonitorService tabOrderMonitorService; + @Autowired + private TabOrderProductCheckService tabOrderProductCheckService; + @Autowired private TabOrderSyncAsyncRunner tabOrderSyncAsyncRunner; @@ -74,6 +79,18 @@ public class TabOrderMonitorController extends BaseController return rsp; } + @PreAuthorize("@ss.hasPermi('monitor:orderSource:list')") + @PostMapping("/checkProducts") + public AjaxResult checkProducts(@RequestBody TabOrderMonitorQuery query) + { + if (!hasRequiredFilters(query)) + { + return error("customerCodes and pickup time required"); + } + TabOrderProductCheckResult data = tabOrderProductCheckService.check(query); + return success(data); + } + @PreAuthorize("@ss.hasPermi('monitor:orderSource:sync')") @Log(title = "order source sync filter", businessType = BusinessType.UPDATE) @PostMapping("/syncByFilter") diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderProductCheckResult.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderProductCheckResult.java new file mode 100644 index 00000000..5fc45f83 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/vo/TabOrderProductCheckResult.java @@ -0,0 +1,277 @@ +package com.bms.price.domain.vo; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Order-source product vs customer package comparison result. + */ +public class TabOrderProductCheckResult implements Serializable +{ + private static final long serialVersionUID = 1L; + + private long orderTotal; + private int scannedOrders; + private int distinctOrderProducts; + private int packageProductCount; + private int matchedCount; + private int missingCount; + private String asOfTime; + private boolean truncated; + private List packageProducts = new ArrayList(); + private List orderProducts = new ArrayList(); + private List missingProducts = new ArrayList(); + + public long getOrderTotal() + { + return orderTotal; + } + + public void setOrderTotal(long orderTotal) + { + this.orderTotal = orderTotal; + } + + public int getScannedOrders() + { + return scannedOrders; + } + + public void setScannedOrders(int scannedOrders) + { + this.scannedOrders = scannedOrders; + } + + public int getDistinctOrderProducts() + { + return distinctOrderProducts; + } + + public void setDistinctOrderProducts(int distinctOrderProducts) + { + this.distinctOrderProducts = distinctOrderProducts; + } + + public int getPackageProductCount() + { + return packageProductCount; + } + + public void setPackageProductCount(int packageProductCount) + { + this.packageProductCount = packageProductCount; + } + + public int getMatchedCount() + { + return matchedCount; + } + + public void setMatchedCount(int matchedCount) + { + this.matchedCount = matchedCount; + } + + public int getMissingCount() + { + return missingCount; + } + + public void setMissingCount(int missingCount) + { + this.missingCount = missingCount; + } + + public String getAsOfTime() + { + return asOfTime; + } + + public void setAsOfTime(String asOfTime) + { + this.asOfTime = asOfTime; + } + + public boolean isTruncated() + { + return truncated; + } + + public void setTruncated(boolean truncated) + { + this.truncated = truncated; + } + + public List getPackageProducts() + { + return packageProducts; + } + + public void setPackageProducts(List packageProducts) + { + this.packageProducts = packageProducts; + } + + public List getOrderProducts() + { + return orderProducts; + } + + public void setOrderProducts(List orderProducts) + { + this.orderProducts = orderProducts; + } + + public List getMissingProducts() + { + return missingProducts; + } + + public void setMissingProducts(List missingProducts) + { + this.missingProducts = missingProducts; + } + + public static class PackageProductRow implements Serializable + { + private static final long serialVersionUID = 1L; + private String customerCode; + private String productCode; + private String productName; + + public String getCustomerCode() + { + return customerCode; + } + + public void setCustomerCode(String customerCode) + { + this.customerCode = customerCode; + } + + public String getProductCode() + { + return productCode; + } + + public void setProductCode(String productCode) + { + this.productCode = productCode; + } + + public String getProductName() + { + return productName; + } + + public void setProductName(String productName) + { + this.productName = productName; + } + } + + public static class OrderProductRow implements Serializable + { + private static final long serialVersionUID = 1L; + private String customerCode; + private String upstreamCode; + private String productName; + private String mappedCode; + private boolean inPackage; + private boolean mappedInPackage; + private boolean covered; + private int orderCount; + private List sampleOrderIds = new ArrayList(); + + public String getCustomerCode() + { + return customerCode; + } + + public void setCustomerCode(String customerCode) + { + this.customerCode = customerCode; + } + + public String getUpstreamCode() + { + return upstreamCode; + } + + public void setUpstreamCode(String upstreamCode) + { + this.upstreamCode = upstreamCode; + } + + public String getProductName() + { + return productName; + } + + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getMappedCode() + { + return mappedCode; + } + + public void setMappedCode(String mappedCode) + { + this.mappedCode = mappedCode; + } + + public boolean isInPackage() + { + return inPackage; + } + + public void setInPackage(boolean inPackage) + { + this.inPackage = inPackage; + } + + public boolean isMappedInPackage() + { + return mappedInPackage; + } + + public void setMappedInPackage(boolean mappedInPackage) + { + this.mappedInPackage = mappedInPackage; + } + + public boolean isCovered() + { + return covered; + } + + public void setCovered(boolean covered) + { + this.covered = covered; + } + + public int getOrderCount() + { + return orderCount; + } + + public void setOrderCount(int orderCount) + { + this.orderCount = orderCount; + } + + public List getSampleOrderIds() + { + return sampleOrderIds; + } + + public void setSampleOrderIds(List sampleOrderIds) + { + this.sampleOrderIds = sampleOrderIds; + } + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/OracleUpstreamOrderMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/OracleUpstreamOrderMapper.java index f62a9de7..52588402 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/OracleUpstreamOrderMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/OracleUpstreamOrderMapper.java @@ -27,4 +27,9 @@ public interface OracleUpstreamOrderMapper @Param("query") TabOrderMonitorQuery query, @Param("startRow") long startRow, @Param("endRow") long endRow); + + /** Distinct product codes for product-vs-package check (grouped, no pagination). */ + List> selectTabOrderProductCodes( + @Param("orderTable") String orderTable, + @Param("query") TabOrderMonitorQuery query); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/oracle/OracleOrderBillMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/oracle/OracleOrderBillMapper.java index 62153648..97587f84 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/oracle/OracleOrderBillMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/oracle/OracleOrderBillMapper.java @@ -40,6 +40,7 @@ public class OracleOrderBillMapper { product = defaultProductCode; } + // Keep upstream product code; package miss then maps via product_code_mapping in doQuote. req.setProductCode(product); req.setCountryCode(firstStr(row, "COUNTRY")); String countryName = firstStr(row, "COUNTRY_NAME", "COUNTRY_EINGTH_NAME", "COUNTRY"); 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 cf5e8a60..9fe75d7d 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 @@ -55,6 +55,32 @@ public class TabOrderMonitorService } } + @DataSource(DataSourceType.SLAVE) + public List> selectDistinctProductCodes(TabOrderMonitorQuery query) + { + validate(query); + String table = resolveTable(); + try + { + PageHelper.clearPage(); + long t0 = System.currentTimeMillis(); + List> rows = oracleUpstreamOrderMapper.selectTabOrderProductCodes(table, query); + log.info("oracle tab_order product codes customerSize={} begin={} end={} groups={} costMs={}", + query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(), + query.getBeginTime(), query.getEndTime(), + rows == null ? 0 : rows.size(), System.currentTimeMillis() - t0); + return rows == null ? Collections.emptyList() : rows; + } + catch (RuntimeException e) + { + throw new ServiceException("Oracle TAB_ORDER product codes failed: " + e.getMessage()); + } + finally + { + PageHelper.clearPage(); + } + } + @DataSource(DataSourceType.SLAVE) public List selectPage(TabOrderMonitorQuery query, int pageNum, int pageSize) { diff --git a/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderProductCheckService.java b/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderProductCheckService.java new file mode 100644 index 00000000..a0c24172 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/oracle/TabOrderProductCheckService.java @@ -0,0 +1,284 @@ +package com.bms.price.oracle; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.bms.common.utils.StringUtils; +import com.bms.price.domain.PrcPriceSetProduct; +import com.bms.price.domain.vo.TabOrderMonitorQuery; +import com.bms.price.domain.vo.TabOrderProductCheckResult; +import com.bms.price.domain.vo.TabOrderProductCheckResult.OrderProductRow; +import com.bms.price.domain.vo.TabOrderProductCheckResult.PackageProductRow; +import com.bms.price.service.ITmsPriceQuoteService; +import com.bms.system.service.IBmsProductCodeMappingService; + +/** + * Compare distinct TAB_ORDER product codes against customer package products. + */ +@Service +public class TabOrderProductCheckService +{ + @Autowired + private TabOrderMonitorService tabOrderMonitorService; + + @Autowired + private ITmsPriceQuoteService tmsPriceQuoteService; + + @Autowired + private IBmsProductCodeMappingService productCodeMappingService; + + public TabOrderProductCheckResult check(TabOrderMonitorQuery query) + { + Date asOf = resolveAsOf(query); + TabOrderProductCheckResult result = new TabOrderProductCheckResult(); + result.setAsOfTime(formatAsOf(asOf)); + result.setTruncated(false); + + List> groups = tabOrderMonitorService.selectDistinctProductCodes(query); + if (groups == null || groups.isEmpty()) + { + result.setOrderTotal(0L); + result.setScannedOrders(0); + return result; + } + + long orderTotal = 0L; + Set customerCodes = new HashSet(); + List orderProducts = new ArrayList(); + List missingProducts = new ArrayList(); + + for (Map g : groups) + { + String customerCode = trim(str(g, "CUSTOMER_CODE")); + String productCode = trim(str(g, "PRODUCT_CODE")); + if (StringUtils.isEmpty(customerCode) || StringUtils.isEmpty(productCode)) + { + continue; + } + customerCodes.add(customerCode); + orderTotal += longValue(g, "ORDER_COUNT"); + } + + Map> packageCodeByCustomer = new HashMap>(); + List packageRows = new ArrayList(); + for (String customerCode : customerCodes) + { + List products = tmsPriceQuoteService.listAllBoundPackageProducts(customerCode, asOf); + Set codes = new HashSet(); + if (products != null) + { + for (PrcPriceSetProduct p : products) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + String code = p.getProductCode().trim(); + String upper = code.toUpperCase(Locale.ROOT); + if (!codes.add(upper)) + { + continue; + } + PackageProductRow pr = new PackageProductRow(); + pr.setCustomerCode(customerCode); + pr.setProductCode(code); + pr.setProductName(trim(p.getProductName())); + packageRows.add(pr); + } + } + packageCodeByCustomer.put(customerCode, codes); + } + packageRows.sort(Comparator + .comparing(PackageProductRow::getCustomerCode, nullSafe()) + .thenComparing(PackageProductRow::getProductCode, nullSafe())); + + int matched = 0; + for (Map g : groups) + { + String customerCode = trim(str(g, "CUSTOMER_CODE")); + String productCode = trim(str(g, "PRODUCT_CODE")); + if (StringUtils.isEmpty(customerCode) || StringUtils.isEmpty(productCode)) + { + continue; + } + Set pkg = packageCodeByCustomer.get(customerCode); + if (pkg == null) + { + pkg = Collections.emptySet(); + } + boolean inPackage = pkg.contains(productCode.toUpperCase(Locale.ROOT)); + String mappedCode = resolveMappedIfDifferent(productCode); + boolean mappedInPackage = false; + if (StringUtils.isNotEmpty(mappedCode)) + { + mappedInPackage = pkg.contains(mappedCode.toUpperCase(Locale.ROOT)); + } + boolean covered = inPackage || mappedInPackage; + + OrderProductRow row = new OrderProductRow(); + row.setCustomerCode(customerCode); + row.setUpstreamCode(productCode); + row.setProductName(trim(str(g, "PRODUCT_NAME"))); + row.setMappedCode(mappedCode); + row.setInPackage(inPackage); + row.setMappedInPackage(mappedInPackage); + row.setCovered(covered); + row.setOrderCount((int) Math.min(Integer.MAX_VALUE, longValue(g, "ORDER_COUNT"))); + orderProducts.add(row); + if (covered) + { + matched++; + } + else + { + missingProducts.add(row); + } + } + + orderProducts.sort(Comparator + .comparing(OrderProductRow::isCovered) + .thenComparing(OrderProductRow::getCustomerCode, nullSafe()) + .thenComparing(OrderProductRow::getUpstreamCode, nullSafe())); + missingProducts.sort(Comparator + .comparing(OrderProductRow::getCustomerCode, nullSafe()) + .thenComparing(OrderProductRow::getUpstreamCode, nullSafe())); + + result.setOrderTotal(orderTotal); + result.setScannedOrders((int) Math.min(Integer.MAX_VALUE, orderTotal)); + result.setDistinctOrderProducts(orderProducts.size()); + result.setPackageProductCount(packageRows.size()); + result.setMatchedCount(matched); + result.setMissingCount(missingProducts.size()); + result.setPackageProducts(packageRows); + result.setOrderProducts(orderProducts); + result.setMissingProducts(missingProducts); + return result; + } + + private String resolveMappedIfDifferent(String upstreamCode) + { + if (productCodeMappingService == null || StringUtils.isEmpty(upstreamCode)) + { + return null; + } + String mapped = productCodeMappingService.resolveBmsProductCode(upstreamCode); + if (StringUtils.isEmpty(mapped) || mapped.equalsIgnoreCase(upstreamCode.trim())) + { + return null; + } + return mapped.trim(); + } + + private static Date resolveAsOf(TabOrderMonitorQuery query) + { + String raw = query == null ? null : query.getEndTime(); + if (StringUtils.isEmpty(raw) && query != null) + { + raw = query.getBeginTime(); + } + Date parsed = parseDateTime(raw); + return parsed != null ? parsed : new Date(); + } + + private static Date parseDateTime(String raw) + { + if (StringUtils.isEmpty(raw)) + { + return null; + } + String s = raw.trim().replace('T', ' '); + try + { + if (s.length() >= 19) + { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(s.substring(0, 19)); + } + if (s.length() >= 10) + { + return new SimpleDateFormat("yyyy-MM-dd").parse(s.substring(0, 10)); + } + } + catch (ParseException ignore) + { + return null; + } + return null; + } + + private static String formatAsOf(Date asOf) + { + if (asOf == null) + { + return null; + } + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(asOf); + } + + private static String trim(String s) + { + return StringUtils.isEmpty(s) ? null : s.trim(); + } + + private static Comparator nullSafe() + { + return Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER); + } + + private static String str(Map row, String key) + { + Object v = value(row, key); + return v == null ? null : String.valueOf(v).trim(); + } + + private static long longValue(Map row, String key) + { + Object v = value(row, key); + if (v == null) + { + return 0L; + } + if (v instanceof Number) + { + return ((Number) v).longValue(); + } + try + { + return Long.parseLong(String.valueOf(v).trim()); + } + catch (Exception e) + { + return 0L; + } + } + + private static Object value(Map row, String key) + { + if (row == null || key == null) + { + return null; + } + if (row.containsKey(key)) + { + return row.get(key); + } + for (Map.Entry e : row.entrySet()) + { + if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) + { + return e.getValue(); + } + } + return null; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/ITmsPriceQuoteService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/ITmsPriceQuoteService.java index b554a882..567b7f0c 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/ITmsPriceQuoteService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/ITmsPriceQuoteService.java @@ -36,4 +36,11 @@ public interface ITmsPriceQuoteService * @param bizDate optional; default today (same as-of window as quote) */ List listBoundPackageProducts(String customerCode, Date bizDate); + + /** + * Union of products from all effective bound packages (for order-source product check). + * @param customerCode required + * @param bizDate optional; default today + */ + List listAllBoundPackageProducts(String customerCode, Date bizDate); } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java index 25d2fe4a..194b2cfa 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/TmsPriceQuoteServiceImpl.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +58,7 @@ import com.bms.price.search.EsBizLogSupport; import com.bms.price.search.EsLogType; import com.bms.price.search.IBillSearchStore; import com.bms.price.service.ITmsPriceQuoteService; +import com.bms.system.service.IBmsProductCodeMappingService; /** * Shared quote/bill calc: customer bind + package version by bizDate (version effective_time). @@ -103,6 +105,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService @Autowired private BmsProductMapper productMapper; + @Autowired(required = false) + private IBmsProductCodeMappingService productCodeMappingService; + @Autowired private PrcSurchargeRelMapper surchargeRelMapper; @@ -261,6 +266,52 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService return Collections.emptyList(); } + @Override + public List listAllBoundPackageProducts(String customerCode, Date bizDate) + { + if (StringUtils.isEmpty(customerCode)) + { + return Collections.emptyList(); + } + List binds = customerPriceSetMapper.selectEffectiveByCustomerCode(customerCode.trim()); + if (binds == null || binds.isEmpty()) + { + return Collections.emptyList(); + } + Date asOf = bizDate != null ? bizDate : new Date(); + Map byCode = new LinkedHashMap(); + for (PrcCustomerPriceSet bind : binds) + { + if (bind == null || bind.getPriceSetId() == null) + { + continue; + } + PrcPriceSet set = priceSetMapper.selectPrcPriceSetHeaderById(bind.getPriceSetId()); + if (set == null || !"E".equals(set.getStatus())) + { + continue; + } + List products = loadProductsForPackage(set.getPriceSetId(), asOf); + if (products == null || products.isEmpty()) + { + continue; + } + for (PrcPriceSetProduct p : products) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + String key = p.getProductCode().trim().toUpperCase(Locale.ROOT); + if (!byCode.containsKey(key)) + { + byCode.put(key, p); + } + } + } + return new ArrayList(byCode.values()); + } + /** Prefer version active at asOf; else latest version that has set-product rows. */ private List loadProductsForPackage(Long priceSetId, Date asOf) { @@ -373,6 +424,25 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService PrcPriceSetProduct setProduct = resolveSetProduct(version.getPsvId(), productCode); if (setProduct == null) + { + // Package has no this product: try product_code_mapping (upstream -> BMS). + String mappedCode = resolveMappedProductCode(productCode); + if (mappedCode != null) + { + setProduct = resolveSetProduct(version.getPsvId(), mappedCode); + if (setProduct != null) + { + productCode = mappedCode; + request.setProductCode(mappedCode); + String mappedName = resolveProductName(mappedCode, bizDate); + if (StringUtils.isNotEmpty(mappedName)) + { + productName = mappedName; + } + } + } + } + if (setProduct == null) { PrcPriceSetProduct probe = new PrcPriceSetProduct(); probe.setPsvId(version.getPsvId()); @@ -470,6 +540,23 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService return setProductMapper.selectByPsvIdAndCode(psvId, productCode); } + /** + * Return mapped BMS product code only when mapping exists and differs from upstream code. + */ + private String resolveMappedProductCode(String upstreamCode) + { + if (productCodeMappingService == null || StringUtils.isEmpty(upstreamCode)) + { + return null; + } + String mapped = productCodeMappingService.resolveBmsProductCode(upstreamCode); + if (StringUtils.isEmpty(mapped) || mapped.equalsIgnoreCase(upstreamCode.trim())) + { + return null; + } + return mapped.trim(); + } + private static final class ZoneResolve { private boolean required; diff --git a/bms-back/bms-system/src/main/java/com/bms/system/domain/BmsProductCodeMapping.java b/bms-back/bms-system/src/main/java/com/bms/system/domain/BmsProductCodeMapping.java new file mode 100644 index 00000000..40c5e3c1 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/domain/BmsProductCodeMapping.java @@ -0,0 +1,92 @@ +package com.bms.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.bms.common.annotation.Excel; +import com.bms.common.core.domain.BaseEntity; + +/** + * 产品代码映射 product_code_mapping + */ +public class BmsProductCodeMapping extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + private Long mapId; + + /** 上游产品代码 */ + @Excel(name = "上游产品代码") + private String upstreamCode; + + /** BMS产品代码 */ + @Excel(name = "BMS产品代码") + private String bmsProductCode; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + private String delFlag; + + public Long getMapId() + { + return mapId; + } + + public void setMapId(Long mapId) + { + this.mapId = mapId; + } + + public String getUpstreamCode() + { + return upstreamCode; + } + + public void setUpstreamCode(String upstreamCode) + { + this.upstreamCode = upstreamCode; + } + + public String getBmsProductCode() + { + return bmsProductCode; + } + + public void setBmsProductCode(String bmsProductCode) + { + this.bmsProductCode = bmsProductCode; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("mapId", getMapId()) + .append("upstreamCode", getUpstreamCode()) + .append("bmsProductCode", getBmsProductCode()) + .append("status", getStatus()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsProductCodeMappingMapper.java b/bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsProductCodeMappingMapper.java new file mode 100644 index 00000000..43845f41 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsProductCodeMappingMapper.java @@ -0,0 +1,23 @@ +package com.bms.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.bms.system.domain.BmsProductCodeMapping; + +public interface BmsProductCodeMappingMapper +{ + List selectList(BmsProductCodeMapping query); + + BmsProductCodeMapping selectById(Long mapId); + + /** Active mapping by upstream code (status=0, del_flag=0). */ + BmsProductCodeMapping selectActiveByUpstreamCode(@Param("upstreamCode") String upstreamCode); + + int countByUpstreamCode(@Param("upstreamCode") String upstreamCode, @Param("excludeId") Long excludeId); + + int insert(BmsProductCodeMapping row); + + int update(BmsProductCodeMapping row); + + int deleteByIds(Long[] mapIds); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsProductCodeMappingService.java b/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsProductCodeMappingService.java new file mode 100644 index 00000000..f53c7a4e --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsProductCodeMappingService.java @@ -0,0 +1,24 @@ +package com.bms.system.service; + +import java.util.List; +import com.bms.system.domain.BmsProductCodeMapping; + +public interface IBmsProductCodeMappingService +{ + List selectList(BmsProductCodeMapping query); + + BmsProductCodeMapping selectById(Long mapId); + + int insert(BmsProductCodeMapping row); + + int update(BmsProductCodeMapping row); + + int deleteByIds(Long[] mapIds); + + /** + * Resolve BMS product code from upstream code. + * Used after package product miss: returns mapped code when active mapping exists; + * otherwise returns original code. + */ + String resolveBmsProductCode(String upstreamCode); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsProductCodeMappingServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsProductCodeMappingServiceImpl.java new file mode 100644 index 00000000..31417ead --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsProductCodeMappingServiceImpl.java @@ -0,0 +1,107 @@ +package com.bms.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.bms.common.exception.ServiceException; +import com.bms.common.utils.StringUtils; +import com.bms.system.domain.BmsProductCodeMapping; +import com.bms.system.mapper.BmsProductCodeMappingMapper; +import com.bms.system.service.IBmsProductCodeMappingService; + +@Service +public class BmsProductCodeMappingServiceImpl implements IBmsProductCodeMappingService +{ + @Autowired + private BmsProductCodeMappingMapper mappingMapper; + + @Override + public List selectList(BmsProductCodeMapping query) + { + return mappingMapper.selectList(query); + } + + @Override + public BmsProductCodeMapping selectById(Long mapId) + { + return mappingMapper.selectById(mapId); + } + + @Override + public int insert(BmsProductCodeMapping row) + { + normalize(row); + validate(row); + if (mappingMapper.countByUpstreamCode(row.getUpstreamCode(), null) > 0) + { + throw new ServiceException("上游产品代码已存在映射:" + row.getUpstreamCode()); + } + if (StringUtils.isEmpty(row.getStatus())) + { + row.setStatus("0"); + } + return mappingMapper.insert(row); + } + + @Override + public int update(BmsProductCodeMapping row) + { + if (row.getMapId() == null) + { + throw new ServiceException("mapId required"); + } + normalize(row); + validate(row); + if (mappingMapper.countByUpstreamCode(row.getUpstreamCode(), row.getMapId()) > 0) + { + throw new ServiceException("上游产品代码已存在映射:" + row.getUpstreamCode()); + } + return mappingMapper.update(row); + } + + @Override + public int deleteByIds(Long[] mapIds) + { + return mappingMapper.deleteByIds(mapIds); + } + + @Override + public String resolveBmsProductCode(String upstreamCode) + { + if (StringUtils.isEmpty(upstreamCode)) + { + return upstreamCode; + } + String code = upstreamCode.trim(); + BmsProductCodeMapping mapped = mappingMapper.selectActiveByUpstreamCode(code); + if (mapped != null && StringUtils.isNotEmpty(mapped.getBmsProductCode())) + { + return mapped.getBmsProductCode().trim(); + } + return code; + } + + private void normalize(BmsProductCodeMapping row) + { + if (row.getUpstreamCode() != null) + { + row.setUpstreamCode(row.getUpstreamCode().trim()); + } + if (row.getBmsProductCode() != null) + { + row.setBmsProductCode(row.getBmsProductCode().trim()); + } + } + + private void validate(BmsProductCodeMapping row) + { + if (StringUtils.isEmpty(row.getUpstreamCode())) + { + throw new ServiceException("上游产品代码不能为空"); + } + if (StringUtils.isEmpty(row.getBmsProductCode())) + { + throw new ServiceException("BMS产品代码不能为空"); + } + } +} diff --git a/bms-back/bms-system/src/main/resources/mapper/data/BmsProductCodeMappingMapper.xml b/bms-back/bms-system/src/main/resources/mapper/data/BmsProductCodeMappingMapper.xml new file mode 100644 index 00000000..d0e7eef1 --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/data/BmsProductCodeMappingMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + select map_id, upstream_code, bms_product_code, status, remark, + create_by, create_time, update_by, update_time, del_flag + from product_code_mapping + + + + + + + + + + + + insert into product_code_mapping ( + upstream_code, bms_product_code, status, remark, create_by, create_time, del_flag + ) values ( + #{upstreamCode}, #{bmsProductCode}, #{status}, #{remark}, #{createBy}, sysdate(), '0' + ) + + + + update product_code_mapping + + upstream_code = #{upstreamCode}, + bms_product_code = #{bmsProductCode}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where map_id = #{mapId} + + + + update product_code_mapping + set del_flag = '1', + upstream_code = concat(upstream_code, '#DEL#', map_id), + update_time = sysdate() + where map_id in + + #{id} + + + 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 11b04384..018a72b2 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 @@ -107,4 +107,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE rn > #{startRow} + + + diff --git a/bms-back/sql/data/product_code_mapping.sql b/bms-back/sql/data/product_code_mapping.sql new file mode 100644 index 00000000..b10f647d --- /dev/null +++ b/bms-back/sql/data/product_code_mapping.sql @@ -0,0 +1,18 @@ +-- 产品代码映射(上游代码 -> BMS产品代码) +SET NAMES utf8mb4; + +CREATE TABLE IF NOT EXISTS product_code_mapping ( + map_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '映射ID', + upstream_code varchar(64) NOT NULL COMMENT '上游产品代码', + bms_product_code varchar(64) NOT NULL COMMENT 'BMS产品代码', + status char(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + remark varchar(500) DEFAULT NULL COMMENT '备注', + create_by varchar(64) DEFAULT '' COMMENT '创建者', + create_time datetime DEFAULT NULL COMMENT '创建时间', + update_by varchar(64) DEFAULT '' COMMENT '更新者', + update_time datetime DEFAULT NULL COMMENT '更新时间', + del_flag char(1) DEFAULT '0' COMMENT '删除标记(0存在 1删除)', + PRIMARY KEY (map_id), + UNIQUE KEY uk_upstream_code (upstream_code), + KEY idx_bms_product_code (bms_product_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品代码映射表'; diff --git a/bms-back/sql/data/product_code_mapping_menu.sql b/bms-back/sql/data/product_code_mapping_menu.sql new file mode 100644 index 00000000..ed9907b1 --- /dev/null +++ b/bms-back/sql/data/product_code_mapping_menu.sql @@ -0,0 +1,37 @@ +-- 数据管理 - 代码映射 +SET NAMES utf8mb4; + +SET @parentId := ( + SELECT menu_id FROM sys_menu + WHERE path = 'data' AND menu_type = 'M' + ORDER BY menu_id ASC LIMIT 1 +); +SET @parentId := IFNULL(@parentId, ( + SELECT parent_id FROM sys_menu + WHERE perms = 'data:region:list' OR component = 'data/region/index' OR component = 'data/exchangerate/index' + ORDER BY menu_id ASC LIMIT 1 +)); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT 2468, N'代码映射', @parentId, 30, 'productCodeMap', 'data/productCodeMap/index', '', 'ProductCodeMap', 1, 0, 'C', '0', '0', 'data:productCodeMap:list', 'tree-table', 'admin', sysdate(), N'上游产品代码与BMS产品代码映射,计费匹配用' +FROM DUAL WHERE @parentId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2468 OR perms = 'data:productCodeMap:list'); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time) +SELECT 2469, N'查询', 2468, 1, '', NULL, 1, 0, 'F', '0', '0', 'data:productCodeMap:query', '#', 'admin', sysdate() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2469); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time) +SELECT 2470, N'新增', 2468, 2, '', NULL, 1, 0, 'F', '0', '0', 'data:productCodeMap:add', '#', 'admin', sysdate() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2470); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time) +SELECT 2471, N'修改', 2468, 3, '', NULL, 1, 0, 'F', '0', '0', 'data:productCodeMap:edit', '#', 'admin', sysdate() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2471); + +INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time) +SELECT 2472, N'删除', 2468, 4, '', NULL, 1, 0, 'F', '0', '0', 'data:productCodeMap:remove', '#', 'admin', sysdate() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2472); + +INSERT INTO sys_role_menu (role_id, menu_id) +SELECT 1, m.menu_id FROM (SELECT 2468 AS menu_id UNION SELECT 2469 UNION SELECT 2470 UNION SELECT 2471 UNION SELECT 2472) m +WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id); diff --git a/bms-front/src/api/data/productCodeMap.js b/bms-front/src/api/data/productCodeMap.js new file mode 100644 index 00000000..66648b99 --- /dev/null +++ b/bms-front/src/api/data/productCodeMap.js @@ -0,0 +1,39 @@ +import request from '@/utils/request' + +export function listProductCodeMap(query) { + return request({ + url: '/data/productCodeMap/list', + method: 'get', + params: query + }) +} + +export function getProductCodeMap(mapId) { + return request({ + url: '/data/productCodeMap/' + mapId, + method: 'get' + }) +} + +export function addProductCodeMap(data) { + return request({ + url: '/data/productCodeMap', + method: 'post', + data + }) +} + +export function updateProductCodeMap(data) { + return request({ + url: '/data/productCodeMap', + method: 'put', + data + }) +} + +export function delProductCodeMap(mapIds) { + return request({ + url: '/data/productCodeMap/' + mapIds, + method: 'delete' + }) +} diff --git a/bms-front/src/api/monitor/orderSource.js b/bms-front/src/api/monitor/orderSource.js index 0f826761..11f566d9 100644 --- a/bms-front/src/api/monitor/orderSource.js +++ b/bms-front/src/api/monitor/orderSource.js @@ -48,3 +48,12 @@ export function getOrderSourceSyncLog(docId) { method: 'get' }) } + +export function checkOrderSourceProducts(data) { + return request({ + url: '/monitor/orderSource/checkProducts', + method: 'post', + data, + timeout: 180000 + }) +} diff --git a/bms-front/src/views/data/productCodeMap/index.vue b/bms-front/src/views/data/productCodeMap/index.vue new file mode 100644 index 00000000..adc22cdb --- /dev/null +++ b/bms-front/src/views/data/productCodeMap/index.vue @@ -0,0 +1,177 @@ + + + diff --git a/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue b/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue new file mode 100644 index 00000000..f1f0b6a8 --- /dev/null +++ b/bms-front/src/views/monitor/orderSource/components/OrderSourceProductCheckDrawer.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/bms-front/src/views/monitor/orderSource/index.vue b/bms-front/src/views/monitor/orderSource/index.vue index 85455fc7..a1370f03 100644 --- a/bms-front/src/views/monitor/orderSource/index.vue +++ b/bms-front/src/views/monitor/orderSource/index.vue @@ -46,6 +46,16 @@ @click="openFilterSync()" >同步计费 + + 检测产品 + + @@ -101,11 +112,14 @@ import { ElMessage } from 'element-plus' import { listOrderSource, syncOrderSourceSelected } from '@/api/monitor/orderSource' import OrderSourceSyncDialog from './components/OrderSourceSyncDialog.vue' import OrderSourceSyncLogDrawer from './components/OrderSourceSyncLogDrawer.vue' +import OrderSourceProductCheckDrawer from './components/OrderSourceProductCheckDrawer.vue' const { proxy } = getCurrentInstance() const tableRef = ref() const syncDialogRef = ref() const logDrawerRef = ref() +const productCheckDrawerRef = ref() +const checkLoading = ref(false) const syncingKey = ref(null) const batchLoading = ref(false) const syncFloat = ref(null) @@ -281,6 +295,24 @@ function handleSyncOne(row) { }) } + +function handleCheckProducts() { + const q = buildFilterQuery() + if (!q.customerCodes || !q.beginTime || !q.endTime) { + ElMessage.warning('请先选择客户和提货时间') + return + } + const codes = Array.isArray(q.customerCodes) ? q.customerCodes.filter(v => v != null && String(v).trim() !== '') : [] + if (!codes.length) { + ElMessage.warning('请先选择客户和提货时间') + return + } + q.customerCodes = codes + checkLoading.value = true + productCheckDrawerRef.value?.open(q) + window.setTimeout(() => { checkLoading.value = false }, 300) +} + function handleBatchSync(selection) { if (!selection || !selection.length) return proxy.$modal.confirm('确认同步选中的 ' + selection.length + ' 条订单?', '二次确认').then(() => {