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