iikoFront API SDK

Внешние фискальные регистраторы

Если в списке поддерживаемых моделей фискальных регистраторов (ФР) в iiko вы не нашли нужной вам модели, вы можете написать свою поддержку интересующей вас модели ФРа. Это будет плагин, подключаемый к iikoFront — внешний ФР. Cм. введение. Для плагинов, реализующих внешние ФРы, вводится специальное лицензирование.

Подключение внешнего фискального регистратора

Подключение внешнего ФРа состоит из 2 шагов.

Шаг 1: Зарегистрировать новую модель ФРа. Для этого нужно реализовать интерфейс ICashRegisterFactory и зарегистрировать его посредством вызова методов API:

var cashRegisterFactory = new SampleCashRegisterFactory();
PluginContext.Operations.RegisterCashRegisterFactory(cashRegisterFactory);

Это нужно для того, чтобы в iikoRMS появилась ваша новая модель ФРа, которую потом можно будет добавить и подключить как контрольно-кассовую технику (ККТ) к кассе.

NewCashRegisterModel

Шаг 2: Добавить ФР новой модели. Для этого нужно реализовать интерфейс ICashRegister и создавать его экземпляр в методе ICashRegisterFactory.Create():

class SampleCashRegisterFactory : MarshalByRefObject, ICashRegisterFactory
{
    ...
    public ICashRegister Create(Guid deviceId, [NotNull] CashRegisterSettings settings)
    {
        if (settings == null)
            throw new ArgumentNullException(nameof(settings));

        return new SampleCashRegister(deviceId, settings);
    }
}

По нажатию «Завершить» в окне добавления ФРа (iikoOffice => «Настройки оборудования»), будет вызван метод ICashRegisterFactory.Create(), который добавит новый ФР в список оборудования. После этого iikoRMS сможет общаться с внешним ФРом, отправлять ему команды и принимать его ответы.

CreateCashRegister

Настройки фискального регистратора CashRegisterSettings

Каждое оборудование имеет свой набор настроек IDeviceFactory.DefaultDeviceSettings:

interface IDeviceFactory
{
    ...
    [NotNull]
    DeviceSettings DefaultDeviceSettings { get; }
}
Произвольные настройки:

В зависимости от модели ФРа, набор настроек может различаться. Для этого предусмотрен контейнер произвольных настроек DeviceSettings.Settings:

class DeviceSettings
{
    ...
    List<DeviceSetting> Settings { get; set; }
}
Варианты настроек:

Все настройки из коллекции DeviceSettings.Settings при подключении ФРа попадают на вкладку «Дополнительные настройки»:

CustomCashRegisterSettings

Обязательные настройки:

Есть настройки, которые присутствуют в любой модели ФРа. К ним относятся:

FiscalRegisterTaxItems

FiscalRegisterTaxItems

class CashRegisterSettings : DeviceSettings
{
    [CanBeNull]
    DeviceNumberSetting Font0Width;
    [CanBeNull]
    DeviceCustomEnumSetting OfdProtocolVersion;
    List<FiscalRegisterTaxItem> FiscalRegisterTaxItems;
    List<FiscalRegisterPaymentType> FiscalRegisterPaymentTypes;
}

Взаимодействие iikoFront с внешним фискальным регистратором

Взаимодействие iikoFront с внешним ФРом происходит через интерфейс ICashRegister. Именно он отвечает за поведение внешнего ФРа. К примеру, если в iikoFront происходит оплата заказа, управление придет в команду ICashRegister.DoCheque() с необходимыми данными ChequeTask для проведения операции на ФР. iikoFront будет ждать выполнения команды и анализировать ответ ФРа CashRegisterResult.

Операций ФРа

1. Setup() — установка настроек, конфигураций ФРа. Это первая команда, выполняемая плагином. Вызывается при добавлении нового ФРа или при редактировании настроек ФРа. Основная задача плагина сохранить и применить новые настройки CashRegisterSettings, которые приходят аргументом. Чаще всего в этой команде происходит остановка драйвера ФР, применение новых настроек и запуск ФР, если ФР был запущен:

public void Setup([NotNull] DeviceSettings newSettings)
{
    if (newSettings == null)
        throw new ArgumentNullException(nameof(newSettings));

    Stop();
    Settings = (CashRegisterSettings)newSettings;
    if (newSettings.Autorun && wasConnected)
        Start();
}

2. Start() — запуск ФРа. Команда вызывается по нажатию «Запустить» в iikoOffice. Также ФР может быть запущен автоматически, если при добавлении устройства в настройках ФР установить флаг «Запускать автоматически».

StartExternalCashRegister

Обычно в этой команде происходит инициализация драйвера, открытие порта, соединение с устройством и тестирование соединения:

public void Start()
{
    SetState(State.Starting);
    try
    {
        driver = new Driver(); // инициализация драйвера устройства
        driver.Start()
    }
    catch (Exception e)
    {
        PluginContext.Log.Error($"Failed to load driver with error: {e.Message}");
        SetState(State.Stopped);
        throw new DeviceException(e.Message);
    }
    SetState(State.Running);
}

3. Stop() — остановка ФРа. Команда вызывается по нажатию «Остановить» в iikoOffice. Команда предназначена для остановки устройства, освобождения ресурсов и закрытия портов, например:

public void  Stop()
{
    SetState(State.Stopping);
    try
    {
        driver?.close();
    }
    catch (Exception e)
    {
        throw new DeviceException(e.Message);
    }
    driver = null;
    SetState(State.Stopped);
}

4. RemoveDevice() — удаление устройства. Команда вызывается при выборе «Удалить» в контекстном меню позиции с внешним ФРом в iikoOffice. Объект ФР в RMS будет помечен удаленным, если выполнение команды не выбросит исключение.

5. GetDeviceInfo() — запрос состояния ФРа. Команда вызывается каждый раз при открытии вкладки «Администрирование» => «Настройки оборудования» или по нажатию кнопки «Обновить» на той же вкладке.

GetDeviceInfo

Исходя из полученного ответа DeviceInfo, RMS понимает протокол общения с ФРом: можно ли с ним работать, какие команды можно отправлять ФРу. Состояние ФРа описывается типом DeviceInfo, который содержит:

Пример реализации запроса состояния подключенного и запущенного устройства:

public DeviceInfo GetDeviceInfo()
{
    return new DeviceInfo
    {
        State = State.Running,
        Comment = "Работает",
        Settings = currentCashRegisterSettings
    };
}

6. DoOpenSession() — открыть кассовую смену (КС). Актуально для тех ФРов, драйверы которых имеют команду «Открыть смену». Например, ФРы ФЗ-54, т.к. нужно указывать имя кассира. Если ФР не имеет отдельной команды для открытия смены, то нужно вернуть успешный ответ CashRegisterResult.Success = true без выполнения операции в ФР.

7. DoXReport() — печать X-отчета или его аналога (промежуточный суточный отчет без закрытия смены). Кассовая смена на iikoFront считается открытой если команда DoOpenSession() выполнится без исключения, а DoXReport() вернет успешный результат: CashRegisterResult.Success = true.

8. DoBillCheque() — пречек заказа или отмена пречека. Команда выполняется на тех ФРах, которые поддерживают печать пречека CashRegisterDriverParameters.IsBillTaskSupported = true (см. чек типа «Счёт»).

Информация по заказу приходит в аргументе BillTask:

Позиции заказа описываются типом ChequeSale:

Так выглядит ответ на большинство команд ФРа, будь то оплата, предоплата, пречек, возврат, печать Z-отчета, печать X-отчета, внесение, изъятие. В зависимости от содержимого ответа, iikoFront решает выполнилась ли команда на ФР и с каким результатом. В общем случае, для любой операции с таким ответом, если результат вернулся неуспешным Success=false, iikoFront выведет ошибку на экран с текстом сообщения Message и iikoFront будет считать, что на ФРом команда не была выполнена. Это не касается проверки на задвоение, которая выполняется при каждой денежной операции (оплата, возврат, внесение, изъятие, предоплата). Задача этого механизма не дать выполнить денежную операцию повторно, на тот случай, если ФР вернул ошибку. iikoFront перед каждой такой операцией будет пытаться выявить несоответствие денежных сумм в ФРе со своими подсчетами.

