Skip to main content

Python client for ATOL libfptr10 fiscal printer driver

Project description

ORD — Python-клиент драйвера АТОЛ libfptr10

Тонкая обёртка над libfptr10 (драйвер ККТ АТОЛ v10.10.x) для типовых кассовых сценариев: открытие/закрытие смены, регистрация чека продажи и возврата, чтение состояния ФН и ОФД-обмена, регистрация ККТ, чтение и запись device settings.

Используется в backend-проекте online-receipt/online-api (Flask + Celery) для фискализации чеков на физических кассах АТОЛ 30Ф, ATOL Sigma и совместимых моделях.

  • Текущая версия: 1.12.1
  • PyPI: pip install ORD
  • Источник истины: GitLab online-receipt/online-receipt-driver (этот репо)
  • Зеркало: GitHub brandshopru/online-receipt-driver
  • Стек: Python 3.6+, libfptr10 >=10.10.0
  • Лицензия: проприетарная, ООО «БШ СТОР» (brandshop.ru)

1. Установка

pip install ORD==1.12.1

Зависимость от libfptr10 (Python wrapper) ставится отдельно из дистрибутива АТОЛ kit (fptr10-rpc-server*.deb + wrappers/python/libfptr10.py).

Минимальные системные требования:

  • Python >=3.6 (3.6 в EOL, рекомендуется 3.8+)
  • libfptr10.so v10.10.x в /lib64/ или LD_LIBRARY_PATH
  • USB-устройство ATOL (Vendor:Product 2912:0005) либо TCP/IP подключение
  • При работе через atol-grpc-service — gRPC мост на 127.0.0.1:4041/:4042

2. Архитектура

2.1. Структура

ORD/
├── __init__.py        # public-экспорт классов
├── ord_core.py        # Core (Singleton, IFptr wrapper)
├── ord_cash.py        # Cash (state ККТ, регистрация)
├── ord_fn.py          # Fn (ФН-операции, ОФД-обмен)
├── ord_receipt.py     # Receipt (чек: open/registration/payment/close)
├── ord_shift.py       # Shift (open/close смены)
├── ord_setting.py     # Setting (device settings R/W)
└── exceptions.py      # OrdError, OrdTimeoutError (§6.8 + §6.9, c 1.11.0)

2.2. Класс Core (Singleton)

Core — единая точка инициализации IFptr. Реализован как Singleton через метакласс SingletonMeta с потокобезопасным threading.Lock. На процесс гарантированно один экземпляр.

Дефолтные settings при Core():

Параметр libfptr10 Значение
LIBFPTR_SETTING_MODEL LIBFPTR_MODEL_ATOL_AUTO (автоопределение)
LIBFPTR_SETTING_PORT LIBFPTR_PORT_USB
LIBFPTR_SETTING_USB_DEVICE_PATH "auto"
LIBFPTR_SETTING_OFD_CHANNEL LIBFPTR_OFD_CHANNEL_AUTO
LIBFPTR_SETTING_AUTO_TIME_SYNC True
LIBFPTR_SETTING_AUTO_TIME_SYNC_TIME 3600 (секунд, c 1.12.0; раньше было 15)

Instance-level state Core.casher_info (c 1.10.0):

core = Core(path="", casher_name="Иванов И.И.", casher_inn="500100732259")
# или сменить runtime:
core.set_casher("Петров П.П.", inn="...")

Используется в _set_casher() перед каждой фискальной операцией (setParam(1021, name), setParam(1203, inn), operatorLogin()). Если casher_name пуст — operatorLogin пропускается (logon оператора не происходит, чек/смена пишутся без тегов 1021/1203).

Жизненный цикл соединения:

  • __init__ создаёт IFptr instance, читает версию, применяет settings
  • open_connect()fptr.open() — реальный коннект к ККТ
  • close_connect()fptr.close()
  • __del__ авто-закрывает если is_opened() == 1
  • __enter__ / __exit__ (с 1.12.0) — with Core() as core: гарантирует close_connect() даже при exception между open и close

2.3. Доменные классы

Cash, Fn, Receipt, Shift, Setting — потребители Core. Все принимают один Core-экземпляр в конструктор и сохраняют self.fptr = core.fptr. Если соединение закрыто, конструктор автоматически вызывает core.open_connect().

from ORD.ord_core import Core
from ORD.ord_receipt import Receipt
from ORD.ord_fn import Fn

core = Core()  # Singleton
core.open_connect()

fn = Fn(core)
receipt = Receipt(core)
# ...
core.close_connect()

3. Типовой жизненный цикл чека

Псевдокод реального флоу из online-api/jobs/handlers.py:

