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