В производственных системах, особенно в таких отраслях, как виноделие, химическая промышленность или фармация, задача построения полной цепочки прослеживаемости (Traceability) является критически важной. Нам необходимо знать не только, из чего сделан конкретный купаж или партия лекарства, но и пройти весь путь назад — через все переделы, декантации, очистки и смешивания — до конкретной закупки сырья или отчета о сборе урожая. В этой статье мы подробно разберем, как спроектировать архитектуру такого решения в 1С, чтобы она работала быстро даже на больших объемах данных за многие годы.
Рассмотрим типовую ситуацию: в системе существует множество документов выпуска (переделов). На каждом этапе списывается одна или несколько входящих партий и генерируется новая исходящая партия. Основная сложность заключается в том, что связь между ними — «многие-ко-многим». Один купаж может состоять из десяти виноматериалов, каждый из которых получен из пяти разных сортов сусла, а те, в свою очередь, из сотен партий винограда. Кроме того, одна и та же партия сырья может разойтись по десяткам разных конечных продуктов.
Попытка построить такое дерево «на лету» простым рекурсивным запросом в 1С часто приводит к диким тормозам, так как платформа не поддерживает рекурсивные CTE (Common Table Expressions) в классическом языке запросов (до последних версий платформы). Если же мы, как автор темы, будем раз в час полностью очищать и перезаполнять вспомогательный регистр связей, то при росте базы за 10–15 лет процедура станет невыполнимой. Проанализируем, как сделать это правильно.
Для обеспечения быстрой работы отчетов нам понадобится независимый Регистр сведений. Назовем его СоответствиеПартийПроизводства. Его задача — фиксировать факт того, что «Партия А» стала частью «Партии Б» в рамках конкретного документа. Рассмотрим оптимальный набор измерений:
Партии.Важный момент: не стоит удалять все записи регистра при каждом обновлении. Это создает колоссальную нагрузку на транзакционный лог и блокирует работу пользователей. Вместо этого разберем принцип инкрементального обновления. Мы будем обновлять данные только по тем документам, которые были изменены с момента последней итерации расчета.
Чтобы отчет «жмакнул и выдал результат», нам нужно решить, будем ли мы хранить в регистре только соседние звенья или же «развернутую» цепочку (всех предков для каждого узла). Рассмотрим второй вариант, который в теории баз данных называется Closure Table. В этом случае для финального купажа в регистре будут записи не только о виноматериале, но и сразу о винограде с указанием уровня вложенности.
Посмотрим на пример логики заполнения такого регистра при проведении документа:
Процедура ЗаполнитьЦепочкуПартий(ДокументОбъект)
// 1. Получаем все входящие партии из ТЧ документа
ТаблицаВхода = ДокументОбъект.Материалы.ВыгрузитьКолонку("Партия");
// 2. Получаем созданную партию выхода
ПартияВыхода = ДокументОбъект.ПартияВыхода;
// 3. Для каждой входящей партии находим всех её предков из регистра
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цепочка.ПартияВхода КАК ПредковаяПартия,
| Цепочка.Уровень + 1 КАК НовыйУровень
|ИЗ
| РегистрСведений.ЦепочкиПартий КАК Цепочка
|ГДЕ
| Цепочка.ПартияВыхода В (&СписокВходящих)";
Запрос.УстановитьПараметр("СписокВходящих", ТаблицаВхода);
// ... выполнение и запись новых связей для ПартияВыхода
КонецПроцедуры
Таким образом, на каждом этапе производства мы «протаскиваем» историю всех предыдущих партий вперед. Это увеличивает объем регистра, но позволяет получить всю историю любого купажа одним запросом к одной таблице без рекурсии.
Если в базу вносятся данные за прошлые периоды (например, коньячные спирты 20-летней выдержки), нам нельзя просто включить запрет редактирования. Выясним, как обеспечить актуальность цепочки в таких условиях. Проанализируем вариант с использованием фонового задания и механизма последовательностей.
ПроизводственнаяЦепочка.Это позволит пользователям работать оперативно, а тяжелые расчеты по восстановлению дерева партий за 10 лет будут происходить в периоды низкой активности сервера.
Когда структура данных готова, отчет становится тривиальной задачей. Рассмотрим, как будет выглядеть запрос для получения всех исходных партий винограда для конкретного бутылочного разлива:
ВЫБРАТЬ
Цепочка.ПартияВхода КАК ИсходноеСырье,
Цепочка.ДокументПередела КАК ЭтапПроизводства,
Цепочка.Уровень КАК Дистанция
ИЗ
РегистрСведений.ЦепочкиПартий КАК Цепочка
ГДЕ
Цепочка.ПартияВыхода = &ВыбранныйКупаж
УПОРЯДОЧИТЬ ПО
Цепочка.Уровень УБЫВ
Важный совет: если вам нужно не просто дерево, но и количественный баланс (сколько конкретно килограмм винограда «вошло» в эту бутылку с учетом потерь), в ресурсы регистра следует добавить поле КоэффициентДоли. При каждом смешивании этот коэффициент будет пересчитываться пропорционально объемам. Например, если мы смешали 100 литров Партии №1 и 200 литров Партии №2, то для новой партии доля первой составит 0.33, а второй — 0.67. Перемножая эти доли по всей цепочке, мы получим точный выход сырья.
Подведем итог нашему исследованию архитектуры прослеживаемости:
Реализовав предложенную схему, вы получите мощный инструмент анализа, который позволит не только отвечать на вопросы контролирующих органов (РАР, ЕГАИС), но и эффективно управлять качеством продукции, отслеживая влияние каждой партии сырья на финальный результат.