Если отрицательный ответ iikoFront обрабатывает одинаково, то успешный результат для каждой операции анализируется по-разному. Так, в случае пречека, номер счета/заказа будет сохранен в базу iikoFront, при условии, что BillNumber != null.

9. DoCheque() — закрытие заказа, предоплата, возврат. Команда вызывается при закрытии заказа (по нажатию «Оплатить» на экране кассы) в iikoFront. Вся необходимая информация по заказу, его оплатах, кассире приходит в аргементе ChequeTask, который включает в себя описание BillTask и дополняет его следующими свойствами:

Все вышеперечисленные виды оплат в сумме покрывают стоимость заказа.

При проведении оплат, предоплат, возвратов, внесений, изъятий iikoFront может сверять свои подсчеты денежных сумм по кассовой смене с подсчетами ФРа, это зависит от конфигурации iikoFront, по-умолчанию сверка происходит. Делается это для того, чтобы избежать возможного дублирования печати чека, который мог все-таки напечататься, даже если вернулся неуспешный результат Success=false. Для этого анализируются поля CashSum и TotalIncomeSum. Также после оплаты iikoFront в БД сохраняет номер документа ФРа DocumentNumber, потом этот номер можно увидеть в закрытом заказе и номер продажи SaleNumber.

Пример реализации DoCheque():

public CashRegisterResult DoCheque([NotNull] ChequeTask chequeTask)
{
	if (chequeTask == null)
		throw new ArgumentNullException(nameof(chequeTask));

	if (chequeTask.IsCancellation)
		throw new DeviceException("Чек аннулирования не поддерживается");

	driver.SetCashier(chequeTask.CashierName, chequeTask.CashierTaxpayerId);

	//Отменяем открытие документы, если они есть
	driver.ResetDevice();
	
	//Открыть чек
	if (chequeTask.isRefund)
		driver.OpenRefundCheck();
	else
		driver.OpenSaleCheck()

	//печатаем номер стола и заказа
	driver.PrintText(string.IsNullOrWhiteSpace(chequeTask.TableNumberLocalized)
		? string.Format("Стол: {0}, Заказ № {1}", chequeTask.TableNumber, chequeTask.OrderNumber)
		: chequeTask.TableNumberLocalized);

	//Печатаем дополнительные строки в начале чека
	if (!string.IsNullOrWhiteSpace(chequeTask.TextBeforeCheque))
		driver.PrintText(chequeTask.TextBeforeCheque);

	foreach (var sale in chequeTask.Sales)
	{
		var taxId = GetTaxId(sale); //получаем налоговую ставку
		driver.RegisterItem(sale.Name, sale.Price, sale.Amount, taxId);

		//Регистрируем скидку на позицию
		if (sale.DiscountSum > 0.0m)
			driver.RegisterDiscount(sale.DiscountSum)
		
		//Регистрируем надбавку на позицию
		if (sale.IncreaseSum > 0.0m)
			driver.RegisterIncrease(sale.IncreaseSum);
	}
	
	//Печатает подытог чека
	driver.PrintSubtotal();
	
	//Регистрируем скидку на подытог чека
	if (chequeTask.DiscountSum > 0.0m)
		driver.RegisterSubtotalDiscount(chequeTask.DiscountSum)

	//Регистрируем надбавку на подытог чека
	if (chequeTask.IncreaseSum > 0.0m)
		driver.RegisterSubtotalIncrease(chequeTask.IncreaseSum);

	foreach (var cardPayment in chequeTask.CardPayments)
	{
		var paymentRegisterId = GetPaymentRegisterId(cardPayment); //получить номер типа оплаты
		driver.AddPayment(GetCardPaymentId(cardPayment), cardPayment.Sum);
	}

	cardPayments = chequeTask.CardPayments.Sum(cardPament=>cardPayment.Sum);
	
	if (chequeTask.CashPayment > 0.0m || cardPayments == 0.0m)
	{
		//cashPaymentId - номер типа оплаты для наличных
		driver.AddPayment(cashPaymentId, cardPayment.Sum);
	}

	//Печатаем дополнительные строки в конце чека
	if (!string.IsNullOrWhiteSpace(chequeTask.TextAfterCheque))
		driver.PrintText(chequeTask.TextAfterCheque);

	//Закрываем чек
	driver.CloseCheck();
	return GetCashRegisterData();
}

10. GetCashRegisterData() — команда запроса данных ФРа. Вызывается всегда при фискальных операциях для получения данных по денежным суммам, для получения серийного номера ФРа (если серийный номер менялся в пределах одной КС, iikoFront попросит закрыть КС и открыть новую), или даты и времени на ФРе при открытии смены для их сверки. Пример реализации GetCashRegisterData():

public CashRegisterResult GetCashRegisterData()
{
	CheckState(State.Running);
	var totalIncomeSum = driver.GetSalesSum() - driver.GetRefundsSum();
	var cashSum = driver.GetCashSum();
	var serialNumber = driver.GetSerialNumber();
	var sessionNumber = driver.GetSessionNumber();
	var dateTime = driver.GetDateTime();
	var receiptNumber = driver.GetLastReceiptNumber();
	var documentNumber = driver.GetLastDocumentNumber();

	return new CashRegisterResult(cashSum, totalIncomeSum, sessionNumber, serialNumber, receiptNumber, 
								  documentNumber, null, dateTime.ToString(CultureInfo.InvariantCulture));
}

11. GetCashRegisterStatus() — команда запроса состояния ФРа. Это состояние отображается в трее iikoFront. Например, если ФР возвращает режим обслуживания RestaurantMode и он не совпадает c режимом обслуживания iikoFront, выведется сообщение «Неверный режим ФР». Также по SessionStatus проверяется просроченность смены. Кроме прочего ФР может выводить сообщение в трей, если:

    status.Success = false && status.Message != null

iikoFront опрашивает состояние ФР каждый раз после выполнения любой фискальной операции.

12. OpenDrawer() — открыть денежный ящик (ДЯ). В зависимости от настройки типа оплаты «Открывать денежный ящик», на ФР будет отправлена команда открыть денежный ящик.

13. IsDrawerOpened() — открыт ли денежный ящик. Если ФР не поддерживает команду получения статуса денежного ящика, то нужно вернуть true.

14. PrintText() — печать нефикального документа. Например, печать отчетов, печать штрихкодов для позиций. Текст документа будет прислан в команду PrintText() для печати его на бумаге.

15. DoPayIn() — внесение денежной суммы в кассу. Информацию по кассиру можно получить так:

IUser cashier = PluginContext.Operations.GetUserById(cashierId)

16. DoPayOut() — изъятие денежной суммы из кассы.

17. DoCorrection() — печать чека коррекции (iikoFront => «Доп» => «Чек коррекции»).

Сorrection По передаваемому объекту CorrectionTask, ФР понимает какой чек корреции нужно провести:

18. DoZReport() — закрыть кассовую смену на ФРе. Кассовая смена на iikoFront считается закрытой если команда DoZReport() вернет успешный результат: CashRegisterResult.Success = true.

19. GetQueryInfo() — запрос на расширенные команды ФРа. С помощью команд GetQueryInfo() и DirectIo() ФР может на свое усмотрение добавлять свои команды, которые можно будет вызывать из iikoFront «Доп» => «Команды фискальному регистратору»*. Метод GetQueryInfo() возвращает описание произвольных команд ФР, а DirectIo() выполняет их. Пример — приветствие пользователя:

public QueryInfoResult GetQueryInfo()
{
    var supportedCommands = new List<SupportedCommand>
    {
        // команда приветствия пользователя с кодовым именем "HelloWorld"
        new SupportedCommand("HelloWorld", "Приветствие")
        {
            // параметры ввода
            Parameters = new List<RequiredParameter>
            {
                // поле для ввода имени пользователя
                new RequiredParameter("UserName", "Имя пользователя", "Введите имя пользователя", "string")
            }
        }
    };

    var result = new QueryInfoResult
    {
        SupportedCommands = supportedCommands
    };

    return result;
}
public DirectIoResult DirectIo(CommandExecute command)
{
    if (command == null)
        throw new ArgumentNullException(nameof(command));

    var result = new DirectIoResult { Document = new Document { Lines = new List<string>() } };

    // находим команду приветствия пользователя по кодовому имени "HelloWorld" 
    if (command.Name == "HelloWorld")
    {
        // считываем данные из параметра ввода имени пользователя
        const string paramName = "UserName";
        var userName = command.Parameters.FirstOrDefault(item => item.Name == paramName)?.Value;
        
        // записываем приветственное сообщение
        result.Message = userName != null ? $"Привет, {userName}!" : "Привет всем!";
    }

    return result;
}

