Finance API

API для работы с финансовыми отчетами и транзакциями в OZON Seller API.

Количество методов: 10 методов

Обзор

Finance API предоставляет полный набор инструментов для финансовой отчетности:

  • 💰 Отчеты о компенсациях и декомпенсациях
  • 📊 Отчеты о реализации товаров
  • 🏢 Отчеты по продажам юридическим лицам (B2B)
  • 💳 Списки транзакций и итоговые суммы
  • 📋 Взаиморасчеты с OZON
  • 📈 Отчеты о выкупленных товарах

Основные возможности

💰 Компенсационная система

  • Компенсации - выплаты продавцу за утерянные/поврежденные товары
  • Декомпенсации - возврат ранее выплаченных компенсаций
  • Автоматическое формирование отчетов по периодам

📊 Отчеты о реализации

  • Детализация по SKU и отправлениям
  • Группировка по периодам
  • Расчет комиссий и выплат
  • Версии v1 и v2 с расширенным функционалом

🏢 B2B отчетность

  • Специальные отчеты для продаж юридическим лицам
  • Форматы Excel и JSON
  • Информация о счетах и НДС

💳 Финансовые транзакции

  • Полная история всех финансовых операций
  • Фильтрация по типам и периодам
  • Агрегированные итоги по транзакциям

Методы API

Отчеты о компенсациях

createCompensationReport()

Назначение: Создать отчет о компенсациях

interface GetCompensationReportRequest {
  date: string; // Формат 'YYYY-MM'
  language: 'DEFAULT' | 'RU' | 'EN';
}

createDecompensationReport()

Назначение: Создать отчет о декомпенсациях

Отчеты о реализации

getRealizationReportPosting()

Назначение: Получить отчет о реализации с группировкой по отправлениям

interface GetRealizationReportPostingRequest {
  date: {
    from: string;
    to: string;
  };
}

getRealizationReportV2()

Назначение: Получить отчет о реализации (версия 2)

interface GetRealizationReportV2Request {
  month: string; // Формат 'YYYY-MM'
  language: 'DEFAULT' | 'RU' | 'EN';
}

B2B отчетность

createDocumentB2BSalesReport()

Назначение: Создать реестр продаж юридическим лицам (Excel)

createDocumentB2BSalesJSONReport()

Назначение: Создать реестр продаж юридическим лицам (JSON)

Финансовые операции

getTransactionList()

Назначение: Получить список транзакций (версия 3)

interface FinanceTransactionListV3Request {
  page: number;
  page_size: number;
  filter: {
    date?: {
      from: string;
      to: string;
    };
    operation_type?: string[];
    posting_number?: string;
  };
}

getTransactionTotals()

Назначение: Получить итоги по транзакциям

Дополнительные отчеты

getProductsBuyout()

Назначение: Получить отчет о выкупленных товарах

createMutualSettlementReport()

Назначение: Создать отчет о взаиморасчетах

Практические примеры

Базовое использование

import { OzonSellerAPI } from 'bmad-ozon-seller-api';

const api = new OzonSellerAPI({
  clientId: 'your-client-id',
  apiKey: 'your-api-key'
});

// Создать отчет о компенсациях за январь 2024
const compensationReport = await api.finance.createCompensationReport({
  date: '2024-01',
  language: 'RU'
});

// Получить список транзакций за период
const transactions = await api.finance.getTransactionList({
  page: 1,
  page_size: 100,
  filter: {
    date: {
      from: '2024-01-01',
      to: '2024-01-31'
    }
  }
});

// Получить отчет о реализации
const realizationReport = await api.finance.getRealizationReportV2({
  month: '2024-01',
  language: 'RU'
});

// Получить итоги по транзакциям
const totals = await api.finance.getTransactionTotals({
  filter: {
    date: {
      from: '2024-01-01',
      to: '2024-01-31'
    }
  }
});

Продвинутые сценарии

Система финансовой отчетности

class FinancialReportingSystem {
  constructor(private api: OzonSellerAPI) {}

