feat: build reporting workbench workflow

This commit is contained in:
hectorzhao
2026-09-02 14:30:15 +08:00
parent 53d3668306
commit 9b8196ecab
31 changed files with 7651 additions and 1973 deletions
+353 -63
View File
@@ -1,5 +1,29 @@
import { request, requestBlob, requestForm, withQuery } from '../core/httpClient';
import type { AdminChannel, ChannelConnectionLogResponse, ChannelGroup, ChannelGroupDeletionImpact, ChannelReportField, ChannelTestResponse, CmppConnectionState, DeleteTargetRequest, DeletionPreflight, DeletionResult, DeletionTargetType, DictionaryItem, PagedResult, ReportImportMapping, ReportImportProfile, ReportImportReviewBatch, ReportMaterialBatch, ReportMaterialBatchPreflight, ReportMaterialBatchResult, ReportMaterialPendingItem, ReportRecord, ReportTask } from '../types';
import type {
AdminChannel,
ChannelConnectionLogResponse,
ChannelGroup,
ChannelGroupDeletionImpact,
ChannelReportField,
ChannelTestResponse,
CmppConnectionState,
DeleteTargetRequest,
DeletionPreflight,
DeletionResult,
DeletionTargetType,
DictionaryItem,
PagedResult,
ReportImportMapping,
ReportImportProfile,
ReportImportReviewBatch,
ReportMaterialBatch,
ReportMaterialBatchPreflight,
ReportMaterialBatchResult,
ReportMaterialPendingItem,
ReportRecord,
ReportTask,
SingleReportMaterialDetail,
} from '../types';
import { assertUploadFileSize } from '@/utils/fileUpload';
// Report generation consumes channel report fields, so these endpoints keep one
@@ -8,88 +32,354 @@ export const adminChannelsReportsApi = {
listChannels: () => request<AdminChannel[]>('/admin/channels'),
listChannelsPage: (query: { keyword?: string; carrier?: string; status?: string; page: number; pageSize: number }) =>
request<PagedResult<AdminChannel>>(withQuery('/admin/channels', query)),
createChannel: (body: Partial<AdminChannel> & { passwordCipher?: string; desiredConnections?: number; windowSize?: number; heartbeatIntervalSeconds?: number; heartbeatMissThreshold?: number }) =>
request<AdminChannel>('/admin/channels', { method: 'POST', body: JSON.stringify(body) }),
updateChannel: (id: string, body: Partial<AdminChannel> & { passwordCipher?: string; desiredConnections?: number; windowSize?: number; heartbeatIntervalSeconds?: number; heartbeatMissThreshold?: number }) =>
request<AdminChannel>(`/admin/channels/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
copyChannel: (id: string, body: { operatorId?: string } = {}) => request<AdminChannel>(`/admin/channels/${id}/copy`, {
method: 'POST',
body: JSON.stringify(body),
}),
testChannel: (id: string, body: { phoneNumber?: string; phones?: string[] | string; content: string; accessNo?: string }) =>
request<ChannelTestResponse>(`/admin/channels/${id}/test`, { method: 'POST', body: JSON.stringify(body) }),
changeChannelStatus: (id: string, status: string, reason?: string) => request<AdminChannel>(`/admin/channels/${id}/status`, {
method: 'POST',
body: JSON.stringify({ status, reason }),
}),
deleteChannel: (id: string, reason?: string) => request<AdminChannel>(`/admin/channels/${id}`, {
method: 'DELETE',
body: JSON.stringify({ reason }),
}),
createChannel: (
body: Partial<AdminChannel> & {
passwordCipher?: string;
desiredConnections?: number;
windowSize?: number;
heartbeatIntervalSeconds?: number;
heartbeatMissThreshold?: number;
},
) => request<AdminChannel>('/admin/channels', { method: 'POST', body: JSON.stringify(body) }),
updateChannel: (
id: string,
body: Partial<AdminChannel> & {
passwordCipher?: string;
desiredConnections?: number;
windowSize?: number;
heartbeatIntervalSeconds?: number;
heartbeatMissThreshold?: number;
},
) => request<AdminChannel>(`/admin/channels/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
copyChannel: (id: string, body: { operatorId?: string } = {}) =>
request<AdminChannel>(`/admin/channels/${id}/copy`, {
method: 'POST',
body: JSON.stringify(body),
}),
testChannel: (
id: string,
body: { phoneNumber?: string; phones?: string[] | string; content: string; accessNo?: string },
) => request<ChannelTestResponse>(`/admin/channels/${id}/test`, { method: 'POST', body: JSON.stringify(body) }),
changeChannelStatus: (id: string, status: string, reason?: string) =>
request<AdminChannel>(`/admin/channels/${id}/status`, {
method: 'POST',
body: JSON.stringify({ status, reason }),
}),
deleteChannel: (id: string, reason?: string) =>
request<AdminChannel>(`/admin/channels/${id}`, {
method: 'DELETE',
body: JSON.stringify({ reason }),
}),
getDeletionPreflight: (type: DeletionTargetType, id: string) =>
request<DeletionPreflight>(`/admin/deletions/${type}/${id}/preflight`),
deleteGovernedTarget: (type: DeletionTargetType, id: string, body: DeleteTargetRequest) =>
request<DeletionResult>(`/admin/deletions/${type}/${id}`, { method: 'POST', body: JSON.stringify(body) }),
listChannelConnectionLogs: (id: string) => request<ChannelConnectionLogResponse>(`/admin/channels/${id}/connection-logs`),
listChannelConnectionLogs: (id: string) =>
request<ChannelConnectionLogResponse>(`/admin/channels/${id}/connection-logs`),
listChannelGroups: () => request<ChannelGroup[]>('/admin/channel-groups'),
createChannelGroup: (body: { code: string; name: string; carrier: 'mobile' | 'unicom' | 'telecom'; description?: string; status?: string; retryEnabled?: boolean; retryTimeLimitHours?: number; retryTimeLimitMinutes?: number }) =>
request<ChannelGroup>('/admin/channel-groups', { method: 'POST', body: JSON.stringify(body) }),
updateChannelGroup: (id: string, body: { code?: string; name?: string; carrier?: 'mobile' | 'unicom' | 'telecom'; description?: string; status?: string; retryEnabled?: boolean; retryTimeLimitHours?: number; retryTimeLimitMinutes?: number; items?: Array<Record<string, unknown>> }) =>
request<ChannelGroup>(`/admin/channel-groups/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
createChannelGroup: (body: {
code: string;
name: string;
carrier: 'mobile' | 'unicom' | 'telecom';
description?: string;
status?: string;
retryEnabled?: boolean;
retryTimeLimitHours?: number;
retryTimeLimitMinutes?: number;
}) => request<ChannelGroup>('/admin/channel-groups', { method: 'POST', body: JSON.stringify(body) }),
updateChannelGroup: (
id: string,
body: {
code?: string;
name?: string;
carrier?: 'mobile' | 'unicom' | 'telecom';
description?: string;
status?: string;
retryEnabled?: boolean;
retryTimeLimitHours?: number;
retryTimeLimitMinutes?: number;
items?: Array<Record<string, unknown>>;
},
) => request<ChannelGroup>(`/admin/channel-groups/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
getChannelGroupDeletionImpact: (id: string) =>
request<ChannelGroupDeletionImpact>(`/admin/channel-groups/${id}/deletion-impact`),
deleteChannelGroup: (id: string) =>
request<ChannelGroup>(`/admin/channel-groups/${id}`, { method: 'DELETE' }),
deleteChannelGroup: (id: string) => request<ChannelGroup>(`/admin/channel-groups/${id}`, { method: 'DELETE' }),
addChannelGroupItem: (body: Record<string, unknown>) =>
request<DictionaryItem>('/admin/channel-groups/items', { method: 'POST', body: JSON.stringify(body) }),
listChannelRouteRules: () => request<DictionaryItem[]>('/admin/channel-route-rules'),
createChannelRouteRule: (body: { tenantId?: string; applicationId: string; groupId: string; carrier: string; priority?: number; status?: string }) =>
request<DictionaryItem>('/admin/channel-route-rules', { method: 'POST', body: JSON.stringify(body) }),
createChannelRouteRule: (body: {
tenantId?: string;
applicationId: string;
groupId: string;
carrier: string;
priority?: number;
status?: string;
}) => request<DictionaryItem>('/admin/channel-route-rules', { method: 'POST', body: JSON.stringify(body) }),
listChannelConnections: (id: string) => request<CmppConnectionState[]>(`/admin/channels/${id}/connections`),
replaceApplicationRouteRules: (applicationId: string, body: { routes: Array<{ carrier: 'mobile' | 'unicom' | 'telecom'; groupId: string; priority?: number; status?: string }> }) =>
request<DictionaryItem[]>(`/admin/enterprise-applications/${applicationId}/route-rules`, { method: 'PUT', body: JSON.stringify(body) }),
listChannelReportFields: (channelId?: string) => request<ChannelReportField[]>(withQuery('/admin/channel-report-fields', { channelId })),
replaceApplicationRouteRules: (
applicationId: string,
body: {
routes: Array<{ carrier: 'mobile' | 'unicom' | 'telecom'; groupId: string; priority?: number; status?: string }>;
},
) =>
request<DictionaryItem[]>(`/admin/enterprise-applications/${applicationId}/route-rules`, {
method: 'PUT',
body: JSON.stringify(body),
}),
listChannelReportFields: (channelId?: string) =>
request<ChannelReportField[]>(withQuery('/admin/channel-report-fields', { channelId })),
createChannelReportField: (body: Record<string, unknown>) =>
request<ChannelReportField>('/admin/channel-report-fields', { method: 'POST', body: JSON.stringify(body) }),
replaceChannelReportFields: (channelId: string, reportType: 'signature' | 'drainage', fields: Array<Record<string, unknown>>) =>
request<ChannelReportField[]>(`/admin/channels/${channelId}/report-fields/${reportType}`, { method: 'PUT', body: JSON.stringify({ fields }) }),
listPendingReportMaterials: (query: { reportType?: 'signature' | 'drainage'; tenantId?: string; applicationId?: string; keyword?: string; startAt?: string; endAt?: string; page?: number; pageSize?: number } = {}) =>
request<PagedResult<ReportMaterialPendingItem>>(withQuery('/admin/report-materials/pending', query)),
replaceChannelReportFields: (
channelId: string,
reportType: 'signature' | 'drainage',
fields: Array<Record<string, unknown>>,
) =>
request<ChannelReportField[]>(`/admin/channels/${channelId}/report-fields/${reportType}`, {
method: 'PUT',
body: JSON.stringify({ fields }),
}),
listPendingReportMaterials: (
query: {
reportType?: 'signature' | 'drainage';
tenantId?: string;
applicationId?: string;
keyword?: string;
startAt?: string;
endAt?: string;
page?: number;
pageSize?: number;
} = {},
) => request<PagedResult<ReportMaterialPendingItem>>(withQuery('/admin/report-materials/pending', query)),
listReportImportProfiles: (reportType?: 'signature' | 'drainage') =>
request<ReportImportProfile[]>(withQuery('/admin/report-materials/import-profiles', { reportType })),
saveReportImportProfile: (body: Omit<ReportImportProfile, 'id'> & { id?: string }) =>
request<ReportImportProfile>('/admin/report-materials/import-profiles', { method: 'POST', body: JSON.stringify(body) }),
analyzeReportMaterialImport: (file: File, body: { tenantId: string; applicationId?: string; reportType: 'signature' | 'drainage'; sheetName?: string; headerRowCount?: number; dataStartRow?: number; profileId?: string }) => {
request<ReportImportProfile>('/admin/report-materials/import-profiles', {
method: 'POST',
body: JSON.stringify(body),
}),
analyzeReportMaterialImport: (
file: File,
body: {
tenantId: string;
applicationId?: string;
reportType: 'signature' | 'drainage';
sheetName?: string;
headerRowCount?: number;
dataStartRow?: number;
profileId?: string;
},
) => {
assertUploadFileSize(file);
const form = new FormData();
form.set('file', file);
Object.entries(body).forEach(([key, value]) => { if (value !== undefined) form.set(key, String(value)); });
return requestForm<Record<string, unknown> & { id: string; columns: Array<{ sourceColumnIndex: number; columnLetter: string; sourceHeader: string; sourceHeaderPath: string; imageCount: number }>; rows: Array<Record<string, unknown>>; suggestedMappings: ReportImportMapping[] }>('/admin/report-materials/imports/analyze', form);
Object.entries(body).forEach(([key, value]) => {
if (value !== undefined) form.set(key, String(value));
});
return requestForm<
Record<string, unknown> & {
id: string;
columns: Array<{
sourceColumnIndex: number;
columnLetter: string;
sourceHeader: string;
sourceHeaderPath: string;
imageCount: number;
}>;
rows: Array<Record<string, unknown>>;
suggestedMappings: ReportImportMapping[];
}
>('/admin/report-materials/imports/analyze', form);
},
commitReportMaterialImport: (id: string, body: { mappings: ReportImportMapping[]; profile?: Omit<ReportImportProfile, 'id'> & { id?: string } }) =>
request<Record<string, unknown>>(`/admin/report-materials/imports/${id}/commit`, { method: 'PUT', body: JSON.stringify(body) }),
listReportImportReviewBatches: (query: { reportType?: 'signature' | 'drainage'; status?: string; keyword?: string; startAt?: string; endAt?: string; page?: number; pageSize?: number } = {}) =>
commitReportMaterialImport: (
id: string,
body: { mappings: ReportImportMapping[]; profile?: Omit<ReportImportProfile, 'id'> & { id?: string } },
) =>
request<Record<string, unknown>>(`/admin/report-materials/imports/${id}/commit`, {
method: 'PUT',
body: JSON.stringify(body),
}),
listReportImportReviewBatches: (
query: {
reportType?: 'signature' | 'drainage';
status?: string;
keyword?: string;
startAt?: string;
endAt?: string;
page?: number;
pageSize?: number;
} = {},
) =>
request<PagedResult<ReportImportReviewBatch>>(withQuery('/admin/report-materials/imports/review-batches', query)),
reviewReportImportItems: (id: string, body: { decision: 'approve' | 'reject'; itemIds?: string[]; reason?: string }) =>
request<{ batchId: string; status: string; approvedCount: number; rejectedCount: number; failedCount: number }>(`/admin/report-materials/imports/${id}/review`, { method: 'POST', body: JSON.stringify(body) }),
listReportMaterialBatches: (query: { keyword?: string; startAt?: string; endAt?: string; page?: number; pageSize?: number } = {}) =>
request<PagedResult<ReportMaterialBatch>>(withQuery('/admin/report-materials/batches', query)),
preflightReportMaterialBatch: (body: { items: Array<{ reportType: 'signature' | 'drainage'; signatureId: string; drainageItemId?: string; materialVersion?: number }> }) =>
request<ReportMaterialBatchPreflight>('/admin/report-materials/batches/preflight', { method: 'POST', body: JSON.stringify(body) }),
createReportMaterialBatch: (body: { idempotencyKey: string; items: Array<{ reportType: 'signature' | 'drainage'; signatureId: string; drainageItemId?: string; materialVersion: number }> }) =>
request<ReportMaterialBatchResult>('/admin/report-materials/batches', { method: 'POST', body: JSON.stringify(body) }),
listReportTasks: (query: { tenantId?: string; status?: string; channelId?: string; reportType?: 'signature' | 'drainage' } = {}) => request<ReportTask[]>(withQuery('/admin/report-tasks', query)),
listReportTasksPage: (query: { tenantId?: string; status?: string; channelId?: string; reportType?: 'signature' | 'drainage'; keyword?: string; createdAtFrom?: string; createdAtTo?: string; page: number; pageSize: number }) =>
request<PagedResult<ReportTask>>(withQuery('/admin/report-tasks', query)),
createReportTask: (body: { tenantId: string; signatureId: string; channelId: string; carrier?: 'mobile' | 'unicom' | 'telecom'; reportType?: 'signature' | 'drainage'; drainageItemId?: string; createdById?: string }) =>
request<ReportTask>('/admin/report-tasks/generate', { method: 'POST', body: JSON.stringify(body) }),
changeReportTaskStatuses: (body: { items: Array<{ signatureId: string; channelId: string; carrier?: 'mobile' | 'unicom' | 'telecom'; status: string; reportType?: 'signature' | 'drainage'; drainageItemId?: string }>; reason?: string; operatorId?: string; sourceEntry?: 'enterprise_signature' | 'report_task' | 'channel_report' }) =>
request<Array<{ signatureId: string; reportStatus: string; carrierReportSummary: Record<string, { status: string; approved: number; total: number }> }>>('/admin/report-tasks/status-change', { method: 'POST', body: JSON.stringify(body) }),
reviewReportImportItems: (
id: string,
body: { decision: 'approve' | 'reject'; itemIds?: string[]; reason?: string },
) =>
request<{ batchId: string; status: string; approvedCount: number; rejectedCount: number; failedCount: number }>(
`/admin/report-materials/imports/${id}/review`,
{ method: 'POST', body: JSON.stringify(body) },
),
listReportMaterialBatches: (
query: { keyword?: string; startAt?: string; endAt?: string; page?: number; pageSize?: number } = {},
) => request<PagedResult<ReportMaterialBatch>>(withQuery('/admin/report-materials/batches', query)),
getReportMaterialBatch: (id: string) => request<ReportMaterialBatch>(`/admin/report-materials/batches/${id}`),
listReportMaterialBatchTasks: (
id: string,
query: {
keyword?: string;
reportType?: 'signature' | 'drainage';
status?: string;
channelId?: string;
page?: number;
pageSize?: number;
} = {},
) => request<PagedResult<ReportTask>>(withQuery(`/admin/report-materials/batches/${id}/tasks`, query)),
preflightReportMaterialBatch: (body: {
items: Array<{
reportType: 'signature' | 'drainage';
signatureId: string;
drainageItemId?: string;
materialVersion?: number;
}>;
}) =>
request<ReportMaterialBatchPreflight>('/admin/report-materials/batches/preflight', {
method: 'POST',
body: JSON.stringify(body),
}),
createReportMaterialBatch: (body: {
idempotencyKey: string;
items: Array<{
reportType: 'signature' | 'drainage';
signatureId: string;
drainageItemId?: string;
materialVersion: number;
}>;
}) =>
request<ReportMaterialBatchResult>('/admin/report-materials/batches', {
method: 'POST',
body: JSON.stringify(body),
}),
listReportTasks: (
query: { tenantId?: string; status?: string; channelId?: string; reportType?: 'signature' | 'drainage' } = {},
) => request<ReportTask[]>(withQuery('/admin/report-tasks', query)),
listReportTasksPage: (query: {
tenantId?: string;
applicationId?: string;
status?: string;
channelId?: string;
reportType?: 'signature' | 'drainage';
keyword?: string;
carrier?: string;
todaySendMin?: number;
todaySendMax?: number;
sort?: string;
createdAtFrom?: string;
createdAtTo?: string;
page: number;
pageSize: number;
}) => request<PagedResult<ReportTask>>(withQuery('/admin/report-tasks', query)),
listReportDetailsPage: (query: {
tenantId?: string;
applicationId?: string;
signatureId?: string;
channelId?: string;
carrier?: string;
status?: string;
reportType?: 'signature' | 'drainage';
keyword?: string;
createdAtFrom?: string;
createdAtTo?: string;
page: number;
pageSize: number;
}) => request<PagedResult<ReportTask>>(withQuery('/admin/report-details', query)),
getSingleReportMaterialDetail: (body: {
reportType?: 'signature' | 'drainage';
signatureId: string;
channelId: string;
carrier?: 'mobile' | 'unicom' | 'telecom';
drainageItemId?: string;
batchItemId?: string;
}) =>
request<SingleReportMaterialDetail>('/admin/report-materials/single-detail', {
method: 'POST',
body: JSON.stringify(body),
}),
exportSingleReportMaterial: (body: {
reportType?: 'signature' | 'drainage';
signatureId: string;
channelId: string;
carrier?: 'mobile' | 'unicom' | 'telecom';
drainageItemId?: string;
batchItemId?: string;
}) => requestBlob('/admin/report-materials/single-export', { method: 'POST', body: JSON.stringify(body) }),
createReportTask: (body: {
tenantId: string;
signatureId: string;
channelId: string;
carrier?: 'mobile' | 'unicom' | 'telecom';
reportType?: 'signature' | 'drainage';
drainageItemId?: string;
createdById?: string;
}) => request<ReportTask>('/admin/report-tasks/generate', { method: 'POST', body: JSON.stringify(body) }),
changeReportTaskStatuses: (body: {
items: Array<{
signatureId: string;
channelId: string;
carrier?: 'mobile' | 'unicom' | 'telecom';
status: string;
reportType?: 'signature' | 'drainage';
drainageItemId?: string;
}>;
reason?: string;
operatorId?: string;
sourceEntry?: 'enterprise_signature' | 'report_task' | 'channel_report';
}) =>
request<
Array<{
signatureId: string;
reportStatus: string;
carrierReportSummary: Record<string, { status: string; approved: number; total: number }>;
}>
>('/admin/report-tasks/status-change', { method: 'POST', body: JSON.stringify(body) }),
createReportExport: (id: string, body: { fileObjectId?: string; fileName: string; rowCount?: number }) =>
request<Record<string, unknown>>(`/admin/report-tasks/${id}/export`, { method: 'POST', body: JSON.stringify(body) }),
importReportReceipt: (id: string, body: { fileObjectId?: string; fileName: string; fileContent?: string; delimiter?: ',' | '\t'; rowCount?: number; successCount?: number; failedCount?: number; statusAfter?: string; reason?: string; result?: Record<string, unknown> }) =>
request<Record<string, unknown>>(`/admin/report-tasks/${id}/receipt-import`, { method: 'POST', body: JSON.stringify(body) }),
listReportRecords: (query: { taskId?: string; channelId?: string } = {}) => request<ReportRecord[]>(withQuery('/admin/report-records', query)),
listReportRecordsPage: (query: { taskId?: string; channelId?: string; keyword?: string; reportType?: 'signature' | 'drainage'; createdAtFrom?: string; createdAtTo?: string; page: number; pageSize: number }) =>
request<PagedResult<ReportRecord>>(withQuery('/admin/report-records', query)),
request<Record<string, unknown>>(`/admin/report-tasks/${id}/export`, {
method: 'POST',
body: JSON.stringify(body),
}),
importReportReceipt: (
id: string,
body: {
fileObjectId?: string;
fileName: string;
fileContent?: string;
delimiter?: ',' | '\t';
rowCount?: number;
successCount?: number;
failedCount?: number;
statusAfter?: string;
reason?: string;
result?: Record<string, unknown>;
},
) =>
request<Record<string, unknown>>(`/admin/report-tasks/${id}/receipt-import`, {
method: 'POST',
body: JSON.stringify(body),
}),
listReportRecords: (query: { taskId?: string; channelId?: string } = {}) =>
request<ReportRecord[]>(withQuery('/admin/report-records', query)),
listReportRecordsPage: (query: {
taskId?: string;
channelId?: string;
batchNo?: string;
statusAfter?: string;
action?: string;
sourceEntry?: string;
operatorKeyword?: string;
keyword?: string;
reportType?: 'signature' | 'drainage';
createdAtFrom?: string;
createdAtTo?: string;
page: number;
pageSize: number;
}) => request<PagedResult<ReportRecord>>(withQuery('/admin/report-records', query)),
};