При переносе данных между конфигурациями 1С (например, из одной базы «Бухгалтерия предприятия» в другую) через обработку «Универсальный обмен данными XML» часто возникает неинформативная ошибка. В логе это выглядит так: Ошибка при вызове метода контекста (Записать): Не удалось записать: "Вести учет отчитывающихся по взносам организаций". Ситуация осложняется тем, что даже если у пользователя полные права, ошибка сохраняется, а запись всего набора констант прерывается из-за одного единственного значения.
В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем внутренние механизмы платформы и программного кода типовых конфигураций, а также рассмотрим эффективные способы решения задачи.
Проанализируем ситуацию с точки зрения платформы 1С. Когда выполняется обмен данными, обработка создает объект типа КонстантыНабор. Если при вызове метода СоздатьНабор() не указаны конкретные имена констант, платформа включает в этот набор абсолютно все константы, существующие в метаданных конфигурации.
Проблема кроется в Модуле менеджера значения конкретной константы. Давайте посмотрим, что происходит в модуле менеджера константы ВестиУчетОтчитывающихсяПоВзносамОрганизаций. В типовых конфигурациях (БП 3.0, КА 2.4/2.5, ERP) в событии ПередЗаписью часто встречается следующий код:
Процедура ПередЗаписью(Отказ, Замещение)
Если Не ОбщегоНазначения.ПодсистемаСуществует("ЗарплатаКадрыКорпоративнаяПодсистемы") Тогда
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Разберем этот механизм: данная константа предназначена для функционала КОРП-версии. Если в вашей текущей базе эта подсистема отсутствует (например, используется версия ПРОФ), программный код жестко блокирует любую попытку записи значения этой константы, устанавливая флаг Отказ = Истина. Поскольку обмен пытается записать весь Набор, отказ в записи одной константы приводит к отмене всей транзакции. В итоге не записываются даже те константы, которые корректны и необходимы для переноса.
Если нам нужно перенести всего несколько значений, мы можем отказаться от использования объекта КонстантыНабор в пользу прямого обращения к менеджеру каждой константы. Проанализируем пример кода, который можно использовать в обработчике «После загрузки» правила конвертации объектов (ПКО) для констант:
// Объект в данном контексте — это загруженный набор констант
Если Объект <> Неопределено Тогда
ПереносимыеКонстанты = Новый Массив;
ПереносимыеКонстанты.Добавить("ВалютаРегламентированногоУчета");
ПереносимыеКонстанты.Добавить("ИспользоватьНесколькоСкладов");
Для каждого Эл из Метаданные.Константы Цикл
Попытка
// Проверяем, входит ли константа в наш список разрешенных
Если ПереносимыеКонстанты.Найти(Эл.Имя) <> Неопределено Тогда
Константы[Эл.Имя].Установить(Объект[Эл.Имя]);
КонецЕсли;
Исключение
// Обработка ошибок записи отдельных констант
Сообщить("Не удалось записать константу: " + Эл.Имя);
КонецПопытки;
КонецЦикла;
// Предотвращаем стандартную попытку записи всего набора
Отказ = Истина;
КонецЕсли;
Этот метод хорош для небольшого количества констант, но крайне неудобен, если в конфигурации (как в «Комплексной автоматизации») их более 500 штук.
Рассмотрим более продвинутое решение. Чтобы не перечислять сотни имен вручную и при этом не вызывать ошибки записи «проблемных» констант, мы можем сравнить текущие значения в базе с теми, что пришли в файле обмена. Записывать будем только те, которые действительно изменились.
Для сравнения значений удобно использовать метод XMLСтрока(), так как он позволяет быстро привести значения разных типов к строковому виду для сопоставления. Проанализируем по шагам реализацию в событии «После загрузки»:
Если Объект <> Неопределено Тогда
лТекущийНаборКонстант = Константы.СоздатьНабор();
лТекущийНаборКонстант.Прочитать();
лСписокИзмененныхИмен = "";
лМассивНовыхЗначений = Новый Массив;
Для каждого сМетаданныеКонстанты Из Метаданные.Константы Цикл
ИмяК = сМетаданныеКонстанты.Имя;
// Преобразуем в XML-строку для корректного сравнения простых типов
Попытка
лЗначениеОбъектаXML = XMLСтрока(Объект[ИмяК]);
лЗначениеТекущееXML = XMLСтрока(лТекущийНаборКонстант[ИмяК]);
Если лЗначениеТекущееXML <> лЗначениеОбъектаXML Тогда
лСписокИзмененныхИмен = лСписокИзмененныхИмен + ИмяК + ",";
лМассивНовыхЗначений.Добавить(Объект[ИмяК]);
КонецЕсли;
Исключение
// Если XMLСтрока не поддерживает тип константы, пропускаем или обрабатываем иначе
КонецПопытки;
КонецЦикла;
Если ЗначениеЗаполнено(лСписокИзмененныхИмен) Тогда
// Убираем лишнюю запятую в конце
лСписокИзмененныхИмен = Лев(лСписокИзмененныхИмен, СтрДлина(лСписокИзмененныхИмен) - 1);
// Создаем набор только для тех констант, которые реально изменились
лИзмененныйНабор = Константы.СоздатьНабор(лСписокИзмененныхИмен);
лМассивИмен = СтрРазделить(лСписокИзмененныхИмен, ",");
Для Индекс = 0 По лМассивИмен.ВГраница() Цикл
лИзмененныйНабор[лМассивИмен[Индекс]] = лМассивНовыхЗначений[Индекс];
КонецЦикла;
Попытка
лИзмененныйНабор.Записать();
Исключение
Сообщить("Ошибка при записи набора: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
// Отменяем стандартную запись исходного (полного) набора
Отказ = Истина;
КонецЕсли;
Метод Константы.СоздатьНабор("Имя1, Имя2") — это штатный механизм платформы 1С. Когда мы указываем конкретные имена констант в параметрах конструктора, платформа при вызове Записать() обращается к модулям менеджеров только этих констант. Таким образом, если проблемная константа (например, по взносам организаций) не изменилась или вы намеренно исключили её из списка переноса, её код с Отказ = Истина просто не будет выполнен, и вся остальная транзакция пройдет успешно.
Важный нюанс: использование XMLСтрока() значительно ускоряет процесс сравнения и избавляет от проблем со сравнением ссылочных типов, которые могут отсутствовать в базе-приемнике на момент загрузки констант.
Подведем итог: если вы столкнулись с ошибкой записи набора констант, не ищите проблему в правах доступа. Скорее всего, это срабатывает программная проверка в одной из сотен констант. Используйте предложенный выше код для выборочной записи, и ваш обмен будет работать стабильно вне зависимости от состава включенных подсистем.