При разработке и кастомизации конфигураций на платформе «1С:Предприятие 8» (особенно таких крупных систем, как «Управление производственным предприятием, редакция 1.3») программисты часто сталкиваются с задачами автоматического заполнения табличных частей документов. Одной из типовых задач является заполнение материалов на основании выпущенной продукции (или брака).
В этой статье мы подробно разберем логическую ошибку, из-за которой автозаполнение по спецификациям может выполняться некорректно, затирая или дублируя данные. Мы также проанализируем архитектурные недостатки исходного кода и рассмотрим профессиональные варианты решения проблемы с примерами оптимального кода.
Давайте сначала проанализируем исходный код, который привел к некорректному поведению системы.
Процедура ЗаполнитьМатериалыБракаНажатие(Элемент)
Ответ = Вопрос("Проверьте правильность заполнения номенклатуры!!!" + Символы.ПС + "Продолжить?" + Символы.ПС, РежимДиалогаВопрос.ДаНет);
Если Не Ответ = КодВозвратаДиалога.Да Тогда
Возврат;
КонецЕсли;
Для каждого Стр из ПродукцияБрака Цикл
Если Стр.Подразделение.Наименование = "2.Участок ТЦ"
ИЛИ Стр.Подразделение.Наименование = "3.Участок 1 исп."
ИЛИ Стр.Подразделение.Наименование = "5.Участок ПСИ"
ИЛИ Стр.Подразделение.Наименование = "6.Участок ок.сб."
ИЛИ Стр.Подразделение.Наименование = "7.Участок Вых.контр." Тогда
НоваяСтрока = МатериалыБрака.Добавить;
НоваяСтрока.Количество = Стр.Количество;
НоваяСтрока.Номенклатура = Стр.Номенклатура;
НоваяСтрока.Заказ = Стр.Заказ;
НоваяСтрока.ЕдиницаИзмерения = Стр.ЕдиницаИзмерения;
НоваяСтрока.Коэффициент = Стр.Коэффициент;
НоваяСтрока.Подразделение = Стр.Подразделение;
ИначеЕсли Стр.Подразделение.Наименование = "1.Участок ТП"
ИЛИ Стр.Подразделение.Наименование = "4.Участок сборки" Тогда
ЗаполнитьМатериалыБракаПоСпецификации(МатериалыБрака, "МатериалыБрака");
КонецЕсли;
КонецЦикла;
Этаформа.ТекущийЭлемент = ЭлементыФормы.МатериалыБрака;
КонецПроцедуры
Суть проблемы: при выполнении данной процедуры система рассчитывает и заполняет спецификации абсолютно по всей продукции в документе, игнорируя условия по подразделениям. При этом строки прямого переноса (без спецификаций) просто дописываются в самый конец таблицы. Давайте разберемся, почему так происходит.
---
Главная логическая ошибка кроется в этой строке, расположенной внутри цикла Для каждого Стр из ПродукцияБрака Цикл:
ЗаполнитьМатериалыБракаПоСпецификации(МатериалыБрака, "МатериалыБрака");
Типовая общая процедура ЗаполнитьМатериалыБракаПоСпецификации предназначена для работы со всем контекстом документа. Она не знает, на какой именно строчке цикла сейчас находится выполнение. «Под капотом» эта процедура выполняет следующие действия:
МатериалыБрака.ПродукцияБрака.МатериалыБрака.Когда мы помещаем такой вызов внутрь построчного цикла, происходит конфликт логики:
ИначеЕсли, вызывается общая процедура.Если, программа просто добавляет эту строку вручную через метод Добавить() в конец уже сформированного общего списка.В результате мы получаем хаотичное смешивание данных и некорректное заполнение.
---
Проверка условий вида Стр.Подразделение.Наименование = "2.Участок ТЦ" является серьезной архитектурной ошибкой по двум причинам:
Если всегда будут возвращать значение Ложь.Стр.Подразделение.Наименование заставляет платформу выполнять неявный SQL-запрос к базе данных для получения наименования при обработке каждой отдельной строки. На больших документах это приведет к зависанию интерфейса.---
Чтобы решить поставленную задачу красиво и надежно, нам нужно разделить заполнение на два независимых этапа:
Давайте рассмотрим подробный пример реализации этого алгоритма с оптимизацией обращений к базе данных.
Процедура ЗаполнитьМатериалыБракаНажатие(Элемент)
Ответ = Вопрос("Проверьте правильность заполнения номенклатуры!!!" + Символы.ПС + "Продолжить?" + Символы.ПС, РежимДиалогаВопрос.ДаНет);
Если Не Ответ = КодВозвратаДиалога.Да Тогда
Возврат;
КонецЕсли;
// Очищаем целевую таблицу перед заполнением
МатериалыБрака.Очистить();
// Создаем массивы для быстрой проверки соответствия подразделений без обращений к БД в цикле
СписокПрямогоПереноса = Новый Массив;
СписокПоСпецификации = Новый Массив;
// Заполняем массивы ссылками на подразделения (поиск по коду надежнее, чем по наименованию)
СписокПрямогоПереноса.Добавить(Справочники.Подразделения.НайтиПоКоду("00002")); // Участок ТЦ
СписокПрямогоПереноса.Добавить(Справочники.Подразделения.НайтиПоКоду("00003")); // Участок 1 исп.
СписокПрямогоПереноса.Добавить(Справочники.Подразделения.НайтиПоКоду("00005")); // Участок ПСИ
СписокПрямогоПереноса.Добавить(Справочники.Подразделения.НайтиПоКоду("00006")); // Участок ок.сб.
СписокПрямогоПереноса.Добавить(Справочники.Подразделения.НайтиПоКоду("00007")); // Участок Вых.контр.
СписокПоСпецификации.Добавить(Справочники.Подразделения.НайтиПоКоду("00001")); // Участок ТП
СписокПоСпецификации.Добавить(Справочники.Подразделения.НайтиПоКоду("00004")); // Участок сборки
// Шаг 1: Создаем временную таблицу-копию продукции для расчета по спецификациям
ТаблицаДляСпецификаций = ПродукцияБрака.ВыгрузитьКолонки();
// Шаг 2: Распределяем строки по правилам обработки
Для каждого Стр Из ПродукцияБрака Цикл
СсылкаПодразделения = Стр.Подразделение;
// Проверяем вхождение ссылки в наши списки (работает мгновенно в оперативной памяти)
Если СписокПрямогоПереноса.Найти(СсылкаПодразделения) <> Неопределено Тогда
// Прямой перенос данных
НоваяСтрока = МатериалыБрака.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Стр);
ИначеЕсли СписокПоСпецификации.Найти(СсылкаПодразделения) <> Неопределено Тогда
// Запоминаем строки, которые нужно отправить на расчет спецификаций
ЗаполнитьЗначенияСвойств(ТаблицаДляСпецификаций.Добавить(), Стр);
КонецЕсли;
КонецЦикла;
// Шаг 3: Если есть продукция для расчета спецификаций, обрабатываем её
Если ТаблицаДляСпецификаций.Количество() > 0 Тогда
// Создаем временную структуру для передачи в типовой механизм
// Мы временно подменяем исходную таблицу документа отфильтрованной таблицей
СохраненнаяТаблицаПродукции = ПродукцияБрака.Выгрузить();
ПродукцияБрака.Загрузить(ТаблицаДляСпецификаций);
// Вызываем типовой механизм для заполнения отфильтрованной части
// Метод допишет рассчитанные строки в конец таблицы МатериалыБрака
ЗаполнитьМатериалыБракаПоСпецификации(МатериалыБрака, "МатериалыБрака");
// Возвращаем исходную продукцию на место
ПродукцияБрака.Загрузить(СохраненнаяТаблицаПродукции);
КонецЕсли;
ЭлементыФормы.МатериалыБрака.Активизировать()
КонецПроцедуры
---
Стр.Подразделение.Наименование внутри цикла и перехода к поиску по ссылкам в массиве, скорость выполнения процедуры увеличивается в несколько раз (особенно на базах данных SQL при работе по сети).ЗаполнитьЗначенияСвойств() вместо ручного копирования каждого реквизита защищает код от падений при возможном изменении состава колонок табличной части в будущем.Применяя данные подходы, вы сможете проектировать гибкие, быстрые и устойчивые к ошибкам алгоритмы заполнения любых связанных табличных частей в вашей информационной базе.