import React, { useState, useEffect } from 'react'; import { Card, Table, Tag, Button, Space, Modal, Form, Input, message, Tabs, Badge, Descriptions, Divider, List, Upload } from 'antd'; import { CheckOutlined, CloseOutlined, EyeOutlined, EditOutlined, UndoOutlined, FileImageOutlined, DeleteOutlined } from '@ant-design/icons'; const { TextArea } = Input; // 项目支出分类 const PROJECT_EXPENSE_CATEGORIES = [ { value: 'material_purchase', label: '材料采购' }, { value: 'equipment_purchase', label: '设备采购' }, { value: 'pole_crossarm', label: '电杆横担支出' }, { value: 'freight', label: '运费支出' }, { value: 'construction', label: '施工费支出' }, { value: 'other', label: '其他支出' } ]; // 公司支出分类 const COMPANY_EXPENSE_CATEGORIES = [ { value: 'office_operations', label: '通用运营(房租/耗材)' }, { value: 'transportation', label: '交通通勤' }, { value: 'marketing', label: '业扩营销' }, { value: 'power_system', label: '电力系统关系' }, { value: 'employee_welfare', label: '员工福利' }, { value: 'logistics', label: '快递物流' }, { value: 'other', label: '其他支出' } ]; const ApprovalManagement: React.FC = () => { const [loading, setLoading] = useState(false); const [detailModalVisible, setDetailModalVisible] = useState(false); const [editModalVisible, setEditModalVisible] = useState(false); const [historyModalVisible, setHistoryModalVisible] = useState(false); const [selectedRecord, setSelectedRecord] = useState(null); const [fullDetail, setFullDetail] = useState(null); const [approvalType, setApprovalType] = useState<'approve' | 'reject'>('approve'); const [form] = Form.useForm(); const [editForm] = Form.useForm(); // 审批记录 const [approvalHistory, setApprovalHistory] = useState([]); // 待审批数据 const [pendingData, setPendingData] = useState([]); // 已审批数据 const [approvedData, setApprovedData] = useState([]); // 项目列表 const [projects, setProjects] = useState([]); // 加载数据 useEffect(() => { fetchPendingData(); fetchProjects(); fetchApprovalHistory(); }, []); // 获取审批历史记录 const fetchApprovalHistory = async () => { setLoading(true); try { console.log('开始获取审批历史记录'); // 获取所有类型的申请记录 const types = ['advances', 'reimbursements', 'payment-requests', 'verifications']; const historyData = []; for (const type of types) { const response = await fetch(`/api/${type}`); const data = await response.json(); if (data.success && data.data) { data.data.forEach((item: any) => { // 对于预支申请,包含所有状态 // 对于其他类型,保持原有逻辑 if (type === 'advances' || item.status === 'approved' || item.status === 'rejected' || (type === 'verifications' && item.status === 'pending_edit')) { let typeText = ''; let code = ''; let date = ''; let action = ''; switch (type) { case 'advances': typeText = '预支申请'; code = item.advance_code; date = item.advance_date; // 根据预支申请的状态设置操作文本 switch (item.status) { case 'pending': action = '待审批'; break; case 'approved': action = '通过'; break; case 'rejected': action = '退回'; break; case 'executed': action = '已执行'; break; case 'partial_verification': action = '部分核销'; break; case 'completed': action = '已完结'; break; default: action = item.status; } break; case 'reimbursements': typeText = '报销申请'; code = item.reimbursement_code; date = item.reimbursement_date; action = item.status === 'approved' ? '通过' : '退回'; break; case 'payment-requests': typeText = '付款申请'; code = item.request_code; date = item.payment_date; action = item.status === 'approved' ? '通过' : '退回'; break; case 'verifications': typeText = '核销申请'; code = item.verification_code; date = item.verification_date; action = item.status === 'approved' ? '通过' : item.status === 'rejected' ? '退回' : '待编辑'; break; } historyData.push({ id: `${type}-${item.id}`, applyCode: code, applyType: typeText, applicant: item.applicant, amount: item.amount, currency: item.currency, action: action, operator: '系统管理员', // 实际应该从数据库中获取 remark: item.approval_remark || '', timestamp: date }); } }); } } console.log('审批历史记录:', historyData); setApprovalHistory(historyData); } catch (error) { console.error('获取审批历史记录失败:', error); message.error('获取审批历史记录失败'); } finally { setLoading(false); } }; // 获取项目列表 const fetchProjects = async () => { try { const response = await fetch('/api/projects'); if (response.ok) { const data = await response.json(); if (data.success && Array.isArray(data.data)) { setProjects(data.data); } } } catch (error) { console.error('获取项目列表失败:', error); } }; // 获取待审批数据 const fetchPendingData = async () => { setLoading(true); try { console.log('开始获取待审批数据'); // 获取预支申请 const advancesRes = await fetch('/api/advances'); console.log('Advances response status:', advancesRes.status); const advancesData = await advancesRes.json(); console.log('Advances data:', advancesData); // 获取报销申请 const reimbursementsRes = await fetch('/api/reimbursements'); console.log('Reimbursements response status:', reimbursementsRes.status); const reimbursementsData = await reimbursementsRes.json(); console.log('Reimbursements data:', reimbursementsData); // 获取付款申请 const paymentsRes = await fetch('/api/payment-requests'); console.log('Payments response status:', paymentsRes.status); const paymentsData = await paymentsRes.json(); console.log('Payments data:', paymentsData); // 获取核销申请 const verificationsRes = await fetch('/api/verifications'); console.log('Verifications response status:', verificationsRes.status); const verificationsData = await verificationsRes.json(); console.log('Verifications data:', verificationsData); // 获取采购申请 const purchaseRes = await fetch('/api/purchase-requests'); console.log('Purchase requests response status:', purchaseRes.status); const purchaseData = await purchaseRes.json(); console.log('Purchase requests data:', purchaseData); // 合并数据 const allPendingData = []; // 添加预支申请 if (advancesData.success && advancesData.data) { console.log('Advances data length:', advancesData.data.length); advancesData.data.forEach((item: any) => { console.log('Advance item:', item); if (item.status === 'pending') { allPendingData.push({ key: `adv-${item.id}`, id: item.id, type: '预支申请', code: item.advance_code, applicant: item.applicant, amount: item.amount, currency: item.currency, date: item.advance_date, reason: item.reason, status: item.status, rawData: item }); } }); } // 添加报销申请 if (reimbursementsData.success && reimbursementsData.data) { console.log('Reimbursements data length:', reimbursementsData.data.length); reimbursementsData.data.forEach((item: any) => { console.log('Reimbursement item:', item); if (item.status === 'pending') { allPendingData.push({ key: `reimb-${item.id}`, id: item.id, type: '报销申请', code: item.reimbursement_code, applicant: item.applicant, amount: item.amount, currency: item.currency, date: item.reimbursement_date, reason: item.reason, status: item.status, rawData: item }); } }); } // 添加付款申请 if (paymentsData.success && paymentsData.data) { console.log('Payments data length:', paymentsData.data.length); paymentsData.data.forEach((item: any) => { console.log('Payment item:', item); if (item.status === 'pending') { allPendingData.push({ key: `pay-${item.id}`, id: item.id, type: '付款申请', code: item.request_code, applicant: item.applicant, amount: item.amount, currency: item.currency, date: item.payment_date, reason: item.reason, status: item.status, rawData: item }); } }); } // 添加核销申请 if (verificationsData.success && verificationsData.data) { console.log('Verifications data length:', verificationsData.data.length); verificationsData.data.forEach((item: any) => { console.log('Verification item:', item); if (item.status === 'pending') { allPendingData.push({ key: `ver-${item.id}`, id: item.id, type: '核销申请', code: item.verification_code, applicant: item.applicant, amount: item.amount, currency: item.currency, date: item.verification_date, reason: item.reason, status: item.status, rawData: item }); } }); } // 添加采购申请 if (purchaseData.success && purchaseData.data) { console.log('Purchase requests data length:', purchaseData.data.length); purchaseData.data.forEach((item: any) => { console.log('Purchase request item:', item); if (item.status === 'pending') { allPendingData.push({ key: `pur-${item.id}`, id: item.id, type: '采购申请', code: item.request_code, applicant: item.applicant, amount: item.total_amount, currency: item.currency, date: item.request_date, reason: item.brief_description || item.remark || '采购申请', status: item.status, rawData: item }); } }); } console.log('Final pending data:', allPendingData); setPendingData(allPendingData); } catch (error) { console.error('获取待审批数据失败:', error); message.error('获取待审批数据失败'); } finally { setLoading(false); } }; // 格式化金额 const formatAmount = (amount: number, currency: string = 'CNY') => { const symbols: Record = { CNY: '¥', USD: '$', LAK: '₭', THB: '฿' }; return (symbols[currency] || '¥') + (amount || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; // 获取类型标签 const getTypeTag = (type: string) => { const colors: Record = { '预支申请': 'blue', '报销申请': 'green', '付款申请': 'orange', '核销申请': 'purple', '采购申请': 'cyan' }; return {type}; }; // 获取状态标签 const getStatusTag = (status: string) => { const statusMap: Record = { pending: { color: 'processing', text: '待审批' }, approved: { color: 'success', text: '已通过' }, rejected: { color: 'error', text: '已退回' }, withdrawn: { color: 'default', text: '已撤回' } }; const config = statusMap[status] || { color: 'default', text: status }; return {config.text}; }; // 根据项目ID获取项目名称 const getProjectName = (projectId: any) => { if (!projectId) return '-'; const project = projects.find(p => p.id === projectId); return project ? project.name : `项目ID: ${projectId}`; }; // 查看详情 const handleViewDetail = async (record: any) => { setSelectedRecord(record); setApprovalType('approve'); form.resetFields(); // 获取完整详情 if (record.type === '采购申请') { try { const response = await fetch(`/api/purchase-requests/${record.id}`); const data = await response.json(); if (data.success) { setFullDetail(data.data); } } catch (error) { console.error('获取采购申请详情失败:', error); } } else { // 其他类型使用 rawData setFullDetail(record.rawData); } setDetailModalVisible(true); }; // 处理审批通过 const handleApprove = async () => { try { const values = await form.validateFields(); // 构建API请求URL const isAdvance = selectedRecord.key.startsWith('adv-'); const isReimbursement = selectedRecord.key.startsWith('reimb-'); const isPayment = selectedRecord.key.startsWith('pay-'); const isVerification = selectedRecord.key.startsWith('ver-'); const isPurchase = selectedRecord.key.startsWith('pur-'); const id = selectedRecord.id; let url = ''; if (isAdvance) url = `/api/advances/${id}/approve`; else if (isReimbursement) url = `/api/reimbursements/${id}/approve`; else if (isPayment) url = `/api/payment-requests/${id}/approve`; else if (isVerification) url = `/api/verifications/${id}/approve`; else if (isPurchase) url = `/api/purchase-requests/${id}/approve`; // 发送API请求 const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values) }); const result = await res.json(); if (result.success) { // 从待审批列表中移除该申请 setPendingData(pendingData.filter(item => item.key !== selectedRecord.key)); message.success(`审批通过:${selectedRecord.code}`); setDetailModalVisible(false); } else { message.error(result.message || '操作失败'); } } catch (error) { console.error('审批操作失败:', error); message.error('操作失败'); } }; // 处理审批退回 const handleReject = async () => { try { const values = await form.validateFields(); // 构建API请求URL const isAdvance = selectedRecord.key.startsWith('adv-'); const isReimbursement = selectedRecord.key.startsWith('reimb-'); const isPayment = selectedRecord.key.startsWith('pay-'); const isVerification = selectedRecord.key.startsWith('ver-'); const isPurchase = selectedRecord.key.startsWith('pur-'); const id = selectedRecord.id; let url = ''; if (isAdvance) url = `/api/advances/${id}/reject`; else if (isReimbursement) url = `/api/reimbursements/${id}/reject`; else if (isPayment) url = `/api/payment-requests/${id}/reject`; else if (isVerification) url = `/api/verifications/${id}/reject`; else if (isPurchase) url = `/api/purchase-requests/${id}/reject`; // 发送API请求 const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values) }); const result = await res.json(); if (result.success) { // 从待审批列表中移除该申请 setPendingData(pendingData.filter(item => item.key !== selectedRecord.key)); message.success(`已退回:${selectedRecord.code}`); setDetailModalVisible(false); } else { message.error(result.message || '操作失败'); } } catch (error) { console.error('审批操作失败:', error); message.error('操作失败'); } }; // 处理撤回申请 const handleWithdraw = (record: any) => { Modal.confirm({ title: '撤回申请', content: `确认撤回申请 ${record.code} 吗?`, okText: '确认撤回', cancelText: '取消', onOk: () => { setPendingData(pendingData.filter(item => item.key !== record.key)); message.success('申请已撤回'); } }); }; // 处理编辑申请 const handleEdit = (record: any) => { setSelectedRecord(record); editForm.setFieldsValue({ amount: record.amount, reason: record.reason }); setEditModalVisible(true); }; // 处理编辑提交 const handleEditSubmit = () => { editForm.validateFields().then(values => { message.success('修改成功,已重新提交审批'); setEditModalVisible(false); }); }; // 获取申请类型对应的API端点 const getApiEndpoint = (key: string) => { if (key.startsWith('adv-')) return 'advances'; if (key.startsWith('reimb-')) return 'reimbursements'; if (key.startsWith('pay-')) return 'payment-requests'; if (key.startsWith('ver-')) return 'verifications'; if (key.startsWith('pur-')) return 'purchase-requests'; return ''; }; // 渲染附件列表 const renderAttachments = (attachments: any) => { // 处理字符串类型的 attachments(JSON字符串) let attachmentList = attachments; if (typeof attachments === 'string') { try { attachmentList = JSON.parse(attachments); } catch (e) { return null; } } // 确保是数组 if (!Array.isArray(attachmentList) || attachmentList.length === 0) return null; return (
{attachmentList.map((url: string, index: number) => (
{url && url.match(/\.(jpg|jpeg|png|gif|webp)$/i) ? ( {`附件${index window.open(url, '_blank')} /> ) : (
)}
))}
); }; // 获取支出分类的中文名称 const getCategoryName = (category: string) => { if (!category) return '-'; // 特殊分类映射 const specialCategories: Record = { // Project expense categories accommodation: '住宿', food: '餐饮', fuel: '加油', materials: '零散材料', customer_relations: '客户关系', subcontract_relations: '分包关系', edl_relations: 'EDL关系', extra_construction: '额外施工', // Company expense categories general_operations: '通用运营(房租/耗材)', transportation: '交通通勤', business_expansion: '业扩营销', power_system_relations: '电力系统关系', employee_benefits: '员工福利', express_logistics: '快递物流', other: '其他' }; // 先检查特殊分类 if (specialCategories[category]) return specialCategories[category]; // 再从项目支出分类中查找 const projectCategory = PROJECT_EXPENSE_CATEGORIES.find(c => c.value === category); if (projectCategory) return projectCategory.label; // 再从公司支出分类中查找 const companyCategory = COMPANY_EXPENSE_CATEGORIES.find(c => c.value === category); if (companyCategory) return companyCategory.label; // 如果都找不到,返回原始值 return category; }; // 渲染明细清单 const renderDetailItems = (detailItems: any) => { // 处理字符串类型的 detailItems(JSON字符串) let itemsList = detailItems; if (typeof detailItems === 'string') { try { itemsList = JSON.parse(detailItems); } catch (e) { return null; } } // 确保是数组 if (!Array.isArray(itemsList) || itemsList.length === 0) return null; return ( (
明细 {index + 1}: {item.description || getCategoryName(item.category) || '-'} {formatAmount(item.amount, item.currency)}
{item.category && (
支出分类:{getCategoryName(item.category)}
)} {item.attachments && (
明细附件: {renderAttachments(item.attachments)}
)}
)} /> ); }; // 待审批列 const pendingColumns = [ { title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => handleViewDetail(r)}>{v} }, { title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => getTypeTag(v) }, { title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 80 }, { title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: (v: number, r: any) => formatAmount(v, r.currency) }, { title: '申请日期', dataIndex: 'date', key: 'date', width: 100 }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) }, { title: '编号', dataIndex: 'code', key: 'code', width: 140 }, { title: '操作', key: 'action', width: 100, render: (_: any, record: any) => ( ) } ]; // 审批记录列 const historyColumns = [ { title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 140 }, { title: '操作', dataIndex: 'action', key: 'action', width: 100 }, { title: '申请编号', dataIndex: 'applyCode', key: 'applyCode', width: 140 }, { title: '类型', dataIndex: 'applyType', key: 'applyType', width: 100, render: (v: string) => getTypeTag(v) }, { title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 80 }, { title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: (v: number, r: any) => formatAmount(v, r.currency) }, { title: '操作人', dataIndex: 'operator', key: 'operator', width: 100 }, { title: '备注/原因', dataIndex: 'remark', key: 'remark', ellipsis: true } ]; const tabItems = [ { key: 'pending', label: 待审批 , children: }, { key: 'history', label: 审批记录 , children:
}, ]; return (

审批管理

审批预支、报销、付款等申请

{/* 详情模态框 */} setDetailModalVisible(false)} width={900} footer={ selectedRecord?.status === 'pending' ? (
) : ( ) } > {fullDetail && ( <> {/* 基本信息 */} {getTypeTag(selectedRecord.type)} {selectedRecord.code} {selectedRecord.applicant} {selectedRecord.date} {formatAmount(selectedRecord.amount, selectedRecord.currency)} {selectedRecord.currency !== 'CNY' && fullDetail.amount_cny > 0 && ( ≈ ¥{fullDetail.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})} )} {getStatusTag(selectedRecord.status)} {selectedRecord.reason} {/* 付款申请特有字段 */} {selectedRecord.type === '付款申请' && ( <> {fullDetail.payee_type === 'subcontractor' ? '分包商' : fullDetail.payee_type === 'supplier' ? '供应商' : fullDetail.payee_type === 'customer' ? '客户' : '其他'} {fullDetail.payee || '-'} {fullDetail.bank_name || '-'} {fullDetail.bank_account || '-'} {fullDetail.expense_type === 'company' ? '公司支出' : '项目支出'} {fullDetail.expense_type === 'project' && fullDetail.project_id && ( {getProjectName(fullDetail.project_id)} )} {fullDetail.expense_type === 'project' ? (PROJECT_EXPENSE_CATEGORIES.find(c => c.value === fullDetail.expense_category)?.label || fullDetail.expense_category) : (COMPANY_EXPENSE_CATEGORIES.find(c => c.value === fullDetail.expense_category)?.label || fullDetail.expense_category) } )} {/* 报销申请特有字段 */} {selectedRecord.type === '报销申请' && fullDetail.expense_type && ( <> {fullDetail.expense_type === 'company' ? '公司支出' : '项目支出'} {fullDetail.project_id && ( {getProjectName(fullDetail.project_id)} )} )} {/* 核销申请特有字段 */} {selectedRecord.type === '核销申请' && fullDetail.advance_code && ( <> {fullDetail.advance_code} {formatAmount(fullDetail.advance_amount, fullDetail.currency)} {fullDetail.settlement ? '是' : '否'} {formatAmount(fullDetail.total_reimbursed || 0, fullDetail.currency)} {formatAmount((fullDetail.advance_amount || 0) - (fullDetail.total_reimbursed || 0), fullDetail.currency)} {fullDetail.settlement && fullDetail.settlement_amount && ( {fullDetail.settlement_amount > 0 ? `退款 ${formatAmount(fullDetail.settlement_amount, fullDetail.currency)}` : `补款 ${formatAmount(Math.abs(fullDetail.settlement_amount), fullDetail.currency)}`} )} )} {/* 采购申请特有字段 */} {selectedRecord.type === '采购申请' && ( <> {fullDetail.purchase_type === 'project' ? '项目采购' : '库存采购'} {fullDetail.purchase_type === 'project' && fullDetail.project_id && ( {getProjectName(fullDetail.project_id)} )} {fullDetail.supplier_name || '-'} {fullDetail.expense_category === 'material' ? '材料' : fullDetail.expense_category === 'equipment' ? '设备' : fullDetail.expense_category === 'pole' ? '电杆' : '其他'} {fullDetail.currency} {fullDetail.request_date} {fullDetail.brief_description || '-'} {fullDetail.remark && ( {fullDetail.remark} )} )} {/* 采购申请商品明细 */} {selectedRecord.type === '采购申请' && fullDetail.items && fullDetail.items.length > 0 && ( <> 商品明细 (
{index + 1}. {item.product_name} {fullDetail.currency} {item.total_price?.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
规格: {item.specification || '-'} | 单位: {item.unit || '-'} | 数量: {item.quantity} | 单价: {fullDetail.currency} {item.unit_price?.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
)} /> )} {/* 明细清单 */} {fullDetail.detail_items && fullDetail.detail_items.length > 0 && ( <> 明细清单 {renderDetailItems(fullDetail.detail_items)} )} {/* 凭证附件或退款凭证 */} {fullDetail.attachments && fullDetail.attachments.length > 0 && ( <> {fullDetail.settlement && fullDetail.settlement_amount > 0 ? '退款凭证' : '凭证附件'} {renderAttachments(fullDetail.attachments)} )} {/* 审批备注表单 */} {selectedRecord.status === 'pending' && ( <> 审批意见