Сразу главное, чтобы дальше читали правильно.
У нас нет ни одной работающей точки. Ни одного магазина, ни одного внедрения, ни одного живого кассира. Мы разрабатываем, а не эксплуатируем.
Продукт не проходил нормального тестирования. Есть 3585 автотестов, и они зелёные — но зелёный набор проверяет ровно то, что разработчик догадался проверить. Ни один незнакомый человек не пытался поставить это с нуля и провести смену.
Багов там гора. Не «пара шероховатостей» — гора, и часть из них мы назовём поимённо ниже.
Именно поэтому мы и выкладываем код. Нам нужны не пользователи. Нам нужны QA — те, кто попробует это сломать, потому что сами мы уже не можем: мы слишком хорошо знаем, куда не нажимать.
Что это такое
Торговая система, которая работает на кассе и вокруг неё. Один исполняемый файл, четыре режима работы, шесть платформ.
| Режим | Про что |
|---|---|
| Розница | Касса: продажа, возврат, смена, деньги, скидки, лояльность |
| Ресторан | Столы, открытые заказы, модификаторы, техкарты, раздельный счёт |
| Сервис | Полный цикл заказа: от прачечной до сервисного центра |
| Склад (WMS) | Адресное хранение: ячейки, партии, серийные номера, претензии |
Это не четыре сборки и не четыре продукта. Одна программа, у которой режим — свойство точки, а не флаг компиляции. Магазин с кухней включает два режима и работает в обоих одновременно.
На чём написано
| Слой | Чем |
|---|---|
| Интерфейс и логика | Flutter / Dart, один код на все платформы |
| База | SQLite через drift: 91 таблица, схема версии 36 |
| Состояние | Riverpod |
| Внедрение зависимостей | get_it + injectable |
| Навигация | go_router с вложенными оболочками |
| Деньги | package:decimal, точность 18, масштаб 3 |
| Провод до браузера | QUIC / WebTransport поверх HTTP/3 |
| Нативный слой | Rust, отдельными пакетами через FFI |
| Telegram | TDLib через libtdjson |
Платформы: Windows, Linux, macOS, Android, iOS, веб. Готовые сборки под Windows и Ubuntu лежат в релизах, остальные собираются из исходников.
Языков пять: русский, английский, казахский, киргизский, узбекский.

экран продажи, светлая тема

тот же экран в тёмной теме
Обе темы повторяют Telegram Desktop: светлая — его дневную палитру, тёмная — ночную, ту самую синеватую, а не нейтрально-серую. Боковая колонка сворачивается в иконки и по умолчанию свёрнута; на тёмном снимке развёрнута, чтобы были видны разделы. Иконки свои — 33 глифа, собранные собственным конвейером, потому что Material рядом с телеграмным интерфейсом выглядит чужим.
Касса: розница
Продажа
Поиск товара по названию, коду, штрихкоду, псевдониму и коду маркировки. Быстрые товары плитками. Универсальный товар со свободной ценой. Комплекты и наборы. Весовой товар с приёмом веса прямо с весов. Штучный, весовой и разливной учёт по единицам измерения.
В чеке: изменение количества и цены построчно, удаление и отмена позиции с сохранением следа, отложенный чек и возврат к нему, частичное изъятие из чека, произвольные дополнительные реквизиты, привязка клиента.
Запреты, которые настраиваются: блокировка продажи по категории и по товару, запрет продажи ниже цены кассы, округление итога по правилам точки.
Оплата
Наличные с расчётом сдачи и подсказкой номиналов. Карта. Смешанная оплата несколькими способами в одном чеке. Бонусы. Продажа в долг с лимитом. Kaspi POS. Платёжный терминал как устройство рабочего места — с сохранением кода авторизации, маски карты и номера транзакции.
Отдельно: погашение долга клиентом, аванс клиента, покупка за наличные как расход из кассы.

оплата, смешанный платёж
Возвраты
Возврат по чеку продажи, в том числе частичный по позициям. Проверка допустимости. Возврат бонусов с откатом начислений. Возврат оплат по способам. Возврат маркированного товара с кодами. Отдельная нумерация чеков возврата и свои правила округления.
Смена и деньги кассы
Открытие и закрытие смены, разменный фонд, X- и Z-отчёты. Внесение и изъятие. Инкассация как перевод между счетами. Расходы по типам: прочее, закуп, зарплата, коммунальные, инкассация. Инвестиции и дивиденды как операции с кассой. Чек кассовой операции с произвольными полями. История смен, сводка по способам оплаты, недостача и излишек при закрытии.
Отдельная вещь, которую обычно узнают дорого: смена переживает копирование базы. Любой код, копирующий или восстанавливающий базу, обязан сначала сделать контрольную точку журнала упреждающей записи — иначе смена теряется.
Каталог и цены
Карточка товара с категорией, единицей, штрихкодом. Дерево категорий и ограничения по ним. Несколько цен на товар (редакции цен) и редакции самой карточки. Псевдонимы. Резервирование штрихкодов. Глобальный справочник. Фискальные поля: ставка НДС, НКТ/NTIN, признак маркируемого. Наценка при поступлении. Нормы потерь по ГОСТ. Фотографии. Пакетные товары.

каталог товаров
Скидки, бонусы, лояльность
Скидка позиции ценой «до/после» с настраиваемым округлением. Наценки по категориям и магазинам отдельной таблицей. Акции по схеме «условие → награда»: товар-триггер, количество, награда — включая механику 1+1 и подарок. Акции, финансируемые поставщиком.
Бонусный счёт клиента: начисление с продажи, списание в оплату, баланс по номеру телефона, отмена транзакции, возврат бонусов при возврате товара. У контрагента два счёта — основной и кэшбэк.
Товародвижение
Поступление товара с ценами поставки и историей. Списание с причиной. Перемещение между складами. Возврат поставщику. Инвентаризация с листом пересчёта и разбором расхождений. Реестр остатков. Правила пополнения по минимальному остатку и сигнал о необходимости заказа. Заказ поставщику. Себестоимость.
Контрагенты
Клиенты и поставщики в одном справочнике с типом. Юридические реквизиты: БИН/ИИН, адреса. Поиск по телефону и по строке. Счета контрагента: основной и бонусный, баланс и задолженность. Онлайн-заказ у поставщика с адресом, ключом, минимальной суммой и днями доставки. Восстановление удалённого контрагента.
Отчёты
Панель показателей, продажи, товары, финансы, клиенты, поставщики, прогнозы, ресторанный отчёт, блок отчётов Казахстана. Плюс отчёты по смене, за день, по остаткам и движению денег через Telegram-подсистему.

отчёты
Ресторан
Ресторанная часть отличается от розничной не набором кнопок, а тем, что чек живёт долго и принадлежит столу, а не кассиру.
- Карта столов и зоны зала. У стола есть состояние, и его видят все терминалы кассы сразу.
- Открытый заказ. Позиции добавляются в течение вечера; заказ переживает смену официанта, уход с экрана и перезапуск терминала.
- Модификаторы блюд с группами — «прожарка», «без лука», «двойная порция»; группа задаёт, сколько вариантов можно выбрать.
- Технологические карты и версии рецептур. Блюдо разбирается на ингредиенты, а рецептура версионируется: чек помнит ту версию карты, по которой готовили. Иначе себестоимость прошлого месяца поедет при первой же правке рецепта.
- Списание ингредиентов при продаже блюда — по действовавшей версии карты.
- Раздельный счёт: и по гостям, и произвольным делением. Полная сумма при этом не теряется — на это есть отдельный тест.
- Перенос заказа на другой стол и объединение столов.
- Пречек, сервисный сбор, чаевые.
- Список открытых заказов и настройки ресторанного режима.