  async generateMonthlyFinancialReport(month: string): Promise<void> {
    console.log(`📊 Создание финансового отчета за ${month}`);
    console.log('='.repeat(50));

    try {
      // 1. Получаем отчет о реализации
      const realizationReport = await this.getRealizationData(month);
      
      // 2. Получаем данные о транзакциях
      const transactionData = await this.getTransactionData(month);
      
      // 3. Получаем данные о компенсациях
      const compensationData = await this.getCompensationData(month);
      
      // 4. Получаем B2B данные
      const b2bData = await this.getB2BData(month);
      
      // 5. Создаем сводный отчет
      const summary = this.createFinancialSummary({
        realization: realizationReport,
        transactions: transactionData,
        compensations: compensationData,
        b2b: b2bData,
        month
      });
      
      // 6. Выводим результаты
      this.printFinancialSummary(summary);
      
      // 7. Сохраняем отчет
      await this.saveFinancialReport(summary, month);
      
    } catch (error) {
      console.error('❌ Ошибка создания финансового отчета:', error);
      throw error;
    }
  }

  private async getRealizationData(month: string): Promise<any> {
    console.log('📈 Получение данных о реализации...');
    
    const realizationReport = await this.api.finance.getRealizationReportV2({
      month,
      language: 'RU'
    });

    // Парсим данные отчета
    const realizationData = {
      totalSales: 0,
      totalCommission: 0,
      totalPayout: 0,
      itemsSold: 0,
      topProducts: []
    };

    if (realizationReport.result?.data) {
      const productStats = new Map();
      
      realizationReport.result.data.forEach((row: any) => {
        // Предполагаем структуру данных отчета
        const sales = parseFloat(row.sale_amount) || 0;
        const commission = parseFloat(row.commission_amount) || 0;
        const quantity = parseInt(row.quantity) || 0;
        const sku = row.sku || 'unknown';
        
        realizationData.totalSales += sales;
        realizationData.totalCommission += commission;
        realizationData.itemsSold += quantity;
        
        if (!productStats.has(sku)) {
          productStats.set(sku, {
            sku,
            name: row.product_name || 'Неизвестно',
            sales: 0,
            commission: 0,
            quantity: 0
          });
        }
        
        const productStat = productStats.get(sku);
        productStat.sales += sales;
        productStat.commission += commission;
        productStat.quantity += quantity;
      });
      
      realizationData.totalPayout = realizationData.totalSales - realizationData.totalCommission;
      
      // Топ-10 товаров по продажам
      realizationData.topProducts = Array.from(productStats.values())
        .sort((a, b) => b.sales - a.sales)
        .slice(0, 10);
    }

    console.log(`✅ Реализация: ${realizationData.totalSales.toFixed(2)} руб. (${realizationData.itemsSold} шт.)`);
    return realizationData;
  }

  private async getTransactionData(month: string): Promise<any> {
    console.log('💳 Получение данных о транзакциях...');
    
    const [year, monthNum] = month.split('-');
    const fromDate = `${year}-${monthNum}-01`;
    const toDate = new Date(parseInt(year), parseInt(monthNum), 0).toISOString().split('T')[0];
    
    // Получаем все транзакции постранично
    let allTransactions = [];
    let page = 1;
    let hasMore = true;
    
    while (hasMore) {
      const transactions = await this.api.finance.getTransactionList({
        page,
        page_size: 1000,
        filter: {
          date: { from: fromDate, to: toDate }
        }
      });
      
      if (transactions.result?.operations) {
        allTransactions.push(...transactions.result.operations);
        hasMore = transactions.result.operations.length === 1000;
        page++;
      } else {
        hasMore = false;
      }
      
      // Пауза между запросами
      await new Promise(resolve => setTimeout(resolve, 500));
    }

    // Анализируем транзакции
    const transactionData = {
      totalTransactions: allTransactions.length,
      totalIncome: 0,
      totalExpense: 0,
      netIncome: 0,
      typeStats: new Map(),
      dailyStats: new Map()
    };

    allTransactions.forEach((transaction: any) => {
      const amount = parseFloat(transaction.amount) || 0;
      const type = transaction.operation_type || 'unknown';
      const date = transaction.operation_date?.split('T')[0] || 'unknown';
      
      if (amount > 0) {
        transactionData.totalIncome += amount;
      } else {
        transactionData.totalExpense += Math.abs(amount);
      }
      
      // Статистика по типам
      if (!transactionData.typeStats.has(type)) {
        transactionData.typeStats.set(type, { count: 0, total: 0 });
      }
      const typeStat = transactionData.typeStats.get(type);
      typeStat.count++;
      typeStat.total += amount;
      
      // Ежедневная статистика
      if (!transactionData.dailyStats.has(date)) {
        transactionData.dailyStats.set(date, { income: 0, expense: 0 });
      }
      const dailyStat = transactionData.dailyStats.get(date);
      if (amount > 0) {
        dailyStat.income += amount;
      } else {
        dailyStat.expense += Math.abs(amount);
      }
    });

    transactionData.netIncome = transactionData.totalIncome - transactionData.totalExpense;
    
    console.log(`✅ Транзакции: ${allTransactions.length} операций, чистый доход ${transactionData.netIncome.toFixed(2)} руб.`);
    return transactionData;
  }

  private async getCompensationData(month: string): Promise<any> {
    console.log('💰 Получение данных о компенсациях...');
    
    try {
      // Создаем отчеты о компенсациях и декомпенсациях
      const [compensationResult, decompensationResult] = await Promise.all([
        this.api.finance.createCompensationReport({
          date: month,
          language: 'RU'
        }),
        this.api.finance.createDecompensationReport({
          date: month,
          language: 'RU'
        })
      ]);

      const compensationData = {
        compensations: {
          created: compensationResult.result?.code === 'SUCCESS',
          message: compensationResult.result?.message || 'Не удалось создать отчет'
        },
        decompensations: {
          created: decompensationResult.result?.code === 'SUCCESS',
          message: decompensationResult.result?.message || 'Не удалось создать отчет'
        }
      };

      console.log('✅ Отчеты о компенсациях созданы');
      return compensationData;
    } catch (error) {
      console.log('⚠️ Ошибка получения данных о компенсациях:', error.message);
      return { compensations: null, decompensations: null };
    }
  }

  private async getB2BData(month: string): Promise<any> {
    console.log('🏢 Получение B2B данных...');
    
    try {
      const [year, monthNum] = month.split('-');
      const fromDate = `${year}-${monthNum}-01`;
      const toDate = new Date(parseInt(year), parseInt(monthNum), 0).toISOString().split('T')[0];
      
      const b2bJsonReport = await this.api.finance.createDocumentB2BSalesJSONReport({
        date_from: fromDate,
        date_to: toDate
      });

      const b2bData = {
        invoices: b2bJsonReport.result?.invoices || [],
        totalAmount: b2bJsonReport.result?.total_amount || 0,
        invoiceCount: b2bJsonReport.result?.invoices?.length || 0
      };

      console.log(`✅ B2B: ${b2bData.invoiceCount} счетов на сумму ${b2bData.totalAmount.toFixed(2)} руб.`);
      return b2bData;
    } catch (error) {
      console.log('⚠️ B2B данные недоступны:', error.message);
      return { invoices: [], totalAmount: 0, invoiceCount: 0 };
    }
  }

