订单数据源增加订单对比功能
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
package com.bms.price.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Order-bill product check: customer package (sales) products vs product catalog.
|
||||
*/
|
||||
@Data
|
||||
public class OrderBillProductCheckResult implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Orders matched by search filters (customer + billing time). */
|
||||
private long orderTotal;
|
||||
private int packageProductCount;
|
||||
private int matchedCount;
|
||||
private int missingCount;
|
||||
private String asOfTime;
|
||||
private List<ProductRow> packageProducts = new ArrayList<ProductRow>();
|
||||
private List<ProductRow> missingProducts = new ArrayList<ProductRow>();
|
||||
|
||||
@Data
|
||||
public static class ProductRow implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String customerCode;
|
||||
private String productCode;
|
||||
private String productName;
|
||||
/** Whether product exists in catalog (effective at asOf). */
|
||||
private boolean inLibrary;
|
||||
/** Order count in search billing window for this product. */
|
||||
private int orderCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bms.price.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单比对异步任务进度(Redis)。
|
||||
* ok=本地已有;fail/缺失=上游有而本地无(仅转单号)。
|
||||
*/
|
||||
public class TabOrderCompareTask implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String STATUS_PENDING = "PENDING";
|
||||
public static final String STATUS_RUNNING = "RUNNING";
|
||||
public static final String STATUS_SUCCESS = "SUCCESS";
|
||||
public static final String STATUS_FAIL = "FAIL";
|
||||
|
||||
private String taskId;
|
||||
private String status;
|
||||
private int percent;
|
||||
private String message;
|
||||
/** 已扫描上游转单号数 */
|
||||
private int scanned;
|
||||
/** 本地已存在 */
|
||||
private int matched;
|
||||
/** 本地缺失 */
|
||||
private int missing;
|
||||
private String error;
|
||||
/** 缺失转单号样本(有上限) */
|
||||
private List<String> missingTransferNos = new ArrayList<String>();
|
||||
|
||||
public String getTaskId() { return taskId; }
|
||||
public void setTaskId(String taskId) { this.taskId = taskId; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public int getPercent() { return percent; }
|
||||
public void setPercent(int percent) { this.percent = percent; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public int getScanned() { return scanned; }
|
||||
public void setScanned(int scanned) { this.scanned = scanned; }
|
||||
public int getMatched() { return matched; }
|
||||
public void setMatched(int matched) { this.matched = matched; }
|
||||
public int getMissing() { return missing; }
|
||||
public void setMissing(int missing) { this.missing = missing; }
|
||||
public String getError() { return error; }
|
||||
public void setError(String error) { this.error = error; }
|
||||
public List<String> getMissingTransferNos() { return missingTransferNos; }
|
||||
public void setMissingTransferNos(List<String> missingTransferNos)
|
||||
{
|
||||
this.missingTransferNos = missingTransferNos == null
|
||||
? new ArrayList<String>() : missingTransferNos;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,15 @@ public interface OracleUpstreamOrderMapper
|
||||
@Param("startRow") long startRow,
|
||||
@Param("endRow") long endRow);
|
||||
|
||||
/**
|
||||
* 订单比对专用(异步分页):仅返回 TRANSFER_ORDER_ID。
|
||||
*/
|
||||
List<Map<String, Object>> selectTabOrderCompareKeyPage(
|
||||
@Param("orderTable") String orderTable,
|
||||
@Param("query") TabOrderMonitorQuery query,
|
||||
@Param("startRow") long startRow,
|
||||
@Param("endRow") long endRow);
|
||||
|
||||
/** List detail by ids: display columns only. */
|
||||
List<Map<String, Object>> selectTabOrderMonitorListByIds(
|
||||
@Param("orderTable") String orderTable,
|
||||
@@ -54,6 +63,16 @@ public interface OracleUpstreamOrderMapper
|
||||
@Param("query") TabOrderMonitorQuery query,
|
||||
@Param("batch") List<String> batch);
|
||||
|
||||
/**
|
||||
* 比对回填转单号专用:仅按 TRANSFER_ORDER_ID IN 分批收集,
|
||||
* 不走提货时间,与 selectMatchedOrderKeys 完全隔离。
|
||||
*/
|
||||
List<Map<String, Object>> selectMatchedTransferOrderKeys(
|
||||
@Param("orderTable") String orderTable,
|
||||
@Param("batch") List<String> batch,
|
||||
@Param("customerCodes") List<String> customerCodes);
|
||||
|
||||
|
||||
/** 按已收集到的订单主键批量查明细(一批为一个内存分页切片)。 */
|
||||
List<Map<String, Object>> selectTabOrderMonitorByIds(
|
||||
@Param("orderTable") String orderTable,
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bms.price.mapper;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.PrcOrderBill;
|
||||
|
||||
@@ -20,6 +21,22 @@ public interface PrcOrderBillMapper
|
||||
|
||||
public List<PrcOrderBill> selectPrcOrderBillList(PrcOrderBill bill);
|
||||
|
||||
/** Distinct customer+product groups for product-check (order counts in filter window). */
|
||||
List<Map<String, Object>> selectDistinctProductGroups(PrcOrderBill bill);
|
||||
|
||||
/** Drill-down orders for one product under product-check filters. */
|
||||
List<PrcOrderBill> selectOrderBillsByProductForCheck(@Param("query") PrcOrderBill query, @Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* 订单比对:批量查本地已存在的单号(order_no / tracking_no / transfer_no)。
|
||||
*/
|
||||
List<String> selectExistingOrderIdentifiers(@Param("identifiers") List<String> identifiers,
|
||||
@Param("customerCodes") List<String> customerCodes);
|
||||
|
||||
/** 订单比对:仅按转单号批量查本地已存在。 */
|
||||
List<String> selectExistingTransferNos(@Param("transferNos") List<String> transferNos,
|
||||
@Param("customerCodes") List<String> customerCodes);
|
||||
|
||||
/** 订单号下拉搜索(带数据权限) */
|
||||
public List<String> selectOrderNoOptions(@Param("query") PrcOrderBill query, @Param("limit") int limit);
|
||||
|
||||
|
||||
+7
@@ -25,4 +25,11 @@ public class OracleOrderKeyCollectWorker
|
||||
{
|
||||
return oracleUpstreamOrderMapper.selectMatchedOrderKeys(table, query, batch);
|
||||
}
|
||||
|
||||
/** 比对回填转单号专用分批(不走三列 UNION)。 */
|
||||
@DataSource(DataSourceType.SLAVE)
|
||||
public List<Map<String, Object>> collectTransferBatch(String table, List<String> batch, List<String> customerCodes)
|
||||
{
|
||||
return oracleUpstreamOrderMapper.selectMatchedTransferOrderKeys(table, batch, customerCodes);
|
||||
}
|
||||
}
|
||||
|
||||
+307
-18
@@ -62,10 +62,11 @@ public class TabOrderMonitorService
|
||||
private static final int COLLECT_CACHE_MAX_ENTRIES = 20;
|
||||
|
||||
/**
|
||||
* 客户+时间列表:一次拉取的主键上限。一次取齐后内存分页,总数稳定;
|
||||
* 超过此数量仅展示前 N 条(避免 COUNT / 翻页估总数导致「共 101→201」)。
|
||||
* 客户+时间列表:一次 ROWNUM 拉主键后内存分页(总数稳定)。
|
||||
* 上限 8000:覆盖常见月窗口,又避免分批深翻 ROWNUM 把查询拖死。
|
||||
* 全量差异请用「订单比对」异步任务。
|
||||
*/
|
||||
private static final int FILTER_LIST_KEY_LIMIT = 2000;
|
||||
private static final int FILTER_LIST_KEY_LIMIT = 8000;
|
||||
|
||||
/** 单批 Oracle 查询最长等待(从库 socketTimeout 为 120s,留同样余量)。 */
|
||||
private static final long COLLECT_BATCH_TIMEOUT_SECONDS = 120L;
|
||||
@@ -121,6 +122,19 @@ public class TabOrderMonitorService
|
||||
}
|
||||
});
|
||||
|
||||
/** 客户+时间精确总数缓存(翻页复用,避免每页重复 COUNT)。 */
|
||||
private final Map<String, FilterCountCacheEntry> filterCountCache =
|
||||
Collections.synchronizedMap(new LinkedHashMap<String, FilterCountCacheEntry>(16, 0.75f, true)
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<String, FilterCountCacheEntry> eldest)
|
||||
{
|
||||
return size() > COLLECT_CACHE_MAX_ENTRIES;
|
||||
}
|
||||
});
|
||||
|
||||
private static final class CacheEntry
|
||||
{
|
||||
private final List<MatchedOrderKey> keys;
|
||||
@@ -145,6 +159,18 @@ public class TabOrderMonitorService
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FilterCountCacheEntry
|
||||
{
|
||||
private final long total;
|
||||
private final long expireAt;
|
||||
|
||||
private FilterCountCacheEntry(long total, long expireAt)
|
||||
{
|
||||
this.total = total;
|
||||
this.expireAt = expireAt;
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdownCollectPool()
|
||||
{
|
||||
@@ -261,6 +287,72 @@ public class TabOrderMonitorService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单比对异步分页:专用 SQL 仅返回当前页转单号。
|
||||
*/
|
||||
@DataSource(DataSourceType.SLAVE)
|
||||
public List<String> selectCompareTransferPage(TabOrderMonitorQuery query, int pageNum, int pageSize)
|
||||
{
|
||||
// 比对与回填列表完全隔离:先清单号再 validate,仅走客户+提货时间
|
||||
if (query != null)
|
||||
{
|
||||
query.setOrderIdentifiers(null);
|
||||
query.setOrderId(null);
|
||||
query.setOrgOrderId(null);
|
||||
query.setTransferOrderId(null);
|
||||
}
|
||||
validate(query);
|
||||
List<String> codes = query.getCustomerCodes();
|
||||
boolean hasCustomer = codes != null && !codes.isEmpty();
|
||||
boolean hasTime = StringUtils.isNotEmpty(query.getBeginTime()) && StringUtils.isNotEmpty(query.getEndTime());
|
||||
if (!hasCustomer || !hasTime)
|
||||
{
|
||||
throw new ServiceException("订单比对需先选择客户和提货时间");
|
||||
}
|
||||
int pn = pageNum < 1 ? 1 : pageNum;
|
||||
int ps = pageSize < 1 ? 100 : pageSize;
|
||||
if (ps > 1000)
|
||||
{
|
||||
ps = 1000;
|
||||
}
|
||||
long endRow = (long) pn * ps;
|
||||
long startRow = endRow - ps;
|
||||
String table = resolveTable();
|
||||
try
|
||||
{
|
||||
PageHelper.clearPage();
|
||||
List<Map<String, Object>> rows =
|
||||
oracleUpstreamOrderMapper.selectTabOrderCompareKeyPage(table, query, startRow, endRow);
|
||||
if (rows == null || rows.isEmpty())
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> out = new ArrayList<String>(rows.size());
|
||||
for (Map<String, Object> row : rows)
|
||||
{
|
||||
Object v = value(row, "TRANSFER_ORDER_ID");
|
||||
if (v == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String tn = String.valueOf(v).trim();
|
||||
if (!tn.isEmpty())
|
||||
{
|
||||
out.add(tn);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new ServiceException("Oracle TAB_ORDER compare page failed: " + e.getMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
PageHelper.clearPage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表查询统一入口:一次数据源调用内返回总数 + 当前页,
|
||||
* 大批量单号场景只做一次分批收集,避免 count 和 page 重复扫描。
|
||||
@@ -279,15 +371,67 @@ public class TabOrderMonitorService
|
||||
List<MatchedOrderKey> keys = doCollectMatchedKeys(query, table);
|
||||
List<TabOrderMonitorVo> rows = doPageByKeys(table, keys, pn, ps);
|
||||
log.info("oracle tab_order identifier page identifiers={} matched={} page={}/{} rows={} costMs={}",
|
||||
Integer.valueOf(query.getOrderIdentifiers().size()), Integer.valueOf(keys.size()),
|
||||
Integer.valueOf(query.getOrderIdentifiers().size()),
|
||||
Integer.valueOf(keys.size()),
|
||||
Integer.valueOf(pn), Integer.valueOf(ps), Integer.valueOf(rows.size()),
|
||||
Long.valueOf(System.currentTimeMillis() - t0));
|
||||
return new TabOrderPageResult(keys.size(), rows);
|
||||
}
|
||||
// Customer+time: collect keys once (capped), memory-page; stable total (no 101->201).
|
||||
// Customer+time: exact COUNT(1) + current-page keys.
|
||||
return doSelectFilterListPage(table, query, pn, ps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比对缺失转单号回填搜索专用入口:
|
||||
* 仅按 TRANSFER_ORDER_ID 分批收集,不带提货时间,
|
||||
* 与列表/同步/比对共用 searchPage 完全隔离。
|
||||
*/
|
||||
@DataSource(DataSourceType.SLAVE)
|
||||
public TabOrderPageResult searchPageByTransferNos(TabOrderMonitorQuery query, int pageNum, int pageSize)
|
||||
{
|
||||
if (query == null || query.getOrderIdentifiers() == null || query.getOrderIdentifiers().isEmpty())
|
||||
{
|
||||
throw new ServiceException("转单号不能为空");
|
||||
}
|
||||
List<String> nos = normalizeLineList(query.getOrderIdentifiers());
|
||||
if (nos == null || nos.isEmpty())
|
||||
{
|
||||
throw new ServiceException("转单号不能为空");
|
||||
}
|
||||
if (nos.size() > IDENTIFIER_HARD_LIMIT)
|
||||
{
|
||||
throw new ServiceException("单号数量超过服务端上限 " + IDENTIFIER_HARD_LIMIT
|
||||
+ ",请分批查询,避免内存溢出");
|
||||
}
|
||||
// optional customer filter only; never use pickup time on this path
|
||||
List<String> codes = query.getCustomerCodes();
|
||||
List<String> cleanedCodes = new ArrayList<String>();
|
||||
if (codes != null)
|
||||
{
|
||||
for (String c : codes)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(c))
|
||||
{
|
||||
cleanedCodes.add(c.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> customerCodes = cleanedCodes.isEmpty() ? null : cleanedCodes;
|
||||
int[] page = normalizePage(pageNum, pageSize);
|
||||
int pn = page[0];
|
||||
int ps = page[1];
|
||||
String table = resolveTable();
|
||||
long t0 = System.currentTimeMillis();
|
||||
List<MatchedOrderKey> keys = doCollectTransferKeys(table, nos, customerCodes);
|
||||
List<TabOrderMonitorVo> rows = doPageByKeys(table, keys, pn, ps);
|
||||
log.info("oracle tab_order transfer-fill page transferNos={} matched={} page={}/{} rows={} costMs={}",
|
||||
Integer.valueOf(nos.size()), Integer.valueOf(keys.size()),
|
||||
Integer.valueOf(pn), Integer.valueOf(ps), Integer.valueOf(rows.size()),
|
||||
Long.valueOf(System.currentTimeMillis() - t0));
|
||||
return new TabOrderPageResult(keys.size(), rows);
|
||||
}
|
||||
|
||||
|
||||
@DataSource(DataSourceType.SLAVE)
|
||||
public List<TabOrderMonitorVo> selectPage(TabOrderMonitorQuery query, int pageNum, int pageSize)
|
||||
{
|
||||
@@ -360,34 +504,67 @@ public class TabOrderMonitorService
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer+time list: pull up to {@link #FILTER_LIST_KEY_LIMIT} keys once (cached),
|
||||
* then memory-page and load display rows by ID. Total stays stable across pages.
|
||||
* Customer+time list: exact COUNT(1) for total + key-page for current rows.
|
||||
* Total uses countTabOrderMonitor (same filters as Oct-13 style COUNT).
|
||||
*/
|
||||
private TabOrderPageResult doSelectFilterListPage(String table, TabOrderMonitorQuery query, int pn, int ps)
|
||||
{
|
||||
long t0 = System.currentTimeMillis();
|
||||
List<String> allIds = doCollectFilterKeyIds(table, query);
|
||||
int total = allIds.size();
|
||||
int from = (pn - 1) * ps;
|
||||
if (from >= total)
|
||||
long total = countFilterTotal(table, query);
|
||||
long startRow = (long) (pn - 1) * ps;
|
||||
long endRow = startRow + ps;
|
||||
if (startRow >= total)
|
||||
{
|
||||
log.info("oracle tab_order filter-list customerSize={} begin={} end={} page={}/{} rows=0 total={} costMs={}",
|
||||
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(),
|
||||
query.getBeginTime(), query.getEndTime(), pn, ps, Integer.valueOf(total),
|
||||
query.getBeginTime(), query.getEndTime(), pn, ps, Long.valueOf(total),
|
||||
Long.valueOf(System.currentTimeMillis() - t0));
|
||||
return new TabOrderPageResult(total, Collections.emptyList());
|
||||
}
|
||||
List<String> slice = allIds.subList(from, Math.min(from + ps, total));
|
||||
List<TabOrderMonitorVo> list = loadListVosByIds(table, new ArrayList<String>(slice));
|
||||
List<String> ids = selectKeyIds(table, query, startRow, endRow);
|
||||
List<TabOrderMonitorVo> list = loadListVosByIds(table, ids);
|
||||
log.info("oracle tab_order filter-list customerSize={} begin={} end={} page={}/{} rows={} total={} costMs={}",
|
||||
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(),
|
||||
query.getBeginTime(), query.getEndTime(), pn, ps,
|
||||
Integer.valueOf(list.size()), Integer.valueOf(total),
|
||||
Integer.valueOf(list.size()), Long.valueOf(total),
|
||||
Long.valueOf(System.currentTimeMillis() - t0));
|
||||
return new TabOrderPageResult(total, list);
|
||||
}
|
||||
|
||||
/** Collect (and cache) filter-path primary keys, newest first, capped at FILTER_LIST_KEY_LIMIT. */
|
||||
/** Exact total for customer+time list; short TTL cache to avoid recount on every page flip. */
|
||||
private long countFilterTotal(String table, TabOrderMonitorQuery query)
|
||||
{
|
||||
String fingerprint = "count|" + collectFingerprint(table, query);
|
||||
FilterCountCacheEntry cached = filterCountCache.get(fingerprint);
|
||||
long now = System.currentTimeMillis();
|
||||
if (cached != null && cached.expireAt > now)
|
||||
{
|
||||
return cached.total;
|
||||
}
|
||||
try
|
||||
{
|
||||
PageHelper.clearPage();
|
||||
long t0 = System.currentTimeMillis();
|
||||
long total = oracleUpstreamOrderMapper.countTabOrderMonitor(table, query);
|
||||
log.info("oracle tab_order count customerSize={} begin={} end={} total={} costMs={}",
|
||||
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(),
|
||||
query.getBeginTime(), query.getEndTime(),
|
||||
Long.valueOf(total), Long.valueOf(System.currentTimeMillis() - t0));
|
||||
filterCountCache.put(fingerprint,
|
||||
new FilterCountCacheEntry(total, now + COLLECT_CACHE_TTL_MS));
|
||||
return total;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new ServiceException("Oracle TAB_ORDER count failed: " + e.getMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
PageHelper.clearPage();
|
||||
}
|
||||
}
|
||||
|
||||
/** Collect (and cache) filter-path primary keys, newest first; hard-capped at FILTER_LIST_KEY_LIMIT. */
|
||||
private List<String> doCollectFilterKeyIds(String table, TabOrderMonitorQuery query)
|
||||
{
|
||||
String fingerprint = "filter|" + collectFingerprint(table, query);
|
||||
@@ -397,11 +574,11 @@ public class TabOrderMonitorService
|
||||
{
|
||||
return new ArrayList<String>(cached.ids);
|
||||
}
|
||||
// Fetch limit+1 to detect cap; keep at most FILTER_LIST_KEY_LIMIT.
|
||||
// One Oracle key-page (ROWNUM <= limit+1). Avoid deep multi-page ROWNUM which hangs month windows.
|
||||
List<String> ids = selectKeyIds(table, query, 0L, FILTER_LIST_KEY_LIMIT + 1L);
|
||||
if (ids.size() > FILTER_LIST_KEY_LIMIT)
|
||||
{
|
||||
log.warn("oracle tab_order filter-list capped at {} keys (customer+time window too large); narrow pickup range",
|
||||
log.warn("oracle tab_order filter-list capped at {} keys (customer+time window too large); use order compare for full diff",
|
||||
Integer.valueOf(FILTER_LIST_KEY_LIMIT));
|
||||
ids = new ArrayList<String>(ids.subList(0, FILTER_LIST_KEY_LIMIT));
|
||||
}
|
||||
@@ -575,6 +752,118 @@ public class TabOrderMonitorService
|
||||
* 查 Oracle(每批 UNION ALL 三列各走索引),Java 侧按 ID 去重,
|
||||
* 再按 PICKUP_DATE DESC, ID DESC 排序。相同查询条件短期缓存,翻页/重复查询秒回。
|
||||
*/
|
||||
/**
|
||||
* 回填转单号专用收集:只走 selectMatchedTransferOrderKeys,
|
||||
* 不走三列 UNION、不带提货时间。
|
||||
*/
|
||||
private List<MatchedOrderKey> doCollectTransferKeys(String table, List<String> transferNos, List<String> customerCodes)
|
||||
{
|
||||
StringBuilder fp = new StringBuilder("tf|").append(table == null ? "" : table).append('|');
|
||||
appendSorted(fp, transferNos);
|
||||
fp.append('|');
|
||||
appendSorted(fp, customerCodes);
|
||||
String fingerprint = Integer.toHexString(fp.toString().hashCode());
|
||||
long now = System.currentTimeMillis();
|
||||
CacheEntry cached = collectCache.get(fingerprint);
|
||||
if (cached != null && now < cached.expireAt)
|
||||
{
|
||||
log.info("oracle tab_order transfer-fill cache-hit transferNos={} matched={}",
|
||||
Integer.valueOf(transferNos.size()), Integer.valueOf(cached.keys.size()));
|
||||
return new ArrayList<MatchedOrderKey>(cached.keys);
|
||||
}
|
||||
|
||||
List<List<String>> batches = new ArrayList<List<String>>();
|
||||
for (int bi = 0; bi < transferNos.size(); bi += IDENTIFIER_BATCH_SIZE)
|
||||
{
|
||||
batches.add(transferNos.subList(bi, Math.min(bi + IDENTIFIER_BATCH_SIZE, transferNos.size())));
|
||||
}
|
||||
|
||||
List<Future<List<Map<String, Object>>>> futures =
|
||||
new ArrayList<Future<List<Map<String, Object>>>>(batches.size());
|
||||
for (List<String> batch : batches)
|
||||
{
|
||||
futures.add(collectPool.submit(() -> collectWorker.collectTransferBatch(table, batch, customerCodes)));
|
||||
}
|
||||
|
||||
Map<String, Timestamp> unique = new LinkedHashMap<String, Timestamp>();
|
||||
long t0 = System.currentTimeMillis();
|
||||
try
|
||||
{
|
||||
for (Future<List<Map<String, Object>>> future : futures)
|
||||
{
|
||||
List<Map<String, Object>> rows;
|
||||
try
|
||||
{
|
||||
rows = future.get(COLLECT_BATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||
throw new ServiceException("Oracle transfer-fill collect failed: " + cause.getMessage());
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
future.cancel(true);
|
||||
throw new ServiceException("Oracle transfer-fill collect timeout");
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ServiceException("Oracle transfer-fill collect interrupted");
|
||||
}
|
||||
if (rows == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (Map<String, Object> row : rows)
|
||||
{
|
||||
Object idVal = value(row, "ID");
|
||||
if (idVal == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String id = String.valueOf(idVal).trim();
|
||||
if (id.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Object dateVal = value(row, "PICKUP_DATE");
|
||||
Timestamp pickup = dateVal instanceof Timestamp ? (Timestamp) dateVal : null;
|
||||
if (!unique.containsKey(id) || (unique.get(id) == null && pickup != null))
|
||||
{
|
||||
unique.put(id, pickup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (Future<List<Map<String, Object>>> f : futures)
|
||||
{
|
||||
if (!f.isDone())
|
||||
{
|
||||
f.cancel(true);
|
||||
}
|
||||
}
|
||||
PageHelper.clearPage();
|
||||
}
|
||||
List<MatchedOrderKey> keys = new ArrayList<MatchedOrderKey>(unique.size());
|
||||
for (Map.Entry<String, Timestamp> e : unique.entrySet())
|
||||
{
|
||||
keys.add(new MatchedOrderKey(e.getKey(), e.getValue()));
|
||||
}
|
||||
keys.sort(Comparator
|
||||
.comparing(MatchedOrderKey::getPickupDate,
|
||||
Comparator.nullsFirst(Comparator.<Timestamp>reverseOrder()))
|
||||
.thenComparing(MatchedOrderKey::getId, Comparator.<String>reverseOrder()));
|
||||
log.info("oracle tab_order transfer-fill collect transferNos={} batches={} matched={} costMs={}",
|
||||
Integer.valueOf(transferNos.size()), Integer.valueOf(batches.size()),
|
||||
Integer.valueOf(keys.size()), Long.valueOf(System.currentTimeMillis() - t0));
|
||||
collectCache.put(fingerprint, new CacheEntry(Collections.unmodifiableList(keys),
|
||||
System.currentTimeMillis() + COLLECT_CACHE_TTL_MS));
|
||||
return keys;
|
||||
}
|
||||
|
||||
private List<MatchedOrderKey> doCollectMatchedKeys(TabOrderMonitorQuery query, String table)
|
||||
{
|
||||
List<String> identifiers = query.getOrderIdentifiers();
|
||||
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
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.annotation.DataScope;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.PrcOrderBill;
|
||||
import com.bms.price.domain.PrcPriceSetProduct;
|
||||
import com.bms.price.domain.vo.OrderBillProductCheckResult;
|
||||
import com.bms.price.domain.vo.OrderBillProductCheckResult.ProductRow;
|
||||
import com.bms.price.mapper.BmsProductMapper;
|
||||
import com.bms.price.mapper.PrcOrderBillMapper;
|
||||
|
||||
/**
|
||||
* Detect package sales products (as of billing end time) that are missing from product catalog.
|
||||
*/
|
||||
@Service
|
||||
public class OrderBillProductCheckService
|
||||
{
|
||||
private static final int ORDER_DRILL_LIMIT = 500;
|
||||
|
||||
@Autowired
|
||||
private ITmsPriceQuoteService tmsPriceQuoteService;
|
||||
|
||||
@Autowired
|
||||
private BmsProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcOrderBillMapper orderBillMapper;
|
||||
|
||||
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id",
|
||||
permission = "price:orderBill:list")
|
||||
public OrderBillProductCheckResult check(PrcOrderBill query)
|
||||
{
|
||||
validate(query);
|
||||
Date asOf = resolveAsOf(query);
|
||||
OrderBillProductCheckResult result = new OrderBillProductCheckResult();
|
||||
result.setAsOfTime(formatAsOf(asOf));
|
||||
|
||||
List<String> customerCodes = normalizeCodes(query.getCustomerCodes());
|
||||
Map<String, Integer> orderCountByKey = loadOrderCounts(query);
|
||||
long orderTotal = 0L;
|
||||
for (Integer c : orderCountByKey.values())
|
||||
{
|
||||
if (c != null)
|
||||
{
|
||||
orderTotal += c.intValue();
|
||||
}
|
||||
}
|
||||
result.setOrderTotal(orderTotal);
|
||||
|
||||
List<ProductRow> packageRows = new ArrayList<ProductRow>();
|
||||
List<ProductRow> missingRows = new ArrayList<ProductRow>();
|
||||
int matched = 0;
|
||||
|
||||
for (String customerCode : customerCodes)
|
||||
{
|
||||
List<PrcPriceSetProduct> products = tmsPriceQuoteService.listAllBoundPackageProducts(customerCode, asOf);
|
||||
Set<String> seen = new HashSet<String>();
|
||||
if (products == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (PrcPriceSetProduct p : products)
|
||||
{
|
||||
if (p == null || StringUtils.isEmpty(p.getProductCode()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String code = p.getProductCode().trim();
|
||||
String upper = code.toUpperCase(Locale.ROOT);
|
||||
if (!seen.add(upper))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
boolean inLibrary = existsInLibrary(code, asOf);
|
||||
ProductRow row = new ProductRow();
|
||||
row.setCustomerCode(customerCode);
|
||||
row.setProductCode(code);
|
||||
row.setProductName(trim(p.getProductName()));
|
||||
row.setInLibrary(inLibrary);
|
||||
Integer oc = orderCountByKey.get(key(customerCode, upper));
|
||||
row.setOrderCount(oc == null ? 0 : oc.intValue());
|
||||
packageRows.add(row);
|
||||
if (inLibrary)
|
||||
{
|
||||
matched++;
|
||||
}
|
||||
else
|
||||
{
|
||||
missingRows.add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packageRows.sort(Comparator
|
||||
.comparing(ProductRow::getCustomerCode, nullSafe())
|
||||
.thenComparing(ProductRow::getProductCode, nullSafe()));
|
||||
missingRows.sort(Comparator
|
||||
.comparing(ProductRow::getCustomerCode, nullSafe())
|
||||
.thenComparing(ProductRow::getProductCode, nullSafe()));
|
||||
|
||||
result.setPackageProductCount(packageRows.size());
|
||||
result.setMatchedCount(matched);
|
||||
result.setMissingCount(missingRows.size());
|
||||
result.setPackageProducts(packageRows);
|
||||
result.setMissingProducts(missingRows);
|
||||
return result;
|
||||
}
|
||||
|
||||
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id",
|
||||
permission = "price:orderBill:list")
|
||||
public List<PrcOrderBill> listOrdersByProduct(PrcOrderBill query)
|
||||
{
|
||||
validate(query);
|
||||
if (StringUtils.isEmpty(query.getProductCode()))
|
||||
{
|
||||
throw new ServiceException("productCode required");
|
||||
}
|
||||
query.setProductCode(query.getProductCode().trim());
|
||||
List<PrcOrderBill> rows = orderBillMapper.selectOrderBillsByProductForCheck(query, ORDER_DRILL_LIMIT);
|
||||
return rows == null ? new ArrayList<PrcOrderBill>() : rows;
|
||||
}
|
||||
|
||||
private Map<String, Integer> loadOrderCounts(PrcOrderBill query)
|
||||
{
|
||||
List<Map<String, Object>> groups = orderBillMapper.selectDistinctProductGroups(query);
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
if (groups == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
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;
|
||||
}
|
||||
map.put(key(customerCode, productCode.toUpperCase(Locale.ROOT)),
|
||||
Integer.valueOf((int) Math.min(Integer.MAX_VALUE, longValue(g, "order_count"))));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private boolean existsInLibrary(String productCode, Date asOf)
|
||||
{
|
||||
BmsProduct hit = productMapper.selectEffectiveBmsProductByCode(productCode, asOf);
|
||||
return hit != null && StringUtils.isNotEmpty(hit.getProductCode());
|
||||
}
|
||||
|
||||
private void validate(PrcOrderBill query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ServiceException("customer and billing time required");
|
||||
}
|
||||
List<String> codes = normalizeCodes(query.getCustomerCodes());
|
||||
if (codes.isEmpty())
|
||||
{
|
||||
throw new ServiceException("please select customer");
|
||||
}
|
||||
query.setCustomerCodes(codes);
|
||||
Map<String, Object> params = query.getParams();
|
||||
String begin = params == null ? null : strObj(params.get("beginBizDate"));
|
||||
String end = params == null ? null : strObj(params.get("endBizDate"));
|
||||
if (StringUtils.isEmpty(begin) || StringUtils.isEmpty(end))
|
||||
{
|
||||
throw new ServiceException("please select billing time range");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> normalizeCodes(List<String> codes)
|
||||
{
|
||||
if (codes == null || codes.isEmpty())
|
||||
{
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
LinkedHashSet<String> set = new LinkedHashSet<String>();
|
||||
for (String c : codes)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(c))
|
||||
{
|
||||
set.add(c.trim());
|
||||
}
|
||||
}
|
||||
return new ArrayList<String>(set);
|
||||
}
|
||||
|
||||
private static Date resolveAsOf(PrcOrderBill query)
|
||||
{
|
||||
Map<String, Object> params = query.getParams();
|
||||
String raw = params == null ? null : strObj(params.get("endBizDate"));
|
||||
if (StringUtils.isEmpty(raw) && params != null)
|
||||
{
|
||||
raw = strObj(params.get("beginBizDate"));
|
||||
}
|
||||
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 key(String customerCode, String productUpper)
|
||||
{
|
||||
return customerCode + "|" + productUpper;
|
||||
}
|
||||
|
||||
private static String trim(String s)
|
||||
{
|
||||
return StringUtils.isEmpty(s) ? null : s.trim();
|
||||
}
|
||||
|
||||
private static String strObj(Object v)
|
||||
{
|
||||
return v == null ? null : String.valueOf(v).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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.bms.price.task;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.bms.common.core.redis.RedisCache;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.vo.TabOrderCompareTask;
|
||||
import com.bms.price.domain.vo.TabOrderMonitorQuery;
|
||||
import com.bms.price.mapper.PrcOrderBillMapper;
|
||||
import com.bms.price.oracle.TabOrderMonitorService;
|
||||
import com.bms.price.search.IBillSearchStore;
|
||||
import com.bms.price.search.TabOrderSyncEsLog;
|
||||
|
||||
/**
|
||||
* 订单比对异步任务:分页拉上游转单号,与本地 transfer_no 比对差异。
|
||||
*/
|
||||
@Component
|
||||
public class TabOrderCompareAsyncRunner
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(TabOrderCompareAsyncRunner.class);
|
||||
|
||||
public static final String CACHE_KEY_PREFIX = "billing:orderSource:compare:";
|
||||
public static final int TTL_MINUTES = 120;
|
||||
public static final int PAGE_SIZE = 500;
|
||||
/** 单次任务最多扫描上游转单号数(防崩) */
|
||||
public static final int SCAN_HARD_LIMIT = 50000;
|
||||
/** 进度/抽屉中保留的缺失转单号上限 */
|
||||
public static final int MISSING_SAMPLE_LIMIT = 5000;
|
||||
|
||||
@Autowired
|
||||
private TabOrderMonitorService tabOrderMonitorService;
|
||||
|
||||
@Autowired
|
||||
private PrcOrderBillMapper prcOrderBillMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@Async("threadPoolTaskExecutor")
|
||||
public void run(String asyncTaskId, TabOrderMonitorQuery query)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (query != null)
|
||||
{
|
||||
query.setOrderIdentifiers(null);
|
||||
query.setOrderId(null);
|
||||
query.setOrgOrderId(null);
|
||||
query.setTransferOrderId(null);
|
||||
}
|
||||
put(asyncTaskId, TabOrderCompareTask.STATUS_RUNNING, 0, "starting", 0, 0, 0, null, null);
|
||||
int scanned = 0;
|
||||
int matched = 0;
|
||||
int missing = 0;
|
||||
List<String> missingSample = new ArrayList<String>();
|
||||
int pageNum = 1;
|
||||
while (true)
|
||||
{
|
||||
if (scanned >= SCAN_HARD_LIMIT)
|
||||
{
|
||||
log.warn("orderSource compare capped at {} asyncTaskId={}", Integer.valueOf(SCAN_HARD_LIMIT), asyncTaskId);
|
||||
break;
|
||||
}
|
||||
int remain = SCAN_HARD_LIMIT - scanned;
|
||||
int pageSize = Math.min(PAGE_SIZE, remain);
|
||||
List<String> transferNos = tabOrderMonitorService.selectCompareTransferPage(query, pageNum, pageSize);
|
||||
if (transferNos == null || transferNos.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
Set<String> existing = findExisting(transferNos, query == null ? null : query.getCustomerCodes());
|
||||
for (String tn : transferNos)
|
||||
{
|
||||
if (StringUtils.isEmpty(tn))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
scanned++;
|
||||
String key = tn.trim().toUpperCase(Locale.ROOT);
|
||||
if (existing.contains(key))
|
||||
{
|
||||
matched++;
|
||||
}
|
||||
else
|
||||
{
|
||||
missing++;
|
||||
if (missingSample.size() < MISSING_SAMPLE_LIMIT)
|
||||
{
|
||||
missingSample.add(tn.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
int percent = Math.min(99, (int) ((scanned * 100L) / Math.max(scanned + pageSize, 1)));
|
||||
// 最后一页不满页时近乎完成;否则按已扫描量给一个缓慢增长的百分比
|
||||
if (transferNos.size() < pageSize)
|
||||
{
|
||||
percent = 99;
|
||||
}
|
||||
put(asyncTaskId, TabOrderCompareTask.STATUS_RUNNING, percent,
|
||||
"scanning page=" + pageNum + " scanned=" + scanned + " missing=" + missing,
|
||||
scanned, matched, missing, null, missingSample);
|
||||
if (transferNos.size() < pageSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
pageNum++;
|
||||
}
|
||||
Map<String, Object> resp = new LinkedHashMap<String, Object>();
|
||||
resp.put("asyncTaskId", asyncTaskId);
|
||||
resp.put("scanned", Integer.valueOf(scanned));
|
||||
resp.put("matched", Integer.valueOf(matched));
|
||||
resp.put("missing", Integer.valueOf(missing));
|
||||
resp.put("missingTransferNos", missingSample);
|
||||
resp.put("missingTruncated", Boolean.valueOf(missing > missingSample.size()));
|
||||
put(asyncTaskId, TabOrderCompareTask.STATUS_SUCCESS, 100,
|
||||
"done scanned=" + scanned + " missing=" + missing,
|
||||
scanned, matched, missing, null, missingSample);
|
||||
TabOrderSyncEsLog.save(billSearchStore, asyncTaskId, "COMPARE_DONE", query, resp, "SUCCESS", null);
|
||||
log.info("orderSource compare done asyncTaskId={} scanned={} matched={} missing={}",
|
||||
asyncTaskId, Integer.valueOf(scanned), Integer.valueOf(matched), Integer.valueOf(missing));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("orderSource compare failed asyncTaskId={}", asyncTaskId, e);
|
||||
String msg = e.getMessage();
|
||||
if (StringUtils.isEmpty(msg))
|
||||
{
|
||||
msg = e.getClass().getSimpleName();
|
||||
}
|
||||
put(asyncTaskId, TabOrderCompareTask.STATUS_FAIL, 100, "failed", 0, 0, 0, msg, null);
|
||||
TabOrderSyncEsLog.save(billSearchStore, asyncTaskId, "COMPARE_DONE", query, null, "FAIL", msg);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> findExisting(List<String> transferNos, List<String> customerCodes)
|
||||
{
|
||||
Set<String> found = new HashSet<String>();
|
||||
if (transferNos == null || transferNos.isEmpty())
|
||||
{
|
||||
return found;
|
||||
}
|
||||
List<String> hit = prcOrderBillMapper.selectExistingTransferNos(transferNos, cleanCodes(customerCodes));
|
||||
if (hit == null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
for (String s : hit)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(s))
|
||||
{
|
||||
found.add(s.trim().toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static List<String> cleanCodes(List<String> codes)
|
||||
{
|
||||
if (codes == null || codes.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<String> out = new ArrayList<String>();
|
||||
for (String c : codes)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(c))
|
||||
{
|
||||
out.add(c.trim());
|
||||
}
|
||||
}
|
||||
return out.isEmpty() ? null : out;
|
||||
}
|
||||
|
||||
public void put(String taskId, String status, int percent, String message,
|
||||
int scanned, int matched, int missing, String error, List<String> missingSample)
|
||||
{
|
||||
TabOrderCompareTask task = new TabOrderCompareTask();
|
||||
task.setTaskId(taskId);
|
||||
task.setStatus(status);
|
||||
task.setPercent(percent);
|
||||
task.setMessage(message);
|
||||
task.setScanned(scanned);
|
||||
task.setMatched(matched);
|
||||
task.setMissing(missing);
|
||||
task.setError(error);
|
||||
if (missingSample != null)
|
||||
{
|
||||
task.setMissingTransferNos(new ArrayList<String>(missingSample));
|
||||
}
|
||||
redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public TabOrderCompareTask get(String taskId)
|
||||
{
|
||||
return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId);
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
WHERE rn > #{startRow}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
订单比对专用 SQL(异步分页):仅返回转单号,
|
||||
筛选与列表相同;排除空转单号。
|
||||
-->
|
||||
<select id="selectTabOrderCompareKeyPage" resultType="java.util.HashMap">
|
||||
SELECT TRANSFER_ORDER_ID FROM (
|
||||
SELECT inner_q.TRANSFER_ORDER_ID AS TRANSFER_ORDER_ID, ROWNUM AS rn
|
||||
FROM (
|
||||
SELECT o.TRANSFER_ORDER_ID, o.PICKUP_DATE, o.ORDER_ID
|
||||
FROM ${orderTable} o
|
||||
WHERE
|
||||
<include refid="tabOrderMonitorWhere"/>
|
||||
AND o.TRANSFER_ORDER_ID IS NOT NULL
|
||||
ORDER BY o.PICKUP_DATE DESC, o.ORDER_ID DESC
|
||||
) inner_q
|
||||
WHERE ROWNUM <= #{endRow}
|
||||
)
|
||||
WHERE rn > #{startRow}
|
||||
</select>
|
||||
|
||||
<!-- List detail by primary keys (display columns only). -->
|
||||
<select id="selectTabOrderMonitorListByIds" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
@@ -195,10 +215,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
ORDER BY o.PICKUP_DATE DESC, o.ORDER_ID DESC
|
||||
</select>
|
||||
|
||||
<!--
|
||||
大批量单号分批收集:每批最多 900 个(避开 Oracle IN 1000 上限)。
|
||||
用 UNION ALL 三段分别匹配三列(比三列 OR 快约 2 倍:不依赖优化器 OR-expansion,
|
||||
ORDER_ID/ORG_ORDER_ID/TRANSFER_ORDER_ID 各走自己的索引);重复 ID 由 Java 侧去重。
|
||||
<!--
|
||||
大批量单号分批收集:每批最多 900 个。
|
||||
三列 UNION ALL(订单号/运单号/转单号),叠加 tabOrderFilterWhere。
|
||||
比对回填搜索请用 selectMatchedTransferOrderKeys,不走本 SQL。
|
||||
-->
|
||||
<select id="selectMatchedOrderKeys" resultType="java.util.HashMap">
|
||||
SELECT * FROM (
|
||||
@@ -231,6 +251,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</select>
|
||||
|
||||
<!--
|
||||
比对缺失转单号回填搜索专用:仅 TRANSFER_ORDER_ID IN,
|
||||
不带提货时间,不影响列表/同步/比对共用 SQL。
|
||||
-->
|
||||
<select id="selectMatchedTransferOrderKeys" resultType="java.util.HashMap">
|
||||
SELECT o.ID AS ID, o.PICKUP_DATE AS PICKUP_DATE
|
||||
FROM ${orderTable} o
|
||||
WHERE o.TRANSFER_ORDER_ID IN
|
||||
<foreach collection="batch" item="bid" open="(" separator="," close=")">
|
||||
#{bid}
|
||||
</foreach>
|
||||
AND o.BL_PICKUP = 1
|
||||
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11)
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND o.CUSTOMER_CODE IN
|
||||
<foreach collection="customerCodes" item="code" open="(" separator="," close=")">
|
||||
#{code}
|
||||
</foreach>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
|
||||
<!-- 内存分页切片后的主键回查(一批最多一个页大小,远低于 IN 上限) -->
|
||||
<select id="selectTabOrderMonitorByIds" resultType="java.util.HashMap">
|
||||
<!-- 全行数据:同步计费时直接复用,不再按单号回查 Oracle -->
|
||||
|
||||
@@ -204,6 +204,69 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<!-- 订单比对:仅转单号 -->
|
||||
<select id="selectExistingTransferNos" resultType="String">
|
||||
SELECT DISTINCT p.transfer_no
|
||||
FROM prc_order_bill p
|
||||
WHERE p.transfer_no IN
|
||||
<foreach collection="transferNos" item="tn" open="(" separator="," close=")">
|
||||
#{tn}
|
||||
</foreach>
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND p.customer_code IN
|
||||
<foreach collection="customerCodes" item="ccode" open="(" separator="," close=")">
|
||||
#{ccode}
|
||||
</foreach>
|
||||
</if>
|
||||
AND p.transfer_no IS NOT NULL AND p.transfer_no != ''
|
||||
</select>
|
||||
|
||||
<!-- 订单比对:上游单号中已落库本地的 order_no/tracking_no/transfer_no -->
|
||||
<select id="selectExistingOrderIdentifiers" resultType="String">
|
||||
SELECT DISTINCT hit.idn
|
||||
FROM (
|
||||
SELECT p.order_no AS idn
|
||||
FROM prc_order_bill p
|
||||
WHERE p.order_no IN
|
||||
<foreach collection="identifiers" item="idn" open="(" separator="," close=")">
|
||||
#{idn}
|
||||
</foreach>
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND p.customer_code IN
|
||||
<foreach collection="customerCodes" item="ccode" open="(" separator="," close=")">
|
||||
#{ccode}
|
||||
</foreach>
|
||||
</if>
|
||||
UNION
|
||||
SELECT p.tracking_no AS idn
|
||||
FROM prc_order_bill p
|
||||
WHERE p.tracking_no IN
|
||||
<foreach collection="identifiers" item="idn" open="(" separator="," close=")">
|
||||
#{idn}
|
||||
</foreach>
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND p.customer_code IN
|
||||
<foreach collection="customerCodes" item="ccode" open="(" separator="," close=")">
|
||||
#{ccode}
|
||||
</foreach>
|
||||
</if>
|
||||
UNION
|
||||
SELECT p.transfer_no AS idn
|
||||
FROM prc_order_bill p
|
||||
WHERE p.transfer_no IN
|
||||
<foreach collection="identifiers" item="idn" open="(" separator="," close=")">
|
||||
#{idn}
|
||||
</foreach>
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND p.customer_code IN
|
||||
<foreach collection="customerCodes" item="ccode" open="(" separator="," close=")">
|
||||
#{ccode}
|
||||
</foreach>
|
||||
</if>
|
||||
) hit
|
||||
WHERE hit.idn IS NOT NULL AND hit.idn != ''
|
||||
</select>
|
||||
|
||||
<select id="selectOrderNoOptions" resultType="String">
|
||||
select p.order_no
|
||||
from prc_order_bill p
|
||||
@@ -246,6 +309,106 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
limit #{limit}
|
||||
</select>
|
||||
|
||||
<!-- Product-check filters: customer + billing time (+ optional status/identifiers). -->
|
||||
<sql id="orderBillProductCheckWhere">
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND p.customer_code IN
|
||||
<foreach collection="customerCodes" item="ccode" open="(" separator="," close=")">
|
||||
#{ccode}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="params != null and params.beginBizDate != null and params.beginBizDate != ''">
|
||||
AND p.biz_date >= #{params.beginBizDate}
|
||||
</if>
|
||||
<if test="params != null and params.endBizDate != null and params.endBizDate != ''">
|
||||
AND p.biz_date <= #{params.endBizDate}
|
||||
</if>
|
||||
<if test="params != null and params.beginPickupDate != null and params.beginPickupDate != ''">
|
||||
AND p.pickup_date >= #{params.beginPickupDate}
|
||||
</if>
|
||||
<if test="params != null and params.endPickupDate != null and params.endPickupDate != ''">
|
||||
AND p.pickup_date <= #{params.endPickupDate}
|
||||
</if>
|
||||
<if test="orderStatuses != null and orderStatuses.size() > 0">
|
||||
AND p.order_status IN
|
||||
<foreach collection="orderStatuses" item="os" open="(" separator="," close=")">
|
||||
#{os}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="billingFailStatuses != null and billingFailStatuses.size() > 0">
|
||||
AND p.billing_fail_status IN
|
||||
<foreach collection="billingFailStatuses" item="bfs" open="(" separator="," close=")">
|
||||
#{bfs}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="orderIdentifiers != null and orderIdentifiers.size() > 0">
|
||||
AND (
|
||||
p.order_no IN
|
||||
<foreach collection="orderIdentifiers" item="idn" open="(" separator="," close=")">
|
||||
#{idn}
|
||||
</foreach>
|
||||
OR p.tracking_no IN
|
||||
<foreach collection="orderIdentifiers" item="idn" open="(" separator="," close=")">
|
||||
#{idn}
|
||||
</foreach>
|
||||
OR p.transfer_no IN
|
||||
<foreach collection="orderIdentifiers" item="idn" open="(" separator="," close=")">
|
||||
#{idn}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
<include refid="orderBillDataScopeFilter"/>
|
||||
</sql>
|
||||
|
||||
<select id="selectDistinctProductGroups" parameterType="PrcOrderBill" resultType="java.util.HashMap">
|
||||
SELECT p.customer_code AS customer_code,
|
||||
p.product_code AS product_code,
|
||||
MAX(p.product_name) AS product_name,
|
||||
COUNT(1) AS order_count
|
||||
FROM prc_order_bill p
|
||||
WHERE p.product_code IS NOT NULL
|
||||
AND p.product_code <> ''
|
||||
<include refid="orderBillProductCheckWhere"/>
|
||||
GROUP BY p.customer_code, p.product_code
|
||||
ORDER BY p.customer_code, p.product_code
|
||||
</select>
|
||||
|
||||
<select id="selectOrderBillsByProductForCheck" resultMap="PrcOrderBillResult">
|
||||
<include refid="selectPrcOrderBillVo"/>
|
||||
<where>
|
||||
<if test="query.productCode != null and query.productCode != ''">
|
||||
AND p.product_code = #{query.productCode}
|
||||
</if>
|
||||
<if test="query.customerCodes != null and query.customerCodes.size() > 0">
|
||||
AND p.customer_code IN
|
||||
<foreach collection="query.customerCodes" item="ccode" open="(" separator="," close=")">
|
||||
#{ccode}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="query.params != null and query.params.beginBizDate != null and query.params.beginBizDate != ''">
|
||||
AND p.biz_date >= #{query.params.beginBizDate}
|
||||
</if>
|
||||
<if test="query.params != null and query.params.endBizDate != null and query.params.endBizDate != ''">
|
||||
AND p.biz_date <= #{query.params.endBizDate}
|
||||
</if>
|
||||
<if test="query.params != null and query.params.beginPickupDate != null and query.params.beginPickupDate != ''">
|
||||
AND p.pickup_date >= #{query.params.beginPickupDate}
|
||||
</if>
|
||||
<if test="query.params != null and query.params.endPickupDate != null and query.params.endPickupDate != ''">
|
||||
AND p.pickup_date <= #{query.params.endPickupDate}
|
||||
</if>
|
||||
<if test="query.orderStatuses != null and query.orderStatuses.size() > 0">
|
||||
AND p.order_status IN
|
||||
<foreach collection="query.orderStatuses" item="os" open="(" separator="," close=")">
|
||||
#{os}
|
||||
</foreach>
|
||||
</if>
|
||||
<include refid="orderBillDataScopeFilterByQuery"/>
|
||||
</where>
|
||||
order by p.biz_date desc, p.bill_id desc
|
||||
limit #{limit}
|
||||
</select>
|
||||
|
||||
<select id="selectPrcOrderBillList" parameterType="PrcOrderBill" resultMap="PrcOrderBillResult">
|
||||
<include refid="selectPrcOrderBillVo"/>
|
||||
<where>
|
||||
@@ -364,6 +527,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="params.endBizDate != null and params.endBizDate != ''">
|
||||
AND p.biz_date <= #{params.endBizDate}
|
||||
</if>
|
||||
<if test="params.beginPickupDate != null and params.beginPickupDate != ''">
|
||||
AND p.pickup_date >= #{params.beginPickupDate}
|
||||
</if>
|
||||
<if test="params.endPickupDate != null and params.endPickupDate != ''">
|
||||
AND p.pickup_date <= #{params.endPickupDate}
|
||||
</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
AND date_format(p.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
</if>
|
||||
|
||||
Reference in New Issue
Block a user