Supplier Reporting & Notifications
Supplier Reporting & Notifications
Система автоматической отчетности и уведомлений для мониторинга обработки таможенных счёт-фактур турецких продавцов.
📊 ИНТЕЛЛЕКТУАЛЬНАЯ ОТЧЕТНОСТЬ — Автоматическое создание отчетов, дашбордов и уведомлений для полного контроля процессов.
📊 Система отчетности
ReportingSystem Class
/**
* Система автоматической генерации отчетов
* Automatic report generation system
*/
export class SupplierReportingSystem {
private readonly templates: Map<string, ReportTemplate> = new Map();
private readonly generators: Map<string, ReportGenerator> = new Map();
private readonly storage: ReportStorage;
constructor(config: ReportingConfig) {
this.storage = new ReportStorage(config.storage);
this.initializeTemplates();
this.initializeGenerators();
}
/**
* Генерация полного отчета по экспортному пакету
* Generate complete export batch report
*/
async generateExportBatchReport(
data: ExportReportData
): Promise<GeneratedReport> {
console.log(`📊 Генерация отчета по экспорту: ${data.executionId}`);
const reportId = this.generateReportId('export_batch');
const startTime = Date.now();
try {
// Подготовка данных для отчета
const reportData = await this.prepareReportData(data);
// Генерация разделов отчета
const sections = await this.generateReportSections(reportData);
// Создание итогового отчета
const report: GeneratedReport = {
id: reportId,
type: 'export_batch',
title: `Отчет по обработке турецкого экспорта`,
subtitle: `Пакет ${data.exportBatch.batchId} от ${new Date().toLocaleDateString('ru-RU')}`,
generatedAt: new Date().toISOString(),
generationTime: 0,
sections,
summary: this.generateSummarySection(reportData),
attachments: await this.generateAttachments(reportData),
metadata: {
executionId: data.executionId,
batchId: data.exportBatch.batchId,
totalFiles: data.exportBatch.invoiceFiles.length,
successRate: reportData.successRate
}
};
report.generationTime = Date.now() - startTime;
// Сохранение отчета
await this.storage.saveReport(report);
console.log(`✅ Отчет сгенерирован за ${Math.round(report.generationTime / 1000)}с`);
console.log(` 📄 Разделов: ${sections.length}`);
console.log(` 📎 Вложений: ${report.attachments?.length || 0}`);
return report;
} catch (error) {
console.error(`❌ Ошибка генерации отчета:`, error);
throw new Error(`Не удалось сгенерировать отчет: ${error}`);
}
}
/**
* Создание дашборда в реальном времени
* Create real-time dashboard
*/
async generateLiveDashboard(
timeframe: DashboardTimeframe = '24h'
): Promise<LiveDashboard> {
console.log(`📈 Генерация live дашборда (${timeframe})`);
// Получение данных за указанный период
const data = await this.collectDashboardData(timeframe);
const dashboard: LiveDashboard = {
id: this.generateDashboardId(),
timeframe,
lastUpdated: new Date().toISOString(),
metrics: {
totalInvoices: data.totalInvoices,
successfulProcessing: data.successfulProcessing,
failedProcessing: data.failedProcessing,
averageProcessingTime: data.averageProcessingTime,
totalAmount: data.totalAmount,
topCurrencies: data.topCurrencies,
topHsCodes: data.topHsCodes
},
charts: [
await this.generateProcessingChart(data),
await this.generateAmountChart(data),
await this.generateErrorChart(data),
await this.generateCurrencyChart(data)
],
alerts: await this.generateDashboardAlerts(data),
recommendations: await this.generateDashboardRecommendations(data)
};
console.log(`📊 Дашборд готов:`);
console.log(` 📈 Метрик: ${Object.keys(dashboard.metrics).length}`);
console.log(` 📊 Графиков: ${dashboard.charts.length}`);
console.log(` ⚠️ Алертов: ${dashboard.alerts.length}`);
return dashboard;
}
/**
* Генерация отчета по валютам
* Generate currency analysis report
*/
async generateCurrencyReport(
period: ReportPeriod
): Promise<CurrencyAnalysisReport> {
console.log(`💰 Анализ валют за период: ${period.from} - ${period.to}`);
const currencyData = await this.collectCurrencyData(period);
return {
id: this.generateReportId('currency_analysis'),
period,
generatedAt: new Date().toISOString(),
totalTransactions: currencyData.totalTransactions,
totalAmount: currencyData.totalAmount,
currencies: currencyData.currencies.map(curr => ({
code: curr.code,
name: curr.name,
symbol: curr.symbol,
transactionCount: curr.transactionCount,
totalAmount: curr.totalAmount,
averageAmount: curr.totalAmount / curr.transactionCount,
percentage: (curr.transactionCount / currencyData.totalTransactions) * 100,
trend: this.calculateCurrencyTrend(curr, period)
})),
insights: [
`Основная валюта: ${currencyData.topCurrency.code} (${currencyData.topCurrency.percentage.toFixed(1)}%)`,
`Средний размер счёт-фактуры: ${currencyData.averageInvoiceAmount.toFixed(2)} USD`,
`Всего валют использовано: ${currencyData.currencies.length}`,
`Динамика: ${currencyData.overallTrend}`
]
};
}
/**
* Генерация разделов отчета
* Generate report sections
*/
private async generateReportSections(
data: ProcessedReportData
): Promise<ReportSection[]> {
const sections: ReportSection[] = [];
// Раздел "Обзор пакета"
sections.push({
title: 'Обзор пакета',
type: 'overview',
content: {
batchInfo: {
id: data.batchId,
totalFiles: data.totalFiles,
processedFiles: data.processedFiles,
successRate: `${data.successRate.toFixed(1)}%`,
totalAmount: `${data.totalAmount} ${data.currency}`,
processingTime: `${Math.round(data.processingTime / 1000)}с`
},
statistics: [
{ label: 'Успешно обработано', value: data.successCount, color: 'green' },
{ label: 'Ошибок обработки', value: data.errorCount, color: 'red' },
{ label: 'Среднее время', value: `${Math.round(data.avgTime)}мс`, color: 'blue' }
]
}
});
// Раздел "Детализация по файлам"
sections.push({
title: 'Детализация по файлам',
type: 'detailed_results',
content: {
successfulFiles: data.results.filter(r => r.success).map(r => ({
filename: r.filename,
postingNumber: r.postingNumber,
processingTime: `${r.processingTime}мс`,
fileUrl: r.fileUrl
})),
failedFiles: data.results.filter(r => !r.success).map(r => ({
filename: r.filename,
postingNumber: r.postingNumber,
error: r.error,
isRetriable: this.isRetriableError(r.error || '')
}))
}
});
// Раздел "Анализ HS-кодов"
sections.push({
title: 'Анализ товарных позиций',
type: 'hs_code_analysis',
content: await this.analyzeHsCodesInBatch(data)
});
return sections;
}
/**
* Инициализация шаблонов отчетов
* Initialize report templates
*/
private initializeTemplates(): void {
// Шаблон отчета по экспорту
this.templates.set('export_batch', {
name: 'Отчет по экспортному пакету',
sections: ['overview', 'detailed_results', 'hs_code_analysis', 'recommendations'],
format: 'html',
styling: 'corporate',
includeCharts: true
});
// Шаблон валютного отчета
this.templates.set('currency_analysis', {
name: 'Анализ валют',
sections: ['currency_breakdown', 'trends', 'recommendations'],
format: 'html',
styling: 'financial',
includeCharts: true
});
// Шаблон дашборда
this.templates.set('live_dashboard', {
name: 'Live дашборд',
sections: ['metrics', 'charts', 'alerts'],
format: 'interactive',
styling: 'dashboard',
includeCharts: true,
autoRefresh: true
});
}
}
/**
* Типы для системы отчетности
*/
interface GeneratedReport {
id: string;
type: string;
title: string;
subtitle: string;
generatedAt: string;
generationTime: number;
sections: ReportSection[];
summary: ReportSummary;
attachments?: ReportAttachment[];
metadata: ReportMetadata;
}
interface LiveDashboard {
id: string;
timeframe: DashboardTimeframe;
lastUpdated: string;
metrics: DashboardMetrics;
charts: DashboardChart[];
alerts: DashboardAlert[];
recommendations: DashboardRecommendation[];
}
interface ReportSection {
title: string;
type: string;
content: any;
}
interface DashboardMetrics {
totalInvoices: number;
successfulProcessing: number;
failedProcessing: number;
averageProcessingTime: number;
totalAmount: number;
topCurrencies: CurrencyMetric[];
topHsCodes: HsCodeMetric[];
}
type DashboardTimeframe = '1h' | '6h' | '24h' | '7d' | '30d';
📢 Система уведомлений
NotificationManager Class
/**
* Менеджер уведомлений и алертов
* Notifications and alerts manager
*/
export class SupplierNotificationManager {
private readonly channels: Map<string, NotificationChannel> = new Map();
private readonly subscribers: NotificationSubscriber[] = [];
private readonly alertRules: AlertRule[] = [];
constructor(config: NotificationConfig) {
this.initializeChannels(config.channels);
this.initializeSubscribers(config.subscribers);
this.initializeAlertRules(config.alertRules);
}
/**
* Отправка уведомления о завершении пакетной обработки
* Send batch processing completion notification
*/
async notifyBatchCompletion(
executionId: string,
result: BatchProcessingResult
): Promise<void> {
console.log(`📢 Отправка уведомлений о завершении пакета: ${executionId}`);
const notification: BatchCompletionNotification = {
type: 'batch_completion',
executionId,
timestamp: new Date().toISOString(),
severity: result.errorCount > 0 ? 'warning' : 'success',
title: `Пакетная обработка завершена`,
message: this.generateBatchCompletionMessage(result),
data: {
totalFiles: result.totalFiles,
successCount: result.successCount,
errorCount: result.errorCount,
processingTime: result.totalProcessingTime,
successRate: (result.successCount / result.totalFiles) * 100
},
actions: [
{
label: 'Просмотреть отчет',
url: `/reports/${executionId}`,
style: 'primary'
},
{
label: 'Повторить ошибки',
url: `/retry/${executionId}`,
style: 'secondary',
condition: result.errorCount > 0
}
]
};
// Отправка через все активные каналы
await this.broadcastNotification(notification);
}
/**
* Отправка критического алерта
* Send critical alert
*/
async sendCriticalAlert(
title: string,
message: string,
context?: AlertContext
): Promise<void> {
console.log(`🚨 КРИТИЧЕСКИЙ АЛЕРТ: ${title}`);
const alert: CriticalAlert = {
type: 'critical_alert',
id: this.generateAlertId(),
timestamp: new Date().toISOString(),
severity: 'critical',
title,
message,
context,
requiresImmediateAction: true,
escalationLevel: 1
};
// Немедленная отправка через приоритетные каналы
await this.sendImmediateAlert(alert);
// Эскалация, если не подтверждено в течение 5 минут
setTimeout(async () => {
if (!await this.isAlertAcknowledged(alert.id)) {
await this.escalateAlert(alert);
}
}, 5 * 60 * 1000);
}
/**
* Настройка автоматических уведомлений
* Configure automatic notifications
*/
async configureAutomaticNotifications(
rules: AutoNotificationRule[]
): Promise<void> {
console.log(`⚙️ Настройка автоматических уведомлений: ${rules.length} правил`);
for (const rule of rules) {
console.log(` 📋 Правило: ${rule.name}`);
console.log(` Триггер: ${rule.trigger.type}`);
console.log(` Каналы: ${rule.channels.join(', ')}`);
console.log(` Получатели: ${rule.recipients.length} чел.`);
// Регистрация обработчика события
this.registerEventHandler(rule.trigger, async (eventData) => {
if (await this.evaluateCondition(rule.condition, eventData)) {
const notification = await this.generateRuleNotification(rule, eventData);
await this.sendNotificationToChannels(notification, rule.channels, rule.recipients);
}
});
}
console.log(`✅ Автоматические уведомления настроены`);
}
/**
* Отправка еженедельного сводного отчета
* Send weekly summary report
*/
async sendWeeklySummary(): Promise<void> {
console.log(`📅 Генерация еженедельного сводного отчета`);
const weekStart = new Date();
weekStart.setDate(weekStart.getDate() - 7);
const weeklyData = await this.collectWeeklyData(weekStart, new Date());
const summary: WeeklySummaryNotification = {
type: 'weekly_summary',
timestamp: new Date().toISOString(),
period: {
from: weekStart.toISOString(),
to: new Date().toISOString()
},
title: 'Еженедельный отчет по турецкому экспорту',
message: this.generateWeeklySummaryMessage(weeklyData),
data: weeklyData,
attachments: [
{
name: 'weekly_report.pdf',
type: 'application/pdf',
url: weeklyData.reportUrl
}
]
};
// Отправка подписчикам еженедельных отчетов
const weeklySubscribers = this.subscribers.filter(s =>
s.preferences.weeklyReports
);
await this.sendToSubscribers(summary, weeklySubscribers);
}
/**
* Инициализация каналов уведомлений
* Initialize notification channels
*/
private initializeChannels(channelConfigs: ChannelConfig[]): void {
channelConfigs.forEach(config => {
let channel: NotificationChannel;
switch (config.type) {
case 'email':
channel = new EmailNotificationChannel(config);
break;
case 'slack':
channel = new SlackNotificationChannel(config);
break;
case 'telegram':
channel = new TelegramNotificationChannel(config);
break;
case 'webhook':
channel = new WebhookNotificationChannel(config);
break;
default:
throw new Error(`Неподдерживаемый тип канала: ${config.type}`);
}
this.channels.set(config.name, channel);
console.log(`📤 Канал инициализирован: ${config.name} (${config.type})`);
});
}
/**
* Генерация сообщения о завершении пакета
* Generate batch completion message
*/
private generateBatchCompletionMessage(result: BatchProcessingResult): string {
const successRate = ((result.successCount / result.totalFiles) * 100).toFixed(1);
const processingTime = Math.round(result.totalProcessingTime / 1000);
let message = `Обработка пакета завершена:\n\n`;
message += `📄 Всего файлов: ${result.totalFiles}\n`;
message += `✅ Успешно: ${result.successCount}\n`;
message += `❌ Ошибок: ${result.errorCount}\n`;
message += `📈 Процент успеха: ${successRate}%\n`;
message += `⏱️ Время обработки: ${processingTime}с\n\n`;
if (result.errorCount > 0) {
message += `⚠️ Внимание: Обнаружены ошибки обработки. `;
message += `Рекомендуется просмотреть детальный отчет и повторить обработку проблемных файлов.`;
} else {
message += `🎉 Все файлы обработаны успешно!`;
}
return message;
}
/**
* Рассылка уведомления по всем каналам
* Broadcast notification across all channels
*/
private async broadcastNotification(
notification: BatchCompletionNotification
): Promise<void> {
const promises = [];
for (const [channelName, channel] of this.channels) {
if (channel.isEnabled()) {
console.log(` 📤 Отправка через ${channelName}...`);
promises.push(
channel.send(notification).catch(error => {
console.error(`❌ Ошибка отправки через ${channelName}:`, error);
})
);
}
}
await Promise.all(promises);
console.log(`✅ Уведомление отправлено через ${promises.length} каналов`);
}
}
/**
* Типы для системы уведомлений
*/
interface BatchCompletionNotification {
type: 'batch_completion';
executionId: string;
timestamp: string;
severity: 'success' | 'warning' | 'error';
title: string;
message: string;
data: {
totalFiles: number;
successCount: number;
errorCount: number;
processingTime: number;
successRate: number;
};
actions: NotificationAction[];
}
interface CriticalAlert {
type: 'critical_alert';
id: string;
timestamp: string;
severity: 'critical';
title: string;
message: string;
context?: AlertContext;
requiresImmediateAction: boolean;
escalationLevel: number;
}
interface NotificationAction {
label: string;
url: string;
style: 'primary' | 'secondary' | 'danger';
condition?: boolean;
}
interface AutoNotificationRule {
name: string;
description: string;
trigger: NotificationTrigger;
condition: NotificationCondition;
channels: string[];
recipients: NotificationRecipient[];
template: string;
enabled: boolean;
}
abstract class NotificationChannel {
constructor(protected config: ChannelConfig) {}
abstract send(notification: any): Promise<void>;
abstract isEnabled(): boolean;
}
interface WeeklySummaryNotification {
type: 'weekly_summary';
timestamp: string;
period: {
from: string;
to: string;
};
title: string;
message: string;
data: WeeklyData;
attachments: NotificationAttachment[];
}
📱 Каналы уведомлений
Email канал
/**
* Email канал уведомлений
* Email notification channel
*/
export class EmailNotificationChannel extends NotificationChannel {
private readonly transporter: any;
constructor(config: EmailChannelConfig) {
super(config);
this.transporter = this.initializeTransporter(config);
}
async send(notification: any): Promise<void> {
const emailData = this.formatEmailNotification(notification);
try {
await this.transporter.sendMail({
from: this.config.from,
to: emailData.recipients,
subject: emailData.subject,
html: emailData.htmlContent,
attachments: emailData.attachments
});
console.log(`✅ Email отправлен: ${emailData.subject}`);
} catch (error) {
console.error(`❌ Ошибка отправки email:`, error);
throw error;
}
}
isEnabled(): boolean {
return this.config.enabled && !!this.transporter;
}
private formatEmailNotification(notification: any): EmailData {
return {
recipients: this.getEmailRecipients(notification),
subject: this.formatEmailSubject(notification),
htmlContent: this.generateEmailHtml(notification),
attachments: notification.attachments || []
};
}
}
Slack канал
/**
* Slack канал уведомлений
* Slack notification channel
*/
export class SlackNotificationChannel extends NotificationChannel {
private readonly webhookUrl: string;
constructor(config: SlackChannelConfig) {
super(config);
this.webhookUrl = config.webhookUrl;
}
async send(notification: any): Promise<void> {
const slackMessage = this.formatSlackMessage(notification);
try {
const response = await fetch(this.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(slackMessage)
});
if (!response.ok) {
throw new Error(`Slack API error: ${response.statusText}`);
}
console.log(`✅ Slack уведомление отправлено`);
} catch (error) {
console.error(`❌ Ошибка отправки в Slack:`, error);
throw error;
}
}
isEnabled(): boolean {
return this.config.enabled && !!this.webhookUrl;
}
private formatSlackMessage(notification: any): SlackMessage {
const color = this.getSlackColor(notification.severity);
return {
text: notification.title,
attachments: [{
color,
title: notification.title,
text: notification.message,
fields: this.formatSlackFields(notification),
actions: this.formatSlackActions(notification.actions),
ts: Math.floor(Date.now() / 1000)
}]
};
}
}
Система отчетности и уведомлений для Supplier API готова! Теперь завершу документацию: