Организация контроля за кассирами на удаленных точках в управлении торговлей 8.1

1С Контроль за кассирамиПроблема стояла следующая:

Есть более двадцати удаленных магазинов (распределенная Управление торговлей), в каждом магазине есть ККМ, подключенная к базе. Иногда возникали сложности с тем, что не совпадали суммы по отчету о розничных продажах и Z-отчету. Иногда умудрялись так пробить, что и не сообразишь сразу, что, да как :???: В связи с этим пришлось организовать небольшой контроль по кассе ККМ и вот что из этого получилось…

Создаем регистр сведений (периодичность — секунда)
Измерения:

  • КассаККМ (Справочник «Кассы ККМ»);

Ресурсы:

  • ТипСобытия (Перечисление «Типы событий ККМ»);
  • Событие (Строка);

Вид регистра сведений

Не забываем включить данный регистр в план обмена РБД.

Перечисление «Типы событий ККМ» может принимать следующие значения:

  • Продажа;
  • Ошибка;
  • Z-отчет;
  • Z-отчет (ошибка);
  • X-отчет;
  • X-отчет (ошибка);
  • Возврат;

Перечисление "Типы событий ККМ"

Немного правим пробитие чека ККМ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
РасшифровкаОшибки = "";
 
// Запишем событие "Начало пробития чека"
ТипСобытия = ?(ВидОперации = Перечисления.ВидыОперацийЧекККМ.Продажа, Перечисления.АП_ТипыСобытийККМ.Продажа, Перечисления.АП_ТипыСобытийККМ.Возврат);
АП_Наработки.ЗаписатьСобытиеККМ(КассаККМ, Товары.Выгрузить(), "Начало пробития чека...", , ТипСобытия);
 
// Организуем задержку в одну секунду, на всякий случай
ТекВремя = ТекущаяДата();
 
Пока ТекВремя = ТекущаяДата() Цикл
   ОбработкаПрерыванияПользователя();
КонецЦикла;
 
Если Не ПровестиИРаспечататьЧек(Ответ, Паника, ЭтаФорма, РасшифровкаОшибки) Тогда
   ОбщегоНазначения.СообщитьОбОшибке(Ответ);
   ТекстПаники = "Возможны расхождения ИБ и ленты ФР!";
   ТекстЗаголовка = "Внимание! Критическая ошибка!!!";
 
   Если Паника Тогда
      Предупреждение(ТекстПаники, , ТекстЗаголовка);
   КонецЕсли;
 
   Если ЗначениеЗаполнено(РасшифровкаОшибки) Тогда
      ТекстЗаголовка = ТекстЗаголовка + Символы.ПС + РасшифровкаОшибки;
   КонецЕсли;
 
   // Запишем событие ККМ
   ТекстСобытия = ОбщегоНазначения.СформироватьТекстСообщения(Ответ);
   АП_Наработки.ЗаписатьСобытиеККМ(КассаККМ, , ТекстСобытия, ТекстПаники + " " + ТекстЗаголовка, Перечисления.АП_ТипыСобытийККМ.Ошибка);
Иначе
   АП_Наработки.ЗаписатьСобытиеККМ(КассаККМ, , "Чек успешно пробит на ККМ" , , ТипСобытия);
КонецЕсли;

Процедура ЗаписатьСобытиеККМ выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Процедура ЗаписатьСобытиеККМ(КассаККМ, ТабличнаяЧасть = Неопределено, ТекстСобытия = Неопределено, ДопТекстСобытия = Неопределено, ТипСобытия) Экспорт
 
   Если ЗначениеЗаполнено(ТекстСобытия) Тогда
 
      Событие = ТекстСобытия;
      Если ЗначениеЗаполнено(ДопТекстСобытия) Тогда
         Событие = Событие + Символы.ПС + ДопТекстСобытия;
      КонецЕсли;
 
      Если ТабличнаяЧасть <> Неопределено Тогда
         // Добавим к тексту ошибки то, что было в чеке, когда она произошла
         Событие = Событие + Символы.ПС + СформироватьТекстСобытияПоТЧ(ТабличнаяЧасть);
      КонецЕсли;
 
   Иначе
      // Регистрируем продажу
      Событие = СформироватьТекстСобытияПоТЧ(ТабличнаяЧасть);
   КонецЕсли;
 
   // Записываем в регистр
   ЗаписьСобытия = РегистрыСведений.АП_СобытияККМ.СоздатьМенеджерЗаписи();
   ЗаписьСобытия.КассаККМ = КассаККМ;
   ЗаписьСобытия.Период = ТекущаяДата();
   ЗаписьСобытия.ТипСобытия = ТипСобытия;
   ЗаписьСобытия.Событие = Событие;
   ЗаписьСобытия.Записать(Истина);
 
КонецПроцедуры
 
Функция СформироватьТекстСобытияПоТЧ(ТабличнаяЧасть)
 
   Событие = "Сумма по чеку: " + Строка(ТабличнаяЧасть.Итог("Сумма"));
   Событие = Событие + Символы.ПС + "Список товаров:";
 
   Для Каждого Обход Из ТабличнаяЧасть Цикл
      // Номер строки
      Событие = Событие + Символы.ПС + "Строка " + Обход.НомерСтроки + ": ";
      // Наименование
      Событие = Событие + Обход.Номенклатура.Артикул + " " + Обход.Номенклатура.Наименование + " ";
      // Количество
      Событие = Событие + "Количество:" + Обход.Количество + " ";
      // Единица измерения
      Событие = Событие + Обход.ЕдиницаИзмерения.Наименование + " ";
      // Цена
      Событие = Событие + "Цена:" + Обход.Цена + " ";
      // Сумма
      Событие = Событие + "Сумма:" + Обход.Сумма + " ";
      // Скидки
      Событие = Событие + Обход.ПроцентАвтоматическихСкидок + " ";
      Событие = Событие + Обход.ПроцентСкидкиНаценки;
   КонецЦикла;
 
   Возврат Событие;
 
КонецФункции

Теперь в обработку закрытия смены добавляем вызов регистрации

1
2
3
4
5
6
Результат = ПолучитьСерверТО().ОтчетСГашением(ФР, Пароль);
Если ЗначениеЗаполнено(Результат) Тогда
   ТекстОшибки = ПолучитьСерверТО().ПолучитьТекстОшибкиФРТО(Результат);
   Сообщить(ТекстОшибки, СтатусСообщения.Важное);
   // Вставка -(
   АП_Наработки.ЗаписатьСобытиеККМ(КассаККМ, , ТекстОшибки, , , Перечисления.АП_ТипыСобытийККМ.ZОтчетОшибка);

В обработку обслуживания ККМ (у нас во всех магазинах стоят Меркурии) добавляем следующее

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Перем СписокСчетчиков;
Перем ЗначенияСчетчиков;
 
Процедура ЗаполнитьЗначениеСчетчика(Значение, НомерСчетчика)
 
   Если НомерСчетчика = 0 Тогда
      ЗначенияСчетчиков.СуммаПродаж = Значение;
   ИначеЕсли НомерСчетчика = 4 Тогда
      ЗначенияСчетчиков.СуммаСкидок = Значение;
   ИначеЕсли НомерСчетчика = 9 Тогда
      ЗначенияСчетчиков.СуммаВнесений = Значение;
   ИначеЕсли НомерСчетчика = 10 Тогда
      ЗначенияСчетчиков.СуммаИзъятий = Значение;
   ИначеЕсли НомерСчетчика = 11 Тогда
      ЗначенияСчетчиков.СуммаНаличных = Значение;
   ИначеЕсли НомерСчетчика = 12 Тогда
      ЗначенияСчетчиков.НарастающийИтог = Значение;
   ИначеЕсли НомерСчетчика = 36 Тогда
      ЗначенияСчетчиков.СуммаВозвратов = Значение;
   КонецЕсли;
 
КонецПроцедуры
 
ЗначенияСчетчиков = Новый Структура();
ЗначенияСчетчиков.Вставить("СуммаПродаж", 0);
ЗначенияСчетчиков.Вставить("СуммаСкидок", 0);
ЗначенияСчетчиков.Вставить("СуммаВнесений", 0);
ЗначенияСчетчиков.Вставить("СуммаИзъятий", 0);
ЗначенияСчетчиков.Вставить("СуммаНаличных", 0);
ЗначенияСчетчиков.Вставить("НарастающийИтог", 0);
ЗначенияСчетчиков.Вставить("СуммаВозвратов", 0);

Правим функцию Z-отчет

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Функция ZОтчет(Объект, Пароль, НомерЧека, НомерСмены) Экспорт
 
   Результат = мНетОшибки;
 
   // Печать Z-отчета
   Попытка
      // Без открытия смены (регистрации кассира) ФР не печатает отчеты
      ОткрытьСмену(Объект, "");
      // Печатаем краткий Z-отчет. Нулевые значения счетчиков не печатаются.
      // Объект.Драйвер.ZОтчет(1);
      // Получим значения со счетчиков ККМ
      Для Каждого Обход Из СписокСчетчиков Цикл
         НомерСчетчика = Обход.Значение;
         ЗаполнитьЗначениеСчетчика(Объект.Драйвер.ЗапроситьСчетчик(НомерСчетчика,0), НомерСчетчика);
      КонецЦикла;
      НомерОтчета = Объект.Драйвер.ZОтчет(1);
      АП_Наработки.ЗаписатьСобытиеККМОтчет(ЗначенияСчетчиков, НомерОтчета, Перечисления.АП_ТипыСобытийККМ.ZОтчет);
   Исключение
      // Получение текстового описания ошибки
      ПолучитьТекстОшибки(Объект);
      Результат = мОшибкаНеизвестно;
   КонецПопытки;
 
   Возврат Результат;
 
КонецФункции


Тоже самое делаем и с X-отчет.
Модели кассовых машин, конечно, могут быть разные, поэтому тут нужен индивидуальный подход.

Осталось написать только еще одну процедуру, которая сохранить данные об отчетах ККМ. Выглядеть она может примерно так

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Процедура ЗаписатьСобытиеККМОтчет(ЗначениеСчетчиков, НомерОтчета, ТипОтчета) Экспорт
 
   Форм = "ЧЦ=15; ЧДЦ=2; ЧН=-";
 
   СуммаПродаж = Формат(ЗначениеСчетчиков.СуммаПродаж , Форм);
   СуммаВнесений = Формат(ЗначениеСчетчиков.СуммаВнесений , Форм);
   СуммаИзъятий = Формат(ЗначениеСчетчиков.СуммаИзъятий , Форм);
   СуммаСкидок = Формат(ЗначениеСчетчиков.СуммаСкидок , Форм);
   СуммаВозвратов = Формат(ЗначениеСчетчиков.СуммаВозвратов , Форм);
   СуммаНаличных = Формат(ЗначениеСчетчиков.СуммаНаличных , Форм);
   НарастающийИтог = Формат(ЗначениеСчетчиков.НарастающийИтог , Форм);
 
   // Получим представление отчета
   ТекстОтчета = "     " + ?(ТипОтчета = Перечисления.АП_ТипыСобытийККМ.XОтчет, "X-ОТЧЕТ СВОДНЫЙ ", "Z-ОТЧЕТ ") + "№ " + НомерОтчета;
   ТекстОтчета = ТекстОтчета + Символы.ПС + "ПРОДАЖИ " + СуммаПродаж;
   ТекстОтчета = ТекстОтчета + ?(ЗначениеСчетчиков.СуммаВозвратов <> 0, Символы.ПС + "ВОЗВРАТЫ " + СуммаВозвратов, "");
   ТекстОтчета = ТекстОтчета + ?(ЗначениеСчетчиков.СуммаСкидок <> 0, Символы.ПС + "СКИДКИ " + СуммаСкидок, "");
   ТекстОтчета = ТекстОтчета + ?(ЗначениеСчетчиков.СуммаВнесений <> 0, Символы.ПС + "ВНЕСЕНИЕ НАЛИЧНЫХ " + СуммаВнесений, "");
   ТекстОтчета = ТекстОтчета + ?(ЗначениеСчетчиков.СуммаИзъятий <> 0, Символы.ПС + "ИЗЪЯТИЕ НАЛИЧНЫХ " + СуммаИзъятий, "");
   ТекстОтчета = ТекстОтчета + Символы.ПС + "CУММА НАЛИЧНЫХ " + СуммаНаличных;
   ТекстОтчета = ТекстОтчета + Символы.ПС + Символы.ПС + "НАРАСТАЮЩИЙ ИТОГ " + НарастающийИтог;
 
   // Кассу ККМ возьмем из настроек пользователя
   КассаККМ = УправлениеПользователями.ПолучитьЗначениеПоУмолчанию(глЗначениеПеременной("глТекущийПользователь"), "ОсновнаяКассаККМ");
   ЗаписатьСобытиеККМ(КассаККМ, , ТекстОтчета, ,ТипОтчета);
 
КонецПроцедуры

Ну и для разнообразия подкрасим строки при выводе в списке нашего регистра сведений

1
2
3
4
5
6
7
8
9
10
11
Если ДанныеСтроки.ТипСобытия = Перечисления.АП_ТипыСобытийККМ.Ошибка Или ДанныеСтроки.ТипСобытия = Перечисления.АП_ТипыСобытийККМ.ZОтчетОшибка Тогда
   ОформлениеСтроки.ЦветФона = Новый Цвет(225, 165, 165);
ИначеЕсли ДанныеСтроки.ТипСобытия = Перечисления.АП_ТипыСобытийККМ.Возврат Тогда
   ОформлениеСтроки.ЦветФона = Новый Цвет(175, 255, 175);
ИначеЕсли ДанныеСтроки.ТипСобытия = Перечисления.АП_ТипыСобытийККМ.XОтчетОшибка Тогда
   ОформлениеСтроки.ЦветФона = Новый Цвет(225, 195, 195);
ИначеЕсли ДанныеСтроки.ТипСобытия = Перечисления.АП_ТипыСобытийККМ.ZОтчет Тогда
   ОформлениеСтроки.ЦветФона = Новый Цвет(205, 255, 255);
ИначеЕсли ДанныеСтроки.ТипСобытия = Перечисления.АП_ТипыСобытийККМ.XОтчет Тогда
   ОформлениеСтроки.ЦветФона = Новый Цвет(240, 255, 255);
КонецЕсли;

В итоге мы получаем полную, ну или практически полную, картину того, что творят наши кассиры.

Осталось только сидеть и наслаждаться жизнью ;-)

События ККМ

Возвраты в событиях ККМ

Информация о Z-отчете

P.S. В последнее время немного модифицировал регистрацию событий — добавил начало пробития чека и его завершение. Результат на скриншотах.

4 комментария к записи «Организация контроля за кассирами на удаленных точках в управлении торговлей 8.1»

  1. хорошее решение!
    только еще в 2000г. делали контроль за кассирами в супермаркете так, что в реальном времени отражалась нажатая кассиром клавиша или просканированный товар и плюс к этому видео камеры за спиной кассира. База на Oracle и хороший доступ в интернет позволял контролировать из Москвы действия кассира в Н.Новгороде. 😉 Это позволяло отследить одну из уловок кассира и его сообщника когда дорогостоящий товар проносится над штрихкодсканером создавая иллюзию пробития товара, но при этом не попадал в чек (например с отвернутым в сторону от сканера штрихкодом)

    • Ну у нас не гипермаркеты, поэтому такой тотальный контроль не требовался 🙄 Хотя…может быть лишним он и не был бы

  2. Эхх, где были мои глаза, месяц назад методом проб и ошибок сам ваял. Единственное, добавил (и вам рекомендую) — при закрытии смены формируется и сохраняется на диск отчет в mxl-е что-бы ст.кассиры закрывающие смену видели z-отчет целиком, с чеками и покупкам.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *