Delivery-FBS API - Управление доставкой и отгрузками FBS

Delivery-FBS API предоставляет полный набор инструментов для управления процессом доставки FBS (Fulfillment by Seller), включая создание отгрузок, управление документооборотом, получение штрихкодов и отслеживание статусов.

Обзор API

Количество методов: 18
Основные функции: Создание и управление отгрузками, документооборот, штрихкоды, этикетки
Особенности: Некоторые методы недоступны для продавцов из СНГ

⚠️ Важно: Для продавцов из СНГ недоступен метод изменения состава отгрузки.

Жизненный цикл отгрузки FBS

Создание отгрузки → Подтверждение → Формирование документов → Отгрузка → Завершение
      (new)       →   (formed)   →      (confirmed)       → (shipped) → (completed)

Основные группы методов

1. Управление отгрузками (6 методов)

  • Создание, подтверждение, отмена отгрузок
  • Получение информации и списков отгрузок
  • Изменение состава отгрузки

2. Документооборот и штрихкоды (9 методов)

  • Создание и получение актов
  • Формирование штрихкодов и этикеток
  • Проверка статусов документов

3. Специальные операции (3 метода)

  • Разделение отправлений
  • Цифровые акты
  • Доступные перевозки

Методы управления отгрузками

1. Создание отгрузки

Метод: createCarriage()
Эндпоинт: POST /v1/carriage/create

Создает первую FBS отгрузку, в которую автоматически попадают все отправления со статусом «Готов к отгрузке».

Параметры запроса

interface DeliveryFbsCarriageCreateRequest {
  delivery_method_id: number;      // ID метода доставки
  first_mile_from_time: string;    // Время начала приёма (HH:mm)
  first_mile_to_time: string;      // Время окончания приёма (HH:mm)
  comment?: string;                // Комментарий к отгрузке
}

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

import { OzonSellerApiClient } from '@spacechemical/ozon-seller-api';

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

// Создать новую отгрузку
const carriage = await client.deliveryFbs.createCarriage({
  delivery_method_id: 123,
  first_mile_from_time: '09:00',
  first_mile_to_time: '18:00',
  comment: 'Отгрузка заказов за 15.12.2024'
});

if (carriage.result?.carriage_id) {
  console.log(`✅ Отгрузка создана с ID: ${carriage.result.carriage_id}`);
  console.log(`Статус: ${carriage.result.status}`); // "new"
  console.log(`Отправлений: ${carriage.result.postings_count}`);
} else {
  console.log('❌ Не удалось создать отгрузку');
}

2. Подтверждение отгрузки

Метод: approveCarriage()
Эндпоинт: POST /v1/carriage/approve

Подтверждает отгрузку и переводит её в статус «Сформирована». После подтверждения можно получить документы.

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

// Подтвердить отгрузку
const approvalResult = await client.deliveryFbs.approveCarriage({
  carriage_id: 12345
});

if (approvalResult.result) {
  console.log('✅ Отгрузка подтверждена и сформирована');
  
  // Теперь можно создать документы
  const documents = await client.deliveryFbs.createAct({
    carriage_id: 12345,
    posting_number: ['12345-0001-1', '12345-0002-1']
  });
  
  if (documents.result) {
    console.log(`📄 Документы созданы, ID акта: ${documents.act_id}`);
  }
}

3. Получение информации об отгрузке

Метод: getCarriage()
Эндпоинт: POST /v1/carriage/get

Возвращает подробную информацию о конкретной отгрузке.

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

// Получить информацию об отгрузке
const carriageInfo = await client.deliveryFbs.getCarriage({
  carriage_id: 12345
});

if (carriageInfo.result) {
  const carriage = carriageInfo.result;
  console.log(`Статус отгрузки: ${carriage.status}`);
  console.log(`Создана: ${new Date(carriage.created_at!).toLocaleString()}`);
  console.log(`Отправлений: ${carriage.postings_count}`);
  console.log(`Метод доставки: ${carriage.delivery_method?.name}`);
  console.log(`Время приёма: ${carriage.first_mile_from_time} - ${carriage.first_mile_to_time}`);
}

