diff --git a/inventory-backend/app/api/v1/purchase.py b/inventory-backend/app/api/v1/purchase.py index 7f2478e..e270639 100644 --- a/inventory-backend/app/api/v1/purchase.py +++ b/inventory-backend/app/api/v1/purchase.py @@ -206,7 +206,7 @@ def approve_purchase_request(purchase_id): action = data.get('action', 'approve') reject_reason = data.get('reject_reason') - if action not in ('approve', 'reject'): + if action not in ('approve', 'reject', 'close'): return jsonify({'code': 400, 'msg': '无效的审批操作'}), 400 if action == 'reject' and not reject_reason: @@ -219,7 +219,7 @@ def approve_purchase_request(purchase_id): reject_reason=reject_reason ) - msg = '审批通过' if action == 'approve' else '已驳回' + msg = '审批通过' if action == 'approve' else ('已驳回' if action == 'reject' else '已完结') # ★ Fail-Closed: 审批响应剥离价格字段 resp = purchase.to_dict() for k in ('unit_price', 'total_price', 'tax_rate'): diff --git a/inventory-backend/app/models/purchase.py b/inventory-backend/app/models/purchase.py index 2f43f0d..a1ff2b4 100644 --- a/inventory-backend/app/models/purchase.py +++ b/inventory-backend/app/models/purchase.py @@ -91,7 +91,7 @@ class PurchaseRequest(db.Model): 'total_price': float(self.total_price) if self.total_price else 0, 'tax_rate': float(self.tax_rate) if self.tax_rate else 0, 'status': self.status, - 'status_text': ['待审批', '已通过', '已驳回', '已完成'][self.status] if self.status in [0, 1, 2, 3] else '未知', + 'status_text': ['待审批', '已通过', '已驳回', '已完成', '已完结'][self.status] if self.status in [0, 1, 2, 3, 4] else '未知', 'requester_id': self.requester_id, 'requester_name': self._get_user_name(self.requester_id), 'requester_email': self._get_user_email(self.requester_id), diff --git a/inventory-backend/app/services/purchase_service.py b/inventory-backend/app/services/purchase_service.py index 46fe694..36dfa2e 100644 --- a/inventory-backend/app/services/purchase_service.py +++ b/inventory-backend/app/services/purchase_service.py @@ -145,12 +145,22 @@ class PurchaseService: if not purchase: raise ValueError("采购申请不存在") - if purchase.status != 0: - raise ValueError("当前状态不允许审批") - beijing_tz = timezone(timedelta(hours=8)) now = datetime.now(beijing_tz) + # ★ 完结:库管将「已通过(1)」的申请单置为「已完结(4)」(仿出库审批) + if action == 'close': + if purchase.status != 1: + raise ValueError("仅「已通过」的采购申请可完结") + purchase.status = 4 + purchase.approver_id = user_id + purchase.approved_at = now + db.session.commit() + return purchase + + if purchase.status != 0: + raise ValueError("当前状态不允许审批") + if action == 'approve': purchase.status = 1 purchase.approver_id = user_id diff --git a/inventory-web/src/views/purchase/index.vue b/inventory-web/src/views/purchase/index.vue index 3ece0d8..e836a83 100644 --- a/inventory-web/src/views/purchase/index.vue +++ b/inventory-web/src/views/purchase/index.vue @@ -10,6 +10,7 @@ 已通过 已驳回 已完成 + 已完结 刷新 @@ -57,6 +58,10 @@ 通过 驳回 + + @@ -703,10 +708,16 @@ const canApprove = computed(() => { || userStore.hasPermission('inbound_purchase:operation') }) +// ★ 完结权限:超级管理员 / 库管(采购操作权限) +const canClose = computed(() => { + return userStore.role === 'SUPER_ADMIN' + || userStore.hasPermission('inbound_purchase:operation') +}) + // --- 工具函数 --- const statusTagType = (status: number) => { const map: Record = { - 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' + 0: 'warning', 1: 'success', 2: 'danger', 3: 'info', 4: 'info' } return map[status] ?? 'info' } @@ -1170,6 +1181,26 @@ const handleApprove = async (row: any) => { } } +// ★ 完结已通过的采购申请(库管) +const handleCloseRequest = async (row: any) => { + try { + await ElMessageBox.confirm( + `确定强制完结采购申请【${row.request_no}】吗?\n\n` + + `此操作将使该单从「已通过」列表中移除,且不可恢复。`, + '⚠️ 强制完结确认', + { confirmButtonText: '确认完结', cancelButtonText: '取消', type: 'warning' } + ) + } catch { return } + + try { + await approvePurchase(row.id, { action: 'close' }) + ElMessage.success(`采购申请 ${row.request_no} 已完结`) + await fetchData() + } catch (err: any) { + ElMessage.error(err?.msg || err?.message || '完结失败') + } +} + const openRejectDialog = (row: any) => { currentRejectRow.value = row rejectReason.value = ''