В этой статье мы подробно рассмотрим, как решить задачу динамического расчета даты напоминания об окончании договора в системе 1С за заданное количество дней, а также способы вывода этих уведомлений пользователю. Мы проанализируем различные подходы, включая математические операции с датами, использование запросов к базе данных и механизмы вывода сообщений.
Прежде всего, давайте разберемся, как 1С работает с датами. В платформе 1С значение типа "Дата" всегда представляет собой не только календарную дату, но и время с точностью до секунд. Это означает, что при выполнении математических операций с датами, результат будет выражен в секундах, что очень удобно для наших целей.
Для того чтобы получить дату напоминания, нам необходимо отнять определенное количество дней от даты окончания действия договора. Как нам подсказали в сообщении 2, ключевая идея заключается в том, чтобы перевести количество дней в секунды и отнять их от даты окончания действия договора. В сообщении 4 нам дополнительно объяснили, что дату саму по себе не нужно переводить в секунды, поскольку она, по сути, уже "и так в секундах" для математических операций. Если от даты отнять или прибавить секунды, мы получим новую дату.
Количество секунд в одном дне легко вычислить: 24 часа * 60 минут * 60 секунд = 86400 секунд. Таким образом, если нам нужно отнять КоличествоДней от ДатаОкончанияДоговора, мы можем использовать следующую формулу:
ДатаНапоминания = ДатаОкончанияДоговора - КоличествоДней * 86400;
Платформа 1С корректно обрабатывает високосные годы при таких вычислениях, поэтому нам не нужно беспокоиться о дополнительных проверках. Помимо прямого вычитания секунд, существуют также встроенные функции для работы с датами в языке запросов, такие как ДОБАВИТЬКДАТЕ(). Эта функция позволяет добавлять или вычитать определенное количество дней, месяцев, лет и других периодов. Например, для вычитания дней можно использовать:
ДОБАВИТЬКДАТЕ(<Дата>, День, -<КоличествоДней>)
Для получения разницы между двумя датами в днях, что может быть полезно для определения количества дней, оставшихся до события, мы можем использовать формулу:
(НачалоДня(ДатаОкончания) - НачалоДня(ДатаНачала)) / (60 * 60 * 24)
Функции НачалоДня() и КонецДня() позволяют привести дату к началу или концу соответствующего дня, что бывает полезно для точных расчетов без учета времени.
В ходе обсуждения в сообщении 5 пользователь предложил следующий код:
Напоминание = ЭтотОбъект.ДатаНапоминания;
Напоминание = 24*60*60;
Мы должны проанализировать эту попытку и выяснить, почему она некорректна. Как было верно замечено в сообщениях 17 и 22, после выполнения такого кода переменная Напоминание всегда будет равна 86400, независимо от значения ЭтотОбъект.ДатаНапоминания. Проблема заключается в том, что в этой строке происходит не вычитание, а переопределение переменной. Чтобы реализовать логику "отнимите", необходимо использовать оператор вычитания:
ДатаНапоминания = ДатаОкончанияДоговора - КоличествоДней * 86400;
Где ДатаОкончанияДоговора — это наша исходная дата из поля "Период по", а КоличествоДней — это число из поля "Напоминать об окончании договоров за: N дней".
Когда речь идет о сотнях или даже тысячах договоров, ручная проверка становится неэффективной. Поэтому мы рассмотрим подходы для автоматизации и эффективной обработки данных.
Для эффективного отбора договоров с истекающим сроком действия необходимо использовать запросы к базе данных. В запросе мы можем выбирать поля, связанные с датой окончания договора и количеством дней до напоминания, а затем применять условия для фильтрации нужных договоров. В сообщении 32 был предложен отличный пример запроса на встроенном языке 1С:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ДоговорыКонтрагентов.Ссылка КАК Ссылка,
| ДоговорыКонтрагентов.Контрагент КАК Контрагент,
| ДоговорыКонтрагентов.ДатаОкончанияДействия КАК ДатаОкончанияДействия,
| ДоговорыКонтрагентов.ДнейДоНапоминания КАК ДнейДоНапоминания,
| ВЫБОР
| КОГДА НЕ ДоговорыКонтрагентов.ДатаОкончанияДействия = ДАТАВРЕМЯ(1, 1, 1)
| И НЕ ДоговорыКонтрагентов.ДнейДоНапоминания = 0
| ТОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(&ТекущаяДата, ДЕНЬ), НАЧАЛОПЕРИОДА(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ), ДЕНЬ)
| ИНАЧЕ 100
| КОНЕЦ КАК ОсталосьДней
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.ДнейДоНапоминания >= ВЫБОР
| КОГДА НЕ ДоговорыКонтрагентов.ДатаОкончанияДействия = ДАТАВРЕМЯ(1, 1, 1)
| И НЕ ДоговорыКонтрагентов.ДнейДоНапоминания = 0
| ТОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(&ТекущаяДата, ДЕНЬ), НАЧАЛОПЕРИОДА(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ), ДЕНЬ)
| ИНАЧЕ 100
| КОНЕЦ";
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
Сообщить("У контрагента: " + РезультатЗапроса.Контрагент + " истекает срок договора через " + РезультатЗапроса.ОсталосьДней + " дней!");
КонецЦикла;
Давайте разберем этот запрос подробнее:
ВЫБРАТЬ: Мы выбираем ссылку на договор, контрагента, дату окончания действия и количество дней для напоминания.РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(&ТекущаяДата, ДЕНЬ), НАЧАЛОПЕРИОДА(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ), ДЕНЬ): Это ключевая часть, которая вычисляет разницу между текущей датой и датой окончания договора в днях. Функции НАЧАЛОПЕРИОДА() используются для того, чтобы убрать влияние времени и сравнивать даты только по дням. Если дата окончания раньше текущей, результат будет отрицательным. Мы хотим получить количество дней, оставшихся *до* даты окончания. Поэтому мы должны быть внимательны к порядку операндов. В данном запросе, если ТекущаяДата (первый параметр) меньше ДатаОкончанияДействия (второй параметр), результат будет положительным. Если текущая дата 15.12.2024, а окончание 31.12.2024, то РАЗНОСТЬДАТ вернет 16.ВЫБОР КОГДА ... ИНАЧЕ ... КОНЕЦ: Это условие проверяет, что дата окончания не равна пустому значению (ДАТАВРЕМЯ(1, 1, 1)) и количество дней до напоминания не равно 0. Это предотвращает ошибки и нежелательные расчеты для некорректных данных. Если условия не выполняются, возвращается значение 100, что служит "заглушкой".ГДЕ ДоговорыКонтрагентов.ДнейДоНапоминания >= ...: В условии ГДЕ мы сравниваем количество дней, за которое нужно напомнить (ДнейДоНапоминания), с фактически оставшимся количеством дней до окончания договора (результат РАЗНОСТЬДАТ). Если ДнейДоНапоминания больше или равно оставшемуся количеству дней, это означает, что пора формировать напоминание. Однако здесь есть небольшая неточность в логике. Если мы хотим напомнить *за* N дней, то напоминание должно сработать, когда ОсталосьДней <= ДнейДоНапоминания и ОсталосьДней > 0 (если мы не хотим напоминать задним числом). Корректнее было бы сравнивать calculated ДатаНапоминания с ТекущаяДата.Корректный подход в запросе для фильтрации может быть таким: вычислить дату напоминания для каждого договора и сравнить ее с текущей датой.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ДоговорыКонтрагентов.Ссылка КАК Ссылка,
| ДоговорыКонтрагентов.Контрагент КАК Контрагент,
| ДоговорыКонтрагентов.ДатаОкончанияДействия КАК ДатаОкончанияДействия,
| ДоговорыКонтрагентов.ДнейДоНапоминания КАК ДнейДоНапоминания,
| ДОБАВИТЬКДАТЕ(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ, -ДоговорыКонтрагентов.ДнейДоНапоминания) КАК ДатаНапоминания
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.ДатаОкончанияДействия <> ДАТАВРЕМЯ(1, 1, 1)
| И ДоговорыКонтрагентов.ДнейДоНапоминания > 0
| И НАЧАЛОДНЯ(ДОБАВИТЬКДАТЕ(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ, -ДоговорыКонтрагентов.ДнейДоНапоминания)) <= НАЧАЛОДНЯ(&ТекущаяДата)
| И НАЧАЛОДНЯ(ДоговорыКонтрагентов.ДатаОкончанияДействия) >= НАЧАЛОДНЯ(&ТекущаяДата)"; // Добавим условие, чтобы не напоминать по уже прошедшим договорам
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
Сообщить("У контрагента: " + РезультатЗапроса.Контрагент + " истекает срок договора " + РезультатЗапроса.ДатаОкончанияДействия + ". Напоминание сработало.");
КонецЦикла;
В этом улучшенном запросе мы:
ДатаНапоминания с помощью ДОБАВИТЬКДАТЕ().НАЧАЛОДНЯ(ДатаНапоминания) <= НАЧАЛОДНЯ(&ТекущаяДата). Это означает, что если вычисленная дата напоминания уже наступила или прошла относительно текущего дня, то договор попадает в выборку.НАЧАЛОДНЯ(ДоговорыКонтрагентов.ДатаОкончанияДействия) >= НАЧАЛОДНЯ(&ТекущаяДата), чтобы отсечь договоры, срок действия которых уже истек, и не выводить по ним напоминания (если это требуется).После выполнения запроса, мы обходим его результат с помощью цикла Пока РезультатЗапроса.Следующий() и для каждого подходящего договора формируем уведомление.
В сообщении 39 нам предоставили пример кода, который демонстрирует, как можно реализовать проверку и вывод сообщения на стороне клиента, например, при открытии формы:
ДатаОкончанияДействия = Дата(2023,2,6); // Предположим, это ваша ДатаОкончанияДействия из договора
ДнейДоНапоминания = 10; // Предположим, это ваше количество дней для напоминания
Напоминать = Истина; // Флаг, что напоминания включены
Если Напоминать И (ДатаОкончанияДействия - 86400 * ДнейДоНапоминания) < КонецДня(ТекущаяДата()) Тогда
Напоминание = СтрШаблон("Договор истекает %1, через %2 дня!",
Формат(ДатаОкончанияДействия, "ДФ=dd.MM.yyyy"),
(КонецДня(ДатаОкончанияДействия) - КонецДня(ТекущаяДата()) ) / 86400);
Сообщить(Напоминание);
КонецЕсли;
Давайте подробно рассмотрим этот фрагмент кода:
ДатаОкончанияДействия - 86400 * ДнейДоНапоминания: Здесь происходит прямой расчет даты, когда должно сработать напоминание, вычитая количество дней (переведенных в секунды) из даты окончания договора.< КонецДня(ТекущаяДата()): Это условие проверяет, наступила ли уже рассчитанная дата напоминания. Если дата напоминания (например, 15.12.2024 00:00:00) меньше, чем конец текущего дня (например, 16.12.2024 23:59:59), это означает, что напоминание уже должно было сработать. Для большей точности и избежания срабатывания напоминания "сегодня на сегодня" в конце дня, мы можем сравнить НАЧАЛОДНЯ(РассчитаннаяДатаНапоминания) <= НАЧАЛОДНЯ(ТекущаяДата()).Напоминание = СтрШаблон(...): Формируется строка сообщения с использованием функции СтрШаблон() для удобного форматирования. В тексте сообщения указывается дата окончания договора и количество оставшихся дней.(КонецДня(ДатаОкончанияДействия) - КонецДня(ТекущаяДата()) ) / 86400: Это вычисление оставшегося количества полных дней до конца договора. Функции КонецДня() используются для того, чтобы получить разницу в целых днях, игнорируя время.Сообщить(Напоминание): Выводит сформированное сообщение пользователю в нижней части клиентского приложения.В 1С существует несколько способов вывода сообщений пользователю, каждый из которых имеет свои особенности:
Сообщить(): Это самый простой и часто используемый способ. Сообщение появляется в нижней части окна программы, в панели сообщений. Он удобен для информационных уведомлений, которые не требуют немедленного действия пользователя. Работает практически на всех видах клиентов (тонкий, веб, толстый).Предупреждение(): Выводит сообщение в виде отдельного модального окна. Оно перекрывает все другие окна приложения до тех пор, пока пользователь не нажмет кнопку "ОК" или не истечет заданный таймаут. Используется для важных уведомлений, требующих внимания пользователя. Имеет параметры для текста, таймаута и заголовка окна.ПоказатьПредупреждение(): Это более современный и гибкий аналог Предупреждение(). Рекомендуется использовать его, так как он работает как с включенной, так и с выключенной модальностью, что важно в контексте управляемых форм и асинхронных операций. Он также позволяет задать обработчик закрытия окна.СообщениеПользователю: Позволяет создавать сообщения, привязанные к конкретным элементам формы или реквизитам. Это особенно полезно для указания на поле, в котором допущена ошибка или куда необходимо обратить внимание. Объект СообщениеПользователю позволяет управлять видом, текстом и привязкой сообщения.Для нашей задачи с напоминаниями чаще всего используются Сообщить() или Предупреждение()/ПоказатьПредупреждение(), в зависимости от критичности и желаемого уровня привлечения внимания пользователя.
Если нам нужно, чтобы напоминания срабатывали автоматически, а не только при открытии формы, мы должны рассмотреть механизм регламентных заданий.
Регламентные задания — это мощный механизм в 1С, который позволяет выполнять прикладные задачи по расписанию в фоновом режиме, не влияя на работу пользователя. Это идеальное решение для регулярных проверок договоров.
Сообщить() или Предупреждение() напрямую, так как они ориентированы на интерактивное взаимодействие с пользователем. Вместо этого мы можем:
СообщениеПользователю, которые будут отображаться при следующем входе пользователя в систему или открытии соответствующей формы.Пример структуры кода для регламентного задания:
// В модуле объекта или общем модуле с флагом "Сервер"
Процедура ПроверитьСрокиДоговоровИНапомнить() Экспорт
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ДоговорыКонтрагентов.Ссылка КАК Ссылка,
| ДоговорыКонтрагентов.Контрагент КАК Контрагент,
| ДоговорыКонтрагентов.ДатаОкончанияДействия КАК ДатаОкончанияДействия,
| ДоговорыКонтрагентов.ДнейДоНапоминания КАК ДнейДоНапоминания,
| ДОБАВИТЬКДАТЕ(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ, -ДоговорыКонтрагентов.ДнейДоНапоминания) КАК ДатаНапоминания,
| Пользователи.Ссылка КАК Ответственный
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи КАК Пользователи
| ПО ДоговорыКонтрагентов.Ответственный = Пользователи.Ссылка
|ГДЕ
| ДоговорыКонтрагентов.ДатаОкончанияДействия <> ДАТАВРЕМЯ(1, 1, 1)
| И ДоговорыКонтрагентов.ДнейДоНапоминания > 0
| И НАЧАЛОДНЯ(ДОБАВИТЬКДАТЕ(ДоговорыКонтрагентов.ДатаОкончанияДействия, ДЕНЬ, -ДоговорыКонтрагентов.ДнейДоНапоминания)) <= НАЧАЛОДНЯ(&ТекущаяДата)
| И НАЧАЛОДНЯ(ДоговорыКонтрагентов.ДатаОкончанияДействия) >= НАЧАЛОДНЯ(&ТекущаяДата)";
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
// Формируем сообщение пользователю
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон("Договор контрагента ""%1"" истекает %2. Требуется продление или завершение.",
РезультатЗапроса.Контрагент,
Формат(РезультатЗапроса.ДатаОкончанияДействия, "ДФ=dd.MM.yyyy"));
Сообщение.Поле = "Ссылка"; // Можно указать поле, к которому привязано сообщение, например, для формы договора
// Если есть поле "Ответственный" в договоре и мы его выбрали
Если РезультатЗапроса.Ответственный <> Неопределено Тогда
Сообщение.Пользователь = РезультатЗапроса.Ответственный;
Иначе
// Если ответственный не указан, можно отправить сообщение всем администраторам или определенной группе
// Или вообще не отправлять, если нет конкретного пользователя.
// Например, можно записывать в журнал регистрации.
КонецЕсли;
Сообщение.Заголовок = "Напоминание об окончании договора";
Сообщение.УстановитьОбъект(РезультатЗапроса.Ссылка); // Привязываем сообщение к объекту договора
Сообщение.ВидСообщения = ВидСообщенияПользователю.Информация;
Сообщение.Вывести();
// Также можно записать в журнал регистрации для аудита
ЖурналРегистрации.Записать(
"НапоминаниеОДоговорах",
УровеньЖурналаРегистрации.Информация,
Метаданные.Справочники.ДоговорыКонтрагентов,
РезультатЗапроса.Ссылка,
Сообщение.Текст
);
КонецЦикла;
КонецПроцедура
Для вывода сообщений, сгенерированных регламентным заданием, в режиме реального времени (например, сразу после входа пользователя), можно использовать механизм СообщениеПользователю, привязывая его к конкретному пользователю или объекту. При следующем входе пользователя в систему или при открытии соответствующего объекта, эти сообщения могут быть отображены.
Мы должны всегда помнить, что некоторые типовые конфигурации 1С (например, 1С:Документооборот, 1С:ЗУП) уже имеют встроенные механизмы для настройки напоминаний о сроках действия договоров, трудовых договоров или других событий. Перед тем как приступать к собственной разработке, всегда стоит проверить наличие таких функций в используемой конфигурации. Эти механизмы часто позволяют настроить напоминание за определенное количество дней до события, а также определяют ответственных за получение уведомлений, что существенно упрощает задачу.
Таким образом, мы видим, что задача динамического расчета даты напоминания и вывода уведомлений в 1С имеет несколько решений, от простых клиентских сообщений до сложных регламентных заданий с запросами и персонализированными уведомлениями. Выбор конкретного подхода зависит от требований к частоте, объему и способу информирования пользователей.