forked from wangziqi/gongxue-base
feat: add platform dunning notifications
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
||||
loadPlatformAuditLogs,
|
||||
loadPlatformAuditNotificationChannels,
|
||||
loadPlatformAuditNotificationEvents,
|
||||
loadPlatformDunningNotificationChannels,
|
||||
loadPlatformDunningNotificationEvents,
|
||||
loadPlatformInvoices,
|
||||
loadPlatformOverview,
|
||||
loadPlatformQuestionBankGrants,
|
||||
@@ -17,6 +19,8 @@ import {
|
||||
type PlatformAuditLogItem,
|
||||
type PlatformAuditNotificationChannelItem,
|
||||
type PlatformAuditNotificationEventItem,
|
||||
type PlatformDunningNotificationChannelItem,
|
||||
type PlatformDunningNotificationEventItem,
|
||||
type PlatformInvoiceItem,
|
||||
type PlatformOverview,
|
||||
type PlatformQuestionBankGrant,
|
||||
@@ -48,6 +52,8 @@ export default function PlatformWorkbenchPage() {
|
||||
const [auditAlerts, setAuditAlerts] = useState<PlatformAuditAlertItem[]>([]);
|
||||
const [auditNotificationChannels, setAuditNotificationChannels] = useState<PlatformAuditNotificationChannelItem[]>([]);
|
||||
const [auditNotificationEvents, setAuditNotificationEvents] = useState<PlatformAuditNotificationEventItem[]>([]);
|
||||
const [dunningNotificationChannels, setDunningNotificationChannels] = useState<PlatformDunningNotificationChannelItem[]>([]);
|
||||
const [dunningNotificationEvents, setDunningNotificationEvents] = useState<PlatformDunningNotificationEventItem[]>([]);
|
||||
const [banks, setBanks] = useState<PlatformQuestionBankItem[]>([]);
|
||||
const [grants, setGrants] = useState<PlatformQuestionBankGrant[]>([]);
|
||||
const [error, setError] = useState('');
|
||||
@@ -64,7 +70,9 @@ export default function PlatformWorkbenchPage() {
|
||||
loadPlatformAuditAlerts({ status: 'open', limit: 6 }).catch(() => ({ items: [] })),
|
||||
loadPlatformAuditNotificationChannels({ enabled: true, limit: 6 }).catch(() => ({ items: [] })),
|
||||
loadPlatformAuditNotificationEvents({ limit: 6 }).catch(() => ({ items: [] })),
|
||||
]).then(([overviewPayload, tenantPayload, invoicePayload, bankPayload, grantPayload, auditPayload, alertPayload, channelPayload, eventPayload]) => {
|
||||
loadPlatformDunningNotificationChannels({ enabled: true, limit: 6 }).catch(() => ({ items: [] })),
|
||||
loadPlatformDunningNotificationEvents({ limit: 6 }).catch(() => ({ items: [] })),
|
||||
]).then(([overviewPayload, tenantPayload, invoicePayload, bankPayload, grantPayload, auditPayload, alertPayload, channelPayload, eventPayload, dunningChannelPayload, dunningEventPayload]) => {
|
||||
setOverview(overviewPayload.item || null);
|
||||
setTenants(tenantPayload.items || []);
|
||||
setInvoices(invoicePayload.items || []);
|
||||
@@ -74,6 +82,8 @@ export default function PlatformWorkbenchPage() {
|
||||
setAuditAlerts(alertPayload.items || []);
|
||||
setAuditNotificationChannels(channelPayload.items || []);
|
||||
setAuditNotificationEvents(eventPayload.items || []);
|
||||
setDunningNotificationChannels(dunningChannelPayload.items || []);
|
||||
setDunningNotificationEvents(dunningEventPayload.items || []);
|
||||
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '平台后台加载失败'));
|
||||
}, []);
|
||||
|
||||
@@ -230,6 +240,30 @@ export default function PlatformWorkbenchPage() {
|
||||
</View>
|
||||
{!auditNotificationChannels.length && !auditNotificationEvents.length ? <View className='platform-empty'>暂无外部通知渠道或发送事件。</View> : null}
|
||||
</View>
|
||||
<View className='platform-section'>
|
||||
<Text className='platform-section-title'>催缴外部通知</Text>
|
||||
<View className='platform-grid'>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>启用渠道</Text><Text className='platform-metric-value'>{String(dunningNotificationChannels.length)}</Text></View>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>最近事件</Text><Text className='platform-metric-value'>{String(dunningNotificationEvents.length)}</Text></View>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>失败事件</Text><Text className='platform-metric-value'>{String(dunningNotificationEvents.filter(item => item.status === 'failed').length)}</Text></View>
|
||||
<View className='platform-metric'><Text className='platform-metric-label'>待重试</Text><Text className='platform-metric-value'>{String(dunningNotificationEvents.filter(item => item.status === 'retrying').length)}</Text></View>
|
||||
</View>
|
||||
<View className='platform-list'>
|
||||
{dunningNotificationChannels.map(item => (
|
||||
<View className='platform-row' key={item.id}>
|
||||
<Text className='platform-row-main'>{item.name || item.channelCode || '-'}</Text>
|
||||
<Text className='platform-row-meta'>{item.provider || '-'} · level {String(item.minReminderLevel || '-')} · {item.webhook?.host || '-'} · {item.reminderTypes?.join('/') || '-'}</Text>
|
||||
</View>
|
||||
))}
|
||||
{dunningNotificationEvents.map(item => (
|
||||
<View className='platform-row' key={item.id}>
|
||||
<Text className='platform-row-main'>{item.invoiceNo || item.invoiceId || '-'}</Text>
|
||||
<Text className='platform-row-meta'>{item.channelName || item.channelCode || '-'} · {item.status || '-'} · HTTP {String(item.lastHttpCode || '-')} · {item.tenantName || item.tenantSlug || '-'} · 欠款 {money(item.invoiceBalanceCents)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{!dunningNotificationChannels.length && !dunningNotificationEvents.length ? <View className='platform-empty'>暂无催缴通知渠道或发送事件。</View> : null}
|
||||
</View>
|
||||
{error ? <Text className='platform-error'>{error}</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -328,6 +328,55 @@ export interface PlatformAuditNotificationEventItem {
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface PlatformDunningNotificationChannelItem {
|
||||
id: string;
|
||||
channelCode?: string | null;
|
||||
name?: string | null;
|
||||
description?: string | null;
|
||||
enabled?: boolean | null;
|
||||
provider?: string | null;
|
||||
secretRef?: string | null;
|
||||
reminderTypes?: string[] | null;
|
||||
reminderChannels?: string[] | null;
|
||||
minReminderLevel?: number | string | null;
|
||||
tenantIds?: string[] | null;
|
||||
timeoutSec?: number | string | null;
|
||||
webhook?: {
|
||||
protocol?: string | null;
|
||||
host?: string | null;
|
||||
pathname?: string | null;
|
||||
} | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface PlatformDunningNotificationEventItem {
|
||||
id: string;
|
||||
channelId?: string | null;
|
||||
channelCode?: string | null;
|
||||
channelName?: string | null;
|
||||
reminderId?: string | null;
|
||||
invoiceId?: string | null;
|
||||
invoiceNo?: string | null;
|
||||
tenantId?: string | null;
|
||||
tenantName?: string | null;
|
||||
tenantSlug?: string | null;
|
||||
provider?: string | null;
|
||||
status?: string | null;
|
||||
attempts?: number | string | null;
|
||||
lastHttpCode?: number | string | null;
|
||||
lastError?: string | null;
|
||||
lastResponseSummary?: string | null;
|
||||
reminderType?: string | null;
|
||||
reminderChannel?: string | null;
|
||||
reminderLevel?: number | string | null;
|
||||
invoiceBalanceCents?: number | string | null;
|
||||
sentAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface CreatePlatformTenantInput {
|
||||
slug: string;
|
||||
name: string;
|
||||
@@ -521,6 +570,32 @@ export async function loadPlatformAuditNotificationEvents(query: {
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformDunningNotificationChannels(query: {
|
||||
enabled?: boolean;
|
||||
provider?: string;
|
||||
limit?: number;
|
||||
} = {}) {
|
||||
return apiRequest<{ items?: PlatformDunningNotificationChannelItem[] }>('/api/platform-admin/dunning-notification-channels', {
|
||||
query: { ...query, limit: query.limit || 50 },
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformDunningNotificationEvents(query: {
|
||||
channelId?: string;
|
||||
reminderId?: string;
|
||||
invoiceId?: string;
|
||||
tenantId?: string;
|
||||
status?: string;
|
||||
provider?: string;
|
||||
limit?: number;
|
||||
} = {}) {
|
||||
return apiRequest<{ items?: PlatformDunningNotificationEventItem[] }>('/api/platform-admin/dunning-notification-events', {
|
||||
query: { ...query, limit: query.limit || 50 },
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformInvoices(query: { tenantId?: string; status?: string; limit?: number } = {}) {
|
||||
return apiRequest<{ items?: PlatformInvoiceItem[] }>('/api/platform-admin/invoices', {
|
||||
query: { ...query, limit: query.limit || 80 },
|
||||
|
||||
Reference in New Issue
Block a user