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