  private createFinancialSummary(data: any): any {
    const summary = {
      month: data.month,
      generatedAt: new Date().toISOString(),
      realization: {
        totalSales: data.realization.totalSales,
        totalCommission: data.realization.totalCommission,
        totalPayout: data.realization.totalPayout,
        itemsSold: data.realization.itemsSold,
        averageOrderValue: data.realization.itemsSold > 0 ? 
          data.realization.totalSales / data.realization.itemsSold : 0,
        commissionRate: data.realization.totalSales > 0 ? 
          (data.realization.totalCommission / data.realization.totalSales) * 100 : 0
      },
      transactions: {
        totalCount: data.transactions.totalTransactions,
        totalIncome: data.transactions.totalIncome,
        totalExpense: data.transactions.totalExpense,
        netIncome: data.transactions.netIncome,
        profitMargin: data.realization.totalSales > 0 ? 
          (data.transactions.netIncome / data.realization.totalSales) * 100 : 0
      },
      b2b: {
        invoiceCount: data.b2b.invoiceCount,
        totalAmount: data.b2b.totalAmount,
        shareOfB2B: data.realization.totalSales > 0 ?
          (data.b2b.totalAmount / data.realization.totalSales) * 100 : 0
      },
      kpis: {
        roi: data.transactions.totalExpense > 0 ?
          (data.transactions.netIncome / data.transactions.totalExpense) * 100 : 0,
        revenuePerTransaction: data.transactions.totalCount > 0 ?
          data.realization.totalSales / data.transactions.totalCount : 0,
        averageCommissionPerItem: data.realization.itemsSold > 0 ?
          data.realization.totalCommission / data.realization.itemsSold : 0
      },
      topProducts: data.realization.topProducts
    };

    return summary;
  }

  private printFinancialSummary(summary: any): void {
    console.log('\n📊 ФИНАНСОВЫЙ ОТЧЕТ');
    console.log('='.repeat(60));
    console.log(`📅 Период: ${summary.month}`);
    console.log(`🕐 Создан: ${new Date(summary.generatedAt).toLocaleString()}`);

    console.log('\n💰 РЕАЛИЗАЦИЯ:');
    console.log(`Общая сумма продаж: ${summary.realization.totalSales.toFixed(2)} руб.`);
    console.log(`Комиссия OZON: ${summary.realization.totalCommission.toFixed(2)} руб. (${summary.realization.commissionRate.toFixed(1)}%)`);
    console.log(`К выплате: ${summary.realization.totalPayout.toFixed(2)} руб.`);
    console.log(`Продано товаров: ${summary.realization.itemsSold} шт.`);
    console.log(`Средний чек: ${summary.realization.averageOrderValue.toFixed(2)} руб.`);

    console.log('\n💳 ТРАНЗАКЦИИ:');
    console.log(`Всего операций: ${summary.transactions.totalCount}`);
    console.log(`Доходы: ${summary.transactions.totalIncome.toFixed(2)} руб.`);
    console.log(`Расходы: ${summary.transactions.totalExpense.toFixed(2)} руб.`);
    console.log(`Чистый доход: ${summary.transactions.netIncome.toFixed(2)} руб.`);
    console.log(`Маржа прибыли: ${summary.transactions.profitMargin.toFixed(1)}%`);

    console.log('\n🏢 B2B ПРОДАЖИ:');
    console.log(`Количество счетов: ${summary.b2b.invoiceCount}`);
    console.log(`Сумма B2B: ${summary.b2b.totalAmount.toFixed(2)} руб.`);
    console.log(`Доля B2B: ${summary.b2b.shareOfB2B.toFixed(1)}%`);

    console.log('\n📈 КЛЮЧЕВЫЕ ПОКАЗАТЕЛИ:');
    console.log(`ROI: ${summary.kpis.roi.toFixed(1)}%`);
    console.log(`Выручка на операцию: ${summary.kpis.revenuePerTransaction.toFixed(2)} руб.`);
    console.log(`Средняя комиссия за товар: ${summary.kpis.averageCommissionPerItem.toFixed(2)} руб.`);

    if (summary.topProducts.length > 0) {
      console.log('\n🏆 ТОП-5 ТОВАРОВ ПО ПРОДАЖАМ:');
      summary.topProducts.slice(0, 5).forEach((product: any, index: number) => {
        console.log(`${index + 1}. ${product.name} (${product.sku})`);
        console.log(`   Продано: ${product.quantity} шт. на ${product.sales.toFixed(2)} руб.`);
      });
    }
  }

  private async saveFinancialReport(summary: any, month: string): Promise<void> {
    // В реальном приложении здесь была бы логика сохранения отчета
    const fileName = `financial_report_${month}.json`;
    console.log(`\n💾 Отчет сохранен: ${fileName}`);
    
    // Пример сохранения:
    // await fs.writeFile(fileName, JSON.stringify(summary, null, 2));
  }
}

