import React, { useState, useEffect } from 'react'; import { Table, Card, Tag, Button, Space, Input, DatePicker, Select, message, Descriptions, Divider, Tabs, Modal, Result } from 'antd'; import { EyeOutlined, CalendarOutlined, UserOutlined, FilterOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import { useAuthStore } from '../../store/authStore'; const { Option } = Select; const { RangePicker } = DatePicker; const AdvanceVerificationStatusPage: React.FC = () => { const { user } = useAuthStore(); const [loading, setLoading] = useState(false); const [detailModalVisible, setDetailModalVisible] = useState(false); const [selectedAdvance, setSelectedAdvance] = useState(null); const [unsettledAdvances, setUnsettledAdvances] = useState([]); const [settledAdvances, setSettledAdvances] = useState([]); const [searchName, setSearchName] = useState(''); const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>(null); const [sortField, setSortField] = useState(''); const [sortOrder, setSortOrder] = useState(''); // 检查权限 const hasPermission = user?.role === 'admin' || user?.department === '财务部'; if (!hasPermission) { return (
); } // 获取未核销和已核销的预支单 const fetchAdvances = async () => { setLoading(true); try { // 获取所有预支单 const res = await fetch('/api/advances'); const data = await res.json(); if (data.success) { // 处理数据,确保每个预支单都有total_reimbursed字段 const processedAdvances = data.data.map((advance: any) => ({ ...advance, total_reimbursed: advance.total_reimbursed || 0, isSettled: advance.status === 'settled' || (advance.total_reimbursed || 0) >= advance.amount })); // 分离未核销和已核销的预支单 const unsettled = processedAdvances.filter((advance: any) => !advance.isSettled && (advance.status === 'approved' || advance.status === 'executed' || advance.status === 'partial_verification')); const settled = processedAdvances.filter((advance: any) => advance.isSettled || advance.status === 'settled' || advance.status === 'completed'); setUnsettledAdvances(unsettled); setSettledAdvances(settled); } } catch (error) { message.error('获取预支单失败'); } finally { setLoading(false); } }; // 获取预支单详情,包括关联的核销单 const fetchAdvanceDetail = async (advanceId: number) => { setLoading(true); try { // 获取预支单详情 const advanceRes = await fetch(`/api/advances/${advanceId}`); const advanceData = await advanceRes.json(); if (advanceData.success) { // 获取关联的核销单 const verificationRes = await fetch(`/api/verifications?advance_id=${advanceId}`); const verificationData = await verificationRes.json(); if (verificationData.success) { // 只保留已执行或已批准的核销单,并且关联的预支单编号与当前预支单一致 const validVerifications = (verificationData.data || []).filter((verification: any) => (verification.status === 'approved' || verification.status === 'executed') && verification.advance_code === advanceData.data.advance_code ); setSelectedAdvance({ ...advanceData.data, verifications: validVerifications }); setDetailModalVisible(true); } } } catch (error) { message.error('获取预支单详情失败'); } finally { setLoading(false); } }; useEffect(() => { fetchAdvances(); }, []); 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 getStatusTag = (status: string) => { const statusMap: Record = { pending: { color: 'processing', text: '待审批' }, approved: { color: 'success', text: '已批准' }, rejected: { color: 'error', text: '已退回' }, withdrawn: { color: 'default', text: '已撤回' }, settled: { color: 'blue', text: '已核销' }, pending_edit: { color: 'warning', text: '待编辑' }, partial_verification: { color: 'orange', text: '部分核销' }, completed: { color: 'green', text: '已完成' }, }; const config = statusMap[status] || { color: 'default', text: status }; return {config.text}; }; const handleSearch = () => { // 这里可以添加搜索逻辑 fetchAdvances(); }; const handleSort = (field: string, order: string) => { setSortField(field); setSortOrder(order); // 这里可以添加排序逻辑 fetchAdvances(); }; const columns = [ { title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => ( fetchAdvanceDetail(r.id)}>{v} ) }, { title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 100, sorter: (a: any, b: any) => a.applicant.localeCompare(b.applicant), onHeaderCell: (column: any) => ({ onClick: () => handleSort('applicant', sortOrder === 'ascend' ? 'descend' : 'ascend') }) }, { title: '预支金额', dataIndex: 'amount', key: 'amount', width: 140, render: (v: number, r: any) => formatAmount(v, r.currency), sorter: (a: any, b: any) => a.amount - b.amount, onHeaderCell: (column: any) => ({ onClick: () => handleSort('amount', sortOrder === 'ascend' ? 'descend' : 'ascend') }) }, { title: '已核销金额', dataIndex: 'total_reimbursed', key: 'total_reimbursed', width: 140, render: (v: number, r: any) => formatAmount(v || 0, r.currency), sorter: (a: any, b: any) => (a.total_reimbursed || 0) - (b.total_reimbursed || 0), onHeaderCell: (column: any) => ({ onClick: () => handleSort('total_reimbursed', sortOrder === 'ascend' ? 'descend' : 'ascend') }) }, { title: '剩余金额', dataIndex: 'remaining', key: 'remaining', width: 140, render: (_, r: any) => formatAmount((r.amount || 0) - (r.total_reimbursed || 0), r.currency), sorter: (a: any, b: any) => ((a.amount || 0) - (a.total_reimbursed || 0)) - ((b.amount || 0) - (b.total_reimbursed || 0)), onHeaderCell: (column: any) => ({ onClick: () => handleSort('remaining', sortOrder === 'ascend' ? 'descend' : 'ascend') }) }, { title: '预支日期', dataIndex: 'advance_date', key: 'advance_date', width: 120, sorter: (a: any, b: any) => new Date(a.advance_date).getTime() - new Date(b.advance_date).getTime(), onHeaderCell: (column: any) => ({ onClick: () => handleSort('advance_date', sortOrder === 'ascend' ? 'descend' : 'ascend') }) }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => getStatusTag(status) }, { title: '编号', dataIndex: 'advance_code', key: 'advance_code', width: 120 }, { title: '操作', key: 'action', width: 80, render: (_: any, record: any) => ( ) } ]; return (

预支核销状态管理

管理预支单的核销状态和进度

{/* 搜索和筛选区域 */}
} value={searchName} onChange={(e) => setSearchName(e.target.value)} style={{ width: '100%' }} />
setDateRange(dates)} style={{ width: '100%' }} />
{/* 预支单列表 */} ) }, { key: 'settled', label: '已完结', children: ( ) } ]} /> {/* 详情模态框 */} setDetailModalVisible(false)} footer={[ ]} width={900} > {selectedAdvance && ( <> {/* 预支单基本信息 */} {selectedAdvance.advance_code} {getStatusTag(selectedAdvance.status)} {selectedAdvance.applicant} {selectedAdvance.advance_date} {formatAmount(selectedAdvance.amount, selectedAdvance.currency)} {selectedAdvance.currency !== 'CNY' && selectedAdvance.amount_cny && ( ≈ ¥{selectedAdvance.amount_cny.toLocaleString('zh-CN', { minimumFractionDigits: 2 })} )} {formatAmount(selectedAdvance.total_reimbursed || 0, selectedAdvance.currency)} {formatAmount((selectedAdvance.amount || 0) - (selectedAdvance.total_reimbursed || 0), selectedAdvance.currency)} {selectedAdvance.currency} {selectedAdvance.reason} {/* 关联的核销单 */} {selectedAdvance.verifications && selectedAdvance.verifications.length > 0 && ( <> 关联核销单
formatAmount(v, r.currency) }, { title: '核销日期', dataIndex: 'verification_date', key: 'verification_date' }, { title: '是否结算', dataIndex: 'settlement', key: 'settlement', render: (v: boolean) => ( {v ? '是' : '否'} ) }, { title: '状态', dataIndex: 'status', key: 'status', render: (status: string) => getStatusTag(status) } ]} /> )} {/* 附件 */} {selectedAdvance.attachments && selectedAdvance.attachments.length > 0 && ( <> 凭证附件
{selectedAdvance.attachments.map((url: string, index: number) => (
{url && url.match(/\.(jpg|jpeg|png|gif|webp)$/i) ? ( {`附件${index window.open(url, '_blank')} /> ) : (
附件 {index + 1}
)}
))}
)} )} ); }; export default AdvanceVerificationStatusPage;