При разработке алгоритмов автозаполнения табличных частей документа остатками номенклатуры разработчики часто переносят привычный код из модуля объекта в модуль формы. В модуле документа получение остатков до момента проведения обычно выглядит лаконично: разработчик обращается к системному методу МоментВремени() и оборачивает его в объект Граница с исключением движений текущего документа. Однако при попытке вызвать этот метод в модуле управляемой формы возникает ошибка времени выполнения. Разберем подробно, почему это происходит и как методологически верно рассчитать остатки из формы документа в платформе 1С:Предприятие 8.3.
В управляемом приложении основной реквизит формы документа (как правило, с именем Объект) не является объектом конфигурации типа ДокументОбъект. На клиенте и в серверном контексте формы он имеет специальный платформенный тип ДанныеФормыСтруктура. Этот тип служит легковесным хранилищем значений реквизитов для передачи между клиентом и сервером и не содержит прикладных методов бизнес-логики, таких как Записать(), ПроверитьЗаполнение() или МоментВремени().
Хотя разработчик может принудительно преобразовать данные формы в объект с помощью вызова РеквизитФормыВЗначение("Объект"), делать это исключительно ради получения позиции документа на оси времени неэффективно. Десериализация всей структуры формы в тяжелый серверный объект создает неоправданную нагрузку на систему. Посмотрим, как решить задачу стандартными средствами платформы без лишних операций.
Вспомним базовое определение платформы: момент времени в 1С представляет собой совокупность даты и ссылки на документ. Платформа предоставляет конструктор объекта МоментВремени, принимающий дату документа и его ссылку. Следовательно, мы можем получить нужную точку на оси времени напрямую из реквизитов Объект.Дата и Объект.Ссылка.
Рассмотрим серверную процедуру запроса остатков, где мы явно инициализируем момент времени и передаем его в виртуальную таблицу регистра накопления:
&НаСервере
Процедура ЗаполнитьОстаткиНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиНоменклатурыОстатки.Номенклатура КАК Номенклатура,
| ОстаткиНоменклатурыОстатки.ВНаличииОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад) КАК ОстаткиНоменклатурыОстатки";
ТочкаВремени = Новый МоментВремени(Объект.Дата, Объект.Ссылка);
ГраницаОстатков = Новый Граница(ТочкаВремени, ВидГраницы.Исключая);
Запрос.УстановитьПараметр("Период", ГраницаОстатков);
Запрос.УстановитьПараметр("Склад", Объект.Склад);
РезультатЗапроса = Запрос.Выполнить();
// Дальнейшее заполнение табличной части
КонецПроцедуры
В платформе 1С конструктор объекта Граница также умеет неявно определять момент времени непосредственно по ссылке на документ. Конструкция вида Новый Граница(Объект.Ссылка, ВидГраницы.Исключая) отработает аналогично, если документ уже существует в информационной базе.
Проанализируем критическую ситуацию: пользователь создал новый документ и нажал кнопку заполнения остатков до того, как нажал кнопку сохранения или проведения. В этот момент ссылка документа еще не сформирована в базе данных — свойство Объект.Ссылка.Пустая() вернет значение Истина.
Если передать в конструктор границы пустую ссылку, платформа не сможет корректно сопоставить документ с движениями регистров. Более того, при формировании Новый МоментВремени(Объект.Дата, Объект.Ссылка) пустая ссылка имеет минимальный GUID (нули), из-за чего созданный момент времени окажется самым первым среди всех документов на данную секунду, что может привести к искажению расчетных данных.
Разберем два правильных подхода к обработке незаписанных документов.
Методологически верным решением без принудительной записи является проверка наличия ссылки. Если документ новый, у него заведомо нет собственных движений по регистрам, а значит, исключать его движения не требуется — достаточно рассчитать остатки на дату документа или на текущий момент времени сеанса.
Посмотрим на реализацию универсальной установки параметра периода:
&НаСервере
Функция ПолучитьГраницуДляОстатков()
Если Объект.Ссылка.Пустая() Тогда
// Для нового документа исключать нечего: используем дату документа или оперативную отметку
Если Объект.Дата = '00010101' Тогда
Возврат ТекущаяДатаСеанса();
Иначе
Возврат Объект.Дата;
КонецЕсли;
Иначе
// Для записанного документа формируем исключающую границу
ТочкаВремени = Новый МоментВремени(Объект.Дата, Объект.Ссылка);
Возврат Новый Граница(ТочкаВремени, ВидГраницы.Исключая);
КонецЕсли;
КонецФункции
Такой подход гарантирует, что если документ уже был проведен ранее, а затем пользователь решил перезаполнить табличную часть, то его старые движения будут корректно исключены из выборки остатков без необходимости распроводить документ.
Если бизнес-процесс требует, чтобы при заполнении остатков документ обязательно имел четкую позицию в последовательности событий, правильным решением будет интерактивная проверка и запись на клиенте перед обращением к серверу.
Разберем по шагам реализацию клиентской команды:
Записать(). Если запись проходит успешно, форма получает реальную валидную ссылку.Объект.Ссылка существует.
&НаКлиенте
Процедура ЗаполнитьПоОстаткам(Команда)
// Проверяем необходимость предварительной записи
Если Объект.Ссылка.Пустая() Или Модифицированность Тогда
ПараметрыЗаписи = Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Запись);
Если НЕ Записать(ПараметрыЗаписи) Тогда
Возврат; // Пользователь отказался от записи или возникли ошибки проверки
КонецЕсли;
КонецЕсли;
ЗаполнитьОстаткиНаСервере();
КонецПроцедуры
В соответствии со стандартами разработки типовых конфигураций (1С:ERP, 1С:УТ, 1С:КА), размещение тяжелых запросов непосредственно в коде формы считается нежелательным. Рекомендуется выносить алгоритмы расчета в модуль менеджера документа.
В модуль менеджера передается структура параметров (например, склад, дата, ссылка и список номенклатуры), а возвращается таблица значений с рассчитанными остатками. Это обеспечивает повторное использование кода: алгоритм можно будет вызывать как из формы документа, так и из внешних обработок или процедур регламентного фонового заполнения.