Как последовательно открывать формы в 1С:РМК и корректно рассчитывать скидки после применения карты лояльности?

Программист 1С v8.3 (Управляемые формы) 1С:Розница Торговля и дистрибуция
← На главную

При разработке или доработке функционала 1С:Рабочего места кассира (РМК) часто возникает потребность в строгой последовательности действий пользователя. Один из типичных сценариев – это сначала предложить покупателю считать карту лояльности, а уже затем перейти к форме оплаты, учитывая примененные скидки. Однако, как показывает практика, простое открытие окна считывания карты перед окном оплаты может не привести к автоматическому расчету скидок. В этой статье мы подробно разберем, как реализовать такую последовательность действий с использованием механизма ОписаниеОповещения и как обеспечить корректный расчет автоматических скидок.

Проблема: неверная последовательность открытия форм и отсутствие расчета скидок

Изначальная задача, с которой мы сталкиваемся, заключается в том, чтобы после нажатия кнопки "Оплата наличными" в РМК сначала появлялось окно для ввода карты лояльности. После успешного считывания или ввода карты, система должна рассчитать все применимые скидки и только потом открыть форму оплаты, уже с учетом этих скидок.

В первой попытке решения проблемы, как правило, код для открытия формы карты лояльности размещается прямо в процедуре оплаты наличными, перед вызовом формы оплаты. Рассмотрим первоначальный код, который демонстрирует эту проблему:


Процедура ОплатитьНаличными(Команда)
    ОчиститьСообщения;
    Если ТолькоПросмотр Тогда
        // Чек пробит. Изменение информации запрещено.
        Возврат;
    КонецЕсли;

    ПараметрыОткрытия = Новый Структура(
        "НеИспользоватьРучнойВвод, НеПодключатьОборудование",
        Не ПраваДоступа.ИзменениеКартЛояльности,
        Истина
    );
    ОткрытьФорму(
        "Справочник.КартыЛояльности.Форма.СчитываниеКартыЛояльности",
        ПараметрыОткрытия,
        ЭтотОбъект,
        ЭтотОбъект.УникальныйИдентификатор
    ); // Здесь открывается форма карты лояльности

    ВыбраннаяФормаОплаты = Новый Структура;
    ВыбраннаяФормаОплаты.Вставить("Наличные", Истина);
    ВыбраннаяФормаОплаты.Вставить("БонусныеБаллы", Ложь);
    ВыбраннаяФормаОплаты.Вставить("ПлатежныеКарты", Ложь);
    ВыбраннаяФормаОплаты.Вставить("ПодарочныеСертификаты", Ложь);

    ДополнительныеПараметры = Новый Структура;
    ДополнительныеПараметры.Вставить("ВыбраннаяФормаОплаты", ВыбраннаяФормаОплаты);

    РозничныеПродажиКлиент.ОбработатьСостояниеСмены(
        ЭтотОбъект,
        Новый ОписаниеОповещения("ОплатитьНаличнымиОбработкаОповещения", ЭтотОбъект, ДополнительныеПараметры)
    );
КонецПроцедуры

В этом коде мы видим, что форма считывания карты лояльности открывается, но выполнение кода не приостанавливается. Сразу же после вызова ОткрытьФорму система продолжает выполнять последующие строки, которые инициируют открытие формы оплаты. В результате, форма оплаты появляется практически одновременно с формой считывания карты или сразу после ее открытия, не дожидаясь закрытия и не обеспечивая расчет скидок. Даже если карта лояльности регистрируется в чеке, автоматические скидки по ней не применяются, поскольку расчет не был инициирован в нужный момент. Пользователю приходится закрывать форму оплаты, а затем снова ее открывать, чтобы увидеть скидки. Такая ситуация свидетельствует о необходимости перестройки логики вызовов.

Решение 1: Последовательное открытие форм с использованием ОписаниеОповещения

Для решения проблемы последовательного открытия форм в 1С, когда дальнейшее выполнение кода зависит от результата работы предыдущей формы, используется механизм ОписаниеОповещения. Этот механизм позволяет асинхронно обрабатывать результат закрытия формы, гарантируя, что определенный код будет выполнен только после того, как пользователь взаимодействовал с предыдущей формой и закрыл ее.

Как работает ОписаниеОповещения?

