Digital API

API для управлСния Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌΠΈ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌΠΈ Π² OZON Seller API.

ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ²: 4 ΠΌΠ΅Ρ‚ΠΎΠ΄Π° (1 Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ для совмСстимости)

ΠžΠ±Π·ΠΎΡ€

Digital API прСдоставляСт Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ для управлСния Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌΠΈ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌΠΈ:

  • πŸ“¦ ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ списка ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ с Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌΠΈ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌΠΈ
  • πŸ”‘ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΊΠΎΠ΄ΠΎΠ² Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² (Π»ΠΈΡ†Π΅Π½Π·ΠΈΠΈ, ΠΊΠ»ΡŽΡ‡ΠΈ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ)
  • πŸ“Š ОбновлСниС остатков Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²
  • ⏰ БоблюдСниС Π΄Π΅Π΄Π»Π°ΠΉΠ½ΠΎΠ² Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ² (24 часа)

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ возмоТности

🎯 Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ отправлСниями

  • ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ списка ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ, Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‰ΠΈΡ… Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ²
  • Π€ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ ΠΏΠΎ Π΄Π°Ρ‚Π΅ ΠΈ статусу
  • ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ аналитичСских ΠΈ финансовых Π΄Π°Π½Π½Ρ‹Ρ…

πŸ” Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π°ΠΌΠΈ

  • Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… ΠΊΠΎΠ΄ΠΎΠ² (Π»ΠΈΡ†Π΅Π½Π·ΠΈΠΈ, ΠΊΠ»ΡŽΡ‡ΠΈ, ΠΊΠΎΠ΄Ρ‹ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ)
  • ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° нСдоступных ΠΊΠΎΠ΄ΠΎΠ²
  • ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ качСства Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Ρ… ΠΊΠΎΠ΄ΠΎΠ²

πŸ“¦ Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ остатками

  • ОбновлСниС ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎ количСствС доступных Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²
  • ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ обновлСния остатков
  • ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ

⚠️ ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΡ

  • Доступно Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΎΠ΄Π°Π²Ρ†Π°ΠΌ, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΌ с Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌΠΈ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌΠΈ
  • ΠšΠΎΠ΄Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Ρ‹ Π² Ρ‚Π΅Ρ‡Π΅Π½ΠΈΠ΅ 24 часов с ΠΌΠΎΠΌΠ΅Π½Ρ‚Π° получСния Π·Π°ΠΊΠ°Π·Π°
  • Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΠ΅ΠΌΡ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Π±Π°Ρ‚Ρ‡Π° для обновлСния остатков: 100 Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²

ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ API

getDigitalPostingsList()

НазначСниС: ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ список ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ с Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌΠΈ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌΠΈ

interface DigitalListPostingCodesRequest {
  dir?: 'ASC' | 'DESC';
  filter?: {
    since?: string;
    to?: string;
    cutoff_from?: string;
    cutoff_to?: string;
  };
  limit?: number;
  offset?: number;
  with?: {
    financial_data?: boolean;
    analytics_data?: boolean;
    legal_info?: boolean;
  };
}

uploadDigitalCodes()

НазначСниС: Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΊΠΎΠ΄Ρ‹ Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² для отправлСния

interface DigitalUploadPostingCodesRequest {
  posting_number: string;
  exemplars_by_sku: Array<{
    sku: number;
    exemplar_qty: number;
    not_available_exemplar_qty: number;
    exemplar_keys: string[];
  }>;
}

updateDigitalStocks()

НазначСниС: ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ остатки Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²

interface DigitalStocksImportRequest {
  stocks: Array<{
    offer_id: string;
    stock: number;
  }>;
}

ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠΈΠ΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹

Π‘Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС

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

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

// ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ отправлСния, ΠΎΠΆΠΈΠ΄Π°ΡŽΡ‰ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ²
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);

const digitalPostings = await api.digital.getDigitalPostingsList({
  filter: {
    since: weekAgo.toISOString().split('T')[0],
    to: new Date().toISOString().split('T')[0]
  },
  limit: 100,
  with: {
    financial_data: true,
    analytics_data: true
  }
});

// Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΊΠΎΠ΄Ρ‹ для отправлСния
await api.digital.uploadDigitalCodes({
  posting_number: '12345-0001-1',
  exemplars_by_sku: [{
    sku: 123456789,
    exemplar_qty: 3,
    not_available_exemplar_qty: 0,
    exemplar_keys: [
      'GAME_KEY_001_ABC123',
      'GAME_KEY_002_DEF456', 
      'GAME_KEY_003_GHI789'
    ]
  }]
});

// ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ остатки Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²
await api.digital.updateDigitalStocks({
  stocks: [
    {
      offer_id: 'GAME_DIGITAL_001',
      stock: 50
    },
    {
      offer_id: 'SOFTWARE_LICENSE_002', 
      stock: 25
    }
  ]
});

ΠŸΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ сцСнарии

ΠœΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Π·Π°ΠΊΠ°Π·ΠΎΠ²

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

  async processDigitalOrders(): Promise<void> {
    // ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ всС Π·Π°ΠΊΠ°Π·Ρ‹, ΠΎΠΆΠΈΠ΄Π°ΡŽΡ‰ΠΈΠ΅ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ²
    const pendingOrders = await this.api.digital.getDigitalPostingsList({
      filter: {
        since: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
      },
      limit: 1000,
      with: {
        analytics_data: true,
        financial_data: true
      }
    });

    const ordersNeedingCodes = pendingOrders.result?.filter(posting => 
      posting.status === 'awaiting_packaging' &&
      posting.products?.some(product => product.required_qty_for_digital_code > 0)
    ) || [];

    console.log(`НайдСно ${ordersNeedingCodes.length} Π·Π°ΠΊΠ°Π·ΠΎΠ², Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‰ΠΈΡ… Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ²`);

    for (const order of ordersNeedingCodes) {
      await this.uploadCodesForOrder(order);
      
      // Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΎΠΉ Π·Π°ΠΊΠ°Π·ΠΎΠ²
      await new Promise(resolve => setTimeout(resolve, 500));
    }
  }

  private async uploadCodesForOrder(order: any): Promise<void> {
    try {
      const codesByProduct = [];
      
      for (const product of order.products || []) {
        if (product.required_qty_for_digital_code > 0) {
          const codes = await this.generateCodesForProduct(
            product.sku, 
            product.required_qty_for_digital_code
          );
          
          codesByProduct.push({
            sku: product.sku,
            exemplar_qty: codes.length,
            not_available_exemplar_qty: 0,
            exemplar_keys: codes
          });
        }
      }

      if (codesByProduct.length > 0) {
        const result = await this.api.digital.uploadDigitalCodes({
          posting_number: order.posting_number,
          exemplars_by_sku: codesByProduct
        });

        this.validateUploadResults(order.posting_number, result);
      }
    } catch (error) {
      console.error(`Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Π·Π°ΠΊΠ°Π·Π° ${order.posting_number}:`, error);
    }
  }

  private async generateCodesForProduct(sku: number, quantity: number): Promise<string[]> {
    // Π›ΠΎΠ³ΠΈΠΊΠ° Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ ΠΈΠ»ΠΈ получСния ΠΊΠΎΠ΄ΠΎΠ² ΠΈΠ· внСшнСй систСмы
    const codes: string[] = [];
    
    for (let i = 0; i < quantity; i++) {
      codes.push(`${sku}_KEY_${Date.now()}_${Math.random().toString(36).substring(7).toUpperCase()}`);
    }
    
    return codes;
  }

  private validateUploadResults(postingNumber: string, result: any): void {
    let totalReceived = 0;
    let totalRejected = 0;

    result.exemplars_by_sku?.forEach((skuResult: any) => {
      totalReceived += skuResult.received_qty || 0;
      totalRejected += skuResult.rejected_qty || 0;
      
      if (skuResult.failed_exemplars?.length > 0) {
        console.warn(`ΠžΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½Π½Ρ‹Π΅ ΠΊΠΎΠ΄Ρ‹ для SKU ${skuResult.sku} Π² Π·Π°ΠΊΠ°Π·Π΅ ${postingNumber}:`);
        skuResult.failed_exemplars.forEach((error: any) => {
          console.warn(`  ${error.code}: ${error.message}`);
        });
      }
    });

    console.log(`Π—Π°ΠΊΠ°Π· ${postingNumber}: принято ΠΊΠΎΠ΄ΠΎΠ² ${totalReceived}, ΠΎΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½ΠΎ ${totalRejected}`);
  }
}

БистСма ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° остатков

class DigitalStockMonitor {
  constructor(
    private api: OzonSellerAPI,
    private lowStockThreshold: number = 10
  ) {}

  async monitorAndUpdateStocks(): Promise<void> {
    // ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ остатки ΠΈΠ· внСшнСй систСмы
    const stockData = await this.getExternalStockData();
    
    // Найти Ρ‚ΠΎΠ²Π°Ρ€Ρ‹ с Π½ΠΈΠ·ΠΊΠΈΠΌΠΈ остатками
    const lowStockItems = stockData.filter(item => item.availableStock <= this.lowStockThreshold);
    
    if (lowStockItems.length > 0) {
      console.warn(`ΠžΠ±Π½Π°Ρ€ΡƒΠΆΠ΅Π½ΠΎ ${lowStockItems.length} Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² с Π½ΠΈΠ·ΠΊΠΈΠΌΠΈ остатками`);
      await this.sendLowStockAlert(lowStockItems);
    }
    
    // ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ остатки Π² OZON
    await this.updateStocksBatch(stockData);
    
    // ГСнСрация ΠΎΡ‚Ρ‡Π΅Ρ‚Π° ΠΏΠΎ остаткам
    await this.generateStockReport(stockData);
  }

  private async updateStocksBatch(stockData: any[]): Promise<void> {
    const batchSize = 100;
    let successCount = 0;
    let errorCount = 0;
    
    for (let i = 0; i < stockData.length; i += batchSize) {
      const batch = stockData.slice(i, i + batchSize);
      
      try {
        const result = await this.api.digital.updateDigitalStocks({
          stocks: batch.map(item => ({
            offer_id: item.offerId,
            stock: item.availableStock
          }))
        });
        
        // ΠŸΠΎΠ΄ΡΡ‡Π΅Ρ‚ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ²
        result.status?.forEach(status => {
          if (status.updated) {
            successCount++;
          } else {
            errorCount++;
            console.error(`Ошибка обновлСния ${status.offer_id}:`, status.errors);
          }
        });
        
        console.log(`ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½ Π±Π°Ρ‚Ρ‡ ${Math.floor(i / batchSize) + 1}/${Math.ceil(stockData.length / batchSize)}`);
        
        // Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° ΠΌΠ΅ΠΆΠ΄Ρƒ Π±Π°Ρ‚Ρ‡Π°ΠΌΠΈ
        if (i + batchSize < stockData.length) {
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      } catch (error) {
        console.error(`Ошибка обновлСния Π±Π°Ρ‚Ρ‡Π°:`, error);
        errorCount += batch.length;
      }
    }
    
    console.log(`ОбновлСниС остатков Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΎ: ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ ${successCount}, ошибок ${errorCount}`);
  }

  private async getExternalStockData(): Promise<any[]> {
    // Эмуляция получСния Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· внСшнСй систСмы
    return [
      { offerId: 'GAME_DIGITAL_001', availableStock: 50 },
      { offerId: 'SOFTWARE_LICENSE_002', availableStock: 5 }, // Низкий остаток
      { offerId: 'DIGITAL_BOOK_003', availableStock: 100 },
      { offerId: 'MUSIC_ALBUM_004', availableStock: 2 }, // ΠšΡ€ΠΈΡ‚ΠΈΡ‡Π΅ΡΠΊΠΈ Π½ΠΈΠ·ΠΊΠΈΠΉ остаток
    ];
  }

  private async sendLowStockAlert(lowStockItems: any[]): Promise<void> {
    // Π›ΠΎΠ³ΠΈΠΊΠ° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ ΡƒΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ ΠΎ Π½ΠΈΠ·ΠΊΠΈΡ… остатках
    console.log('🚨 Π£Π²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠ΅ ΠΎ Π½ΠΈΠ·ΠΊΠΈΡ… остатках:');
    lowStockItems.forEach(item => {
      const severity = item.availableStock <= 5 ? 'πŸ”΄ КРИВИЧНО' : '🟑 Π’ΠΠ˜ΠœΠΠΠ˜Π•';
      console.log(`${severity} ${item.offerId}: ${item.availableStock} ΡˆΡ‚.`);
    });
  }

  private async generateStockReport(stockData: any[]): Promise<void> {
    const totalItems = stockData.length;
    const totalStock = stockData.reduce((sum, item) => sum + item.availableStock, 0);
    const avgStock = Math.round(totalStock / totalItems);
    const lowStockCount = stockData.filter(item => item.availableStock <= this.lowStockThreshold).length;
    
    console.log('\nπŸ“Š ΠžΡ‚Ρ‡Π΅Ρ‚ ΠΏΠΎ остаткам Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²:');
    console.log(`ВсСго Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²: ${totalItems}`);
    console.log(`ΠžΠ±Ρ‰ΠΈΠΉ остаток: ${totalStock} ΡˆΡ‚.`);
    console.log(`Π‘Ρ€Π΅Π΄Π½ΠΈΠΉ остаток: ${avgStock} ΡˆΡ‚.`);
    console.log(`Π’ΠΎΠ²Π°Ρ€ΠΎΠ² с Π½ΠΈΠ·ΠΊΠΈΠΌ остатком: ${lowStockCount} (${Math.round(lowStockCount / totalItems * 100)}%)`);
  }
}

АналитичСская систСма отчСтности

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

  async generateDigitalSalesReport(dateFrom: string, dateTo: string): Promise<void> {
    const digitalPostings = await this.api.digital.getDigitalPostingsList({
      filter: {
        since: dateFrom,
        to: dateTo
      },
      limit: 1000,
      with: {
        financial_data: true,
        analytics_data: true
      }
    });

    const analytics = this.analyzeDigitalSales(digitalPostings.result || []);
    this.generateReport(analytics, dateFrom, dateTo);
  }

  private analyzeDigitalSales(postings: any[]): any {
    const analytics = {
      totalOrders: postings.length,
      totalRevenue: 0,
      totalCommission: 0,
      productStats: new Map(),
      regionStats: new Map(),
      statusStats: new Map(),
      codeUploadStats: {
        totalRequired: 0,
        uploaded: 0,
        pending: 0
      }
    };

    postings.forEach(posting => {
      // Ѐинансовая Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊΠ°
      if (posting.financial_data) {
        analytics.totalRevenue += posting.financial_data.order_amount || 0;
        analytics.totalCommission += posting.financial_data.commission || 0;
      }

      // Бтатистика статусов
      const currentCount = analytics.statusStats.get(posting.status) || 0;
      analytics.statusStats.set(posting.status, currentCount + 1);

      // РСгиональная Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊΠ°
      if (posting.analytics_data?.region) {
        const regionCount = analytics.regionStats.get(posting.analytics_data.region) || 0;
        analytics.regionStats.set(posting.analytics_data.region, regionCount + 1);
      }

      // Аналитика ΠΏΠΎ ΠΏΡ€ΠΎΠ΄ΡƒΠΊΡ‚Π°ΠΌ ΠΈ ΠΊΠΎΠ΄Π°ΠΌ
      posting.products?.forEach((product: any) => {
        const sku = product.sku.toString();
        if (!analytics.productStats.has(sku)) {
          analytics.productStats.set(sku, {
            name: product.name,
            totalSold: 0,
            totalRevenue: 0,
            codesRequired: 0
          });
        }

        const productStat = analytics.productStats.get(sku);
        productStat.totalSold += product.quantity;
        productStat.totalRevenue += (product.price || 0) * product.quantity;
        productStat.codesRequired += product.required_qty_for_digital_code || 0;

        analytics.codeUploadStats.totalRequired += product.required_qty_for_digital_code || 0;
        
        if (product.required_qty_for_digital_code > 0 && posting.status === 'awaiting_packaging') {
          analytics.codeUploadStats.pending += product.required_qty_for_digital_code;
        } else if (product.required_qty_for_digital_code > 0) {
          analytics.codeUploadStats.uploaded += product.required_qty_for_digital_code;
        }
      });
    });

    return analytics;
  }

