feat: add reconciliation and quality reporting

This commit is contained in:
hectorzhao
2026-07-15 14:22:50 +08:00
parent 16311546af
commit 8c3336600e
32 changed files with 1730 additions and 200 deletions
+79 -3
View File
@@ -581,7 +581,16 @@ export type ApplicationReportField = {
required: boolean;
description?: string | null;
reportTypes: string[];
channels: Array<{ id: string; code: string; name: string; groupId: string; groupName: string; required: boolean; reportType: 'signature' | 'drainage' | 'both' }>;
commonReportTypes?: Array<'signature' | 'drainage'>;
channels: Array<{ id: string; code: string; name: string; groupId: string; groupName: string; required: boolean; reportType: 'signature' | 'drainage' | 'both'; source?: 'common' | 'channel' | 'both' }>;
};
export type CommonReportField = DictionaryItem & {
drainageFieldId: string;
reportType: 'signature' | 'drainage';
required: boolean;
sortOrder: number;
drainageField: DictionaryItem & { code?: string; name?: string; fieldType?: string; description?: string | null };
};
export type ReportTask = DictionaryItem & {
@@ -696,6 +705,59 @@ export type PagedResponse<T> = {
pageSize: number;
};
export type DailyReconciliationReport = {
id: string;
reportDate: string;
tenantId: string;
tenantName: string;
applicationId: string;
applicationName: string;
sentUnits: number;
successUnits: number;
generatedAt: string;
updatedAt: string;
};
export type DailyProfitReport = {
id: string;
reportDate: string;
dimensionType: 'application' | 'channel';
dimensionId: string;
dimensionName: string;
tenantId?: string | null;
tenantName?: string | null;
applicationId?: string | null;
channelId?: string | null;
sentUnits: number;
successUnits: number;
revenueCents: number;
costCents: number;
profitCents: number;
profitRateBps: number;
generatedAt: string;
updatedAt: string;
};
export type DailyQualityReport = {
id: string;
reportDate: string;
dimensionType: 'application' | 'channel' | 'signature' | 'drainage';
dimensionId: string;
dimensionName: string;
tenantId?: string | null;
tenantName?: string | null;
applicationId?: string | null;
channelId?: string | null;
signatureId?: string | null;
drainageInfoId?: string | null;
sentUnits: number;
successUnits: number;
successRateBps: number;
avgArrivalMs?: number | null;
generatedAt: string;
updatedAt: string;
};
export type CursorPage<T> = {
items: T[];
pageSize: number;
@@ -979,9 +1041,17 @@ export const adminApi = {
request<ApplicationConnectionsResponse>(`/admin/enterprise-applications/${applicationId}/connections`),
listApplicationReportFields: (applicationId: string, reportType?: 'signature' | 'drainage') =>
request<ApplicationReportField[]>(withQuery(`/admin/enterprise-applications/${applicationId}/report-fields`, { reportType })),
listCommonApplicationReportFields: (reportType: 'signature' | 'drainage') =>
request<ApplicationReportField[]>(withQuery('/admin/report-fields/common', { reportType })),
getApplicationCmppParams: (applicationId: string) =>
request<ApplicationCmppParams>(`/admin/enterprise-applications/${applicationId}/cmpp-params`),
listChannels: () => request<AdminChannel[]>('/admin/channels'),
listReconciliationReports: (query: { dateFrom?: string; dateTo?: string; tenantId?: string; applicationId?: string; page?: number; pageSize?: number } = {}) =>
request<PagedResponse<DailyReconciliationReport>>(withQuery('/admin/reports/reconciliation', query)),
listProfitReports: (query: { dateFrom?: string; dateTo?: string; dimensionType?: 'application' | 'channel'; tenantId?: string; applicationId?: string; channelId?: string; page?: number; pageSize?: number } = {}) =>
request<PagedResponse<DailyProfitReport> & { dimensionType: 'application' | 'channel' }>(withQuery('/admin/reports/profit', query)),
listQualityReports: (query: { dateFrom?: string; dateTo?: string; dimensionType?: 'application' | 'channel' | 'signature' | 'drainage'; tenantId?: string; applicationId?: string; channelId?: string; page?: number; pageSize?: number } = {}) =>
request<PagedResponse<DailyQualityReport> & { dimensionType: DailyQualityReport['dimensionType'] }>(withQuery('/admin/reports/quality', query)),
createChannel: (body: Partial<AdminChannel> & { passwordCipher?: string; desiredConnections?: number; windowSize?: number }) =>
request<AdminChannel>('/admin/channels', { method: 'POST', body: JSON.stringify(body) }),
updateChannel: (id: string, body: Partial<AdminChannel> & { passwordCipher?: string; desiredConnections?: number; windowSize?: number }) =>
@@ -1150,6 +1220,10 @@ export const adminApi = {
createDrainageField: (body: { code: string; name: string; fieldType: 'string' | 'image' | 'file'; required?: boolean; status?: string; description?: string }) =>
request<DictionaryItem>('/admin/dictionaries/drainage-fields', { method: 'POST', body: JSON.stringify(body) }),
deleteDrainageField: (id: string) => request<DictionaryItem>(`/admin/dictionaries/drainage-fields/${id}`, { method: 'DELETE' }),
listCommonReportFields: () => request<CommonReportField[]>('/admin/dictionaries/common-report-fields'),
createCommonReportField: (body: { drainageFieldId: string; reportType: 'signature' | 'drainage'; required: boolean; sortOrder?: number }) =>
request<CommonReportField>('/admin/dictionaries/common-report-fields', { method: 'POST', body: JSON.stringify(body) }),
deleteCommonReportField: (id: string) => request<CommonReportField>(`/admin/dictionaries/common-report-fields/${id}`, { method: 'DELETE' }),
uploadFileObject: async (file: File, body: { purpose: string; prefix?: string }, tenantId?: string) => {
const form = new FormData();
form.set('file', file);
@@ -1215,8 +1289,10 @@ export const clientApi = {
request<ClientSmsApplication[]>('/client/applications', { tenantId }),
getApplicationCmppParams: (applicationId: string, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ApplicationCmppParams>(`/client/applications/${applicationId}/cmpp-params`, { tenantId }),
listApplicationReportFields: (applicationId: string, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ApplicationReportField[]>(`/client/applications/${applicationId}/report-fields`, { tenantId }),
listApplicationReportFields: (applicationId: string, reportType: 'signature' | 'drainage' = 'drainage', tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ApplicationReportField[]>(withQuery(`/client/applications/${applicationId}/report-fields`, { reportType }), { tenantId }),
listCommonApplicationReportFields: (reportType: 'signature' | 'drainage', tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ApplicationReportField[]>(withQuery('/client/report-fields/common', { reportType }), { tenantId }),
listSignatures: (tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>
request<ClientSmsSignature[]>('/client/signatures', { tenantId }),
createSignature: (body: { tenantId?: string; applicationId?: string; name: string; purpose?: string; drainageInfo?: Record<string, unknown> }, tenantId = getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID) =>