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