feat(cost+wms): 新增生产指标标签功能并优化钢卷成本展示
1. 为生产指标实体、BO、VO新增tags标签字段并完善MyBatis映射 2. 在生产指标查询中添加标签模糊筛选条件 3. 新增生产指标计算结果API接口 4. 优化成本综合页面:支持标签字段的增改查,新增指标结果批量保存逻辑 5. 移除废弃的costDataService文件,重构钢卷详情页成本展示模块,新增加工路径可视化和吨钢成本计算展示 6. 注释并禁用了原有的检验任务相关代码逻辑
This commit is contained in:
@@ -180,6 +180,7 @@
|
||||
<el-table :data="mgrList" border stripe size="mini">
|
||||
<el-table-column label="名称" prop="metricName" width="150" />
|
||||
<el-table-column label="公式" prop="metricFormula" min-width="200" />
|
||||
<el-table-column label="标签" prop="tags" width="100" />
|
||||
<el-table-column label="单位" prop="remark" width="70" />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template slot-scope="s">
|
||||
@@ -206,6 +207,7 @@
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item label="单位"><el-input v-model="defForm.unit" placeholder="如 %" /></el-form-item>
|
||||
<el-form-item label="标签"><el-input v-model="defForm.tags" placeholder="请输入标签" /></el-form-item>
|
||||
<el-form-item label="使用单价"><el-switch v-model="defForm.usePrice" :active-value="1" :inactive-value="0" /></el-form-item>
|
||||
<el-form-item label="单价"><el-input v-model="defForm.metricValue" type="number" placeholder="请输入单价" /></el-form-item>
|
||||
</el-form>
|
||||
@@ -324,6 +326,7 @@
|
||||
import { listProdReport, getProdReport, addProdReport, updateProdReport, delProdReport, copyProdReport } from "@/api/cost/prodReport"
|
||||
import { listProdDetail, batchSaveProdDetail } from "@/api/cost/prodDetail"
|
||||
import { listProdMetric, addProdMetric, updateProdMetric, delProdMetric, getProdMetric } from "@/api/cost/prodMetric"
|
||||
import { listProdMetricResult, batchSaveProdMetricResult } from "@/api/cost/prodMetricResult"
|
||||
import { listItem } from "@/api/cost/item"
|
||||
import { listLightPendingAction } from "@/api/wms/pendingAction"
|
||||
import { getCoilStatisticsList } from "@/api/wms/coil"
|
||||
@@ -608,7 +611,7 @@ export default {
|
||||
},
|
||||
doPickMetric() {
|
||||
this.selMp.forEach(m => {
|
||||
this.allCols.push({ $type: 'metric', metricId: m.metricId, metricName: m.metricName, metricFormula: m.metricFormula, unit: m.remark||'', isShift: false, color: null, metricValue: m.metricValue, usePrice: m.usePrice })
|
||||
this.allCols.push({ $type: 'metric', metricId: m.metricId, metricName: m.metricName, metricFormula: m.metricFormula, unit: m.remark||'', tags: m.tags || '', isShift: false, color: null, metricValue: m.metricValue, usePrice: m.usePrice })
|
||||
})
|
||||
this.metricPickOpen = false
|
||||
},
|
||||
@@ -618,15 +621,15 @@ export default {
|
||||
this.mgrList = this._allMetricDefs || []
|
||||
this.mgrOpen = true
|
||||
},
|
||||
addMetricDef() { this.defForm = { metricId: null, metricName: '', metricFormula: '', unit: '', usePrice: 0, metricValue: '' }; this.defTitle = '新增指标'; this.defOpen = true },
|
||||
editMetricDef(row) { this.defForm = { metricId: row.metricId, metricName: row.metricName, metricFormula: row.metricFormula, unit: row.remark||'', usePrice: row.usePrice || 0, metricValue: row.metricValue || '' }; this.defTitle = '编辑指标'; this.defOpen = true },
|
||||
addMetricDef() { this.defForm = { metricId: null, metricName: '', metricFormula: '', unit: '', tags: '', usePrice: 0, metricValue: '' }; this.defTitle = '新增指标'; this.defOpen = true },
|
||||
editMetricDef(row) { this.defForm = { metricId: row.metricId, metricName: row.metricName, metricFormula: row.metricFormula, unit: row.remark||'', tags: row.tags || '', usePrice: row.usePrice || 0, metricValue: row.metricValue || '' }; this.defTitle = '编辑指标'; this.defOpen = true },
|
||||
async submitMetricDef() {
|
||||
const f = this.defForm
|
||||
if (!f.metricName) { this.$modal.msgWarning('请输入指标名称'); return }
|
||||
if (f.metricId) {
|
||||
await updateProdMetric({ metricId: f.metricId, metricName: f.metricName, metricFormula: f.metricFormula, remark: f.unit, usePrice: f.usePrice, metricValue: f.metricValue || 0 })
|
||||
await updateProdMetric({ metricId: f.metricId, metricName: f.metricName, metricFormula: f.metricFormula, remark: f.unit, tags: f.tags || '', usePrice: f.usePrice, metricValue: f.metricValue || 0 })
|
||||
} else {
|
||||
await addProdMetric({ reportId: this.activeReport.reportId, metricCode: f.metricName, metricName: f.metricName, metricFormula: f.metricFormula, metricValue: f.metricValue || 0, remark: f.unit || '', usePrice: f.usePrice })
|
||||
await addProdMetric({ reportId: this.activeReport.reportId, metricCode: f.metricName, metricName: f.metricName, metricFormula: f.metricFormula, metricValue: f.metricValue || 0, remark: f.unit || '', tags: f.tags || '', usePrice: f.usePrice })
|
||||
}
|
||||
this.defOpen = false; this.$modal.msgSuccess('保存成功')
|
||||
await this.openMetricMgr()
|
||||
@@ -676,7 +679,7 @@ export default {
|
||||
// ensure metric definitions exist in DB for all metric columns before saving config
|
||||
for (const mc of metricCols) {
|
||||
if (!mc.metricId) {
|
||||
const r = await addProdMetric({ reportId: rid, metricCode: mc.metricName, metricName: mc.metricName, metricFormula: mc.metricFormula || '', metricValue: 0, remark: mc.unit || '' })
|
||||
const r = await addProdMetric({ reportId: rid, metricCode: mc.metricName, metricName: mc.metricName, metricFormula: mc.metricFormula || '', metricValue: 0, remark: mc.unit || '', tags: mc.tags || '' })
|
||||
mc.metricId = r.data && r.data.metricId || r.metricId
|
||||
}
|
||||
}
|
||||
@@ -721,7 +724,7 @@ export default {
|
||||
if (!def && c.id) {
|
||||
try { const r = await getProdMetric(c.id); if (r.data) { def = r.data; this._allMetricDefs.push(def) } } catch(e) {}
|
||||
}
|
||||
if (def) cols.push({ $type: 'metric', metricId: def.metricId, metricName: def.metricName, metricFormula: def.metricFormula, unit: def.remark||'', isShift: !!c.s, color: c.c || null, metricValue: def.metricValue, usePrice: def.usePrice })
|
||||
if (def) cols.push({ $type: 'metric', metricId: def.metricId, metricName: def.metricName, metricFormula: def.metricFormula, unit: def.remark||'', tags: def.tags || '', isShift: !!c.s, color: c.c || null, metricValue: def.metricValue, usePrice: def.usePrice })
|
||||
}
|
||||
}
|
||||
this.allCols = cols
|
||||
@@ -782,6 +785,7 @@ export default {
|
||||
async saveGrid() {
|
||||
const rid = this.activeReport.reportId; if (!rid) return; this.saving = true
|
||||
try {
|
||||
this.recalcAll()
|
||||
const exist = await listProdDetail({ reportId: rid, pageNum: 1, pageSize: 9999 })
|
||||
const ids = (exist.rows||[]).map(d => d.detailId)
|
||||
const detailCols = this.allCols.filter(c => c.$type === 'detail'); const detailList = []
|
||||
@@ -793,6 +797,31 @@ export default {
|
||||
})
|
||||
})
|
||||
await batchSaveProdDetail({ detailIds: ids, prodDetailList: detailList })
|
||||
|
||||
const metricCols = this.allCols.filter(c => c.$type === 'metric' && c.metricId)
|
||||
if (metricCols.length) {
|
||||
const existMR = await listProdMetricResult({ reportId: rid, pageNum: 1, pageSize: 99999 })
|
||||
const resultIds = (existMR.rows || []).map(r => r.resultId)
|
||||
const metricResultList = []
|
||||
this.gridRows.forEach(row => {
|
||||
if (!row.detailDate) return
|
||||
metricCols.forEach(m => {
|
||||
const push = (teamGroup, value) => {
|
||||
if (value != null && value !== '') {
|
||||
metricResultList.push({ reportId: rid, metricId: m.metricId, metricDate: row.detailDate, teamGroup, calcValue: value, tags: m.tags || '' })
|
||||
}
|
||||
}
|
||||
if (m.isShift) {
|
||||
push('1', row['mv' + m.mIdx + '_1'])
|
||||
push('2', row['mv' + m.mIdx + '_2'])
|
||||
} else {
|
||||
push('0', row['mv' + m.mIdx])
|
||||
}
|
||||
})
|
||||
})
|
||||
await batchSaveProdMetricResult({ resultIds, prodMetricResultList: metricResultList })
|
||||
}
|
||||
|
||||
this.$modal.msgSuccess("保存成功"); await this.loadGrid()
|
||||
} catch(e) { this.$modal.msgError("保存失败") } finally { this.saving = false }
|
||||
},
|
||||
@@ -815,7 +844,7 @@ export default {
|
||||
else if (sc.t === 'm') {
|
||||
const sid = String(sc.id)
|
||||
let def = this._allMetricDefs.find(m=>String(m.metricId)===sid)
|
||||
if (def && !usedMids.has(sid)) { usedMids.add(sid); this.allCols.push({ $type:'metric', metricId:String(def.metricId), metricName:def.metricName, metricFormula:def.metricFormula, unit:def.remark||'', isShift:!!sc.s, color:sc.c||null, metricValue: def.metricValue, usePrice: def.usePrice }) }
|
||||
if (def && !usedMids.has(sid)) { usedMids.add(sid); this.allCols.push({ $type:'metric', metricId:String(def.metricId), metricName:def.metricName, metricFormula:def.metricFormula, unit:def.remark||'', tags: def.tags || '', isShift:!!sc.s, color:sc.c||null, metricValue: def.metricValue, usePrice: def.usePrice }) }
|
||||
}
|
||||
})
|
||||
this.copyCfgOpen = false; let mi = 0; this.allCols.forEach(c => { if (c.$type === 'metric') c.mIdx = mi++ })
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
import { listProdReport } from '@/api/cost/prodReport'
|
||||
import { listProdDetail } from '@/api/cost/prodDetail'
|
||||
import { listProdMetric } from '@/api/cost/prodMetric'
|
||||
import { listItem } from '@/api/cost/item'
|
||||
import { listPrice } from '@/api/cost/price'
|
||||
|
||||
/**
|
||||
* 安全计算公式值
|
||||
* @param {string} f - 公式表达式
|
||||
* @returns {number|null}
|
||||
*/
|
||||
function evalFormula(f) {
|
||||
const s = f.replace(/[^0-9+\-*/.()\s]/g, '')
|
||||
if (!s) return null
|
||||
try {
|
||||
const r = new Function('return (' + s + ')')()
|
||||
return isFinite(r) ? Math.round(r * 10000) / 10000 : null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将明细数据按日期分组,并计算指标公式,生成完整行数据
|
||||
*
|
||||
* @param {Object} report - 生产月报
|
||||
* @param {Array} details - 生产成本明细列表
|
||||
* @param {Array} metrics - 生产指标列表
|
||||
* @param {Array} items - 成本项目配置列表
|
||||
* @returns {Array<Object>} data - 完整行数据数组
|
||||
*/
|
||||
function buildComputedRows(report, details, metrics, items) {
|
||||
const itemCodeMap = {} // itemId -> itemCode
|
||||
items.forEach(item => {
|
||||
itemCodeMap[item.itemId] = item.itemCode
|
||||
})
|
||||
|
||||
const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
// 1. 按 detailDate 分组明细
|
||||
const dateMap = {}
|
||||
details.forEach(d => {
|
||||
if (!d.detailDate) return
|
||||
if (!dateMap[d.detailDate]) {
|
||||
dateMap[d.detailDate] = { detailDate: d.detailDate }
|
||||
}
|
||||
const code = itemCodeMap[d.itemId]
|
||||
const sfx = d.shift && d.shift !== '0' ? '_' + d.shift : ''
|
||||
if (code) {
|
||||
dateMap[d.detailDate]['q_' + code + sfx] = parseFloat(d.quantity) || 0
|
||||
}
|
||||
})
|
||||
|
||||
const rows = Object.values(dateMap).sort((a, b) => a.detailDate.localeCompare(b.detailDate))
|
||||
|
||||
// 2. 补齐每个 itemCode 的总量(跨班次汇总)
|
||||
items.forEach(item => {
|
||||
const code = item.itemCode
|
||||
if (!code) return
|
||||
rows.forEach(row => {
|
||||
const v1 = row['q_' + code + '_1'] || 0
|
||||
const v2 = row['q_' + code + '_2'] || 0
|
||||
if (v1 || v2) {
|
||||
row['q_' + code] = v1 + v2
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 3. 逐行计算指标公式
|
||||
const rp = report || {}
|
||||
rows.forEach(row => {
|
||||
metrics.forEach((m, mi) => {
|
||||
if (!m.metricFormula) return
|
||||
|
||||
let f = m.metricFormula
|
||||
|
||||
// 替换其他指标的引用(按定义顺序,前面的指标可被后面的引用)
|
||||
for (let pmi = 0; pmi < mi; pmi++) {
|
||||
const pm = metrics[pmi]
|
||||
if (!pm.metricName) continue
|
||||
const pn = esc(pm.metricName)
|
||||
const pv = row['mv_' + pm.metricName]
|
||||
if (pv != null) {
|
||||
f = f.replace(new RegExp('@\\{' + pn + '\\}', 'g'), pv)
|
||||
}
|
||||
}
|
||||
|
||||
// 替换明细项引用 @{itemCode}
|
||||
items.forEach(item => {
|
||||
if (!item.itemCode) return
|
||||
const code = esc(item.itemCode)
|
||||
const totalVal = parseFloat(row['q_' + item.itemCode]) || 0
|
||||
const val1 = parseFloat(row['q_' + item.itemCode + '_1']) || 0
|
||||
const val2 = parseFloat(row['q_' + item.itemCode + '_2']) || 0
|
||||
|
||||
f = f.replace(new RegExp('@\\{' + code + '\\}\\.甲班', 'g'), val1)
|
||||
f = f.replace(new RegExp('@\\{' + code + '\\}\\.乙班', 'g'), val2)
|
||||
f = f.replace(new RegExp('@\\{' + code + '\\}', 'g'), totalVal)
|
||||
})
|
||||
|
||||
// 替换系统变量
|
||||
f = f.replace(/input_weight/g, rp.inputWeight || 0)
|
||||
.replace(/output_weight/g, rp.outputWeight || 0)
|
||||
.replace(/price/g, (m.usePrice === 1) ? (m.metricValue || 0) : 0)
|
||||
|
||||
const result = evalFormula(f)
|
||||
if (result != null) {
|
||||
row['mv_' + m.metricName] = result
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期和产线类型的全部成本数据
|
||||
*
|
||||
* @param {string} date - 日期,格式 yyyy-MM-dd,如 "2024-01-15"
|
||||
* @param {string} lineType - 产线类型,"1"=镀锌,"2"=酸轧
|
||||
* @returns {Promise<{
|
||||
* report: Object|null, // 生产月报
|
||||
* metrics: Array, // 生产指标明细列表(公式列)
|
||||
* details: Array, // 生产成本明细列表(原始数据)
|
||||
* items: Array, // 成本项目配置列表(原始数据列定义)
|
||||
* data: Array, // 完整行数据:按日期分组,包含原始值(q_xxx)和计算值(mv_xxx)
|
||||
* prices: Array, // 成本单价历史列表
|
||||
* detailMap: Object, // 按 itemId 分组的明细
|
||||
* itemMap: Object, // 按 itemId 索引的项目配置
|
||||
* metricMap: Object // 按 metricId 索引的指标
|
||||
* }>}
|
||||
*/
|
||||
export async function getCostDataByDate(date, lineType) {
|
||||
// 1. 查找指定日期和产线类型的生产月报
|
||||
// 假设每个日期只有一条月报,所以取第一个, 需要将日期转为第一天的日期
|
||||
const firstDay = new Date(date)
|
||||
firstDay.setDate(1)
|
||||
console.log(firstDay)
|
||||
// 使用北京时间,转换为东八区时间
|
||||
const year = firstDay.getFullYear()
|
||||
const month = (firstDay.getMonth() + 1).toString().padStart(2, '0')
|
||||
const firstDayStr = year + '-' + month + '-01 00:00:00'
|
||||
const reportRes = await listProdReport({
|
||||
reportDate: firstDayStr,
|
||||
lineType: lineType,
|
||||
pageNum: 1,
|
||||
pageSize: 1
|
||||
})
|
||||
console.log(reportRes)
|
||||
const reports = reportRes.rows || []
|
||||
const report = reports.length > 0 ? reports[0] : null
|
||||
|
||||
// 2. 获取所有成本项目配置(用于映射 itemId)
|
||||
const itemRes = await listItem({ pageNum: 1, pageSize: 9999 })
|
||||
const items = itemRes.rows || []
|
||||
const itemMap = {}
|
||||
items.forEach(item => {
|
||||
itemMap[item.itemId] = item
|
||||
})
|
||||
|
||||
// 3. 获取该日期下生效的单价历史
|
||||
const priceRes = await listPrice({
|
||||
effectiveDate: date,
|
||||
pageNum: 1,
|
||||
pageSize: 9999
|
||||
})
|
||||
const prices = priceRes.rows || []
|
||||
|
||||
// 如果没有找到月报,返回基础数据
|
||||
if (!report) {
|
||||
return {
|
||||
report: null,
|
||||
metrics: [],
|
||||
details: [],
|
||||
items,
|
||||
data: [],
|
||||
prices,
|
||||
detailMap: {},
|
||||
itemMap,
|
||||
metricMap: {}
|
||||
}
|
||||
}
|
||||
|
||||
const reportId = report.reportId
|
||||
|
||||
// 4. 获取该月报的所有生产成本明细
|
||||
const detailRes = await listProdDetail({
|
||||
reportId,
|
||||
pageNum: 1,
|
||||
pageSize: 9999
|
||||
})
|
||||
const details = detailRes.rows || []
|
||||
|
||||
// 5. 构建按 itemId 分组的明细
|
||||
const detailMap = {}
|
||||
details.forEach(detail => {
|
||||
const key = detail.itemId
|
||||
if (!detailMap[key]) {
|
||||
detailMap[key] = []
|
||||
}
|
||||
detailMap[key].push(detail)
|
||||
})
|
||||
|
||||
// 6. 获取该月报的所有生产指标
|
||||
const metricRes = await listProdMetric({
|
||||
reportId,
|
||||
pageNum: 1,
|
||||
pageSize: 9999
|
||||
})
|
||||
const metrics = metricRes.rows || []
|
||||
const metricMap = {}
|
||||
metrics.forEach(metric => {
|
||||
metricMap[metric.metricId] = metric
|
||||
})
|
||||
|
||||
// 7. 构建完整行数据(原始值 + 公式计算值)
|
||||
const data = buildComputedRows(report, details, metrics, items)
|
||||
|
||||
return {
|
||||
report,
|
||||
metrics,
|
||||
details,
|
||||
items,
|
||||
data,
|
||||
prices,
|
||||
detailMap,
|
||||
itemMap,
|
||||
metricMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期范围、产线类型的成本数据,支持多天批量查询
|
||||
*
|
||||
* @param {string} startDate - 开始日期 yyyy-MM-dd
|
||||
* @param {string} endDate - 结束日期 yyyy-MM-dd
|
||||
* @param {string} lineType - 产线类型
|
||||
* @returns {Promise<Array<{ date: string, report, metrics, details, items, data, prices }>>}
|
||||
*/
|
||||
export async function getCostDataByDateRange(startDate, endDate, lineType) {
|
||||
// 查询日期范围内的所有月报
|
||||
const reportRes = await listProdReport({
|
||||
lineType,
|
||||
pageNum: 1,
|
||||
pageSize: 9999
|
||||
})
|
||||
const allReports = (reportRes.rows || []).filter(r => {
|
||||
return r.reportDate >= startDate && r.reportDate <= endDate
|
||||
})
|
||||
|
||||
// 获取所有项目配置和单价(共享数据,只查一次)
|
||||
const itemRes = await listItem({ pageNum: 1, pageSize: 9999 })
|
||||
const items = itemRes.rows || []
|
||||
const itemMap = {}
|
||||
items.forEach(item => {
|
||||
itemMap[item.itemId] = item
|
||||
})
|
||||
|
||||
const priceRes = await listPrice({
|
||||
pageNum: 1,
|
||||
pageSize: 9999
|
||||
})
|
||||
const prices = priceRes.rows || []
|
||||
|
||||
// 逐日报表获取完整数据
|
||||
const results = []
|
||||
for (const report of allReports) {
|
||||
const reportId = report.reportId
|
||||
|
||||
const [detailRes, metricRes] = await Promise.all([
|
||||
listProdDetail({ reportId, pageNum: 1, pageSize: 9999 }),
|
||||
listProdMetric({ reportId, pageNum: 1, pageSize: 9999 })
|
||||
])
|
||||
|
||||
const details = detailRes.rows || []
|
||||
const metrics = metricRes.rows || []
|
||||
|
||||
const detailMap = {}
|
||||
details.forEach(detail => {
|
||||
const key = detail.itemId
|
||||
if (!detailMap[key]) detailMap[key] = []
|
||||
detailMap[key].push(detail)
|
||||
})
|
||||
|
||||
const metricMap = {}
|
||||
metrics.forEach(metric => {
|
||||
metricMap[metric.metricId] = metric
|
||||
})
|
||||
|
||||
const data = buildComputedRows(report, details, metrics, items)
|
||||
|
||||
results.push({
|
||||
date: report.reportDate,
|
||||
report,
|
||||
metrics,
|
||||
details,
|
||||
items,
|
||||
data,
|
||||
prices,
|
||||
detailMap,
|
||||
itemMap,
|
||||
metricMap
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 itemId 获取指定日期下该成本项目的明细数据
|
||||
*
|
||||
* @param {string} date - 日期 yyyy-MM-dd
|
||||
* @param {string} lineType - 产线类型
|
||||
* @param {string|number} itemId - 成本项目ID
|
||||
* @returns {Promise<{ quantity: number|null, unitPrice: number|null, amount: number|null }>}
|
||||
*/
|
||||
export async function getCostItemData(date, lineType, itemId) {
|
||||
const { details, items } = await getCostDataByDate(date, lineType)
|
||||
const itemDetails = details.filter(d => String(d.itemId) === String(itemId))
|
||||
const item = items.find(i => String(i.itemId) === String(itemId))
|
||||
|
||||
let totalQuantity = 0
|
||||
itemDetails.forEach(d => {
|
||||
totalQuantity += parseFloat(d.quantity) || 0
|
||||
})
|
||||
|
||||
return {
|
||||
itemId,
|
||||
itemName: item ? item.itemName : '',
|
||||
itemCode: item ? item.itemCode : '',
|
||||
category: item ? item.category : '',
|
||||
unit: item ? item.unit : '',
|
||||
quantity: totalQuantity || null,
|
||||
unitPrice: itemDetails.length > 0 ? itemDetails[0].unitPrice : null,
|
||||
amount: itemDetails.length > 0 ? itemDetails[0].amount : null,
|
||||
details: itemDetails
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getCostDataByDate,
|
||||
getCostDataByDateRange,
|
||||
getCostItemData
|
||||
}
|
||||
Reference in New Issue
Block a user