FBO Supply Request API
FBO Supply Request API
API для управления заявками на поставку FBO в OZON Seller API.
Количество методов: 19 методов
Обзор
FBO Supply Request API предоставляет полный цикл управления заявками на поставку FBO:
- 📝 Создание и управление черновиками заявок
- 📦 Управление грузоместами и их составом
- 🏷️ Генерация этикеток для грузомест
- ✏️ Редактирование товарного состава заявок
- ❌ Отмена заявок на поставку
- 🏭 Получение информации о складах и таймслотах
Основные возможности
🎯 Жизненный цикл заявки
- Черновик → создание с товарным составом
- Таймслот → выбор времени поставки
- Заявка → создание официальной заявки
- Грузоместа → упаковка и маркировка
- Этикетки → генерация для логистики
📦 Типы поставок
- DIRECT - прямая поставка на склад
- CROSS_DOCK - кросс-докинг через сортировочный центр
🏷️ Управление грузоместами
- Создание с указанием размеров и веса
- Распределение товаров по грузоместам
- Генерация этикеток для логистики
- Удаление и редактирование грузомест
⚠️ Важные ограничения
- Черновики действуют ограниченное время
- Нельзя редактировать заявки в статусе “В пути”
- Обязательное указание размеров и веса грузомест
- Ограничения по количеству палет в таймслоте
Методы API
Управление черновиками
createDraft()
Назначение: Создать черновик заявки на поставку
interface FboSupplyRequestDraftCreateRequest {
supply_type: 'DIRECT' | 'CROSS_DOCK';
warehouse_id: number;
items: Array<{
sku: string;
quantity: number;
}>;
}
getDraftInfo()
Назначение: Получить информацию о черновике заявки
getTimeslotInfo()
Назначение: Получить доступные таймслоты для поставки
createSupplyOrderFromDraft()
Назначение: Создать заявку на поставку по черновику
getSupplyOrderCreateStatus()
Назначение: Получить статус создания заявки
Управление грузоместами
createCargoes()
Назначение: Установить грузоместа в заявке
interface FboSupplyRequestCargoesCreateRequest {
supply_order_id: number;
cargoes: Array<{
cargo_number: string;
weight: number;
length: number;
width: number;
height: number;
items: Array<{
sku: string;
quantity: number;
}>;
}>;
}
getCargoesCreateInfo()
Назначение: Получить информацию о созданных грузоместах
deleteCargoes()
Назначение: Удалить грузоместа из заявки
getCargoesDeleteStatus()
Назначение: Получить статус удаления грузомест
getCargoRules()
Назначение: Получить правила создания грузомест
Управление этикетками
createCargoLabels()
Назначение: Сгенерировать этикетки для грузомест
interface FboSupplyRequestCargoesLabelCreateRequest {
supply_order_id: number;
cargo_ids: number[];
}
getCargoLabels()
Назначение: Получить статус генерации этикеток
getCargoLabelsFile()
Назначение: Скачать PDF файл с этикетками
Справочная информация
getClusterList()
Назначение: Получить список кластеров и складов
getWarehouseFboList()
Назначение: Найти точки отгрузки для поставки
Управление заявками
cancelSupplyOrder()
Назначение: Отменить заявку на поставку
getSupplyOrderCancelStatus()
Назначение: Получить статус отмены заявки
updateSupplyOrderContent()
Назначение: Редактировать товарный состав заявки
getSupplyOrderContentUpdateStatus()
Назначение: Получить статус редактирования состава
Практические примеры
Базовое использование
import { OzonSellerAPI } from 'bmad-ozon-seller-api';
const api = new OzonSellerAPI({
clientId: 'your-client-id',
apiKey: 'your-api-key'
});
// Создать черновик заявки
const draft = await api.fboSupplyRequest.createDraft({
supply_type: 'DIRECT',
warehouse_id: 123,
items: [
{ sku: '123456789', quantity: 10 },
{ sku: '987654321', quantity: 5 }
]
});
// Получить доступные таймслоты
const timeslots = await api.fboSupplyRequest.getTimeslotInfo({
warehouse_id: 123,
date_from: '2024-01-15',
date_to: '2024-01-20'
});
// Создать заявку на поставку
const supplyOrder = await api.fboSupplyRequest.createSupplyOrderFromDraft({
draft_id: draft.draft?.draft_id!,
timeslot_id: 'slot_67890'
});
// Установить грузоместа
const cargoes = await api.fboSupplyRequest.createCargoes({
supply_order_id: 12345,
cargoes: [{
cargo_number: 'CARGO001',
weight: 25.5,
length: 40, width: 30, height: 20,
items: [
{ sku: '123456789', quantity: 5 },
{ sku: '987654321', quantity: 3 }
]
}]
});
// Сгенерировать этикетки
const labels = await api.fboSupplyRequest.createCargoLabels({
supply_order_id: 12345,
cargo_ids: [1001, 1002]
});
Продвинутые сценарии
Менеджер поставок FBO
class FboSupplyManager {
constructor(private api: OzonSellerAPI) {}
async createCompleteSupply(items: Array<{sku: string; quantity: number}>): Promise<void> {
console.log('🚀 Начинаем создание поставки FBO');
try {
// 1. Создаем черновик
const draft = await this.api.fboSupplyRequest.createDraft({
supply_type: 'DIRECT',
warehouse_id: 123,
items
});
if (!draft.draft?.draft_id) {
throw new Error('Не удалось создать черновик');
}
console.log(`✅ Черновик создан: ${draft.draft.draft_id}`);
// 2. Получаем доступные таймслоты
const timeslots = await this.api.fboSupplyRequest.getTimeslotInfo({
warehouse_id: 123,
date_from: new Date().toISOString().split('T')[0],
date_to: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
});
const availableSlot = timeslots.timeslots?.find(slot => slot.is_available);
if (!availableSlot?.id) {
throw new Error('Нет доступных таймслотов');
}
console.log(`📅 Выбран таймслот: ${availableSlot.start_time} - ${availableSlot.end_time}`);
// 3. Создаем заявку на поставку
const createTask = await this.api.fboSupplyRequest.createSupplyOrderFromDraft({
draft_id: draft.draft.draft_id,
timeslot_id: availableSlot.id
});
// 4. Ждем создания заявки
const supplyOrderId = await this.waitForTaskCompletion(
createTask.task_id!,
(taskId) => this.api.fboSupplyRequest.getSupplyOrderCreateStatus({ task_id: taskId })
);
console.log(`✅ Заявка создана: ${supplyOrderId}`);
// 5. Создаем оптимальные грузоместа
const cargoes = await this.createOptimalCargoes(supplyOrderId, items);
// 6. Генерируем этикетки
const labelsFile = await this.generateLabels(supplyOrderId, cargoes.map(c => c.cargo_id));
console.log('🎉 Поставка FBO полностью готова!');
console.log(`📄 Этикетки: ${labelsFile}`);
} catch (error) {
console.error('❌ Ошибка создания поставки:', error);
throw error;
}
}
private async createOptimalCargoes(supplyOrderId: number, items: Array<{sku: string; quantity: number}>): Promise<any[]> {
// Оптимизация упаковки товаров в грузоместа
const cargoes = this.optimizePackaging(items);
const createTask = await this.api.fboSupplyRequest.createCargoes({
supply_order_id: supplyOrderId,
cargoes: cargoes.map((cargo, index) => ({
cargo_number: `CARGO${String(index + 1).padStart(3, '0')}`,
weight: cargo.weight,
length: cargo.dimensions.length,
width: cargo.dimensions.width,
height: cargo.dimensions.height,
items: cargo.items
}))
});
const result = await this.waitForTaskCompletion(
createTask.task_id!,
(taskId) => this.api.fboSupplyRequest.getCargoesCreateInfo({ task_id: taskId })
);
console.log(`📦 Создано грузомест: ${result.cargoes?.length || 0}`);
return result.cargoes || [];
}
private optimizePackaging(items: Array<{sku: string; quantity: number}>): any[] {
// Простая логика оптимизации упаковки
const cargoes = [];
let currentCargo = {
items: [] as any[],
weight: 0,
dimensions: { length: 40, width: 30, height: 20 }
};
const maxWeight = 30; // кг
const weightPerItem = 2; // средний вес товара
for (const item of items) {
const itemWeight = item.quantity * weightPerItem;
if (currentCargo.weight + itemWeight > maxWeight) {
if (currentCargo.items.length > 0) {
cargoes.push({ ...currentCargo });
}
currentCargo = {
items: [item],
weight: itemWeight,
dimensions: { length: 40, width: 30, height: 20 }
};
} else {
currentCargo.items.push(item);
currentCargo.weight += itemWeight;
}
}
if (currentCargo.items.length > 0) {
cargoes.push(currentCargo);
}
return cargoes;
}
private async generateLabels(supplyOrderId: number, cargoIds: number[]): Promise<string> {
const labelTask = await this.api.fboSupplyRequest.createCargoLabels({
supply_order_id: supplyOrderId,
cargo_ids: cargoIds
});
const result = await this.waitForTaskCompletion(
labelTask.task_id!,
(taskId) => this.api.fboSupplyRequest.getCargoLabels({ task_id: taskId })
);
if (result.file_guid) {
console.log('🏷️ Этикетки сгенерированы');
return result.file_guid;
}
throw new Error('Не удалось сгенерировать этикетки');
}
private async waitForTaskCompletion(taskId: string, statusChecker: (id: string) => Promise<any>): Promise<any> {
const maxAttempts = 30;
const delay = 2000; // 2 секунды
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await statusChecker(taskId);
if (status.status === 'completed') {
return status;
} else if (status.status === 'error') {
throw new Error(status.error_message || 'Ошибка выполнения задачи');
}
console.log(`⏳ Ожидание завершения задачи... (${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Превышено время ожидания выполнения задачи');
}
}
Система мониторинга поставок
class FboSupplyMonitor {
constructor(private api: OzonSellerAPI) {}
async monitorActiveSupplies(): Promise<void> {
// Получаем информацию о доступных складах
const warehouses = await this.api.fboSupplyRequest.getWarehouseFboList({
warehouse_type: 'DIRECT'
});
console.log('🏭 Мониторинг активных поставок FBO');
console.log('='.repeat(50));
// Анализируем доступность складов
const availableWarehouses = warehouses.warehouses?.filter(w => w.is_available) || [];
console.log(`✅ Доступных складов: ${availableWarehouses.length}`);
for (const warehouse of availableWarehouses) {
await this.analyzeWarehouseCapacity(warehouse);
}
// Проверяем правила создания грузомест
await this.checkCargoRules();
}
private async analyzeWarehouseCapacity(warehouse: any): Promise<void> {
console.log(`\n🏭 Анализ склада: ${warehouse.name}`);
console.log(`📍 Адрес: ${warehouse.address}`);
console.log(`📊 Тип: ${warehouse.type}`);
try {
// Получаем доступные таймслоты на ближайшую неделю
const timeslots = await this.api.fboSupplyRequest.getTimeslotInfo({
warehouse_id: warehouse.warehouse_id,
date_from: new Date().toISOString().split('T')[0],
date_to: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
});
const availableSlots = timeslots.timeslots?.filter(slot => slot.is_available) || [];
console.log(`📅 Доступных слотов на неделю: ${availableSlots.length}`);
if (availableSlots.length > 0) {
const totalCapacity = availableSlots.reduce((sum, slot) => sum + (slot.max_pallets || 0), 0);
console.log(`📦 Общая вместимость: ${totalCapacity} палет`);
// Найдем ближайший доступный слот
const nearestSlot = availableSlots[0];
console.log(`⏰ Ближайший слот: ${nearestSlot.start_time} - ${nearestSlot.end_time}`);
console.log(`📦 Макс. палет: ${nearestSlot.max_pallets}`);
} else {
console.log('⚠️ Нет доступных слотов на ближайшую неделю');
}
} catch (error) {
console.log(`❌ Ошибка анализа склада: ${error.message}`);
}
}
private async checkCargoRules(): Promise<void> {
console.log('\n📋 Проверка правил создания грузомест');
try {
const rules = await this.api.fboSupplyRequest.getCargoRules({});
console.log('📜 Действующие правила:');
rules.rules?.forEach((rule, index) => {
const required = rule.required ? '[ОБЯЗАТЕЛЬНО]' : '[РЕКОМЕНДУЕТСЯ]';
console.log(`${index + 1}. ${required} ${rule.type}: ${rule.description}`);
if (rule.min_value !== undefined || rule.max_value !== undefined) {
console.log(` Ограничения: ${rule.min_value || 'мин'} - ${rule.max_value || 'макс'}`);
}
});
} catch (error) {
console.log(`❌ Ошибка получения правил: ${error.message}`);
}
}
async generateSupplyReport(dateFrom: string, dateTo: string): Promise<void> {
console.log(`\n📊 Отчет по поставкам FBO (${dateFrom} - ${dateTo})`);
console.log('='.repeat(60));
// Получаем список кластеров для анализа
const clusters = await this.api.fboSupplyRequest.getClusterList({});
console.log('\n🌐 Анализ по кластерам:');
clusters.clusters?.forEach(cluster => {
console.log(`\n🏭 Кластер: ${cluster.name} (ID: ${cluster.cluster_id})`);
console.log(`📍 Регион: ${cluster.region || 'Не указан'}`);
cluster.warehouses?.forEach(warehouse => {
const status = warehouse.is_available ? '✅ Доступен' : '❌ Недоступен';
console.log(` 📦 ${warehouse.name} (${warehouse.type}): ${status}`);
if (warehouse.working_hours) {
console.log(` ⏰ Время работы: ${warehouse.working_hours}`);
}
});
});
// Рекомендации по оптимизации
console.log('\n💡 Рекомендации по оптимизации:');
console.log('1. Планируйте поставки заранее - слоты быстро заполняются');
console.log('2. Используйте оптимальную упаковку для экономии места');
console.log('3. Следите за правилами создания грузомест');
console.log('4. Рассматривайте кросс-докинг для больших объемов');
}
}
Автоматизированный редактор заявок
class SupplyOrderEditor {
constructor(private api: OzonSellerAPI) {}
async updateSupplyContent(
supplyOrderId: number,
changes: Array<{sku: string; newQuantity: number}>
): Promise<void> {
console.log(`✏️ Редактирование заявки ${supplyOrderId}`);
try {
// Подготавливаем операции обновления
const operations = changes.map(change => {
if (change.newQuantity === 0) {
return { sku: change.sku, quantity: 0, operation: 'delete' as const };
} else {
return { sku: change.sku, quantity: change.newQuantity, operation: 'update' as const };
}
});
// Выполняем обновление
const updateTask = await this.api.fboSupplyRequest.updateSupplyOrderContent({
supply_order_id: supplyOrderId,
items: operations
});
console.log(`🔄 Запущена задача редактирования: ${updateTask.task_id}`);
// Ждем результата
const result = await this.waitForUpdateCompletion(updateTask.task_id!);
// Анализируем результаты
let successCount = 0;
let errorCount = 0;
result.results?.forEach((itemResult: any) => {
if (itemResult.success) {
successCount++;
console.log(`✅ ${itemResult.sku} (${itemResult.operation}): успешно`);
} else {
errorCount++;
console.log(`❌ ${itemResult.sku} (${itemResult.operation}): ${itemResult.error_message}`);
}
});
console.log(`\n📊 Результат редактирования:`);
console.log(`✅ Успешно: ${successCount}`);
console.log(`❌ Ошибок: ${errorCount}`);
// Если есть ошибки, пересоздаем грузоместа
if (successCount > 0 && errorCount === 0) {
console.log('🔄 Пересоздаем грузоместа после изменений...');
await this.recreateCargoes(supplyOrderId);
}
} catch (error) {
console.error('❌ Ошибка редактирования заявки:', error);
throw error;
}
}
private async recreateCargoes(supplyOrderId: number): Promise<void> {
// Здесь была бы логика удаления старых и создания новых грузомест
// с учетом обновленного товарного состава
console.log('📦 Грузоместа обновлены с учетом изменений');
}
private async waitForUpdateCompletion(taskId: string): Promise<any> {
const maxAttempts = 20;
const delay = 1000;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await this.api.fboSupplyRequest.getSupplyOrderContentUpdateStatus({
task_id: taskId
});
if (status.status === 'completed') {
return status;
} else if (status.status === 'error') {
throw new Error(status.error_message || 'Ошибка обновления');
}
console.log(`⏳ Обновление в процессе... (${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Превышено время ожидания обновления');
}
async cancelSupplyIfNeeded(supplyOrderId: number, reason: string): Promise<boolean> {
console.log(`❌ Попытка отмены заявки ${supplyOrderId}`);
console.log(`📝 Причина: ${reason}`);
try {
const cancelTask = await this.api.fboSupplyRequest.cancelSupplyOrder({
supply_order_id: supplyOrderId,
reason
});
const result = await this.waitForCancelCompletion(cancelTask.task_id!);
if (result.success) {
console.log('✅ Заявка успешно отменена');
return true;
} else {
console.log(`❌ Ошибка отмены: ${result.error_message}`);
return false;
}
} catch (error) {
console.error('❌ Ошибка процесса отмены:', error);
return false;
}
}
private async waitForCancelCompletion(taskId: string): Promise<any> {
const maxAttempts = 15;
const delay = 2000;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await this.api.fboSupplyRequest.getSupplyOrderCancelStatus({
task_id: taskId
});
if (status.status === 'completed') {
return status;
} else if (status.status === 'error') {
throw new Error(status.error_message || 'Ошибка отмены');
}
console.log(`⏳ Отмена в процессе... (${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Превышено время ожидания отмены');
}
}
Обработка ошибок
try {
await api.fboSupplyRequest.createDraft({
supply_type: 'DIRECT',
warehouse_id: 123,
items: [/* ... */]
});
} catch (error) {
if (error.response?.status === 400) {
console.error('Ошибка валидации данных:', error.response.data);
} else if (error.response?.status === 404) {
console.error('Склад не найден или недоступен');
} else if (error.response?.status === 409) {
console.error('Конфликт состояния (например, склад заполнен)');
} else {
console.error('Неожиданная ошибка:', error.message);
}
}
Рекомендации по использованию
🎯 Планирование поставок
- Создавайте черновики заранее для резервирования товаров
- Проверяйте доступность таймслотов на несколько дней вперед
- Учитывайте время на подготовку грузомест и этикеток
📦 Оптимизация грузомест
- Равномерно распределяйте товары по весу и размеру
- Соблюдайте максимальные ограничения по весу (обычно 30 кг)
- Учитывайте хрупкость товаров при упаковке
- Используйте правила создания грузомест из API
🏷️ Работа с этикетками
- Генерируйте этикетки заранее для ускорения отгрузки
- Проверяйте качество PDF файлов перед печатью
- Сохраняйте копии этикеток для отчетности
🚀 Автоматизация
- Автоматизируйте создание оптимальных грузомест
- Настройте мониторинг доступности таймслотов
- Используйте уведомления об изменении статусов задач
🔒 Безопасность и надежность
- Всегда проверяйте статус выполнения асинхронных задач
- Реализуйте retry-логику для критически важных операций
- Логируйте все этапы создания поставки для отладки
FBO Supply Request API обеспечивает полный контроль над процессом поставок FBO, от создания черновика до получения готовых этикеток для отгрузки.