  private generateReport(analytics: any, dateFrom: string, dateTo: string): void {
    console.log(`\nπŸ“ˆ ΠžΡ‚Ρ‡Π΅Ρ‚ ΠΏΠΎ Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌ (${dateFrom} - ${dateTo})`);
    console.log('='.repeat(60));
    
    // ΠžΠ±Ρ‰Π°Ρ статистика
    console.log('\nπŸ“Š ΠžΠ±Ρ‰Π°Ρ статистика:');
    console.log(`ВсСго Π·Π°ΠΊΠ°Π·ΠΎΠ²: ${analytics.totalOrders}`);
    console.log(`ΠžΠ±Ρ‰Π°Ρ Π²Ρ‹Ρ€ΡƒΡ‡ΠΊΠ°: ${analytics.totalRevenue.toFixed(2)} Ρ€ΡƒΠ±.`);
    console.log(`ΠžΠ±Ρ‰Π°Ρ комиссия: ${analytics.totalCommission.toFixed(2)} Ρ€ΡƒΠ±.`);
    console.log(`Чистая ΠΏΡ€ΠΈΠ±Ρ‹Π»ΡŒ: ${(analytics.totalRevenue - analytics.totalCommission).toFixed(2)} Ρ€ΡƒΠ±.`);
    console.log(`Π‘Ρ€Π΅Π΄Π½ΠΈΠΉ Ρ‡Π΅ΠΊ: ${(analytics.totalRevenue / analytics.totalOrders).toFixed(2)} Ρ€ΡƒΠ±.`);

    // Бтатистика ΠΏΠΎ ΠΊΠΎΠ΄Π°ΠΌ
    console.log('\nπŸ”‘ Бтатистика ΠΏΠΎ ΠΊΠΎΠ΄Π°ΠΌ:');
    console.log(`ВсСго трСбуСтся ΠΊΠΎΠ΄ΠΎΠ²: ${analytics.codeUploadStats.totalRequired}`);
    console.log(`Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ ΠΊΠΎΠ΄ΠΎΠ²: ${analytics.codeUploadStats.uploaded}`);
    console.log(`ΠžΠΆΠΈΠ΄Π°Π΅Ρ‚ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: ${analytics.codeUploadStats.pending}`);
    
    if (analytics.codeUploadStats.totalRequired > 0) {
      const uploadRate = (analytics.codeUploadStats.uploaded / analytics.codeUploadStats.totalRequired * 100).toFixed(1);
      console.log(`ΠŸΡ€ΠΎΡ†Π΅Π½Ρ‚ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: ${uploadRate}%`);
    }

    // Π’ΠΎΠΏ Ρ‚ΠΎΠ²Π°Ρ€Ρ‹
    console.log('\nπŸ† Π’ΠΎΠΏ-5 Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² ΠΏΠΎ ΠΏΡ€ΠΎΠ΄Π°ΠΆΠ°ΠΌ:');
    const topProducts = Array.from(analytics.productStats.entries())
      .sort(([,a], [,b]) => b.totalRevenue - a.totalRevenue)
      .slice(0, 5);
      
    topProducts.forEach(([sku, stats], index) => {
      console.log(`${index + 1}. ${stats.name} (SKU: ${sku})`);
      console.log(`   ΠŸΡ€ΠΎΠ΄Π°Π½ΠΎ: ${stats.totalSold} ΡˆΡ‚., Π’Ρ‹Ρ€ΡƒΡ‡ΠΊΠ°: ${stats.totalRevenue.toFixed(2)} Ρ€ΡƒΠ±.`);
    });

    // Бтатистика ΠΏΠΎ Ρ€Π΅Π³ΠΈΠΎΠ½Π°ΠΌ
    if (analytics.regionStats.size > 0) {
      console.log('\n🌍 Π’ΠΎΠΏ-5 Ρ€Π΅Π³ΠΈΠΎΠ½ΠΎΠ²:');
      const topRegions = Array.from(analytics.regionStats.entries())
        .sort(([,a], [,b]) => b - a)
        .slice(0, 5);
        
      topRegions.forEach(([region, count], index) => {
        const percentage = (count / analytics.totalOrders * 100).toFixed(1);
        console.log(`${index + 1}. ${region}: ${count} Π·Π°ΠΊΠ°Π·ΠΎΠ² (${percentage}%)`);
      });
    }

    // Бтатистика ΠΏΠΎ статусам
    console.log('\nπŸ“¦ Бтатусы Π·Π°ΠΊΠ°Π·ΠΎΠ²:');
    Array.from(analytics.statusStats.entries()).forEach(([status, count]) => {
      const percentage = (count / analytics.totalOrders * 100).toFixed(1);
      console.log(`${status}: ${count} Π·Π°ΠΊΠ°Π·ΠΎΠ² (${percentage}%)`);
    });
  }
}

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок

try {
  await api.digital.uploadDigitalCodes({
    posting_number: '12345-0001-1',
    exemplars_by_sku: [/* ... */]
  });
} 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 === 404) {
    console.error('ΠžΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ');
  } else {
    console.error('НСоТиданная ошибка:', error.message);
  }
}

Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ ΠΏΠΎ использованию

🎯 ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ

  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ обновлСния остатков (Π΄ΠΎ 100 Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ² Π·Π° запрос)
  • ДобавляйтС Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ запросами для избСТания rate limiting
  • ΠšΠ΅ΡˆΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ получСния списка ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ

πŸ”’ Π‘Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒ

  • Π₯Ρ€Π°Π½ΠΈΡ‚Π΅ Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Π΅ ΠΊΠΎΠ΄Ρ‹ Π² Π·Π°ΡˆΠΈΡ„Ρ€ΠΎΠ²Π°Π½Π½ΠΎΠΌ Π²ΠΈΠ΄Π΅
  • Π›ΠΎΠ³ΠΈΡ€ΡƒΠΉΡ‚Π΅ всС ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ с ΠΊΠΎΠ΄Π°ΠΌΠΈ для Π°ΡƒΠ΄ΠΈΡ‚Π°
  • Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΈ слоТныС ΠΊΠΎΠ΄Ρ‹ для прСдотвращСния ΠΌΠΎΡˆΠ΅Π½Π½ΠΈΡ‡Π΅ΡΡ‚Π²Π°

πŸ“Š ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³

  • ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°ΠΉΡ‚Π΅ Π΄Π΅Π΄Π»Π°ΠΉΠ½Ρ‹ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ² (24 часа)
  • ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΡŒΡ‚Π΅ Π½ΠΈΠ·ΠΊΠΈΠ΅ остатки Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… Ρ‚ΠΎΠ²Π°Ρ€ΠΎΠ²
  • АнализируйтС статистику ΠΎΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½Π½Ρ‹Ρ… ΠΊΠΎΠ΄ΠΎΠ²

πŸš€ Автоматизация

  • АвтоматизируйтС процСсс Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ ΠΊΠΎΠ΄ΠΎΠ²
  • НастройтС увСдомлСния ΠΎ Π½ΠΈΠ·ΠΊΠΈΡ… остатках
  • Π˜Π½Ρ‚Π΅Π³Ρ€ΠΈΡ€ΡƒΠΉΡ‚Π΅ с внСшними систСмами управлСния лицСнзиями

Digital API прСдоставляСт ΠΏΠΎΠ»Π½Ρ‹ΠΉ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒ Π½Π°Π΄ Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌΠΈ Ρ‚ΠΎΠ²Π°Ρ€Π°ΠΌΠΈ, ΠΎΡ‚ управлСния отправлСниями Π΄ΠΎ обновлСния остатков, обСспСчивая ΡΡ„Ρ„Π΅ΠΊΡ‚ΠΈΠ²Π½ΡƒΡŽ Ρ€Π°Π±ΠΎΡ‚Ρƒ с Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹ΠΌ ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚ΠΎΠΌ Π½Π° ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅ OZON.