增加代码检测,和映射

This commit is contained in:
wuweihua
2026-10-13 13:39:15 +08:00
parent b40033f424
commit 52816ad618
22 changed files with 1573 additions and 0 deletions
@@ -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)
{
@@ -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);
}
@@ -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;