Пример создание http сервера для работы с различным торговым оборудованием.
В статье рассматривается пример для фр — ККТ "Касса №1" ("К1-Ф").
Задача:
Есть bitrix-сайт(1С Битрикс). Подключили эквайринг СБ, купили фр К1-Ф, нужно связать.
Отступление:
1. Да я знаю, что эквайринг СБ совместно с ГК Атол предоставляет возможность пробивать чеки.(Проектировал не я, что имеем, то имеем (bitrix-сайт, php-модуль для СБ, всё это на хостинге, с одной стороны, и с другой где-то в офисе воткнута в сеть К1-Ф)).
2. Хотя и у К1-Ф есть web админка, я не нашел доков по http методам, искал здесь:
http://www.kassa.astralnalog.ru/docs, http://v8.1c.ru/to/k1f/, на сайте производителя, итс и тд.
3. Да я знаю, как можно сделать в модуле php get/post и тд, сделано все таки разделение: php пишет в базу об оплате, python читает, при добавлении новых пробивает чек. Отслеживание происходит через запрос в бд источника с передачей в параметре последнего ид, и дальнейшего прохода по выборке для пробития.
В статье рассматриваются следующие компоненты:
1. Создание эмулятора интерфейса команд ФР (нужен для теста, и вообще понять какой формат обмена)
2. Создание Web(HTTP) сервера для работы c ФР (фактически проксирование с HTTP на TCP, + некоторая инкапсуляция методов для работы, об этом ниже)
3. Разработка клиентской части (пример чтения таблиц mysql(источник на стороне хоста), пример пробития, пример записи результата в sql lite(приемник на стороне офиса))
Шаг 0. Протокол обмена ФР.
Обмен с ФР проходит по TCP(ФР — TCP сервер), по заданному формату. Формат — JSON(UTF-8), структура сообщения — первые 4 байта длина сообщения, с 5-го — сообщение.
Структура команд будет описана ниже.
Шаг 1. Эмулятор ФР.
from socketserver import TCPServer, ThreadingMixIn, BaseRequestHandler
from datetime import datetime, date
from protocol import do_command
class ThreadedTCPServer(ThreadingMixIn, TCPServer):
pass
class K1FSocketServerHandler(BaseRequestHandler):
def handle(self):
self.callback(self.server, self.request, self.client_address)
class K1FSocketServer():
handler = K1FSocketServerHandler
def __init__(self):
self.handler.callback = self.callback
def callback(self, server, request, client_address):
print('')
while True:
try:
buf = request.recv(8192)
except:
break
if not buf:
break
res = do_command(buf)
request.sendall(res)
print('')
TCP_IP = '0.0.0.0'
TCP_PORT = 49152
if __name__ == '__main__':
ones_serv = K1FSocketServer()
server = ThreadedTCPServer((TCP_IP, TCP_PORT), K1FSocketServerHandler)
print('starting k1f socket server '+str(TCP_IP)+':'+str(TCP_PORT)+' (use <Ctrl-C> to stop)')
server.serve_forever()
TCP сервер, описание по основным моментам делал в прошлой статье.
import json
OFSSET = 4
RESPONSE = {}
RESPONSE['OpenSession'] = """{"result": 0,
"description": "Успешно завершено",
"sessionKey": "CAC1A797-6A48-474A-A08E-72A8CD3AEFE2",
"protocolVer": "1.0"}"""
RESPONSE['CloseSession'] = """{"result": 0,
"description": "Успешно завершено"}"""
RESPONSE['GetCommonInfo'] = """{"result": 0,
"description": "Успешно завершено",
"model": "Название модели ККТ",
"kktNum": "123456789012",
"fnNum": "9908176526",
"ffdFnVer": "1.0.5",
"ffdKktVer": "1.0",
"mac": "45:F2:0D:73:37:00",
"programVer": "3.1",
"programDate": "2024-09-04",
"protocolVer": "3.0",
"dateTime": "2024-10-01T09:15:43",
"crc": "C5AD",
"cpl": [21, 10],
"dpl": 240,
"maxGoodsSum": 999999999,
"maxCheckSum": 999999999,
"maxGoodsQty": 99}"""
RESPONSE['GetRegistrationInfo'] = """{"isRegistered": true,
"registrationInfo": {
"dateTime": "2024-10-03T12:35:27",
"owner": {
"inn": "3890473625"
},
"kkt": {
"regNum": "123456789012",
"mode": {
"encryptData": true,
"offline": false,
"forService": true,
"ASBSO": false
}
},
"ofd": {
"inn": "4029017981",
"url": "ofd.astralnalog.ru",
"port": 7779
},
"taxSystem": [0, 1, 3],
"reason": 2}}"""
RESPONSE['GetDateTime'] = """{"result": 0,
"description": "Успешно завершено",
"dateTime": "2024-10-01T09:15:43"}"""
RESPONSE['GetStatus'] = """{"result": 0,
"description": "Успешно завершено",
"dateTime": "2024-10-01T09:15:43",
"shiftInfo": {
"isOpen": true,
"is24Expired": false,
"num": 17,
"lastOpen": "2024-09-30T08:10:15",
"cash": 500680,
"check0": {
"qty": 15,
"sum": 500680
},
"check1": {
"qty": 0,
"sum": 0
},
"check2": {
"qty": 0,
"sum": 0
},
"check3": {
"qty": 0,
"sum": 0
},
"check4": {
"qty": 0,
"sum": 0
},
"check5": {
"qty": 0,
"sum": 0
},
"bring": {
"qty": 0,
"sum": 0
},
"withdraw": {
"qty": 0,
"sum": 0
}
},
"checkInfo": {
"isOpen": true,
"num": 4,
"goodsQty": 8,
"sum": 12534
},
"fnInfo": {
"status": 1,
"lastDoc": {
"num": 60,
"dateTime": "2024-10-01T09:10:24"
},
"unsignedDocs": {
"qty": 1,
"firstNum": 61,
"firstDateTime": "2024-10-01T09:13:37"}}}"""
RESPONSE['OpenShift'] = """{"result": 0,
"description": "Успешно завершено",
"shiftNum": 125,
"fiscalDocNum": 1153,
"fiscalSign": "1189046352"}"""
RESPONSE['CloseShift'] = """{"result": 0,
"description": "Успешно завершено",
"shiftNum": 125,
"fiscalDocNum": 1153,
"fiscalSign": "1189046352"}"""
RESPONSE['OpenCheck'] = """{"result": 0,
"description": "Успешно завершено",
"shiftNum": 125,
"checkNum": 18}"""
RESPONSE['CloseCheck'] = """{"result": 0,
"description": "Успешно завершено",
"shiftNum": 125,
"checkNum": 18,
"fiscalDocNum": 1173,
"fiscalSign": "1189046352",
"checkUrl": "nalog.ru",
"changeSum": 4000}"""
RESPONSE['AddGoods'] = """{"result": 0,
"description": "Успешно завершено",
"shiftNum": 235,
"checkNum": 18,
"goodsNum": 2,
"taxSum": 0,
"extraSum": -116,
"goodsSum": 3134,
"checkSum": 5392}"""
RESPONSE['PrintReport'] = """{"result": 0,
"description": "Успешно завершено"}"""
RESPONSE['ResetCheck'] = """{"result": 0,
"description": "Успешно завершено"}"""
def do_command(data):
data = data[OFSSET:].decode('utf-8')
data = data.rstrip('x00')
res = json.loads(data)
print('->')
print(res)
if res.get('command') is None:
return b''
message = RESPONSE.get(res['command'])
if message is None:
return b''
print('<-')
print(message)
message = message.encode('utf-8')
len_message = len(message)
len_message = len_message.to_bytes(OFSSET, byteorder='big', signed=True)
return len_message + bytes(message)
RESPONSE — соответствие имя команды — структура ответа. Формирование сообщения к отправке делается так:
message = message.encode('utf-8')
len_message = len(message)
len_message = len_message.to_bytes(OFSSET, byteorder='big', signed=True)
return len_message + bytes(message)
to_bytes — инт в байт с учетом оффсета(у нас 4 первых из протокола)
len_message + bytes(message) — само сообщение в байтах.
От производителя шла утилита работы.
Пример работы:
Шаг 2. HTTP апи сервер.
В качестве web сервера используется flask описание по основным моментам делал в прошлой статье.
from server.conf import *
from server import app
if __name__ == '__main__':
app.run(API_SERVER, API_PORT, threaded=True)
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app, support_credentials=True)
import server.views
API_SERVER = '0.0.0.0'
API_PORT = 8081
OFSSET = 4
SERVER = ('192.168.1.99', 49152)
LOGIN = 'LOGIN'
PASSWORD = 'PASSWORD'
CON_PASSWORD = 'CON_PASSWORD'
BUF_SIZE = 8192
CHECK_TYPE = 0
TAX_SYSTEM = 0
OPER_LIST = []
OPER_LIST.append('OpenSession')
OPER_LIST.append('GetCommonInfo')
OPER_LIST.append('GetRegistrationInfo')
OPER_LIST.append('GetStatus')
OPER_LIST.append('CloseSession')
OPER_LIST.append('OpenShift')
OPER_LIST.append('CloseShift')
OPER_LIST.append('OpenCheck')
OPER_LIST.append('CloseCheck')
OPER_LIST.append('AddGoods')
PRINT_DOC = False
SERVER = (‘192.168.1.99’, 49152) — адреспорт фр
LOGIN = ‘LOGIN’ — по дефолту admin
PASSWORD = ‘PASSWORD’ — по дефолту 12
CON_PASSWORD = ‘CON_PASSWORD’ — меняется в админке
OPER_LIST — массив реализованных операций фр
from flask import render_template, request, jsonify
import server.fisc_reg_exchange as fisc_reg
from server.conf import *
from server import app
import json
HEADERS = {"Content-type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Expose-Headers": "Access-Control-Allow-Origin",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"}
@app.route('/')
@app.route('/index')
def index():
return """it's k1-f server api""", 200, HEADERS
@app.errorhandler(Exception)
def handle_invalid_usage(error):
str_error = "Exception: %s" % error
print(str_error)
response = jsonify(str_error)
response.status_code = 500
response.content_type = 'text/plain; charset=utf-8'
return response
@app.route('/get_common_info', methods=['GET'])
def get_common_info():
sock = fisc_reg.init_oper()
if sock is None:
raise Exception('Нет связи')
res = fisc_reg.do_oper('OpenSession', sock, session_key=None)
if res.get('sessionKey') is None:
raise Exception('session_key is None')
session_key = res['sessionKey']
res = fisc_reg.do_oper('GetCommonInfo', sock, session_key=session_key)
res = json.dumps(res, default=json_serial)
fisc_reg.do_oper('CloseSession', sock, session_key=session_key)
return res, 200, HEADERS
@app.route('/get_registration_info', methods=['GET'])
def get_registration_info():
sock = fisc_reg.init_oper()
if sock is None:
raise Exception('Нет связи')
res = fisc_reg.do_oper('OpenSession', sock, session_key=None)
if res.get('sessionKey') is None:
raise Exception('session_key is None')
session_key = res['sessionKey']
res = fisc_reg.do_oper('GetRegistrationInfo', sock, session_key=session_key)
res = json.dumps(res, default=json_serial)
fisc_reg.do_oper('CloseSession', sock, session_key=session_key)
return res, 200, HEADERS
@app.route('/get_status', methods=['GET'])
def get_status():
sock = fisc_reg.init_oper()
if sock is None:
raise Exception('Нет связи')
res = fisc_reg.do_oper('OpenSession', sock, session_key=None)
if res.get('sessionKey') is None:
raise Exception('session_key is None')
session_key = res['sessionKey']
res = fisc_reg.do_oper('GetStatus', sock, session_key=session_key)
res = json.dumps(res, default=json_serial)
fisc_reg.do_oper('CloseSession', sock, session_key=session_key)
return res, 200, HEADERS
@app.route('/open_shift', methods=['GET'])
def open_shift():
sock = fisc_reg.init_oper()
if sock is None:
raise Exception('Нет связи')
res = fisc_reg.do_oper('OpenSession', sock, session_key=None)
if res.get('sessionKey') is None:
raise Exception('session_key is None')
session_key = res['sessionKey']
res = fisc_reg.do_oper('OpenShift', sock, session_key=session_key)
res = json.dumps(res, default=json_serial)
fisc_reg.do_oper('CloseSession', sock, session_key=session_key)
return res, 200, HEADERS
@app.route('/close_shift', methods=['GET'])
def close_shift():
sock = fisc_reg.init_oper()
if sock is None:
raise Exception('Нет связи')
res = fisc_reg.do_oper('OpenSession', sock, session_key=None)
if res.get('sessionKey') is None:
raise Exception('session_key is None')
session_key = res['sessionKey']
res = fisc_reg.do_oper('CloseShift', sock, session_key=session_key)
res = json.dumps(res, default=json_serial)
fisc_reg.do_oper('CloseSession', sock, session_key=session_key)
return res, 200, HEADERS
@app.route('/make_payment', methods=['POST'])
def make_payment():
body = request.data.decode('utf-8-sig')
data = json.loads(body)
sock = fisc_reg.init_oper()
if sock is None:
raise Exception('Нет связи')
res = fisc_reg.do_oper('OpenSession', sock, session_key=None)
if res.get('sessionKey') is None:
raise Exception('session_key is None')
session_key = res['sessionKey']
res_status = fisc_reg.do_oper('GetStatus', sock, session_key=session_key)
if res_status['shiftInfo']['is24Expired'] == True:
res = fisc_reg.do_oper('CloseShift', sock, session_key=session_key)
if res['result'] != 0:
raise Exception('CloseShift error: ' + str(res))
if res_status['shiftInfo']['isOpen'] != True:
res = fisc_reg.do_oper('OpenShift', sock, session_key=session_key)
if res['result'] != 0:
raise Exception('OpenShift error: ' + str(res))
res = fisc_reg.do_oper('OpenCheck', sock, session_key=session_key)
if res['result'] != 0:
raise Exception('OpenCheck error: ' + str(res))
for x in data['goods']:
code = x['code']
id = x['id']
name = x['name']
qty = x['qty']
price = x['price']
extra_type = x['extra_type']
extra_value = x['extra_value']
section = x['section']
tax_code = x['tax_code']
payment_form_code = x['payment_form_code']
res = fisc_reg.do_oper('AddGoods', sock, session_key=session_key, code=code, id=id, name=name, qty=qty, section=section, tax_code=tax_code, payment_form_code=payment_form_code, price=price, extra_type=extra_type, extra_value=extra_value)
if res['result'] != 0:
raise Exception('AddGoods error: ' + str(res))
client = data['client']
add_info = data['add_info']
cash = data['cash']
ecash = data['ecash']
prepayment = data['prepayment']
credit = data['credit']
consideration = data['consideration']
res = fisc_reg.do_oper('CloseCheck', sock, session_key=session_key, client=client, add_info=add_info, cash=cash, ecash=ecash, prepayment=prepayment, credit=credit, consideration=consideration)
if res['result'] != 0:
raise Exception('CloseCheck error: ' + str(res))
res = json.dumps(res, default=json_serial)
fisc_reg.do_oper('CloseSession', sock, session_key=session_key)
return res, 200, HEADERS
def json_serial(obj):
from datetime import datetime, date
import decimal
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, decimal.Decimal):
return float(obj)
pass
Енд поинты апи.
Итак, перед тем как отправить команду на фр(например пробитие чека) нужно:
1. Установить tcp соединение ("физическое соединение")
2. Получить ключ сеанса ("логическое соединение", установить сессию)
3. Выполнить команду (пробитие чека)
4. Разорвать, закрыть сессию ("логическое соединение")
Выше было упомянуто про инкапсуляцию, имелось ввиду вот что:
Для пробития чека, этап(3) (метод в коде "make_payment") логически выглядит как:
1. Получить статус фр
2. Если смена превысила 24 час, тогда закрыть
3. Если смена не открыта, тогда открыть
4. Открыть чек
5. Добавить позиции
6. Закрыть чек
при этом все выполняется в рамках одной сессии.
make_payment — принимает в теле пост запроса структуру JSON (о ней ниже).
server/fisc_reg_exchange.py
import json
from server.conf import *
def message_to_byte(message):
message = json.dumps(message)
message = message.encode('utf-8')
len_message = len(message)
len_message = len_message.to_bytes(OFSSET, byteorder='big', signed=True)
return len_message + bytes(message)
def send_data(message, sock):
mes = message_to_byte(message)
sock.send(mes)
data = sock.recv(BUF_SIZE)
data = data[OFSSET:].decode('utf-8')
res = json.loads(data)
print(res)
print('')
return res
def init_oper():
import socket
sock = None
try:
sock = socket.socket()
sock.connect(SERVER)
except Exception as e:
raise Exception('Socket error ' + str(e))
return None
return sock
def do_oper(oper, sock, **data):
res = None
if not oper in OPER_LIST:
raise Exception('Operation is not defined' + str(oper))
session_key = data['session_key']
d = {'sessionKey': session_key, 'command': oper}
if oper == 'OpenSession':
d['connectionPassword'] = CON_PASSWORD
d['login'] = LOGIN
d['password'] = PASSWORD
elif oper == 'OpenCheck':
d['checkType'] = CHECK_TYPE
d['taxSystem'] = TAX_SYSTEM
d['printDoc'] = PRINT_DOC
elif oper == 'CloseCheck':
d['sendCheckTo'] = data['client']
d['addInfo'] = data['add_info']
payment = {'cash':data['cash'], 'ecash':data['ecash'], 'prepayment':data['prepayment'], 'credit':data['credit'], 'consideration':data['consideration']}
d['payment'] = payment
elif oper == 'AddGoods':
d['nomenclatureCode'] = data['code']
d['productID'] = data['id']
d['productName'] = data['name']
d['qty'] = data['qty']
d['section'] = data['section']
d['taxCode'] = data['tax_code']
d['paymentFormCode'] = data['payment_form_code']
d['price'] = data['price']
extra = {'type': data['extra_type'], 'value': data['extra_value']}
d['extra'] = extra
res = send_data(d, sock)
return res
Дозаполнение полей, приведение к формату для работы с ФР, ну типа (d[‘paymentFormCode’] = data[‘payment_form_code’] и тд).
TCP клиент:
sock = socket.socket()
sock.connect(SERVER)
+ код для формирования сообщения.
import requests
import json
server = '192.168.1.99'
port = 8081
method = 'get_common_info'
r = requests.get(f"""http://{server}:{port}/{method}""")
print(r)
print(r.text)
method = 'get_registration_info'
r = requests.get(f"""http://{server}:{port}/{method}""")
print(r)
print(r.text)
method = 'get_status'
r = requests.get(f"""http://{server}:{port}/{method}""")
print(r)
print(r.text)
method = 'open_shift'
r = requests.get(f"""http://{server}:{port}/{method}""")
print(r)
print(r.text)
method = 'close_shift'
r = requests.get(f"""http://{server}:{port}/{method}""")
print(r)
print(r.text)
data = {}
data['client'] = 'test@mail.ru'
data['add_info'] = 'оплата по заказу номер 100'
data['cash'] = 0
data['ecash'] = 100
data['prepayment'] = 0
data['credit'] = 0
data['consideration'] = 0
data['goods'] = []
row_goods = {}
row_goods['code'] = '001'
row_goods['id'] = '001'
row_goods['name'] = 'услуги'
row_goods['qty'] = 1
row_goods['price'] = 100
row_goods['extra_type'] = 0
row_goods['extra_value'] = 0
row_goods['section'] = 1
row_goods['tax_code'] = 1
row_goods['payment_form_code'] = 0
data['goods'].append(row_goods)
data = json.dumps(data)
method = 'make_payment'
r = requests.post(f"""http://{server}:{port}/{method}""", data=data)
print(r)
print(r.text)
p = input()
Пример для тестов(клиент)
Пример работы:
Шаг 3. Клиент.
import pymysql.cursors
import time
import requests
import json
import sqlite3
from sqlite3 import Error
from datetime import datetime, date
from secret import *
T_SLEEP = 60
API_SERVER = '192.168.1.99'
API_PORT = 8081
API_MAKE_PAYMENT = 'make_payment'
DB_LOCAL = 'operations.db'
def get_data_source(last_id):
try:
connection = pymysql.connect(host=DB_HOST, user=DB_USER, password=DB_PASS, db=DB, charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)
except Exception as e:
print(e)
return None
cursor = connection.cursor()
query_text = f"SELECT b_tszh_payment.*, b_user.* FROM b_tszh_payment LEFT JOIN b_user ON b_tszh_payment.USER_ID = b_user.ID WHERE b_tszh_payment.ID > {last_id}"
try:
res = cursor.execute(query_text, )
except Exception as e:
print(e)
return None
result = cursor.fetchall()
cursor = None
connection.close()
return result
def get_last_id():
try:
connection = sqlite3.connect(DB_LOCAL)
except Error as e:
print(e)
return None
cursor = connection.cursor()
query_text = f"SELECT last_id FROM operations ORDER BY last_id DESC LIMIT 1"
try:
res = cursor.execute(query_text, )
except Exception as e:
print(e)
return None
result = cursor.fetchall()
cursor = None
connection.close()
if len(result) == 0:
return 0
return result[0][0]
def set_result(res, x):
try:
connection = sqlite3.connect(DB_LOCAL)
except Error as e:
print(e)
return None
source_data = json.dumps(res, default=json_serial)
fisc_reg_data = json.dumps(x, default=json_serial)
last_id = x['ID']
fisc_reg_doc = res['fiscalDocNum']
cursor = connection.cursor()
rows = [(source_data, fisc_reg_data, last_id, fisc_reg_doc)]
cursor.executemany('INSERT INTO operations values (?,?,?,?)', rows)
connection.commit()
cursor = None
connection.close()
return None
def make_payment(client, summ, add_info):
data = {}
data['client'] = client
data['add_info'] = add_info
data['cash'] = 0
data['ecash'] = summ
data['prepayment'] = 0
data['credit'] = 0
data['consideration'] = 0
data['goods'] = []
row_goods = {}
row_goods['code'] = '001'
row_goods['id'] = '001'
row_goods['name'] = add_info
row_goods['qty'] = 1
row_goods['price'] = summ
row_goods['extra_type'] = 0
row_goods['extra_value'] = 0
row_goods['section'] = 1
row_goods['tax_code'] = 1
row_goods['payment_form_code'] = 0
data['goods'].append(row_goods)
data = json.dumps(data, default=json_serial)
r = requests.post(f"""http://{API_SERVER}:{API_PORT}/{API_MAKE_PAYMENT}""", data=data)
print('API_SERVER response:')
print(r)
print(r.text)
if r.status_code == 200:
res = json.loads(r.text)
return res
return None
def do_loop():
print('')
print(datetime.now())
last_id = get_last_id()
if last_id is None:
return None
result = get_data_source(last_id)
if result is None:
return None
print('new records ' + str(len(result)))
if len(result) == 0:
return None
for x in result:
print(x)
res = make_payment(x['EMAIL'], x['SUMM_PAYED'], x['C_ADDRESS'])
if not res is None:
set_result(res, x)
pass
def json_serial(obj):
from datetime import datetime, date
import decimal
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, decimal.Decimal):
return float(obj)
pass
while True:
do_loop()
time.sleep(T_SLEEP)
connection = pymysql/sqlite3.connect(… — установка соединения с базой источником
res = cursor.execute(query_text, ) — выполнение запроса
cursor.executemany(‘INSERT INTO operations values (?,?,?,?)’, rows) — вставка в базу приемник
остальное было выше
BEGIN TRANSACTION;
CREATE TABLE "operations" (
`source_data` TEXT NOT NULL,
`fisc_reg_data` TEXT,
`last_id` INTEGER,
`fisc_reg_doc` INTEGER
);
INSERT INTO `operations` VALUES ('',NULL,1235,NULL);
INSERT INTO `operations` VALUES ('{"result": 0, "description": "u0423u0441u043fu0435u0448u043du043e u0437u0430u0432u0435u0440u0448u0435u043du043e", "shiftNum": 125, "checkNum": 18, "fiscalDocNum": 1173, "fiscalSign": "1189046352", "checkUrl": "nalog.ru", "changeSum": 4000}','{"ID": 1236, "LID": "s1", "PAYED": "N", "DATE_PAYED": null, "EMP_PAYED_ID": null, "SUMM": 12848.03, "SUMM_PAYED": 0.0, "CURRENCY": "RUB", "USER_ID": 128, "PAY_SYSTEM_ID": 2, "DATE_INSERT": "2024-06-30T09:26:37", ...88", "LANGUAGE_ID": null}',1236,1173);
INSERT INTO `operations` VALUES ('{"result": 0, "description": "u0423u0441u043fu0435u0448u043du043e u0437u0430u0432u0435u0440u0448u0435u043du043e", "shiftNum": 125, "checkNum": 18, "fiscalDocNum": 1173, "fiscalSign": "1189046352", "checkUrl": "nalog.ru", "changeSum": 4000}','{"ID": 1237, "LID": "s1", "PAYED": "N", "DATE_PAYED": null, "EMP_PAYED_ID": null, "SUMM": 12848.03, "SUMM_PAYED": 0.0, "CURRENCY": "RUB", "USER_ID": 128, "PAY_SYSTEM_ID": 2, "DATE_INSERT": "2024-06-30T09:26:46", "DATE_UPDATE": "2024-06-30T09:26:...4cu0435u0432u0438u0447", "CONFIRM_CODE": null, "LOGIN_ATTEMPTS": 0, "LAST_ACTIVITY_DATE": null, "AUTO_TIME_ZONE": null, "TIME_ZONE": null, "TIME_ZONE_OFFSET": 0, "TITLE": null, "BX_USER_ID": "aa51cf01e427ee3481364316b2f89088", "LANGUAGE_ID": null}',1237,1173);
INSERT INTO `operations` VALUES ('{"result": 0, "description": "u0423u0441u043fu0435u0448u043du043e u0437u0430u0432u0435u0440u0448u0435u043du043e", "shiftNum": 125, "checkNum": 18, "fiscalDocNum": 1173, "fiscalSign": "1189046352", "checkUrl": "nalog.ru", "changeSum": 4000}','{"ID": 1238, "LID": "s1", "PAYED": "N", "DATE_PAYED": null, "EMP_PAYED_ID": null, "SUMM": 12848.03, "SUMM_PAYED": 0.0, "CURRENCY": "RUB", "USER_ID": 128, "PAY_SYSTEM_ID": 2, "DATE_INSERT": "2024-06-30T09:34:38", "DATE_U...LANGUAGE_ID": null}',1238,1173);
CREATE INDEX `idx_last_id` ON `operations` (`last_id` DESC)
;
COMMIT;
Структура базы приемника
Пример работы всех компонентов вместе:
для сервера
#!/bin/sh
screen -dmUS k1f_fisc_reg_api_server python3.5 main.py
для клиента
#!/bin/sh
screen -dmUS b_tszh python3.5 main.py
Так же возможно писать не в tcp порт, а на пример в com, в pipe, в блочное устройство(файл в терминах linux)