карта столов
Чего пока нет: экрана кухни. Ресторанный режим сделан со стороны зала, а его вторая половина — экран повара, который получает позиции, отмечает готовность и возвращает состояние в зал, — только спроектирована.
Сервис: от прачечной до сервисного центра
Это часть, которой в кассовых системах обычно нет вовсе, а если есть — то в виде «услуга как товар». Здесь она сделана как отдельный жизненный цикл.
Что несёт сервисный заказ — это поля, а не пожелания:
| Что | Зачем |
|---|---|
| Номер заказа, чек, точка | Заказ живёт отдельно от продажи и связывается с ней, когда дошло до денег |
| Клиент из справочника или просто имя и телефон | Разовому клиенту не нужно заводить карточку |
| Описание изделия, серийный номер, жалоба клиента | То, с чем пришли, и то, что не работает — разные поля, и это не педантизм |
| Опись при приёме | Что принято вместе с изделием: чехол, зарядка, сумка. Спор «а где мой блок питания» решается описью, а не памятью |
| Фотографии к заказу | Состояние на момент приёма. Тот же спор, но про царапину |
| Время приёма и ожидаемый срок | Клиенту называют срок, а не «позвоните на неделе» |
| Предоплата и предварительная сумма | Приняли аванс — он учтён до того, как работа закончена |
| Исполнитель | Кто ведёт заказ; по нему сортируется очередь |
| Расходные материалы | Списываются со склада в заказ, а не «куда-то» |
| Коды маркировки | Если изделие маркированное, марки едут с заказом и согласуются |
| Итоговая сумма | Считается в конце и может отличаться от предварительной |
| Срок гарантии в днях и гарантийные записи | Гарантия на работу — отдельная сущность, а не строка в примечании |
| Оценка качества с комментарием | Материал для разбора претензий |
Экраны: приёмка, очередь заказов, карточка с переходами состояний, каталог услуг и типов работ, выдача с чеком.
Почему «от прачечной»: там ровно тот же цикл — приняли вещь, описали состояние, назвали срок, взяли предоплату, выполнили, выдали. Отличается только каталог услуг, а он настраивается.

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

адресный склад
Фискализация, маркировка, документы
Написано под Казахстан — это единственная страна, чьи правила мы изучали.
- WebKassa как фискальный оператор: авторизация по токену, фискальный признак, QR и ссылка на чек.
- Очередь офлайн-документов с досылкой, когда связь вернулась, и окном автономной работы.
- Чек коррекции отдельным экраном, журнал фискальных ошибок.
- ЭСФ — настройки, исходящая очередь, отправка.
- СНТ — настройки и документы.
- ИС МПТ — проверка марок и вывод из оборота через фискальный чек.
- ЭСУТД — экран и настройки.
- Ворота фискализации по выбранному оператору; расчёт НДС со ставкой на позицию.
Нефискальный режим — полноправный, а не деградация. Есть точки и страны, где регистратор не применяется: чек формируется, нумеруется и хранится так же, просто не уходит оператору. Ветвей «если фискализации нет, то как-нибудь» в коде нет.
Маркировка проходит через продажу, возврат и сервисный заказ: хранилище кодов, статусы, жизненный цикл марки, проверка у оператора.
Оборудование
Система знает классы устройств, а не модели. Смена модели принтера — это запись в перечне профилей, а не правка кода.
Чековые принтеры по USB, сети, Bluetooth и через Linux. Дополнительные принтеры кухни и бара. Принтеры этикеток с шаблонами и загрузкой ZPL. Шаблоны чеков. Денежный ящик, в том числе через принтер. Весы с настройкой порта и скорости. Сканеры штрихкодов. Дисплей покупателя вторым окном. Платёжный терминал. Kaspi POS.
Три вещи, которые обычно приходится делать руками, здесь есть из интерфейса:
- обнаружение подключённых устройств кнопкой «искать ещё раз»;
- проверка устройства с названной причиной отказа, а не «ошибка»;
- привязка устройства к конкретному рабочему месту.
И правило, ради которого всё это затевалось: печать не задерживает оплату. Задание уходит в очередь, деньги приняты, экран свободен.

настройки, терминалы и сеть
Как это ставится
Одна касса
Ставите, запускаете, работаете. База лежит рядом, всё локально. Сервера нет, облака нет, интернета может не быть вовсе. Это полноценная установка, а не пробный режим.

экран входа
Одна касса и несколько рабочих мест
Это сделано и проверено на отдельном устройстве. Касса раздаёт своё рабочее место в браузер — на планшет, на второй компьютер, на что угодно с современным браузером в той же сети.
Не через REST и не через веб-сокеты, а через QUIC/WebTransport поверх HTTP/3. Причина не в моде:
- Касса говорит первой. Изменение остатка, новый чек, закрытие смены приезжают подпиской, а не опросом раз в пять секунд.
- Соединение переживает смену адреса — планшет ушёл с Wi-Fi на мобильную сеть, сессия не порвалась. Это свойство QUIC.
- Много независимых потоков без общей блокировки.
Дальше самое неудобное: WebTransport работает только на защищённой странице. Значит кассе нужен настоящий сертификат — и она поднимает собственный удостоверяющий центр: выписывает корень, выдаёт листовые сертификаты на имя и на IP-адрес, а браузеру отдаёт корень один раз по одноразовому коду привязки.
Терминал при знакомстве получает 256-битный секрет; касса хранит только его отпечаток. При каждом следующем подключении терминал предъявляет секрет обратно и получает ту же личность — перезагрузка вкладки не превращает планшет в новое устройство.
Каждая операция провода объявляет своё требование к правам доводом в каталоге, а сторож проверяет его в одной точке врезки до вызова обработчика. Забыть проверку нельзя: не собирается.
Несколько касс, магазин, сеть — это план
Здесь честно, потому что легко перепутать с предыдущим пунктом.
Архитектура рассчитана на четыре уровня: терминал, касса, сервер магазина, сервер сети. Правило, из которого растёт вся модель данных: у каждой единицы данных ровно один владеющий уровень; владелец меняет, остальные читают и предлагают; нижний уровень может сузить полученные права, но не расширить.
Что из этого есть: роль --server — тот же исполняемый файл в серверном облике: владеет данными, устройств не имеет, обслуживает терминалы. Экраны одни и те же на всех уровнях, различаются привязки контрактов, а не виджеты.
Чего нет: сущностей магазина и сети в базе, то есть самой модели владения. Сводной отчётности по сети. Кластеризации серверов.
Обмен между кассами через CouchDB написан и ни разу не гонялся между двумя настоящими кассами. Мы не станем утверждать, что он работает, пока этого не увидим — и не станем утверждать, что сломан, пока не измерим.
Приборный образ
Отдельный репозиторий: загрузочный образ Linux, стартующий прямо в киоск. ISO для x86 и образ для Raspberry Pi 5. Управление машиной — в отдельном привилегированном демоне на Rust: сеть, драйверы, экран, службы, время, резервные копии, сброс к заводским. Торговое приложение с правами суперпользователя не работает никогда — оно разговаривает с демоном через сокет.
Технические моменты, которые могут быть интересны
Деньги — только Decimal
Точность 18, масштаб 3. Ни одного double ни в одном слое, включая передачу по сети: в JSON число по спецификации — двоичное с плавающей точкой, поэтому суммы едут строкой.
Копейки на плавающей точке — это то, как касса заканчивает день на шестьдесят тенге короче, и никто не может объяснить, куда они делись.
Печать и фискализация не блокируют деньги
Порядок жёсткий: деньги приняты → чек записан в базу → печать и отправка оператору поставлены в очередь.
Это чинилось по факту: отсутствующий /dev/usb/lp* стоил около 2,8 секунды после того, как деньги уже были взяты. Кассир в этот момент смотрит на застывший экран, а за ним стоит очередь.
Идемпотентность денежных операций
Ключ создаётся инициатором до первой попытки. Повтор с тем же ключом возвращает результат первой попытки, а не выполняет операцию заново. Без этого сетевой таймаут превращается в двойное списание, а перезагрузка посреди оплаты — в потерянный платёж.
Отдельно: опасно не «оплата не прошла», а «неизвестно, прошла ли». Незавершённая оплата не исчезает — она остаётся в состоянии «выясняется» и разрешается опросом терминала или сверкой итогов дня, но никогда предположением.
Остаток не хранится числом
CouchDB гарантирует, что реплики сойдутся. Он не гарантирует, что сойдутся к правильному: при конфликте он детерминированно выбирает победителя, а проигравшая ревизия остаётся лежать в документе.
Если две кассы одновременно списали последнюю единицу товара, «сходимость» означает, что одно списание тихо исчезнет — а товар физически ушёл дважды.
Поэтому остаток это сумма движений, а не поле. Каждое движение — документ, созданный один раз и никогда не редактируемый. Два документа от разных касс не конфликтуют: они просто оба есть.
Слои, которые проверяются машиной
UI lib/presentation/ экраны, виджеты, контроллеры
U95; зависит от
КОНТРАКТ lib/domain/ интерфейсы, сущности, сценарии
U93; реализуется
БЭКЕНД lib/data/ hardware/ telegram/ wire/
Интерфейс импортирует только domain. Никогда data, hardware, drift, dart:io, dart:ffi.
Это не про красоту. Пока каждый экран импортировал таблицу маршрутов ради констант, а таблица импортировала все экраны, граф был единым комом: браузерная сборка тянула 656 файлов из ~700 вместе с драйвером SQLite и FFI — то есть не собиралась в принципе. После разрыва одной связи — 52 файла и ноль нарушений слоёв.
Слой это не эстетика. Это ответ на вопрос, компилируется ли программа для второй платформы вообще.
Нативная часть — отдельными пакетами на Rust
Того, что нужно, в Dart нет: QUIC отсутствует, и оба запроса в трекере SDK закрыты как «не планируется». Пакеты опубликованы отдельно и под MIT — их можно брать в свои проекты без обязательств:
| Пакет | Что закрывает |
|---|---|
rk_quic |
QUIC и HTTP/3 — касса сама отдаёт WebTransport браузеру |
rk_pki |
Сертификаты, mTLS, свой удостоверяющий центр установки |
rk_mdns |
Обнаружение кассы в сети |
rk_syslog |
Журнал по RFC 5424, отправка по RFC 5425, буфер на диске |
Граница жёсткая: нативный код живёт строго ниже контракта. Проверка любого предложения одна — собирается ли после него web. В браузере dart:ffi не существует, и это свойство платформы, а не наша недоработка.
И правило, стоившее отдельного решения: переписывать ради скорости нельзя без замера до. В репозитории ноль профилирований — значит любое «перепишем на Rust, будет быстрее» пока необоснованно. Переписывать ради возможности (QUIC, инференс, реальное время) обосновано и без замеров.
Права — пересечение, а не сумма
Право проверяется у пары «кто» и «откуда»: пользователь и терминал. Действующее право — пересечение прав пользователя, прав терминала и ограничений режима.
Отсюда главное свойство самообслуживания: терминал самообслуживания не умеет открывать ящик, даже если на нём авторизовался директор.
Разграничение видимости сделано на уровне выборки данных, а не интерфейса: ограничение фильтром в UI обходится прямым обращением к API, а API у нас открытый по замыслу.
Архитектура как проверяемый документ
В репозитории лежит документ почти на три тысячи строк с 155 пронумерованными инвариантами — утверждениями, которые обязаны быть истинны всегда. У каждого назван вид проверки: механическая разбором исходников, модульная, на стенде одной кассы, на нескольких машинах, или обзор с датой и ответственным.
Правило простое: инвариант без проверки — это пожелание.
Что планируется
Интеграция с 1С — ближайшая крупная работа
Скажу прямо, потому что здешняя аудитория проверит за минуту: интеграции с 1С сейчас нет. В коде лежит one_c_bridge.dart на 118 строк — протокол обмена поверх телеграм-канала с методами «запросить номенклатуру», «выгрузить продажи», «запросить остатки». Он зарегистрирован в контейнере зависимостей и не вызывается ниоткуда, а 1С-стороны не существует вовсе.
То есть готово место, куда её вставить, и не готова она сама.
Это следующая крупная работа, и именно на неё мы больше всего ждём замечаний от читателей: какой обмен нужен на практике, через что его вести (файлы, HTTP-сервис, прямое подключение), какие конфигурации в приоритете, что в существующих решениях мешает настолько, что переделали бы.
Если вы работаете с 1С в рознице — напишите, что нужно вам. Это ровно тот случай, когда одно замечание в комментариях экономит месяц не туда.
ТСД — терминал сбора данных
Есть отдельное приложение под ТСД: сканирование, приёмка, пересчёт, складские операции «в поле» на обычном Android-терминале со сканером. Оно существует — 383 файла кода, 72 теста, — но работа над ним стоит.
План понятный: перевести на тот же провод, что и браузерный терминал, и на ту же модель прав. Тогда ТСД станет не отдельной программой со своей синхронизацией, а ещё одним рабочим местом кассы — как планшет в браузере, только со сканером и в руке.
Остальное по порядку
- Довести обмен между кассами до состояния, когда он проверен на двух настоящих кассах, а не написан.
- Модель владения данными для магазина и сети: сущности, права, сводная отчётность.
- Довести первый запуск до состояния, когда его проходит незнакомый человек без подсказок.
- Экран кухни — вторая половина ресторанного режима.
- Продажа из браузерного терминала — вход и настройки работают, продажа нет.
- Проверить страны, кроме Казахстана — с валидацией правил, а не «вроде подходит».
- Самообслуживание: роль рабочего места объявлена, экранов нет.
Что честно не работает
Раздел не для скромности. Если его не написать, первый же человек, который скачает и запустит, найдёт всё это сам — и решит, что его обманули.
Первый запуск и мастер настройки. Самое слабое место продукта, и по злой иронии — первое, что видит новый человек. Одиннадцать шагов, каждый по отдельности работает, вместе — как повезёт.
Обмен между кассами не проверен. Код написан, ни одного прогона между двумя настоящими кассами не было. Пока это так, многокассовый магазин мы не предлагаем никому.
Страны кроме Казахстана не проверены. Ставки, форматы идентификаторов и маски для России, Киргизии и Узбекистана лежат в коде и не сверялись ни с одним источником. Более того, известно прямое противоречие внутри собственных констант: в одной таблице ставка НДС России 22, в другой 20. Какая верна — вопрос к бизнесу, а не к коду; но хуже самой ошибки то, что таблиц две, и они разойдутся снова.
Продажа из браузера не сделана. Вход, привязка, настройки и провод работают и проверены вживую. Сама продажа — нет, и это решение, а не забывчивость: её померили, а не оценили на глаз. Одна продажа это порядка шестидесяти обращений к базе на высокочастотном пути, а не редкое событие; такому нужен свой проект, а не «дописать по аналогии».
Проверка сканера, дисплея покупателя и платёжного терминала отвечает «не реализовано». Сканер по последовательному порту и камерой — настройка сохраняется, работы нет.
Промокоды, купоны, подарочные сертификаты и накопительные карты — ничего из этого нет. Скидка вводится ценой позиции; автоматических скидочных правил по сумме чека, времени или клиенту не существует.
Мелкие экраны и стилус. Вёрстка ломается ниже планшетной ширины, прокрутка стилусом работает плохо.
Непрерывной интеграции нет. Процесс, который был, падал и проверкой всё равно не являлся; красный бейдж, на который никто не реагирует, хуже честного пробела. Настоящая проверка сегодня локальная.
Как это проверяется
3585 автотестов, из них 57 сквозных сценариев, которые поднимают настоящий граф зависимостей над базой в памяти — то есть гоняют настоящие репозитории и сценарии, а не подделки. 149 эталонных снимков экранов.
Но главное, чему научил этот проект: зелёный прогон не является проверкой. Несколько случаев из журнала, каждый стоил круга правок:
- Набор из 21 иконки прожил в репозитории месяц. Конвейер, шрифт, класс констант, тест формы, эталонный тест и сам эталон. Всё зелёное. Ни один тест не спрашивал, показывает ли её хоть один экран. Не показывал ни один: место, ради которого её рисовали, убрали редизайном.
- Тёмная тема была написана целиком — со своими семантическими цветами — и не подключена ничем. Мёртвый код, выглядящий живым.
- Версия на заставке была выдуманная.
- При подготовке этой статьи выяснилось, что шрифт иконок не загружался в тестовой среде: каждый глиф рисовался пустым квадратом, сто семнадцать эталонов перезаписались с этими квадратами, и ни одна проверка не покраснела — механизм сравнения записывает то, что нарисовалось, каким бы оно ни было. Нашлось глазами, на скриншоте для этой статьи.
Отсюда правило: проверка обязана уметь покраснеть, и это надо доказывать, а не предполагать. Новый сторож на шрифт проверили отключением шрифта — он покраснел, и только после этого был засчитан.
Второе правило того же класса: живая проверка находит то, чего не видит набор. Провод между кассой и планшетом однажды молчал при полностью зелёном наборе — потому что скомпилированная библиотека в каталоге сборки была на две версии старше правки. Стенд честно мерил прошлогодний продукт.

