Returns API

Returns API для управления возвратами FBO и FBS с 1 методом для получения информации о возвратах.

Обзор

Returns API предоставляет базовый интерфейс для получения информации о возвратах товаров как по схеме FBO (со склада Ozon), так и по схеме FBS (со склада продавца).

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

  • 📋 Получение списка возвратов с фильтрацией
  • 🔍 Поиск возвратов по статусам и датам
  • 📊 Пагинация для больших объемов данных
  • 📈 Аналитика возвратов по различным критериям
  • ⚙️ Интеграция с системами учета и аналитики

Доступные методы

getList(request) - Получить список возвратов FBO и FBS

const returns = await returnsApi.getList({
  filter: {
    status: ['NEW', 'PROCESSING'],
    created_at_from: '2024-01-01T00:00:00Z'
  },
  limit: 100
});

TypeScript интерфейсы

// Основные запросы
interface GetReturnsListRequest {
  filter?: {
    created_at_from?: string;
    created_at_to?: string;
    updated_at_from?: string;
    updated_at_to?: string;
    status?: Array<"NEW" | "PROCESSING" | "COMPLETED" | "CANCELLED" | "RETURNED_TO_SELLER" | "DISPOSED">;
    posting_number?: string[];
    return_id?: number[];
    product_id?: number[];
    sku?: number[];
    return_reason_id?: number[];
  };
  limit?: number;
  last_id?: string;
  sort?: "created_at" | "updated_at" | "return_date";
  sort_dir?: "asc" | "desc";
}

// Ответы
interface GetReturnsListResponse {
  returns: Array<{
    id: number;
    posting_number: string;
    name: string;
    sku: number;
    product_id: number;
    quantity: number;
    price: string;
    currency_code: string;
    status: "NEW" | "PROCESSING" | "COMPLETED" | "CANCELLED" | "RETURNED_TO_SELLER" | "DISPOSED";
    return_reason_id: number;
    return_reason_name: string;
    return_date: string;
    created_at: string;
    updated_at: string;
    delivery_schema: "FBO" | "FBS";
    commission_amount: string;
    commission_percent: string;
    refund_amount: string;
    pickup_amount: string;
    return_clearing_id?: number;
    customer_info: {
      customer_id: string;
      customer_name: string;
      return_comment?: string;
    };
    logistics_info: {
      logistics_status: string;
      tracking_number?: string;
      pickup_date?: string;
      delivery_date?: string;
      warehouse_id?: number;
      warehouse_name?: string;
    };
    financial_info: {
      return_cost: string;
      logistics_cost: string;
      total_refund: string;
      seller_compensation: string;
    };
  }>;
  total: number;
  has_next: boolean;
  last_id?: string;
}

Примеры использования

Получение списка возвратов с фильтрацией

// Получение новых возвратов за текущий месяц
const currentMonth = new Date();
const firstDayOfMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);

const newReturns = await returnsApi.getList({
  filter: {
    created_at_from: firstDayOfMonth.toISOString(),
    created_at_to: new Date().toISOString(),
    status: ['NEW', 'PROCESSING']
  },
  limit: 100,
  sort: 'created_at',
  sort_dir: 'desc'
});

console.log(`\n=== Новые возвраты за текущий месяц: ${newReturns.returns.length} ===`);

// Группировка по статусам
const statusGroups = newReturns.returns.reduce((groups, returnItem) => {
  const status = returnItem.status;
  if (!groups[status]) {
    groups[status] = [];
  }
  groups[status].push(returnItem);
  return groups;
}, {} as Record<string, any[]>);

Object.entries(statusGroups).forEach(([status, returns]) => {
  const totalValue = returns.reduce((sum, ret) => sum + parseFloat(ret.refund_amount), 0);
  console.log(`${status}: ${returns.length} возвратов на сумму ${totalValue.toFixed(2)} руб`);
});

// Детализация по каждому возврату
newReturns.returns.slice(0, 10).forEach(returnItem => {
  console.log(`\n📦 Возврат ${returnItem.id} (${returnItem.posting_number})`);
  console.log(`   Товар: ${returnItem.name} (SKU: ${returnItem.sku})`);
  console.log(`   Количество: ${returnItem.quantity}`);
  console.log(`   Статус: ${returnItem.status}`);
  console.log(`   Причина: ${returnItem.return_reason_name}`);
  console.log(`   Схема: ${returnItem.delivery_schema}`);
  console.log(`   Сумма возврата: ${returnItem.refund_amount} ${returnItem.currency_code}`);
  console.log(`   Дата возврата: ${returnItem.return_date}`);
  
  if (returnItem.customer_info.return_comment) {
    console.log(`   Комментарий клиента: "${returnItem.customer_info.return_comment}"`);
  }
  
  if (returnItem.logistics_info.tracking_number) {
    console.log(`   Трек-номер: ${returnItem.logistics_info.tracking_number}`);
  }
});

Аналитика возвратов по причинам

// Получение всех возвратов за последние 3 месяца
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);

let allReturns: any[] = [];
let lastId: string | undefined;

do {
  const response = await returnsApi.getList({
    filter: {
      created_at_from: threeMonthsAgo.toISOString(),
      created_at_to: new Date().toISOString()
    },
    limit: 1000,
    last_id: lastId
  });

  allReturns.push(...response.returns);
  lastId = response.has_next ? response.last_id : undefined;

  console.log(`Загружено возвратов: ${allReturns.length} из ${response.total}`);

} while (lastId);

console.log(`\n=== Анализ ${allReturns.length} возвратов за 3 месяца ===`);

// Анализ по причинам возврата
const reasonAnalysis = new Map<string, {
  count: number;
  total_amount: number;
  products: Set<number>;
  fbo_count: number;
  fbs_count: number;
}>();

allReturns.forEach(returnItem => {
  const reason = returnItem.return_reason_name;
  const amount = parseFloat(returnItem.refund_amount);
  
  if (!reasonAnalysis.has(reason)) {
    reasonAnalysis.set(reason, {
      count: 0,
      total_amount: 0,
      products: new Set(),
      fbo_count: 0,
      fbs_count: 0
    });
  }
  
  const analysis = reasonAnalysis.get(reason)!;
  analysis.count++;
  analysis.total_amount += amount;
  analysis.products.add(returnItem.product_id);
  
  if (returnItem.delivery_schema === 'FBO') {
    analysis.fbo_count++;
  } else {
    analysis.fbs_count++;
  }
});

// Сортировка причин по количеству возвратов
const sortedReasons = Array.from(reasonAnalysis.entries())
  .sort((a, b) => b[1].count - a[1].count);

console.log(`\n=== Топ-10 причин возвратов ===`);
sortedReasons.slice(0, 10).forEach(([reason, analysis], index) => {
  const avgAmount = analysis.total_amount / analysis.count;
  const percentage = (analysis.count / allReturns.length * 100);
  
  console.log(`\n${index + 1}. ${reason}:`);
  console.log(`   Количество: ${analysis.count} (${percentage.toFixed(1)}%)`);
  console.log(`   Общая сумма: ${analysis.total_amount.toFixed(2)} руб`);
  console.log(`   Средняя сумма: ${avgAmount.toFixed(2)} руб`);
  console.log(`   Уникальных товаров: ${analysis.products.size}`);
  console.log(`   FBO: ${analysis.fbo_count}, FBS: ${analysis.fbs_count}`);
});

// Анализ по схемам доставки
const schemaAnalysis = allReturns.reduce((acc, returnItem) => {
  const schema = returnItem.delivery_schema;
  if (!acc[schema]) {
    acc[schema] = { count: 0, amount: 0 };
  }
  acc[schema].count++;
  acc[schema].amount += parseFloat(returnItem.refund_amount);
  return acc;
}, {} as Record<string, { count: number; amount: number }>);

console.log(`\n=== Анализ по схемам доставки ===`);
Object.entries(schemaAnalysis).forEach(([schema, data]) => {
  const percentage = (data.count / allReturns.length * 100);
  const avgAmount = data.amount / data.count;
  
  console.log(`${schema}:`);
  console.log(`  Возвратов: ${data.count} (${percentage.toFixed(1)}%)`);
  console.log(`  Общая сумма: ${data.amount.toFixed(2)} руб`);
  console.log(`  Средняя сумма: ${avgAmount.toFixed(2)} руб`);
});

Мониторинг проблемных товаров

// Анализ товаров с высоким уровнем возвратов
const productReturns = new Map<number, {
  sku: number;
  name: string;
  return_count: number;
  total_quantity_returned: number;
  total_refund_amount: number;
  reasons: Map<string, number>;
  avg_return_value: number;
}>();

allReturns.forEach(returnItem => {
  const productId = returnItem.product_id;
  
  if (!productReturns.has(productId)) {
    productReturns.set(productId, {
      sku: returnItem.sku,
      name: returnItem.name,
      return_count: 0,
      total_quantity_returned: 0,
      total_refund_amount: 0,
      reasons: new Map(),
      avg_return_value: 0
    });
  }
  
  const product = productReturns.get(productId)!;
  product.return_count++;
  product.total_quantity_returned += returnItem.quantity;
  product.total_refund_amount += parseFloat(returnItem.refund_amount);
  
  const reason = returnItem.return_reason_name;
  product.reasons.set(reason, (product.reasons.get(reason) || 0) + 1);
});

// Рассчет средней стоимости возврата для каждого товара
productReturns.forEach(product => {
  product.avg_return_value = product.total_refund_amount / product.return_count;
});

// Сортировка товаров по количеству возвратов
const problematicProducts = Array.from(productReturns.entries())
  .sort((a, b) => b[1].return_count - a[1].return_count)
  .slice(0, 20);

console.log(`\n=== Топ-20 товаров по возвратам ===`);
problematicProducts.forEach(([productId, product], index) => {
  const mainReason = Array.from(product.reasons.entries())
    .sort((a, b) => b[1] - a[1])[0];
  
  console.log(`\n${index + 1}. ${product.name} (ID: ${productId}, SKU: ${product.sku})`);
  console.log(`   Возвратов: ${product.return_count}`);
  console.log(`   Количество: ${product.total_quantity_returned} шт`);
  console.log(`   Сумма возвратов: ${product.total_refund_amount.toFixed(2)} руб`);
  console.log(`   Средняя стоимость: ${product.avg_return_value.toFixed(2)} руб`);
  console.log(`   Главная причина: ${mainReason[0]} (${mainReason[1]} случаев)`);
  
  // Показываем все причины для товаров с большим количеством возвратов
  if (product.return_count > 10) {
    console.log(`   Все причины:`);
    Array.from(product.reasons.entries())
      .sort((a, b) => b[1] - a[1])
      .forEach(([reason, count]) => {
        const percentage = (count / product.return_count * 100);
        console.log(`     ${reason}: ${count} (${percentage.toFixed(1)}%)`);
      });
  }
});

Временной анализ и тренды

// Анализ возвратов по неделям
const weeklyAnalysis = new Map<string, {
  count: number;
  amount: number;
  new_count: number;
  processing_count: number;
  completed_count: number;
}>();

allReturns.forEach(returnItem => {
  const returnDate = new Date(returnItem.created_at);
  const weekStart = new Date(returnDate);
  weekStart.setDate(returnDate.getDate() - returnDate.getDay()); // начало недели
  const weekKey = weekStart.toISOString().split('T')[0];
  
  if (!weeklyAnalysis.has(weekKey)) {
    weeklyAnalysis.set(weekKey, {
      count: 0,
      amount: 0,
      new_count: 0,
      processing_count: 0,
      completed_count: 0
    });
  }
  
  const week = weeklyAnalysis.get(weekKey)!;
  week.count++;
  week.amount += parseFloat(returnItem.refund_amount);
  
  switch (returnItem.status) {
    case 'NEW':
      week.new_count++;
      break;
    case 'PROCESSING':
      week.processing_count++;
      break;
    case 'COMPLETED':
      week.completed_count++;
      break;
  }
});

// Сортировка по неделям
const sortedWeeks = Array.from(weeklyAnalysis.entries())
  .sort((a, b) => a[0].localeCompare(b[0]));

console.log(`\n=== Анализ возвратов по неделям ===`);
sortedWeeks.forEach(([week, data]) => {
  const weekDate = new Date(week).toLocaleDateString('ru-RU');
  const avgAmount = data.amount / data.count;
  
  console.log(`\nНеделя с ${weekDate}:`);
  console.log(`  Всего возвратов: ${data.count}`);
  console.log(`  Общая сумма: ${data.amount.toFixed(2)} руб`);
  console.log(`  Средняя сумма: ${avgAmount.toFixed(2)} руб`);
  console.log(`  По статусам: NEW=${data.new_count}, PROCESSING=${data.processing_count}, COMPLETED=${data.completed_count}`);
});

// Расчет трендов
if (sortedWeeks.length >= 4) {
  const lastFourWeeks = sortedWeeks.slice(-4);
  const firstTwoWeeks = lastFourWeeks.slice(0, 2);
  const lastTwoWeeks = lastFourWeeks.slice(2);
  
  const firstHalfAvg = firstTwoWeeks.reduce((sum, [, data]) => sum + data.count, 0) / 2;
  const secondHalfAvg = lastTwoWeeks.reduce((sum, [, data]) => sum + data.count, 0) / 2;
  
  const trend = ((secondHalfAvg - firstHalfAvg) / firstHalfAvg * 100);
  
  console.log(`\n=== Тренд за последние 4 недели ===`);
  if (trend > 10) {
    console.log(`📈 Рост возвратов: +${trend.toFixed(1)}% - требует внимания`);
  } else if (trend < -10) {
    console.log(`📉 Снижение возвратов: ${trend.toFixed(1)}%`);
  } else {
    console.log(`➡️ Стабильный уровень: ${trend.toFixed(1)}%`);
  }
}

Сложные сценарии

ReturnAnalyticsEngine - Система аналитики возвратов

class ReturnAnalyticsEngine {
  constructor(private api: ReturnsApi) {}

  async generateComprehensiveReport(period: AnalysisPeriod): Promise<ReturnAnalyticsReport> {
    console.log(`📊 Генерация комплексного отчета по возвратам за период: ${period.from} - ${period.to}`);

    // Получение всех возвратов за период
    const allReturns = await this.getAllReturnsForPeriod(period);
    
    // Базовая статистика
    const basicStats = this.calculateBasicStatistics(allReturns);
    
    // Анализ по причинам
    const reasonAnalysis = this.analyzeReturnReasons(allReturns);
    
    // Анализ по товарам
    const productAnalysis = this.analyzeProductReturns(allReturns);
    
    // Временной анализ
    const temporalAnalysis = this.analyzeTemporalPatterns(allReturns);
    
    // Финансовый анализ
    const financialAnalysis = this.analyzeFinancialImpact(allReturns);
    
    // Анализ логистики
    const logisticsAnalysis = this.analyzeLogisticsPerformance(allReturns);
    
    // Генерация рекомендаций
    const recommendations = this.generateRecommendations(
      basicStats, reasonAnalysis, productAnalysis, temporalAnalysis
    );

    return {
      period,
      generated_at: new Date().toISOString(),
      basic_statistics: basicStats,
      reason_analysis: reasonAnalysis,
      product_analysis: productAnalysis,
      temporal_analysis: temporalAnalysis,
      financial_analysis: financialAnalysis,
      logistics_analysis: logisticsAnalysis,
      recommendations
    };
  }

  private async getAllReturnsForPeriod(period: AnalysisPeriod): Promise<any[]> {
    const returns: any[] = [];
    let lastId: string | undefined;

    do {
      const response = await this.api.getList({
        filter: {
          created_at_from: period.from,
          created_at_to: period.to
        },
        limit: 1000,
        last_id: lastId
      });

      returns.push(...response.returns);
      lastId = response.has_next ? response.last_id : undefined;

      console.log(`📥 Загружено возвратов: ${returns.length}`);

    } while (lastId);

    return returns;
  }

  private calculateBasicStatistics(returns: any[]): BasicReturnStatistics {
    const totalReturns = returns.length;
    const totalRefundAmount = returns.reduce((sum, ret) => sum + parseFloat(ret.refund_amount), 0);
    const avgRefundAmount = totalRefundAmount / totalReturns;

    const statusCounts = returns.reduce((counts, ret) => {
      counts[ret.status] = (counts[ret.status] || 0) + 1;
      return counts;
    }, {});

    const schemaCounts = returns.reduce((counts, ret) => {
      counts[ret.delivery_schema] = (counts[ret.delivery_schema] || 0) + 1;
      return counts;
    }, {});

    const uniqueProducts = new Set(returns.map(ret => ret.product_id)).size;
    const uniqueCustomers = new Set(returns.map(ret => ret.customer_info.customer_id)).size;

    return {
      total_returns: totalReturns,
      total_refund_amount: totalRefundAmount,
      average_refund_amount: avgRefundAmount,
      status_breakdown: statusCounts,
      schema_breakdown: schemaCounts,
      unique_products_affected: uniqueProducts,
      unique_customers_affected: uniqueCustomers,
      return_rate_estimate: this.estimateReturnRate(totalReturns)
    };
  }

  private analyzeReturnReasons(returns: any[]): ReasonAnalysis {
    const reasonStats = new Map<string, ReasonStatistics>();

    returns.forEach(returnItem => {
      const reason = returnItem.return_reason_name;
      
      if (!reasonStats.has(reason)) {
        reasonStats.set(reason, {
          reason_name: reason,
          reason_id: returnItem.return_reason_id,
          count: 0,
          total_amount: 0,
          affected_products: new Set(),
          fbo_count: 0,
          fbs_count: 0,
          avg_processing_days: 0,
          completion_rate: 0
        });
      }

      const stats = reasonStats.get(reason)!;
      stats.count++;
      stats.total_amount += parseFloat(returnItem.refund_amount);
      stats.affected_products.add(returnItem.product_id);
      
      if (returnItem.delivery_schema === 'FBO') {
        stats.fbo_count++;
      } else {
        stats.fbs_count++;
      }
    });

    // Вычисление дополнительных метрик
    reasonStats.forEach(stats => {
      stats.avg_amount = stats.total_amount / stats.count;
      stats.product_diversity = stats.affected_products.size;
    });

    const sortedReasons = Array.from(reasonStats.values())
      .sort((a, b) => b.count - a.count);

    return {
      total_unique_reasons: reasonStats.size,
      top_reasons: sortedReasons.slice(0, 10),
      reason_distribution: Object.fromEntries(
        sortedReasons.map(r => [r.reason_name, (r.count / returns.length * 100).toFixed(2)])
      ),
      high_value_reasons: sortedReasons.filter(r => r.avg_amount > 1000),
      problematic_reasons: sortedReasons.filter(r => r.count > returns.length * 0.1) // >10% от всех возвратов
    };
  }

  private generateRecommendations(
    basicStats: BasicReturnStatistics,
    reasonAnalysis: ReasonAnalysis,
    productAnalysis: ProductAnalysis,
    temporalAnalysis: TemporalAnalysis
  ): ReturnRecommendation[] {
    const recommendations: ReturnRecommendation[] = [];

    // Анализ общего уровня возвратов
    if (basicStats.return_rate_estimate > 0.15) {
      recommendations.push({
        category: 'RETURN_RATE',
        priority: 'HIGH',
        title: 'Высокий уровень возвратов',
        description: `Уровень возвратов составляет ${(basicStats.return_rate_estimate * 100).toFixed(1)}%`,
        actions: [
          'Улучшить качество описаний товаров',
          'Проанализировать качество упаковки',
          'Рассмотреть изменения в ассортименте'
        ],
        expected_impact: 'Снижение возвратов на 20-30%'
      });
    }

    // Анализ основных причин возвратов
    const topReason = reasonAnalysis.top_reasons[0];
    if (topReason && topReason.count > basicStats.total_returns * 0.25) {
      recommendations.push({
        category: 'TOP_REASON',
        priority: 'HIGH',
        title: 'Доминирующая причина возвратов',
        description: `"${topReason.reason_name}" составляет ${(topReason.count / basicStats.total_returns * 100).toFixed(1)}% возвратов`,
        actions: [
          'Детально изучить причину возвратов',
          'Внедрить меры по устранению основной причины',
          'Мониторить изменения после внедрения мер'
        ],
        expected_impact: 'Снижение возвратов на 15-25%'
      });
    }

    // Анализ проблемных товаров
    const problematicProducts = productAnalysis.high_return_products.slice(0, 5);
    if (problematicProducts.length > 0) {
      recommendations.push({
        category: 'PROBLEMATIC_PRODUCTS',
        priority: 'MEDIUM',
        title: 'Товары с высоким уровнем возвратов',
        description: `${problematicProducts.length} товаров имеют аномально высокий уровень возвратов`,
        actions: [
          'Пересмотреть описания проблемных товаров',
          'Проверить качество товаров у поставщиков',
          'Рассмотреть исключение проблемных товаров из ассортимента'
        ],
        expected_impact: 'Снижение возвратов на 10-20%'
      });
    }

    return recommendations.sort((a, b) => {
      const priorityOrder = { 'HIGH': 3, 'MEDIUM': 2, 'LOW': 1 };
      return priorityOrder[b.priority] - priorityOrder[a.priority];
    });
  }
}

interface AnalysisPeriod {
  from: string;
  to: string;
}

interface ReturnAnalyticsReport {
  period: AnalysisPeriod;
  generated_at: string;
  basic_statistics: BasicReturnStatistics;
  reason_analysis: ReasonAnalysis;
  product_analysis: ProductAnalysis;
  temporal_analysis: TemporalAnalysis;
  financial_analysis: FinancialAnalysis;
  logistics_analysis: LogisticsAnalysis;
  recommendations: ReturnRecommendation[];
}

interface BasicReturnStatistics {
  total_returns: number;
  total_refund_amount: number;
  average_refund_amount: number;
  status_breakdown: Record<string, number>;
  schema_breakdown: Record<string, number>;
  unique_products_affected: number;
  unique_customers_affected: number;
  return_rate_estimate: number;
}

interface ReturnRecommendation {
  category: 'RETURN_RATE' | 'TOP_REASON' | 'PROBLEMATIC_PRODUCTS' | 'LOGISTICS' | 'FINANCIAL';
  priority: 'HIGH' | 'MEDIUM' | 'LOW';
  title: string;
  description: string;
  actions: string[];
  expected_impact: string;
}

ReturnMonitoringSystem - Система мониторинга возвратов

class ReturnMonitoringSystem {
  private alertThresholds = {
    dailyReturnLimit: 50,
    highValueReturnAmount: 5000,
    returnRateThreshold: 0.15,
    newReturnsAlertLimit: 20
  };

  constructor(private api: ReturnsApi) {}

  async runDailyMonitoring(): Promise<MonitoringReport> {
    console.log("🔍 Запуск ежедневного мониторинга возвратов...");

    const today = new Date();
    const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);

    // Получение возвратов за последние 24 часа
    const recentReturns = await this.api.getList({
      filter: {
        created_at_from: yesterday.toISOString(),
        created_at_to: today.toISOString()
      },
      limit: 1000
    });

    // Анализ алертов
    const alerts = this.analyzeAlerts(recentReturns.returns);
    
    // Анализ трендов
    const trends = await this.analyzeTrends();
    
    // Генерация сводки
    const summary = this.generateDailySummary(recentReturns.returns);

    const report: MonitoringReport = {
      date: today.toISOString().split('T')[0],
      total_returns_24h: recentReturns.returns.length,
      alerts,
      trends,
      summary,
      requires_attention: alerts.some(alert => alert.severity === 'HIGH')
    };

    // Отправка уведомлений при критических алертах
    if (report.requires_attention) {
      await this.sendCriticalAlerts(report);
    }

    return report;
  }

  private analyzeAlerts(returns: any[]): MonitoringAlert[] {
    const alerts: MonitoringAlert[] = [];

    // Проверка превышения лимита возвратов
    if (returns.length > this.alertThresholds.dailyReturnLimit) {
      alerts.push({
        type: 'HIGH_VOLUME',
        severity: 'HIGH',
        message: `Превышен дневной лимит возвратов: ${returns.length} > ${this.alertThresholds.dailyReturnLimit}`,
        count: returns.length,
        threshold: this.alertThresholds.dailyReturnLimit
      });
    }

    // Проверка высокостоимостных возвратов
    const highValueReturns = returns.filter(ret => 
      parseFloat(ret.refund_amount) > this.alertThresholds.highValueReturnAmount
    );

    if (highValueReturns.length > 0) {
      const totalHighValue = highValueReturns.reduce((sum, ret) => 
        sum + parseFloat(ret.refund_amount), 0
      );

      alerts.push({
        type: 'HIGH_VALUE',
        severity: 'MEDIUM',
        message: `${highValueReturns.length} высокостоимостных возвратов на сумму ${totalHighValue.toFixed(2)} руб`,
        count: highValueReturns.length,
        threshold: this.alertThresholds.highValueReturnAmount,
        details: highValueReturns.slice(0, 5).map(ret => 
          `${ret.name}: ${ret.refund_amount} руб (${ret.return_reason_name})`
        )
      });
    }

    // Проверка новых возвратов, требующих обработки
    const newReturns = returns.filter(ret => ret.status === 'NEW');
    if (newReturns.length > this.alertThresholds.newReturnsAlertLimit) {
      alerts.push({
        type: 'PROCESSING_BACKLOG',
        severity: 'MEDIUM',
        message: `Накопилось ${newReturns.length} новых возвратов для обработки`,
        count: newReturns.length,
        threshold: this.alertThresholds.newReturnsAlertLimit
      });
    }

    return alerts;
  }

  private async sendCriticalAlerts(report: MonitoringReport): Promise<void> {
    const criticalAlerts = report.alerts.filter(alert => alert.severity === 'HIGH');
    
    if (criticalAlerts.length > 0) {
      console.log("🚨 Критические алерты по возвратам:");
      criticalAlerts.forEach(alert => {
        console.log(`   ${alert.type}: ${alert.message}`);
      });
      
      // Здесь можно добавить интеграцию с системами уведомлений:
      // - отправка email
      // - Slack/Teams уведомления
      // - SMS для критических случаев
      // - интеграция с системами мониторинга
    }
  }
}

interface MonitoringReport {
  date: string;
  total_returns_24h: number;
  alerts: MonitoringAlert[];
  trends: TrendAnalysis;
  summary: DailySummary;
  requires_attention: boolean;
}

interface MonitoringAlert {
  type: 'HIGH_VOLUME' | 'HIGH_VALUE' | 'PROCESSING_BACKLOG' | 'ANOMALY';
  severity: 'HIGH' | 'MEDIUM' | 'LOW';
  message: string;
  count: number;
  threshold: number;
  details?: string[];
}

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

try {
  const returns = await returnsApi.getList({
    filter: {
      status: ['NEW', 'PROCESSING']
    },
    limit: 100
  });

  console.log(`Получено ${returns.returns.length} возвратов`);
} 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("Превышен лимит запросов - повтор через минуту");
    await new Promise(resolve => setTimeout(resolve, 60000));
  } else {
    console.error("Неожиданная ошибка:", error.message);
  }
}

Лучшие практики

Оптимизация работы с большими объемами данных

// Эффективная загрузка всех возвратов с прогресс-баром
async function loadAllReturnsWithProgress(
  api: ReturnsApi,
  filter: any
): Promise<any[]> {
  const allReturns: any[] = [];
  let lastId: string | undefined;
  let totalLoaded = 0;

  console.log("📥 Начинаем загрузку возвратов...");
  
  do {
    const response = await api.getList({
      filter,
      limit: 1000,
      last_id: lastId
    });

    allReturns.push(...response.returns);
    totalLoaded += response.returns.length;
    lastId = response.has_next ? response.last_id : undefined;

    // Прогресс-бар
    const progress = response.total ? (totalLoaded / response.total * 100).toFixed(1) : '?';
    console.log(`📊 Загружено: ${totalLoaded} возвратов (${progress}%)`);

    // Пауза для соблюдения rate limits
    if (lastId) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }

  } while (lastId);

  console.log(`✅ Загрузка завершена: ${allReturns.length} возвратов`);
  return allReturns;
}

Кэширование результатов для аналитики

// Система кэширования для повторяющихся запросов
class ReturnsCache {
  private cache = new Map<string, { data: any; timestamp: number }>();
  private cacheTime = 30 * 60 * 1000; // 30 минут

  async getCachedReturns(filter: any): Promise<any[] | null> {
    const cacheKey = JSON.stringify(filter);
    const cached = this.cache.get(cacheKey);
    
    if (cached && (Date.now() - cached.timestamp) < this.cacheTime) {
      console.log("📋 Используем кэшированные данные");
      return cached.data;
    }
    
    return null;
  }

  setCachedReturns(filter: any, data: any[]): void {
    const cacheKey = JSON.stringify(filter);
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });
  }

  clearCache(): void {
    this.cache.clear();
    console.log("🗑️ Кэш очищен");
  }
}

Интеграционные заметки

  • Single Endpoint: API содержит только один метод для получения списка возвратов
  • Unified Returns: Обрабатывает возвраты как FBO, так и FBS в едином формате
  • Rich Filtering: Поддерживает множественные фильтры по датам, статусам, товарам
  • Pagination Support: Курсорная пагинация для эффективной работы с большими объемами
  • Rate Limiting: API поддерживает до 100 запросов в минуту
  • Data Retention: Данные о возвратах хранятся до 2 лет
  • Real-time Updates: Статусы возвратов обновляются в режиме реального времени
  • Integration Friendly: Структура данных оптимизирована для интеграции с внешними системами