Система мониторинга транзакций

class TransactionMonitoringSystem {
  constructor(private api: OzonSellerAPI) {}

  async monitorDailyTransactions(): Promise<void> {
    console.log('🔍 Мониторинг транзакций за сегодня');

    const today = new Date().toISOString().split('T')[0];
    
    try {
      // Получаем транзакции за сегодня
      const todayTransactions = await this.api.finance.getTransactionList({
        page: 1,
        page_size: 1000,
        filter: {
          date: {
            from: today,
            to: today
          }
        }
      });

      if (!todayTransactions.result?.operations || todayTransactions.result.operations.length === 0) {
        console.log('ℹ️ Транзакций за сегодня не найдено');
        return;
      }

      // Анализируем транзакции
      const analysis = this.analyzeTransactions(todayTransactions.result.operations);
      
      // Проверяем аномалии
      const anomalies = await this.detectAnomalies(analysis);
      
      // Создаем отчет
      this.generateDailyTransactionReport(analysis, anomalies);
      
      // Отправляем уведомления при необходимости
      if (anomalies.length > 0) {
        await this.sendAnomalyAlerts(anomalies);
      }

    } catch (error) {
      console.error('❌ Ошибка мониторинга транзакций:', error);
    }
  }

  private analyzeTransactions(transactions: any[]): any {
    const analysis = {
      totalCount: transactions.length,
      income: { count: 0, total: 0, transactions: [] },
      expense: { count: 0, total: 0, transactions: [] },
      typeBreakdown: new Map(),
      hourlyDistribution: new Map(),
      averageAmount: 0,
      largestTransaction: null,
      suspiciousTransactions: []
    };

    let totalAmount = 0;

    transactions.forEach(transaction => {
      const amount = parseFloat(transaction.amount) || 0;
      const type = transaction.operation_type || 'unknown';
      const time = new Date(transaction.operation_date).getHours();
      
      totalAmount += Math.abs(amount);

      // Доходы и расходы
      if (amount > 0) {
        analysis.income.count++;
        analysis.income.total += amount;
        analysis.income.transactions.push(transaction);
      } else {
        analysis.expense.count++;
        analysis.expense.total += Math.abs(amount);
        analysis.expense.transactions.push(transaction);
      }

      // Разбивка по типам
      if (!analysis.typeBreakdown.has(type)) {
        analysis.typeBreakdown.set(type, { count: 0, total: 0 });
      }
      const typeData = analysis.typeBreakdown.get(type);
      typeData.count++;
      typeData.total += Math.abs(amount);

      // Почасовое распределение
      if (!analysis.hourlyDistribution.has(time)) {
        analysis.hourlyDistribution.set(time, 0);
      }
      analysis.hourlyDistribution.set(time, analysis.hourlyDistribution.get(time) + 1);

      // Самая крупная транзакция
      if (!analysis.largestTransaction || Math.abs(amount) > Math.abs(parseFloat(analysis.largestTransaction.amount))) {
        analysis.largestTransaction = transaction;
      }

      // Подозрительные транзакции (очень крупные)
      if (Math.abs(amount) > 50000) {
        analysis.suspiciousTransactions.push({
          ...transaction,
          reason: 'Large amount'
        });
      }
    });

    analysis.averageAmount = analysis.totalCount > 0 ? totalAmount / analysis.totalCount : 0;
    
    return analysis;
  }

