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