При автоматизации загрузки заказов из внешних источников (например, Excel) в 1С:Управление торговлей 11.4 одной из наиболее трудоемких задач является корректное распределение серий номенклатуры. Если в системе ведется учет по сериям с контролем остатков, недостаточно просто создать строку в документе — необходимо убедиться, что выбранная серия есть в наличии, а если ее недостаточно, распределить количество по нескольким доступным сериям. В этой статье мы подробно разберем, как построить надежный алгоритм распределения серий на основе остатков.
Представим ситуацию: мы читаем файл Excel, в котором указан артикул товара и количество. В 1С этот товар может храниться на складе в разрезе разных серий (например, с разными сроками годности). Нам нужно не просто создать строку в документе Заказ клиента, а реализовать логику FEFO (First Expired, First Out) или простого списания доступных остатков. Если заказано 10 единиц, а на первой найденной серии всего 4, программа должна автоматически добавить вторую строку с другой серией на оставшиеся 6 единиц.
Рассмотрим распространенную ошибку: поиск номенклатуры в цикле с использованием оператора ПОДОБНО в запросе. Это крайне медленно. Проанализируем более эффективный подход. Вместо запроса внутри цикла лучше использовать поиск по реквизиту или предварительную выгрузку соответствий артикулов в таблицу значений. Однако, если мы работаем в контексте загрузки, воспользуемся проверенным методом поиска НайтиПоРеквизиту.
Важный совет: избегайте конструкции ПОДОБНО, если артикул известен точно. Это избавит вас от получения «лишних» позиций номенклатуры в выборке.
// Пример быстрого поиска номенклатуры
НоменклатураСсылка = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", АртикулИзФайла);
Если НоменклатураСсылка.Пустая() Тогда
Сообщить("Товар с артикулом " + АртикулИзФайла + " не найден!");
Продолжить;
КонецЕсли;
Для распределения нам необходимо знать, какие серии есть в наличии на конкретном складе. Проанализируем ситуацию: использовать виртуальную таблицу ОстаткиИОбороты излишне, если нам нужны только текущие данные. Правильнее использовать таблицу Остатки регистра накопления ТоварыНаСкладах.
Разберем структуру запроса для получения остатков с сортировкой по сроку годности (чтобы первыми списывались товары, у которых срок годности истекает раньше):
ЗапросСерии = Новый Запрос;
ЗапросСерии.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Серия КАК Серия,
| ТоварыНаСкладахОстатки.Серия.ГоденДо КАК СрокГодности,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура И Склад = &Склад) КАК ТоварыНаСкладахОстатки
|ГДЕ
| ТоварыНаСкладахОстатки.ВНаличииОстаток > 0
|УПОРЯДОЧИТЬ ПО
| СрокГодности ВОЗР";
ЗапросСерии.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
ЗапросСерии.УстановитьПараметр("Склад", СкладСсылка);
ЗапросСерии.УстановитьПараметр("Период", ТекущаяДата());
ВыборкаСерий = ЗапросСерии.Выполнить().Выбрать();
Теперь рассмотрим основную логику. Допустим, переменная ОсталосьРаспределить содержит общее количество из Excel. Мы начинаем обход выборки серий. Выясним причину, по которой это решение является наиболее гибким: оно позволяет разбивать одну исходную позицию на несколько строк документа.
ОсталосьРаспределить.
ОсталосьРаспределить = КоличествоИзФайла;
Пока ВыборкаСерий.Следующий() И ОсталосьРаспределить > 0 Цикл
НоваяСтрока = ОбъектЗаказ.Товары.Добавить();
НоваяСтрока.Номенклатура = НоменклатураСсылка;
НоваяСтрока.Серия = ВыборкаСерий.Серия;
// Определяем, сколько мы можем взять из текущей серии
ВзятьИзСерии = Мин(ОсталосьРаспределить, ВыборкаСерий.Остаток);
НоваяСтрока.Количество = ВзятьИзСерии;
НоваяСтрока.КоличествоУпаковок = ВзятьИзСерии;
// Заполняем служебные поля УТ 11
НоваяСтрока.СтатусУказанияСерий = 0; // Значение зависит от настроек политики серий
НоваяСтрока.ВариантОбеспечения = Перечисления.ВариантыОбеспечения.Отгрузить;
// Уменьшаем остаток для следующей итерации
ОсталосьРаспределить = ОсталосьРаспределить - ВзятьИзСерии;
КонецЦикла;
Если ОсталосьРаспределить > 0 Тогда
Сообщить("Внимание: По товару " + НоменклатураСсылка + " не хватило остатка: " + ОсталосьРаспределить);
КонецЕсли;
Проанализируем технический нюанс конфигурации УТ 11.4. Поле СтатусУказанияСерий в табличной части Товары не всегда заполняется просто числом. В типовых решениях это поле является индикатором того, насколько заполнены серии для строки. Если вы программно подставляете серию прямо в строку (как в примере выше), система может потребовать определенное числовое значение в этом поле, чтобы документ считался корректно заполненным.
Важный момент: если в вашей политике учета серии указываются при планировании отгрузки, то СтатусУказанияСерий может принимать значение, равное количеству в строке. Если же серии заполняются в отдельном окне, программная подстановка может потребовать вызова типовых процедур пересчета из модулей конфигурации.
В исходном обсуждении упоминался COMОбъект("Excel.Application"). Рассмотрим альтернативу. Использование COM требует установленного Excel на сервере, что часто невозможно. Мы рекомендуем использовать встроенные средства 1С для чтения файлов .xlsx через ТабличныйДокумент.
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ИмяФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Ошибка чтения файла: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
ВсегоСтрок = ТабДок.ВысотаТаблицы;
Для Стр = 2 По ВсегоСтрок Цикл
Артикул = ТабДок.Область(Стр, КолонкаАртикул).Текст;
Количество = Число(ТабДок.Область(Стр, КолонкаКоличество).Текст);
// ... дальнейшая логика поиска и распределения
КонецЦикла;
Разберем, как сделать загрузку еще быстрее, если в файле тысячи строк:
ТаблицуЗначений.Номенклатура. Это позволит мгновенно находить нужные строки через метод НайтиСтроки().Использование предложенного алгоритма распределения ОсталосьРаспределить = ОсталосьРаспределить - ВзятьИзСерии гарантирует, что ваш Заказ клиента всегда будет соответствовать реальным остаткам на складе, а кладовщикам не придется вручную исправлять серии при сборке заказа.