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