  private async detectAnomalies(analysis: any): Promise<any[]> {
    const anomalies = [];

    // Аномалия 1: Слишком много расходных транзакций
    const expenseRatio = analysis.totalCount > 0 ? analysis.expense.count / analysis.totalCount : 0;
    if (expenseRatio > 0.8) {
      anomalies.push({
        type: 'high_expense_ratio',
        severity: 'medium',
        message: `Высокий процент расходных операций: ${(expenseRatio * 100).toFixed(1)}%`,
        value: expenseRatio
      });
    }

    // Аномалия 2: Очень крупная транзакция
    if (analysis.largestTransaction && Math.abs(parseFloat(analysis.largestTransaction.amount)) > 100000) {
      anomalies.push({
        type: 'large_transaction',
        severity: 'high',
        message: `Обнаружена крупная транзакция: ${analysis.largestTransaction.amount} руб.`,
        transaction: analysis.largestTransaction
      });
    }

    // Аномалия 3: Подозрительно малое количество транзакций
    const currentHour = new Date().getHours();
    if (currentHour > 10 && analysis.totalCount < 5) {
      anomalies.push({
        type: 'low_transaction_count',
        severity: 'low',
        message: `Мало транзакций для времени ${currentHour}:00 - только ${analysis.totalCount}`,
        value: analysis.totalCount
      });
    }

    // Аномалия 4: Нетипичное время активности
    const nightTransactions = Array.from(analysis.hourlyDistribution.entries())
      .filter(([hour, count]) => hour >= 23 || hour <= 5)
      .reduce((sum, [, count]) => sum + count, 0);
      
    if (nightTransactions > analysis.totalCount * 0.3) {
      anomalies.push({
        type: 'unusual_timing',
        severity: 'medium',
        message: `Много ночных транзакций: ${nightTransactions} из ${analysis.totalCount}`,
        value: nightTransactions
      });
    }

    return anomalies;
  }

  private generateDailyTransactionReport(analysis: any, anomalies: any[]): void {
    console.log('\n📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ ПО ТРАНЗАКЦИЯМ');
    console.log('='.repeat(50));
    console.log(`📅 Дата: ${new Date().toLocaleDateString()}`);

    console.log('\n💰 ОБЩАЯ СТАТИСТИКА:');
    console.log(`Всего операций: ${analysis.totalCount}`);
    console.log(`Доходных операций: ${analysis.income.count} (${analysis.income.total.toFixed(2)} руб.)`);
    console.log(`Расходных операций: ${analysis.expense.count} (${analysis.expense.total.toFixed(2)} руб.)`);
    console.log(`Средняя сумма операции: ${analysis.averageAmount.toFixed(2)} руб.`);
    console.log(`Чистый результат: ${(analysis.income.total - analysis.expense.total).toFixed(2)} руб.`);

    console.log('\n📊 РАЗБИВКА ПО ТИПАМ ОПЕРАЦИЙ:');
    Array.from(analysis.typeBreakdown.entries())
      .sort(([,a], [,b]) => b.total - a.total)
      .slice(0, 5)
      .forEach(([type, data]) => {
        console.log(`${type}: ${data.count} операций, ${data.total.toFixed(2)} руб.`);
      });

    if (analysis.largestTransaction) {
      console.log('\n💎 САМАЯ КРУПНАЯ ТРАНЗАКЦИЯ:');
      console.log(`Сумма: ${analysis.largestTransaction.amount} руб.`);
      console.log(`Тип: ${analysis.largestTransaction.operation_type}`);
      console.log(`Время: ${new Date(analysis.largestTransaction.operation_date).toLocaleString()}`);
    }

    if (anomalies.length > 0) {
      console.log('\n⚠️ ОБНАРУЖЕННЫЕ АНОМАЛИИ:');
      anomalies.forEach(anomaly => {
        const emoji = anomaly.severity === 'high' ? '🚨' : 
                     anomaly.severity === 'medium' ? '⚠️' : 'ℹ️';
        console.log(`${emoji} ${anomaly.message}`);
      });
    } else {
      console.log('\n✅ Аномалий не обнаружено');
    }

    // Почасовое распределение
    if (analysis.hourlyDistribution.size > 0) {
      console.log('\n🕐 АКТИВНОСТЬ ПО ЧАСАМ:');
      const sortedHours = Array.from(analysis.hourlyDistribution.entries())
        .sort(([,a], [,b]) => b - a)
        .slice(0, 5);
        
      sortedHours.forEach(([hour, count]) => {
        console.log(`${hour}:00 - ${count} операций`);
      });
    }
  }

  private async sendAnomalyAlerts(anomalies: any[]): Promise<void> {
    const highSeverityAnomalies = anomalies.filter(a => a.severity === 'high');
    
    if (highSeverityAnomalies.length > 0) {
      console.log('\n🚨 КРИТИЧЕСКИЕ АНОМАЛИИ - ТРЕБУЕТСЯ ВНИМАНИЕ!');
      highSeverityAnomalies.forEach(anomaly => {
        console.log(`🚨 ${anomaly.message}`);
      });
      
      // Здесь была бы логика отправки уведомлений:
      // - Email уведомления
      // - Push уведомления
      // - Slack/Telegram боты
      // - SMS для критических случаев
    }
  }

  async generateWeeklyFinancialSummary(): Promise<void> {
    console.log('📈 Еженедельная финансовая сводка');

    const weekAgo = new Date();
    weekAgo.setDate(weekAgo.getDate() - 7);
    
    const fromDate = weekAgo.toISOString().split('T')[0];
    const toDate = new Date().toISOString().split('T')[0];

    try {
      // Получаем итоги по транзакциям за неделю
      const weeklyTotals = await this.api.finance.getTransactionTotals({
        filter: {
          date: { from: fromDate, to: toDate }
        }
      });

      // Получаем детализированный список для анализа
      let allWeeklyTransactions = [];
      let page = 1;
      let hasMore = true;

      while (hasMore) {
        const transactions = await this.api.finance.getTransactionList({
          page,
          page_size: 1000,
          filter: {
            date: { from: fromDate, to: toDate }
          }
        });

        if (transactions.result?.operations) {
          allWeeklyTransactions.push(...transactions.result.operations);
          hasMore = transactions.result.operations.length === 1000;
          page++;
        } else {
          hasMore = false;
        }
      }

      // Анализируем тренды
      const weeklyAnalysis = this.analyzeWeeklyTrends(allWeeklyTransactions);
      
      // Создаем сводку
      this.printWeeklySummary(weeklyTotals.result, weeklyAnalysis, fromDate, toDate);

    } catch (error) {
      console.error('❌ Ошибка создания еженедельной сводки:', error);
    }
  }

  private analyzeWeeklyTrends(transactions: any[]): any {
    const dailyStats = new Map();
    const typeGrowth = new Map();

    transactions.forEach(transaction => {
      const date = transaction.operation_date.split('T')[0];
      const amount = parseFloat(transaction.amount) || 0;
      const type = transaction.operation_type;

      // Ежедневная статистика
      if (!dailyStats.has(date)) {
        dailyStats.set(date, { income: 0, expense: 0, count: 0 });
      }
      const dayStat = dailyStats.get(date);
      dayStat.count++;
      if (amount > 0) {
        dayStat.income += amount;
      } else {
        dayStat.expense += Math.abs(amount);
      }

      // Статистика по типам
      if (!typeGrowth.has(type)) {
        typeGrowth.set(type, []);
      }
      typeGrowth.get(type).push({ date, amount: Math.abs(amount) });
    });

    // Вычисляем тренды
    const dailyTotals = Array.from(dailyStats.entries())
      .map(([date, stats]) => ({
        date,
        total: stats.income - stats.expense,
        income: stats.income,
        expense: stats.expense,
        count: stats.count
      }))
      .sort((a, b) => a.date.localeCompare(b.date));

    const trend = this.calculateTrend(dailyTotals.map(d => d.total));

    return {
      dailyTotals,
      trend,
      bestDay: dailyTotals.reduce((best, current) => 
        current.total > best.total ? current : best, dailyTotals[0]),
      worstDay: dailyTotals.reduce((worst, current) => 
        current.total < worst.total ? current : worst, dailyTotals[0])
    };
  }

  private calculateTrend(values: number[]): string {
    if (values.length < 2) return 'недостаточно данных';
    
    const firstHalf = values.slice(0, Math.floor(values.length / 2));
    const secondHalf = values.slice(Math.floor(values.length / 2));
    
    const firstAvg = firstHalf.reduce((sum, v) => sum + v, 0) / firstHalf.length;
    const secondAvg = secondHalf.reduce((sum, v) => sum + v, 0) / secondHalf.length;
    
    const change = ((secondAvg - firstAvg) / Math.abs(firstAvg)) * 100;
    
    if (change > 10) return '📈 растущий';
    if (change < -10) return '📉 падающий';
    return '➡️ стабильный';
  }