Выбор команды ФРа:

SelectCashRegisterCommand

Ввод имени пользователя:

InputCommandParameters

Приветствие пользователя:

CommandResult

Таким образом, плагин может запрашивать какие-то данные у пользователя, выводить сообщения, отображать на UI документы. QueryInfoResult:

20. DirectIo() — выполнение произвольной команды ФРа. Если команда возвращает заполненный DirectIoResult.Document, этот документ будет отображен на экране iikoFront.

21. GetCashRegisterDriverParameters() — настройки драйвера ФРа. Пример:

public CashRegisterDriverParameters GetCashRegisterDriverParameters()
{
    return new CashRegisterDriverParameters
    {
        CanPrintText = true, \\ Поддерживает ли ККМ печать текста
        SupportsPayInAfterSessionClose = true, \\ Поддерживает ли ККМ внесение при закрытой кассовой смене
        CanPrintBarcode = true, \\ Поддерживает ли ККМ печать простых штрихкодов
        CanPrintQRCode = true, \\ Поддерживает ли ККМ печать QR-кодов
        CanUseFontSizes = true, \\ Поддерживает ли ККМ использование шрифтов для печати текста
        Font0Width = 44, \\ Ширина строки шрифтом F0
        Font1Width = 42,\\ Ширина строки шрифтом F1
        Font2Width = 22,\\ Ширина строки шрифтом F2
        IsCancellationSupported = true, \\ Поддерживает ли ФР операцию Аннулирования
        ZeroCashOnClose = false, \\ Обнуляет ли ККМ сумму наличных в кассе при закрытии смены
        IsBillTaskSupported = false, \\ Поддерживается ли ФР печать пречека через команду «Счет»
    };
}

Как правило, эти настройки считываются с драйвера устройства после его запуска, т.к. до запуска они еще не известны. От этих настроек iikoFront понимает умеет ли ФР печатать текст, может ли вносить деньги в закрытую КС, поддерживает ли печать QR-кодов и т.д.

FAQ

1. Почему не появляется в списке моделей внешний ФР?

Вариант 1: Скорее всего регистрация внешнего ФР прошла неуспешно. Это можно проверить по логам api.log в папке с данными фронта. Наличие следующих записей говорит об успехе регистрации внешнего ФРа:

[2019-04-05 14:58:52,546] DEBUG [48]  Plugin CashRegisterPluginFolderName connected.
[2019-04-05 14:58:52,731] DEBUG [48]  Plugin CashRegisterPluginFolderName is calling RegisterCashRegisterFactory operation
[2019-04-05 14:58:52,791]  INFO [48]  Device factory: "CodeName" added.

CashRegisterPluginFolderName — имя папки c плагином iikoFront/Plugins/CashRegisterPluginFolderName CodeNameDeviceSettings.CodeName Иначе, в логах будет зафиксировано исключение.

Вариант 2: Обновить вкладку с настройками оборудования в iikoOffice: «Администрирование» => «Настройка оборудования» => кнопка «Обновить». Если вкладка была уже открыта до запуска iikoFront, то список доступных моделей оборудования не был обновлен автоматически, его нужно обновлять вручную или переоткрыть вкладку.

2. Как по идентификатору кассира Guid cashierId получить имя кассира?

IUser user = PluginContext.Operations.GetUserById(cashierId);
var name = user.Name;

3. Как сконфигурировать типы внесений и изъятий в iikoOffice, чтобы вызывались методы ФРа DoPayIn() и DoPayOut() ?

Скорее всего настроены нефискальные внесения и изъятия. Это просто бухгалтерские перемещения, которые не вызывают команды ФРа на их печать. Фискальные внесения и изъятия должны иметь пустой Шеф-счёт. См. документацию iiko про «Типы внесений и изъятий п.4».