Мы открываем форму с помощью функции ОткрытьФорму(), передавая в нее объект ОписаниеОповещения. В ОписаниеОповещения указывается имя процедуры, которая будет вызвана после закрытия открываемой формы.

  1. Создание ОписаниеОповещения: На первом шаге мы создаем объект ОписаниеОповещения, который связывает открываемую форму с процедурой-обработчиком. Мы указываем имя экспортной процедуры, которая будет вызвана после закрытия формы, а также контекст, в котором она должна быть вызвана (обычно ЭтотОбъект – текущая форма). Мы также можем передать дополнительные параметры, которые будут доступны в обработчике.
  2. Вызов ОткрытьФорму(): Затем мы вызываем функцию ОткрытьФорму(), передавая в нее, помимо прочих параметров, созданный объект ОписаниеОповещения. Важно передать его в соответствующий параметр, обычно это шестой параметр, если не используются именованные параметры.
  3. Выполнение кода формы: Система открывает указанную форму и ожидает ее закрытия. Весь код, идущий после ОткрытьФорму() в текущей процедуре, немедленно выполняется, если не использован режим модального открытия, который в управляемых формах не рекомендуется и во многих случаях невозможен. Именно поэтому нам нужен асинхронный механизм ОписаниеОповещения.
  4. Вызов обработчика после закрытия формы: После того как пользователь закрывает открытую форму, система вызывает процедуру-обработчик, указанную в ОписаниеОповещения. В эту процедуру передаются результаты работы закрытой формы (например, выбранный элемент, отказ от выбора) и дополнительные параметры, которые мы указали при создании ОписаниеОповещения.

Пример кода для последовательного открытия форм

Давайте изменим процедуру ОплатитьНаличными, чтобы она использовала ОписаниеОповещения для корректной последовательности: сначала открываем форму считывания карты лояльности, а затем, после ее закрытия, переходим к форме оплаты.


Процедура ОплатитьНаличными(Команда)
    ОчиститьСообщения;
    Если ТолькоПросмотр Тогда
        // Чек пробит. Изменение информации запрещено.
        Возврат;
    КонецЕсли;

    // Подготавливаем параметры для открытия формы считывания карты лояльности
    ПараметрыОткрытия = Новый Структура(
        "НеИспользоватьРучнойВвод, НеПодключатьОборудование",
        Не ПраваДоступа.ИзменениеКартЛояльности,
        Истина
    );

    // Создаем структуру с выбранной формой оплаты, которую передадим в обработчик
    // Это позволяет сохранить контекст оплаты (наличными) до момента ее инициирования.
    ВыбраннаяФормаОплаты = Новый Структура;
    ВыбраннаяФормаОплаты.Вставить("Наличные", Истина);
    ВыбраннаяФормаОплаты.Вставить("БонусныеБаллы", Ложь);
    ВыбраннаяФормаОплаты.Вставить("ПлатежныеКарты", Ложь);
    ВыбраннаяФормаОплаты.Вставить("ПодарочныеСертификаты", Ложь);

    // Создаем описание оповещения. Указываем имя процедуры-обработчика ("ПриЗакрытииФормыКартыЛояльности"),
    // контекст выполнения (ЭтотОбъект) и дополнительные параметры (ВыбраннаяФормаОплаты).
    ОП = Новый ОписаниеОповещения("ПриЗакрытииФормыКартыЛояльности", ЭтотОбъект, ВыбраннаяФормаОплаты);

    // Открываем форму считывания карты лояльности.
    // Важно: объект ОписаниеОповещения (ОП) передается в шестой параметр.
    ОткрытьФорму(
        "Справочник.КартыЛояльности.Форма.СчитываниеКартыЛояльности",
        ПараметрыОткрытия,
        ЭтотОбъект,
        ЭтотОбъект.УникальныйИдентификатор,,,ОП
    );
КонецПроцедуры

Теперь нам нужна процедура-обработчик ПриЗакрытииФормыКартыЛояльности, которая будет вызвана после закрытия формы карты лояльности. Именно в ней мы продолжим логику открытия формы оплаты.

Процедура-обработчик закрытия формы

Процедура-обработчик, указанная в ОписаниеОповещения, должна быть экспортной и выполняться на клиенте. Это критически важно, так как механизм ОписаниеОповещения работает исключительно в клиентском контексте. Если вы попытаетесь вызвать его на сервере, вы получите ошибку "Тип не определен (ОписаниеОповещения)".


