Совместные закупки в одном PWA: заказ, сборка и выдача в пункте
Организатор совместных закупок собирал заявки в трёх мессенджерах. Я собрал одно PWA: город, пункт выдачи, заказ и общий лимит позиции на всех, плюс закупщик, сборщик и франчайзи. Система работает, но MAX и онлайн-оплата не подключены.
Коротко (TL;DR)
- Организатор совместных закупок возил товар с рынка в несколько городов, а заказы собирал в трёх мессенджерах. Нужна была одна ссылка вместо трёх ботов: город, пункт выдачи, заказ и общий лимит позиции на всех.
- Я собрал PWA на Node.js и SQLite без сборки фронта. Пять ролей команды (закупщик, сборщик, владелец, франчайзи, продавец пункта), живые остатки, посты в каналы городов, взвешивание и расчёт долей.
- Код написан за девять дней, 65 коммитов, 14 проверочных скриптов плюс сквозной тест через интерфейс.
- Система в бою: на момент проверки 2 октября сайт отвечает 200 и отдаёт витрину с реальными товарами. Сколько живых покупателей, я с публичной стороны не вижу, это не проверено.
Совместная закупка выглядит просто: человек едет на оптовый рынок, берёт ящики ягод и фруктов, раздаёт по пунктам выдачи. Сложность прячется в учёте. Расскажу, как я собрал его в одно приложение и что до сих пор не доделано.
Задача
Заказчик собирал заявки в Telegram, MAX и ВКонтакте отдельно. Три потока, три таблицы и один человек, который сводит их руками. Цель: один адрес, куда можно зайти с телефона без регистрации, выбрать город, пункт выдачи и положить в заказ нужные позиции.
У каждой позиции общий лимит на всех. Если закупщик взял 300 кг маракуйи, то остаток один на всю сеть пунктов, и он должен быть одинаково виден каждому, кто смотрит витрину. Когда остаток доходит до нуля, на карточке появляется «Товар разобрали». Оплата при получении, цена одна для всех городов.
Что я сделал
Node.js, Express и встроенный SQLite, фронт без сборщика. В репозитории около 2,5 тысячи строк серверного кода и 2,8 тысячи строк фронта, 22 таблицы и 96 маршрутов API (посчитал командами по файлам). Выкладка сводится к копированию папки и перезапуску службы.
Три манифеста PWA: покупатель, команда и владелец. Витрина и «Мои заказы» открываются без сети из кэша. На iPhone установленное приложение не видит куки Safari, поэтому команда входит по восьмизначному коду.
Лимит на всех и остаток в реальном времени
Заказ оформляется в одной транзакции SQLite с блокировкой записи. Сервер заново считает, сколько продано в текущем сборе, и только потом принимает позицию. Остатки доезжают до открытых витрин серверными событиями. Покупатель каждый раз выбирает город и пункт заново: так попросил заказчик, чтобы не ошибиться пунктом по старой привычке.
Цепочка команды и каналы
Закупщик заводит карточку из двух частей. Открытая: фото, видео, название, цена, шаг, объём. Закрытая: цена закупки, тара, где лежит товар, телефон продавца. Закрытая часть уходит только сборщику. Публикация выпускает карточки с заполненными полями, остальные остаются черновиками.
Сборщик взвешивает: брутто, число мест, фактическая тара, из этого считается нетто. Деньги считаю целыми числами в граммах, потому что на плавающей точке 8,7 × 215 давало 1870,4999. Франчайзи видит заказы к выдаче и отмечает выданные. Оценку пункту покупатель ставит только по своему выданному заказу.
Публикация рассылает пост в Telegram и ВКонтакте, в каждом городе свои каналы, их владелец подключает в админке сам. Код для MAX тоже написан. Ключом сообщества ВКонтакте фото к посту не прикрепляется, поэтому там уходит текст со ссылкой.
Позже добавилась карта пунктов с фото, часами и рейтингом, товар в наличии на сегодня с бронью без предоплаты, доска объявлений и рецепты. Это ещё около 770 строк. Адрес объявления ставится на карту геокодером, и я отсекаю точки дальше 50 км от центра города: поиск по слову «Самара» однажды увёл объявление в посёлок под Кемерово.
Что было сложно
Первое, гонка за последним килограммом. На копии сервера 300 человек одновременно брали 100 кг, перепродаж ноль. Это замер от 24 сентября, сегодня заново не гонял.
Второе, квант закупки. Товар берут ящиками по 10 кг, а покупатели заказывают по килограмму. Сколько ящиков брать, зависит от заказов по каждому пункту отдельно: меньше ящика всё равно полный ящик, на крупных заказах добавляется запас. Правила за неделю менялись несколько раз, поэтому проценты вынесены в настройки.
Третье, формула цены. Закупщик, сборщик, платформа и франчайзи получают свои доли за единицу закупки, и доли фиксируются снимком в момент взвешивания. Если пересчитать формулу, старые поставки не поплывут.
Четвёртое, ручная кнопка отправки. Вечером после закрытия сбора кнопка «Посты сейчас» отправила тестовый список в боевые каналы трёх городов. Я переделал кнопку: теперь она шлёт только в тестовые каналы и скрыта, если их нет. Любую ручную отправку клиенту я теперь делаю тестовой по умолчанию.
Пятое, ограничения среды. Тесты я гоняю только на локальном стенде, боевые данные клиента не трогаю. Рядом была отдельная работа, оформление чужого маркетплейса на общей кодовой базе. Она шла через два канала: файл стилей по SFTP и консоль темы с проверкой контраста. Ни бэкенда, ни базы мне там не открывали, и это ограничивало всё, что можно сделать.
Что получилось
| Что | Состояние |
|---|---|
| Публичный адрес | отвечает 200, отдаёт витрину |
| Города и пункты выдачи | 9 городов, 6 пунктов в 4 городах |
| Роли команды | 5, плюс покупатель без регистрации |
| Проверочные скрипты | 14 плюс сквозной тест |
| Нагрузка на копии | 2000 открытых витрин, замер 24.09 |
| Посты в MAX | не проверено |
| Онлайн-оплата | нет, оплата при получении |
Система живая. Я открыл публичные адреса витрины: текущий сбор закрыт, выдача назначена на 3 октября, остатки по позициям считаются, у пунктов есть оценки покупателей. Сколько из них настоящие, а сколько тестовые, снаружи не определить, поэтому цифр по выручке и числу заказов в кейсе нет.
Резервного копирования базы в проекте нет, данные лежат на одном сервере.
Что дальше
В очереди чат с пунктом выдачи для жалоб с фото и подключение MAX после того, как заказчик оформит юридическую часть. Каждое идёт отдельным этапом. Если вам нужна похожая схема, начните с лимитов и ролей, а оформление оставьте на конец. Про сам формат есть статья о PWA из сайта, про каналы разбор Telegram и ВК, а про MAX заметка о каналах и рассылках.
Частые вопросы
Зачем PWA, если есть боты?
Три бота дают три базы заказов. Одна ссылка складывает всё в одну базу, показывает общий остаток и ставится на экран телефона. Подробнее про формат в материале про мини-приложения MAX.
Сколько стоит такая система?
Цену конкретного проекта я здесь не называю. Она зависит от числа ролей, формулы расчёта и каналов публикации. Типовые форматы описаны на странице мобильных приложений и PWA.
Платёж проходит онлайн?
Нет. В этом проекте оплата при получении в пункте, платёжный провайдер не подключён.
Коротко о главном
Общий лимит на всех, транзакция при заказе и цепочка ролей от закупщика до пункта выдачи закрывают основную боль совместных закупок. Остальное решается настройками: формулой цены, квантом, каналами по городам.
Приложение работает в бою, но часть интеграций не проверена: MAX остаётся на следующий этап, фото во ВКонтакте не доходят, онлайн-оплаты нет. Нужна своя система заказов с пунктами выдачи или платформа под вашу схему? Напишите в Telegram, MAX или ВК. Если считаете бюджет, начните со статьи о стоимости сайта.
Что я делаю для бизнеса
- Боты в Telegram, MAX, VK
- Автоматизация процессов и CRM
- Аналитика и дашборды
- Сайты и лендинги под ключ
Бесплатно: чек-лист «Готов ли ваш бизнес к 152-ФЗ»
12 пунктов, которые проверяют готовность за час: данные, согласия, уведомление в РКН, локализация, защита. Отметьте, что уже сделано, и увидите дыры, за которые сейчас штрафуют.
Готовы обсудить вашу задачу?
Бесплатная консультация — разберём, как внедрить это в вашем бизнесе под ключ. Без форм, пишите напрямую.
Разборы, кейсы и практика — без воды. Выходит регулярно, читать 3–5 минут.


