diff --git a/apps/admin/src/pages/Deposits/index.tsx b/apps/admin/src/pages/Deposits/index.tsx index 4ae3bcf..564c688 100644 --- a/apps/admin/src/pages/Deposits/index.tsx +++ b/apps/admin/src/pages/Deposits/index.tsx @@ -58,6 +58,8 @@ const DepositsPage: React.FC = () => { const [installmentModal, setInstallmentModal] = useState(null); const [pendingRefunds, setPendingRefunds] = useState([]); const [pendingLoading, setPendingLoading] = useState(false); + const [rejectModal, setRejectModal] = useState(null); + const [rejectReason, setRejectReason] = useState(''); const [createForm] = Form.useForm(); const [refundForm] = Form.useForm(); const [installmentForm] = Form.useForm(); @@ -160,6 +162,20 @@ const DepositsPage: React.FC = () => { } }; + + const handleRejectRefund = async () => { + if (!rejectModal) return; + try { + await api.put(`/deposits/${rejectModal.id}/reject-refund`, { reason: rejectReason || '未说明原因' }); + message.success('已驳回退款申请'); + setRejectModal(null); + setRejectReason(''); + fetchPendingRefunds(); + fetchData(); + } catch (e: any) { + message.error(e?.message || '驳回失败'); + } + }; const handleAddInstallment = async () => { if (installmentModal == null) return; const values = await installmentForm.validateFields(); @@ -306,16 +322,29 @@ const DepositsPage: React.FC = () => { }, { title: '操作', - render: (_: any, record: any) => ( - } - onClick={() => handleApproveRefund(record)} - > - 审批通过 - + render: (_: unknown, record: PendingRefund) => ( + + } + onClick={() => handleApproveRefund(record)} + > + 审批通过 + + { + setRejectModal(record); + setRejectReason(''); + }} + > + 驳回 + + ), }, ]; @@ -586,6 +615,23 @@ const DepositsPage: React.FC = () => { + + {/* Reject Refund Modal */} + { setRejectModal(null); setRejectReason(''); }} + okText="确认驳回" + okButtonProps={{ danger: true }} + > + setRejectReason(e.target.value)} + rows={3} + /> + ); }; diff --git a/apps/server/src/deposits/deposits.controller.ts b/apps/server/src/deposits/deposits.controller.ts index 636ff4d..71998a2 100644 --- a/apps/server/src/deposits/deposits.controller.ts +++ b/apps/server/src/deposits/deposits.controller.ts @@ -95,8 +95,22 @@ export class DepositsController { async addInstallment( @Param('id') id: string, @Body() body: { amount: number; dueDate: string }, + @Request() req: { user?: { id: number; username: string }; headers?: Record }, ) { - return this.service.addInstallment(+id, body.amount, body.dueDate); + const { ipAddress, userAgent } = extractRequestInfo(req); + const result = await this.service.addInstallment(+id, body.amount, body.dueDate); + await this.logService.log({ + userId: req.user?.id, + username: req.user?.username, + module: '押金管理', + action: '新增分期', + targetId: result.id, + targetType: 'deposit-installment', + detail: `押金${id} 新增分期 ¥${result.amount}`, + ipAddress, + userAgent, + }); + return result; } @Put('installments/:installmentId') @@ -104,14 +118,44 @@ export class DepositsController { async updateInstallment( @Param('installmentId') installmentId: string, @Body() body: { paidDate?: string; status?: string }, + @Request() req: { user?: { id: number; username: string }; headers?: Record }, ) { - return this.service.updateInstallment(+installmentId, body); + const { ipAddress, userAgent } = extractRequestInfo(req); + const result = await this.service.updateInstallment(+installmentId, body); + await this.logService.log({ + userId: req.user?.id, + username: req.user?.username, + module: '押金管理', + action: '更新分期', + targetId: +installmentId, + targetType: 'deposit-installment', + detail: `更新分期${installmentId}, 状态:${result.status ?? '-'}, 实付日:${result.paidDate ?? '-'}`, + ipAddress, + userAgent, + }); + return result; } @Delete('installments/:installmentId') @RequirePermission('deposit:delete') - async deleteInstallment(@Param('installmentId') installmentId: string) { - return this.service.deleteInstallment(+installmentId); + async deleteInstallment( + @Param('installmentId') installmentId: string, + @Request() req: { user?: { id: number; username: string }; headers?: Record }, + ) { + const { ipAddress, userAgent } = extractRequestInfo(req); + const result = await this.service.deleteInstallment(+installmentId); + await this.logService.log({ + userId: req.user?.id, + username: req.user?.username, + module: '押金管理', + action: '删除分期', + targetId: +installmentId, + targetType: 'deposit-installment', + detail: `删除分期${installmentId}`, + ipAddress, + userAgent, + }); + return result; } @Put(':id/refund') @@ -183,6 +227,29 @@ export class DepositsController { return result; } + @Put(':id/reject-refund') + @RequirePermission('deposit:approve') + async rejectRefund( + @Param('id') id: string, + @Body() body: { reason: string }, + @Request() req: any, + ) { + const { ipAddress, userAgent } = extractRequestInfo(req); + const result = await this.service.rejectRefund(+id, body.reason, req.user?.id); + await this.logService.log({ + userId: req.user?.id, + username: req.user?.username, + module: '押金管理', + action: '驳回退款', + targetId: +id, + targetType: 'deposit', + detail: `驳回原因:${body.reason}`, + ipAddress, + userAgent, + }); + return result; + } + @Delete(':id') @RequirePermission('deposit:delete') async remove(@Param('id') id: string, @Request() req: any) { diff --git a/apps/server/src/deposits/deposits.service.ts b/apps/server/src/deposits/deposits.service.ts index ee9367e..0efe28a 100644 --- a/apps/server/src/deposits/deposits.service.ts +++ b/apps/server/src/deposits/deposits.service.ts @@ -158,6 +158,21 @@ export class DepositsService { return this.repo.save(deposit); } + + async rejectRefund(id: number, reason: string, userId: number) { + const deposit = await this.repo.findOne({ where: { id } }); + if (!deposit) throw new NotFoundException('押金记录不存在'); + + if (!deposit.refundStatus || deposit.refundStatus === 'refunded') { + throw new BadRequestException('未找到待审批的退款申请'); + } + + deposit.refundStatus = null; + deposit.refundApprovedBy = userId; + deposit.refundApprovedAt = new Date(); + deposit.refundRejectedReason = reason; + return this.repo.save(deposit); + } async findPendingRefunds() { const baseWhere = await this.scope.filter({}); return this.repo.find({ diff --git a/apps/server/src/entities/deposit.entity.ts b/apps/server/src/entities/deposit.entity.ts index d8b3c46..c761a22 100644 --- a/apps/server/src/entities/deposit.entity.ts +++ b/apps/server/src/entities/deposit.entity.ts @@ -48,7 +48,7 @@ export class Deposit { recordedBy: number; @Column({ name: 'refund_status', length: 30, nullable: true }) - refundStatus: string; // pending | head_teacher_approved | finance_approved | refunded + refundStatus: string | null; // pending | head_teacher_approved | finance_approved | refunded @Column({ name: 'refund_requested_at', type: 'datetime', nullable: true }) refundRequestedAt: Date; @@ -59,6 +59,9 @@ export class Deposit { @Column({ name: 'refund_approved_at', type: 'datetime', nullable: true }) refundApprovedAt: Date; + @Column({ name: 'refund_rejected_reason', length: 500, nullable: true }) + refundRejectedReason: string; + @OneToMany(() => DepositInstallment, (i) => i.deposit, { cascade: true, eager: true }) installments: DepositInstallment[];