import { useEffect, useState } from 'react'; import { adminApi, type ManualRechargePreflight, type ManualRechargeResult } from '@/api/adminApi'; import { formatCents, isValidMoneyInput, yuanToMoneyUnits } from '@/utils/currency'; import { Button } from './Button'; import { Input } from './Input'; import { Modal } from './Modal'; import { Select } from './Select'; import { Textarea } from './Textarea'; export type ManualRechargeTarget = { id: string; name: string; code: string; balanceCents: number; }; type ManualRechargeDialogProps = { initialTargetId?: string; lockTarget?: boolean; onClose: () => void; onCompleted: () => void | Promise; open: boolean; targets: ManualRechargeTarget[]; }; export function ManualRechargeDialog({ initialTargetId, lockTarget = false, onClose, onCompleted, open, targets }: ManualRechargeDialogProps) { const [tenantId, setTenantId] = useState(''); const [amount, setAmount] = useState(''); const [remark, setRemark] = useState(''); const [review, setReview] = useState(null); const [result, setResult] = useState(null); const [idempotencyKey, setIdempotencyKey] = useState(''); const [error, setError] = useState(''); const [submitting, setSubmitting] = useState(false); useEffect(() => { if (!open) return; setTenantId(initialTargetId ?? targets[0]?.id ?? ''); setAmount(''); setRemark(''); setReview(null); setResult(null); setIdempotencyKey(''); setError(''); setSubmitting(false); }, [initialTargetId, open]); if (!open) return null; const selectedTarget = targets.find((target) => target.id === tenantId); function resetReview() { setReview(null); setResult(null); setIdempotencyKey(''); setError(''); } function closeAndDestroyDraft() { if (submitting) return; setAmount(''); setRemark(''); resetReview(); onClose(); } async function preflight() { if (!tenantId || !Number.isFinite(Number(amount)) || !isValidMoneyInput(amount, { allowNegative: true, allowZero: false })) { setError('请填写非 0 的充值金额;金额支持负数冲正。'); return; } setSubmitting(true); setError(''); try { const preview = await adminApi.preflightManualRecharge({ tenantId, amountCents: yuanToMoneyUnits(amount) }); setReview(preview); setIdempotencyKey(`manual-recharge:${crypto.randomUUID()}`); } catch (failure) { setError(failure instanceof Error ? failure.message : '人工充值资格核对失败'); } finally { setSubmitting(false); } } async function submit() { if (!review || !idempotencyKey) return; setSubmitting(true); setError(''); try { const nextResult = await adminApi.createManualRecharge({ tenantId, amountCents: review.amountCents, expectedAccountUpdatedAt: review.expectedAccountUpdatedAt, idempotencyKey, remark, }); setResult(nextResult); await onCompleted(); } catch (failure) { setError(failure instanceof Error ? failure.message : '人工充值失败'); } finally { setSubmitting(false); } } const footer = (requestClose: () => void) => result ? ( ) : review ? ( <> ) : ( <> ); return ( footer(requestClose)} onClose={closeAndDestroyDraft} open size="md" title={result ? '人工充值结果' : review ? '确认人工充值' : '企业人工充值'} > {result ? (
{result.amountCents > 0 ? '充值已入账' : '余额冲正已入账'} 订单号:{result.orderNo} 充值后余额:¥{formatCents(result.balanceAfterCents)} 操作单号:{result.operationId}{result.replayed ? '(幂等重放)' : ''}
) : review ? (

请核对以下资金变更,确认后将立即写入企业账户。

充值对象
{review.tenant.name}{review.tenant.code} · {review.tenant.id}
操作方向
{review.direction === 'topup' ? '余额充值' : '余额冲正'}
当前现金余额
¥{formatCents(review.balanceCents)}
本次变动
0 ? 'is-positive' : 'is-negative'}>{review.amountCents > 0 ? '+' : '-'}¥{formatCents(Math.abs(review.amountCents))}
预计现金余额
¥{formatCents(review.balanceAfterCents)}
{remark.trim() ?

备注:{remark.trim()}

: null}
) : (
{lockTarget ? ( ) : ( { setAmount(event.target.value); resetReview(); }} prefix="¥" required step="0.0001" type="number" value={amount} />