core = Core()
core.open_connect()

# 1. Открыть смену (необязательно — первая фискальная операция откроет автоматически)
Shift(core).open_shift()

# 2. Открыть чек
receipt = Receipt(core)
receipt.open_receipt({
    "docType":      "SALE",                     # или "RETURN"
    "printReceipt": False,                      # электронный
    "email":        "client@example.com",
    "taxMode":      "OSN",                      # OSN / USN_INCOME / ...
})

# 3. Регистрация каждой позиции
receipt.receipt_registration({
    "name":             "Кроссовки Nike",
    "price":            10000,
    "quantity":         "1",
    "vatTag":           1102,                   # 1102 — Сумма НДС по ставке 20%
    "vat":              22,                     # ставка 22 → LIBFPTR_TAX_VAT22
    "discSum":          0,
    "nomenclatureCode": "0104680001234567...",  # КМ Data Matrix, опционально
    "codeCheck":        "UUID=...&Time=...",    # результат ПИоТ-проверки, опционально
})

# 4. Оплата
receipt.receipt_payment({"paymentType": "CARD", "sum": 10000})
# paymentType резолвится через Receipt.payment_type → LIBFPTR_PT_* (с 1.8.26.1)

# 5. Закрытие чека → ФН → ОФД
receipt.receipt_close()

# 6. Получить фискальные реквизиты
last = Fn(core).get_info_last_doc()
# → {"document_number": 123, "fiscal_sign": "1234567890", "date_time": "..."}

Закрытие смены (Z-отчёт): Shift(core).close_shift().


4. API Reference

4.1. Core

Метод Возврат Назначение
Core(path: str = '') Singleton-инициализация IFptr с дефолтными settings
open_connect() bool fptr.open() — коннект к ККТ
close_connect() bool fptr.close()
is_opened() int 0/1, см. §5.1 — не отражает реальное состояние
get_version_driver() str версия libfptr10
get_current_datetime() str "%Y-%m-%d %H:%M:%S" от ККТ
get_setting() dict текущие device settings
reboot() bool fptr.deviceReboot()
_set_casher() bool оператор тег 1021 + 1203 + operatorLogin() (приватный)
_check_document_close() bool проверка checkDocumentClosed, допечатка (приватный)
_error_log() пишет errorCode + errorDescription в журнал libfptr10
info_log(msg: str) пишет INFO в журнал libfptr10

4.2. Cash

Метод Возврат Назначение
Cash(core) Конструктор; авто-open_connect если закрыто
get_cash_info() dict 13 полей: модель, ФН, версия прошивки, состояние смены, ФФД
get_cash_info_v2() dict 31 поле: всё из v1 + флаги принтера/бумаги/ФН/блокировок
get_uptime_cash() int секунды непрерывной работы ККТ
cash_registration() bool Регистрация ККТ (МГМ). Хардкод реквизитов brandshop, см. §5.2

shift_stage mapping: 0→CLOSED, 1→OPENED, 2→EXPIRED.

4.3. Fn

Метод Возврат Назначение
Fn(core) Конструктор
get_status_fn() str FNS_INITIAL/CONFIGURED/FISCAL_MODE/POSTFISCAL_MODE/ACCESS_ARCHIVE
get_info_last_receipt() dict номер, тип, сумма, ФПД, datetime последнего чека
get_info_last_doc() dict то же для последнего фискального документа
get_version_ffd() dict device/fn/min/max версии ФФД (есть баг ключа, см. §5.8)
get_info_doc(number_doc) dict тип, номер, ФПД, datetime, has_ofd_ticket для номера ФД
get_ticket_ofd(number_doc) dict номер, datetime, OFD ФПД (bytearray) квитанции от ОФД
get_ofd_document_by_number(n) list[dict] TLV-структуры ФД (tag_number, tag_name, tag_type, tag_value)
get_fn_error() dict network / ofd / fn ошибки и их тексты
get_exchange_status() dict статус ОФД-обмена, кол-во неотправленных
get_registration_number() str РНМ ККТ (тег 1037)

4.4. Receipt

Метод Возврат Назначение
Receipt(core) Конструктор
open_receipt(receipt_data) bool Открытие чека (см. ниже)
cancel_receipt() bool fptr.cancelReceipt() — отмена незакрытого чека
receipt_registration(product) bool Регистрация одной позиции
receipt_payment(money_position) bool Оплата (paymentType через Receipt.payment_type)
receipt_total(money_position) bool Регистрация итога (необязательно)
receipt_close() bool fptr.closeReceipt() → запись в ФН

open_receipt(receipt_data) ожидает:

Ключ Тип Значения
docType str см. Receipt.receipt_type: SALE/RETURN, SALE_CORRECTION/RETURN_CORRECTION, BUY/BUY_RETURN, BUY_CORRECTION/BUY_RETURN_CORRECTION
printReceipt bool False → electronic чек (LIBFPTR_PARAM_RECEIPT_ELECTRONICALLY=True)
email str тег 1008 — email клиента
taxMode str OSN/USN_INCOME/USN_INCOME_OUTCOME/ESN/PATENT

Жёстко устанавливается тег 1125=1 («признак расчёта в интернете») — это hard-coded для интернет-магазина brandshop.

receipt_registration(product) ожидает:

Ключ Тип Назначение
name str LIBFPTR_PARAM_COMMODITY_NAME
price int/float LIBFPTR_PARAM_PRICE
quantity str LIBFPTR_PARAM_QUANTITY
vat int см. Receipt.vat_type: 0/5/7/10/20/22 + расчётные 5/105, 7/107, 10/110, 20/120, 22/122 + "NO" (без НДС)
discSum int LIBFPTR_PARAM_INFO_DISCOUNT_SUM (если > 0)
nomenclatureCode str / None КМ Data Matrix; запускает beginMarkingCodeValidation + polling + acceptMarkingCode
codeCheck str / None tag 1265 от ПИоТ; собирает TLV-тег 1260 (отраслевой реквизит предмета расчёта)
vatTag int поле в payload, но не используется в коде

Цепочка libfptr10 для маркированного товара (упрощённо):

setParam(MARKING_CODE, km)
setParam(MARKING_CODE_STATUS, 2)
setParam(MARKING_PROCESSING_MODE, 0)
setParam(MEASUREMENT_UNIT, IU_PIECE)
beginMarkingCodeValidation()
while not getParamBool(MARKING_CODE_VALIDATION_READY):
    getMarkingCodeValidationStatus()
validation_result = getParamInt(MARKING_CODE_ONLINE_VALIDATION_RESULT)
acceptMarkingCode()

# Если есть codeCheck (tag 1265 от ПИоТ) — формируется TLV-тег 1260.
# С 1.10.0 значения 1262/1263/1264 параметризованы через product['industry']:
setParam(1262, industry['mode'])      # режим обработки (например, '030')
setParam(1263, industry['date'])      # дата НПА (например, '21.11.2023')
setParam(1264, industry['num'])       # номер НПА (например, '1944')
setParam(1265, codeCheck)
utilFormTlv() → tag 1260 TLV
setParam(1260, tlv)
setParam(1212, 33)                    # признак предмета расчёта: маркированный товар + услуга

4.5. Shift

Метод Возврат Назначение
Shift(core) Конструктор
open_shift() bool _set_casher()fptr.openShift() → проверка закрытия
close_shift() bool _set_casher()report(LIBFPTR_RT_CLOSE_SHIFT) → Z-отчёт

4.6. Setting

Метод Возврат Назначение
Setting(core) Конструктор (composition, не наследуется от Core)
init_setting() bool fptr.initSettings()
get_device_setting_by_id(id, type) bool/int/str Чтение настройки ККТ (typeint/string)
set_device_setting_by_id(id, value) bool Запись настройки
commit_setting() bool fptr.commitSettings()

5. Известные ограничения и hidden behavior

5.1. Core.is_opened() не отражает реальное состояние — by design libfptr10

Из docstring: «Результат метода не отражает текущее состояние подключения — если с ККТ была разорвана связь, то метод всё также будет возвращать true». Это особенность libfptr10.isOpened() — она проверяет только локальный флаг сессии. Реально упало соединение или нет — узнаётся только на первом вызове, который вернёт LIBFPTR_ERROR_NO_CONNECTION. ORD не может это исправить — поведение задаётся нативной so-библиотекой.

5.2. Хардкод бизнес-данных — устранён в 1.9.0 и 1.10.0

Раньше в коде были захардкожены бизнес-значения:

  • Cash.cash_registration() — ИНН/адрес/ОФД brandshop. Параметризовано в 1.9.0 (registration_data: dict).
  • Receipt.receipt_registration() для маркированных товаров — теги 1262/1263/1264 = '030' / '21.11.2023' / '1944'. Параметризовано в 1.10.0 (поле industry в payload позиции).
  • Core.casher_info class-level "СИС. АДМИНИСТРАТОР". Параметризовано в 1.10.0: Core(casher_name, casher_inn) + Core.set_casher(name, inn). Дефолт пустой → _set_casher() пропускает operatorLogin.

5.3. Setting наследуется от Core — исправлено в 1.10.0