настройки в тёмной теме, виден набор иконок
Чего мы просим
Тестировщиков
Главная просьба, и она не требует уметь программировать.
Продукт не проходил нормального тестирования вообще. Не «мало тестировали» — не тестировали. У нас нет тестировщика, нет магазина, где можно постоять смену, и нет ни одного человека, который увидел бы этот интерфейс впервые.
Автотесты этого не заменяют. Зелёный набор проверяет то, что разработчик догадался проверить, — то есть ровно те случаи, которые он себе представил. Он молчит про всё, чего никто не вообразил: про то, что кассир нажмёт не туда, про порядок действий, который никому не пришёл в голову, про экран, на котором непонятно, что делать дальше.
Самое ценное:
- Поставить и попробовать настроить с нуля. Записать, где застряли, что было непонятно, что ожидали увидеть вместо того, что увидели.
- Погонять на мелком экране и на планшете со стилусом.
- Проверить свою страну, если вы не из Казахстана.
- Сломать деньги. Смешанная оплата, частичный возврат, округление, отложенный чек, продажа в долг — всё, где арифметика встречается с человеческим поведением.
Что делает отчёт полезным: платформа и сборка, шаги по порядку, что ожидали, что произошло. Для денег — чек, товары, разбивка оплаты и суммы, которые вы считали правильными. «Итог посчитался неверно» невоспроизводимо; «один товар за 2250, скидка 10% строкой, оплата 2000 наличными и 25 картой, в чеке 2025 вместо ожидаемого» — воспроизводимо.
Про правки кода — сразу и прямо
Мы не принимаем pull request'ы. Код вливает только своя команда.
Это решение, а не недосмотр, и причина конкретная: этот код берёт деньги у прилавка. Дефект здесь — не сломанная страница, а касса, которая закончила день короче, или чек, который не ушёл оператору. Влить чужую правку значит поручиться за неё на этом уровне, и честнее сказать «не можем», чем принимать патчи и просматривать их тонко.
Что при этом остаётся полностью открытым: форк (лицензия прямо гарантирует право взять код, изменить и запустить), распространение форка, отчёты, воспроизведения, вопросы и критика. Разработка закрытая, проект — нет.
Лицензия
GNU AGPL v3 или новее. Выбрана намеренно и не самая удобная. AGPL требует открывать исходники производных работ — включая случай, когда вашу доработку люди используют по сети, не получая копии программы. Для продукта, который отдаёт рабочее место в браузер, обычная GPL оставила бы эту лазейку открытой.
- Поставили у себя в магазине и работаете — никаких обязательств.
- Используете коммерчески — можно, никаких обязательств.
- Доработали и раздали или подняли как услугу — обязаны отдать исходники своей версии.
Библиотеки rk_* опубликованы отдельно под MIT — берите в любые проекты без обязательств.
Продукт бесплатный. Всегда
Тут важно, чем это подкреплено, потому что обещаний в интернете много.
Обещание — не гарантия. Лицензия — гарантия. Код открыт под AGPL, и это необратимо: уже выпущенные версии нельзя перелицензировать задним числом. Даже если мы исчезнем, передумаем или продадимся, выложенное останется свободным, и любой продолжит с этой точки. Именно это, а не честное слово, стоит за словами «всегда бесплатный».
Ни подписки на терминал, ни облачного аккаунта, без которого касса не продаёт, ни функций «только в платной версии». Одиночная касса — полноценная установка, а не приманка.
Про поддержку деньгами
Проект развивается на свои, и главная статья расходов понятна из всего сказанного выше: железо для проверки. Чековые и этикеточные принтеры, сканеры, весы, денежные ящики, платёжные терминалы, планшеты, тонкие клиенты. Половину дефектов нашли именно на настоящем устройстве, и каждый следующий класс оборудования — ещё одна коробка, которую надо купить, чтобы узнать, что мы про неё не угадали.
| Что | Адрес |
|---|---|
| Gram (бывший Toncoin) | UQBtGnK5d3cnM5d1lwLPZJJGJ0YoTYRqUxVyObU5HN0Ze-t4 |
| USDT, сеть TON | UQBtGnK5d3cnM5d1lwLPZJJGJ0YoTYRqUxVyObU5HN0Ze-t4 |
| USDT, сеть Ethereum | 0xE28D172292AAe792657AdF59d942F51Cc2318aAE |
Первые два адреса совпадают — это не опечатка: один кошелёк TON принимает и Gram, и USDT в сети TON. А вот сеть перепутать нельзя: USDT из Ethereum на TON-адрес не придёт никуда.
Отдельно: пожертвования ничего не покупают. Ни приоритета в очереди задач, ни обещания реализовать функцию, ни поддержки. Продукт останется бесплатным и открытым независимо от того, пришлёт кто-нибудь что-нибудь или нет.
И честно: хороший отчёт о дефекте полезнее денег. Железо мы рано или поздно купим сами; человека, который поставит кассу с нуля и запишет, где застрял, купить нельзя.
Где взять
Готовые сборки — чтобы попробовать, не нужно ставить Flutter и собирать из исходников:
- Windows — установщик
.msi, ставится и удаляется обычным образом, библиотеки и браузерный бандл внутри. - Ubuntu — архив со сборкой, распаковать и запустить.
Исходники: <https://github.com/kvgosu/telepos>
Сборки: <https://github.com/kvgosu/telepos/releases/latest>
Пакеты rk_* — на pub.dev, отдельно от кассы и под MIT: rk_quic, rk_pki, rk_mdns, rk_syslog.
В репозитории, кроме кода: документ архитектуры на 155 инвариантов, карта сделанного и несделанного, и заметки о проверке — включая перечень случаев, когда зелёный прогон означал «мы туда не смотрели».
Спасибо, что дочитали. Ломайте, пожалуйста.
Вступайте в нашу телеграмм-группу Инфостарт