feat: MES/MOM 出入库状态解绑与直接完结通道(后端契约 + 移动端)
- TaskTransferRequest 新增 finish_directly 开关:闭环当前任务但不产生任何下游任务, 产品 overall_status 保持原样,工人无需再借道「入库(virtual_warehouse)」来关闭任务, 从根源上避免产品被误标「待仓库收货」而卡住 MOM 对账 - 空 assignees 分支加防呆校验:想直接完结必须显式传 finish_directly=true, 杜绝漏填导致「转交」静默退化成「直接完结」的丢件级隐患 - 直接完结跳过 _mark_after_sales_if_reactivated:不产生新任务即不构成「回流返厂」信号, 否则已出库设备的正常收官会把产品误翻成售后机(该标志单向不可回退) - 补齐直接完结的独立响应文案,不再拼出「已创建 0 个下一道工序任务「None」」 - 移动端转交弹窗改为「转交个人 / 入库 / 直接完结」三选一互斥
This commit is contained in:
@ -148,7 +148,7 @@
|
||||
</template>
|
||||
<template v-if="actionPopup.type === 'transfer'">
|
||||
<text class="popup-title">完工转交</text>
|
||||
<view class="field-label">接收人 <text class="required">*</text></view>
|
||||
<view class="field-label">接收人 / 处理方式 <text class="required">*</text></view>
|
||||
<view class="user-grid">
|
||||
<view v-for="u in userGridOptions" :key="u.id"
|
||||
:class="['user-grid-item', transferForm.selectedUserId === u.id ? 'user-grid-active' : '']"
|
||||
@ -156,10 +156,12 @@
|
||||
</view>
|
||||
<view class="field-label" style="margin-top:12px;">或</view>
|
||||
<view :class="['user-grid-item', transferForm.isWarehouse ? 'user-grid-active' : '']" style="width:100%;" @tap="toggleWarehouse">📦 入库 (virtual_warehouse)</view>
|
||||
<view :class="['user-grid-item', transferForm.isFinishDirect ? 'user-grid-active' : '']" style="width:100%;margin-top:8px;" @tap="toggleFinishDirect">🏁 直接完结 (不入库)</view>
|
||||
<text class="popup-hint">🏁 直接完结:结束本任务且不创建下游任务,产品状态保持原样(不进入「待仓库收货」)。用于售后返厂直接发走、半成品被直接提走等无需入库的场景</text>
|
||||
<view class="field-label" style="margin-top:12px;">交接备注 <text class="required">*</text></view>
|
||||
<textarea v-model="transferForm.note" class="popup-textarea" placeholder="请填写交接备注(必填)" :maxlength="500" />
|
||||
<view v-if="transferForm.isWarehouse || transferForm.selectedUserId" class="preview-hint">{{ transferForm.isWarehouse ? '产品将入库并从个人待办中移除' : '将创建新任务指派给 ' + (transferUserName || '—') }}</view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary" :disabled="actionLoading || (!transferForm.isWarehouse && !transferForm.selectedUserId) || !transferForm.note.trim()" @tap="doTransfer">{{ actionLoading ? '提交中...' : (transferForm.isWarehouse ? '📦 确认入库' : '确认转交') }}</button></view>
|
||||
<view v-if="transferMode" class="preview-hint">{{ transferPreview }}</view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary" :disabled="actionLoading || !transferMode || !transferForm.note.trim()" @tap="doTransfer">{{ actionLoading ? '提交中...' : transferSubmitLabel }}</button></view>
|
||||
</template>
|
||||
<template v-if="actionPopup.type === 'spawn'">
|
||||
<text class="popup-title">➕ 派发协助分支</text>
|
||||
@ -250,7 +252,7 @@ export default {
|
||||
actionPopup: { visible: false, type: "", task: null }, actionLoading: false, rejectReason: "", receiveRemark: "", receiveTaskName: "",
|
||||
// 📷 驳回异常图片(选填):与追加记录共用同一套选图/上传流程
|
||||
rejectForm: { images: [], pendingCount: 0 },
|
||||
transferForm: { selectedUserId: "", isWarehouse: false, note: "" },
|
||||
transferForm: { selectedUserId: "", isWarehouse: false, isFinishDirect: false, note: "" },
|
||||
spawnForm: { assignee_id: "", remark: "" },
|
||||
// 🛡️ 双重确认倒计时:避免误触
|
||||
confirming: "", // 当前倒计时中的操作 key('' = 无)
|
||||
@ -271,6 +273,10 @@ export default {
|
||||
userLabels() { return this.users.map(u => `${u.full_name} (${u.username})`); },
|
||||
canDeleteImage() { if (!this.recordPopup.task) return true; if (!this.currentUser) return true; const frozen = ["COMPLETED","REJECTED","ARCHIVED","CANCELED"]; if (frozen.includes(this.recordPopup.task.status)) return false; const assignee = this.recordPopup.task.assignee_id; return assignee == this.currentUserId || assignee == this.currentUsername || (this.currentUser && this.currentUser.id == assignee) || (this.currentUser && this.currentUser.username == assignee); },
|
||||
transferUserName() { const u = this.userOptions.find(u => u.id === this.transferForm.selectedUserId); return u ? u.name : ""; },
|
||||
// 🏁 转交弹窗的三选一模式:'' = 未选 | 'user' 转交个人 | 'warehouse' 入库 | 'direct' 直接完结
|
||||
transferMode() { const f = this.transferForm; if (f.isFinishDirect) return 'direct'; if (f.isWarehouse) return 'warehouse'; return f.selectedUserId ? 'user' : ''; },
|
||||
transferSubmitLabel() { return { direct: '🏁 确认直接完结', warehouse: '📦 确认入库', user: '确认转交' }[this.transferMode] || '确认转交'; },
|
||||
transferPreview() { return { direct: '任务将直接完结,不创建下游任务;产品状态保持不变,不会进入「待仓库收货」', warehouse: '产品将入库并从个人待办中移除', user: '将创建新任务指派给 ' + (this.transferUserName || '—') }[this.transferMode] || ''; },
|
||||
spawnUserName() { const u = this.userOptions.find(u => u.id === this.spawnForm.assignee_id); return u ? u.name : ""; },
|
||||
userGridOptions() { return (this.userOptions || []).map(u => ({ id: u.id, name: u.name })); },
|
||||
modeToggleLabel() { if (this.currentMode === 'workspace') return '📇 流转卡片'; if (this.currentMode === 'swipe') return '🌳 流转树'; return '🛠️ 工作区'; },
|
||||
@ -488,7 +494,7 @@ export default {
|
||||
if (type === "transfer" || type === "spawn") { uni.showLoading({ title: "加载数据..." }); try { if (!this.userOptions.length) await this.loadUsers(); this.processOptions = ["🏭 入库 (virtual_warehouse)", ...this.availableTaskOptions]; } finally { uni.hideLoading(); } }
|
||||
this.actionPopup = { visible: true, type, task }; this.rejectReason = ""; this.receiveRemark = ""; this.receiveTaskName = "";
|
||||
this.rejectForm = { images: [], pendingCount: 0 }; this.isUploading = false;
|
||||
this.transferForm = { selectedUserId: "", isWarehouse: false, note: "" };
|
||||
this.transferForm = { selectedUserId: "", isWarehouse: false, isFinishDirect: false, note: "" };
|
||||
this.spawnForm = { assignee_id: "", remark: "" };
|
||||
},
|
||||
handleViewRecords(task) { uni.navigateTo({ url: `/pages/scan/records?taskId=${task.id}` }); },
|
||||
@ -598,9 +604,28 @@ export default {
|
||||
// 驳回:reason 必填,images 选填(编号错误等场景允许空数组)
|
||||
async doReject() { if (this.isUploading) return; this.actionLoading = true; try { const opId = this.currentUsername || this.currentUserId; await post(`/tasks/${this.actionPopup.task.id}/reject?operator_id=${encodeURIComponent(opId)}`, { reason: this.rejectReason.trim(), images: this.rejectForm.images }); uni.showToast({ title: "已驳回", icon: "success" }); this.closeActionPopup(); this.doQuery(this.product.serial_number); } catch (e) { this.handleNetworkFailure(e, "驳回任务"); } finally { this.actionLoading = false; } },
|
||||
// 转交 — 互斥选择
|
||||
selectTransferUser(userId) { this.transferForm.selectedUserId = userId; this.transferForm.isWarehouse = false; },
|
||||
toggleWarehouse() { this.transferForm.isWarehouse = !this.transferForm.isWarehouse; if (this.transferForm.isWarehouse) this.transferForm.selectedUserId = ""; },
|
||||
async doTransfer() { this.actionLoading = true; try { const assignees = this.transferForm.isWarehouse ? ["virtual_warehouse"] : [this.transferForm.selectedUserId]; const taskName = this.transferForm.isWarehouse ? "🏭 入库 (virtual_warehouse)" : "待确认"; await post(`/tasks/${this.actionPopup.task.id}/transfer`, { next_tasks: [{ task_name: taskName, assignees }], note: this.transferForm.note.trim() || undefined }); uni.showToast({ title: this.transferForm.isWarehouse ? "已入库" : "转交成功", icon: "success" }); this.closeActionPopup(); this.doQuery(this.product.serial_number); } catch (e) { this.handleNetworkFailure(e, this.transferForm.isWarehouse ? "入库" : "转交"); } finally { this.actionLoading = false; } },
|
||||
selectTransferUser(userId) { this.transferForm.selectedUserId = userId; this.transferForm.isWarehouse = false; this.transferForm.isFinishDirect = false; },
|
||||
toggleWarehouse() { this.transferForm.isWarehouse = !this.transferForm.isWarehouse; if (this.transferForm.isWarehouse) { this.transferForm.selectedUserId = ""; this.transferForm.isFinishDirect = false; } },
|
||||
// 🏁 直接完结:与前两者互斥。不发往仓库 → 后端落入"无下家"分支,overall_status 原样保留
|
||||
toggleFinishDirect() { this.transferForm.isFinishDirect = !this.transferForm.isFinishDirect; if (this.transferForm.isFinishDirect) { this.transferForm.selectedUserId = ""; this.transferForm.isWarehouse = false; } },
|
||||
async doTransfer() {
|
||||
this.actionLoading = true;
|
||||
const { isWarehouse, isFinishDirect } = this.transferForm;
|
||||
try {
|
||||
const note = this.transferForm.note.trim() || undefined;
|
||||
// 🏁 直接完结:next_tasks 留空 + finish_directly=true,
|
||||
// 后端据此跳过分支解析,只闭环任务、不改产品宏观状态(不会变成"待仓库收货")
|
||||
const payload = isFinishDirect
|
||||
? { next_tasks: [], finish_directly: true, note }
|
||||
: { next_tasks: [{ task_name: isWarehouse ? "🏭 入库 (virtual_warehouse)" : "待确认", assignees: isWarehouse ? ["virtual_warehouse"] : [this.transferForm.selectedUserId] }], note };
|
||||
await post(`/tasks/${this.actionPopup.task.id}/transfer`, payload);
|
||||
uni.showToast({ title: isFinishDirect ? "已直接完结" : (isWarehouse ? "已入库" : "转交成功"), icon: "success" });
|
||||
this.closeActionPopup();
|
||||
this.doQuery(this.product.serial_number);
|
||||
} catch (e) {
|
||||
this.handleNetworkFailure(e, isFinishDirect ? "直接完结" : (isWarehouse ? "入库" : "转交"));
|
||||
} finally { this.actionLoading = false; }
|
||||
},
|
||||
// 派发协助分支
|
||||
async doSpawn() { this.actionLoading = true; try { const opId = this.currentUsername || this.currentUserId; await post(`/tasks/${this.actionPopup.task.id}/spawn?operator_id=${encodeURIComponent(opId)}`, { task_name: "待确认", assignee_id: this.spawnForm.assignee_id, remark: this.spawnForm.remark.trim() || undefined }); uni.showToast({ title: "协助分支已派发", icon: "success" }); this.closeActionPopup(); this.doQuery(this.product.serial_number); } catch (e) { this.handleNetworkFailure(e, "派发协助分支"); } finally { this.actionLoading = false; } },
|
||||
// 💬 留言板
|
||||
|
||||
Reference in New Issue
Block a user