На складах производственных и торговых компаний часто встречается ситуация, когда готовая продукция хранится в разобранном виде — в виде отдельных упаковок, коробок или модулей. Например, «Шкаф двухстворчатый» физически состоит из трех коробок: коробки с фурнитурой, коробки с деревянным корпусом и коробки с зеркальным фасадом. При этом некоторые коробки могут быть унифицированы и входить в состав разных изделий (например, одна и та же коробка с фурнитурой подходит и для шкафа, и для тумбы).
Во время инвентаризации кладовщики считают именно физические коробки. Возникает сложная задача: как на основе фактического количества найденных коробок рассчитать реальное количество готовой номенклатуры, которую можно собрать и продать клиенту? Разберем эту проблему по шагам и рассмотрим варианты её решения — от штатных механизмов 1С:ERP до написания собственного алгоритма оптимизации.
Если бы каждая коробка принадлежала только одному уникальному товару, расчет был бы тривиальным: мы бы просто взяли количество коробок и разделили его на количество, необходимое для сборки одного комплекта. Однако на практике компоненты часто пересекаются.
Проанализируем классический пример:
Коробка 1 (1 шт.) и Коробка 2 (1 шт.).Коробка 1 (1 шт.) и Коробка 3 (1 шт.).Предположим, в ходе инвентаризации на складе фактически насчитали следующее количество коробок:
Коробка 1 — 100 шт.Коробка 2 — 80 шт.Коробка 3 — 120 шт.В зависимости от того, по какому принципу мы начнем собирать готовые комплекты, мы получим разные результаты учета:
Вариант 1 (приоритет сборки Номенклатуры 1):
Номенклатуры 1. Для этого нам нужно по 80 штук Коробки 1 и Коробки 2. Получаем 80 шт. Номенклатуры 1.Коробки 1 на складе уменьшается: 100 - 80 = 20 шт.Номенклатуру 2 из оставшихся коробок. У нас есть 20 штук Коробки 1 и 120 штук Коробки 3. Мы можем собрать только 20 шт. Номенклатуры 2.Коробки 3.Вариант 2 (приоритет сборки Номенклатуры 2):
Номенклатуру 2. Используем 100 штук Коробки 1 и 100 штук Коробки 3. Получаем 100 шт. Номенклатуры 2.Коробка 1 полностью исчерпана (остаток 0).Номенклатуру 1 мы больше не можем, так как для нее нет Коробки 1, несмотря на то, что на складе лежит 80 штук неиспользованной Коробки 2.Коробки 2 и 20 штук Коробки 3.Выясним, какой из вариантов правильный. Ответ зависит исключительно от бизнес-целей компании: что выгоднее продать, какая номенклатура дороже, или какая позиция сейчас находится в дефиците под заказы клиентов. Поэтому перед написанием алгоритма необходимо жестко определить правила приоритетов.
Если вы используете современные конфигурации (1С:ERP, 1С:Комплексная автоматизация или 1С:Управление торговлей ред. 11), в системе уже есть готовый инструмент для решения этой задачи. Он называется механизмом товарных мест.
Рассмотрим, как настроить этот процесс без программирования:
Пересчет товаров на адресном складе система автоматически заполняет табличную часть не целыми товарами, а именно товарными местами.Корректировка излишков и недостач по товарным местам. Этот документ автоматически анализирует излишки одних частей и недостачи других, «схлопывает» их в целые комплекты номенклатуры и выявляет реальный финансовый результат инвентаризации.Если штатный функционал по каким-то причинам не подходит (например, учет ведется в более простой конфигурации, или требуется кастомный документ инвентаризации, где на одной вкладке отображается готовая номенклатура, а на другой — составные коробки), необходимо реализовать собственный алгоритм автоматического расчета.
Проанализируем структуру документа инвентаризации, предложенную в обсуждении:
Наша задача — после заполнения фактического количества коробок рассчитать фактическое количество готовой номенклатуры и обновить первую вкладку.
Рассмотрим последовательный (жадный) алгоритм сборки с приоритетом по порядку строк. Этот алгоритм прост в реализации, работает быстро и закрывает 95% потребностей бизнеса.
Посмотрим, как выглядит программная реализация данного алгоритма. Этот код можно разместить в обработчике команды «Рассчитать факт номенклатуры» в форме документа инвентаризации.
&НаСервере
Процедура РассчитатьФактНоменклатурыНаСервере()
// Шаг 1. Создаем таблицу фактических остатков коробок для оперативной работы в памяти
ТаблицаКоробок = Новый ТаблицаЗначений;
ТаблицаКоробок.Колонки.Добавить("Коробка", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаКоробок.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
// Заполняем таблицу данными из вкладки "Состав" документа
Для Каждого СтрокаСостава Из Объект.Состав Цикл
НоваяСтрока = ТаблицаКоробок.Добавить();
НоваяСтрока.Коробка = СтрокаСостава.Коробка;
НоваяСтрока.Количество = СтрокаСостава.КоличествоФакт;
КонецЦикла;
// Индексируем таблицу для быстрого поиска коробок
ТаблицаКоробок.Индексы.Добавить("Коробка");
// Шаг 2. Обходим номенклатуру в порядке приоритета (как они расположены на вкладке Номенклатура)
Для Каждого СтрокаНом Из Объект.Номенклатура Цикл
ИмяНоменклатуры = СтрокаНом.Номенклатура;
СобраноШтук = 0;
// Получаем спецификацию (состав коробок) для 1 единицы товара
// Предположим, функция ВозвратитьСоставНоменклатуры(Номенклатура) возвращает Таблицу Значений с колонками "Коробка", "Количество"
ТаблицаСостава = ПолучитьСоставОднойЕдиницы(ИмяНоменклатуры);
Если ТаблицаСостава.Количество() = 0 Тогда
Продолжить; // Если состава нет, собрать невозможно
КонецЕсли;
// Цикл поштучной сборки текущей номенклатуры
Пока Истина Цикл
ВозможноСобратьЕдиницу = Истина;
// Проверяем наличие всех компонентов для сборки 1 штуки
Для Каждого СтрокаКомпонента Из ТаблицаСостава Цикл
НайденнаяКоробка = ТаблицаКоробок.Найти(СтрокаКомпонента.Коробка, "Коробка");
Если НайденнаяКоробка = Неопределено Или НайденнаяКоробка.Количество < СтрокаКомпонента.Количество Тогда
ВозможноСобратьЕдиницу = Ложь;
Прервать; // Не хватает компонентов, прерываем проверку сборки этой единицы
КонецЕсли;
КонецЦикла;
// Если все компоненты на месте, производим "списание" коробок со склада и добавляем готовый товар
Если ВозможноСобратьЕдиницу Тогда
Для Каждого СтрокаКомпонента Из ТаблицаСостава Цикл
НайденнаяКоробка = ТаблицаКоробок.Найти(СтрокаКомпонента.Коробка, "Коробка");
НайденнаяКоробка.Количество = НайденнаяКоробка.Количество - СтрокаКомпонента.Количество;
КонецЦикла;
СобраноШтук = СобраноШтук + 1;
Иначе
Прервать; // Сборка данной номенклатуры завершена, переходим к следующему товару в списке
КонецЕсли;
КонецЦикла;
// Записываем результат расчета в табличную часть номенклатуры
СтрокаНом.КоличествоФакт = СобраноШтук;
КонецЦикла;
// Шаг 3. Оставшиеся в таблице "ТаблицаКоробок" записи с количеством > 0 — это некомплектные излишки.
// Их можно вывести пользователю или записать в отдельную таблицу некомплекта.
ОтразитьНекомплектныеОстатки(ТаблицаКоробок);
КонецПроцедуры
Рассмотренный выше жадный алгоритм отлично работает, если у вас четко заданы приоритеты в списке. Однако, если перед вами стоит задача автоматической оптимизации (например, собрать товары так, чтобы получить максимальную суммарную стоимость или минимизировать остаток неликвидных деталей), обычный последовательный перебор не поможет.
Для решения задач глобальной оптимизации распределения применяется метод линейного программирования (в частности, целочисленного программирования):
Сумма (Количество_Номенклатуры_i * Цена_i) -> Max.scipy.optimize) или задействовать COM-соединение с MS Excel и его надстройку «Поиск решения» (Solver).Важно помнить, что коробки, оставшиеся после расчета в некомплекте, нельзя просто проигнорировать. С точки зрения регламентированного и управленческого учета необходимо корректно отразить их судьбу:
Использование предложенного алгоритма сборки и штатных механизмов 1С позволит вам навести идеальный порядок на складе, оперативно получать информацию о реальных товарных остатках, доступных к продаже, и исключить пересортицу дорогостоящих комплектующих.