4. Список отгрузок по методам доставки

Метод: getCarriageDeliveryList()
Эндпоинт: POST /v1/carriage/delivery/list

Возвращает список созданных отгрузок для каждого метода доставки.

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

// Получить список отгрузок
const deliveryList = await client.deliveryFbs.getCarriageDeliveryList({
  status: 'new',  // новые отгрузки
  limit: 50
});

console.log('📋 Список отгрузок по методам доставки:');
deliveryList.result?.forEach(item => {
  console.log(`\nМетод доставки: ${item.delivery_method?.name} (ID: ${item.delivery_method?.id})`);
  
  if (item.carriages && item.carriages.length > 0) {
    item.carriages.forEach(carriage => {
      console.log(`  - Отгрузка ${carriage.carriage_id}: ${carriage.status}`);
      console.log(`    Отправлений: ${carriage.postings_count}, создана: ${carriage.created_at}`);
    });
  } else {
    console.log('  Нет отгрузок');
  }
});

5. Изменение состава отгрузки

Метод: setPostings()
Эндпоинт: POST /v1/carriage/set-postings

⚠️ Ограничение: Недоступно для продавцов из СНГ.

Полностью перезаписывает список заказов в отгрузке.

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

// Изменить состав отгрузки (только для продавцов не из СНГ)
try {
  const result = await client.deliveryFbs.setPostings({
    carriage_id: 12345,
    posting_number: ['12345-0001-1', '12345-0003-1', '12345-0005-1']
  });

  if (result.result) {
    console.log('✅ Состав отгрузки изменён');
  }
} catch (error) {
  if (error.response?.status === 403) {
    console.log('⚠️ Изменение состава недоступно для продавцов из СНГ');
  }
}

6. Отмена отгрузки

Метод: cancelCarriage()
Эндпоинт: POST /v1/carriage/cancel

Удаляет отгрузку. Возможно только до подтверждения.

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

// Отменить отгрузку
const cancelResult = await client.deliveryFbs.cancelCarriage({
  carriage_id: 12345
});

if (cancelResult.result) {
  console.log('✅ Отгрузка отменена');
}

Методы работы с документами и штрихкодами

7. Создание документов отгрузки

Метод: createAct()
Эндпоинт: POST /v2/posting/fbs/act/create

Подтверждает отгрузку и запускает формирование транспортной накладной и штрихкода.

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

// Создать документы для отгрузки
const actResult = await client.deliveryFbs.createAct({
  carriage_id: 12345,
  posting_number: ['12345-0001-1', '12345-0002-1', '12345-0003-1']
});

if (actResult.result) {
  console.log(`📄 Акт создан с ID: ${actResult.act_id}`);
  
  // Проверить статус формирования документов
  const status = await client.deliveryFbs.checkActStatus({
    carriage_id: 12345
  });
  
  console.log(`Статус отгрузки: ${status.carriage_status}`);
  console.log(`Статус штрихкода: ${status.barcode_status}`);
}

8. Проверка статуса документов

Метод: checkActStatus()
Эндпоинт: POST /v2/posting/fbs/act/check-status

Возвращает статус формирования штрихкода и документов отгрузки.

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

// Проверить статус документов
const status = await client.deliveryFbs.checkActStatus({
  carriage_id: 12345
});

console.log(`Статус отгрузки: ${status.carriage_status}`);
console.log(`Статус штрихкода: ${status.barcode_status}`);

if (status.documents && status.documents.length > 0) {
  console.log('Статус документов:');
  status.documents.forEach(doc => {
    console.log(`  ${doc.type}: ${doc.status}`);
    if (doc.error_message) {
      console.log(`    Ошибка: ${doc.error_message}`);
    }
  });
}

// Если все готово - получить документы
if (status.barcode_status === 'ready' && status.carriage_status === 'confirmed') {
  console.log('✅ Документы и штрихкод готовы для загрузки');
}

9. Получение штрихкода отгрузки

Метод: getBarcode()
Эндпоинт: POST /v2/posting/fbs/act/get-barcode

Возвращает штрихкод в виде изображения (base64) для отгрузки.

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

// Получить штрихкод отгрузки
const barcode = await client.deliveryFbs.getBarcode({
  carriage_id: 12345
});

if (barcode.barcode) {
  console.log(`📊 Получен штрихкод: ${barcode.content_type}`);
  
  // Сохранить изображение штрихкода
  const fs = require('fs');
  const barcodeBuffer = Buffer.from(barcode.barcode, 'base64');
  fs.writeFileSync(`barcode-${12345}.png`, barcodeBuffer);
  
  console.log('✅ Штрихкод сохранён в файл');
}

10. Получение текста штрихкода

Метод: getBarcodeText()
Эндпоинт: POST /v2/posting/fbs/act/get-barcode/text

Возвращает штрихкод в текстовом виде.

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

// Получить текст штрихкода
const barcodeText = await client.deliveryFbs.getBarcodeText({
  carriage_id: 12345
});

if (barcodeText.barcode_text) {
  console.log(`🏷️ Штрихкод: ${barcodeText.barcode_text}`);
  
  // Можно использовать для печати или отображения
  console.log('Покажите этот код в пункте выдачи:');
  console.log(`******************`);
  console.log(`* ${barcodeText.barcode_text} *`);
  console.log(`******************`);
}

11. Получение документов отгрузки

Метод: getAct()
Эндпоинт: POST /v2/posting/fbs/act/get-pdf

Возвращает PDF с документами отгрузки (лист отгрузки и транспортная накладная).

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

// Получить документы отгрузки
const documents = await client.deliveryFbs.getAct({
  carriage_id: 12345,
  doc_type: 'act'  // или 'transport_waybill'
});

if (documents.content) {
  console.log(`📄 Получен документ: ${documents.filename}`);
  console.log(`Тип содержимого: ${documents.content_type}`);
  
  // Сохранить PDF файл
  const fs = require('fs');
  const docBuffer = Buffer.from(documents.content, 'base64');
  fs.writeFileSync(documents.filename!, docBuffer);
  
  console.log('✅ Документ сохранён в файл');
}

12. Этикетки для грузовых мест

Метод: getContainerLabels()
Эндпоинт: POST /v2/posting/fbs/act/get-container-labels

Создает этикетки для грузовых мест в отгрузке.

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

// Получить этикетки для контейнеров
const labels = await client.deliveryFbs.getContainerLabels({
  carriage_id: 12345,
  container_numbers: ['CONT001', 'CONT002', 'CONT003']
});

if (labels.content) {
  console.log(`🏷️ Получены этикетки: ${labels.content_type}`);
  
  // Сохранить PDF с этикетками
  const fs = require('fs');
  const labelsBuffer = Buffer.from(labels.content, 'base64');
  fs.writeFileSync(`labels-${12345}.pdf`, labelsBuffer);
  
  console.log('✅ Этикетки сохранены в файл');
}

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

1. Полный цикл создания и отгрузки

class FbsShipmentManager {
  constructor(private client: OzonSellerApiClient) {}

  async createCompleteShipment(deliveryMethodId: number, fromTime: string, toTime: string) {
    console.log('🚀 Начинаем процесс создания отгрузки...');
    
    try {
      // 1. Создаем отгрузку
      const carriage = await this.client.deliveryFbs.createCarriage({
        delivery_method_id: deliveryMethodId,
        first_mile_from_time: fromTime,
        first_mile_to_time: toTime,
        comment: `Отгрузка от ${new Date().toLocaleDateString()}`
      });

      if (!carriage.result?.carriage_id) {
        throw new Error('Не удалось создать отгрузку');
      }

      const carriageId = carriage.result.carriage_id;
      console.log(`✅ Отгрузка создана: ID ${carriageId}`);

      // 2. Подтверждаем отгрузку
      await this.client.deliveryFbs.approveCarriage({ carriage_id: carriageId });
      console.log('✅ Отгрузка подтверждена');

      // 3. Получаем список отправлений
      const carriageInfo = await this.client.deliveryFbs.getCarriage({ 
        carriage_id: carriageId 
      });
      
      // 4. Создаем документы (предполагаем, что знаем номера отправлений)
      const actResult = await this.client.deliveryFbs.createAct({
        carriage_id: carriageId,
        posting_number: [] // В реальности здесь должны быть реальные номера
      });

      if (actResult.result) {
        console.log(`📄 Акт создан: ID ${actResult.act_id}`);
      }

      // 5. Ждем готовности документов
      await this.waitForDocuments(carriageId);

      // 6. Скачиваем все необходимые документы
      await this.downloadAllDocuments(carriageId);

      console.log('🎉 Отгрузка полностью готова!');
      
      return {
        carriageId,
        actId: actResult.act_id,
        status: 'ready'
      };

    } catch (error) {
      console.error('❌ Ошибка в процессе создания отгрузки:', error);
      throw error;
    }
  }

  private async waitForDocuments(carriageId: number, maxAttempts: number = 10) {
    console.log('⏳ Ожидаем готовности документов...');
    
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      const status = await this.client.deliveryFbs.checkActStatus({
        carriage_id: carriageId
      });

      console.log(`Попытка ${attempt}: отгрузка ${status.carriage_status}, штрихкод ${status.barcode_status}`);

      if (status.carriage_status === 'confirmed' && status.barcode_status === 'ready') {
        console.log('✅ Документы готовы!');
        return;
      }

      if (status.carriage_status === 'error' || status.barcode_status === 'error') {
        throw new Error('Ошибка при формировании документов');
      }

      // Ждем 30 секунд перед следующей проверкой
      await new Promise(resolve => setTimeout(resolve, 30000));
    }

    throw new Error('Превышено время ожидания готовности документов');
  }

  private async downloadAllDocuments(carriageId: number) {
    const fs = require('fs');
    const timestamp = Date.now();

    try {
      // Скачиваем штрихкод
      const barcode = await this.client.deliveryFbs.getBarcode({
        carriage_id: carriageId
      });

      if (barcode.barcode) {
        const barcodeBuffer = Buffer.from(barcode.barcode, 'base64');
        fs.writeFileSync(`barcode-${carriageId}-${timestamp}.png`, barcodeBuffer);
        console.log('📊 Штрихкод сохранён');
      }

      // Скачиваем акт
      const act = await this.client.deliveryFbs.getAct({
        carriage_id: carriageId,
        doc_type: 'act'
      });

      if (act.content) {
        const actBuffer = Buffer.from(act.content, 'base64');
        fs.writeFileSync(`act-${carriageId}-${timestamp}.pdf`, actBuffer);
        console.log('📄 Акт сохранён');
      }

      // Скачиваем транспортную накладную
      const waybill = await this.client.deliveryFbs.getAct({
        carriage_id: carriageId,
        doc_type: 'transport_waybill'
      });

      if (waybill.content) {
        const waybillBuffer = Buffer.from(waybill.content, 'base64');
        fs.writeFileSync(`waybill-${carriageId}-${timestamp}.pdf`, waybillBuffer);
        console.log('📋 Транспортная накладная сохранена');
      }

      console.log('✅ Все документы скачаны');

    } catch (error) {
      console.error('❌ Ошибка при скачивании документов:', error);
    }
  }
}

// Использование
const shipmentManager = new FbsShipmentManager(client);

const shipment = await shipmentManager.createCompleteShipment(
  123,      // ID метода доставки
  '09:00',  // Время начала приёма
  '18:00'   // Время окончания приёма
);

console.log(`Отгрузка ${shipment.carriageId} готова к отправке!`);

2. Мониторинг и управление отгрузками

class ShipmentMonitor {
  constructor(private client: OzonSellerApiClient) {}

  // Получить обзор всех отгрузок
  async getShipmentsOverview() {
    const deliveryList = await this.client.deliveryFbs.getCarriageDeliveryList({
      limit: 100
    });

    const overview = {
      totalMethods: 0,
      totalCarriages: 0,
      statusCounts: new Map<string, number>(),
      deliveryMethods: [] as any[]
    };

    deliveryList.result?.forEach(method => {
      overview.totalMethods++;
      
      const methodInfo = {
        id: method.delivery_method?.id,
        name: method.delivery_method?.name,
        carriages: method.carriages || [],
        carriageCount: method.carriages?.length || 0
      };

      overview.deliveryMethods.push(methodInfo);
      overview.totalCarriages += methodInfo.carriageCount;

      // Подсчет по статусам
      method.carriages?.forEach(carriage => {
        const count = overview.statusCounts.get(carriage.status!) || 0;
        overview.statusCounts.set(carriage.status!, count + 1);
      });
    });

    return overview;
  }

  // Мониторинг проблемных отгрузок
  async checkProblematicShipments() {
    const overview = await this.getShipmentsOverview();
    const issues: Array<{
      carriageId: number;
      issue: string;
      severity: 'low' | 'medium' | 'high';
      recommendation: string;
    }> = [];

    // Проверить отгрузки в статусе new слишком долго
    for (const method of overview.deliveryMethods) {
      for (const carriage of method.carriages) {
        if (carriage.status === 'new') {
          const createdAt = new Date(carriage.created_at!);
          const hoursSinceCreation = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60);
          
          if (hoursSinceCreation > 24) {
            issues.push({
              carriageId: carriage.carriage_id!,
              issue: `Отгрузка в статусе "new" более ${Math.round(hoursSinceCreation)} часов`,
              severity: 'high',
              recommendation: 'Подтвердите отгрузку или отмените её'
            });
          }
        }

        // Проверить статус документов для подтверждённых отгрузок
        if (carriage.status === 'formed' || carriage.status === 'confirmed') {
          try {
            const status = await this.client.deliveryFbs.checkActStatus({
              carriage_id: carriage.carriage_id!
            });

            if (status.barcode_status === 'error') {
              issues.push({
                carriageId: carriage.carriage_id!,
                issue: 'Ошибка при формировании штрихкода',
                severity: 'high',
                recommendation: 'Проверьте состав отгрузки и пересоздайте документы'
              });
            }

            if (status.documents?.some(doc => doc.status === 'error')) {
              issues.push({
                carriageId: carriage.carriage_id!,
                issue: 'Ошибка при формировании документов',
                severity: 'high',
                recommendation: 'Проверьте данные отправлений и пересоздайте документы'
              });
            }
          } catch (error) {
            issues.push({
              carriageId: carriage.carriage_id!,
              issue: 'Не удалось получить статус документов',
              severity: 'medium',
              recommendation: 'Проверьте доступность API или повторите запрос позже'
            });
          }
        }
      }
    }

    return issues;
  }

  // Автоматическое решение простых проблем
  async autoFixIssues() {
    const issues = await this.checkProblematicShipments();
    const fixedIssues: number[] = [];
    const failedFixes: Array<{ carriageId: number; error: string }> = [];

    for (const issue of issues) {
      if (issue.severity === 'high' && issue.issue.includes('new')) {
        try {
          // Попробуем подтвердить старые отгрузки
          await this.client.deliveryFbs.approveCarriage({
            carriage_id: issue.carriageId
          });
          
          fixedIssues.push(issue.carriageId);
          console.log(`✅ Автоматически подтверждена отгрузка ${issue.carriageId}`);
          
        } catch (error) {
          failedFixes.push({
            carriageId: issue.carriageId,
            error: error.message
          });
          console.log(`❌ Не удалось подтвердить отгрузку ${issue.carriageId}: ${error.message}`);
        }
      }
    }

    return {
      totalIssues: issues.length,
      fixedCount: fixedIssues.length,
      fixedCarriages: fixedIssues,
      failedFixes
    };
  }

  // Отчет по отгрузкам
  async generateShipmentReport(days: number = 7) {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - days);

    const acts = await this.client.deliveryFbs.getActList({
      filter: {
        since: cutoffDate.toISOString(),
        to: new Date().toISOString()
      },
      limit: 1000
    });

    const report = {
      period: `${cutoffDate.toLocaleDateString()} - ${new Date().toLocaleDateString()}`,
      totalActs: acts.result?.length || 0,
      statusBreakdown: new Map<string, number>(),
      totalPostings: 0,
      averagePostingsPerAct: 0
    };

    acts.result?.forEach(act => {
      // Подсчет по статусам
      const count = report.statusBreakdown.get(act.status!) || 0;
      report.statusBreakdown.set(act.status!, count + 1);
      
      // Общее количество отправлений
      report.totalPostings += act.postings_count || 0;
    });

    report.averagePostingsPerAct = report.totalActs > 0 ? 
      Math.round(report.totalPostings / report.totalActs * 100) / 100 : 0;

    return report;
  }
}

const monitor = new ShipmentMonitor(client);

// Получить обзор отгрузок
const overview = await monitor.getShipmentsOverview();
console.log(`Всего методов доставки: ${overview.totalMethods}`);
console.log(`Всего отгрузок: ${overview.totalCarriages}`);

// Проверить проблемы
const issues = await monitor.checkProblematicShipments();
if (issues.length > 0) {
  console.log(`⚠️ Найдено ${issues.length} проблем:`);
  issues.forEach(issue => {
    console.log(`${issue.severity.toUpperCase()}: Отгрузка ${issue.carriageId} - ${issue.issue}`);
  });
}

// Сгенерировать отчет
const report = await monitor.generateShipmentReport(7);
console.log(`📊 Отчет за ${report.period}: ${report.totalActs} актов, ${report.totalPostings} отправлений`);

3. Пакетная обработка отгрузок

class BatchShipmentProcessor {
  constructor(private client: OzonSellerApiClient) {}

  // Массовое создание отгрузок по методам доставки
  async createMultipleShipments(
    shipmentRequests: Array<{
      deliveryMethodId: number;
      fromTime: string;
      toTime: string;
      comment?: string;
    }>
  ) {
    const results = [];
    const errors = [];

    for (const [index, request] of shipmentRequests.entries()) {
      try {
        console.log(`📦 Создание отгрузки ${index + 1}/${shipmentRequests.length}...`);
        
        const carriage = await this.client.deliveryFbs.createCarriage({
          delivery_method_id: request.deliveryMethodId,
          first_mile_from_time: request.fromTime,
          first_mile_to_time: request.toTime,
          comment: request.comment
        });

        if (carriage.result?.carriage_id) {
          results.push({
            deliveryMethodId: request.deliveryMethodId,
            carriageId: carriage.result.carriage_id,
            status: carriage.result.status,
            postingsCount: carriage.result.postings_count
          });
          
          console.log(`✅ Отгрузка ${carriage.result.carriage_id} создана`);
        } else {
          errors.push({
            deliveryMethodId: request.deliveryMethodId,
            error: 'Не удалось получить ID отгрузки'
          });
        }

        // Пауза между запросами
        if (index < shipmentRequests.length - 1) {
          await new Promise(resolve => setTimeout(resolve, 1000));
        }

      } catch (error) {
        errors.push({
          deliveryMethodId: request.deliveryMethodId,
          error: error.message
        });
        console.error(`❌ Ошибка создания отгрузки для метода ${request.deliveryMethodId}: ${error.message}`);
      }
    }

    return { results, errors };
  }

  // Массовое подтверждение отгрузок
  async approveMultipleCarriages(carriageIds: number[]) {
    const results = [];
    const errors = [];

    for (const carriageId of carriageIds) {
      try {
        await this.client.deliveryFbs.approveCarriage({
          carriage_id: carriageId
        });
        
        results.push(carriageId);
        console.log(`✅ Отгрузка ${carriageId} подтверждена`);
        
      } catch (error) {
        errors.push({
          carriageId,
          error: error.message
        });
        console.error(`❌ Ошибка подтверждения отгрузки ${carriageId}: ${error.message}`);
      }

      // Пауза между запросами
      await new Promise(resolve => setTimeout(resolve, 500));
    }

    return { results, errors };
  }

  // Массовое скачивание документов
  async downloadMultipleDocuments(
    carriageIds: number[],
    documentTypes: Array<'act' | 'transport_waybill'> = ['act', 'transport_waybill']
  ) {
    const fs = require('fs');
    const timestamp = Date.now();
    
    // Создаем папку для сохранения
    const folderName = `shipment-documents-${timestamp}`;
    if (!fs.existsSync(folderName)) {
      fs.mkdirSync(folderName);
    }

    const results = [];
    const errors = [];

    for (const carriageId of carriageIds) {
      try {
        // Проверяем готовность документов
        const status = await this.client.deliveryFbs.checkActStatus({
          carriage_id: carriageId
        });

        if (status.carriage_status !== 'confirmed' || status.barcode_status !== 'ready') {
          errors.push({
            carriageId,
            error: 'Документы еще не готовы'
          });
          continue;
        }

        const carriageFiles = [];

        // Скачиваем штрихкод
        const barcode = await this.client.deliveryFbs.getBarcode({
          carriage_id: carriageId
        });

        if (barcode.barcode) {
          const barcodeBuffer = Buffer.from(barcode.barcode, 'base64');
          const barcodeFilename = `${folderName}/barcode-${carriageId}.png`;
          fs.writeFileSync(barcodeFilename, barcodeBuffer);
          carriageFiles.push(barcodeFilename);
        }

        // Скачиваем документы
        for (const docType of documentTypes) {
          const document = await this.client.deliveryFbs.getAct({
            carriage_id: carriageId,
            doc_type: docType
          });

          if (document.content) {
            const docBuffer = Buffer.from(document.content, 'base64');
            const docFilename = `${folderName}/${docType}-${carriageId}.pdf`;
            fs.writeFileSync(docFilename, docBuffer);
            carriageFiles.push(docFilename);
          }
        }

        results.push({
          carriageId,
          files: carriageFiles
        });

        console.log(`✅ Документы для отгрузки ${carriageId} скачаны`);

      } catch (error) {
        errors.push({
          carriageId,
          error: error.message
        });
        console.error(`❌ Ошибка скачивания документов для отгрузки ${carriageId}: ${error.message}`);
      }

      // Пауза между запросами
      await new Promise(resolve => setTimeout(resolve, 2000));
    }

    console.log(`📁 Все документы сохранены в папку: ${folderName}`);
    return { results, errors, folder: folderName };
  }
}

const batchProcessor = new BatchShipmentProcessor(client);

// Создать несколько отгрузок одновременно
const shipmentRequests = [
  {
    deliveryMethodId: 123,
    fromTime: '09:00',
    toTime: '18:00',
    comment: 'Отгрузка курьерская доставка'
  },
  {
    deliveryMethodId: 124,
    fromTime: '10:00',
    toTime: '17:00',
    comment: 'Отгрузка самовывоз'
  }
];

const createResults = await batchProcessor.createMultipleShipments(shipmentRequests);
console.log(`Создано отгрузок: ${createResults.results.length}, ошибок: ${createResults.errors.length}`);

// Подтвердить созданные отгрузки
if (createResults.results.length > 0) {
  const carriageIds = createResults.results.map(r => r.carriageId);
  const approveResults = await batchProcessor.approveMultipleCarriages(carriageIds);
  console.log(`Подтверждено отгрузок: ${approveResults.results.length}`);
}

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

Типичные ошибки и их обработка

try {
  const carriage = await client.deliveryFbs.createCarriage({
    delivery_method_id: 123,
    first_mile_from_time: '09:00',
    first_mile_to_time: '18:00'
  });
} catch (error) {
  if (error.response?.status === 400) {
    const errorData = error.response.data;
    
    switch (errorData.code) {
      case 'NO_POSTINGS_FOR_SHIPMENT':
        console.error('Нет отправлений готовых к отгрузке');
        break;
      case 'INVALID_DELIVERY_METHOD':
        console.error('Неверный метод доставки');
        break;
      case 'INVALID_TIME_RANGE':
        console.error('Неверный временной диапазон');
        break;
      case 'CARRIAGE_ALREADY_EXISTS':
        console.error('Отгрузка для этого метода доставки уже существует');
        break;
      default:
        console.error('Неизвестная ошибка:', errorData.message);
    }
  } else if (error.response?.status === 403) {
    console.error('Недостаточно прав или функция недоступна для вашего региона');
  } else if (error.response?.status === 429) {
    console.error('Превышен лимит запросов. Повторите попытку позже.');
  } else {
    console.error('Произошла ошибка:', error.message);
  }
}

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

1. Управление жизненным циклом отгрузки

// Всегда проверяйте статус перед выполнением операций
const checkCarriageStatus = async (carriageId: number) => {
  const carriage = await client.deliveryFbs.getCarriage({
    carriage_id: carriageId
  });
  
  return carriage.result?.status;
};

// Создавайте отгрузки регулярно в определенное время
const scheduleShipmentCreation = () => {
  // Каждый день в 10:00 создавать отгрузки
  const schedule = require('node-schedule');
  
  schedule.scheduleJob('0 10 * * *', async () => {
    console.log('🕙 Запуск ежедневного создания отгрузок...');
    await batchProcessor.createMultipleShipments(dailyShipmentRequests);
  });
};

2. Мониторинг и алертинг

// Настройте мониторинг проблемных отгрузок
const setupShipmentMonitoring = () => {
  setInterval(async () => {
    const issues = await monitor.checkProblematicShipments();
    
    if (issues.length > 0) {
      const criticalIssues = issues.filter(i => i.severity === 'high');
      if (criticalIssues.length > 0) {
        console.log(`🚨 КРИТИЧЕСКИЕ ПРОБЛЕМЫ: ${criticalIssues.length}`);
        // Отправить уведомления администратору
      }
    }
  }, 30 * 60 * 1000); // Каждые 30 минут
};

3. Оптимизация производительности

// Кэшируйте статусы документов для избежания лишних запросов
class DocumentStatusCache {
  private cache = new Map<number, { status: any; timestamp: number }>();
  private readonly CACHE_TTL = 5 * 60 * 1000; // 5 минут

  async getStatus(client: OzonSellerApiClient, carriageId: number) {
    const cached = this.cache.get(carriageId);
    
    if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
      return cached.status;
    }

    const status = await client.deliveryFbs.checkActStatus({
      carriage_id: carriageId
    });
    
    this.cache.set(carriageId, {
      status,
      timestamp: Date.now()
    });
    
    return status;
  }
}

Связанные API: FBS API (управление FBS заказами), Delivery-RFBS API (доставка rFBS), Warehouse API (склады)