В процессе поддержки и обновления старых или сильно кастомизированных конфигураций на платформе «1С:Предприятие» разработчики часто сталкиваются с неприятной ситуацией: после очередного обновления базы данных дублируется предопределенный элемент. Особенно болезненно это происходит в кадровых и расчетных базах, таких как «Зарплата и управление персоналом» редакции 2.5. Например, может задвоиться критически важный предопределенный элемент плана видов расчета (ПВР) «Районный коэффициент» или «Северная надбавка».
При этом старый элемент, на который ссылаются сотни документов прошлых периодов и миллионы записей в регистрах расчета, внезапно теряет признак предопределенного (лишается «золотой точки» на пиктограмме), а новый пустой элемент становится предопределенным. В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем ошибки коллег на форумах и выработаем максимально безопасный и эффективный алгоритм слияния дублей без необходимости опасного перепроведения документов прошлых периодов.
Для того чтобы понять корень проблемы, нам необходимо обратиться к архитектуре платформы «1С:Предприятие». Связь между предопределенным элементом, описанным разработчиком в конфигураторе, и реальной физической записью в таблице базы данных (в режиме «1С:Предприятие») в старых версиях платформы (до версии 8.3.3) и во всех конфигурациях, работающих в режиме совместимости с версией 8.2, осуществляется строго по внутреннему уникальному идентификатору — UUID (Universally Unique Identifier).
Когда мы обновляем конфигурацию, платформа выполняет сопоставление объектов. Сценарий возникновения дубля выглядит следующим образом:
В результате мы получаем ситуацию, когда расчетчики не могут начислять зарплату по новому предопределенному элементу (так как по нему нет истории и настроек), а старый элемент потерял статус предопределенного, что нарушает логику работы программных алгоритмов конфигурации.
Проанализируем обсуждения специалистов и выделим ключевые ошибки, которые часто совершаются при попытке быстро исправить эту ситуацию.
Попытка восстановить предопределенность через код. Некоторые разработчики пытаются программно перезаписать имя предопределенного элемента, используя свойства ИмяПредопределенного или ИмяПредопределенныхДанных у объекта. В конфигурациях уровня ЗУП 2.5, работающих в режиме совместимости с платформой 8.2, этот код вызовет ошибку: Поле объекта не обнаружено (ИмяПредопределенныхДанных). Данный механизм управления предопределенными данными напрямую из кода был добавлен только в платформе 8.3.3 и работает исключительно при отключенном режиме совместимости. В нашем случае восстановить связь «старого» элемента с метаданными программным путем невозможно.
Заблуждение об ограничениях стандартных обработок. Существует мнение, что стандартные обработки поиска и замены дублей (например, с дисков или портала ИТС) умеют работать только со справочниками. Это абсолютно не так. Универсальная обработка «Поиск и замена значений» (ПоискИЗаменаЗначений.epf) работает напрямую со ссылочными типами данных метаданных платформы «1С:Предприятие» и способна находить и корректно заменять ссылки любого типа, включая документы, планы счетов, виды субконто и планы видов расчета.
Опасность перепроведения документов. Самая главная и критическая ошибка — это замена ссылок ручным кодом с последующим групповым перепроведением документов прошлых периодов. В таких конфигурациях, как «Зарплата и управление персоналом», перепроводить документы за закрытые периоды прошлых месяцев или лет категорически запрещено. Это неизбежно приведет к:
Для того чтобы корректно слить два элемента плана видов расчета обратно в один без негативных последствий для базы данных, мы должны использовать метод замены ссылок в режиме загрузки данных. Этот метод позволяет перезаписать реквизиты документов и записи регистров напрямую, минуя проведение и вызов тяжелых транзакционных обработчиков.
Рассмотрим два основных и наиболее безопасных способа реализации этой задачи.
Этот способ является наиболее предпочтительным, так как он не требует написания кода и полностью исключает человеческий фактор при составлении алгоритмов перезаписи объектов.
ПоискИЗаменаЗначений.epf (обратите внимание, что вам нужна версия для обычных форм, если вы работаете в ЗУП 2.5).Универсальная обработка автоматически производит запись всех найденных объектов с установленным флагом ОбменДанными.Загрузка = Истина. Это гарантирует, что документы не будут перепроводиться, а записи регистров изменятся напрямую в базе данных без искажения исторических расчетов.
Если по каким-то причинам у вас нет доступа к стандартной обработке, мы можем написать небольшую самописную обработку, которая выполнит замену точечно и безопасно. Рассмотрим, как правильно спроектировать этот код.
При написании скрипта ключевое правило — использовать свойство ОбменДанными.Загрузка = Истина для каждого изменяемого объекта. Проанализируем пример кода для замены ссылок в документах:
// Пример программной замены ссылки в документах без перепроведения
Процедура ЗаменитьСсылкуВДокументах(СтараяСсылка, НоваяСсылка)
// Получаем все ссылки на заменяемый элемент ПВР
ТаблицаСсылок = НайтиПоСсылкам(Новый Массив(1, СтараяСсылка));
Для Каждого СтрокаСсылки Из ТаблицаСсылок Цикл
СсылкаНаОбъект = СтрокаСсылки.Ссылка;
ТипОбъекта = ТипЗнч(СсылкаНаОбъект);
// Обрабатываем только документы
Если Документы.ТипВсеСсылки().СодержитТип(ТипОбъекта) Тогда
ДокументОбъект = СсылкаНаОбъект.ПолучитьОбъект();
// Нам необходимо обойти все реквизиты и табличные части документа
// Для этого воспользуемся универсальным обходом метаданных документа
МетаданныеДокумента = ДокументОбъект.Метаданные();
ИзмененияВнесены = Ложь;
// 1. Проверяем шапку документа
Для Каждого Реквизит Из МетаданныеДокумента.Реквизиты Цикл
ИмяРеквизита = Реквизит.Имя;
Если ДокументОбъект[ИмяРеквизита] = СтараяСсылка Тогда
ДокументОбъект[ИмяРеквизита] = НоваяСсылка;
ИзмененияВнесены = Истина;
КонецЕсли;
КонецЦикла;
// 2. Проверяем все табличные части документа
Для Каждого ТЧ Из МетаданныеДокумента.ТабличныеЧасти Цикл
ИмяТЧ = ТЧ.Имя;
ТабличнаяЧасть = ДокументОбъект[ИмяТЧ];
Для Каждого СтрокаТЧ Из ТабличнаяЧасть Цикл
Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
ИмяРеквизитаТЧ = РеквизитТЧ.Имя;
Если СтрокаТЧ[ИмяРеквизитаТЧ] = СтараяСсылка Тогда
СтрокаТЧ[ИмяРеквизитаТЧ] = НоваяСсылка;
ИзмененияВнесены = Истина;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
// Если в документе были найдены и заменены ссылки, записываем его
Если ИзмененияВнесены Тогда
// Отключаем проведение и проверки при записи
ДокументОбъект.ОбменДанными.Загрузка = Истина;
Попытка
ДокументОбъект.Записать(ДокументОбъект.РежимЗаписи);
Сообщить("Успешно обработан документ: " + Строка(СсылкаНаОбъект));
Исключение
Сообщить("Ошибка записи документа " + Строка(СсылкаНаОбъект) + ": " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Помимо документов, расчетные конфигурации хранят важнейшие сведения в регистрах расчета (например, в регистре «Основные начисления сотрудников организаций»). Напрямую через объект документа эти записи при включенном режиме Загрузка = Истина могут не обновиться автоматически (в зависимости от настроек проведения). Поэтому нам также необходимо выполнить прямую замену в наборах записей регистров расчета.
Рассмотрим, как выглядит процедура корректного изменения записей в регистре расчета:
// Пример программной замены ссылки в движениях регистров расчета
Процедура ЗаменитьСсылкаВРегистрахРасчета(ИмяРегистра, СтараяСсылка, НоваяСсылка, ДокументРегистратор)
// Создаем набор записей регистра расчета по конкретному регистратору (документу)
НаборЗаписей = РегистрыРасчета[ИмяРегистра].СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументРегистратор);
НаборЗаписей.Прочитать();
ИзмененияВнесены = Ложь;
Для Каждого Запись Из НаборЗаписей Цикл
// Поле "ВидРасчета" хранит ссылку на ПВР
Если Запись.ВидРасчета = СтараяСсылка Тогда
Запись.ВидРасчета = НоваяСсылка;
ИзмененияВнесены = Истина;
КонецЕсли;
КонецЦикла;
Если ИзмененияВнесены Тогда
// Записываем набор записей без вызова обработчиков событий регистра
НаборЗаписей.ОбменДанными.Загрузка = Истина;
Попытка
НаборЗаписей.Записать();
Сообщить("Обновлен набор записей регистра " + ИмяРегистра + " для документа " + Строка(ДокументРегистратор));
Исключение
Сообщить("Ошибка записи регистра расчета: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
КонецЕсли;
КонецПроцедуры
После того как замена ссылок во всех документах и движениях регистров успешно завершена одним из описанных способов, нам необходимо выполнить финальные действия по очистке базы данных:
Проанализировав данную проблему, мы пришли к выводу, что своевременное понимание механизмов платформы «1С:Предприятие» позволяет избегать грубых ошибок администрирования. Использование режима загрузки данных при замене ссылок сохраняет целостность исторических расчетов и бережет нервы программистов и бухгалтеров вашей организации.