Статья Принцип работы онлайн-кассы на примере АТОЛ и взаимодействие с ней через библиотеку

Привет Codeby !

main.jpg



Онлайн-касса ( ОК ), которую можно видеть сейчас в любом магазине, супермаркете, аптеке и так далее, представляет собой такое устройство. В каждой пятерочке или ашане, конечно, каждый видел подобный аппарат, кому-то приходилось нетерпеливо ожидать чека, когда времени мало, а инет в магазине видимо подтупливает)

kassa.png


Работа с этими аппаратами регламентируется 54 ФЗ "О применении контрольно-кассовой техники при осуществлении расчетов в Российской Федерации" и если кратко, то основное отличие онлайн-кассы от кассовых аппаратов, которые использовались раньше - это собственно слово "онлайн", а значит передача данных происходит через сеть и вся процедура продажи и отправки данных проходит в течении 2-5 секунд.

Весь аппарат по сути является принтером чеков, а основную функцию передачи выполняет фискальный накопитель (ФН, скрин ниже). Это устройство, которое вставляется в онлайн-кассу, получает, обрабатывает и передаёт данные об операциях на кассе оператору фискальных данных (ОФД), а тот – в налоговую инспекцию.

ОФД — компания-посредник, которая передает данные о платежах физических лиц в налоговую инспекцию. Все предприниматели обязаны самостоятельно заключить договор с одним из ОФД — это требование 54-ФЗ.

ОФД обрабатывает поступившую информацию, передает её в ФНС и направляет на онлайн-кассу специальный сигнал, который подтверждает, что процедура выполнена успешно.

fn.png


За сроком действия ФН кстати пристально следят, они покупаются на определенный срок (13, 15 и 36 месяцев) и их очень важно менять вовремя, иначе могут возникнуть проблемы с налоговой, штрафы и тд. Объем памяти таких ФН - около 250 000 записей. После окончания срока действия или объема памяти, ФН меняется и происходит процедура регистрации нового ФН.

Как для любого устройства подключаемого к ПК, для ОК необходим драйвер. Ставится он стандартно, и по умолчанию падает в C:\Program Files\Driver_version. Все функции и настройки находятся внутри него. По TCP/IP основной порт для работы - 5555. Хотя может кто-то использует и другие.

Безымянный.png


Основные функции выполняет библиотека fptr10.dll которая находится в папке bin. Когда я нашел официальную доку для работы с кассой, не мог понять где взять библиотеку libfptr10.py, которая используется в частности для питона (через pip она не ставится) Но все оказалось проще. Внутри драйвера нашел папку lang, внутри нее подкаталоги для различных ЯП. Там и go, java, c++, python и еще много чего. В общем библиотека которая мне нужна, лежала в папке python. Я лично предпочитаю забирать библиотеки .py и .dll в свой каталог и работать с ними там. Хотя можно и прописывать пути к директории драйвера.

Для примера скрипт работы с ОК может выглядеть так :

Python:
import os
from libfptr10 import IFptr
from datetime import datetime

# инициализация объекта драйвера
DRIVER_PATH = os.path.join(os.getcwd(), 'fptr10.dll')
fptr = IFptr(DRIVER_PATH)


STATUS_STATE_STR = ''
IP = '192.168.1.250'
PORT = "5555"
SOCKET_KKM = '{}:{}'.format(IP, PORT)

none_time = datetime(1970, 1, 1, 0)

# инициализация параметров ( по документации )
fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_MODEL, str(IFptr.LIBFPTR_MODEL_ATOL_AUTO))
fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_PORT, str(IFptr.LIBFPTR_PORT_TCPIP))
fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_IPADDRESS, IP)
fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_IPPORT, PORT)
result = fptr.applySingleSettings()

# проверка, есть ли коннект
try:
    fptr.open()
    isOpened = fptr.isOpened()
    if isOpened == 0:
        print('no connection\n')
    print('connection successful\n')

   # Установка параметров
    fptr.setParam(IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_STATUS)
    fptr.queryData()

   # Запрос внутренних параметров
    NUMBER_KKT = fptr.getParamString(IFptr.LIBFPTR_PARAM_SERIAL_NUMBER)
    #STATUS_STATE = fptr.getParamInt(IFptr.LIBFPTR_PARAM_SHIFT_STATE)
    KKM_TIME = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME)

    # Запрос параметров обмена с ОФН
    fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_OFD_EXCHANGE_STATUS)
    fptr.fnQueryData()

    NOT_TRANS_DOC = fptr.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENTS_COUNT)
    DATA_LAST_TRANS = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME)
    DATA_FN_KEY = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_LAST_SUCCESSFUL_OKP)

    #if STATUS_STATE == 0:
        #STATUS_STATE_STR = 'Закрыта'
    #elif STATUS_STATE == 1:
        #STATUS_STATE_STR = 'Открыта'
    #elif STATUS_STATE == 2:
        #STATUS_STATE_STR = 'Истекла'


    if int(NOT_TRANS_DOC) > 0:
        if DATA_FN_KEY == none_time:
            print('{}; ККМ №: {}; Дата/Время: {}; Непереданные чеки: {}; Первый непереданный: {};'.format(
            SOCKET_KKM, NUMBER_KKT, KKM_TIME, NOT_TRANS_DOC, DATA_LAST_TRANS))
        else:
            print('{}; ККМ №: {}; Дата/Время: {}; Непереданные чеки: {}; Первый непереданный: {}; Дата/время последнего ОКП: {};'.format(
                SOCKET_KKM, NUMBER_KKT, KKM_TIME, NOT_TRANS_DOC, DATA_LAST_TRANS, DATA_FN_KEY))
    else:
        if DATA_FN_KEY == none_time:
            print('{}; ККМ №: {}; Дата/Время: {}; Непереданные чеки: {};'.format(
            SOCKET_KKM, NUMBER_KKT, KKM_TIME, NOT_TRANS_DOC))
        else:
            print('{}; ККМ №: {}; Дата/Время: {}; Непереданные чеки: {}; Дата/время последнего ОКП: {};'.format(
                SOCKET_KKM, NUMBER_KKT, KKM_TIME, NOT_TRANS_DOC, DATA_FN_KEY))


    # debugger
    # print(fptr.errorDescription())
    fptr.close()

except Exception as e:
    print(e)
    fptr.close()

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

Внутри библиотеки libfptr10.py находятся функции и настройки, вызываемые из библиотеки .dll через ctypes. Ctypes - предоставляет типы данных, совместимые с C, и позволяет вызывать функции в библиотеках DLL или совместно используемых библиотеках.

Безымянный1.png


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

В общем, для того чтобы быстро начать работать через api, я думаю - все понятно. Ссылка на документацию тут
Можно глянуть что там вообще доступно, надо кстати тоже чекнуть функции, я ведь и не дочитал до конца, да и то что нужно, нашел не сразу ) Надеюсь, кому-то было интересно.


Спасибо за внимание.
 
Мы в соцсетях: