fix: 审批流程三个bug修复

1. 点通过不再重复创建审批记录(后端无NULL记录时返回400而非自动创建)
2. 非提交人不再显示撤销按钮(列表页增加applicant_id校验)
3. 已审批工单不再显示通过/拒绝按钮(is_approver只计算pending记录)
4. 前端审批失败时弹出错误提示而非静默忽略
This commit is contained in:
logan committed 2026-06-03 17:17:12 +08:00
1 parent 3a6dd99633
commit 74748e4914
2 files changed
+19 -21

No files matched your search

+8 -17
View File
@@ -138,7 +138,8 @@ func ListTickets(c *gin.Context) {
model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND step_type = 'execute'", t.ID).Count(&execCount) model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND step_type = 'execute'", t.ID).Count(&execCount)
var isExec, isAppr int64 var isExec, isAppr int64
model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND step_type = 'execute' AND approver_id = ?", t.ID, userID).Count(&isExec) model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND step_type = 'execute' AND approver_id = ?", t.ID, userID).Count(&isExec)
model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND step_type != 'execute' AND step_type != 'submit' AND approver_id = ?", t.ID, userID).Count(&isAppr) // Only count PENDING approvals (action IS NULL) — already-approved records should not show approve button
model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND step_type != 'execute' AND step_type != 'submit' AND approver_id = ? AND action IS NULL", t.ID, userID).Count(&isAppr)
result = append(result, TicketWithMeta{ result = append(result, TicketWithMeta{
Ticket: t, Ticket: t,
HasAuditStep: auditCount > 0, HasAuditStep: auditCount > 0,
@@ -496,24 +497,14 @@ func ApproveTicket(c *gin.Context) {
now := time.Now() now := time.Now()
// Update approval record — auto-create if missing (legacy tickets) // Find PENDING approval for this user (action IS NULL)
var approval model.TicketApproval var approval model.TicketApproval
if err := model.DB.Where("ticket_id = ? AND approver_id = ? AND action IS NULL", id, userID).First(&approval).Error; err != nil { if err := model.DB.Where("ticket_id = ? AND approver_id = ? AND action IS NULL AND step_type != 'execute'", id, userID).First(&approval).Error; err != nil {
// No approval record — authorized role can create one // Already approved or no pending approval for this user
stepName := "审核" c.JSON(http.StatusBadRequest, gin.H{"detail": "没有待处理的审批记录(你可能已经审批过了)"})
stepType := "audit" return
approverRole := userRole
approval = model.TicketApproval{
TicketID: ticket.ID,
StepNumber: 1,
StepName: &stepName,
StepType: &stepType,
ApproverID: &userID,
ApproverRole: &approverRole,
}
model.DB.Create(&approval)
} }
approval.Action = &input.Action approval.Action = &input.Action
approval.Comment = input.Comment approval.Comment = input.Comment
approval.ActualApproverID = &userID approval.ActualApproverID = &userID
+11 -4
View File
@@ -6,7 +6,7 @@ import {
ArrowUpDown, Ban, ChevronLeft, AlertTriangle, ArrowUpDown, Ban, ChevronLeft, AlertTriangle,
} from 'lucide-react'; } from 'lucide-react';
import { statusMap, statusColorMap, dbTypeColorMap } from '@/lib/constants'; import { statusMap, statusColorMap, dbTypeColorMap } from '@/lib/constants';
import { useActionPermission } from '@/hooks/useAuth'; import { useActionPermission, useAuth } from '@/hooks/useAuth';
import PageHeader from '@/components/PageHeader'; import PageHeader from '@/components/PageHeader';
import EmptyState from '@/components/EmptyState'; import EmptyState from '@/components/EmptyState';
import { ticketApi } from '@/api/ticket'; import { ticketApi } from '@/api/ticket';
@@ -190,6 +190,7 @@ function TableRowSkeleton() {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
export default function TicketsPage() { export default function TicketsPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { user: currentUser } = useAuth();
const canCreate = useActionPermission('ticket.create'); const canCreate = useActionPermission('ticket.create');
const canApprove = useActionPermission('ticket.approve'); const canApprove = useActionPermission('ticket.approve');
const canExecute = useActionPermission('ticket.execute'); const canExecute = useActionPermission('ticket.execute');
@@ -455,7 +456,10 @@ export default function TicketsPage() {
try { try {
await ticketApi.review(ticket.id, { action: 'approve', comment: '通过' }); await ticketApi.review(ticket.id, { action: 'approve', comment: '通过' });
fetchTickets(); fetchStats(); fetchTickets(); fetchStats();
} catch { /* ignore */ } } catch (e: any) {
const msg = e?.response?.data?.detail || e?.message || '审批失败';
alert(msg);
}
}} }}
className="dg-btn dg-btn-sm dg-btn-ghost text-green-400 hover:text-green-300" className="dg-btn dg-btn-sm dg-btn-ghost text-green-400 hover:text-green-300"
> >
@@ -470,7 +474,10 @@ export default function TicketsPage() {
try { try {
await ticketApi.review(ticket.id, { action: 'reject', comment: '拒绝' }); await ticketApi.review(ticket.id, { action: 'reject', comment: '拒绝' });
fetchTickets(); fetchStats(); fetchTickets(); fetchStats();
} catch { /* ignore */ } } catch (e: any) {
const msg = e?.response?.data?.detail || e?.message || '操作失败';
alert(msg);
}
}} }}
className="dg-btn dg-btn-sm dg-btn-ghost text-red-400 hover:text-red-300" className="dg-btn dg-btn-sm dg-btn-ghost text-red-400 hover:text-red-300"
> >
@@ -494,7 +501,7 @@ export default function TicketsPage() {
</button> </button>
)} )}
{(ticket.status === 'pending' || ticket.status === 'reviewing' || ticket.status === 'approved') && ( {currentUser?.id === ticket.applicant_id && (ticket.status === 'pending' || ticket.status === 'reviewing' || ticket.status === 'approved') && (
<button <button
onClick={async () => { onClick={async () => {
try { try {