Почему запрос к регистру накопления в событии ПриЗаписи возвращает пустой результат и как работает фиксация изменений в 1С:ERP?

Программист 1С v8.3 (Управляемые формы) 1С:ERP Управление предприятием Управленческий учет IT и автоматизация бизнеса
← На главную

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

1. Транзакционная модель работы событий набора записей регистра

Для понимания причин происходящего проанализируем последовательность действий платформы при записи набора записей с установленным свойством Замещение = Истина (поведение по умолчанию при проведении документов):

  1. Событие ПередЗаписью модуля набора записей:

    В этот момент коллекция ЭтотОбъект содержит новые строки, которые только планируется записать в базу данных. Однако сама физическая таблица СУБД еще не модифицирована: в ней по-прежнему лежат старые движения по текущему регистратору. Если выполнить прямой запрос к таблице регистра, система прочитает состояние данных до начала текущей записи.

  2. Физическая перезапись данных платформой (между событиями):

    Внутри открытой транзакции платформа удаляет из таблицы СУБД все строки, соответствующие установленному отбору (по регистратору), и вставляет в нее строки из коллекции ЭтотОбъект.

  3. Событие ПриЗаписи модуля набора записей:

    В этот момент таблица базы данных внутри текущей транзакции уже обновлена. Если на шаге подготовки коллекция ЭтотОбъект была пустой (например, документ отменил проведение или сформировал пустой набор записей), то в таблице базы данных по данному регистратору физически остается 0 строк. Соответственно, любой запрос внутри транзакции вернет пустой результат.

2. Почему консоль запросов показывает старые данные

Выясним причину расхождения между результатом запроса в отладке и в консоли запросов. Код модуля набора записей выполняется внутри незафиксированной транзакции проведения. В соответствии с уровнем изоляции транзакций (Read Committed / Повторяемость чтения), изменения, сделанные в транзакции документа, изолированы от внешних сеансов:

  1. Внутри процедуры ПриЗаписи запрос видит актуальное состояние транзакции — строки уже очищены или замещены.
  2. Консоль запросов, запущенная в отдельном сеансе без блокирующих чтений, читает зафиксированные данные прошлых транзакций, поэтому отображает старые записи до тех пор, пока документ не завершит транзакцию проведения (Commit) или не отменит ее (Rollback).

3. Типовой механизм выявления изменений движений в 1С:ERP

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

Рассмотрим, как этот алгоритм реализован в модуле набора записей регистра (например, РегистрНакопления.ТрудозатратыНезавершенногоПроизводства):

В процедуре ПередЗаписью срез старых записей помещается во временную таблицу:


Процедура ПередЗаписью(Отказ, Замещение)
	
	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ДополнительныеСвойства.Свойство("ДляПроведения") Тогда
		Возврат;
	КонецЕсли;
	
	СтруктураВременныеТаблицы = ДополнительныеСвойства.ДляПроведения.СтруктураВременныеТаблицы;
	
	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = СтруктураВременныеТаблицы.МенеджерВременныхТаблиц;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	Затраты.Период КАК Период,
		|	Затраты.Регистратор КАК Регистратор,
		|	Затраты.Организация КАК Организация,
		|	Затраты.Подразделение КАК Подразделение,
		|	Затраты.ЗаказНаПроизводство КАК ЗаказНаПроизводство,
		|	Затраты.КодСтрокиПродукция КАК КодСтрокиПродукция,
		|	Затраты.Этап КАК Этап,
		|	Затраты.СтатьяКалькуляции КАК СтатьяКалькуляции,
		|	Затраты.ВидРабот КАК ВидРабот,
		|	Затраты.ГруппаПродукции КАК ГруппаПродукции,
		|	Затраты.Количество КАК Количество,
		|	Затраты.НормативнаяСтоимость КАК НормативнаяСтоимость,
		|	Затраты.Стоимость КАК Стоимость,
		|	Затраты.СтоимостьРегл КАК СтоимостьРегл
		|ПОМЕСТИТЬ ТрудозатратыНезавершенногоПроизводстваПередЗаписью
		|ИЗ
		|	РегистрНакопления.ТрудозатратыНезавершенногоПроизводства КАК Затраты
		|ГДЕ
		|	Затраты.Регистратор = &Регистратор";
		
	Запрос.УстановитьПараметр("Регистратор", Отбор.Регистратор.Значение);
	Запрос.Выполнить();
	
КонецПроцедуры

В процедуре ПриЗаписи платформа сравнивает старые данные из временной таблицы с новыми данными, уже записанными в физическую таблицу регистра:


Процедура ПриЗаписи(Отказ, Замещение)
	
	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ДополнительныеСвойства.Свойство("ДляПроведения") Тогда
		Возврат;
	КонецЕсли;
	
	СтруктураВременныеТаблицы = ДополнительныеСвойства.ДляПроведения.СтруктураВременныеТаблицы;
	
	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = СтруктураВременныеТаблицы.МенеджерВременныхТаблиц;
	Запрос.Текст = 
		"ВЫБРАТЬ РАЗЛИЧНЫЕ
		|	НАЧАЛОПЕРИОДА(Таблица.Период, МЕСЯЦ) КАК Месяц,
		|	Таблица.Организация КАК Организация,
		|	Таблица.Регистратор КАК Документ
		|ПОМЕСТИТЬ ТрудозатратыНезавершенногоПроизводстваЗаданияКРасчетуСебестоимости
		|ИЗ
		|	(ВЫБРАТЬ
		|		Затраты.Период КАК Период,
		|		Затраты.Регистратор КАК Регистратор,
		|		Затраты.Организация КАК Организация,
		|		Затраты.Подразделение КАК Подразделение,
		|		Затраты.ЗаказНаПроизводство КАК ЗаказНаПроизводство,
		|		Затраты.КодСтрокиПродукция КАК КодСтрокиПродукция,
		|		Затраты.Этап КАК Этап,
		|		Затраты.СтатьяКалькуляции КАК СтатьяКалькуляции,
		|		Затраты.ВидРабот КАК ВидРабот,
		|		Затраты.ГруппаПродукции КАК ГруппаПродукции,
		|		Затраты.Количество КАК Количество,
		|		Затраты.НормативнаяСтоимость КАК НормативнаяСтоимость,
		|		Затраты.Стоимость КАК Стоимость,
		|		Затраты.СтоимостьРегл КАК СтоимостьРегл
		|	ИЗ
		|		ТрудозатратыНезавершенногоПроизводстваПередЗаписью КАК Затраты
		|	
		|	ОБЪЕДИНИТЬ ВСЕ
		|	
		|	ВЫБРАТЬ
		|		Затраты.Период,
		|		Затраты.Регистратор,
		|		Затраты.Организация,
		|		Затраты.Подразделение,
		|		Затраты.ЗаказНаПроизводство,
		|		Затраты.КодСтрокиПродукция,
		|		Затраты.Этап,
		|		Затраты.СтатьяКалькуляции,
		|		Затраты.ВидРабот,
		|		Затраты.ГруппаПродукции,
		|		-Затраты.Количество,
		|		-Затраты.НормативнаяСтоимость,
		|		-Затраты.Стоимость,
		|		-Затраты.СтоимостьРегл
		|	ИЗ
		|		РегистрНакопления.ТрудозатратыНезавершенногоПроизводства КАК Затраты
		|	ГДЕ
		|		Затраты.Регистратор = &Регистратор) КАК Таблица
		|СГРУППИРОВАТЬ ПО
		|	НАЧАЛОПЕРИОДА(Таблица.Период, МЕСЯЦ),
		|	Таблица.Период,
		|	Таблица.Регистратор,
		|	Таблица.Организация,
		|	Таблица.Подразделение,
		|	Таблица.ЗаказНаПроизводство,
		|	Таблица.КодСтрокиПродукция,
		|	Таблица.Этап,
		|	Таблица.СтатьяКалькуляции,
		|	Таблица.ВидРабот,
		|	Таблица.ГруппаПродукции
		|ИМЕЮЩИЕ
		|	СУММА(Таблица.Количество) <> 0
		|	ИЛИ СУММА(Таблица.НормативнаяСтоимость) <> 0
		|	ИЛИ СУММА(Таблица.Стоимость) <> 0
		|	ИЛИ СУММА(Таблица.СтоимостьРегл) <> 0;
		|
		|УНИЧТОЖИТЬ ТрудозатратыНезавершенногоПроизводстваПередЗаписью";
		
	Запрос.УстановитьПараметр("Регистратор", Отбор.Регистратор.Значение);
	Запрос.Выполнить();
	
КонецПроцедуры

Если в результате группировки с условием ИМЕЮЩИЕ СУММА(...) <> 0 формируются строки, система регистрирует необходимость пересчета себестоимости за указанный месяц. Когда новые движения по ошибке очищаются или не формируются, старые движения схлопываются с нулем, разница оказывается ненулевой, и документ при каждом перепроведении бесконечно сдвигает границу расчета себестоимости назад.

4. Диагностика и устранение причин пустых движений

Если при отладке запрос в ПриЗаписи возвращает пустую таблицу, выполним следующую последовательность проверок:

  1. Проверка содержимого коллекции ЭтотОбъект:

    В точке останова внутри процедуры ПередЗаписью проверим свойство ЭтотОбъект.Количество(). Если коллекция пуста, значит, сам документ (например, Документ.ЭтапПроизводства2_2) передал на запись пустой набор записей.

  2. Проверка двухфазной записи движений:

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

  3. Анализ бизнес-логики документа-регистратора:

    В документе Этап производства 2.2 движения по трудозатратам зависят от настроек этапа и способа оформления выработки:

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

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

← На главную