Как исправить ошибку «Не удалось записать/провести документ» в 1С и настроить логирование изменений его реквизитов?

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия IT и автоматизация бизнеса
← На главную

В процессе разработки и сопровождения конфигураций на платформе 1С:Предприятие программисты часто сталкиваются со скрытыми ошибками при записи или проведении документов. Особенно остро эта проблема встает в распределенных информационных базах (РИБ), где действуют жесткие правила синхронизации данных. Кроме того, часто возникает задача отслеживания истории изменения важных реквизитов документа (например, суммы, контрагента или договора) для последующего анализа и построения отчетов.

В данной статье мы подробно разберем, как локализовать и устранить неочевидную ошибку блокировки проведения документа, а также по шагам спроектируем и реализуем оптимальный механизм логирования изменений реквизитов с использованием независимого периодического регистра сведений.

Часть 1. Выясняем причину ошибки «Не удалось записать/провести документ»

Когда при попытке проведения документа система выдает лаконичное предупреждение «Не удалось записать/провести документ» без указания конкретной технической ошибки в окне сообщений, это часто ставит разработчика в тупик. Обычная точка останова в процедуре ОбработкаПроведения модуля документа может даже не срабатывать. Давайте разберем последовательность действий для локализации этой проблемы.

Шаг 1. Анализ Журнала регистрации

Первое, что необходимо сделать — обратиться к Журналу регистрации. Очень часто платформа или прикладной код записывают туда подробные сведения об ошибках базы данных, блокировках или программных исключениях, которые «глушатся» в интерфейсе пользователя конструкцией Попытка...Исключение. Перейдем в меню «Сервис» — «Журнал регистрации» (в обычном приложении) или «Администрирование» — «Обслуживание» — «Журнал регистрации» (в управляемом приложении) и установим отбор по текущему пользователю и времени совершения операции.

Шаг 2. Проверка предопределенных обработчиков событий

Если точка останова в модуле документа не срабатывает, это означает, что отказ происходит на более раннем этапе — еще до начала транзакции проведения. Нам необходимо проверить всю цепочку событий при записи объекта:

  1. Процедура ПередЗаписью в модуле формы документа.
  2. Процедура ПередЗаписью в модуле самого документа.
  3. Общие подписки на события с источником, соответствующим нашему документу (например, подписки на события ПередЗаписью или ПриЗаписи).

Для отладки этих процедур в файловом или клиент-серверном варианте обязательно должна быть включена отладка на сервере (параметр запуска службы 1С -debug). Установим точки останова в самом начале процедур ПередЗаписью формы и объекта.

Шаг 3. Специфика работы в РИБ (Распределенных Информационных Базах)

Проанализировав логику подписок на события, мы можем обнаружить специфическое поведение конфигурации. В базах, работающих по распределенной схеме, часто программно запрещается повторное редактирование и проведение документов, которые были созданы в других подразделениях (узлах РИБ) или уже были отправлены в центральный офис. В коде подписки на событие перед записью может стоять проверка:


Если Источник.ОбменДанными.Загрузка Тогда
    Возврат;
КонецЕсли;

// Проверка на то, является ли документ "чужим" или повторно записываемым
Если ЭтотУзелРИБНеРазрешаетРедактирование(Источник) Тогда
    Отказ = Истина;
    // Часто здесь забывают вывести информационное сообщение пользователю!
КонецЕсли;

Если условие выполняется, переменной Отказ присваивается значение Истина, запись прерывается, а пользователь видит стандартное системное предупреждение без объяснения причин. Для решения этой проблемы необходимо либо скорректировать бизнес-логику ограничений в коде подписки, либо выполнять запись документа под учетной записью с административными правами, для которой эти ограничения не действуют.


Часть 2. Проектирование регистра сведений для логирования изменений

Предположим, нам поставлена задача: фиксировать каждое изменение реквизита «СуммаКредита» в документе и записывать историю этих изменений в специальный регистр сведений (РС) для последующего вывода в отчет. Разберем типичные ошибки проектирования и придем к правильной архитектуре.

Ошибочный подход: Подчиненный регистр сведений

Если мы создадим регистр сведений, подчиненный регистратору (нашему документу), то столкнемся со следующими проблемами:

Правильный подход: Независимый периодический регистр сведений

Чтобы история изменений не удалялась при отмене проведения документа, регистр сведений должен быть независимым (режим записи — «Независимый»). Чтобы фиксировать изменения во времени, он должен быть периодическим (например, с периодичностью «В пределах секунды»).

Спроектируем структуру нашего регистра сведений (назовем его ПП_КОЖ):

Периодичность: Позиция регистратора или В пределах секунды.

Измерения:

  1. ДокументСсылка (тип: Ссылка на наш документ). Это ключевое измерение. Оно связывает записи регистра с конкретным документом.

Ресурсы:

  1. Сумма (тип: Число). Сюда мы будем записывать новое значение суммы.

Реквизиты:

  1. ДатаИзменения (тип: Дата).
  2. Менеджер (тип: СправочникСсылка.Пользователи).
  3. Заемщик (тип: СправочникСсылка.Контрагенты).

Благодаря наличию измерения ДокументСсылка и периодичности регистра, мы сможем хранить неограниченное число версий одного документа, так как уникальность ключа записи будет определяться комбинацией: Период + ДокументСсылка.


Часть 3. Реализация алгоритма записи истории изменений

Теперь напишем обработчик, который будет срабатывать при проведении документа. Нам нужно решить важную задачу: записывать данные в регистр только в том случае, если контролируемый реквизит действительно изменился по сравнению с предыдущей версией. Если пользователь просто перепроводит документ без изменения ключевых полей, новая запись в историю лога добавляться не должна.

Для реализации этого алгоритма мы будем использовать подписку на событие ОбработкаПроведения или напишем код непосредственно в модуле документа.

Рассмотрим пошагово оптимальный алгоритм:

Шаг 1. Получение последней сохраненной записи из регистра

Перед тем как записать новую строку, мы должны узнать, с какими значениями реквизитов документ был записан в прошлый раз. Для этого обратимся к виртуальной таблице СрезПоследних нашего независимого регистра сведений.

Шаг 2. Сравнение значений реквизитов

Сравним текущие реквизиты проводимого документа со значениями из последней записи регистра. Если они отличаются (или если записей в регистре по этому документу еще вообще нет), мы создаем новую запись.

Шаг 3. Использование Менеджера записи для создания новой строки истории

Для добавления одной записи в независимый регистр сведений эффективнее использовать объект РегистрыСведений.[ИмяРегистра].СоздатьМенеджерЗаписи().

Ниже представлен готовый к использованию код процедуры обработки проведения:


Процедура ПП_КОЖОбработкаПроведения(Источник, Отказ) Экспорт
    
    // Шаг 1. Запросом получаем последнюю запись по нашему документу из регистра сведений
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПП_КОЖСрезПоследних.Сумма КАК Сумма,
    |	ПП_КОЖСрезПоследних.Заемщик КАК Заемщик,
    |	ПП_КОЖСрезПоследних.Менеджер КАК Менеджер
    |ИЗ
    |	РегистрСведений.ПП_КОЖ.СрезПоследних(, ДокументСсылка = &ДокументСсылка) КАК ПП_КОЖСрезПоследних";
    
    Запрос.УстановитьПараметр("ДокументСсылка", Источник.Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    
    НужноЗаписатьНовуюВерсию = Ложь;
    
    Если РезультатЗапроса.Пустой() Тогда
        // Если записей по документу еще не было, это первое проведение — обязательно записываем
        НужноЗаписатьНовуюВерсию = Истина;
    Иначе
        Выборка = РезультатЗапроса.Выбрать();
        Выборка.Следующий();
        
        // Шаг 2. Сравниваем ключевые реквизиты
        Если Выборка.Сумма <> Источник.СуммаКредита 
            Или Выборка.Заемщик <> Источник.Клиент 
            Или Выборка.Менеджер <> Источник.Сотрудник Тогда
            
            НужноЗаписатьНовуюВерсию = Истина;
        КонецЕсли;
    КонецЕсли;
    
    // Шаг 3. Если зафиксированы изменения, пишем новую строку в регистр истории
    Если НужноЗаписатьНовуюВерсию Тогда
        
        МенеджерЗаписи = РегистрыСведений.ПП_КОЖ.СоздатьМенеджерЗаписи();
        
        // Заполняем ключевые поля (Период и Измерения), определяющие уникальность записи
        // В качестве периода используем текущее системное время сеанса, чтобы избежать коллизий при перепроведении
        МенеджерЗаписи.Период = ТекущаяДатаСеанса();
        МенеджерЗаписи.ДокументСсылка = Источник.Ссылка;
        
        // Заполняем ресурсы и реквизиты
        МенеджерЗаписи.Сумма = Источник.СуммаКредита;
        МенеджерЗаписи.Заемщик = Источник.Клиент;
        МенеджерЗаписи.Менеджер = Источник.Сотрудник;
        МенеджерЗаписи.ДатаИзменения = ТекущаяДатаСеанса();
        
        Попытка
            // Записываем с признаком замещения = Истина, чтобы обновить запись, 
            // если вдруг в одну и ту же секунду документ проводится повторно
            МенеджерЗаписи.Записать(Истина);
        Исключение
            // В случае непредвиденных ошибок блокировок или СУБД выводим информативное сообщение
            ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
                НСтр("ru = 'Не удалось записать историю изменения документа в регистр: '") + ОписаниеОшибки()
            );
        КонецПопытки;
        
    КонецЕсли;
    
КонецПроцедуры

Часть 4. Анализ данных и выгрузка отчетов по изменениям

После того как наш независимый периодический регистр успешно наполняется данными, перед нами встает задача анализа этой информации. Например, нам нужно получить отчет по всем договорам, у которых за время их жизненного цикла хотя бы один раз менялась сумма.

Для решения этой задачи мы можем использовать запрос с группировкой по договору (измерению ДокументСсылка или реквизиту НомерДоговора). Если количество записей в регистре по конкретному договору строго больше единицы, это означает, что по нему происходили изменения реквизитов.

Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    ПП_КОЖ.ДокументСсылка КАК Документ,
    ПП_КОЖ.ДокументСсылка.Номер КАК НомерДокумента,
    КОЛИЧЕСТВО(ПП_КОЖ.Период) КАК КоличествоИзменений
ИЗ
    РегистрСведений.ПП_КОЖ КАК ПП_КОЖ
СГРУППИРОВАТЬ ПО
    ПП_КОЖ.ДокументСсылка,
    ПП_КОЖ.ДокументСсылка.Номер
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(ПП_КОЖ.Период) > 1

Этот простой и лаконичный запрос выберет исключительно те документы, по которым была зафиксирована история правок реквизитов. Результат можно передать в Систему компоновки данных (СКД) для формирования красивого и понятного отчета руководству компании.

← На главную