&НаКлиенте
Процедура ПриЗакрытииФормыКартыЛояльности(Результат, ВыбраннаяФормаОплаты) экспорт
    // Здесь мы можем обработать "Результат" - то, что вернула закрывшаяся форма.
    // Например, "Результат" может содержать ссылку на выбранную карту лояльности,
    // или быть Неопределено, если пользователь отменил выбор.

    // "ВыбраннаяФормаОплаты" - это те дополнительные параметры, которые мы передали
    // при создании ОписаниеОповещения.
    ДополнительныеПараметры = Новый Структура;
    ДополнительныеПараметры.Вставить("ВыбраннаяФормаОплаты", ВыбраннаяФормаОплаты);

    // Теперь, когда форма карты лояльности закрыта, мы можем инициировать
    // открытие формы оплаты, передавая необходимые параметры.
    РозничныеПродажиКлиент.ОбработатьСостояниеСмены(
        ЭтотОбъект,
        Новый ОписаниеОповещения("ОплатитьНаличнымиОбработкаОповещения", ЭтотОбъект, ДополнительныеПараметры)
    );
КонецПроцедуры

Важное замечание по контексту выполнения: Как мы уже упомянули, механизм ОписаниеОповещения и процедура-обработчик, указанная в нем, должны выполняться на клиенте. Если внутри этой клиентской процедуры потребуется выполнить серверный код (например, для записи данных или выполнения сложных расчетов), используйте стандартные клиент-серверные вызовы, такие как вызов серверной экспортной процедуры формы или общего модуля с директивой &НаСервере.

Решение 2: Расчет автоматических скидок после применения карты лояльности

Даже при правильной последовательности открытия форм, используя ОписаниеОповещения, может возникнуть проблема, что скидки не рассчитываются сразу после регистрации карты лояльности в чеке. Карта добавляется в документ, но сумма скидки остается прежней, пока мы не закроем и снова не откроем форму оплаты или не выполним явный перерасчет. Это происходит потому, что система не всегда автоматически пересчитывает скидки при изменении документа, влияющем на их применение.

Почему скидки не рассчитываются сразу?

В 1С:РМК, 1С:Рознице и других конфигурациях, автоматические скидки рассчитываются программой по заранее заданным условиям. Этот расчет обычно не происходит "магически" при каждом изменении данных, влияющих на скидки. Ему нужен определенный "триггер". Таким триггером может быть:

  1. Явное нажатие кнопки "Рассчитать скидки" в документе.
  2. Вызов специальной процедуры, которая инициирует перерасчет.
  3. Запись документа (в некоторых случаях, хотя это не всегда гарантирует полный перерасчет всех типов скидок).

Если в документе произошли изменения, влияющие на расчет скидок (например, изменение контрагента, состава табличной части, или, как в нашем случае, применение карты лояльности), система может сигнализировать о необходимости перерасчета (например, значок расчета скидок может стать красным в некоторых интерфейсах). Нам нужно программно вызвать этот перерасчет.

Как инициировать расчет скидок?

Для того чтобы скидки рассчитывались корректно, необходимо, чтобы карта лояльности была зарегистрирована в чеке *до* вызова процедуры расчета скидок. В нашей процедуре ПриЗакрытииФормыКартыЛояльности, после того как карта лояльности успешно зарегистрирована в ЧекеККМ (что обычно происходит при закрытии формы считывания карты лояльности), нам нужно явно инициировать перерасчет автоматических скидок.

Как правило, в формах документов (таких как ЧекККМ) или в общих модулях, связанных с РМК, есть процедуры, отвечающие за расчет автоматических скидок. Одной из таких процедур, упомянутой в обсуждении, является НазначитьАвтоматическиеСкидки(). Эта процедура может быть реализована в модуле формы документа или в общем модуле СкидкиНаценкиКлиент (или аналогичном) и вызываться для обновления скидок.

Нам необходимо добавить вызов процедуры перерасчета скидок *между* успешным применением карты лояльности и открытием формы оплаты. После вызова процедуры расчета скидок, система должна обновить табличную часть документа, отображая примененные скидки. Только после этого следует открывать форму оплаты.

Интеграция расчета скидок в последовательность

Давайте модифицируем процедуру ПриЗакрытииФормыКартыЛояльности, чтобы она включала вызов расчета скидок. Предположим, что в форме документа РМК существует экспортная процедура или метод, который отвечает за применение автоматических скидок. Для ЧекККМ, в зависимости от конфигурации (например, 1С:Розница 2.3/3.0, 1С:УНФ, 1С:УТ), это может быть метод объекта или формы, который запускает механизм перерасчета.

Поскольку ПриЗакрытииФормыКартыЛояльности выполняется на клиенте, и расчет скидок часто требует серверного обращения (например, для получения актуальных условий скидок из информационной базы), нам нужно убедиться, что вызываемый метод либо сам является клиент-серверным (если это метод формы, который вызывает серверный код), либо мы должны явно вызвать серверную процедуру, которая уже внутри себя инициирует расчет скидок.

В типовых конфигурациях 1С:РМК часто используется механизм общего модуля РозничныеПродажиКлиент. В нем могут быть методы, такие как ОбновитьИнформациюОСкидках(Форма) или подобные, которые запускают весь процесс перерасчета скидок и бонусных баллов для текущего документа. Мы будем использовать его в качестве примера.


&НаКлиенте
Процедура ПриЗакрытииФормыКартыЛояльности(Результат, ВыбраннаяФормаОплаты) экспорт
    // Проверяем, что карта лояльности была успешно выбрана или считана.
    // "Результат" может содержать ссылку на выбранную карту или другую информацию.
    // Если результат Неопределено, это может означать, что пользователь отменил выбор.
    Если Результат <> Неопределено Тогда
        // Здесь мы предполагаем, что применение карты к чеку уже произошло
        // при закрытии формы "СчитываниеКартыЛояльности" (например, форма возвращает
        // выбранную карту, и она записывается в реквизит документа).
        // Теперь необходимо инициировать перерасчет автоматических скидок.

        // Вызываем процедуру расчета автоматических скидок.
        // Имя и параметры процедуры могут отличаться в зависимости от конфигурации.
        // В 1С:Рознице часто используется метод из общего модуля РозничныеПродажиКлиент
        // для обновления информации о скидках в РМК.
        Попытка
            // Этот метод обновит скидки в текущем чеке ККМ.
            РозничныеПродажиКлиент.ОбновитьИнформациюОСкидках(ЭтотОбъект);
            // Также может потребоваться вызов, если используется управление управляемыми скидками:
            // СкидкиНаценкиКлиент.РассчитатьУправляемыеСкидки(ЭтотОбъект); // Если необходимо обновить управляемые скидки
        Исключение
            Сообщить("Ошибка при расчете скидок: " + ОписаниеОшибки());
        КонецПопытки;

        // Важно! После расчета скидок, возможно, потребуется обновить данные на форме,
        // чтобы увидеть изменения. Часто это делается автоматически при перерасчете
        // или через вызов "ОбновитьОтображениеДанных()", если таковой метод предусмотрен.
    КонецЕсли; // Конец Если Результат <> Неопределено

    // После расчета скидок, продолжаем логику открытия формы оплаты.
    ДополнительныеПараметры = Новый Структура;
    ДополнительныеПараметры.Вставить("ВыбраннаяФормаОплаты", ВыбраннаяФормаОплаты);

    РозничныеПродажиКлиент.ОбработатьСостояниеСмены(
        ЭтотОбъект,
        Новый ОписаниеОповещения("ОплатитьНаличнымиОбработкаОповещения", ЭтотОбъект, ДополнительныеПараметры)
    );
КонецПроцедуры

Сервер лояльности и локальный расчет: Стоит отметить, что в современных конфигурациях 1С (УТ 11, КА 2, ERP 2, УНФ 3.0, Розница 3.0/2.3) РМК может работать с внешним сервером лояльности. В этом случае расчет скидок происходит на стороне сервера лояльности. При отсутствии соединения с сервером лояльности возможен локальный расчет скидок, если это предусмотрено настройками. Вызов соответствующей процедуры в 1С должен инициировать запрос к серверу лояльности или запустить локальный механизм.

Итоговый код и последовательность действий

Давайте соберем все вместе, чтобы получить полный и рабочий код для обработки оплаты наличными с учетом карты лояльности и автоматических скидок. Этот код следует размещать в модуле формы документа ЧекККМ или в расширении для формы РМК.

Шаг 1: Модификация процедуры ОплатитьНаличными

Эта процедура будет вызываться при нажатии кнопки "Оплатить наличными". Она инициирует открытие формы считывания карты лояльности и настраивает вызов обработчика после ее закрытия.


Процедура ОплатитьНаличными(Команда)
    ОчиститьСообщения;
    Если ТолькоПросмотр Тогда
        // Чек пробит. Изменение информации запрещено.
        Возврат;
    КонецЕсли;

    // Параметры для открытия формы считывания карты лояльности
    ПараметрыОткрытия = Новый Структура(
        "НеИспользоватьРучнойВвод, НеПодключатьОборудование",
        Не ПраваДоступа.ИзменениеКартЛояльности,
        Истина
    );

    // Подготавливаем структуру, описывающую выбранную форму оплаты (Наличные).
    // Эта структура будет передана в процедуру-обработчик после закрытия формы карты лояльности.
    ВыбраннаяФормаОплаты = Новый Структура;
    ВыбраннаяФормаОплаты.Вставить("Наличные", Истина);
    ВыбраннаяФормаОплаты.Вставить("БонусныеБаллы", Ложь);
    ВыбраннаяФормаОплаты.Вставить("ПлатежныеКарты", Ложь);
    ВыбраннаяФормаОплаты.Вставить("ПодарочныеСертификаты", Ложь);

    // Создаем описание оповещения: указываем, какая процедура будет вызвана
    // после закрытия формы считывания карты лояльности, и какие дополнительные параметры ей передать.
    ОП = Новый ОписаниеОповещения("ПриЗакрытииФормыКартыЛояльности", ЭтотОбъект, ВыбраннаяФормаОплаты);

    // Открываем форму считывания карты лояльности.
    // Выполнение текущей процедуры продолжится асинхронно после закрытия формы карты лояльности,
    // путем вызова процедуры "ПриЗакрытииФормыКартыЛояльности".
    ОткрытьФорму(
        "Справочник.КартыЛояльности.Форма.СчитываниеКартыЛояльности",
        ПараметрыОткрытия,
        ЭтотОбъект,
        ЭтотОбъект.УникальныйИдентификатор,,,ОП
    );
КонецПроцедуры

Шаг 2: Создание или модификация процедуры ПриЗакрытииФормыКартыЛояльности

Эта процедура будет вызвана автоматически после закрытия формы считывания карты лояльности. В ней мы сначала инициируем перерасчет скидок, а затем переходим к открытию формы оплаты.


&НаКлиенте
Процедура ПриЗакрытииФормыКартыЛояльности(Результат, ВыбраннаяФормаОплаты) экспорт
    // "Результат" содержит значение, возвращенное закрывшейся формой.
    // Это может быть ссылка на выбранную карту лояльности или Неопределено,
    // если пользователь отменил выбор.
    Если Результат <> Неопределено Тогда
        // Если карта лояльности была выбрана или считана, то она уже должна быть
        // добавлена в документ (ЧекККМ) самой формой "СчитываниеКартыЛояльности"
        // при ее успешном закрытии.

        // Теперь критически важно инициировать перерасчет автоматических скидок,
        // чтобы они применились к чеку до открытия формы оплаты.
        Попытка
            // В большинстве типовых конфигураций 1С:РМК для обновления скидок
            // используется метод общего клиентского модуля "РозничныеПродажиКлиент".
            // Этот метод обычно включает серверный вызов для получения и применения скидок.
            РозничныеПродажиКлиент.ОбновитьИнформациюОСкидках(ЭтотОбъект);
            // Если в вашей конфигурации есть другая процедура для перерасчета скидок
            // на форме РМК (например, "ЭтотОбъект.НазначитьАвтоматическиеСкидки()"),
            // используйте ее вместо или в дополнение.
        Исключение
            // Обработка возможной ошибки при расчете скидок.
            Сообщить("Произошла ошибка при автоматическом расчете скидок: " + ОписаниеОшибки());
        КонецПопытки;

        // Если необходимо, можно добавить задержку или дополнительную проверку
        // для гарантированного обновления интерфейса перед открытием формы оплаты.
        // Однако, обычно "ОбновитьИнформациюОСкидках" уже обновляет данные на форме.
    КонецЕсли;

    // После того как скидки рассчитаны (или если карта не была выбрана),
    // продолжаем процесс оплаты, используя ранее подготовленные параметры.
    ДополнительныеПараметры = Новый Структура;
    ДополнительныеПараметры.Вставить("ВыбраннаяФормаОплаты", ВыбраннаяФормаОплаты);

    РозничныеПродажиКлиент.ОбработатьСостояниеСмены(
        ЭтотОбъект,
        Новый ОписаниеОповещения("ОплатитьНаличнымиОбработкаОповещения", ЭтотОбъект, ДополнительныеПараметры)
    );
КонецПроцедуры

Заключение

Реализация последовательного открытия форм и корректного расчета скидок в 1С:РМК требует понимания асинхронной работы управляемых форм и механизмов расчета скидок. Используя ОписаниеОповещения, мы можем эффективно управлять порядком выполнения действий пользователя, гарантируя, что каждая последующая операция начинается только после завершения предыдущей. Крайне важно не забывать о контексте выполнения процедур (клиент/сервер) и о необходимости явного инициирования перерасчета автоматических скидок после применения карты лояльности или других изменений, влияющих на скидки. Это позволит создать более гибкий, удобный и безошибочный интерфейс для кассиров, а также обеспечит правильное применение всех маркетинговых акций и программ лояльности.

Всегда проверяйте документацию к вашей конкретной конфигурации 1С, так как имена процедур и механизмы расчета скидок могут незначительно отличаться в разных версиях и редакциях.

← На главную