В процессе организации розничной торговли часто возникает необходимость контролировать финансовые операции на кассе. Одной из наиболее критичных операций является возврат товара. Чтобы минимизировать риски внутреннего мошенничества (фрода) со стороны кассиров, компании стремятся ограничить предельную сумму возврата, которую кассир может оформить самостоятельно. Например, возвраты до 10 000 рублей кассир проводит свободно, а всё, что свыше этой суммы, должно требовать обязательного подтверждения старшего кассира или администратора.
В этой статье мы подробно разберем, как реализовать подобное ограничение в типовых конфигурациях 1С (таких как 1С:Розница, 1С:Управление нашей фирмой и 1С:Управление торговлей), проанализируем возможные уязвимости этого процесса и разработаем надежный алгоритм проверки.
Проанализируем стандартные возможности конфигураций 1С. В типовых решениях права доступа кассиров разграничиваются бинарно. В дополнительных правах пользователя мы можем либо полностью разрешить операцию «Чек возврата», либо полностью её запретить. Настройка конкретных денежных лимитов на одну операцию или на смену в стандартном функционале отсутствует.
По этой причине для решения задачи нам потребуется кастомизация системы. Чтобы не снимать программу с поддержки и не усложнять процесс последующих обновлений, мы будем использовать механизм расширений конфигурации.
Перед тем как перейти к написанию кода, обсудим важнейший методологический нюанс, который часто упускают при постановке технического задания. Если мы просто установим жесткий лимит на сумму одного чека возврата (например, 10 000 рублей), недобросовестный сотрудник сможет легко обойти это ограничение.
Допустим, клиенту (или сообщнику кассира) нужно вернуть товар на сумму 30 000 рублей. Что помешает менеджеру оформить три последовательных возврата по 9 900 рублей? Ровным счетом ничего. Система пропустит эти чеки, так как каждый из них по отдельности не превышает установленный лимит.
Чтобы исключить подобные злоупотребления, мы должны заложить в алгоритм проверки один из следующих вариантов контроля:
Рассмотрим по шагам техническую реализацию контроля суммы возврата с защитой от обхода ограничений.
Для реализации проверки нам необходимо перехватить событие записи документа. Использовать процедуру модуля формы документа ПередЗаписью не всегда эффективно, так как кассир может работать через специализированный интерфейс — Рабочее место кассира (РМК), где запись чеков происходит программно. Наиболее надежным решением является использование подписки на событие или перехват процедуры в модуле объекта документа ЧекККМ.
Создадим расширение конфигурации и добавим в него документ ЧекККМ. Переопределим обработчик события ПередЗаписью модуля объекта документа с помощью аннотации &Перед.
Разберем пример кода, который проверяет сумму текущего возврата, а также контролирует попытки дробления операций, анализируя общую сумму возвратов кассира за текущую смену.
&Перед("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что документ проводится и это чек на возврат
Если ЭтотОбъект.ОбменДанными.Загрузка ИЛИ НЕ ЭтотОбъект.Проведение Тогда
Возврат;
КонецЕсли;
// Проверяем вид операции чека
Если ЭтотОбъект.ВидОперации = Перечисления.ВидыОперацийЧекККМ.Возврат Тогда
// Если у пользователя есть полные права или роль Администратора, проверку не выполняем
Если РольДоступна("ПолныеПрава") ИЛИ РольДоступна("ДобавлениеИзменениеПраваДоступа") Тогда
Возврат;
КонецЕсли;
ЛимитОдногоВозврата = 10000; // Порог в рублях
ЛимитСменыВсего = 30000; // Максимальная сумма возвратов кассира за смену
// Проверка 1. Превышение лимита по одному чеку
Если ЭтотОбъект.СуммаДокумента > ЛимитОдногоВозврата Тогда
Отказ = Истина;
ВызватьИсключение "Превышен лимит суммы возврата (10 000 руб.). Требуется авторизация администратора!";
КонецЕсли;
// Проверка 2. Защита от дробления (контроль общей суммы за смену)
ТекущийПользователь = Пользователи.ТекущийПользователь();
СуммаВозвратовЗаСмену = ПолучитьСуммуВозвратовЗаСмену(ТекущийПользователь, ЭтотОбъект.Ссылка);
Если (СуммаВозвратовЗаСмену + ЭтотОбъект.СуммаДокумента) > ЛимитСменыВсего Тогда
Отказ = Истина;
ВызватьИсключение "Превышен дневной лимит возвратов для кассира (30 000 руб.). Операция заблокирована!";
КонецЕсли;
КонецЕсли;
КонецПроцедуры
В данном коде мы использовали вспомогательную функцию ПолучитьСуммуВозвратовЗаСмену. Проанализируем, как она должна выглядеть. Функция выполняет запрос к базе данных и собирает сумму уже проведенных чеков на возврат по текущему пользователю за текущие сутки.
&НаСервереБезКонтекста
Функция ПолучитьСуммуВозвратовЗаСмену(Пользователь, ТекущийЧекСсылка)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(СУММА(ЧекККМ.СуммаДокумента), 0) КАК Сумма
|ИЗ
| Документ.ЧекККМ КАК ЧекККМ
|ГДЕ
| ЧекККМ.Проведен = ИСТИНА
| И ЧекККМ.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОперацийЧекККМ.Возврат)
| И ЧекККМ.Ответственный = &Пользователь
| И ЧекККМ.Дата МЕЖДУ &НачалоДня И &КонецДня
| И ЧекККМ.Ссылка <> &ТекущийЧек";
Запрос.УстановитьПараметр("Пользователь", Пользователь);
Запрос.УстановитьПараметр("НачалоДня", НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецДня", КонецДня(ТекущаяДата()));
Запрос.УстановитьПараметр("ТекущийЧек", ТекущийЧекСсылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Сумма;
КонецЕсли;
Возврат 0;
КонецФункции
Просто вызывать исключение через ВызватьИсключение — технически надежный, но не самый удобный для работы интерфейсный вариант. На практике кассиру придется закрывать чек, звать администратора, тот будет заходить под своей учетной записью, оформлять возврат и снова перезаходить под кассиром. Это существенно задерживает очередь покупателей.
Рассмотрим более эргономичный подход. В современном Рабочем месте кассира (РМК) платформы 1С реализован механизм «замены прав» или временного подтверждения старшего кассира:
Для настройки такой схемы в коде формы РМК перехватывается нажатие кнопки «Оформить возврат» и вызывается стандартная форма авторизации платформы: РМКУправлениеКлиент.ЗапроситьАвторизацию(...).
Внедрение лимита на сумму возврата — это действенный инструмент защиты бизнеса от финансовых потерь. Разрабатывая такое решение, важно соблюдать баланс между безопасностью и удобством работы касс: