权限: receive/reject校验assignee_id匹配 + 前端myActiveTask只显示本人任务 + 403处理
This commit is contained in:
@ -238,12 +238,16 @@ async def receive_task(
|
|||||||
"""
|
"""
|
||||||
task = await _get_task_or_404(db, task_id)
|
task = await _get_task_or_404(db, task_id)
|
||||||
|
|
||||||
|
# 权限校验:只有负责人本人可接收
|
||||||
|
if operator_id and task.assignee_id and operator_id != task.assignee_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=f"您无权操作此任务,当前任务负责人为 {task.assignee_id}",
|
||||||
|
)
|
||||||
|
|
||||||
# 校验:只有 PENDING 状态可接收
|
# 校验:只有 PENDING 状态可接收
|
||||||
if task.status != TASK_STATUS_PENDING:
|
if task.status != TASK_STATUS_PENDING:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail=f"只有待接收(PENDING)状态的任务可接收,当前状态: {task.status}",
|
|
||||||
)
|
|
||||||
|
|
||||||
now = get_beijing_time()
|
now = get_beijing_time()
|
||||||
task.status = TASK_STATUS_WIP
|
task.status = TASK_STATUS_WIP
|
||||||
@ -284,6 +288,13 @@ async def reject_task(
|
|||||||
"""
|
"""
|
||||||
task = await _get_task_or_404(db, task_id)
|
task = await _get_task_or_404(db, task_id)
|
||||||
|
|
||||||
|
# 权限校验:只有负责人本人可驳回
|
||||||
|
if operator_id and task.assignee_id and operator_id != task.assignee_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=f"您无权操作此任务,当前任务负责人为 {task.assignee_id}",
|
||||||
|
)
|
||||||
|
|
||||||
# 校验:不能重复驳回已完成/已驳回的任务
|
# 校验:不能重复驳回已完成/已驳回的任务
|
||||||
if task.status in (TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED):
|
if task.status in (TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
@ -90,21 +90,21 @@
|
|||||||
<!-- ================================================================ -->
|
<!-- ================================================================ -->
|
||||||
<!-- 底部操作栏(钉死,不随滚动) -->
|
<!-- 底部操作栏(钉死,不随滚动) -->
|
||||||
<!-- ================================================================ -->
|
<!-- ================================================================ -->
|
||||||
<view v-if="activeTask" class="footer-actions">
|
<view v-if="myActiveTask" class="footer-actions">
|
||||||
<button v-if="activeTask.status === 'WIP'" class="footer-btn footer-transfer"
|
<button v-if="myActiveTask.status === 'WIP'" class="footer-btn footer-transfer"
|
||||||
@tap="handleTaskAction({ task: activeTask, type: 'transfer' })">
|
@tap="handleTaskAction({ task: myActiveTask, type: 'transfer' })">
|
||||||
🔄 完工转交
|
🔄 完工转交
|
||||||
</button>
|
</button>
|
||||||
<button v-if="activeTask.status === 'WIP'" class="footer-btn footer-record"
|
<button v-if="myActiveTask.status === 'WIP'" class="footer-btn footer-record"
|
||||||
@tap="handleTaskAction({ task: activeTask, type: 'record' })">
|
@tap="handleTaskAction({ task: myActiveTask, type: 'record' })">
|
||||||
📝 记录/拍照
|
📝 记录/拍照
|
||||||
</button>
|
</button>
|
||||||
<button v-if="activeTask.status === 'PENDING'" class="footer-btn footer-receive"
|
<button v-if="myActiveTask.status === 'PENDING'" class="footer-btn footer-receive"
|
||||||
@tap="handleTaskAction({ task: activeTask, type: 'receive' })">
|
@tap="handleTaskAction({ task: myActiveTask, type: 'receive' })">
|
||||||
✅ 接收任务
|
✅ 接收任务
|
||||||
</button>
|
</button>
|
||||||
<button v-if="activeTask.status === 'PENDING'" class="footer-btn footer-reject"
|
<button v-if="myActiveTask.status === 'PENDING'" class="footer-btn footer-reject"
|
||||||
@tap="handleTaskAction({ task: activeTask, type: 'reject' })">
|
@tap="handleTaskAction({ task: myActiveTask, type: 'reject' })">
|
||||||
❌ 驳回任务
|
❌ 驳回任务
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
@ -379,7 +379,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
activeTask() {
|
activeTask() {
|
||||||
// 查找树中第一个 WIP 或 PENDING 任务作为底部 Footer 的操作目标
|
// 查找树中第一个 WIP 或 PENDING 任务(不限归属)
|
||||||
const findActive = (tasks) => {
|
const findActive = (tasks) => {
|
||||||
if (!tasks) return null;
|
if (!tasks) return null;
|
||||||
for (const t of tasks) {
|
for (const t of tasks) {
|
||||||
@ -393,6 +393,24 @@ export default {
|
|||||||
};
|
};
|
||||||
return this.product ? findActive(this.product.task_tree) : null;
|
return this.product ? findActive(this.product.task_tree) : null;
|
||||||
},
|
},
|
||||||
|
myActiveTask() {
|
||||||
|
// 只返回当前登录人自己的 WIP/PENDING 任务
|
||||||
|
const findMine = (tasks) => {
|
||||||
|
if (!tasks) return null;
|
||||||
|
for (const t of tasks) {
|
||||||
|
const isMine = t.status === 'WIP' || t.status === 'PENDING';
|
||||||
|
const matchId = t.assignee_id == this.currentUserId;
|
||||||
|
const matchName = t.assignee_id == this.currentUsername;
|
||||||
|
if (isMine && (matchId || matchName)) return t;
|
||||||
|
if (t.child_tasks && t.child_tasks.length) {
|
||||||
|
const found = findMine(t.child_tasks);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
return this.product ? findMine(this.product.task_tree) : null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
this.loadUsers();
|
this.loadUsers();
|
||||||
@ -516,7 +534,8 @@ export default {
|
|||||||
// 场景B: 立即接收
|
// 场景B: 立即接收
|
||||||
if (this.firstForm.autoReceive) {
|
if (this.firstForm.autoReceive) {
|
||||||
try {
|
try {
|
||||||
await post(`/tasks/${task.id}/receive`);
|
const opId = this.currentUsername || this.currentUserId;
|
||||||
|
await post(`/tasks/${task.id}/receive?operator_id=${encodeURIComponent(opId)}`);
|
||||||
} catch { /* receive 失败不影响流程 */ }
|
} catch { /* receive 失败不影响流程 */ }
|
||||||
}
|
}
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@ -696,7 +715,8 @@ export default {
|
|||||||
this.actionLoading = true;
|
this.actionLoading = true;
|
||||||
try {
|
try {
|
||||||
const remark = this.receiveRemark.trim() || undefined;
|
const remark = this.receiveRemark.trim() || undefined;
|
||||||
await post(`/tasks/${this.actionPopup.task.id}/receive`, { remark });
|
const opId = this.currentUsername || this.currentUserId;
|
||||||
|
await post(`/tasks/${this.actionPopup.task.id}/receive?operator_id=${encodeURIComponent(opId)}`, { remark });
|
||||||
uni.showToast({ title: "已接收", icon: "success" });
|
uni.showToast({ title: "已接收", icon: "success" });
|
||||||
this.closeActionPopup();
|
this.closeActionPopup();
|
||||||
this.doQuery(this.product.serial_number);
|
this.doQuery(this.product.serial_number);
|
||||||
@ -705,7 +725,8 @@ export default {
|
|||||||
async doReject() {
|
async doReject() {
|
||||||
this.actionLoading = true;
|
this.actionLoading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/tasks/${this.actionPopup.task.id}/reject`, { reason: this.rejectReason.trim() });
|
const opId = this.currentUsername || this.currentUserId;
|
||||||
|
await post(`/tasks/${this.actionPopup.task.id}/reject?operator_id=${encodeURIComponent(opId)}`, { reason: this.rejectReason.trim() });
|
||||||
uni.showToast({ title: "已驳回,返工任务已创建", icon: "success" });
|
uni.showToast({ title: "已驳回,返工任务已创建", icon: "success" });
|
||||||
this.closeActionPopup();
|
this.closeActionPopup();
|
||||||
this.doQuery(this.product.serial_number);
|
this.doQuery(this.product.serial_number);
|
||||||
|
|||||||
@ -30,6 +30,9 @@ export default function request(options) {
|
|||||||
uni.showToast({ title: "登录已过期,请重新登录", icon: "none" });
|
uni.showToast({ title: "登录已过期,请重新登录", icon: "none" });
|
||||||
setTimeout(() => uni.reLaunch({ url: "/pages/login/login" }), 1000);
|
setTimeout(() => uni.reLaunch({ url: "/pages/login/login" }), 1000);
|
||||||
reject(res);
|
reject(res);
|
||||||
|
} else if (code === 403) {
|
||||||
|
uni.showToast({ title: res.data?.detail || "无权操作", icon: "none", duration: 3000 });
|
||||||
|
reject(res);
|
||||||
} else if (code === 400) {
|
} else if (code === 400) {
|
||||||
uni.showToast({ title: res.data?.detail || "请求参数有误", icon: "none", duration: 2500 });
|
uni.showToast({ title: res.data?.detail || "请求参数有误", icon: "none", duration: 2500 });
|
||||||
reject(res);
|
reject(res);
|
||||||
|
|||||||
Reference in New Issue
Block a user