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