diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/PriceLogController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/PriceLogController.java index 50d20834..5f20894b 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/PriceLogController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/PriceLogController.java @@ -16,6 +16,7 @@ import com.bms.common.core.page.TableSupport; import com.bms.price.search.BillSearchPage; import com.bms.price.search.EsLogType; import com.bms.price.search.IBillSearchStore; +import com.bms.price.search.PriceChangeGroupPage; /** * 来自 ES 的价格变更前后日志(销售价 / 产品价 / 红线价)。 @@ -31,12 +32,39 @@ public class PriceLogController extends BaseController @GetMapping("/list") public TableDataInfo list( @RequestParam(value = "bizId", required = false) String bizId, - @RequestParam(value = "status", required = false) String status) + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "psvId", required = false) Long psvId, + @RequestParam(value = "priceKind", required = false) String priceKind, + @RequestParam(value = "productCode", required = false) String productCode, + @RequestParam(value = "updateTime", required = false) String updateTime, + @RequestParam(value = "updateTimePrefix", required = false) String updateTimePrefix) { PageDomain pageDomain = TableSupport.buildPageRequest(); int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue(); int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue(); - BillSearchPage page = billSearchStore.search(EsLogType.PRICE_CHANGE, bizId, status, pageNum, pageSize); + BillSearchPage page = billSearchStore.search(EsLogType.PRICE_CHANGE, bizId, status, psvId, priceKind, + productCode, updateTime, updateTimePrefix, pageNum, pageSize); + TableDataInfo rsp = new TableDataInfo(); + rsp.setCode(HttpStatus.SUCCESS); + rsp.setMsg("查询成功"); + rsp.setRows(page.getRows()); + rsp.setTotal(page.getTotal()); + return rsp; + } + + /** Group by productCode + write time for package-version drill-down. */ + @PreAuthorize("@ss.hasPermi('monitor:priceLog:list')") + @GetMapping("/groups") + public TableDataInfo groups( + @RequestParam(value = "psvId", required = true) Long psvId, + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "priceKind", required = false) String priceKind) + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue(); + int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue(); + PriceChangeGroupPage page = billSearchStore.searchPriceChangeGroups(psvId, priceKind, status, pageNum, + pageSize); TableDataInfo rsp = new TableDataInfo(); rsp.setCode(HttpStatus.SUCCESS); rsp.setMsg("查询成功"); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/search/EsBillSearchStore.java b/bms-back/bms-system/src/main/java/com/bms/price/search/EsBillSearchStore.java index 2e0ec3c6..85338de6 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/search/EsBillSearchStore.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/search/EsBillSearchStore.java @@ -3,7 +3,11 @@ package com.bms.price.search; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; @@ -148,6 +152,27 @@ public class EsBillSearchStore implements IBillSearchStore @Override public BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize) + { + return search(logType, bizId, status, null, null, pageNum, pageSize); + } + + @Override + public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind, + int pageNum, int pageSize) + { + return search(logType, bizId, status, psvId, priceKind, null, null, pageNum, pageSize); + } + + @Override + public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind, + String productCode, String updateTime, int pageNum, int pageSize) + { + return search(logType, bizId, status, psvId, priceKind, productCode, updateTime, null, pageNum, pageSize); + } + + @Override + public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind, + String productCode, String updateTime, String updateTimePrefix, int pageNum, int pageSize) { BillSearchPage page = new BillSearchPage(); if (pageNum < 1) @@ -171,7 +196,8 @@ public class EsBillSearchStore implements IBillSearchStore sortItem.put("updatedAt.keyword", sortOpt); sort.add(sortItem); body.put("sort", sort); - body.put("query", buildQuery(logType, bizId, status)); + body.put("query", buildQuery(logType, bizId, status, psvId, priceKind, productCode, updateTime, + updateTimePrefix)); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity entity = new HttpEntity(body.toJSONString(), headers); @@ -217,6 +243,108 @@ public class EsBillSearchStore implements IBillSearchStore return page; } + @Override + public PriceChangeGroupPage searchPriceChangeGroups(Long psvId, String priceKind, String status, + int pageNum, int pageSize) + { + PriceChangeGroupPage page = new PriceChangeGroupPage(); + if (psvId == null) + { + return page; + } + if (pageNum < 1) + { + pageNum = 1; + } + if (pageSize < 1) + { + pageSize = 10; + } + List all = loadAllPriceChanges(psvId, priceKind, status, 10000); + Map grouped = new LinkedHashMap(); + for (BillSyncSnapshot snap : all) + { + if (snap == null) + { + continue; + } + JSONObject req = parseRequestObject(snap.getRequestJson()); + JSONObject keys = req == null ? null : req.getJSONObject("bizKeys"); + String productCode = keys == null ? "" : StringUtils.nvl(keys.getString("productCode"), ""); + String time = StringUtils.nvl(snap.getUpdateTime(), ""); + String key = productCode + "|" + time; + PriceChangeGroup group = grouped.get(key); + if (group == null) + { + group = new PriceChangeGroup(); + group.setProductCode(StringUtils.isEmpty(productCode) ? "-" : productCode); + group.setUpdateTime(time); + group.setAction(req == null ? null : req.getString("action")); + group.setOperator(req == null ? null : req.getString("operator")); + group.setStatus(snap.getStatus()); + group.setChangeCount(0); + grouped.put(key, group); + } + group.setChangeCount(group.getChangeCount() + 1); + if (EsBizLogSupport.STATUS_FAIL.equals(snap.getStatus())) + { + group.setStatus(EsBizLogSupport.STATUS_FAIL); + } + } + List list = new ArrayList(grouped.values()); + page.setTotal(list.size()); + int from = (pageNum - 1) * pageSize; + if (from >= list.size()) + { + return page; + } + int to = Math.min(from + pageSize, list.size()); + page.setRows(new ArrayList(list.subList(from, to))); + return page; + } + + private List loadAllPriceChanges(Long psvId, String priceKind, String status, int max) + { + List all = new ArrayList(); + int batch = 1000; + int pageNum = 1; + while (all.size() < max) + { + int size = Math.min(batch, max - all.size()); + BillSearchPage chunk = search(EsLogType.PRICE_CHANGE, null, status, psvId, priceKind, null, null, + pageNum, size); + List rows = chunk.getRows(); + if (rows == null || rows.isEmpty()) + { + break; + } + all.addAll(rows); + if (rows.size() < size || all.size() >= chunk.getTotal()) + { + break; + } + pageNum++; + } + return all; + } + + private JSONObject parseRequestObject(String requestJson) + { + if (StringUtils.isEmpty(requestJson)) + { + return null; + } + try + { + Object parsed = JSON.parse(requestJson); + return parsed instanceof JSONObject ? (JSONObject) parsed : null; + } + catch (Exception e) + { + return null; + } + } + private BillSyncSnapshot toSnapshot(JSONObject source, String id) { if (source == null) @@ -249,12 +377,18 @@ public class EsBillSearchStore implements IBillSearchStore return snap; } - private JSONObject buildQuery(String logType, String bizId, String status) + private JSONObject buildQuery(String logType, String bizId, String status, Long psvId, String priceKind, + String productCode, String updateTime, String updateTimePrefix) { JSONArray filters = new JSONArray(); addTerm(filters, "logType", logType); addWildcard(filters, "bizId", bizId); addTerm(filters, "status", status); + addPsvIdFilter(filters, psvId); + addTerm(filters, "request.priceKind", priceKind); + addProductCodeFilter(filters, productCode); + addUpdateTimeFilter(filters, updateTime); + addUpdateTimePrefixFilter(filters, updateTimePrefix); if (filters.isEmpty()) { JSONObject matchAll = new JSONObject(); @@ -268,6 +402,136 @@ public class EsBillSearchStore implements IBillSearchStore return query; } + private void addProductCodeFilter(JSONArray filters, String productCode) + { + if (StringUtils.isEmpty(productCode)) + { + return; + } + String code = productCode.trim(); + JSONArray should = new JSONArray(); + JSONObject termKw = new JSONObject(); + termKw.put("request.bizKeys.productCode.keyword", code); + JSONObject wrapKw = new JSONObject(); + wrapKw.put("term", termKw); + should.add(wrapKw); + + JSONObject term = new JSONObject(); + term.put("request.bizKeys.productCode", code); + JSONObject wrap = new JSONObject(); + wrap.put("term", term); + should.add(wrap); + + JSONObject inner = new JSONObject(); + inner.put("should", should); + inner.put("minimum_should_match", Integer.valueOf(1)); + JSONObject boolWrap = new JSONObject(); + boolWrap.put("bool", inner); + filters.add(boolWrap); + } + + private void addUpdateTimeFilter(JSONArray filters, String updateTime) + { + if (StringUtils.isEmpty(updateTime)) + { + return; + } + String time = updateTime.trim(); + JSONArray should = new JSONArray(); + JSONObject t1 = new JSONObject(); + t1.put("updateTime.keyword", time); + JSONObject w1 = new JSONObject(); + w1.put("term", t1); + should.add(w1); + + JSONObject t2 = new JSONObject(); + t2.put("updatedAt.keyword", time); + JSONObject w2 = new JSONObject(); + w2.put("term", t2); + should.add(w2); + + JSONObject t3 = new JSONObject(); + t3.put("updateTime", time); + JSONObject w3 = new JSONObject(); + w3.put("term", t3); + should.add(w3); + + JSONObject t4 = new JSONObject(); + t4.put("updatedAt", time); + JSONObject w4 = new JSONObject(); + w4.put("term", t4); + should.add(w4); + + JSONObject inner = new JSONObject(); + inner.put("should", should); + inner.put("minimum_should_match", Integer.valueOf(1)); + JSONObject wrap = new JSONObject(); + wrap.put("bool", inner); + filters.add(wrap); + } + + /** Match write-time by minute prefix, e.g. 2026-11-05 13:27. */ + private void addUpdateTimePrefixFilter(JSONArray filters, String updateTimePrefix) + { + if (StringUtils.isEmpty(updateTimePrefix)) + { + return; + } + String prefix = escapeWildcard(updateTimePrefix.trim()) + "*"; + JSONArray should = new JSONArray(); + for (String field : new String[] { "updateTime.keyword", "updatedAt.keyword", "updateTime", "updatedAt" }) + { + JSONObject opts = new JSONObject(); + opts.put("value", prefix); + opts.put("case_insensitive", Boolean.TRUE); + JSONObject wildcard = new JSONObject(); + wildcard.put(field, opts); + JSONObject wrap = new JSONObject(); + wrap.put("wildcard", wildcard); + should.add(wrap); + } + JSONObject inner = new JSONObject(); + inner.put("should", should); + inner.put("minimum_should_match", Integer.valueOf(1)); + JSONObject wrap = new JSONObject(); + wrap.put("bool", inner); + filters.add(wrap); + } + + /** Prefer numeric term; also accept keyword/string mapping of bizKeys.psvId. */ + private void addPsvIdFilter(JSONArray filters, Long psvId) + { + if (psvId == null) + { + return; + } + JSONArray should = new JSONArray(); + JSONObject termNum = new JSONObject(); + termNum.put("request.bizKeys.psvId", psvId); + JSONObject wrapNum = new JSONObject(); + wrapNum.put("term", termNum); + should.add(wrapNum); + + JSONObject termKw = new JSONObject(); + termKw.put("request.bizKeys.psvId.keyword", String.valueOf(psvId)); + JSONObject wrapKw = new JSONObject(); + wrapKw.put("term", termKw); + should.add(wrapKw); + + JSONObject termStr = new JSONObject(); + termStr.put("request.bizKeys.psvId", String.valueOf(psvId)); + JSONObject wrapStr = new JSONObject(); + wrapStr.put("term", termStr); + should.add(wrapStr); + + JSONObject inner = new JSONObject(); + inner.put("should", should); + inner.put("minimum_should_match", Integer.valueOf(1)); + JSONObject wrap = new JSONObject(); + wrap.put("bool", inner); + filters.add(wrap); + } + private void addTerm(JSONArray filters, String field, String value) { if (StringUtils.isEmpty(value)) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/search/IBillSearchStore.java b/bms-back/bms-system/src/main/java/com/bms/price/search/IBillSearchStore.java index fc162d07..e5294582 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/search/IBillSearchStore.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/search/IBillSearchStore.java @@ -17,4 +17,37 @@ public interface IBillSearchStore BillSyncSnapshot getByDocId(String docId); BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize); + + /** + * Search with optional sales-version / priceKind filters (request.bizKeys.psvId, request.priceKind). + */ + default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind, + int pageNum, int pageSize) + { + return search(logType, bizId, status, psvId, priceKind, null, null, pageNum, pageSize); + } + + /** + * Search with optional productCode + updateTime (exact) / updateTimePrefix (e.g. yyyy-MM-dd HH:mm) filters. + */ + default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind, + String productCode, String updateTime, int pageNum, int pageSize) + { + return search(logType, bizId, status, psvId, priceKind, productCode, updateTime, null, pageNum, pageSize); + } + + default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind, + String productCode, String updateTime, String updateTimePrefix, int pageNum, int pageSize) + { + return search(logType, bizId, status, pageNum, pageSize); + } + + /** + * Group PRICE_CHANGE logs by productCode + updateTime for one package version. + */ + default PriceChangeGroupPage searchPriceChangeGroups(Long psvId, String priceKind, String status, + int pageNum, int pageSize) + { + return new PriceChangeGroupPage(); + } } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeEsLog.java b/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeEsLog.java index 954cb482..b363a165 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeEsLog.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeEsLog.java @@ -25,12 +25,33 @@ public final class PriceChangeEsLog public static final String ACTION_DELETE = "DELETE"; public static final String ACTION_IMPORT = "IMPORT"; + /** Async import workers have no SecurityContext; pass operName via this override. */ + private static final ThreadLocal OPERATOR_OVERRIDE = new ThreadLocal(); + private PriceChangeEsLog() { } + public static void beginOperator(String operName) + { + if (StringUtils.isNotEmpty(operName)) + { + OPERATOR_OVERRIDE.set(operName.trim()); + } + } + + public static void endOperator() + { + OPERATOR_OVERRIDE.remove(); + } + public static String operator() { + String override = OPERATOR_OVERRIDE.get(); + if (StringUtils.isNotEmpty(override)) + { + return override; + } try { String name = SecurityUtils.getUsername(); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeGroup.java b/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeGroup.java new file mode 100644 index 00000000..d059db8c --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeGroup.java @@ -0,0 +1,74 @@ +package com.bms.price.search; + +/** + * Price-change log group key: productCode + write time. + */ +public class PriceChangeGroup +{ + private String productCode; + private String updateTime; + private String action; + private String operator; + private String status; + private int changeCount; + + public String getProductCode() + { + return productCode; + } + + public void setProductCode(String productCode) + { + this.productCode = productCode; + } + + public String getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(String updateTime) + { + this.updateTime = updateTime; + } + + public String getAction() + { + return action; + } + + public void setAction(String action) + { + this.action = action; + } + + public String getOperator() + { + return operator; + } + + public void setOperator(String operator) + { + this.operator = operator; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public int getChangeCount() + { + return changeCount; + } + + public void setChangeCount(int changeCount) + { + this.changeCount = changeCount; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeGroupPage.java b/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeGroupPage.java new file mode 100644 index 00000000..ce5159e1 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/search/PriceChangeGroupPage.java @@ -0,0 +1,31 @@ +package com.bms.price.search; + +import java.util.ArrayList; +import java.util.List; + +/** Paginated product+time groups of price-change logs. */ +public class PriceChangeGroupPage +{ + private long total; + private List rows = new ArrayList(); + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows == null ? new ArrayList() : rows; + } +} diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java index 041cf63a..e5d908c9 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java @@ -1582,6 +1582,23 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService @Transactional(rollbackFor = Exception.class) public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, String taskId, boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception + { + // Async import has no SecurityContext; bind operName for PriceChangeEsLog.operator(). + PriceChangeEsLog.beginOperator(operName); + try + { + return importDetailPricesInOperator(priceSetId, psvId, bytes, filename, operName, taskId, updateOnly, + replaceByProductCountry, countryCode); + } + finally + { + PriceChangeEsLog.endOperator(); + } + } + + private String importDetailPricesInOperator(Long priceSetId, Long psvId, byte[] bytes, String filename, + String operName, String taskId, boolean updateOnly, boolean replaceByProductCountry, String countryCode) + throws Exception { PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); diff --git a/bms-front/src/api/monitor/priceLog.js b/bms-front/src/api/monitor/priceLog.js index 5e5accca..309aff4e 100644 --- a/bms-front/src/api/monitor/priceLog.js +++ b/bms-front/src/api/monitor/priceLog.js @@ -8,6 +8,15 @@ export function listPriceLog(query) { }) } +/** Group by productCode + updateTime for a package version. */ +export function listPriceLogGroups(query) { + return request({ + url: '/monitor/priceLog/groups', + method: 'get', + params: query + }) +} + export function getPriceLog(docId) { return request({ url: '/monitor/priceLog/' + encodeURIComponent(docId), diff --git a/bms-front/src/views/price/set/components/detail/SetVersionPriceLogDrawer.vue b/bms-front/src/views/price/set/components/detail/SetVersionPriceLogDrawer.vue new file mode 100644 index 00000000..fb6c2970 --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetVersionPriceLogDrawer.vue @@ -0,0 +1,450 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetVersionTable.vue b/bms-front/src/views/price/set/components/detail/SetVersionTable.vue index e569bf64..999478af 100644 --- a/bms-front/src/views/price/set/components/detail/SetVersionTable.vue +++ b/bms-front/src/views/price/set/components/detail/SetVersionTable.vue @@ -35,10 +35,16 @@ - +