Раньше class Setting(Core) + одновременно self.core = core — двойственная связь, бьётся с SingletonMeta. С 1.10.0 — чистая композиция: class Setting: с делегированием логирования через self.core._error_log(). См. §6.6.

5.4. cash_registration пишет в FN — by design (необратимо)

Метод реально вызывает fptr.fnOperation() с LIBFPTR_FNOP_REGISTRATION — это необратимая операция перерегистрации ФН. Использовать только для первоначальной активации новой ККТ или замены ФН, никогда не дёргать в тестах. Это документированное поведение API libfptr10; защиту от случайного вызова не добавляем — драйвер должен быть тонкой обёрткой.

5.5. casher_info class-level — исправлено в 1.10.0

Core.casher_info теперь instance-level (Core(casher_name=..., casher_inn=...)

  • метод set_casher(name, inn)). См. §6.7.

5.6. Молчаливая обработка ошибок — исправлено в 1.11.0

Опционально через Core(raise_on_error=True) методы поднимают OrdError(code, description, method) вместо return False. Дефолт raise_on_error=False — backward compatible. См. §6.8.

5.7. _check_document_close infinite loop — исправлено в 1.11.0

Заменён на deadline-loop с параметром timeout: float = 30.0 и OrdTimeoutError при превышении. См. §6.9.

5.8. get_version_ffd баг ключа — исправлено в 1.12.1

В ord_fn.py:174 ключ "document_number" заменён на корректный "min_ffd_version". Теперь Fn.get_version_ffd() возвращает dict с полным набором заявленных ключей.


6. Roadmap (закрыт)

Все задачи раздела закрыты, версии указаны рядом.

  • §6.1 paymentType маппинг — в 1.8.26.1 (Receipt.payment_type dict)
  • §6.2 VAT mapping — в 1.8.27.0 (Receipt.vat_type dict, 0/5/7/10/20/22 + расчётные + NO)
  • §6.3 docType коррекции и расход — в 1.8.27.0 (Receipt.receipt_type dict с 8 типами)
  • §6.4 Cash.cash_registration параметризация — в 1.9.0. Принимает registration_data: dict (inn/address/org_name/email/place/reg_number/ofd_inn/ofd_name + tax_system/agent_type/ffd_version/tax_site/flags). Breaking change: дефолтов brandshop больше нет, ValueError при отсутствии обязательных полей.
  • §6.5 Параметризация отраслевого реквизита (тег 1260) — в 1.10.0. Receipt.receipt_registration принимает поле industry ({mode, date, num} для тегов 1262/1263/1264). Breaking change: при наличии codeCheck блок industry обязателен, иначе ValueError.
  • §6.6 Setting(Core)Setting — в 1.10.0. Убрано наследование от Core, теперь composition (self.core._error_log()), убран __del__ (Setting не владеет соединением).
  • §6.7 casher_info instance-level — в 1.10.0. Core(path, casher_name='', casher_inn='') + метод Core.set_casher(name, inn=''). Класс-атрибут с brandshop-хардкодом удалён. Backward-compat: при пустом casher_name метод _set_casher пропускает operatorLogin (фискальные операции работают без logon оператора).
  • §6.8 Опциональные исключения — в 1.11.0. Core(..., raise_on_error=True) поднимает OrdError(code, description, method) вместо тихого return False. Helper Core._handle_error(method) объединяет log + raise/return. Дефолт raise_on_error=False — backward compatible.
  • §6.9 Timeout в _check_document_close — в 1.11.0. Бесконечные while ... < 0: continue заменены на deadline-loop с параметром timeout: float = 30.0. При превышении — OrdTimeoutError (всегда, независимо от raise_on_error).
  • §6.10 Тесты — в 1.11.0. Структура tests/unit/*.py с 17 unit-тестами (Core/Receipt/Cash, ~70% покрытия публичного API). libfptr10.IFptr подменяется MagicMock'ом в conftest.py — тесты идут без реальной кассы. Dev-зависимости в requirements-dev.txt. Integration-тесты против реальной АТОЛ 30Ф — отдельным этапом.
  • §6.11 pyproject.toml — в 1.12.0. Перевели проект на PEP 517 build (setuptools.build_meta). setup.py + setup.cfg удалены, метаданные в pyproject.toml. Сборка через python -m build.
  • §6.12 Type hints (частично) — в 1.12.0. Добавлены return-аннотации публичным методам Core/Cash/Fn/Receipt/Shift/Setting и type hints параметров. TypedDict для CashInfo/FnDocInfo — не делали (низкий приоритет, оставляем dict).
  • §6.16 Контекст-менеджеры — в 1.12.0. Core.__enter__/__exit__with Core(...) as core: гарантирует close_connect() даже при exception между open и close.
  • §6.17 Thread-safety — в 1.12.0. Документировано «один процесс, один поток» как требование. Добавлен Core._operation_lock = threading.Lock() для опционального оборачивания критической секции извне: with core._operation_lock: .... Каждый публичный метод НЕ оборачивается мьютексом, чтобы не платить за однопоточные сценарии.
  • §6.18 AutoTimeSyncTime — в 1.12.0. LIBFPTR_SETTING_AUTO_TIME_SYNC_TIME = 3600 (раз в час, было 15с). Компромисс между «реже трогаем ФН» и «успеваем поймать drift».
  • §6.19 Стандартный logging — в 1.12.0. Добавлен logging.getLogger("ORD"), ошибки и info-сообщения теперь пишутся и в журнал libfptr10, и в системный logger приложения.
  • §6.20 Документация кода (частично) — в 1.12.0. Docstrings главных методов Core/Receipt приведены к единому стилю (Russian-comment + :param/:return), новые публичные методы (_handle_error, set_casher, __enter__/__exit__) задокументированы. Полное прохождение по Fn/Cash — оставляем (низкий приоритет).
  • §6.21 Опечатка cancel_reciept.py — файл не существует (был удалён ранее). Закрываем без изменений.
  • §6.22 .gitignore — в 1.11.0 (__pycache__/, *.pyc, *.egg-info/, dist/, build/, .pytest_cache/, venv/).

7. Версионирование и релизы

Используется semver: MAJOR.MINOR.PATCH[.HOTFIX].

  • MAJOR — несовместимое API изменение
  • MINOR — новый функционал, обратно совместимый
  • PATCH — багфиксы

Процесс релиза:

  1. Внести изменения в feature-ветке
  2. Merge в master после ревью
  3. Bump версии в setup.py (потом — в pyproject.toml)
  4. Поставить tag: git tag X.Y.Z && git push origin --tags
  5. Сборка: python -m build
  6. Публикация: twine upload dist/*

Текущая ситуация (на 2026-06-25):

Источник Версия master
Локально (IdeaProjects/online-receipt-driver) 1.8.26.0
GitLab origin (master) 1.8.26.0
GitHub зеркало (master) 1.8.26.0
PyPI ORD 1.8.26.0
Production: online-api/venv на cashdev 1.8.26.0

Все источники синхронизированы.

История версий:

  • 1.8.x — текущая линия (НДС 22%, AutoTimeSync, разрешительный режим 030)
  • 1.6.x (ветка legacy-1.6.x в GitLab) — заброшенная ветка с устаревшим API, оставлена для исторической справки
  • denis — старая ветка экспериментов, не мерджена

8. Тестирование

До настоящего момента тесты для ORD делались только в проекте-потребителе online-api (см. online-api/tests/unit/test_piot_client_mock.py, tests/e2e/test_fn_real.py). Прогон против реальной АТОЛ 30Ф на cashdev:

ssh volodya@cashdev 'cd /var/www/api/current && \
  ./venv/bin/pytest tests/e2e/ -m e2e -v'

После §6.10 тесты переедут в этот репо.


9. Контакты

  • Автор: Vladimir Smirnov (volodya@brandshop.ru)
  • Баги/MR: GitLab online-receipt/online-receipt-driver

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ord-1.12.1.tar.gz (35.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ord-1.12.1-py3-none-any.whl (30.5 kB view details)

Uploaded Python 3

File details

Details for the file ord-1.12.1.tar.gz.

File metadata

  • Download URL: ord-1.12.1.tar.gz
  • Upload date:
  • Size: 35.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for ord-1.12.1.tar.gz
Algorithm Hash digest
SHA256 8578854f6ee786e8b34794c6e7bf31200e591266c4727b50e0e238db1e0a4614
MD5 87995e807b9e67dc4c693c8c7e4b3d7e
BLAKE2b-256 91151404abe0bff47c1bc6fd938931c550229e6cb880534b6e41d07c2173d3fd

See more details on using hashes here.

File details

Details for the file ord-1.12.1-py3-none-any.whl.

File metadata

  • Download URL: ord-1.12.1-py3-none-any.whl
  • Upload date:
  • Size: 30.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for ord-1.12.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7c93cbce9ca8c2c046506219dcf6f16b95e5ab35b5e080da6dc003196dad8822
MD5 c00a1cb1fb8799f049528541f7a6afc5
BLAKE2b-256 89822b307aaa917e719b244f350fda285261a0cf2c68c750c6d164e52755a4cc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page