  private printWeeklySummary(totals: any, analysis: any, fromDate: string, toDate: string): void {
    console.log('\n📈 ЕЖЕНЕДЕЛЬНАЯ ФИНАНСОВАЯ СВОДКА');
    console.log('='.repeat(50));
    console.log(`📅 Период: ${fromDate} - ${toDate}`);

    if (totals) {
      console.log('\n💰 ИТОГИ ЗА НЕДЕЛЮ:');
      console.log(`Общая сумма доходов: ${totals.income?.toFixed(2) || 0} руб.`);
      console.log(`Общая сумма расходов: ${totals.expense?.toFixed(2) || 0} руб.`);
      console.log(`Чистый результат: ${(totals.income - totals.expense).toFixed(2)} руб.`);
      console.log(`Количество операций: ${totals.operation_count || 0}`);
    }

    if (analysis.trend) {
      console.log(`\n📊 ТРЕНД: ${analysis.trend}`);
    }

    if (analysis.bestDay && analysis.worstDay) {
      console.log('\n🏆 ЛУЧШИЙ ДЕНЬ:');
      console.log(`${analysis.bestDay.date}: ${analysis.bestDay.total.toFixed(2)} руб. (${analysis.bestDay.count} операций)`);
      
      console.log('\n📉 ХУДШИЙ ДЕНЬ:');
      console.log(`${analysis.worstDay.date}: ${analysis.worstDay.total.toFixed(2)} руб. (${analysis.worstDay.count} операций)`);
    }

    console.log('\n📊 ЕЖЕДНЕВНАЯ ДИНАМИКА:');
    analysis.dailyTotals.forEach((day: any) => {
      const emoji = day.total > 0 ? '📈' : day.total < 0 ? '📉' : '➡️';
      console.log(`${emoji} ${day.date}: ${day.total.toFixed(2)} руб. (${day.count} оп.)`);
    });
  }
}

Обработка ошибок

try {
  await api.finance.getTransactionList({
    page: 1,
    page_size: 100,
    filter: { /* ... */ }
  });
} catch (error) {
  if (error.response?.status === 400) {
    console.error('Ошибка валидации параметров запроса:', error.response.data);
  } else if (error.response?.status === 403) {
    console.error('Нет доступа к финансовым данным');
  } else if (error.response?.status === 429) {
    console.error('Превышен лимит запросов к Finance API');
  } else {
    console.error('Неожиданная ошибка:', error.message);
  }
}

Рекомендации по использованию

📊 Планирование отчетности

  • Создавайте месячные отчеты в первые дни следующего месяца
  • Настройте автоматическое создание регулярных отчетов
  • Сохраняйте исторические данные для анализа трендов
  • Используйте разные языки отчетов для международной отчетности

💰 Мониторинг финансов

  • Настройте ежедневный мониторинг транзакций
  • Отслеживайте аномалии в финансовых операциях
  • Анализируйте тренды доходов и расходов
  • Контролируйте процент комиссий от оборота

🏢 B2B отчетность

  • Отдельно отслеживайте продажи юридическим лицам
  • Используйте JSON формат для автоматической обработки
  • Контролируйте НДС и документооборот
  • Интегрируйте с учетными системами предприятия

📈 Аналитика и KPI

  • Рассчитывайте ROI по периодам
  • Анализируйте рентабельность по товарным группам
  • Отслеживайте динамику среднего чека
  • Мониторьте соотношение доходов к расходам

🚀 Автоматизация процессов

  • Автоматизируйте создание ежемесячных отчетов
  • Настройте уведомления о критических финансовых событиях
  • Интегрируйте с системами бухгалтерского учета
  • Используйте webhook для получения уведомлений о новых транзакциях

Finance API предоставляет полную картину финансовых операций на OZON, позволяя эффективно управлять денежными потоками и принимать обоснованные бизнес-решения.