1С и Wildberries связывают тремя способами: типовым механизмом конфигурации (если он есть в вашем релизе), готовым коннектором или своим модулем на WB API. Ниже — что выгружать из 1С на маркетплейс и что забирать обратно, код своего модуля (токен, сопоставление по баркодам, остатки, цены, сборочные задания FBS) и типичные проблемы с баркодами, складами и лимитами. Методы сверены с документацией dev.wildberries.ru в октябре 2026 года; WB часто меняет API, перед запуском проверьте их ещё раз.
| Вариант | Когда подходит | Что проверить |
|---|---|---|
| Типовой механизм | работа с WB заявлена в описании релиза | какие модели и данные поддержаны |
| Готовый коннектор | стандартные процессы, нужен быстрый старт | расширение вместо правки типовой, поддержка вашей конфигурации, скорость обновлений под изменения API |
| Свой модуль | несколько кабинетов и складов, своя схема резервов | где хранится токен, где видны ошибки, кто следит за изменениями API |
С типовым механизмом осторожнее. В выгрузке УТ 11.4.14 объектов для маркетплейсов нет. В КА 2.5.20 интеграция с маркетплейсами есть, но в перечислении «Виды маркетплейсов» только Ozon и Яндекс Маркет (подробно — в статье про 1С и Ozon). Есть ли Wildberries в более новых релизах УТ 11.5, КА 2.5, ERP, БП и УНФ, смотрите в описании релиза на its.1c.ru. Лимиты WB считаются на аккаунт продавца, поэтому коннектор и ваши доработки делят один бюджет запросов.
Общая схема своего модуля — справочник кабинетов, регистры сопоставления, регламентное задание, создание заказа клиента — та же, что в статье про 1С и Яндекс Маркет. Здесь — только специфика Wildberries.
| Данные | Направление | Метод WB API |
|---|---|---|
| Карточки, размеры, баркоды | WB → 1С | POST /content/v2/get/cards/list |
| Остатки на складах продавца | 1С → WB | PUT /api/v3/stocks/{warehouseId} |
| Цены и скидки | 1С → WB | POST /api/v2/upload/task |
| Новые сборочные задания FBS | WB → 1С | GET /api/v3/orders/new |
| Поставки FBS | 1С → WB | POST /api/v3/supplies и другие методы поставок |
| Продажи, финансовые отчёты | WB → 1С | категории «Статистика» и «Финансы» |
Главная путаница — в идентификаторах: артикул продавца vendorCode, артикул WB nmID (карточка), ID размера chrtID, баркоды размера skus. Цены задаются на карточку, остатки — на размер, причём в карточке пишут chrtID, а в остатках и заданиях — chrtId. Поэтому сопоставление в 1С ведут по размеру. Остатки на складах WB продавец не выгружает: метод работает только со складами продавца.
Токен создают в личном кабинете продавца, отмечая категории: модулю нужны «Контент», «Цены и скидки» и «Маркетплейс». У каждой категории свой хост: content-api.wildberries.ru, discounts-prices-api.wildberries.ru, marketplace-api.wildberries.ru. Продлить токен нельзя — по окончании срока выпускают новый. В запросе он передаётся в заголовке Authorization как есть, без Bearer.
Объекты расширения условные: справочник КабинетыWB (вид цен, скидка), регистры сведений ТоварыWB (кабинет и ID размера → номенклатура, характеристика, артикул WB, баркод, артикул продавца), СкладыWB (ID склада WB → склад 1С) и СборочныеЗаданияWB (ID задания → заказ клиента). Токен лежит в безопасном хранилище БСП, форма кабинета пишет его через ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище(Кабинет, Токен, "ТокенWB"). Расширение подключайте без безопасного режима или с профилем безопасности. Основы — в статье HTTP-запрос из 1С.
// Общий модуль ОбменWildberries (условное имя): флажок «Сервер», остальные сняты.
// Справочник КабинетыWB и регистры сведений ТоварыWB, СкладыWB, СборочныеЗаданияWB —
// условные объекты расширения, их структура описана в тексте статьи.
// Вызывает метод WB API. Хост — домен категории: content-api.wildberries.ru,
// discounts-prices-api.wildberries.ru, marketplace-api.wildberries.ru.
// Исключений не вызывает: продолжать ли обмен, решает вызывающий код.
Функция ВызватьWB(Кабинет, Хост, HTTPМетод, АдресРесурса, Данные = Неопределено) Экспорт
Результат = Новый Структура("Успех, КодСостояния, Ответ, ТекстОшибки, МожноПродолжить",
Ложь, 0, Неопределено, "", Ложь);
Операция = HTTPМетод + " " + АдресРесурса;
УстановитьПривилегированныйРежим(Истина);
Токен = ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища(Кабинет, "ТокенWB");
УстановитьПривилегированныйРежим(Ложь);
Если Не ЗначениеЗаполнено(Токен) Тогда
Результат.ТекстОшибки = "Не задан токен WB API";
ЗаписатьЖурналWB(Кабинет, Операция + ": " + Результат.ТекстОшибки);
Возврат Результат;
КонецЕсли;
Заголовки = Новый Соответствие;
Заголовки.Вставить("Authorization", Токен); // сам токен, без префикса Bearer
Заголовки.Вставить("Accept", "application/json");
ЗапросHTTP = Новый HTTPЗапрос(АдресРесурса, Заголовки);
Если Данные <> Неопределено Тогда
ЗапросHTTP.Заголовки.Вставить("Content-Type", "application/json");
ЗапросHTTP.УстановитьТелоИзСтроки(ЗначениеВJSON(Данные), КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
КонецЕсли;
Попытка
Соединение = Новый HTTPСоединение(Хост, 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL());
Ответ = Соединение.ВызватьHTTPМетод(HTTPМетод, ЗапросHTTP);
Исключение
Результат.ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписатьЖурналWB(Кабинет, Операция + ": " + Результат.ТекстОшибки);
Возврат Результат;
КонецПопытки;
Результат.КодСостояния = Ответ.КодСостояния;
Результат.Успех = (Ответ.КодСостояния >= 200 И Ответ.КодСостояния < 300);
ТелоОтвета = Строка(Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
Если ЗначениеЗаполнено(ТелоОтвета) Тогда
Попытка
Результат.Ответ = ЗначениеИзJSON(ТелоОтвета);
Исключение
Результат.Ответ = Неопределено; // не JSON: начало тела попадет в текст ошибки
КонецПопытки;
КонецЕсли;
// 400 и 409 — ошибка в данных этой пачки, следующую отправлять можно.
// 401, 403, 429 и 5xx повторятся на любом запросе: обмен ждет следующего запуска.
Результат.МожноПродолжить = Результат.Успех
Или Ответ.КодСостояния = 400 Или Ответ.КодСостояния = 409;
Если Результат.Успех Тогда
Возврат Результат;
КонецЕсли;
Результат.ТекстОшибки = ТекстОшибкиWB(Результат.Ответ, ТелоОтвета);
Если Ответ.КодСостояния = 429 Тогда
ПаузаСекунд = ЗначениеЗаголовка(Ответ.Заголовки, "X-Ratelimit-Retry");
Результат.ТекстОшибки = СтрШаблон("Лимит запросов, повтор через %1 с. %2",
?(ПустаяСтрока(ПаузаСекунд), "?", ПаузаСекунд), Результат.ТекстОшибки);
КонецЕсли;
ЗаписатьЖурналWB(Кабинет, СтрШаблон("%1: HTTP %2. %3", Операция,
Формат(Ответ.КодСостояния, "ЧГ=0"), Результат.ТекстОшибки));
Возврат Результат;
КонецФункции
// У сервисов WB разный формат ошибки: title и detail, code и message, errorText,
// а при 409 в остатках — массив объектов, в data которых chrtId или баркод (sku).
Функция ТекстОшибкиWB(Ответ, ТелоОтвета)
Описания = Новый Массив;
Если ТипЗнч(Ответ) = Тип("Массив") Тогда
Описания = Ответ;
ИначеЕсли ТипЗнч(Ответ) = Тип("Соответствие") Тогда
Описания.Добавить(Ответ);
КонецЕсли;
Части = Новый Массив;
Для Каждого Описание Из Описания Цикл
Если ТипЗнч(Описание) <> Тип("Соответствие") Тогда
Продолжить;
КонецЕсли;
Для Каждого Поле Из СтрРазделить("title,detail,code,message,errorText", ",") Цикл
Значение = Описание.Получить(Поле);
Если ТипЗнч(Значение) = Тип("Строка") И ЗначениеЗаполнено(Значение) Тогда
Части.Добавить(Значение);
КонецЕсли;
КонецЦикла;
Позиции = Описание.Получить("data");
Если ТипЗнч(Позиции) = Тип("Массив") Тогда
Для Каждого Позиция Из Позиции Цикл
Если ТипЗнч(Позиция) <> Тип("Соответствие") Тогда
Продолжить;
КонецЕсли;
Если Позиция.Получить("chrtId") <> Неопределено Тогда
Части.Добавить("chrtId " + Формат(Позиция.Получить("chrtId"), "ЧГ=0"));
ИначеЕсли ЗначениеЗаполнено(Позиция.Получить("sku")) Тогда
Части.Добавить("баркод " + Позиция.Получить("sku"));
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Если Части.Количество() = 0 Тогда
Возврат Лев(ТелоОтвета, 300);
КонецЕсли;
Возврат Лев(СтрСоединить(Части, "; "), 1000);
КонецФункции
// Значение заголовка ответа без учета регистра имени.
Функция ЗначениеЗаголовка(Заголовки, Имя)
Для Каждого Заголовок Из Заголовки Цикл
Если НРег(Заголовок.Ключ) = НРег(Имя) Тогда
Возврат Строка(Заголовок.Значение);
КонецЕсли;
КонецЦикла;
Возврат "";
КонецФункции
Функция ЗначениеВJSON(Значение)
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Значение);
Возврат Запись.Закрыть();
КонецФункции
Функция ЗначениеИзJSON(Текст)
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Текст);
Значение = ПрочитатьJSON(Чтение, Истина); // Соответствие: доступ через Получить()
Чтение.Закрыть();
Возврат Значение;
КонецФункции
Процедура ЗаписатьЖурналWB(Кабинет, Текст, Уровень = Неопределено) Экспорт
ЗаписьЖурналаРегистрации("Wildberries.Обмен",
?(Уровень = Неопределено, УровеньЖурналаРегистрации.Ошибка, Уровень),
Метаданные.Справочники.КабинетыWB, Кабинет, Текст);
КонецПроцедурыЧто важно в обёртке:
X-Ratelimit-Retry, через сколько секунд повторять; заголовок ищем без учёта регистра.title и detail, code и message, errorText, а при 409 в остатках — массив с ID размеров. ТекстОшибкиWB собирает всё в одну строку журнала.МожноПродолжить: после 400 и 409 следующую пачку отправлять можно, после остальных ошибок — нет./ping показывает, доходит ли запрос, действует ли токен и подходит ли его категория сервису. Лимит — 3 запроса за 30 секунд на хост, автоматические вызовы WB временно блокирует, поэтому проверку вешают на кнопку в форме кабинета.
// Проверка токена кнопкой из формы кабинета (через серверный вызов):
// /ping каждого хоста, с которым работает обмен. Лимит /ping — 3 запроса
// за 30 секунд на хост, поэтому в регламентном обмене его не вызывают.
Функция ПроверитьТокенWB(Кабинет) Экспорт
Итог = Новый Массив;
Хосты = СтрРазделить("content-api,discounts-prices-api,marketplace-api", ",");
Для Каждого Хост Из Хосты Цикл
Результат = ВызватьWB(Кабинет, Хост + ".wildberries.ru", "GET", "/ping");
Если Результат.Успех Тогда
Итог.Добавить(Хост + ": OK");
Иначе
Итог.Добавить(СтрШаблон("%1: HTTP %2. %3", Хост,
Формат(Результат.КодСостояния, "ЧН=0; ЧГ=0"), Результат.ТекстОшибки));
КонецЕсли;
КонецЦикла;
Возврат СтрСоединить(Итог, Символы.ПС);
КонецФункцииКарточки отдаются страницами до 100: следующую запрашивают с updatedAt и nmID из курсора ответа, пока total не станет меньше limit. Лимит категории «Контент» — 100 запросов в минуту. Баркоды ищем в регистре ШтрихкодыНоменклатуры (УТ 11.4, КА 2.5: штрихкод → номенклатура, характеристика, упаковка), подробнее — в статье про штрихкоды товаров.
// Читает карточки кабинета и сопоставляет новые размеры (chrtID) с номенклатурой
// по баркодам. Уже сопоставленные размеры, в том числе вручную, не трогает.
Процедура ОбновитьТоварыWB(Кабинет) Экспорт
РазмерСтраницы = 100; // больше карточек за один запрос метод не отдает
ТипЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 0));
ТипАртикул = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100));
ТипБаркод = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(200));
Размеры = Новый ТаблицаЗначений; // колонки с типами: таблица уйдет в запрос
Размеры.Колонки.Добавить("ИдентификаторРазмера", ТипЧисло);
Размеры.Колонки.Добавить("АртикулWB", ТипЧисло);
Размеры.Колонки.Добавить("АртикулПродавца", ТипАртикул);
Размеры.Колонки.Добавить("Баркод", ТипБаркод);
Курсор = Новый Структура("limit", РазмерСтраницы);
Для НомерСтраницы = 1 По 1000 Цикл // предохранитель: не больше 100 000 карточек за запуск
Настройки = Новый Структура;
Настройки.Вставить("sort", Новый Структура("ascending", Истина));
Настройки.Вставить("filter", Новый Структура("withPhoto", -1)); // все карточки
Настройки.Вставить("cursor", Курсор);
Результат = ВызватьWB(Кабинет, "content-api.wildberries.ru", "POST",
"/content/v2/get/cards/list", Новый Структура("settings", Настройки));
Если Не Результат.Успех Или ТипЗнч(Результат.Ответ) <> Тип("Соответствие") Тогда
Прервать; // причина в журнале, уже прочитанное сопоставим
КонецЕсли;
Карточки = Результат.Ответ.Получить("cards");
Если ТипЗнч(Карточки) = Тип("Массив") Тогда
Для Каждого Карточка Из Карточки Цикл
ДобавитьРазмерыКарточки(Размеры, Карточка);
КонецЦикла;
КонецЕсли;
// Следующая страница — с updatedAt и nmID из курсора ответа, пока total не меньше limit.
КурсорОтвета = Результат.Ответ.Получить("cursor");
Всего = ?(ТипЗнч(КурсорОтвета) = Тип("Соответствие"), КурсорОтвета.Получить("total"), 0);
Если ТипЗнч(Всего) <> Тип("Число") Или Всего < РазмерСтраницы Тогда
Прервать;
КонецЕсли;
Курсор = Новый Структура("limit, updatedAt, nmID", РазмерСтраницы,
КурсорОтвета.Получить("updatedAt"), КурсорОтвета.Получить("nmID"));
КонецЦикла;
СопоставитьРазмерыПоБаркодам(Кабинет, Размеры);
КонецПроцедуры
// По строке на каждый баркод размера. Размер без баркодов — строка с пустым баркодом,
// чтобы он попал в журнал несопоставленных.
Процедура ДобавитьРазмерыКарточки(Размеры, Карточка)
РазмерыКарточки = Карточка.Получить("sizes");
Если ТипЗнч(РазмерыКарточки) <> Тип("Массив") Тогда
Возврат;
КонецЕсли;
Для Каждого Размер Из РазмерыКарточки Цикл
Баркоды = Размер.Получить("skus");
Если ТипЗнч(Баркоды) <> Тип("Массив") Или Баркоды.Количество() = 0 Тогда
Баркоды = Новый Массив;
Баркоды.Добавить("");
КонецЕсли;
Для Каждого Баркод Из Баркоды Цикл
НоваяСтрока = Размеры.Добавить();
НоваяСтрока.ИдентификаторРазмера = Размер.Получить("chrtID"); // в карточке ID заглавными
НоваяСтрока.АртикулWB = Карточка.Получить("nmID");
НоваяСтрока.АртикулПродавца = Карточка.Получить("vendorCode");
НоваяСтрока.Баркод = Баркод;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
// Все штучные штрихкоды размера (без упаковки) должны вести к одной номенклатуре
// и характеристике. Остальное — в журнал для ручного разбора.
Процедура СопоставитьРазмерыПоБаркодам(Кабинет, Размеры)
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Размеры.ИдентификаторРазмера КАК ИдентификаторРазмера,
| Размеры.АртикулWB КАК АртикулWB,
| Размеры.АртикулПродавца КАК АртикулПродавца,
| Размеры.Баркод КАК Баркод
|ПОМЕСТИТЬ ВтРазмеры
|ИЗ
| &Размеры КАК Размеры
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВтРазмеры.ИдентификаторРазмера КАК ИдентификаторРазмера,
| МАКСИМУМ(ВтРазмеры.АртикулWB) КАК АртикулWB,
| МАКСИМУМ(ВтРазмеры.АртикулПродавца) КАК АртикулПродавца,
| ЕСТЬNULL(МАКСИМУМ(Штрихкоды.Штрихкод), МАКСИМУМ(ВтРазмеры.Баркод)) КАК Баркод,
| МАКСИМУМ(Штрихкоды.Номенклатура) КАК Номенклатура,
| МАКСИМУМ(Штрихкоды.Характеристика) КАК Характеристика,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Штрихкоды.Номенклатура) КАК ВариантовНоменклатуры,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Штрихкоды.Характеристика) КАК ВариантовХарактеристик
|ИЗ
| ВтРазмеры КАК ВтРазмеры
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
| ПО ВтРазмеры.Баркод = Штрихкоды.Штрихкод
| И (ВтРазмеры.Баркод <> """")
| И (Штрихкоды.Упаковка = ЗНАЧЕНИЕ(Справочник.УпаковкиЕдиницыИзмерения.ПустаяСсылка))
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ТоварыWB КАК Сопоставленные
| ПО (Сопоставленные.Кабинет = &Кабинет)
| И ВтРазмеры.ИдентификаторРазмера = Сопоставленные.ИдентификаторРазмера
|ГДЕ
| Сопоставленные.ИдентификаторРазмера ЕСТЬ NULL
|
|СГРУППИРОВАТЬ ПО
| ВтРазмеры.ИдентификаторРазмера");
Запрос.УстановитьПараметр("Размеры", Размеры);
Запрос.УстановитьПараметр("Кабинет", Кабинет);
Выборка = Запрос.Выполнить().Выбрать();
Несопоставленные = Новый Массив;
Пока Выборка.Следующий() Цикл
// 0 — штрихкод не найден, больше 1 — баркоды размера ведут к разным товарам.
Если Выборка.ВариантовНоменклатуры <> 1 Или Выборка.ВариантовХарактеристик <> 1 Тогда
Несопоставленные.Добавить(СтрШаблон("%1 (%2)", Выборка.АртикулПродавца, Выборка.Баркод));
Продолжить;
КонецЕсли;
Запись = РегистрыСведений.ТоварыWB.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(Запись, Выборка);
Запись.Кабинет = Кабинет;
Запись.Записать();
КонецЦикла;
Если Несопоставленные.Количество() > 0 Тогда
ЗаписатьЖурналWB(Кабинет, "Не сопоставлены по баркодам, артикул продавца (баркод): "
+ СтрСоединить(Несопоставленные, ", "), УровеньЖурналаРегистрации.Предупреждение);
КонецЕсли;
КонецПроцедурыБерутся только штучные штрихкоды, без упаковки. Ручное сопоставление код не перезапишет: размеры с записью в ТоварыWB отсекает ЕСТЬ NULL. КОЛИЧЕСТВО(РАЗЛИЧНЫЕ …) ловит неоднозначность: если баркоды размера ведут к разным товарам, размер уходит в журнал. Карточки из корзины WB метод списка не возвращает.
Склад продавца в WB — не склад 1С: его создают в кабинете или методом POST /api/v3/warehouses и привязывают к складу WB (officeId), куда сдают заказы FBS. GET /api/v3/warehouses отдаёт ID, тип товара cargoType (МГТ, СГТ, КГТ+), тип доставки deliveryType и флаг isProcessing: пока склад обновляется, менять его остатки нельзя.
Остатки уходят парами chrtId и amount, включая нули, — иначе WB продолжит продавать старый остаток. Поэтому запрос строится от регистра сопоставления с левым соединением к виртуальной таблице остатков; отладить его удобно в бесплатной консоли запросов. Лимит — 300 запросов в минуту на аккаунт (интервал 200 мс, всплеск до 20), а любой ответ с кодом 4xx, в том числе 409, считается за 10 запросов.
// Выгружает свободные остатки на склады продавца WB (соответствие складов —
// регистр СкладыWB). Нули тоже: иначе WB продолжит продавать старый остаток.
Процедура ВыгрузитьОстаткиWB(Кабинет) Экспорт
РазмерПачки = 1000; // не больше 1000 позиций в одном запросе
Доступные = ДоступныеСкладыWB(Кабинет);
Если Доступные = Неопределено Тогда
Возврат; // список складов не получен, причина в журнале
КонецЕсли;
// УТ 11.4: в наличии минус резервы — как «Доступно сейчас» в отчете
// «Остатки и доступность товаров», без резервов по графику.
Запрос = Новый Запрос(
"ВЫБРАТЬ
| СкладыWB.ИдентификаторСклада КАК ИдентификаторСклада,
| Товары.ИдентификаторРазмера КАК ИдентификаторРазмера,
| ЕСТЬNULL(Остатки.ВНаличииОстаток, 0)
| - ЕСТЬNULL(Остатки.ВРезервеСоСкладаОстаток, 0)
| - ЕСТЬNULL(Остатки.ВРезервеПодЗаказОстаток, 0) КАК Остаток
|ИЗ
| РегистрСведений.СкладыWB КАК СкладыWB
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ТоварыWB КАК Товары
| ПО СкладыWB.Кабинет = Товары.Кабинет
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстатки.Остатки(
| ,
| Склад В
| (ВЫБРАТЬ
| Склады.Склад
| ИЗ
| РегистрСведений.СкладыWB КАК Склады
| ГДЕ
| Склады.Кабинет = &Кабинет)) КАК Остатки
| ПО СкладыWB.Склад = Остатки.Склад
| И Товары.Номенклатура = Остатки.Номенклатура
| И Товары.Характеристика = Остатки.Характеристика
|ГДЕ
| СкладыWB.Кабинет = &Кабинет
|
|УПОРЯДОЧИТЬ ПО
| ИдентификаторСклада
|ИТОГИ ПО
| ИдентификаторСклада");
Запрос.УстановитьПараметр("Кабинет", Кабинет);
ПоСкладам = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ПоСкладам.Следующий() Цикл
НомерСклада = Формат(ПоСкладам.ИдентификаторСклада, "ЧГ=0");
Если Доступные.Найти(ПоСкладам.ИдентификаторСклада) = Неопределено Тогда
ЗаписатьЖурналWB(Кабинет, "Склад WB " + НомерСклада
+ " не найден, обновляется или удаляется: остатки не отправлены",
УровеньЖурналаРегистрации.Предупреждение);
Продолжить;
КонецЕсли;
АдресРесурса = "/api/v3/stocks/" + НомерСклада;
Пачка = Новый Массив;
Строки = ПоСкладам.Выбрать();
Пока Строки.Следующий() Цикл
Пачка.Добавить(Новый Структура("chrtId, amount",
Строки.ИдентификаторРазмера, Макс(0, Цел(Строки.Остаток))));
Если Пачка.Количество() = РазмерПачки Тогда
Если Не ОтправитьОстаткиWB(Кабинет, АдресРесурса, Пачка) Тогда
Возврат;
КонецЕсли;
Пачка = Новый Массив;
КонецЕсли;
КонецЦикла;
Если Пачка.Количество() > 0 И Не ОтправитьОстаткиWB(Кабинет, АдресРесурса, Пачка) Тогда
Возврат; // токен, лимит или связь: остальное уйдет при следующем запуске
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// PUT остатков одной пачки, успех — ответ 204 без тела.
// Ложь — продолжать обмен до следующего запуска бессмысленно.
Функция ОтправитьОстаткиWB(Кабинет, АдресРесурса, Пачка)
Результат = ВызватьWB(Кабинет, "marketplace-api.wildberries.ru", "PUT", АдресРесурса,
Новый Структура("stocks", Пачка));
Возврат Результат.МожноПродолжить;
КонецФункции
// ID складов продавца, по которым сейчас можно менять остатки.
// Пока у склада isProcessing = true, обновление его остатков недоступно.
Функция ДоступныеСкладыWB(Кабинет)
Результат = ВызватьWB(Кабинет, "marketplace-api.wildberries.ru", "GET", "/api/v3/warehouses");
Если Не Результат.Успех Или ТипЗнч(Результат.Ответ) <> Тип("Массив") Тогда
Возврат Неопределено;
КонецЕсли;
Доступные = Новый Массив;
Для Каждого СкладWB Из Результат.Ответ Цикл
Если ТипЗнч(СкладWB) = Тип("Соответствие")
И СкладWB.Получить("isProcessing") <> Истина
И СкладWB.Получить("isDeleting") <> Истина Тогда
Доступные.Добавить(СкладWB.Получить("id"));
КонецЕсли;
КонецЦикла;
Возврат Доступные;
КонецФункцииОстаток считается как «Доступно сейчас» в отчёте «Остатки и доступность товаров» УТ 11.4, без резервов по графику. В КА 2.5.20 регистра СвободныеОстатки нет — там берут регистр «Запасы и потребности» по своей схеме резервов.
Цена задаётся на карточку: nmID, целая цена price и скидка продавца discount в процентах, до 1000 товаров за запрос. Разные цены размеров одной карточки ставит отдельный метод /api/v2/upload/task/size, код такие карточки пропускает. Ответ возвращает ID загрузки, ошибки по товарам смотрят методами состояния и детализации загрузки. Если новая цена со скидкой хотя бы в 3 раза меньше старой, товар попадает в карантин и продаётся по старой цене. Лимит — 10 запросов за 6 секунд, поэтому цены отправляют по изменению, а не каждые пять минут. Реквизиты кабинета читает ЗначенияРеквизитовОбъекта.
// Выгружает цены вида цен кабинета. Цена WB ставится на карточку (nmID), поэтому
// карточки без цены или с разными ценами размеров в 1С пропускаем.
Процедура ВыгрузитьЦеныWB(Кабинет) Экспорт
РазмерПачки = 1000; // максимум товаров в одном запросе по документации метода
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Кабинет, "ВидЦены, СкидкаПроцент");
// УТ 11.4: РегистрСведений.ЦеныНоменклатуры. Цена за упаковку не подходит —
// берем только цену за единицу хранения (пустая упаковка).
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Товары.АртикулWB КАК АртикулWB,
| МИНИМУМ(ЕСТЬNULL(Цены.Цена, 0)) КАК ЦенаМин,
| МАКСИМУМ(ЕСТЬNULL(Цены.Цена, 0)) КАК ЦенаМакс
|ИЗ
| РегистрСведений.ТоварыWB КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| ,
| ВидЦены = &ВидЦены) КАК Цены
| ПО Товары.Номенклатура = Цены.Номенклатура
| И Товары.Характеристика = Цены.Характеристика
| И (Цены.Упаковка = ЗНАЧЕНИЕ(Справочник.УпаковкиЕдиницыИзмерения.ПустаяСсылка))
|ГДЕ
| Товары.Кабинет = &Кабинет
| И Товары.АртикулWB > 0
|
|СГРУППИРОВАТЬ ПО
| Товары.АртикулWB");
Запрос.УстановитьПараметр("Кабинет", Кабинет);
Запрос.УстановитьПараметр("ВидЦены", Реквизиты.ВидЦены);
Выборка = Запрос.Выполнить().Выбрать();
Пачка = Новый Массив;
Пропущенные = Новый Массив;
Продолжать = Истина;
Пока Продолжать И Выборка.Следующий() Цикл
Если Выборка.ЦенаМин = 0 Или Выборка.ЦенаМин <> Выборка.ЦенаМакс Тогда
Пропущенные.Добавить(Формат(Выборка.АртикулWB, "ЧГ=0"));
Продолжить;
КонецЕсли;
// price — целое число, discount — скидка продавца в процентах, тоже целая.
Пачка.Добавить(Новый Структура("nmID, price, discount",
Выборка.АртикулWB, Окр(Выборка.ЦенаМакс, 0), Цел(Реквизиты.СкидкаПроцент)));
Если Пачка.Количество() = РазмерПачки Тогда
Продолжать = ОтправитьЦеныWB(Кабинет, Пачка);
Пачка = Новый Массив;
КонецЕсли;
КонецЦикла;
Если Продолжать И Пачка.Количество() > 0 Тогда
ОтправитьЦеныWB(Кабинет, Пачка);
КонецЕсли;
Если Пропущенные.Количество() > 0 Тогда
ЗаписатьЖурналWB(Кабинет, "Цены не отправлены (нет цены или у размеров разные цены), nmID: "
+ СтрСоединить(Пропущенные, ", "), УровеньЖурналаРегистрации.Предупреждение);
КонецЕсли;
КонецПроцедуры
// Отправляет пачку цен. Ответ подтверждает прием и дает ID загрузки; ошибки по товарам,
// в том числе карантин цены, смотрят методами состояния и детализации загрузки.
Функция ОтправитьЦеныWB(Кабинет, Пачка)
Результат = ВызватьWB(Кабинет, "discounts-prices-api.wildberries.ru", "POST",
"/api/v2/upload/task", Новый Структура("data", Пачка));
Если Результат.Успех И ТипЗнч(Результат.Ответ) = Тип("Соответствие") Тогда
ДанныеОтвета = Результат.Ответ.Получить("data");
Если ТипЗнч(ДанныеОтвета) = Тип("Соответствие") Тогда
ЗаписатьЖурналWB(Кабинет, СтрШаблон("Цены: загрузка %1, товаров %2",
Формат(ДанныеОтвета.Получить("id"), "ЧГ=0"), Пачка.Количество()),
УровеньЖурналаРегистрации.Информация);
КонецЕсли;
КонецЕсли;
Возврат Результат.МожноПродолжить;
КонецФункцииВ КА 2.5.20 рядом с ЦеныНоменклатуры есть ЦеныНоменклатуры25 с измерением ХарактеристикаЦО — проверьте, какой регистр использует ваша база. Условие на пустую упаковку не даёт отправить цену коробки вместо цены штуки.
Заказ FBS приходит как сборочное задание на одну единицу товара: один chrtId, поля количества нет. Три одинаковых товара у покупателя — три задания с разными id. GET /api/v3/orders/new отдаёт задания, пока они новые, поэтому принятые отсекаются по регистру СборочныеЗаданияWB.
// Новые сборочные задания FBS, которых еще нет в регистре СборочныеЗаданияWB.
// Одно задание — одна единица товара. Задание с несопоставленным размером
// не загружается и придет снова при следующем запуске.
Функция НовыеЗаданияWB(Кабинет) Экспорт
ТипЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 0));
Список = Новый ТаблицаЗначений;
Список.Колонки.Добавить("ИдентификаторЗадания", ТипЧисло);
Список.Колонки.Добавить("ИдентификаторСклада", ТипЧисло);
Список.Колонки.Добавить("ИдентификаторРазмера", ТипЧисло);
Список.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100)));
Результат = ВызватьWB(Кабинет, "marketplace-api.wildberries.ru", "GET", "/api/v3/orders/new");
ЗаданияWB = ?(Результат.Успех И ТипЗнч(Результат.Ответ) = Тип("Соответствие"),
Результат.Ответ.Получить("orders"), Неопределено);
Если ТипЗнч(ЗаданияWB) = Тип("Массив") Тогда
Для Каждого ЗаданиеWB Из ЗаданияWB Цикл
НоваяСтрока = Список.Добавить();
НоваяСтрока.ИдентификаторЗадания = ЗаданиеWB.Получить("id");
НоваяСтрока.ИдентификаторСклада = ЗаданиеWB.Получить("warehouseId");
НоваяСтрока.ИдентификаторРазмера = ЗаданиеWB.Получить("chrtId"); // здесь Id строчными
НоваяСтрока.Артикул = ЗаданиеWB.Получить("article");
КонецЦикла;
КонецЕсли;
// Отсекаем принятые раньше задания и подставляем номенклатуру одним запросом.
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Список.ИдентификаторЗадания КАК ИдентификаторЗадания,
| Список.ИдентификаторСклада КАК ИдентификаторСклада,
| Список.ИдентификаторРазмера КАК ИдентификаторРазмера,
| Список.Артикул КАК Артикул
|ПОМЕСТИТЬ ВтЗадания
|ИЗ
| &Список КАК Список
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВтЗадания.ИдентификаторЗадания КАК ИдентификаторЗадания,
| ВтЗадания.ИдентификаторСклада КАК ИдентификаторСклада,
| ВтЗадания.Артикул КАК Артикул,
| ЕСТЬNULL(Товары.Номенклатура,
| ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура,
| ЕСТЬNULL(Товары.Характеристика,
| ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)) КАК Характеристика
|ИЗ
| ВтЗадания КАК ВтЗадания
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ТоварыWB КАК Товары
| ПО (Товары.Кабинет = &Кабинет)
| И ВтЗадания.ИдентификаторРазмера = Товары.ИдентификаторРазмера
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СборочныеЗаданияWB КАК Принятые
| ПО (Принятые.Кабинет = &Кабинет)
| И ВтЗадания.ИдентификаторЗадания = Принятые.ИдентификаторЗадания
|ГДЕ
| Принятые.ИдентификаторЗадания ЕСТЬ NULL");
Запрос.УстановитьПараметр("Список", Список);
Запрос.УстановитьПараметр("Кабинет", Кабинет);
Задания = Запрос.Выполнить().Выгрузить();
Несопоставленные = Новый Массив;
Отбор = Новый Структура("Номенклатура", Справочники.Номенклатура.ПустаяСсылка());
Для Каждого СтрокаЗадания Из Задания.НайтиСтроки(Отбор) Цикл
Несопоставленные.Добавить(СтрШаблон("%1 (%2)",
Формат(СтрокаЗадания.ИдентификаторЗадания, "ЧГ=0"), СтрокаЗадания.Артикул));
Задания.Удалить(СтрокаЗадания);
КонецЦикла;
Если Несопоставленные.Количество() > 0 Тогда
ЗаписатьЖурналWB(Кабинет, "Не загружены задания с несопоставленным размером, "
+ "задание (артикул): " + СтрСоединить(Несопоставленные, ", "),
УровеньЖурналаРегистрации.Предупреждение);
КонецЕсли;
Возврат Задания;
КонецФункции
// Строки заказа клиента по одному складу WB: сколько заданий, столько единиц товара.
Функция СтрокиЗаказаWB(Задания, ИдентификаторСклада) Экспорт
Отбор = Новый Структура("ИдентификаторСклада", ИдентификаторСклада);
ТипКоличество = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3));
СтрокиЗаказа = Задания.Скопировать(Отбор, "Номенклатура, Характеристика");
СтрокиЗаказа.Колонки.Добавить("Количество", ТипКоличество);
СтрокиЗаказа.ЗаполнитьЗначения(1, "Количество");
СтрокиЗаказа.Свернуть("Номенклатура, Характеристика", "Количество");
Возврат СтрокиЗаказа;
КонецФункции
// Запоминает задания склада, вошедшие в заказ клиента, чтобы не загрузить их повторно.
// Вызывайте в той же транзакции, в которой записывается заказ.
Процедура ЗарегистрироватьЗаданияWB(Кабинет, Задания, ИдентификаторСклада, ЗаказКлиента) Экспорт
Отбор = Новый Структура("ИдентификаторСклада", ИдентификаторСклада);
Для Каждого СтрокаЗадания Из Задания.НайтиСтроки(Отбор) Цикл
Запись = РегистрыСведений.СборочныеЗаданияWB.СоздатьМенеджерЗаписи();
Запись.Кабинет = Кабинет;
Запись.ИдентификаторЗадания = СтрокаЗадания.ИдентификаторЗадания;
Запись.ЗаказКлиента = ЗаказКлиента;
Запись.Записать();
КонецЦикла;
КонецПроцедурыДля каждого склада WB: СтрокиЗаказаWB даёт строки заказа клиента, заказ создаётся, как в статье про Яндекс Маркет, а ЗарегистрироватьЗаданияWB вызывается в той же транзакции. Если загрузку можно запустить вручную параллельно с заданием, добавьте управляемую блокировку по кабинету. Цену в заказ берите из вида цен 1С, фактическую цену продажи — из отчётов WB.
Затем задания собирают в поставку: POST /api/v3/supplies возвращает ID вида WB-GI-…. В поставку попадают задания одного габаритного типа (cargoType первого задания), добавленное задание переходит из новых в подтверждённые. Если у задания заполнен requiredMeta (например, uin или sgtin), эти данные передают до отгрузки.
sku. Текущий метод ждёт chrtId и имена полей не проверяет: ответ 204, а остатки на WB прежние.X-Ratelimit-Retry секунд.В выгрузках УТ 11.4.14 и КА 2.5.20 её нет: типовой механизм КА 2.5.20 работает с Ozon и Яндекс Маркетом. Для новых релизов и других конфигураций смотрите описание релиза на its.1c.ru.
Для кода из статьи — «Контент», «Цены и скидки» и «Маркетплейс». Для загрузки продаж и финансовых отчётов добавьте «Статистику» и «Финансы». Лишние категории не включайте: меньше прав — меньше вреда от утечки.
В песочнице — хосты с суффиксом -sandbox: content-api-sandbox, discounts-prices-api-sandbox и marketplace-api-sandbox в домене wildberries.ru. Для неё нужен тестовый токен, а лимит там — один запрос в секунду на все методы сервиса, поэтому пачки отправляйте с паузой.
Проверьте состояние загрузки по ID из журнала: товар мог уйти в карантин из-за резкого снижения цены. Если nmID есть в журнале пропущенных, у размеров карточки в 1С разные цены или нет цены за штуку.
Если нужно подключить несколько кабинетов и складов, учесть свою схему резервов или доработать коннектор, закажите интеграцию 1С с маркетплейсами — Wildberries, Ozon или Яндекс Маркет. Укажите конфигурацию, релиз и модели работы с WB и разместите задание: оценка бесплатная, ответ — в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.