МойСклад знает всё про ваши операции. И ничего — про вашу прибыль
ОПиУ, ДДС и управленческий баланс — прямо внутри МойСклада, из живых данных, по вашим статьям доходов и расходов. Ставится из маркетплейса в один клик: без отдельного логина, без выгрузок в Excel и без «сведём в конце месяца».
Учётная система считает документы. Собственник считает деньги
Отгрузки, приходы, платежи и остатки в МойСкладе есть — а ответа на вопрос «сколько мы заработали в мае и почему меньше, чем в апреле» нет. Его собирают вручную: выгрузка, сводная таблица, неделя работы бухгалтера и цифра, которой уже месяц.
Выручка, себестоимость, валовая прибыль, операционные расходы по вашим статьям, налоги, чистая прибыль. С раскрытием каждой строки до документов.
Приход и расход по кассам и расчётным счетам, по статьям и контрагентам. Прибыль на бумаге и деньги на счету — это разные числа, и видно оба.
Запасы по складам с роллапом вложенности, деньги по счетам, дебиторка и кредиторка по каждому юрлицу отдельно. Активы минус пассивы.
Разрез переключается мгновенно, потому что он уже посчитан
Наивная реализация ходит в API МойСклада заново на каждое переключение — и пользователь ждёт по полминуты за каждый клик. Здесь все разрезы предсчитываются за один прогон, а переключение — это выбор из готового результата на клиенте.
Документы выкачиваются один раз
Дальше они фильтруются в памяти на каждую колонку. По сети на колонку остаётся только себестоимость — то, что МойСклад считает сам и отдать пачкой не может.
Видно, что происходит, и первые цифры — раньше
Отчёт считает фоновый воркер и публикует прогресс. Готовый «Итог» уходит пользователю, не дожидаясь остальных разрезов: смотреть можно уже через несколько секунд.
Справочник статей — в самом МойСкладе
Приложение создаёт пользовательские справочники и навешивает атрибуты на документы. Менеджер проставляет статью прямо в приходе или платеже, а не в чужом интерфейсе.
Заметки к строкам отчёта
«Почему в марте маркетинг вырос вдвое» — записано рядом с цифрой и остаётся там при следующем построении отчёта.
Четыре места, где «работает у меня» ломается у клиента
Виджет молча выкидывал пользователей Safari и Яндекс.Браузера
Приложение живёт в iframe внутри МойСклада, значит его cookie для браузера —
сторонняя. Safari без поддержки CHIPS и «Протект» Яндекса просто не
сохраняли её — даже с SameSite=None; Secure; Partitioned.
Пользователь входил и тут же получал 401.
Сессия уехала из cookie в подписанный HMAC-SHA256 токен: сервер отдаёт его
в теле ответа на вход, вкладка держит в памяти и шлёт заголовком
Authorization: Bearer. Авторизация перестала зависеть от
политики сторонних cookie в каком бы то ни было браузере.
Токен живёт только в памяти вкладки — не в localStorage. Тариф в сессию не кладётся: сессия отвечает на вопрос «кто пришёл», а не «что ему продано».
Лимиты МойСклада считаются по токену, а не по процессу
Веб-процесс тянет фильтры, воркер считает отчёт, рядом идёт второй прогон по тому же аккаунту — и каждый держит свой счётчик частоты. Их сумма уверенно пробивает лимит МС, и клиент получает 429 в середине отчёта.
Ограничитель вынесен в Redis: атомарный токен-бакет, общий для всех процессов, с ключом на аккаунт. Если Redis не сконфигурирован или лёг — деградация в локальный бакет, а не отказ.
Поверх — ретраи на 429 и 5xx с экспоненциальной задержкой и пагинация с ограниченной конкурентностью. Долгий отчёт дочитывается до конца, а не падает на середине.
Маркетплейс присылает вебхуки повторно. Дублей быть не должно
Реализованы обе стороны Vendor API 1.0: активация, смена тарифа,
продление, приостановка и удаление установки. Входящие вебхуки проверяются
по JWT — подпись HS256, срок жизни и одноразовость jti;
повторная доставка отсекается идемпотентностью по
X-Lognex-RequestId.
Тариф — это таблица соответствия «идентификатор тарифа → набор доступных отчётов». Три отчёта — три независимых флага: в личном кабинете вендора они продаются и по отдельности, и комбинациями, поэтому вывести один из других нельзя.
«Приостановлено» гасит токен, но сохраняет подписку: вернувшийся клиент не начинает с нуля.
Два унаследованных сервиса стали одним
Проект достался как связка отдельного бэкенда и отдельного фронта. Версия, которая работает сейчас, переписана по чистому: доменное ядро вынесено в слой без единой зависимости от фреймворка, интерфейс — FastAPI с серверными шаблонами и лёгким JS.
Ни одной внешней библиотеки с CDN: HTMX, Alpine.js, Tabulator и ECharts лежат в образе. Приложение поднимается и работает там, где до внешних сетей достучаться нельзя.
Один процесс вместо двух сервисов, одна точка сборки зависимостей, одна реализация разбиения периодов вместо двух разошедшихся.
Ports & Adapters: расчёт прибыли не знает ни про HTTP, ни про базу
Зависимости идут строго снизу вверх. Поэтому логика управленческого баланса и сборки ОПиУ покрывается юнит-тестами без единого внешнего сервиса — а тесты гоняются на SQLite и брокере в памяти.
123 теста, ruff и mypy в одном make check
Тесты покрывают ядро расчётов, слой представления, вебхуки вендора, лимитер частоты и фоновые задачи — и не требуют ни Postgres, ни Redis, ни доступа к МойСкладу.
Пять сервисов, одна команда
Postgres, Redis, миграции, API и воркер — в изолированной сети; наружу торчит только порт API. Миграции применяются автоматически перед стартом.
FastAPI · SQLAlchemy 2 async · taskiq
Нужен виджет в маркетплейс — или отчётность, которой сейчас нет?
Делаю интеграции с учётными системами и приложения для их маркетплейсов: от расчётного ядра и работы с чужим API под лимитами до кабинета, который открывается внутри знакомого пользователю интерфейса.
Похожие задачи в других отраслях
Автопривязка платежей
Платёж сам привязывается к документу по одному из восемнадцати правил.
смотреть кейс → Медиа · SEOНовостной портал
Карты сайта, RSS, разметка и мгновенные пуши поисковикам собираются сами.
смотреть кейс → Производство · десктопСверление и резка
Задание разбирается на отрезки профиля и уходит на станок одной кнопкой.
смотреть кейс →