Предлагаемая публикация - просто некоторые заметки и наработки, позволившие мне быстро и почти безболезненно запустить в работу ККМ-онлайн модели Штрих-М 01Ф. Поскольку драйвер (а значит, и API) у многих моделей схож или вообще одинаков, думаю, можно распространить эти наработки ещё на несколько популярных моделей. На мой взгляд, и аппаратно, и программно указанный ФР прост, надёжен и стабилен (мне вот никакими адскими опытами пока не удалось ввергнуть его в ошибку, блокировку или иную бяку). По продукции Штрих-М на официальном сайте выложено множество документации, в т.ч. хорошее пользовательское описание и отличная документация для разработчиков, которой я и воспользовался. Там даже есть отрывочные примеры, правда, для 7.5 (если кому надо, могу выложить, я делал версию для 7.7 тоже).
Описанное хорошо подойдёт в случае, если разбираться в механизмах подключаемого оборудования БСП некогда, или если речь о конфигурациях на обычных формах, поколения УТ 10.3, как это было у меня. Или для самописных. Сверх опубликованного более ничего не требуется, никакие общие модули (я вообще на пустой конфе проверял сначала).
Установку и регистрацию в ОФД делал приглашённый специалист, но весь этот процесс также описан в руководствах.
Поскольку ничего внятного о тестовой эксплуатации узнать не удалось, работы велись сразу в "боевом" режиме, что обязывало минимизировать ошибки и количество тестовых запусков. Вообще, кто-то утверждает, что в природе есть тестовые фискальные накопители и даже якобы их можно купить, регистрируясь при этом в рабочем кабинете ОФД; некоторые ОФД открыли тестовые площадки приёма-перехвата данных, предоставляемые тоже по отдельному договору за отдельную плату. Но "старый добрый" способ теста - пробивать минимальные суммы - тоже законен и ФНС не напряжёт, и это мне в один голос говорили многие знающие люди. Так и поступил.
Общие принципы
Связь с аппаратом возможна через COM-порт кабелем usb, или через патчкорд по TCP-сокету. Важно, что связь через ком позволяет аппарату легко работать с терминалами. 1С где-то далеко, а рабочее место кассира оснащается ФР и надо лишь поставить драйвер на сам сервер да прокинуть порт (указать в настройках терминала и поправить безопасность, если потребуется), впрочем, эти приёмы обычно хорошо знают системные администраторы. У меня ФР уже работает с 1С-кой в терминале.
Разумеется, я реализовал только самые первоочерёдно нужные операции, вообще их существенно больше, но общий принцип одинаков. При установке драйвера сразу ставится и программная компонента, подключаемая как внешняя штатным образом (обычно она сама сразу нормально регистрируется в системе). ПО к этому ФР включает пользовательский интерфейс утилиты ручного управления, и, что ценно, его поведение абсолютно совпадает с поведением программного интерфейса этой же утилиты. Т.е. можно сначала безо всякой 1С оформить некую операцию, используя интерфейс утилиты, а затем попробовать отдать аналогичные команды в коде 1С.
При работе используется переменная, содержащая com-объект, чьи свойства и методы, собственно, и используются. Поскольку, по сути, ФР это просто маленький принтер с некоторыми пакетными предзаданными действиями и собственной памятью, мы запрашиваем драйвер о значениях неких свойств, устанавливаем значения этих свойств, вызываем методы. Принята следующая идеология: сначала устанавливаются значения свойств, нужные для вызова действия, потом вызывается метод. Все методы - без параметров. По вызове метода некоторые свойства меняют свои значения. Есть понятие "состояние ФР", определяющее, что он может и что нет в каждый момент времени. У некоторых из них есть уточняющие "подсостояния". Некоторые методы изменяют состояние ФР согласно своей внутренней логике. Подробнее всё это описано в руководстве для разработчика.
ФР обладает собственной аппаратной памятью, где хранит таблицы - это действительно плоские простые таблицы, никак не связанные между собой и наполненные значениями простых типов. "Настройка свойств" - "Таблицы". Без установленной связи с включённым аппаратом, ясен перец, недоступны. Советую полазить и ознакомиться. Они доступны для чтения и записи из программного кода. При работе с методами тем или иным свойствам указываются обычно числовые ID, соответствующие порядковым номерам строк в этих таблицах.
Почти каждый метод перед выполнением требует авторизации (указания ID пользователя), не ленитесь их указывать. Для выполнения некоторых методов требуется указание 29 (админ) или 30 (системный админ), так, например, простые смертные кассиры не могут закрыть смену и даже снять Х-отчёт. Подробнее эти завихрения разделения прав описаны в руководстве пользователя.
Исходники для основных действий.
// Инициализирует ФР как COM-объект; рекомендуется расположить в модуле "повт.исп." с сохранением в течение сеанса.
Процедура ИнициализироватьФР() Экспорт
Попытка
ПодключитьВнешнююКомпоненту("AddIn.DrvFR"); // достаточно штатной установки драйвера на локальный ПК или сервер
ком=Новый COMОбъект("AddIn.DrvFR"); // никакой более регистрации на современных ОС обычно не требуется
глФР=ком; // экспортная переменная глФР переменная должна быть объявлена в глобальном модуле обычного приложения
Исключение
Сообщить("Ошибка инициализации фискального регистратора кассовых чеков: "+ОписаниеОшибки(),СтатусСообщения.Важное);
КонецПопытки;
КонецПроцедуры
// Зависит от контекста реализации - например, это может быть параметр сеанса или реквизит справочника Пользователи, Кассы, Подразделения;
// возвращает элемент справочника, содержащего сведения о подключаемых устройствах; типовой или сделанный отдельно. Должен содержать
// сведения, необходимые для подключения к конкретному устройству (см. ПодключитьФР).
//
Функция ПолучитьИспользуемыйФР() Экспорт
//
КонецФункции
// Зависит от контекста реализации - например, это может быть параметр сеанса или реквизит справочника Пользователи, Кассы, Подразделения;
// возвращает число от 1 до 30, позицию в таблице пользователей и паролей ФР. Нулевое значение означает обычно невозможность работы.
// Число 29 означает права обычного администратора, 30 - системного администратора (под 30 рекомендуется вести тестирование и спец.операции).
Функция ПолучитьИдПользователя() Экспорт
//
КонецФункции
// Подключает физическое устройство, проверяет связь, приводит в рабочий режим логическое устройство.
// При нестабильной связи или нетривиальном кэшировании данных 1С/ОС, рекомендуется вызывать перед каждым рабочим действием.
//
// Возвращает успешность (булево). При ошибке возвращает Ложь.
//
// Параметры:
// рУстройство - логическая запись об устройстве (например, элемент справочника), содержащая обычно следующие сведения для подключения:
//BaudRate
//НомерПортаCOM
//Таймаут
//ТаймаутСоединения
//ТаймаутСоединенияTCP
//ПортTCP
//ИмяУстройства
//IPУстройства
//РежимПодключения (см.руководство; например, 0 - ком-порт, 6 - tcp-сокет)
//
// рКодОшибки - число; на входе 0, на выходе либо 0 (ошибок нет), либо код ошибки.
// рПояснятьОК - булево; комментировать ли успешное подключение.
//
Функция ПодключитьФР(рУстройство,рКодОшибки=0,рПояснятьОК=Ложь) Экспорт
Попытка
рКодОшибки=0;
Если не ЗначениеЗаполнено(рУстройство) Тогда Возврат Ложь КонецЕсли;
фр=глФР;
фр.Password=30; // можно ещё 29
рез=фр.CheckConnection();
Если рез<>0 Тогда
//Сообщить("ПодключитьФР, ошибка предварительной проверки подключения: "+фр.ResultCodeDescription);
//рКодОшибки=фр.ResultCode;
рУжеПодключены=Ложь;
Иначе
Если рПояснятьОК Тогда Сообщить("ПодключитьФР, предварительная проверка подключения успешна.") КонецЕсли;
// смотрим, что она определила
Попытка рУжеПодключены=фр.Connected Исключение рУжеПодключены=Ложь КонецПопытки;
КонецЕсли;
//
Если рУжеПодключены Тогда Возврат Истина КонецЕсли;
Если рУстройство.РежимПодключения=0 Тогда
фр.BaudRate=6;
ИначеЕсли рУстройство.РежимПодключения=6 Тогда
фр.BaudRate=1;
КонецЕсли;
//
фр.ComNumber=рУстройство.НомерПортаCOM;
фр.Timeout=рУстройство.Таймаут;
фр.ConnectionTimeout=рУстройство.ТаймаутСоединения;
фр.TCPConnectionTimeout=рУстройство.ТаймаутСоединенияTCP;
фр.SyncTimeout=0;
фр.ComputerName=рУстройство.ИмяУстройства;
фр.LDIndex=0;
фр.LockTimeout=10000;
фр.TCPPort=рУстройство.ПортTCP;
фр.IPAddress=рУстройство.IPУстройства;
фр.UseIPAddress=False;
фр.ConnectionType=рУстройство.РежимПодключения;
фр.EscapeIP="127.0.0.1";
фр.EscapePort=1000;
фр.EscapeTimeout=1000;
фр.SysAdminPassword=30;
фр.CardPayType=2;
фр.CardPayEnabled=False;
фр.LogCommands=False;
фр.LogMethods=False;
фр.SaleError=False;
фр.MobilePayEnabled=False;
фр.PayDepartment=15;
фр.ParamsPageIndex=0;
фр.RealPayDepartment=1;
фр.WaitForPrintingDelay=1000;
фр.BufferingType=0;
фр.FeedAfterCut=False;
фр.FeedLineCount=3;
фр.StatusCommand=0;
фр.LogMaxFileSize=10;
фр.LogMaxFileCount=10;
фр.CodePage=0;
фр.PrintJournalBeforeZReport=False;
фр.TranslationEnabled=False;
фр.AdjustRITimeout=False;
фр.ReconnectPort=False;
фр.DoNotSendENQ=False;
рез=фр.Connect();
Если рез<>0 Тогда
Сообщить("ПодключитьФР, ошибка собственно подключения: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
Иначе
Если рПояснятьОК Тогда Сообщить("ПодключитьФР, собственно подключение успешно.") КонецЕсли;
КонецЕсли;
Если рез<>0 Тогда
Сообщить("ПодключитьФР, ошибка заключительной проверки подключения: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
Иначе
Если рПояснятьОК Тогда Сообщить("ПодключитьФР, заключительная проверка подключения успешна.") КонецЕсли;
КонецЕсли;
Попытка рУжеПодключены=фр.Connected Исключение рУжеПодключены=Ложь КонецПопытки;
Возврат рУжеПодключены;
Исключение
Сообщить("ПодключитьФР, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Возвращает успешность (булево). Необязательное; но важно, что при уничтожении переменной связь разрушается не всегда
// (возможно, проявляется описанное в СП кэширование подключенных объектов внешних компонент в течение всего сеанса).
//
Функция ОтключитьФР(рКодОшибки=0,рПояснятьОК=Ложь) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=ПолучитьИдПользователя();
Попытка рУжеПодключены=фр.Connected Исключение рУжеПодключены=Ложь КонецПопытки;
Если не рУжеПодключены Тогда Возврат Истина КонецЕсли;
рез=фр.Disconnect(); // собственно главное
Если рез<>0 Тогда
Сообщить("ОтключитьФР, ошибка подключения: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
Иначе
Если рПояснятьОК Тогда Сообщить("ОтключитьФР, подключение успешно.") КонецЕсли;
Возврат Истина;
КонецЕсли;
Исключение
Сообщить("ОтключитьФР, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Возвращает структуру вида: НомерОператора, ЕстьЛента, РазрядностьСумм, РазрядностьКоличеств, Режим, ОписаниеРежима,
// Подрежим, ОписаниеПодрежима, СостояниеФН. Если Режим=8 (открытый документ), то добавляется РежимОткрытогоДокумента
// и ОперацийВЧеке.
//
// При ошибке возвращает Неопределено.
//
// Номера и назначение режимов и статусов (поле ECRMode):
//0 Принтер в рабочем режиме
//1 Выдача данных
//2 Открытая смена, 24 часа не кончились
//3 Открытая смена, 24 часа кончились
//4 Закрытая смена
//5 Блокировка по неправильному паролю налогового инспектора
//6 Ожидание подтверждения ввода даты
//7 Разрешение изменения положения десятичной точки
//8 Открытый документ // Продажа, Покупка, Возврат продажи, Возврат покупки, уточняется через ECRMode8Status
//9 Режим разрешения технологического обнуления (В этот режим ККМ переходит по включению питания, если некорректна информация в энергонезависимом ОЗУ ККМ).
//10 Тестовый прогон
//11 Печать полного фискального отчета
//12 Печать длинного отчета ЭКЛЗ
//13 Работа с фискальным подкладным документом
//14 Печать подкладного документа
//15 Фискальный подкладной документ сформирован
//
Функция ПолучитьРежимФР(рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=ПолучитьИдПользователя();
Если фр.ECRMode=1 Тогда // вызывать нельзя
Возврат Неопределено;
КонецЕсли;
рРезультат=Новый Структура;
фр.GetDeviceMetrics();
Если фр.CapGetShortECRStatus Тогда
рез=фр.GetShortECRStatus();
Иначе
рез=фр.GetECRStatus(); // длинный полный
КонецЕсли;
Если рез<>0 Тогда
Сообщить("ПолучитьРежимФР, ошибка получения: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Неопределено;
КонецЕсли;
рРезультат.Вставить("НомерОператора",фр.OperatorNumber);
рРезультат.Вставить("ЕстьЛента",фр.ReceiptRibbonIsPresent); // есть ли лента
рРезультат.Вставить("РазрядностьСумм",?(фр.PointPosition,2,0)); // для сумм, если Истина, то 2 разряда, иначе 0 разрядов
рРезультат.Вставить("РазрядностьКоличеств",?(фр.QuantityPointPosition,3,6)); // для количеств, если Истина, то 3 знака, иначе 6 знаков
рРезультат.Вставить("Режим",фр.ECRMode);
рРезультат.Вставить("ОписаниеРежима",фр.ECRModeDescription);
#Область ОписаниеПодрежимов
//0 Бумага есть – ККТ не в фазе печати операции – может принимать от хоста команды, связанные с печатью на том ленте, датчик которой сообщает о наличии бумаги.
//1 Пассивное отсутствие бумаги – ККМ не в фазе печати операции – не принимает от хоста команды, связанные с печатью на том ленте,
// датчик которой сообщает об отсутствии бумаги.
//2 Активное отсутствие бумаги – ККМ в фазе печати операции – принимает только команды, не связанные с печатью. Переход из этого подрежима только в подрежим 3.
//3 После активного отсутствия бумаги – ККМ ждет команду продолжения печати. Кроме этого принимает команды, не связанные с печатью.
//4 Фаза печати операции длинного отчета (полные фискальные отчеты, полные отчеты ЭКЛЗ, печать контрольных лент из ЭКЛЗ) – ККМ не принимает от
// хоста команды, связанные с печатью, кроме команды прерывания печати.
//5 Фаза печати операции – ККМ не принимает от хоста команды, связанные с печатью.
#КонецОбласти
рРезультат.Вставить("Подрежим",фр.ECRAdvancedMode);
рРезультат.Вставить("ОписаниеПодрежима",фр.ECRAdvancedModeDescription);
Если фр.ECRMode=8 Тогда
// 0 Открыт чек продажи, 1 Открыт чек покупки, 2 Открыт чек возврата продажи, 3 Открыт чек возврата покупки
рРезультат.Вставить("РежимОткрытогоДокумента",фр.ECRMode8Status);
рРезультат.Вставить("ОперацийВЧеке",фр.QuantityOfOperations); // количество операций в текущем чеке
КонецЕсли;
рРезультат.Вставить("СостояниеФН",фр.FMResultCode);
Возврат рРезультат;
Исключение
Сообщить("ПолучитьРежимФР, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Возвращает структуру вида СостояниеСмены (см.руководство программиста), НомерЧека, НомерСмены.
// При ошибке возвращает Неопределено.
//
Функция ПолучитьНомерСменыИЧека(рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=30; // и только так
рез=фр.FNGetCurrentSessionParams();
Если рез<>0 Тогда
Сообщить("ПолучитьНомерСменыИЧека, ошибка получения: "+фр.ResultCodeDescription+" (использован пароль "+СокрЛП(фр.Password)+").");
рКодОшибки=фр.ResultCode;
Возврат Неопределено;
КонецЕсли;
рРезультат=Новый Структура;
рРезультат.Вставить("СостояниеСмены",фр.FNSessionState);
рРезультат.Вставить("НомерСмены",фр.SessionNumber); // если закрыта, то последней, если открыта, то текущей
рРезультат.Вставить("НомерЧека",фр.ReceiptNumber); // если закрыта, то 1, если открыта, то последниего текущего
Возврат рРезультат;
Исключение
Сообщить("ПолучитьНомерСменыИЧека, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Открывает кассовую смену. Возвращает успешность (булево).
Функция ОткрытьСмену(рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=ПолучитьИдПользователя();
Если фр.ECRMode<>4 Тогда // смена уже открыта, или некий другой режим
рКодОшибки=99;
Возврат Истина;
КонецЕсли;
рез=фр.OpenSession();
Если рез<>0 Тогда
Сообщить("ОткрытьСмену, ошибка выполнения: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
Сообщить("ОткрытьСмену, смена успешно открыта.");
Возврат Истина;
Исключение
Сообщить("ОткрытьСмену, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Закрывает кассовую смену (снимает Z-отчёт). Возвращает успешность (булево).
Функция ЗакрытьСмену(рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=30; // можно ещё 29
Если фр.ECRMode<>2 и фр.ECRMode<>3 Тогда // смена уже закрыта, или другой режим
рКодОшибки=99;
Возврат Истина;
КонецЕсли;
рез=фр.PrintReportWithCleaning();
Если рез<>0 Тогда
Сообщить("ЗакрытьСмену, ошибка выполнения: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
Сообщить("ЗакрытьСмену, смена успешно закрыта.");
Возврат Истина;
Исключение
Сообщить("ЗакрытьСмену, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Возвращает успешность (булево).
Функция СнятьХОтчет(рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=30; // можно ещё 29
Если фр.ECRMode<>2 и фр.ECRMode<>3 Тогда // смена уже закрыта, или другой режим
Возврат Истина;
КонецЕсли;
рез=фр.PrintReportWithoutCleaning();
Если рез<>0 Тогда
Сообщить("СнятьХОтчет, ошибка снятия отчёта: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
Сообщить("СнятьХОтчет, отчёт успешно снят.");
Возврат Истина;
Исключение
Сообщить("СнятьХОтчет, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Выполняет печать чека (продажи, покупки, возврата продажи, возврата покупки)
// Возвращает булево, успешность. При ошибке возвращает Ложь.
// КодСтавкиНДС в данном случае общий на все позиции, т.е. для правильной печати у всех строк должна быть одинаковая ставка НДС.
//
// Параметры:
// рПараметры - структура с обязательными ключами:
// ДанныеПродажи (таблица значений или массив структур: колонки ПредставлениеНоменклатуры, Количество, Цена);
// КодПодразделения (число);
// КодСтавкиНДС (число), где 1 - 18%, 2 - 10%, 3 - 0%, 4 - Без налога, 5 - 18/118, 6 - 10/110;
// СуммыОплат (структура: ключ - число, 1 нал, 2 безнал; значение - сумма платежа).
// рКодОшибки - число, на входе 0, на выходе содержит числовой код ошибки. 0 - ошибок нет.
//
Функция НапечататьЧек(рПараметры,рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
фр.Password=ПолучитьИдПользователя();
Если фр.ECRMode<>2 и фр.ECRMode<>3 и фр.ECRMode<>4 Тогда // смена уже закрыта, или другой режим
Возврат Истина;
КонецЕсли;
фр.CheckType=рПараметры.РежимЧека; // «0» - продажа, «1» - покупка, «2» - возврат продажи, «3» - возврат покупки.
рез=фр.OpenCheck();
Если рез<>0 Тогда
Сообщить("НапечататьЧек, ошибка открытия нового чека: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
фр.StringForPrinting="============================================";
фр.UseReceiptRibbon=Истина;
фр.UseJournalRibbon=Ложь;
фр.PrintString();
Для каждого строДанных Из рПараметры.ДанныеПродажи Цикл
фр.Quantity=строДанных.Количество;
фр.Price=строДанных.Цена;
фр.Department=рПараметры.КодПодразделения;
фр.Tax1=рПараметры.КодСтавкиНДС;
фр.Tax2=0;
фр.Tax3=0;
фр.Tax4=0;
фр.StringForPrinting=СокрЛП(строДанных.ПредставлениеНоменклатуры);
//Работает в режимах 2 (проверка на окончание 24 часов производится запросом из ФП до
//выполнения операции), 4, 7, 8 (если статус 8-го режима ККМ=0) и 9
Если рПараметры.РежимЧека=0 Тогда
рез=фр.Sale(); // продажа
ИначеЕсли рПараметры.РежимЧека=1 Тогда
рез=фр.Buy(); // покупка
ИначеЕсли рПараметры.РежимЧека=2 Тогда
рез=фр.ReturnSale(); // возврат продажи
ИначеЕсли рПараметры.РежимЧека=3 Тогда
рез=фр.ReturnBuy(); // возврат покупки
КонецЕсли;
Если рез<>0 Тогда
Сообщить("НапечататьЧек, ошибка оформления записи продажи для "+СокрЛП(строДанных.ПредставлениеНоменклатуры)+": "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
КонецЦикла;
//
рез=фр.CheckSubTotal();
Если рез<>0 Тогда
Сообщить("НапечататьЧек, ошибка получения подытога чека: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
фр.Summ1=0;
фр.Summ2=0;
фр.Summ3=0;
фр.Summ4=0;
Попытка фр.Summ1=рПараметры.СуммыОплат.Получить(1) Исключение КонецПопытки;
Попытка фр.Summ2=рПараметры.СуммыОплат.Получить(2) Исключение КонецПопытки;
Попытка фр.Summ2=рПараметры.СуммыОплат.Получить(3) Исключение КонецПопытки;
Попытка фр.Summ2=рПараметры.СуммыОплат.Получить(4) Исключение КонецПопытки;
фр.DiscountOnCheck=0; // в данной версии считается, что скидка уже заложена в изменённых ценах
фр.Tax1=рПараметры.КодСтавкиНДС;
фр.Tax2=0;
фр.StringForPrinting="============================================";
рез=фр.CloseCheck();
Если рез<>0 Тогда
Сообщить("НапечататьЧек, ошибка закрытия чека: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
Возврат Истина;
Исключение
Сообщить("НапечататьЧек, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Возвращает успешность (булево).
Функция АннулироватьЧек(рЗакрытиеАдминистратора=Ложь,рКодОшибки=0) Экспорт
Попытка
рКодОшибки=0;
фр=глФР;
Если рЗакрытиеАдминистратора Тогда
фр.Password=30; // системный
Иначе
фр.Password=ПолучитьИдПользователя();
КонецЕсли;
Если фр.ECRMode<>8 Тогда // чек не открыт
Возврат Истина;
КонецЕсли;
Если рЗакрытиеАдминистратора Тогда
рез=фр.SysAdminCancelCheck();
Иначе
рез=фр.CancelCheck();
КонецЕсли;
Если рез<>0 Тогда
Сообщить("АннулироватьЧек, ошибка отмены чека: "+фр.ResultCodeDescription);
рКодОшибки=фр.ResultCode;
Возврат Ложь;
КонецЕсли;
Сообщить("АннулироватьЧек, чек успешно аннулирован.");
Возврат Истина;
Исключение
Сообщить("АннулироватьЧек, ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецФункции
Я не стал реализовывать множество других команд, вроде "ПродолжитьПечать" или "ОтрезатьЧек", т.к. принцип, думаю, ясен, и сделать их самостоятельно не проблема. Кстати, "ПродолжитьПечать" (ContinuePrint) бывает архиполезна, если посередине печати чека, пока он открыт, случилась ошибка, а аннулирование чека (в т.ч. под админом) не прокатывает.
Примечания
Некоторые советы сверх комментариев в коде.
1. Если Вам непонятно, что делается со свойствами в момент выполнения некоей команды, вызываемой из тестовой утилиты, включите ведение лога ("Настройка свойств" - "Дополнит.параметры" - "Лог") в свой файл, и внимательно прочитайте, что запишется при вызове команды. Так можно доподлинно узнать, "чего ему надо". Учтите, что программные вызовы драйвера (из вашей 1С) в лог не пишутся, только действия из интерфейса утилиты.
2. Большинство кнопок и некоторые поля в интерфейсе утилиты снабжено всплывающими подсказками, которые отображают имя команды API. Это удобно, чтобы понять, а что же скрывается за той или иной кнопкой, не нажимая её, безо всякого лога.
3. Если некая операция не удаётся программно, приходится использовать команды из интерфейса утилиты. Автоустановка ключей в какой-нибудь сложной ситуации с хитрым сочетанием факторов, предусмотренная разработчиками и не учтённая вами, поможет быстро выйти из тупика.
4. Используйте только латинские варианты свойств и методов. Русскоязычные - себе на уме.
5. Некоторые сообщения не то, чем кажутся. Например, ошибка несоответствия суммы подытога напрямую указанной сумме чека может выглядеть как отсутствие бумаги - и по коду, и по тексту ошибки, и по состоянию, в котором окажется ФР.
6. Если смена закрыта, то действия по чекам её открывают автоматически.
7. В зависимости от совокупности факторов (кэширование 1С, кэширование ОС) период жизни соединения колеблется, поэтому иногда приходится вызывать установку связи перед каждым полезным действием, а иногда и так прокатывает. Но, при этом, см. пункт 8.
8. Драйвер не понимает более 1 активного соединения, и неважно, это ваш COMОбъект или его родная утилита. Утилиту можно открыть "на посмотреть", но она вряд ли установит связь, пока существует соединение (ваша переменная в памяти 1С), и наоборот. Соединение в таких случаях не устанавливается, а наиболее характерная ошибка - таймаут. Не ставьте большой таймаут - всё равно не додумается. Чтобы запустить что-то, вам понадобится закрыть (выгрузить из памяти) что-то другое. В ряде случаев - ещё и выключить/включить сам ФР.
9. Методы и поля, представленные в интерфейсе утилиты, не жёсткая данность. Например, CheckSubTotal всегда вносит сумму в поле "summ1", но она может быть насильно перебита на другой тип оплаты. Учтите, "защита от дурака" у интерфейса утилиты понадёжнее, чем у методов API.
10. Не ленитесь прокручивать таблицы ФР при просмотре. Верхние строки могут быть пусты, а где-то внизу, строке на тридцатой, может поджидать интересный сюрприз.
Разумеется, весь этот примитивный код малополезен без инфраструктуры более высокого уровня - справочников, хранящих сведения об ФР и прочих кодах, документов вроде Кассовой смены, Чека ККМ итд. Но сие уж слишком зависит от конкретики, а опубликованное - оно общее.
Недостатки
Отмечу недостатки предложенного решения, возникшие либо из-за неполной ясности вопроса (в т.ч. юридической), либо из-за отсутствия надобности и времени) Они таковы:
1. Не допускается наличие в составе одного чека товаров с разными ставками НДС. Кому будет надо - это можно довольно легко доработать, но уже с упором на более высокоуровневые конструкции (табчасти документов, механику расчёта НДС итд).
2. Не обрабатываются скидки. Подразумевается, что скидка "размазана" пропорционально по ценам либо суммам позиций, или что цены указаны уже с учётом скидок. Важно, что в методе печати чека ФР оперирует ценой и количеством, и сам считает сумму, поэтому при всяких там скидках на сумму и иных манипуляциях, отличающих произведение цены и количества от итоговой суммы, следует это учитывать, либо допилить предложенный код до нормальной работы со скидками. Грешен, у меня на это времени пока нету.
3. Не раскрыты возможности оформления чеков и печати штрихкодов. Ну, тут уж мануал в руки и запасную катушку ленты в аппарат)
4. Проверка проводилась только на 3 модификациях ФР Штрих-М, и я не очень уверен, что на чём-то заковыристом абсолютно всё будет аналогично.
При небольшом допиливании или вовсе без него, должно работать и на 8.1, также уже гонял этот код с небольшими доработками под УФ на УТ 11.3 (заказчик не возжелал обновляться на типовую, ибо конфа весьма покуроченная).
Планирую докопаться до ФР АТОЛ, но у них с драйверами и их программными интерфейсами несколько больший разнобой, так что это пока наполеоновские планы.
Приглашаю всех к обсуждению. Если где накосячил, жду тухлых помидоров)