В складском учете и логистике часто возникает задача резервирования и распределения остатков номенклатуры в разрезе нескольких аналитических измерений (например, Производитель, Завод-изготовитель, Склад, Серия). Сложность возникает в ситуации, когда строки заказа покупателя содержат требования разной степени строгости:
Простой перебор по порядку или наивное жадное списание (по ФИФО) в таких условиях может привести к ложному отказу: система зарезервирует дефицитный ресурс под строку со свободными условиями, после чего не сможет выполнить строку с жесткими ограничениями. Проанализируем математическую суть проблемы и разберем алгоритмы ее решения на платформе 1С:Предприятие.
Формально эта задача сводится к поиску максимального потока в транспортной сети (частный случай задачи линейного программирования). Построим двудольную сеть распределения:
П1_З1, П1_З2, П2_З1, П2_З2). Пропускная способность дуг от фиктивного истока к этим вершинам равна доступному количеству товара в остатке.З1, З2, З3...). Пропускная способность дуг от этих вершин к фиктивному стоку равна требуемому количеству по строке заказа.Так как все значения пропускных способностей целочисленные, по теореме о целочисленности алгоритмы нахождения максимального потока гарантированно дают решение в целых числах без необходимости применения методов целочисленного линейного программирования (метода Гомори или ветвей и границ).
Если найденный максимальный поток равен суммарной потребности заказа, заказ гарантированно выполним. Если поток меньше, заказ выполнить нельзя. При этом минимальный разрез сети позволяет точно локализовать узкое место — группу ограничений, вызвавшую дефицит.
Для гарантированного нахождения решения при произвольных пересечениях ограничений реализуем расчет остаточной сети. Рассмотрим по шагам логику построения графа во встроенном языке 1С:
Структура, Массив и Соответствие, обеспечивающих быстрый доступ к узлам по ключу ($O(1)$).Посмотрим на пример структуры хранения графа и построения матрицы распределения:
// Подготовка структур данных графа
ГрафСети = Новый Соответствие;
// Заполнение ребер от истока к остаткам
Для Каждого СтрОстатка Из ТаблицаОстатков Цикл
КлючИсточника = "SRC_" + СтрОстатка.Производитель + "_" + СтрОстатка.Завод;
ГрафСети.Вставить(КлючИсточника, Новый Структура("Остаток, Ребра", СтрОстатка.Количество, Новый Массив));
КонецЦикла;
// Добавление допустимых связей к строкам заказа
Для Каждого СтрЗаказа Из ТаблицаЗаказа Цикл
КлючПотребителя = "DST_" + СтрЗаказа.НомерСтроки;
Для Каждого КлючИЗначение Из ГрафСети Цикл
// Проверяем соответствие условиям строки заказа
Если СоответствуетУсловию(КлючИЗначение.Ключ, СтрЗаказа) Тогда
КлючИЗначение.Значение.Ребра.Добавить(КлючПотребителя);
КонецЕсли;
КонецЦикла;
КонецЦикла;
В учетных задачах на 1С, где число измерений невелико (2–3 аналитики), часто применяется быстрый каскадный жадный алгоритм, выполняемый за линейное время:
Разберем по шагам реализацию предварительного списания жестких условий и контроля остатков:
// Шаг 1: Прямое списание жестко заданных аналитик
Для Каждого СтрЗаказа Из ТаблицаЗаказовЖесткие Цикл
ОтборОстатка = Новый Структура("Производитель, Завод", СтрЗаказа.Производитель, СтрЗаказа.Завод);
НайденныеОстатки = ТаблицаОстатков.НайтиСтроки(ОтборОстатка);
Если НайденныеОстатки.Количество() > 0 Тогда
СтрокаОстатка = НайденныеОстатки[0];
Если СтрокаОстатка.Количество >= СтрЗаказа.Количество Тогда
СтрокаОстатка.Количество = СтрокаОстатка.Количество - СтрЗаказа.Количество;
СтрЗаказа.Количество = 0;
Иначе
// Недостаточно точного остатка
ВызватьИсключение "Недостаточно остатка по точной аналитике!";
КонецЕсли;
КонецЕсли;
КонецЦикла;
ФоновыеЗадания), чтобы не вызывать длительных блокировок таблиц регистров остатков.