В процессе разработки в системе 1С:Предприятие часто возникает классическая задача: при проведении документа необходимо проверить, не превышает ли себестоимость товара (хранящаяся в регистре) ту сумму, которую мы указали в документе. Это критически важно для контроля рентабельности продаж. Давайте подробно разберем, как правильно организовать такое сравнение в модуле объекта, избегая типичных ошибок начинающих разработчиков.
Первое, с чем сталкиваются новички, — это ошибка «Переменная не определена» при попытке обратиться к табличной части. Проанализируем ситуацию: если вы пишете код внутри модуля объекта документа (например, в процедуре ОбработкаПроведения), вам не нужно обращаться к документу через точку типа Документ.РеализацияТоваров.Товары. В контексте модуля объекта все реквизиты и табличные части текущего документа уже доступны напрямую по их именам.
Рассмотрим пример. Если ваша табличная часть называется Товары, то для работы с ней достаточно написать просто Товары. Чтобы сделать код более читаемым и явно указать, что мы работаем с данными текущего объекта, рекомендуется использовать ключевое слово ЭтотОбъект.
// Правильное обращение к табличной части в модуле объекта
Для Каждого СтрокаТЧ Из ЭтотОбъект.Товары Цикл
// Здесь мы можем работать с данными строки
ТекущаяСумма = СтрокаТЧ.Сумма;
КонецЦикла;
Разберем типичную архитектурную ошибку. Часто возникает соблазн внутри цикла по строкам табличной части документа делать запрос к регистру накопления для получения себестоимости каждого конкретного товара. Так делать категорически нельзя.
Выполнение запроса внутри цикла (так называемый «запрос в цикле») — это кратчайший путь к деградации производительности системы. Если в документе 100 строк, система сделает 100 обращений к базе данных. Правильный алгоритм, который мы сейчас изучим, заключается в использовании одного запроса на весь документ.
Для эффективного сравнения сумм документа и данных регистра нам необходимо выполнить следующие шаги:
Остатки или Обороты).Ниже приведен пример кода для процедуры ОбработкаПроведения. Мы проанализируем ситуацию, когда нам нужно сравнить колонку Сумма из документа с остатком по ресурсу Стоимость в регистре накопления Запасы.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// 1. Создаем запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокТовары.Номенклатура КАК Номенклатура,
| ДокТовары.Сумма КАК СуммаДокумента,
| ЕСТЬNULL(ЗапасыОстатки.СтоимостьОстаток, 0) КАК СебестоимостьРегистр
|ИЗ
| Документ.РеализацияТоваров.Товары КАК ДокТовары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Запасы.Остатки(&Период, ) КАК ЗапасыОстатки
| ПО ДокТовары.Номенклатура = ЗапасыОстатки.Номенклатура
|ГДЕ
| ДокТовары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
// Для получения оперативных остатков передаем Неопределено или текущую дату
Запрос.УстановитьПараметр("Период", МоментВремени());
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
// 2. Перебор результатов и сравнение
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если ВыборкаДетальныеЗаписи.СуммаДокумента < ВыборкаДетальныеЗаписи.СебестоимостьРегистр Тогда
// 3. Сообщаем об ошибке
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Внимание! По товару " + ВыборкаДетальныеЗаписи.Номенклатура
+ " себестоимость (" + ВыборкаДетальныеЗаписи.СебестоимостьРегистр
+ ") выше суммы продажи (" + ВыборкаДетальныеЗаписи.СуммаДокумента + ")!";
Сообщение.Сообщить();
// Если нужно запретить проведение документа:
// Отказ = Истина;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Проанализируем несколько технических моментов, которые помогут сделать ваше решение более надежным:
1. Момент времени: Обратите внимание, что в параметрах виртуальной таблицы остатков мы используем МоментВремени(). Это позволяет получить данные регистра ровно на позицию нашего документа в шкале времени. Если использовать просто дату, можно не учесть движения, сделанные другими документами в ту же секунду.
2. Управляемые блокировки: В высоконагруженных системах перед выполнением запроса крайне важно установить блокировку на данные регистра. Это гарантирует, что пока ваш документ анализирует себестоимость, другой документ не изменит её. Для этого используется объект БлокировкаДанных.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Запасы");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = ЭтотОбъект.Товары;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
3. Использование Общих модулей: Если вы планируете использовать аналогичную проверку в нескольких документах (например, в «Реализации» и в «Отчете о розничных продажах»), выясним причину, по которой код лучше вынести в общий модуль. Вынос логики в серверный общий модуль позволяет соблюдать принцип Don't Repeat Yourself (DRY). В этом случае в модуль документа вы добавите лишь вызов функции, передав ей ссылку на документ.
Мы выяснили, что для решения задачи сравнения данных документа и регистра не нужно пытаться «вытащить» переменные из формы или писать сложные конструкции обращения к метаданным. Основной инструмент разработчика 1С в этой ситуации — Запрос. Соединяя в запросе таблицу товаров документа и виртуальную таблицу остатков регистра, мы получаем готовую выборку для анализа, что работает быстро и стабильно.
Помните: правильное проведение проверок в модуле объекта — это залог чистоты данных в вашей информационной базе. Всегда используйте Отказ = Истина, если бизнес-логика требует жесткого запрета на проведение некорректных операций.