增加代码检测,和映射
This commit is contained in:
+73
@@ -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<BmsProductCodeMapping> 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));
|
||||
}
|
||||
}
|
||||
+17
@@ -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")
|
||||
|
||||
+277
@@ -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<PackageProductRow> packageProducts = new ArrayList<PackageProductRow>();
|
||||
private List<OrderProductRow> orderProducts = new ArrayList<OrderProductRow>();
|
||||
private List<OrderProductRow> missingProducts = new ArrayList<OrderProductRow>();
|
||||
|
||||
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<PackageProductRow> getPackageProducts()
|
||||
{
|
||||
return packageProducts;
|
||||
}
|
||||
|
||||
public void setPackageProducts(List<PackageProductRow> packageProducts)
|
||||
{
|
||||
this.packageProducts = packageProducts;
|
||||
}
|
||||
|
||||
public List<OrderProductRow> getOrderProducts()
|
||||
{
|
||||
return orderProducts;
|
||||
}
|
||||
|
||||
public void setOrderProducts(List<OrderProductRow> orderProducts)
|
||||
{
|
||||
this.orderProducts = orderProducts;
|
||||
}
|
||||
|
||||
public List<OrderProductRow> getMissingProducts()
|
||||
{
|
||||
return missingProducts;
|
||||
}
|
||||
|
||||
public void setMissingProducts(List<OrderProductRow> 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<String> sampleOrderIds = new ArrayList<String>();
|
||||
|
||||
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<String> getSampleOrderIds()
|
||||
{
|
||||
return sampleOrderIds;
|
||||
}
|
||||
|
||||
public void setSampleOrderIds(List<String> sampleOrderIds)
|
||||
{
|
||||
this.sampleOrderIds = sampleOrderIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Map<String, Object>> selectTabOrderProductCodes(
|
||||
@Param("orderTable") String orderTable,
|
||||
@Param("query") TabOrderMonitorQuery query);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -55,6 +55,32 @@ public class TabOrderMonitorService
|
||||
}
|
||||
}
|
||||
|
||||
@DataSource(DataSourceType.SLAVE)
|
||||
public List<Map<String, Object>> selectDistinctProductCodes(TabOrderMonitorQuery query)
|
||||
{
|
||||
validate(query);
|
||||
String table = resolveTable();
|
||||
try
|
||||
{
|
||||
PageHelper.clearPage();
|
||||
long t0 = System.currentTimeMillis();
|
||||
List<Map<String, Object>> 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<TabOrderMonitorVo> selectPage(TabOrderMonitorQuery query, int pageNum, int pageSize)
|
||||
{
|
||||
|
||||
+284
@@ -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<Map<String, Object>> groups = tabOrderMonitorService.selectDistinctProductCodes(query);
|
||||
if (groups == null || groups.isEmpty())
|
||||
{
|
||||
result.setOrderTotal(0L);
|
||||
result.setScannedOrders(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
long orderTotal = 0L;
|
||||
Set<String> customerCodes = new HashSet<String>();
|
||||
List<OrderProductRow> orderProducts = new ArrayList<OrderProductRow>();
|
||||
List<OrderProductRow> missingProducts = new ArrayList<OrderProductRow>();
|
||||
|
||||
for (Map<String, Object> 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<String, Set<String>> packageCodeByCustomer = new HashMap<String, Set<String>>();
|
||||
List<PackageProductRow> packageRows = new ArrayList<PackageProductRow>();
|
||||
for (String customerCode : customerCodes)
|
||||
{
|
||||
List<PrcPriceSetProduct> products = tmsPriceQuoteService.listAllBoundPackageProducts(customerCode, asOf);
|
||||
Set<String> codes = new HashSet<String>();
|
||||
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<String, Object> 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<String> 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<String> nullSafe()
|
||||
{
|
||||
return Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
private static String str(Map<String, Object> row, String key)
|
||||
{
|
||||
Object v = value(row, key);
|
||||
return v == null ? null : String.valueOf(v).trim();
|
||||
}
|
||||
|
||||
private static long longValue(Map<String, Object> 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<String, Object> row, String key)
|
||||
{
|
||||
if (row == null || key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (row.containsKey(key))
|
||||
{
|
||||
return row.get(key);
|
||||
}
|
||||
for (Map.Entry<String, Object> e : row.entrySet())
|
||||
{
|
||||
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key))
|
||||
{
|
||||
return e.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -36,4 +36,11 @@ public interface ITmsPriceQuoteService
|
||||
* @param bizDate optional; default today (same as-of window as quote)
|
||||
*/
|
||||
List<PrcPriceSetProduct> 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<PrcPriceSetProduct> listAllBoundPackageProducts(String customerCode, Date bizDate);
|
||||
}
|
||||
|
||||
+87
@@ -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<PrcPriceSetProduct> listAllBoundPackageProducts(String customerCode, Date bizDate)
|
||||
{
|
||||
if (StringUtils.isEmpty(customerCode))
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<PrcCustomerPriceSet> binds = customerPriceSetMapper.selectEffectiveByCustomerCode(customerCode.trim());
|
||||
if (binds == null || binds.isEmpty())
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Date asOf = bizDate != null ? bizDate : new Date();
|
||||
Map<String, PrcPriceSetProduct> byCode = new LinkedHashMap<String, PrcPriceSetProduct>();
|
||||
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<PrcPriceSetProduct> 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<PrcPriceSetProduct>(byCode.values());
|
||||
}
|
||||
|
||||
/** Prefer version active at asOf; else latest version that has set-product rows. */
|
||||
private List<PrcPriceSetProduct> 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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+23
@@ -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<BmsProductCodeMapping> 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);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.bms.system.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.system.domain.BmsProductCodeMapping;
|
||||
|
||||
public interface IBmsProductCodeMappingService
|
||||
{
|
||||
List<BmsProductCodeMapping> 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);
|
||||
}
|
||||
+107
@@ -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<BmsProductCodeMapping> 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产品代码不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.system.mapper.BmsProductCodeMappingMapper">
|
||||
|
||||
<resultMap type="BmsProductCodeMapping" id="BmsProductCodeMappingResult">
|
||||
<id property="mapId" column="map_id" />
|
||||
<result property="upstreamCode" column="upstream_code" />
|
||||
<result property="bmsProductCode" column="bms_product_code" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectVo">
|
||||
select map_id, upstream_code, bms_product_code, status, remark,
|
||||
create_by, create_time, update_by, update_time, del_flag
|
||||
from product_code_mapping
|
||||
</sql>
|
||||
|
||||
<select id="selectList" parameterType="BmsProductCodeMapping" resultMap="BmsProductCodeMappingResult">
|
||||
<include refid="selectVo"/>
|
||||
<where>
|
||||
del_flag = '0'
|
||||
<if test="upstreamCode != null and upstreamCode != ''">
|
||||
and upstream_code like concat('%', #{upstreamCode}, '%')
|
||||
</if>
|
||||
<if test="bmsProductCode != null and bmsProductCode != ''">
|
||||
and bms_product_code like concat('%', #{bmsProductCode}, '%')
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
and status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
order by map_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectById" parameterType="Long" resultMap="BmsProductCodeMappingResult">
|
||||
<include refid="selectVo"/>
|
||||
where map_id = #{mapId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectActiveByUpstreamCode" resultMap="BmsProductCodeMappingResult">
|
||||
<include refid="selectVo"/>
|
||||
where del_flag = '0' and status = '0' and upstream_code = #{upstreamCode}
|
||||
order by map_id desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="countByUpstreamCode" resultType="int">
|
||||
select count(1) from product_code_mapping
|
||||
where del_flag = '0' and upstream_code = #{upstreamCode}
|
||||
<if test="excludeId != null">and map_id != #{excludeId}</if>
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="BmsProductCodeMapping" useGeneratedKeys="true" keyProperty="mapId">
|
||||
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'
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="BmsProductCodeMapping">
|
||||
update product_code_mapping
|
||||
<set>
|
||||
<if test="upstreamCode != null and upstreamCode != ''">upstream_code = #{upstreamCode},</if>
|
||||
<if test="bmsProductCode != null and bmsProductCode != ''">bms_product_code = #{bmsProductCode},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where map_id = #{mapId}
|
||||
</update>
|
||||
|
||||
<update id="deleteByIds" parameterType="Long">
|
||||
update product_code_mapping
|
||||
set del_flag = '1',
|
||||
upstream_code = concat(upstream_code, '#DEL#', map_id),
|
||||
update_time = sysdate()
|
||||
where map_id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -107,4 +107,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
WHERE rn > #{startRow}
|
||||
</select>
|
||||
|
||||
<!-- Distinct product codes under the same filters (no pagination). -->
|
||||
<select id="selectTabOrderProductCodes" resultType="java.util.HashMap">
|
||||
SELECT o.CUSTOMER_CODE AS CUSTOMER_CODE,
|
||||
o.PRODUCT_CODE AS PRODUCT_CODE,
|
||||
MAX(o.PRODUCT_NAME) AS PRODUCT_NAME,
|
||||
COUNT(1) AS ORDER_COUNT
|
||||
FROM ${orderTable} o
|
||||
WHERE
|
||||
<include refid="tabOrderMonitorWhere"/>
|
||||
AND o.PRODUCT_CODE IS NOT NULL
|
||||
GROUP BY o.CUSTOMER_CODE, o.PRODUCT_CODE
|
||||
ORDER BY o.CUSTOMER_CODE, o.PRODUCT_CODE
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -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='产品代码映射表';
|
||||
@@ -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);
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<basic-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
row-key="mapId"
|
||||
selection
|
||||
>
|
||||
<template #toolbar="{ ids }">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['data:productCodeMap:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="!ids.length" @click="handleDelete(ids)" v-hasPermi="['data:productCodeMap:remove']">删除</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #col-status="{ row }">
|
||||
<el-tag size="small" :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '正常' : '停用' }}</el-tag>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['data:productCodeMap:edit']">修改</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete([row.mapId])" v-hasPermi="['data:productCodeMap:remove']">删除</el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<el-dialog :title="title" v-model="open" width="520px" append-to-body destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="上游产品代码" prop="upstreamCode">
|
||||
<el-input v-model="form.upstreamCode" placeholder="上游/渠道产品代码" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="BMS产品代码" prop="bmsProductCode">
|
||||
<el-select
|
||||
v-model="form.bmsProductCode"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
placeholder="请输入产品代码或名称"
|
||||
:remote-method="searchProduct"
|
||||
:loading="productLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in productOptions"
|
||||
:key="p.productCode"
|
||||
:label="(p.productName || '') + ' (' + p.productCode + ')'"
|
||||
:value="p.productCode"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio value="0">正常</el-radio>
|
||||
<el-radio value="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<el-button @click="open = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="ProductCodeMap">
|
||||
import { listProductCodeMap, getProductCodeMap, addProductCodeMap, updateProductCodeMap, delProductCodeMap } from '@/api/data/productCodeMap'
|
||||
import { listProductOptions } from '@/api/price/product'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const tableRef = ref()
|
||||
const formRef = ref()
|
||||
const open = ref(false)
|
||||
const title = ref('')
|
||||
const productLoading = ref(false)
|
||||
const productOptions = ref([])
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '正常', value: '0' },
|
||||
{ label: '停用', value: '1' }
|
||||
]
|
||||
|
||||
const searchColumns = [
|
||||
{ label: '上游代码', prop: 'upstreamCode', component: 'input', placeholder: '上游产品代码', width: '180px' },
|
||||
{ label: 'BMS代码', prop: 'bmsProductCode', component: 'input', placeholder: 'BMS产品代码', width: '180px' },
|
||||
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '请选择', width: '140px', props: { clearable: true } }
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ label: '上游产品代码', prop: 'upstreamCode', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: 'BMS产品代码', prop: 'bmsProductCode', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: '状态', prop: 'status', align: 'center', width: 90, slot: 'status' },
|
||||
{ label: '备注', prop: 'remark', minWidth: 160, showOverflowTooltip: true },
|
||||
{ label: '创建时间', prop: 'createTime', align: 'center', width: 170 },
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 150, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
rules: {
|
||||
upstreamCode: [{ required: true, message: '请输入上游产品代码', trigger: 'blur' }],
|
||||
bmsProductCode: [{ required: true, message: '请选择BMS产品代码', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
const { form, rules } = toRefs(data)
|
||||
|
||||
function queryList(params) {
|
||||
return listProductCodeMap(params)
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.value = {
|
||||
mapId: undefined,
|
||||
upstreamCode: '',
|
||||
bmsProductCode: '',
|
||||
status: '0',
|
||||
remark: ''
|
||||
}
|
||||
productOptions.value = []
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
resetForm()
|
||||
title.value = '新增代码映射'
|
||||
open.value = true
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
resetForm()
|
||||
getProductCodeMap(row.mapId).then(res => {
|
||||
form.value = Object.assign({}, res.data || {})
|
||||
if (form.value.bmsProductCode) {
|
||||
productOptions.value = [{ productCode: form.value.bmsProductCode, productName: form.value.bmsProductCode }]
|
||||
searchProduct(form.value.bmsProductCode)
|
||||
}
|
||||
title.value = '修改代码映射'
|
||||
open.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
const req = form.value.mapId ? updateProductCodeMap(form.value) : addProductCodeMap(form.value)
|
||||
req.then(() => {
|
||||
proxy.$modal.msgSuccess('保存成功')
|
||||
open.value = false
|
||||
tableRef.value?.refresh()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(ids) {
|
||||
if (!ids || !ids.length) return
|
||||
proxy.$modal.confirm('确认删除选中的 ' + ids.length + ' 条映射?').then(() => {
|
||||
return delProductCodeMap(ids.join(','))
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
tableRef.value?.refresh()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function searchProduct(keyword) {
|
||||
productLoading.value = true
|
||||
listProductOptions({ productCode: keyword || undefined, status: '1' }).then(res => {
|
||||
productOptions.value = res.data || res.rows || []
|
||||
}).finally(() => {
|
||||
productLoading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="920px" title="产品套餐比对" append-to-body destroy-on-close>
|
||||
<div v-loading="loading">
|
||||
<el-empty v-if="!loading && !result" description="暂无结果" />
|
||||
<template v-else-if="result">
|
||||
<el-alert
|
||||
v-if="result.truncated"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="订单超过上限,仅对已扫描部分进行比对"
|
||||
/>
|
||||
<el-descriptions :column="3" border size="small" class="mb12">
|
||||
<el-descriptions-item label="筛选订单数">{{ result.orderTotal ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="已扫描">{{ result.scannedOrders ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="套餐截止时间">{{ result.asOfTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="套餐产品数">{{ result.packageProductCount ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单产品数">{{ result.distinctOrderProducts ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="命中/缺失">
|
||||
<el-tag type="success" size="small">{{ result.matchedCount ?? 0 }}</el-tag>
|
||||
<span class="sep">/</span>
|
||||
<el-tag type="danger" size="small">{{ result.missingCount ?? 0 }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane :label="missingTabLabel" name="missing">
|
||||
<el-table :data="result.missingProducts || []" border size="small" max-height="520">
|
||||
<el-table-column prop="customerCode" label="客户代码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="upstreamCode" label="上游产品代码" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="mappedCode" label="映射后代码" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.mappedCode || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="90" align="center" />
|
||||
<el-table-column label="示例订单" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ (row.sampleOrderIds || []).join(', ') || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!(result.missingProducts || []).length" description="未发现缺失产品" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="全部比对" name="all">
|
||||
<el-table :data="result.orderProducts || []" border size="small" max-height="520">
|
||||
<el-table-column prop="customerCode" label="客户代码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="upstreamCode" label="上游产品代码" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="mappedCode" label="映射后代码" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.mappedCode || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否在套餐" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.covered" size="small" type="success">在套餐</el-tag>
|
||||
<el-tag v-else size="small" type="danger">不在套餐</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="命中方式" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ hitText(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="90" align="center" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="packageTabLabel" name="package">
|
||||
<el-table :data="result.packageProducts || []" border size="small" max-height="520">
|
||||
<el-table-column prop="customerCode" label="客户代码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="productCode" label="套餐产品代码" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty v-if="!(result.packageProducts || []).length" description="客户未绑定套餐产品" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { checkOrderSourceProducts } from '@/api/monitor/orderSource'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const result = ref(null)
|
||||
const activeTab = ref('missing')
|
||||
|
||||
const missingTabLabel = computed(() => {
|
||||
const n = result.value?.missingCount ?? 0
|
||||
return '缺失产品 (' + n + ')'
|
||||
})
|
||||
const packageTabLabel = computed(() => {
|
||||
const n = result.value?.packageProductCount ?? 0
|
||||
return '套餐产品 (' + n + ')'
|
||||
})
|
||||
|
||||
function hitText(row) {
|
||||
if (!row) return '-'
|
||||
if (row.inPackage) return '直接命中'
|
||||
if (row.mappedInPackage) return '映射后命中'
|
||||
if (row.mappedCode) return '有映射但仍不在套餐'
|
||||
return '无映射'
|
||||
}
|
||||
|
||||
function open(query) {
|
||||
if (!query || !query.customerCodes || !query.beginTime || !query.endTime) {
|
||||
ElMessage.warning('请先选择客户和提货时间')
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
activeTab.value = 'missing'
|
||||
result.value = null
|
||||
loading.value = true
|
||||
checkOrderSourceProducts(query).then(res => {
|
||||
result.value = res.data || null
|
||||
if ((result.value?.missingCount || 0) > 0) {
|
||||
activeTab.value = 'missing'
|
||||
} else {
|
||||
activeTab.value = 'all'
|
||||
}
|
||||
}).catch(() => {
|
||||
result.value = null
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 { margin-bottom: 12px; }
|
||||
.sep { margin: 0 6px; color: var(--el-text-color-secondary); }
|
||||
</style>
|
||||
@@ -46,6 +46,16 @@
|
||||
@click="openFilterSync()"
|
||||
>同步计费</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Search"
|
||||
:loading="checkLoading"
|
||||
v-hasPermi="['monitor:orderSource:list']"
|
||||
@click="handleCheckProducts()"
|
||||
>检测产品</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
@@ -93,6 +103,7 @@
|
||||
@success="onSyncSuccess"
|
||||
/>
|
||||
<order-source-sync-log-drawer ref="logDrawerRef" />
|
||||
<order-source-product-check-drawer ref="productCheckDrawerRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user