Из сорока шести карточек, которые я взял на контрольную выборку, правильно были заполнены девятнадцать. Товар на площадке при этом был у всех сорока шести, карточка была, штрихкод в ней был виден глазами. И всё равно почти шесть позиций из десяти не находились по тому номеру, который напечатан на коробке.
Разбираться пришлось потому, что к первому октября карточка товара на маркетплейсе перестаёт быть витриной и становится объектом проверки: площадка сверяет сведения сама. Штрихкод тут не главное поле, но он ключ, по которому к товару цепляется остальное.
Работал я на Ozon и Яндекс Маркете, из типового учёта на УТ 11.5. Дальше говорю про обоих и каждый раз называю, про кого речь: поведение у них разное, и в этом половина сюжета.
Один номер, два штрихкода
Начну с того, что сбивает с толку первым. В карточке товара на Ozon нет поля "штрихкод". Там список.
Пока в голове стоит "у товара один штрихкод", логика выгрузки строится неправильно: вы пишете код, который кладёт значение, хотя нужен код, который добавляет элемент в список и смотрит, чего в этом списке ещё нет.
Теперь про две формы. Глобальный номер товарной позиции, он же GTIN (Global Trade Item Number), хранится у нас четырнадцатизначным, с ведущим нулём. На коробке напечатан тринадцатизначный, он же EAN-13. Это один и тот же номер: тринадцатизначная форма получается отбрасыванием ведущего нуля, и контрольная цифра остаётся прежней.
Проверить это стоит руками, потому что весь дальнейший разговор на этом стоит. Возьмём номер из диапазона внутренней нумерации предприятия, префикс 20, такие номера не выдаются никому:
// Контрольная цифра EAN-13: веса 1 и 3 попеременно, слева направо по первым 12 знакам. // 2*1 + 0*3 + 0*1 + 0*3 + 0*1 + 0*3 + 0*1 + 0*3 + 0*1 + 0*3 + 0*1 + 1*3 = 5 // Дополнение до десятки: (10 - 5 % 10) % 10 = 5 EAN13 = "2000000000015"; // Контрольная цифра GTIN-14: те же веса, сдвинутые на знак, по первым 13 знакам. // 0*3 + 2*1 + 0*3 + 0*1 + 0*3 + 0*1 + 0*3 + 0*1 + 0*3 + 0*1 + 0*3 + 0*1 + 1*3 = 5 // Дополнение то же: 5 GTIN14 = "02000000000015";
Ведущий ноль попадает на позицию с весом 3 и умножается на ноль. Поэтому он ничего не меняет, и поэтому приём работает:
EAN13 = Сред(GTIN14, 2); // "2000000000015"
Ozon это знает, но ведёт себя не так, как ожидаешь. Он хранит 02000000000015 и 2000000000015 как два разных элемента списка. Не как одно значение в двух видах. Он их не склеивает, одну форму из другой не выводит и недостающую не достраивает. Лежит одна форма, значит по второй товар не находится.
Если ваш обмен кладёт одну форму, вы уже потеряли половину способов найти товар, и узнаете об этом от склада.
Где отбрасывание нуля даёт неверный номер
Приём с отбрасыванием первого знака легко превратить в общее правило и испортить себе справочник, поэтому сразу про границу.
Отбрасывать можно только ведущий ноль. Первый знак четырнадцатизначного номера это индикатор уровня упаковки: ноль у штучного товара, единица и дальше до восьми у короба и паллеты. У такого номера контрольная цифра посчитана уже вместе с индикатором.
Возьмём тот же номер, но коробом:
GTIN14 = "12000000000012"; // индикатор 1, контрольная цифра 2 Обрезанный = Сред(GTIN14, 2); // "2000000000012"
У 2000000000012 контрольная цифра по формуле EAN-13 должна быть 5, а стоит 2. Это уже не штрихкод штучного товара, это строка из тринадцати цифр, похожая на штрихкод.
Поэтому проверка на ведущий ноль обязательна и стоит одну строку:
Если СтрДлина(GTIN14) = 14 И Лев(GTIN14, 1) = "0" Тогда Штрихкоды.Добавить(Сред(GTIN14, 2)); КонецЕсли;
Правило формулируется через ведущий ноль, и слово "ведущий" тут несущее. "Отбрасываем первый знак" звучит так же, работает иначе, и разница видна в испорченном справочнике.
Оговорюсь, чего я не проверял: примет ли Ozon номер с несошедшейся контрольной цифрой. Битый номер я туда не заливал и утверждать не буду. Проверять такое на живом каталоге мне не хотелось, а стенда под рукой не было.
Отдельного поля под GTIN у Ozon нет вовсе
Первая мысль была другая. Раз номер глобальный и стандартный, у карточки должен быть отдельный атрибут под него, а штрихкоды это про своё, внутреннее.
Я прогнал все атрибуты карточки через /v4/product/info/attributes и посмотрел, в каком из них живёт номер. Атрибутов оказалось 66. Поля с глобальным номером товарной позиции среди них нет ни одного.
Документация Ozon говорит то же самое с другой стороны: она предписывает класть такой номер в штрихкоды и следить, чтобы он совпадал с указанным на товаре. Отдельной сущности для него не предусмотрено намеренно.
Оговорюсь честно: состав атрибутов у Ozon категорийный, и 66 это одна категория. Утверждение "атрибута нет" держится на документации, а прогон показал только, что в моей категории искать больше негде.
Прежде чем писать маппинг под "атрибут GTIN", проверьте, существует ли он у вашей категории. Скорее всего вы потратите день на поле, которого нет.
Заодно про типовую, чтобы снять вопрос сразу. В УТ 11.5.22.67 интеграция с маркетплейсами есть, и немаленькая, но в перечислении ВидыОбъектовМаркетплейсов вида "Штрихкод" нет: номер уезжает внутри вида "Товар". Вопрос "сколько форм должно лежать в карточке" типовая не ставит и за вас не решает.
Код ответа 200 у Яндекс Маркета ничего не значит
У Яндекса отказ приезжает внутри тела. Код ответа при этом двухсотый.
Запрос принят, разобран, и дальше в теле лежит поле status. Если в нём стоит ошибка, порция не применяется целиком: не отдельная строка, а всё, что вы прислали одним запросом. Отчёт покажет успех по транспорту, в карточках не изменится ничего.
Вердикт читается из тела. Код ответа отвечает на другой вопрос, доехал ли запрос, и если писать проверку по нему, журнал обмена будет зелёным всегда.
Обновление дополняет. Но соседнее поле того же метода затирает
Тут я едва не сломал чужие данные, и спасло только то, что проверял на одном товаре.
Запись штрихкодов у Яндекса через offer-mappings/update дополняет список. Прислали одно значение, остальные остались на месте. Плюс обновление асинхронное: сразу после записи чтение отдаёт прежний список, примерно через минуту оба значения.
А поле certificates того же самого метода ведёт себя обратно: набор заменяется целиком, и прежние номера надо отправлять вместе с новым, иначе сотрёте чужие.
Один метод, два поля, противоположное поведение. Разница сидит в поле, и в документации она никак не подсвечена.
Перед первой массовой записью проверьте на товаре, у которого в поле уже лежит что-то постороннее. Не на пустом. Пустой товар покажет вам успех в обоих случаях.
Отказ, который называет не ту причину
Метод Ozon /v1/barcode/add отвечал, что штрихкодов должно быть от одного до ста. Читается однозначно: пачка слишком большая, уменьшай. Пачка была меньше сотни.
Причина сидела в другом слое. Тело запроса уходило строкой вместо структуры, и Ozon получал пустой список. Пустой список так же не попадает в диапазон от одного до ста, как и список из трёхсот элементов, и текст отказа у них общий.
// КоннекторHTTP.Post(URL, , Доп) - тело передаётся третьим параметром, // и ключ внутри решает, как оно будет сериализовано. Доп = Новый Структура; Доп.Вставить("Json", ТелоЗапроса); // структура уедет как JSON // Доп.Вставить("Строка", ТелоЗапроса); // а так Ozon увидит пустоту
Сообщение честное, но указывает не туда: элементов действительно не от одного до ста, их ноль. Мы же читаем диапазон и идём проверять размер пачки, потому что про ноль не думаем.
Когда отказ говорит про диапазон, проверяйте сначала нижнюю границу. Ноль встречается чаще перебора и стоит дороже, потому что выглядит как ваша арифметика.
Пачка на сто, и считаются в ней штрихкоды
Ограничение у /v1/barcode/add такое: не больше ста штрихкодов за запрос и не чаще двадцати запросов в минуту.
Пока мы слали одну форму, пачка в сто пар товара и номера укладывалась ровно в лимит. Как только каждая пара стала давать два штрихкода, сто пар превратились в двести штрихкодов.
Лечится арифметикой: пачка уменьшается вдвое.
// было: Если Пачка.Количество() >= 100 Тогда ... // стало: каждая пара даёт два штрихкода, поэтому Если Пачка.Количество() >= 50 Тогда Порции.Добавить(Пачка); Пачка = Новый Массив; КонецЕсли;
Лимит считается в тех единицах, которые площадка пишет в документации. Написано "штрихкодов", значит товары к делу не относятся.
Отдельно про чтение, и это уже Яндекс. У метода offer-mappings свой потолок на список offerIds, и сейчас он равен сотне. У нас в коде стояло двести, и откуда взялась эта цифра, я честно не знаю: то ли потолок был другим, когда обмен писался, то ли мы её однажды придумали и с тех пор не перепроверяли. Поймали отказом на боевом прогоне, чтение документации тут не помогло бы. Если у вас в обмене стоит число, которого вы сегодня не можете объяснить, считайте его неверным, пока не проверите запросом.
Ночной регламент отработал без ошибок и недобрал
Разовая дозаливка это половина работы. Вторая половина в том, чтобы новые карточки получали обе формы сами, поэтому правились сами методы, а не только данные.
Первая же ночь после правки подняла долю связанных карточек Ozon с трёх сотых очереди до семи десятых. Это рост в двадцать три раза, и тут я сам едва не соврал в свою невыгоду: в черновике у меня стояло "почти на порядок", то есть вдвое с лишним меньше того, что получилось.
И при этом регламент недобрал часть очереди, закончившись без единой ошибки в журнале.
Что оказалось не так, как я думал
Две гипотезы я проверил, и обе не подтвердились. Вторая не подтвердилась уже при подготовке этого текста.
Первая: Яндекс берёт только тринадцать знаков. Это убеждение я принёс с собой и написал по нему метод, который аккуратно срезал ведущий ноль перед отправкой. Прогон показал, что четырнадцатизначную форму Яндекс принимает и хранит спокойно. Метод годами срезал то, что срезать было не нужно, и оставлял карточку с одной формой вместо двух.
Вторая: недобор ночного прогона это лимит запросов. Объяснение выглядело очевидным: пачку мы уменьшили вдвое, запросов стало вдвое больше, значит упёрлись в двадцать запросов в минуту. Так и было записано у меня в протоколе прогона.
Арифметика это не подтверждает. За ту ночь связано 1 647 карточек. При пачке в сто штрихкодов это семнадцать запросов, меньше минуты работы при лимите двадцать в минуту. Лимит столько карточек остановить не может физически.
Чем регламент упёрся на самом деле, я не знаю: журнал прогона такой детализации не держал, а воспроизводить ночь ради этого дорого. Поэтому в тексте остаётся дыра, и пусть она лучше будет видна, чем закрыта правдоподобной причиной.
Общее у обеих гипотез одно: каждая звучала убедительно и каждая объясняла наблюдаемое. Убедительность тут и есть ловушка, она снимает вопрос вместо того, чтобы его поставить. Вторую гипотезу я поймал только потому, что сел пересчитывать числа для этой статьи.
Что в итоге поменялось в цифрах
Контрольная выборка та же самая, сорок шесть карточек, замер до работы и после.
| Что в карточке | До | После |
|---|---|---|
| обе формы | 19 | 45 |
| только четырнадцатизначная | 26 | 0 |
| только тринадцатизначная | 0 | 0 |
| ни одной | 1 | 1 |
Строка про тринадцатизначную форму пустая в обеих колонках намеренно: ни одна карточка не держала только её. Это и подсказало, где искать. Дело было в двух методах выгрузки, написанных под разные площадки в разное время: один слал длинную форму, другой короткую, и ни один не смотрел, что лежит в карточке сейчас.
Карточка, оставшаяся без штрихкодов совсем, такой и осталась: у товара нет номера в самой учётной базе, и площадка тут ни при чём. Это честный остаток почти любой такой работы: то, что решается кодом, кончается быстрее, чем кажется.
Всего операций записи по всем кабинетам получилось около семи тысяч двухсот пятидесяти. Ошибок в отчёте прогона ноль, и я уточню, что это значит: ноль отказов, которые вернула площадка и которые дошли до счётчика. Сетевых сбоев на такой серии я не видел, но отдельного счётчика повторов у меня там нет, так что про идеальную сеть утверждать не буду.
Чем я это делал
Всё описанное собиралось под конкретную задачу, руками, поверх типового учёта. Под соседнюю часть той же истории у меня выложены три обработки по разрешительным документам: там то же взаимодействие с карточкой товара, только со стороны документов.
- Загрузка сертификатов и деклараций в Яндекс Маркет из 1С - там та же механика пачки и то же чтение вердикта из поля
status, что описано выше; - Загрузка сертификатов в OZON из 1С;
- Загрузка сертификатов и деклараций в Wildberries из 1С.
Что меняется с первого октября и почему документ становится частью карточки, разбирал отдельно в статье Платформенная экономика РФ: почему сертификат становится частью карточки товара. Там же про то, что у одного товара может быть несколько кодов, и почему привязываться надо к артикулу.
Другие наши инструменты
- Сводная таблица в Excel из 1С без COM - выгрузить результат сверки и отдать людям.
- Анализ кода внешних обработок 1С - посмотреть, что делают обработки, которые ходят наружу с вашими данными.
- Журнал регистрации, свёрнутый для нейросети - разобрать, кто и когда трогал справочник штрихкодов.
Вопрос
Держать в карточке обе формы я решил из осторожности: так товар находится любым способом. Площадка при этом обе не требует, она пишет, что примет товар с любым из действующих штрихкодов. То есть мы сознательно дублируем данные.
И вот чего я не знаю. Что происходит на стороне 1С, когда обе формы лежат в справочнике штрихкодов у одной номенклатуры? Поиск на ТСД отработает по любой, это понятно. А загрузка отчёта о продажах, где площадка присылает тринадцать знаков, а у вас ключом четырнадцать, не начинает двоить строки при сверке? И ловил ли кто-нибудь коллизию, когда тринадцатизначная форма одного товара уже занята в базе другой номенклатурой?
Вступайте в нашу телеграмм-группу Инфостарт