Архив метки: онлайн касса

Контроль касс Атол в Zabbix

Основная задача скрипта — вывести в консоль информацию об основных параметрах кассы, что бы агент заббикса мог как то распарсить данные.

Пример скрипта:

#!/usr/bin/python3.5
# -*- coding: utf-8 -*-
import base64
import ctypes
import sys
import json
import datetime
import os
import platform
from pprint import pprint

from lib import IFptr
#fptr = IFptr("")

#get extr param
if len(sys.argv)==1:
    print ("Возможные параметры:")
    print ("--info IP - общая информация о ККМ")
    exit(0)
arg=sys.argv[1]
if arg=="--info":
    ip=sys.argv[2];
    
LIBRARY_PATH = os.path.dirname(os.path.abspath(__file__))
fptr = IFptr(os.path.join(LIBRARY_PATH, "libfptr10.so"))
#connect to ATOL
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,"5555")
fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_ACCESS_PASSWORD,"0")
fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_USER_PASSWORD,"30")

fptr.applySingleSettings()
#открываем соединение
fptr.open()
isOpened = fptr.isOpened()
if isOpened==0:
    print ("9) Доступность:",0)
    exit(1)
    
if arg=="--info":
    fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_REG_INFO)
    fptr.fnQueryData()

    taxationTypes               = fptr.getParamInt(1062)
    agentSign                   = fptr.getParamInt(1057)
    ffdVersion                  = fptr.getParamInt(1209)
    print("Версия ФН:",ffdVersion)

    autoModeSign                = fptr.getParamBool(1001)
    offlineModeSign             = fptr.getParamBool(1002)
    encryptionSign              = fptr.getParamBool(1056)
    internetSign                = fptr.getParamBool(1108)
    serviceSign                 = fptr.getParamBool(1109)
    bsoSign                     = fptr.getParamBool(1110)
    lotterySign                 = fptr.getParamBool(1126)
    gamblingSign                = fptr.getParamBool(1193)
    exciseSign                  = fptr.getParamBool(1207)
    machineInstallationSign     = fptr.getParamBool(1221)
    fnsUrl                      = fptr.getParamString(1060)
    organizationVATIN           = fptr.getParamString(1018)
    organizationName            = fptr.getParamString(1048)
    organizationEmail           = fptr.getParamString(1117)
    paymentsAddress             = fptr.getParamString(1187)
    registrationNumber          = fptr.getParamString(1037)
    machineNumber               = fptr.getParamString(1036)
    ofdVATIN                    = fptr.getParamString(1017)
    ofdName                     = fptr.getParamString(1046)
    print("1) Организация:",organizationName)
    print("2) ОФД:",ofdName)
        
    fptr.setParam(IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_STATUS)
    fptr.queryData()
    serialNumber    = fptr.getParamString(IFptr.LIBFPTR_PARAM_SERIAL_NUMBER)
    modelName       = fptr.getParamString(IFptr.LIBFPTR_PARAM_MODEL_NAME)
    firmwareVersion = fptr.getParamString(IFptr.LIBFPTR_PARAM_UNIT_VERSION)
    print("3) S/N:",serialNumber)
    print("4) Модель:",modelName)
    print("5) Прошивка:",firmwareVersion)
    fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_VALIDITY)
    fptr.fnQueryData()
    dateTime            = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME)
    print("6) Дата окончания ФН:",dateTime)
    fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_RECEIPT)
    fptr.fnQueryData()
    documentNumber      = fptr.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER)
    print("7) Номер чека:",documentNumber)
    # Получение информации о неотправленных документах
    fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_OFD_EXCHANGE_STATUS)
    res=fptr.fnQueryData()
    unsentCount = fptr.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENTS_COUNT)
    if unsentCount>0:
        unsentFirstNumber = fptr.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER)
        unsentDateTime = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME)
        print ("8) Не фискализировано:",unsentCount)
        fptr.close()    
        del fptr
        exit(1)
    else:
        print ("8) Не фискализировано:",0)
    print ("9) Доступность:",1)

Пример выполнения:

root@pavel-All-Series:~/online_kassa# ./common.py --info 192.168.0.103
Версия ФН: 105
1) Организация: Индивидуальный предприниматель Пупкин Александр Владимирович
2) ОФД: ООО "Компания "Тензор"
3) S/N: 00103452021133752352
4) Модель: АТОЛ 55Ф
5) Прошивка: 1245
6) Дата окончания ФН: 2021-06-07 00:00:00
7) Номер чека: 414
8) Не фискализировано: 0
9) Доступность: 1

Пример оформления триггера в агенте zabbix:

UserParameter=ping_kassa,/home/pavel/online_kassa/common.py --info 192.168.0.103 | grep "9)" | awk '{print $3}'

UserParameter=non_fiskalize,/home/pavel/online_kassa/common.py --info 192.168.0.103 | grep "8)" | awk '{print $3}'

Пример печати чека на ККМ Атол посредством JavaScript

Накопал на просторах интернета.. Выложил, чтоб подсматривать саму методику работы. Принцип на любом языке одинаковый.

Читать далее Пример печати чека на ККМ Атол посредством JavaScript

Костылище при работе с ККМ в Розница 2.2.

С какого-то момента на одном из компьютеров с 1С, перестали печататься чеки на онлайн-кассе. Причем ошибок 1С никаких не показывала.  Причем при открытии/закрытии смен, чеки с Z отчетами выдавались.  Бился два дня с разнообразными настройками и поисками решения. Грешил и на ОС, и на 1С и на ККМ. Сегодня утром уже даже отладчиком 1С стал код шерстить, чтобы понять на каком моменте, что-то не так. Нашел:

&НаКлиенте
Процедура НапечататьЧекКлиент()
	Перем Отказ, ИдентификаторУстройства, ИспользоватьКассуБезПодключенияОборудования, РаспределениеВыручкиПоСекциям;
	
	ПодготовитьИПроверитьПечатьЧека(Отказ, ИдентификаторУстройства, ИспользоватьКассуБезПодключенияОборудования, РаспределениеВыручкиПоСекциям);
	
	Если Отказ Тогда
		Возврат
	КонецЕсли;
!!!!!!!! ошибка здесь!!!!!
	Если НЕ ИспользоватьПодключаемоеОборудование ИЛИ ИспользоватьКассуБезПодключенияОборудования Тогда
		
		Если НомерДокументаКассыККМ[Объект.КассаККМ] <> Неопределено Тогда
			Объект.НомерЧекаККМ  = НомерДокументаКассыККМ[Объект.КассаККМ];
		Иначе
			Объект.НомерЧекаККМ  = ПорядковыйНомерПродажи;
		КонецЕсли;
		Объект.ПробитЧек     = Истина;
		
		Модифицированность = Истина;

ИспользоватьПодключаемоеОборудование = ВСЕГДА = ЛОЖЬ.  Причем только когда работаешь в 1С именно на этом ПК. На других всё ок. Почему? Не понятно. Галочка стоит:

Место, где заполняется значение нашел:

// Проверяет, что включена ф.о "Использовать подключаемое оборудование" и авторизовался пользователь,
// а не внешний пользователь.
Функция ИспользоватьПодключаемоеОборудование() Экспорт
	
	Возврат ПолучитьФункциональнуюОпцию("ИспользоватьПодключаемоеОборудование") И ТипЗнч(Пользователи.АвторизованныйПользователь()) = Тип("СправочникСсылка.Пользователи");
	
КонецФункции

И оно возвращает ИСТИНА! В общем, бился, бился..

Пока прибил всё это костылем:

&НаКлиенте
Процедура НапечататьЧекКлиент()
	Перем Отказ, ИдентификаторУстройства, ИспользоватьКассуБезПодключенияОборудования, РаспределениеВыручкиПоСекциям;
	
	ПодготовитьИПроверитьПечатьЧека(Отказ, ИдентификаторУстройства, ИспользоватьКассуБезПодключенияОборудования, РаспределениеВыручкиПоСекциям);
	
	Если Отказ Тогда
		Возврат
	КонецЕсли;
	// грибов
	// совершенно не понятный глюк
	ИспользоватьПодключаемоеОборудование=истина;
	//
	Если НЕ ИспользоватьПодключаемоеОборудование ИЛИ ИспользоватьКассуБезПодключенияОборудования Тогда

Фискализация чеков через онлайн-кассу АТОЛ 55Ф на ОС Ubuntu, python

Прилетела задача фискализировать поступления платежей через оплату услуг с карточек через интернет, из личного кабинета абонента, оплаты абонента посредством Сбербанк-онлайн и т.п. Из ТУ : операционная система семейства Linux.

Было рассмотрено несколько вариантов решения:

1) Сервис Атол-Онлайн. Плюсы: простота интеграции, очень вменяемое API. Минусы — со второго года — выходит очень дорого по сравнению со «своей железкой»

2)PAYONLINE-01-ФА. Плюсы: прям в «базе» нет принтера, что удешевляет. Минусы: нет толкового описания работы с драйверами, примеров, нет драйверов под LInux

3) Атол-55Ф. Плюсы: драйвера под Linux, вменяемое описание работы с примерами на Java, cpp. Плюс натолкнулся на пример работы https://github.com/parshin/kkt с этой железкой. Минусы — дорого по сравнению с 2) но дешево по сравнению с 1)

Собственно драйвера ставятся стандартно. Далее пример пробития чека на python

#!/usr/bin/python2.7
# -*- coding: utf-8 -*-

# import web
import sys
import os
import json
import logging

from conf import *	    # загружаем настройки
import dto9fptr		    # загружаем заголовки драйвера

def DeviceSetup(driver,params):    
    driver.put_DeviceSingleSetting("Port", params["Port"])
    driver.put_DeviceSingleSetting("IPAddress", params["IPAddress"])
    driver.put_DeviceSingleSetting("IPPort", params["IPPort"])
    driver.put_DeviceSingleSetting("Model", params["Model"])
    driver.put_DeviceSingleSetting("Protocol", params["Protocol"])
    driver.put_DeviceSingleSetting("AccessPassword", params["AccessPassword"])
    driver.put_DeviceSingleSetting("UserPassword", params["UserPassword"])
    driver.put_DeviceSingleSetting("Protocol", params["Protocol"])
    driver.put_DeviceSingleSetting('SearchDir', os.path.dirname(DTO_LIB_NAME))
    driver.ApplySingleSettings()
    driver.put_DeviceEnabled(True)
    return 0

def GetCurrentParamDevice(driver):
    result = driver.GetStatus()    
    if result[0]!=0:
	print t_err+unicode(result[1])+t_off
	logging.info(result[1])
	exit(1)
    result = driver.get_DeviceSettings()
    print t_mess+'Текщие настройки: '+t_off + str(result)
    if str(result)=="None":
	etxt="Драйвер не подключился к устройству"
	print t_err+etxt+t_off
	logging.info(etxt)
	exit(1)
    return result

def print_check(driver, check_data):
    res=0
    mess="check_data: " + str(check_data)
    
    print t_mess+mess+t_off
    logging.info(mess)
    
    result = driver.put_DeviceEnabled(True)
    logging.info("put device enabled: " + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code != 0:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	print t_err+str(result_code)+t_off
	print t_err+result_description+t_off
        exit(1)  
    print t_ok+result_description+t_off		
    
    print t_mess+"- переводим ККМ в режим регистрации"+t_off
    result = driver.put_Mode(1)
    logging.info("put mode 1:" + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code != 0:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	print t_err+str(result_code)+t_off
	print t_err+result_description+t_off
    print t_ok+result_description+t_off					   
	
    print t_mess+"- создаем новый документ"+t_off
    result = driver.NewDocument()	
    logging.info("new document:" + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code == -3822:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
        logging.error("  trying close shift...")
	print t_err+str(result_code)+t_off
	print t_err+result_description+t_off
	exit(1)
    print t_ok+result_description+t_off	
    
    print t_mess+"- выставляем что тип чека - продажа"+t_off
    result = driver.put_CheckType(1)
    logging.info("put check type:" + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code != 0:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	print t_err+str(result_code)+t_off
	print t_err+result_description+t_off
	exit(1)
    print t_ok+result_description+t_off	
	
    print t_mess+"- открываем чек"+t_off
    result = driver.OpenCheck()
    logging.info("open check:" + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code != 0:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	print t_err+str(result_code)+t_off
	print t_err+result_description+t_off
	exit(1)
    print t_ok+result_description+t_off	
    
    print t_mess+"- регистрация позиций"+t_off
    logging.info("registering positions...")
    
    for position in check_data:
	print "--- №:"+str(position["num"])
	print "--- Дата:"+str(position["docdate"])
	print "--- Всего:"+str(position["summdoc"])
	for goods in position["goods"]:
	    print "---- Товар:"+str(goods["name"])
	    result = driver.put_Name(goods["name"].decode('utf8'))
	    print "---- Цена:"+str(goods["price"])
	    result = driver.put_Price(goods["price"])
	    print "---- Количество:"+str(goods["quiantity"])
	    result = driver.put_Quantity(goods["quiantity"])
	    result = driver.put_TaxNumber(3) 
	    print "---- Сумма:"+str(goods["summ"])
	    result_code = driver.put_PositionSum(goods["summ"]) 
	    print "---- Регистрация позиции:"
	    result_code = driver.Registration() 	    
	    if result_code[0] != 0:
		logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
		logging.error("  result description:" + repr(result_description).decode("unicode_escape"))	    
		print t_err+str(result_code)+t_off
		print t_err+result_description+t_off
		exit(1)
	

	print t_mess+"- прием оплаты"+t_off
	result = driver.put_TypeClose(1)  # Тип оплаты - Платежная карта
	logging.info("put type close:" + repr(result).decode("unicode_escape"))
	result_code = driver.get_ResultCode()
	result_description = driver.get_ResultDescription()
	if result_code != 0:
	    logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
	    logging.error("  result description:" + repr(result_description).decode("unicode_escape"))	    
	    print t_err+str(result_code)+t_off
	    print t_err+result_description+t_off
	    exit(1)

	print t_mess+"- сумма оплаты"+t_off
	result = driver.put_Summ(position["summdoc"])
	logging.info("put summ:" + repr(result).decode("unicode_escape"))
	result_code = driver.get_ResultCode()
	result_description = driver.get_ResultDescription()
	if result_code != 0:
	    logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
	    logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	    print t_err+str(result_code)+t_off
	    print t_err+result_description+t_off
	    exit(1)

	print t_mess+"- регистрация платежа"+t_off
	result = driver.Payment()
	logging.info("pyment:" + repr(result).decode("unicode_escape"))
	result_code = driver.get_ResultCode()
	result_description = driver.get_ResultDescription()
	if result_code != 0:
	    logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
	    logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	    print t_err+str(result_code)+t_off
	    print t_err+result_description+t_off
	    exit(1)

	print t_mess+"- проверка фиксации "+t_off
	result = driver.get_ResultCode()
	logging.info("payment result code:" + repr(result).decode("unicode_escape"))
	result_code = driver.get_ResultCode()
	result_description = driver.get_ResultDescription()
	if result_code != 0:
	    logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
	    logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
	    print t_err+str(result_code)+t_off
	    print t_err+result_description+t_off
	    exit(1)

    print t_mess+"- закрытие чека "+t_off
    result = driver.CloseCheck()
    logging.info("close check:" + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code != 0:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
        print t_err+str(result_code)+t_off
        print t_err+result_description+t_off
        exit(1)

    print t_mess+"- получение номера чека "+t_off
    result = driver.get_CheckNumber()
    logging.info("check number:" + repr(result).decode("unicode_escape"))
    result_code = driver.get_ResultCode()
    result_description = driver.get_ResultDescription()
    if result_code != 0:
        logging.error("  result code:" + repr(result_code).decode("unicode_escape"))
        logging.error("  result description:" + repr(result_description).decode("unicode_escape"))
        print t_err+str(result_code)+t_off
        print t_err+result_description+t_off
        exit(1)


    exit(1)
    return res

os.environ['LD_LIBRARY_PATH'] = os.path.dirname(os.path.realpath(__file__))
logging.info(os.path.dirname(os.path.realpath(__file__)))
logging.info(os.environ)

print t_mess+"Фискализация чеков v 1.0 (c) 2018 by Pavel Gribov http://грибовы.рф"+t_off

DTO_LIB_NAME = '/opt/ATOL/drivers9/bin/libfptr.so'
VERSION = 15
driver = dto9fptr.Fptr(DTO_LIB_NAME, VERSION)

print t_mess+"Драйвер:"+t_off+str(driver)
print t_mess+"Параметры:"+t_off+str(params)

# устанавливаем параметры соединения
DeviceSetup(driver,params)
# Читаем текущие настройки
cursettings=GetCurrentParamDevice(driver)
# печатаем чек
check_data=[]

goodsa=[]
goodsa.append({"name":"Телематические услуги связи 1","price":0.01,"quiantity":1,"summ":0.01});
goodsa.append({"name":"Телематические услуги связи 2","price":0.01,"quiantity":1,"summ":0.01});

check_data.append({"num":"1","docdate":"07.05.2018","summdoc":0.02,"goods":goodsa});

print check_data
print_check(driver, check_data)

driver.Beep()


#Закончили работу
driver.put_DeviceEnabled(False)
del driver

conf.py:

params = {
    "Port": "TCPIP",
    "IPAddress": "192.168.0.99",
    "IPPort": "5555",
    "Model": "62",
    "AccessPassword": "0",
    "UserPassword": "30",
    "Protocol": "2",
    "LogFileName": "/var/log/kkt.log"
}
t_mess="\x1b[33m"
t_err="\x1b[31m"
t_ok="\x1b[32m"
t_off="\x1b[0m"

Далее осталась рутина, отключить физическую печать, доделать автоматическое открытие-закрытие смены и т.п. Но в принципе всё уже есть в https://github.com/parshin/kkt

В файле conf.py нужно прописать параметры подключения к ККТ IP адрес, порт, путь к лог файлу и т.д. Параметр «Model» смотрим в Руководстве программиста приложение 7 модели ККМ ???(у меня 47 не подошло, подошло 62, почему не понял).

VERSION = 15 — это для драйверов 9.12.1. Возможно потом нужно будет менять на 16 и т.д.

P.S. Решение применимо и для других аппаратов АТОЛ с минимальными изменениями.