feat: gate PDA loading evidence by ready task
This commit is contained in:
+92
-44
@@ -14,18 +14,18 @@
|
||||
<view class="work-content">
|
||||
<view class="box-select-wrap">
|
||||
<button class="box-select-btn" @tap="toggleBoxDropdown">
|
||||
<text>{{ currentBox.boxNo }}<template v-if="currentBox.currentContainerNo"> · 当前柜号 {{ currentBox.currentContainerNo }}</template> · 订单 {{ currentBox.orderNo }}</text>
|
||||
<text>集装箱号 {{ currentBox.currentContainerNo || '同步中' }} · 柜次号 {{ currentBox.cabinetNo || '同步中' }} · 订单 {{ currentBox.orderNo }}</text>
|
||||
<text class="box-chevron" :class="{ open: boxDropdownOpen }">▼</text>
|
||||
</button>
|
||||
<view v-if="boxDropdownOpen" class="box-dropdown">
|
||||
<view
|
||||
v-for="(box, index) in boxes"
|
||||
:key="box.boxNo"
|
||||
:key="box.taskId || box.cabinetNo"
|
||||
class="box-option"
|
||||
:class="{ active: index === currentBoxIndex, completed: completedBoxes[index] }"
|
||||
@tap="requestBoxSwitch(index)"
|
||||
>
|
||||
<view><text class="box-option-number">{{ box.boxNo }}</text><text class="box-option-order">{{ box.orderNo }}</text></view>
|
||||
<view><text class="box-option-number">{{ box.currentContainerNo || '集装箱号同步中' }}</text><text class="box-option-order">柜次号 {{ box.cabinetNo || '同步中' }} · {{ box.orderNo }}</text></view>
|
||||
<text v-if="completedBoxes[index]" class="box-option-state">已完成</text>
|
||||
<text v-else-if="hasBoxDraft(index)" class="box-option-state draft">有草稿</text>
|
||||
</view>
|
||||
@@ -70,7 +70,7 @@
|
||||
<text> / {{ summary.total }} 箱</text>
|
||||
</template>
|
||||
</view>
|
||||
<button class="box-tasks-btn" @tap="openBoxList('pending')">箱任务 {{ completedCount }}/{{ boxes.length }}</button>
|
||||
<button class="box-tasks-btn" @tap="openBoxList('pending')">柜次任务 {{ completedCount }}/{{ boxes.length }}</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="progress-bar-bg">
|
||||
@@ -81,7 +81,7 @@
|
||||
<view class="pick-list">
|
||||
<view v-if="orderedItems.length === 0" class="empty-state">
|
||||
<text class="empty-state-icon">□</text>
|
||||
<text class="empty-state-text">该箱号暂无拣货任务</text>
|
||||
<text class="empty-state-text">当前集装箱暂无拣货明细</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="item in orderedItems"
|
||||
@@ -297,7 +297,7 @@
|
||||
<view class="complete-modal" role="dialog" aria-modal="true" aria-label="拣货完成" @tap.stop>
|
||||
<PopupHeader title="拣货完成" @close="closeOverlay" />
|
||||
<view class="complete-icon">✓</view>
|
||||
<text class="complete-desc">箱号 {{ currentBox.boxNo }} · 订单 {{ currentBox.orderNo }}</text>
|
||||
<text class="complete-desc">集装箱号 {{ currentBox.currentContainerNo }} · 柜次号 {{ currentBox.cabinetNo }} · 订单 {{ currentBox.orderNo }}</text>
|
||||
<view class="complete-stats">
|
||||
<view class="complete-stat">
|
||||
<text class="complete-stat-num green">{{ completionSummary.picked }}</text>
|
||||
@@ -324,10 +324,10 @@
|
||||
</uni-popup>
|
||||
|
||||
<uni-popup ref="allCompletePopup" type="center" :is-mask-click="true" @change="handleOverlayChange('allComplete', $event)">
|
||||
<view class="complete-modal" role="dialog" aria-modal="true" aria-label="全部箱号已完成" @tap.stop>
|
||||
<PopupHeader title="全部箱号已完成" @close="closeOverlay" />
|
||||
<view class="complete-modal" role="dialog" aria-modal="true" aria-label="全部柜次已完成" @tap.stop>
|
||||
<PopupHeader title="全部柜次已完成" @close="closeOverlay" />
|
||||
<view class="complete-icon">✓</view>
|
||||
<text class="complete-desc">本班次演示任务 {{ boxes.length }} 箱已全部处理</text>
|
||||
<text class="complete-desc">本班次 {{ boxes.length }} 个柜次任务已全部处理</text>
|
||||
<button class="all-complete-btn" @tap="leavePicking">返回工作台</button>
|
||||
</view>
|
||||
</uni-popup>
|
||||
@@ -385,7 +385,7 @@
|
||||
selectedIncompleteReason: '',
|
||||
completionSummary: { picked: 0, partial: 0, skipped: 0, autoSkipped: 0 },
|
||||
evidenceSlots: EVIDENCE_SLOTS,
|
||||
evidenceFiles: {}, evidenceDrafts: {}, evidencePreviewCache: {}, evidenceQueueItems: [], evidenceQueue: null, evidenceContextId: 0, evidenceIdentity: null, cameraPicking: false, sealNo: '', loadingVersion: null, pickingVersion: null, remotePick: null,
|
||||
evidenceFiles: {}, evidenceDrafts: {}, evidencePreviewCache: {}, evidenceQueueItems: [], evidenceQueue: null, evidenceContextId: 0, evidenceIdentity: null, cameraPicking: false, evidencePreviewOpening: false, evidencePreviewReturning: false, sealNo: '', loadingVersion: null, pickingVersion: null, remotePick: null,
|
||||
remoteTaskId: null, assignmentVersion: null, submitId: '',
|
||||
taskRequestId: 0,
|
||||
remoteError: '', remoteLoading: false, submitting: false,
|
||||
@@ -424,6 +424,11 @@
|
||||
return Boolean(this.completedBoxes[this.currentBoxIndex])
|
||||
|| isPickingCompleteTask(this.remotePick)
|
||||
},
|
||||
pickingOperationReady() {
|
||||
return this.remotePick?.workflowStatus === 'IN_PROGRESS'
|
||||
&& Boolean(this.remoteTaskId && this.currentBox.inboundId && this.currentBox.loadingPlanId && this.currentBox.containerBookingId)
|
||||
&& this.pickingVersion !== null
|
||||
},
|
||||
loadingStageReady() {
|
||||
return this.pickingCompleted
|
||||
&& this.evidenceIdentity
|
||||
@@ -461,10 +466,10 @@
|
||||
return ''
|
||||
},
|
||||
submitButtonText() {
|
||||
return this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted ? '该箱已完成' : this.summary.buttonText
|
||||
return this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted ? '该柜次已完成' : this.summary.buttonText
|
||||
},
|
||||
submitStatusHint() {
|
||||
if (this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted) return '本箱任务已完成'
|
||||
if (this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted) return '本柜次任务已完成'
|
||||
if (!this.summary.canSubmit) return '请先处理至少一件'
|
||||
if (this.summary.pending > 0) return '提交时需选择原因'
|
||||
return `已处理 ${this.summary.processed}/${this.summary.total}`
|
||||
@@ -494,10 +499,10 @@
|
||||
},
|
||||
onLoad(options = {}) {
|
||||
if (!requireSession()) return
|
||||
if (options.taskId && (options.cabinetNo || options.containerNo)) {
|
||||
if (options.taskId) {
|
||||
this.boxes = [{
|
||||
boxNo: String(options.cabinetNo || options.containerNo), cabinetNo: String(options.cabinetNo || options.containerNo), currentContainerNo: options.currentContainerNo ? String(options.currentContainerNo) : null, orderNo: String(options.orderNo || options.inboundCode || ''),
|
||||
taskId: String(options.taskId), loadingPlanId: options.loadingPlanId ? String(options.loadingPlanId) : null, containerBookingId: options.containerBookingId ? String(options.containerBookingId) : null,
|
||||
boxNo: String(options.currentContainerNo || options.cabinetNo || '同步中'), cabinetNo: options.cabinetNo ? String(options.cabinetNo) : null, currentContainerNo: options.currentContainerNo ? String(options.currentContainerNo) : null, orderNo: String(options.orderNo || options.inboundCode || ''),
|
||||
taskId: String(options.taskId), loadingPlanId: null, containerBookingId: null,
|
||||
inboundId: options.inboundId ? String(options.inboundId) : null,
|
||||
inboundVersion: options.inboundVersion == null ? null : Number(options.inboundVersion),
|
||||
warehouseId: options.warehouseId ? String(options.warehouseId) : null,
|
||||
@@ -507,16 +512,27 @@
|
||||
this.remoteTaskId = options.taskId ? String(options.taskId) : null
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
if (!requireSession()) return
|
||||
this.startHardwareScanner()
|
||||
if (this.cameraPicking) {
|
||||
this.debugEvidence('choose.resume', { evidenceIdentity: this.evidenceIdentity })
|
||||
return
|
||||
}
|
||||
this.syncRemoteTask()
|
||||
},
|
||||
onHide() { this.stopHardwareScanner() },
|
||||
onShow() {
|
||||
if (!requireSession()) return
|
||||
this.startHardwareScanner()
|
||||
if (this.cameraPicking) {
|
||||
this.debugEvidence('choose.resume', { evidenceIdentity: this.evidenceIdentity })
|
||||
return
|
||||
}
|
||||
if (this.evidencePreviewReturning) {
|
||||
this.evidencePreviewReturning = false
|
||||
this.debugEvidence('preview.resume', { evidenceIdentity: this.evidenceIdentity })
|
||||
return
|
||||
}
|
||||
this.syncRemoteTask()
|
||||
},
|
||||
onHide() {
|
||||
if (this.evidencePreviewOpening) {
|
||||
this.evidencePreviewOpening = false
|
||||
this.evidencePreviewReturning = true
|
||||
}
|
||||
this.stopHardwareScanner()
|
||||
},
|
||||
onUnload() { this.stopHardwareScanner(); this.persistSession(); this.clearTimers() },
|
||||
beforeUnmount() { this.stopHardwareScanner(); this.clearTimers() },
|
||||
onBackPress() {
|
||||
@@ -593,6 +609,11 @@
|
||||
}
|
||||
return true
|
||||
},
|
||||
requirePickingOperation() {
|
||||
if (this.pickingOperationReady) return true
|
||||
this.showToast('任务尚未领取完成,请稍候或刷新重试', 'warning')
|
||||
return false
|
||||
},
|
||||
requireEvidenceStage() {
|
||||
if (!this.pickingCompleted) {
|
||||
this.showToast('请先提交拣货完成', 'warning')
|
||||
@@ -680,7 +701,7 @@
|
||||
savePickingSession({ boxes: this.boxes, currentBoxIndex: this.currentBoxIndex, itemStates: this.itemStates, partialQuantities: this.partialQuantities, scannedBoxCodes: this.scannedBoxCodes, boxDrafts: this.boxDrafts, completedBoxes: this.completedBoxes, scanValue: this.scanValue, scanHistory: this.scanHistory }, this.draftIdentity())
|
||||
},
|
||||
leavePicking() { this.persistSession(); this.activeOverlay = ''; navigateBackOr('/pages/home/home') },
|
||||
clearTimers() { clearTimeout(this._toastTimer) },
|
||||
clearTimers() { clearTimeout(this._toastTimer); clearTimeout(this._evidencePreviewOpenTimer) },
|
||||
scrollToTop() { this.$nextTick(() => { if (typeof uni !== 'undefined' && typeof uni.pageScrollTo === 'function') uni.pageScrollTo({ scrollTop: 0, duration: 0 }) }) },
|
||||
saveCurrentBoxDraft() {
|
||||
if (!this.currentBox.boxNo) return
|
||||
@@ -739,7 +760,7 @@
|
||||
this.activeStage = 'picking'
|
||||
this.currentBox.inboundId = null
|
||||
this.currentBox.inboundVersion = null
|
||||
this.remoteTaskId = null
|
||||
this.remoteTaskId = this.currentBox.taskId || null
|
||||
this.remotePick = null
|
||||
this.pickingVersion = null
|
||||
this.loadingVersion = null
|
||||
@@ -748,8 +769,8 @@
|
||||
this.boxDropdownOpen = false
|
||||
this.loadBoxState(index)
|
||||
this.scrollToTop()
|
||||
if (session.token && session.tenantId && this.currentBox.boxNo) this.syncRemoteTask()
|
||||
this.showToast(`切换至 ${this.currentBox.boxNo}`, 'success')
|
||||
if (session.token && this.currentBox.taskId) this.syncRemoteTask()
|
||||
this.showToast(`切换至集装箱 ${this.currentBox.currentContainerNo || '同步中'}`, 'success')
|
||||
},
|
||||
itemState(item) {
|
||||
return this.itemStates[item.id] || ITEM_STATUS.PENDING
|
||||
@@ -765,6 +786,7 @@
|
||||
return `${item.desc}${reason}`
|
||||
},
|
||||
processScannedValue(scanValue, source = 'broadcast') {
|
||||
if (!this.requirePickingOperation()) return
|
||||
const value = scanValue.trim()
|
||||
if (!value) {
|
||||
const manual = source === 'manual'
|
||||
@@ -812,7 +834,10 @@
|
||||
if (!current || !urls.length) { this.showToast('照片地址尚未同步,请刷新后重试', 'warning'); return }
|
||||
if (typeof uni.previewImage !== 'function') { this.showToast('当前环境不支持预览照片', 'warning'); return }
|
||||
this.debugEvidence('preview.open', { evidenceType: type, evidenceIdentity: this.evidenceIdentity, count: urls.length })
|
||||
uni.previewImage({ current, urls })
|
||||
this.evidencePreviewOpening = true
|
||||
clearTimeout(this._evidencePreviewOpenTimer)
|
||||
this._evidencePreviewOpenTimer = setTimeout(() => { this.evidencePreviewOpening = false }, 1500)
|
||||
uni.previewImage({ current, urls, fail: () => { this.evidencePreviewOpening = false } })
|
||||
},
|
||||
evidenceItems(result) {
|
||||
if (Array.isArray(result?.evidences)) return result.evidences
|
||||
@@ -835,25 +860,33 @@
|
||||
return ''
|
||||
},
|
||||
async syncRemoteTask() {
|
||||
if (!isRemoteReady(this.currentBox.inboundId || this.currentBox.boxNo)) return
|
||||
if (!session.token) return
|
||||
const requestedTaskId = this.remoteTaskId || this.currentBox.taskId
|
||||
if (!requestedTaskId) { this.remoteError = '当前没有可进入的拣货任务,请返回任务列表刷新'; return }
|
||||
this.rememberEvidenceDraft()
|
||||
const requestId = ++this.taskRequestId
|
||||
const selector = { taskId: this.remoteTaskId || this.currentBox.taskId, cabinetNo: this.currentBox.cabinetNo, loadingPlanId: this.currentBox.loadingPlanId }
|
||||
const selector = { taskId: requestedTaskId }
|
||||
this.currentBox.inboundId = null
|
||||
this.currentBox.loadingPlanId = null
|
||||
this.currentBox.containerBookingId = null
|
||||
this.loadingVersion = null
|
||||
this.resetEvidenceState()
|
||||
this.debugSubmit('refresh.start', { requestId, cabinetNo: this.currentBox.cabinetNo || null, selector })
|
||||
this.debugSubmit('refresh.start', { requestId, selector })
|
||||
this.remoteLoading = true
|
||||
this.remoteError = ''
|
||||
try {
|
||||
const warehouseId = this.currentBox.warehouseId || session.currentUser?.warehouseId || session.currentUser?.warehouse
|
||||
const task = await getContainerPickingTask(selector, warehouseId)
|
||||
if (requestId !== this.taskRequestId) return
|
||||
if (!task) throw new Error('未找到当前柜次的拣货任务')
|
||||
if (!task) throw new Error('服务端未返回当前拣货任务')
|
||||
const detailTaskId = task.taskId ?? task.id
|
||||
if (String(detailTaskId || '') !== String(requestedTaskId)) throw new Error('服务端返回的拣货任务与当前选择不一致,请返回刷新')
|
||||
if (!task.inboundId || !task.loadingPlanId || !task.containerBookingId) throw new Error('任务尚未同步完整作业关联,请返回任务列表刷新')
|
||||
if (task.version === null || task.version === undefined) throw new Error('任务状态尚未同步,请刷新重试')
|
||||
if (!task.currentContainerNo) throw new Error('任务尚未同步集装箱号,请返回任务列表刷新')
|
||||
const remoteItems = task.items || task.pickItems || task.details || task.lines
|
||||
if (Array.isArray(remoteItems) && remoteItems.length) {
|
||||
if (!Array.isArray(remoteItems) || !remoteItems.length) throw new Error('当前拣货任务暂无可作业明细,请返回刷新')
|
||||
{
|
||||
this.currentBox.items = remoteItems.map((item) => ({
|
||||
...item,
|
||||
id: item.id ?? item.itemId,
|
||||
@@ -873,10 +906,11 @@
|
||||
skipReason: item.skipReason || '', finalState: item.finalState || '', finalPartialQuantity: item.finalPartialQuantity || 0
|
||||
}))
|
||||
const firstItem = this.currentBox.items[0]
|
||||
const evidenceItem = this.currentBox.items.find((item) => String(item.inboundId || '') === String(task.inboundId))
|
||||
if (!evidenceItem) throw new Error('任务明细与入库单关联不一致,请返回刷新')
|
||||
this.currentBox.orderNo = firstItem.orderNo || firstItem.inboundCode || this.currentBox.orderNo
|
||||
if (!firstItem.inboundId) throw new Error('服务端任务明细缺少 inboundId,无法进入装柜取证')
|
||||
this.currentBox.inboundId = firstItem.inboundId
|
||||
this.currentBox.tenantId = firstItem.tenantId || null
|
||||
this.currentBox.inboundId = task.inboundId
|
||||
this.currentBox.tenantId = evidenceItem.tenantId || null
|
||||
const serverStates = createItemStates(this.currentBox.items)
|
||||
const serverPartialQuantities = {}
|
||||
this.currentBox.items.forEach((item) => {
|
||||
@@ -890,28 +924,35 @@
|
||||
this.partialQuantities = serverPartialQuantities
|
||||
this.scannedBoxCodes = {}
|
||||
}
|
||||
this.remoteTaskId = task.taskId ?? task.id
|
||||
this.remoteTaskId = detailTaskId
|
||||
if (!this.remoteTaskId) throw new Error('服务端任务缺少 taskId,无法开始拣货')
|
||||
this.assignmentVersion = task.assignmentVersion ?? task.version ?? 0
|
||||
this.currentBox.loadingPlanId = task.loadingPlanId
|
||||
this.currentBox.containerBookingId = task.containerBookingId
|
||||
this.currentBox.cabinetNo = task.cabinetNo
|
||||
this.currentBox.boxNo = task.cabinetNo
|
||||
this.currentBox.boxNo = task.currentContainerNo
|
||||
this.currentBox.currentContainerNo = task.currentContainerNo
|
||||
const remoteTask = normalizePickingTask(task)
|
||||
this.assignmentVersion = remoteTask.assignment?.version ?? remoteTask.assignmentVersion ?? (remoteTask.workflowStatus === 'UNASSIGNED' ? 0 : null)
|
||||
if (this.assignmentVersion === null) throw new Error('任务领取状态尚未同步,请刷新重试')
|
||||
let claimed = null
|
||||
if (!isPickingCompleteTask(remoteTask)) {
|
||||
if (remoteTask.workflowStatus === 'UNASSIGNED') {
|
||||
const operationId = `claim-${session.currentUser?.id || 'operator'}-${this.remoteTaskId}-${task.loadingPlanId}-${task.containerBookingId}`
|
||||
claimed = await claimContainerPickingTask(this.remoteTaskId, this.assignmentVersion, operationId)
|
||||
if (requestId !== this.taskRequestId) return
|
||||
} else if (remoteTask.workflowStatus === 'IN_PROGRESS') {
|
||||
const assigneeId = remoteTask.assignment?.assigneeId
|
||||
if (!assigneeId || String(assigneeId) !== String(session.currentUser?.id || '')) throw new Error('当前任务已由其他作业员领取,请返回任务列表刷新')
|
||||
} else if (!isPickingCompleteTask(remoteTask)) {
|
||||
throw new Error('当前任务尚未进入可拣货状态,请返回任务列表刷新')
|
||||
}
|
||||
this.remotePick = normalizePickingTask({ ...task, ...(claimed || {}) })
|
||||
if (remoteTask.workflowStatus === 'UNASSIGNED' && this.remotePick.workflowStatus !== 'IN_PROGRESS') throw new Error('任务领取尚未完成,请刷新重试')
|
||||
this.pickingVersion = this.remotePick.pickingVersion ?? this.remotePick.version ?? null
|
||||
this.loadingVersion = task.inboundVersion ?? task.loadingVersion ?? this.remotePick.inboundVersion ?? this.remotePick.loadingVersion ?? null
|
||||
this.loadingVersion = this.remotePick.inboundVersion ?? this.remotePick.loadingVersion ?? null
|
||||
this.evidenceIdentity = this.captureEvidenceIdentity()
|
||||
this.restoreEvidenceDraft()
|
||||
this.debugSubmit('refresh.ready', { requestId, workflowStatus: this.remotePick.workflowStatus, taskStatus: this.remotePick.taskStatus, assignmentVersion: this.assignmentVersion, loadingVersion: this.loadingVersion, evidenceIdentity: this.evidenceIdentity })
|
||||
this.assignmentVersion = this.remotePick.assignmentVersion ?? this.assignmentVersion
|
||||
this.assignmentVersion = this.remotePick.assignment?.version ?? this.remotePick.assignmentVersion ?? this.assignmentVersion
|
||||
if (!this.pickingCompleted) this.restoreRemoteDraft()
|
||||
if (this.loadingStageReady) await this.loadLoadingEvidence()
|
||||
} catch (error) {
|
||||
@@ -1133,6 +1174,7 @@
|
||||
this.persistSession()
|
||||
},
|
||||
pickItem(id, source = 'manual') {
|
||||
if (!this.requirePickingOperation()) return
|
||||
const item = this.currentBox.items.find((entry) => entry.id === id)
|
||||
const available = this.availablePickQuantity(item)
|
||||
if (available !== null && available < Number(item.qty || 0)) { this.showToast(`库存/库位可用量不足:可拣 ${available}`, 'error'); return }
|
||||
@@ -1146,6 +1188,7 @@
|
||||
this.scanHistory = [entry, ...this.scanHistory].slice(0, 3)
|
||||
},
|
||||
undoItem(id) {
|
||||
if (!this.requirePickingOperation()) return
|
||||
const item = this.currentBox.items.find((entry) => entry.id === id)
|
||||
if (item) item.skipReason = ''
|
||||
this.itemStates = { ...this.itemStates, [id]: ITEM_STATUS.PENDING }
|
||||
@@ -1158,11 +1201,13 @@
|
||||
this.persistSession()
|
||||
},
|
||||
openSkip(id) {
|
||||
if (!this.requirePickingOperation()) return
|
||||
this.skipTargetId = id
|
||||
this.selectedSkipReason = ''
|
||||
this.openOverlay('skip')
|
||||
},
|
||||
confirmSkip() {
|
||||
if (!this.requirePickingOperation()) return
|
||||
if (!this.selectedSkipReason) {
|
||||
this.showToast('请先选择跳拣原因', 'warning')
|
||||
return
|
||||
@@ -1176,6 +1221,7 @@
|
||||
this.persistSession()
|
||||
},
|
||||
openPartial(id) {
|
||||
if (!this.requirePickingOperation()) return
|
||||
this.partialTargetId = id
|
||||
this.partialInput = ''
|
||||
this.openOverlay('partial')
|
||||
@@ -1191,6 +1237,7 @@
|
||||
})
|
||||
},
|
||||
confirmPartial() {
|
||||
if (!this.requirePickingOperation()) return
|
||||
const item = this.partialTargetItem
|
||||
if (!item) return
|
||||
if (!this.partialInputValid) return
|
||||
@@ -1232,6 +1279,7 @@
|
||||
this.requestBoxSwitch(entry.index)
|
||||
},
|
||||
submitPicking() {
|
||||
if (!this.requirePickingOperation()) return
|
||||
if (!this.summary.canSubmit || this.completedBoxes[this.currentBoxIndex] || this.pickingCompleted) return
|
||||
if (this.summary.pending > 0) {
|
||||
this.selectedIncompleteReason = ''
|
||||
|
||||
Reference in New Issue
Block a user