В процессе разработки и кастомизации конфигурации «1С:Управление производственным предприятием» (УПП) редакции 1.3 программисты часто сталкиваются с задачами автоматизации ввода данных. Одна из типовых и востребованных задач — автоматическое заполнение сырья, материалов или полуфабрикатов в табличной части документа сразу после того, как пользователь указал выпускаемую продукцию и ее объем.
В этой статье мы подробно разберем, как реализовать автозаполнение колонок с полуфабрикатами и их расчетным количеством на основании активной (основной) спецификации номенклатуры. Мы спроектируем отказоустойчивый алгоритм, напишем необходимые обработчики событий для формы и обсудим важнейший архитектурный нюанс связи «один ко многим».
Проанализируем ситуацию. У нас есть документ с табличной частью, в которой присутствуют четыре ключевые колонки:
СправочникСсылка.Номенклатура)Число)СправочникСсылка.Номенклатура)Число)Наша цель: сделать так, чтобы при заполнении первых двух колонок система автоматически находила активную спецификацию для выбранного продукта, извлекала из нее комплектующие (полуфабрикаты) и рассчитывала их количество пропорционально объему выпуска.
---
В системе 1С:УПП 1.3 основная спецификация для номенклатуры может определяться динамически. Она зависит от множества факторов: даты документа, конкретной характеристики продукции, а также производственного подразделения. Напрямую обращаться запросом к регистру сведений ОсновныеСпецификацииНоменклатуры не всегда эффективно, так как можно упустить важные разрезы учета.
Для надежного получения спецификации мы воспользуемся проверенным временем типовым методом из общего модуля УПП. Проанализируем вызов функции ОпределитьСпецификациюПоУмолчанию общего модуля УправлениеПроизводством:
Спецификация = УправлениеПроизводством.ОпределитьСпецификациюПоУмолчанию(
СтрокаТабличнойЧасти.ИсполняемыйПродукт,
СтрокаТабличнойЧасти.ХарактеристикаПродукта, // Если используется, иначе Неопределено
ЭтотОбъект.Дата,
ЭтотОбъект.Подразделение // Если используется в документе, иначе Неопределено
);
Этот метод самостоятельно проанализирует регистр сведений на нужную дату с учетом переданных параметров и вернет ссылку на элемент справочника СпецификацииНоменклатуры.
---
Каждая спецификация содержит перечень исходных комплектующих. Эти данные хранятся в табличной части ИсходныеКомплектующие справочника СпецификацииНоменклатуры. Чтобы получить список полуфабрикатов, нам потребуется написать запрос к этой табличной части.
Обратите внимание на то, что в спецификации указывается норматив расхода компонентов на определенное количество выхода готовой продукции. Например, на выпуск 10 единиц продукта требуется 5 единиц полуфабриката. Нам обязательно нужно выгрузить как количество расхода компонента, так и количество выпуска самой спецификации для последующего пропорционального расчета.
Рассмотрим структуру запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходныеКомплектующие.Номенклатура КАК Полуфабрикат,
| ИсходныеКомплектующие.Количество КАК КоличествоПоСпецификации,
| ИсходныеКомплектующие.Ссылка.Количество КАК КоличествоВыходаСпецификации
|ИЗ
| Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК ИсходныеКомплектующие
|ГДЕ
| ИсходныеКомплектующие.Ссылка = &Спецификация";
Запрос.УстановитьПараметр("Спецификация", Спецификация);
РезультатЗапроса = Запрос.Выполнить();
---
Поскольку конфигурация УПП 1.3 в подавляющем большинстве случаев использует обычные формы, нам необходимо привязать вызов процедуры автозаполнения к событиям изменения элементов управления на форме.
Автозаполнение должно инициироваться в двух случаях: когда пользователь выбрал (или изменил) номенклатуру продукции в первой колонке, либо когда он изменил ее количество во второй колонке. Откроем свойства соответствующих полей ввода в нашей табличной части и создадим обработчики событий ПриИзменении.
Процедура ТабличнаяЧастьИсполняемыйПродуктПриИзменении(Элемент)
СтрокаТЧ = ЭлементыФормы.ТабличнаяЧасть.ТекущаяСтрока;
ЗаполнитьПолуфабрикатыПоСпецификации(СтрокаТЧ);
КонецПроцедуры
Процедура ТабличнаяЧастьКоличествоПриИзменении(Элемент)
СтрокаТЧ = ЭлементыФормы.ТабличнаяЧасть.ТекущаяСтрока;
ЗаполнитьПолуфабрикатыПоСпецификации(СтрокаТЧ);
КонецПроцедуры
---
Теперь объединим все шаги в единую процедуру. Нам нужно предусмотреть проверку заполнения ключевых реквизитов, корректно обработать деление на ноль (на случай некорректно заполненных спецификаций в базе данных) и рассчитать итоговое количество полуфабриката.
Формула расчета выглядит следующим образом:
Количество Используемого Полуфабриката = (Количество Выпуска / Выход По Спецификации) * Расход Компонента По Спецификации
Запишем готовую процедуру:
Процедура ЗаполнитьПолуфабрикатыПоСпецификации(СтрокаТЧ)
// Проверяем заполненность обязательных полей строки
Если Не ЗначениеЗаполнено(СтрокаТЧ.ИсполняемыйПродукт)
Или Не ЗначениеЗаполнено(СтрокаТЧ.КоличествоИсполняемогоПродукта) Тогда
Возврат;
КонецЕсли;
// Определяем активную спецификацию на дату документа
ПодразделениеДокумента = ?(ОбщегоНазначения.ЕстьРеквизитДокумента("Подразделение", ЭтотОбъект.Метаданные()), ЭтотОбъект.Подразделение, Неопределено);
Спецификация = УправлениеПроизводством.ОпределитьСпецификациюПоУмолчанию(
СтрокаТЧ.ИсполняемыйПродукт,
Неопределено,
ЭтотОбъект.Дата,
ПодразделениеДокумента
);
// Если спецификация не задана, очищаем зависимые колонки и выходим
Если Не ЗначениеЗаполнено(Спецификация) Тогда
СтрокаТЧ.ИспользуемыеПолуфабрикаты = Справочники.Номенклатура.ПустаяСсылка();
СтрокаТЧ.КоличествоИспользуемыхПолуфабрикатов = 0;
Возврат;
КонецЕсли;
// Запрашиваем состав спецификации
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходныеКомплектующие.Номенклатура КАК Полуфабрикат,
| ИсходныеКомплектующие.Количество КАК КоличествоПоСпецификации,
| ИсходныеКомплектующие.Ссылка.Количество КАК КоличествоВыходаСпецификации
|ИЗ
| Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК ИсходныеКомплектующие
|ГДЕ
| ИсходныеКомплектующие.Ссылка = &Спецификация";
Запрос.УстановитьПараметр("Спецификация", Спецификация);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
// Считываем первый полуфабрикат из спецификации
Если Выборка.Следующий() Тогда
СтрокаТЧ.ИспользуемыеПолуфабрикаты = Выборка.Полуфабрикат;
// Защита от деления на ноль в количестве выхода спецификации
КоэффициентВыхода = ?(Выборка.КоличествоВыходаСпецификации = 0, 1, Выборка.КоличествоВыходаСпецификации);
// Расчитываем пропорциональное количество полуфабрикатов
СтрокаТЧ.КоличествоИспользуемыхПолуфабрикатов =
(СтрокаТЧ.КоличествоИсполняемогоПродукта / КоэффициентВыхода) * Выборка.КоличествоПоСпецификации;
КонецЕсли;
КонецПроцедуры
---
Рассмотренный выше вариант отлично подходит для простой структуры производства, когда один продукт строго сопоставляется с одним полуфабрикатом. Но на реальных промышленных предприятиях в спецификации готового изделия практически всегда содержится несколько компонентов (сырье, материалы, сопутствующие полуфабрикаты).
Если мы оставим алгоритм в исходном виде, он запишет в текущую строку документа только самый первый найденный в спецификации полуфабрикат, проигнорировав остальные. Это критическая логическая ошибка. Рассмотрим два изящных пути решения данной проблемы.
Если архитектура вашего документа требует, чтобы все данные находились строго в одной табличной части, то при изменении продукта нам нужно не просто перезаписывать текущую строку, а генерировать новые строки под каждый полуфабрикат из спецификации.
Проанализируем алгоритм реализации такого подхода:
Процедура ЗаполнитьПолуфабрикатыСРазмножениемСтрок(СтрокаТЧ)
// Проверки заполнения
Если Не ЗначениеЗаполнено(СтрокаТЧ.ИсполняемыйПродукт)
Или Не ЗначениеЗаполнено(СтрокаТЧ.КоличествоИсполняемогоПродукта) Тогда
Возврат;
КонецЕсли;
// Получаем спецификацию
Спецификация = УправлениеПроизводством.ОпределитьСпецификациюПоУмолчанию(
СтрокаТЧ.ИсполняемыйПродукт, Неопределено, ЭтотОбъект.Дата, Неопределено
);
Если Не ЗначениеЗаполнено(Спецификация) Тогда
Возврат;
КонецЕсли;
// Запрос к спецификации
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходныеКомплектующие.Номенклатура КАК Полуфабрикат,
| ИсходныеКомплектующие.Количество КАК КоличествоПоСпецификации,
| ИсходныеКомплектующие.Ссылка.Количество КАК КоличествоВыходаСпецификации
|ИЗ
| Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК ИсходныеКомплектующие
|ГДЕ
| ИсходныеКомплектующие.Ссылка = &Спецификация";
Запрос.УстановитьПараметр("Спецификация", Спецификация);
ТаблицаСостава = Запрос.Выполнить().Выгрузить();
Если ТаблицаСостава.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Сохраняем исходные данные продукта, чтобы размножить их
Продукт = СтрокаТЧ.ИсполняемыйПродукт;
КоличествоПродукта = СтрокаТЧ.КоличествоИсполняемогоПродукта;
ИндексТекущейСтроки = ЭтотОбъект.ТабличнаяЧасть.Индекс(СтрокаТЧ);
// Модифицируем первую (текущую) строку
ПерваяСтрокаСостава = ТаблицаСостава[0];
СтрокаТЧ.ИспользуемыеПолуфабрикаты = ПерваяСтрокаСостава.Полуфабрикат;
КоэфВыхода = ?(ПерваяСтрокаСостава.КоличествоВыходаСпецификации = 0, 1, ПерваяСтрокаСостава.КоличествоВыходаСпецификации);
СтрокаТЧ.КоличествоИспользуемыхПолуфабрикатов = (КоличествоПродукта / КоэфВыхода) * ПерваяСтрокаСостава.КоличествоПоСпецификации;
// Для всех последующих комплектующих спецификации добавляем новые строки в ТЧ
Для Ит = 1 По ТаблицаСостава.Количество() - 1 Цикл
ТекущийСостав = ТаблицаСостава[Ит];
НоваяСтрока = ЭтотОбъект.ТабличнаяЧасть.Вставить(ИндексТекущейСтроки + Ит);
НоваяСтрока.ИсполняемыйПродукт = Продукт;
НоваяСтрока.КоличествоИсполняемогоПродукта = КоличествоПродукта;
НоваяСтрока.ИспользуемыеПолуфабрикаты = ТекущийСостав.Полуфабрикат;
КоэфВыхода = ?(ТекущийСостав.КоличествоВыходаСпецификации = 0, 1, ТекущийСостав.КоличествоВыходаСпецификации);
НоваяСтрока.КоличествоИспользуемыхПолуфабрикатов = (КоличествоПродукта / КоэфВыхода) * ТекущийСостав.КоличествоПоСпецификации;
КонецЦикла;
КонецПроцедуры
Если вы хотите следовать классическим архитектурным стандартам фирмы 1С, заложенным в УПП (например, в документе ОтчетПроизводстваЗаСмену), рекомендуется разделить информацию на две независимые табличные части:
В этом случае на форму выводится кнопка «Заполнить -> По спецификации». При ее нажатии система полностью очищает табличную часть «Материалы», обходит циклом все строки таблицы «Продукция», собирает их спецификации и формирует единый список сырья, сворачивая одинаковые позиции по номенклатуре. Это наиболее масштабируемый и профессиональный подход для автоматизации производственного учета.