增加查看价格套餐历史版本的变更
This commit is contained in:
+30
-2
@@ -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("查询成功");
|
||||
|
||||
@@ -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<String> entity = new HttpEntity<String>(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<BillSyncSnapshot> all = loadAllPriceChanges(psvId, priceKind, status, 10000);
|
||||
Map<String, PriceChangeGroup> grouped = new LinkedHashMap<String, PriceChangeGroup>();
|
||||
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<PriceChangeGroup> list = new ArrayList<PriceChangeGroup>(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<PriceChangeGroup>(list.subList(from, to)));
|
||||
return page;
|
||||
}
|
||||
|
||||
private List<BillSyncSnapshot> loadAllPriceChanges(Long psvId, String priceKind, String status, int max)
|
||||
{
|
||||
List<BillSyncSnapshot> all = new ArrayList<BillSyncSnapshot>();
|
||||
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<BillSyncSnapshot> 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))
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> OPERATOR_OVERRIDE = new ThreadLocal<String>();
|
||||
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<PriceChangeGroup> rows = new ArrayList<PriceChangeGroup>();
|
||||
|
||||
public long getTotal()
|
||||
{
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total)
|
||||
{
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<PriceChangeGroup> getRows()
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<PriceChangeGroup> rows)
|
||||
{
|
||||
this.rows = rows == null ? new ArrayList<PriceChangeGroup>() : rows;
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user