При организации ежедневного учета рабочего времени в программе 1С:Зарплата и Управление Персоналом 3.0 часто возникает задача жесткого контроля ввода данных. Например, когда ответственные сотрудники (табельщики) должны заполнять табель каждый день, но при этом им запрещается изменять данные за прошедшие дни месяца или удалять уже сохраненные строки. Это необходимо для предотвращения злоупотреблений и сохранения актуальности оперативного учета.
Разработку такого механизма правильнее всего выполнять через расширение конфигурации, чтобы основная программа оставалась на поддержке без изменений. Однако при реализации этой задачи разработчики часто сталкиваются с неочевидным поведением платформы 1С:Предприятие 8.3 — от «пропадания» стандартных событий табличной части до сложности настройки ограничений для динамических колонок дней (которых в табеле может быть от 28 до 31). В этой статье мы подробно разберем, как решить эти проблемы и реализовать надежную блокировку редактирования данных.
Первая техническая сложность, с которой сталкиваются разработчики при переносе логики в расширение, — это игнорирование платформой созданных обработчиков событий (например, ПриИзменении, ПередНачаломИзменения или ПередУдалением). Вы можете создать процедуру вручную, связать ее в свойствах элемента управления, но при работе в режиме «1С:Предприятие» отладчик просто игнорирует эту процедуру.
Выясним причину этого поведения. Когда управляемая форма заимствуется в расширение, платформа переносит только саму визуальную оболочку. Связи с данными и внутренние механизмы оптимизируются платформой, чтобы не перегружать оперативную память. Если вы хотите, чтобы в расширении стали доступны и корректно отрабатывали все стандартные события таблицы формы, необходимо выполнить следующие действия:
ТабельУчетаРабочегоВремени, но и основной реквизит формы — обычно это Объект.ДанныеФизическихЛиц) со всеми необходимыми реквизитами.ПередНачаломИзменения) действительно указано имя вашей процедуры-обработчика из расширения, а не просто пустая строка.Если вы не хотите перегружать расширение заимствованием большого количества реквизитов формы, существует альтернативный программный путь. Мы можем динамически назначить обработчик события в процедуре ПриСозданииНаСервере (с аннотацией &После), используя метод УстановитьДействие(). Рассмотрим пример такого кода:
&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Динамически связываем событие таблицы с процедурой в модуле формы
ЭлементТаблица = Элементы.ДанныеФизическихЛиц;
ЭлементТаблица.УстановитьДействие("ПередНачаломИзменения", "Расш_ДанныеФизическихЛицПередНачаломИзменения");
ЭлементТаблица.УстановитьДействие("ПередУдалением", "Расш_ДанныеФизическихЛицПередУдалением");
КонецПроцедуры
Теперь, когда обработчики гарантированно срабатывают, мы можем перейти к реализации бизнес-логики запрета редактирования.
Основная специфика документа «Табель» в 1С:ЗУП 3.0 заключается в том, что колонки для ввода отработанного времени (с 1 по 31 число) генерируются формой динамически при открытии, в зависимости от выбранного месяца регистрации. Статически настроить Условное Оформление в конфигураторе на конкретные колонки не получится, так как их состав постоянно меняется.
Решением является программное добавление элементов условного оформления на сервере при открытии формы или при изменении периода. Предположим, что текущая дата — 21 сентября. Нам необходимо заблокировать для редактирования все колонки дней с 1 по 20 число, оставив доступными только 21 число и последующие дни месяца.
Проанализируем алгоритм динамической настройки оформления:
&После("ПриСозданииНаСервере")
Процедура Расш_ПрограммноеУсловноеОформление(Отказ, СтандартнаяОбработка)
ТекущаяДатаСеанса = ТекущаяДатаСеанса();
ТекущийДень = День(ТекущаяДатаСеанса);
МесяцДокумента = Объект.ПериодРегистрации;
// Проверяем, что документ оформляется за текущий месяц
Если НачалоМесяца(МесяцДокумента) = НачалоМесяца(ТекущаяДатаСеанса) Тогда
// Будем блокировать колонки прошедших дней месяца
Для НомерДня = 1 По ТекущийДень - 1 Цикл
// Формируем имя динамической колонки времени
ИмяКолонкиВремя = "ДанныеФизическихЛицПервыйВидВремени" + НомерДня; // Имя реквизита может отличаться в вашей версии ЗУП
Если Элементы.Найти(ИмяКолонкиВремя) <> Неопределено Тогда
// Создаем новый элемент условного оформления
ЭлементОформления = УсловноеОформление.Элементы.Добавить();
// Настраиваем только просмотр для ячейки
ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ТолькоПросмотр", Истина);
// Добавляем оформляемое поле
ОформляемоеПоле = ЭлементОформления.Поля.Добавить();
ОформляемоеПоле.Поле = Новый ПолеКомпоновкиДанных(ИмяКолонкиВремя);
ОформляемоеПоле.Использование = Истина;
// Условие применения (всегда истинно для всех строк этой колонки)
ЭлементОтбора = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Объект.ДанныеФизическихЛиц.Сотрудник");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Заполнено;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
КонецДля;
КонецЕсли;
КонецПроцедуры
Этот способ является максимально дружелюбным для пользователя: система визуально затеняет ячейки за прошедшие дни, показывая, что они закрыты для корректировки, но при этом разрешает редактировать текущий день.
Условное оформление отлично защищает ячейки от случайного изменения ввода, но оно не способно предотвратить удаление целых строк из документа или добавление новых пустых строк задним числом. Чтобы полностью обезопасить документ от модификации структуры данных, задействуем обработчики событий ПередНачаломИзменения и ПередУдалением.
Рассмотрим по шагам реализацию ограничений в модуле формы расширения:
&НаКлиенте
Процедура Расш_ДанныеФизическихЛицПередНачаломИзменения(Элемент, Отказ, Копирование)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Если строка уже существует и была записана ранее (проверяем по заполненности ключевых полей)
Если ЗначениеЗаполнено(ТекущиеДанные.Сотрудник) И Не Копирование Тогда
// Разрешаем редактирование только администраторам или пользователям с особыми правами
Если Не РольДоступна("ПолныеПрава") Тогда
Отказ = Истина;
ПоказатьПредупреждение(, "Редактирование ранее созданных строк сотрудников запрещено! Обратитесь к администратору.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура Расш_ДанныеФизическихЛицПередУдалением(Элемент, Отказ)
// Запрещаем удаление любых строк из табеля обычным пользователям
Если Не РольДоступна("ПолныеПрава") Тогда
Отказ = Истина;
ПоказатьПредупреждение(, "Удаление строк из утвержденного табеля категорически запрещено!");
КонецЕсли;
КонецПроцедуры
При таком подходе пользователь сможет добавлять новые строки для ввода данных за текущий день, но любые попытки изменить старых сотрудников или удалить строки из таблицы будут жестко пресекаться системой на уровне интерфейса.
Если вам требуется максимально гибкое управление доступностью конкретных ячеек «на лету» без использования тяжеловесного механизма условного оформления, можно использовать событие ПриАктивизацииЯчейки на клиенте. Этот метод работает мгновенно и активируется каждый раз, когда курсор пользователя перемещается по таблице.
Посмотрим на пример реализации данного алгоритма:
&НаКлиенте
Процедура Расш_ДанныеФизическихЛицПриАктивизацииЯчейки(Элемент)
ТекущаяКолонка = Элемент.ТекущаяКолонка;
Если ТекущаяКолонка = Неопределено Тогда
Возврат;
КонецЕсли;
ИмяКолонки = ТекущаяКолонка.Имя;
// Проверяем, является ли колонка одной из ячеек ввода времени (содержит индекс дня)
Если СтрНайти(ИмяКолонки, "ДанныеФизическихЛицПервыйВидВремени") > 0 Тогда
// Извлекаем номер дня из имени колонки
НомерДняСтр = СтрЗаменить(ИмяКолонки, "ДанныеФизическихЛицПервыйВидВремени", "");
НомерДня = Число(НомерДняСтр);
ТекущийДень = День(ТекущаяДатаСеанса());
// Если номер дня меньше текущего - запрещаем редактирование ячейки
Если НомерДня < ТекущийДень Тогда
Элемент.ТолькоПросмотр = Истина;
Иначе
Элемент.ТолькоПросмотр = Ложь;
КонецЕсли;
Иначе
// Для всех остальных служебных колонок оставляем стандартное поведение
Элемент.ТолькоПросмотр = Ложь;
КонецЕсли;
КонецПроцедуры
Важное примечание: Метод динамического переключения свойства ТолькоПросмотр в событии активизации ячейки очень производителен, однако он защищает данные только от интерактивного изменения пользователем вручную. Если пользователь попытается запустить какую-либо групповую обработку заполнения, этот клиентский механизм не сработает. Поэтому рекомендуется комбинировать его с серверной проверкой в модуле объекта ПередЗаписью.
Для создания комплексной защиты табеля учета рабочего времени в 1С:ЗУП 3.0 от изменения задним числом используйте комбинированный подход:
ПередЗаписью, чтобы исключить запись измененного табеля программным путем или обработками.Использование расширения конфигурации позволит вам внедрить эти правила оперативного контроля без снятия программы с поддержки, гарантируя простоту последующих обновлений 1С.