← К портфолио

🏭 B2B-платформа изготовителей и ремонтников оборудования Узбекистана

Техническое задание, архитектура данных, структура интерфейса и пользовательские сценарии (User Flow)

B2B Marketplace Мультиязычность RU / UZ Latin / UZ Cyrillic Гео-поиск Аукцион заказов Telegram Bot Подписочная модель Арбитраж споров Барахолка станков и инструмента Вакансии и резюме Самостоятельная регистрация без админа

Суть проекта: сервис, который соединяет заказчиков (предприятия, нуждающиеся в изготовлении деталей, узлов или в ремонте оборудования) с исполнителями (частные мастера, цеха, заводы Узбекистана). Заказчик публикует заявку в свободной форме (текст/фото/чертежи/видео), система автоматически подбирает исполнителей по техническим возможностям и геолокации, оповещает их через Telegram, и исполнители конкурируют за заказ в формате аукциона (цена/срок/условия). Доступ к платформе — только после обязательной регистрации («жёсткий paywall» на уровне разделов, а не главной страницы). Все профили — и заказчика, и исполнителя — заполняются пользователем самостоятельно при регистрации, без ручного участия администратора. Дополнительно платформа включает доску объявлений купли-продажи металлообрабатывающих станков и инструмента, а также раздел вакансий/резюме (найм персонала) — оба раздела построены по единой модели «объявление + отклик» с мгновенным Telegram-уведомлением обеим сторонам.

1 Роли пользователей и права доступа

👤 Заказчик (Customer)

Юр. или физ. лицо, которому требуется изготовление детали/узла или ремонт оборудования. Публикует заказы, просматривает каталог и карту исполнителей, выбирает победителя аукциона, оставляет отзывы, инициирует споры.

🔧 Исполнитель (Executor)

Подтипы: Мастер (частное лицо/ИП), Цех (малое производство), Завод (крупное производство). Заполняет карточку станочного парка и допустимых габаритов, получает уведомления о заказах, участвует в аукционах, оплачивает подписку.

🛡️ Администратор / Модератор

Проверяет исполнителей (верификация), модерирует контент, разбирает споры (арбитраж), управляет тарифами подписки, видит агрегированную аналитику.

🤖 Система (Matching Engine / Bot)

Не пользователь, а служебная роль: движок автоподбора исполнителей и Telegram-бот, действующие по правилам модулей 2 и 7.

Один аккаунт (users.id) может иметь только одну активную бизнес-роль (customer либо executor) для простоты MVP; переключение роли — через создание второго профиля на тот же email, по аналогии с «личным» и «рабочим» кабинетом. Роль admin — отдельный флаг, не публичная регистрация.
Принцип самообслуживания: ни один профиль на платформе не создаётся и не заполняется вручную администратором. И заказчик, и исполнитель проходят один и тот же механизм — обязательную регистрацию (модуль 1) и мастер заполнения профиля (модуль 3) сразу после неё. Роль администратора ограничена последующей верификацией/модерацией уже заполненных данных, а не их вводом.

2 Архитектура базы данных

Реляционная модель (PostgreSQL + расширение PostGIS для гео-запросов). Ниже — таблицы, сгруппированные по доменам. Все таблицы имеют id UUID PK, created_at, updated_at — опущены в схемах для краткости.

2.1 Пользователи и авторизация

users — базовая учётная запись
ПолеТипОписание
emailvarchar UNIQUEосновной email, обязателен даже при входе через Google
password_hashvarchar NULLNULL, если вход только через Google
google_idvarchar NULL UNIQUEsub из Google OAuth
phonevarchar NULLдля Telegram-верификации и связи
roleenum(customer, executor, admin)основная роль аккаунта
preferred_languageenum(ru, uz_latin, uz_cyrillic)язык интерфейса
statusenum(active, blocked, pending_verification)статус аккаунта
is_email_verifiedboolean
last_login_attimestamp
customer_profiles — карточка заказчика (1:1 к users)
ПолеТипОписание
user_idFK → users.id
company_namevarchar NULLесли юр. лицо
stir_innvarchar NULLИНН/СТИР для верификации бизнеса
region_id / city_idFK → regions / citiesосновной регион
address_textvarchar
locationgeography(Point,4326)координаты объекта заказчика (для радиуса поиска)
rating_avgnumeric(3,2)кэш среднего рейтинга от исполнителей
reviews_countint
executor_profiles — карточка исполнителя (1:1 к users)
ПолеТипОписание
user_idFK → users.id
org_typeenum(master, tsekh, zavod)масштаб производства
display_namevarcharназвание цеха/завода или ФИО мастера
descriptiontextописание, хранится по языку публикации + опц. переводы
stir_innvarchar NULL
region_id / city_idFK → regions / cities
address_textvarchar
locationgeography(Point,4326)координаты цеха/завода — основа гео-поиска
service_radius_kmintготовность выезжать/принимать заказы в радиусе (для ремонта на месте)
is_verifiedbooleanпрошёл проверку админом
rating_avgnumeric(3,2)кэш рейтинга от заказчиков
reviews_countint
subscription_statusenum(trial, active, expired, none)кэш из subscriptions для быстрой проверки доступа
executor_equipment — станочный парк исполнителя (1:N)
ПолеТипОписание
executor_idFK → executor_profiles.id
equipment_typeenum / FK → equipment_typesнапр. «токарный ЧПУ», «лазерная резка», «сварка аргон», «фрезерный», «пресс», «зубофрезерный»
model_namevarchar NULLмарка/модель станка
quantityintколичество единиц
notestext NULL

equipment_types — справочник (токарный ЧПУ, фрезерный ЧПУ, лазерная резка, плазменная резка, сварка (аргон/полуавтомат/дуговая), гибка листа, литьё, шлифовка, зубофрезерование, электроэрозия, и т.д.), управляется админом, мультиязычные названия.

executor_capabilities — допустимые габариты и материалы (1:1 или 1:N по категориям)
ПолеТипОписание
executor_idFK → executor_profiles.id
max_length_mmint NULLмакс. длина детали
max_diameter_mmint NULLмакс. диаметр (для токарных работ)
max_width_mm / max_height_mmint NULLдля листовых/фрезерных работ
max_weight_kgnumeric NULLмакс. вес заготовки, которую поднимает оборудование (кран-балка и т.п.)
materialsvarchar[] / FK-таблицасталь, чугун, алюминий, нержавейка, пластик, латунь и т.д.
service_categoriesvarchar[] / FK-таблицатеги услуг: токарные работы, фрезеровка, лазерная резка, сварочные работы, ремонт редукторов, ремонт насосов…

Поля service_categories и materials — управляемые справочники (many-to-many через связочные таблицы executor_capability_services / executor_capability_materials), что необходимо для точного алгоритма автоподбора (модуль 2).

2.2 География

regions / cities — справочник регионов Узбекистана
ПолеТипОписание
name_ru / name_uz_latin / name_uz_cyrillicvarchar14 областей + Каракалпакстан + Ташкент, привязанные города
locationgeography(Point,4326)центр региона/города — для дефолтного центрирования карты

2.3 Заказы, медиа и матчинг

orders — заявка заказчика
ПолеТипОписание
customer_idFK → customer_profiles.id
titlevarchar
descriptiontext
content_languageenum(ru, uz_latin, uz_cyrillic)язык, на котором заказчик реально ввёл текст
order_typeenum(manufacturing, repair)изготовление или ремонт
service_categoryFK → service_categoriesтип работ, по которому идёт матчинг
materialFK → materials NULL
dimensions_length_mm / diameter_mm / weight_kgnumeric NULLпараметры детали — сверяются с executor_capabilities
budget_min / budget_maxnumeric NULLвилка бюджета (опционально, влияет на аукцион)
deadline_datedate NULLжелаемый срок
region_id / city_idFK
locationgeography(Point,4326)точка объекта — для ремонта на месте и радиуса поиска
auction_deadline_attimestampвремя закрытия приёма ставок
statusenum(draft, published, matching, auction_open, awaiting_choice, assigned, in_progress, completed, disputed, cancelled)см. модуль 2 и 6
order_media — вложения заявки (1:N)
ПолеТипОписание
order_idFK → orders.id
media_typeenum(photo, drawing, video, document)чертежи хранятся как photo/document с флагом drawing
file_urlvarcharобъектное хранилище (S3-совместимое)
thumbnail_urlvarchar NULL
sort_orderint
order_matches — результат автоподбора (1:N)
ПолеТипОписание
order_idFK → orders.id
executor_idFK → executor_profiles.id
match_scorenumeric(5,2)вес совпадения — см. модуль 2
distance_kmnumeric NULL
notified_viaenum(telegram, email, in_app, none)
notified_attimestamp NULL
viewed_attimestamp NULL
bids — ставки исполнителей в аукционе (1:N к order)
ПолеТипОписание
order_idFK → orders.id
executor_idFK → executor_profiles.id
pricenumericпредложенная цена
currencyenum(UZS, USD)
lead_time_daysintсрок исполнения
commenttext NULLусловия, гарантия и т.п.
statusenum(active, withdrawn, accepted, rejected)
order_assignments — назначение победителя аукциона (1:1 к order)
ПолеТипОписание
order_idFK → orders.id UNIQUE
bid_idFK → bids.idвыигравшая ставка
agreed_price / agreed_deadlinenumeric / dateзафиксированные условия
assigned_attimestamp
completed_attimestamp NULL
order_status_history — журнал статусов (аудит)
ПолеТипОписание
order_idFK → orders.id
from_status / to_statusenum
changed_by_user_idFK → users.id NULLNULL — системное изменение

2.4 Отзывы и рейтинги

reviews — двусторонние отзывы (1:N к order)
ПолеТипОписание
order_idFK → orders.idотзыв возможен только по завершённому заказу
author_idFK → users.id
target_idFK → users.id
directionenum(customer_to_executor, executor_to_customer)
ratingint (1..5)
rating_breakdownjsonb NULLдля исполнителя: качество/сроки/коммуникация; для заказчика: адекватность ТЗ/оплата вовремя/коммуникация
commenttext NULL
is_publishedbooleanпубликуется одновременно с обеих сторон либо через N дней — против «эффекта заложника»

Уникальность: один (order_id, author_id) — один отзыв. Триггер пересчитывает rating_avg/reviews_count в профиле цели.

2.5 Арбитраж

disputes — спор по заказу (1:1 к order)
ПолеТипОписание
order_idFK → orders.id
opened_by_user_idFK → users.id
reason_categoryenum(defect, delay, quality, price_dispute, other)
descriptiontext
statusenum(open, evidence_collection, in_review, resolved_customer, resolved_executor, resolved_partial, closed)
assigned_admin_idFK → users.id NULL
resolution_texttext NULL
resolved_attimestamp NULL
dispute_evidence — доказательства сторон (1:N)
ПолеТипОписание
dispute_idFK → disputes.id
uploaded_by_user_idFK → users.idзаказчик или исполнитель
media_typeenum(photo, video, document)
file_urlvarchar
commenttext NULL
dispute_messages — переписка сторон + админа внутри спора (1:N)
ПолеТипОписание
dispute_idFK → disputes.id
sender_idFK → users.id
texttext
is_internal_admin_notebooleanзаметки админа, невидимые сторонам

2.6 Монетизация и подписки

subscription_plans — тарифы (справочник)
ПолеТипОписание
codeenum(basic, standard, pro)
price_monthly_uzsnumeric
max_bids_per_monthint NULLNULL = без лимита (pro)
telegram_alert_delay_secint0 — мгновенно (pro), задержка для дешёвых тарифов
catalog_priorityintвес поднятия в каталоге/матчинге
features_jsonjsonbпрочие фичи тарифа
subscriptions — активная подписка исполнителя (1:N история, 1 активная)
ПолеТипОписание
executor_idFK → executor_profiles.id
plan_idFK → subscription_plans.id
statusenum(trialing, active, past_due, expired, cancelled)
started_at / expires_attimestamp
auto_renewboolean
payments — платежи по подписке (1:N)
ПолеТипОписание
subscription_idFK → subscriptions.id
amount / currencynumeric / enum
providerenum(payme, click, uzcard)локальные платёжные провайдеры РУз
provider_transaction_idvarchar
statusenum(pending, succeeded, failed, refunded)

2.7 Уведомления, Telegram, чат

telegram_links — привязка Telegram-аккаунта (1:1 к users)
ПолеТипОписание
user_idFK → users.id
telegram_chat_idbigint UNIQUE
telegram_usernamevarchar NULL
linked_attimestamp
notifications_enabledboolean
notifications — журнал уведомлений (1:N к users)
ПолеТипОписание
user_idFK → users.id
typeenum(new_order_match, outbid, bid_accepted, bid_rejected, dispute_update, subscription_expiring, review_received, listing_response, vacancy_response, resume_invite)последние три — отклики по модулям 8 и 9
channelenum(telegram, email, in_app)
payload_jsonjsonb
sent_at / read_attimestamp NULL
order_messages — чат заказчик ↔ исполнитель по заказу (1:N)
ПолеТипОписание
order_idFK → orders.id
sender_id / receiver_idFK → users.id
texttext NULL
attachment_urlvarchar NULL

Чат открывается только после того, как заказчик проявил интерес к ставке исполнителя — защита от спама на этапе аукциона.

content_translations — кэш авто-перевода UGC (опционально)
ПолеТипОписание
entity_typeenum(order, executor_profile)
entity_iduuid
target_languageenum(ru, uz_latin, uz_cyrillic)
translated_title / translated_texttext
providervarcharсервис машинного перевода

UI-строки (кнопки, лейблы, статусы) переводятся статически (i18n JSON-файлы ru.json, uz_latin.json, uz_cyrillic.json), а не через БД. Данная таблица покрывает только пользовательский контент (заявки, описания цехов), чтобы заказчик и исполнитель с разным родным языком понимали друг друга.

2.8 Барахолка — купля-продажа станков и инструмента

Отдельный от заказов домен: не услуга, а конкретный физический товар (станок, инструмент, запчасть, расходники). Продавцом или покупателем может быть любой зарегистрированный пользователь — и заказчик, и исполнитель (цех нередко одновременно и покупает, и распродаёт оборудование).

listings — объявление о продаже/покупке (1:N к users)
ПолеТипОписание
author_idFK → users.idавтор объявления (продавец либо покупатель)
listing_intentenum(sell, buy)«продаю» или «ищу купить»
categoryFK → listing_categoriesстанки с ЧПУ, ручной инструмент, сварочное оборудование, запчасти, расходники и т.д.
title / descriptionvarchar / text
content_languageenum(ru, uz_latin, uz_cyrillic)
conditionenum(new, used, for_parts)состояние — не заполняется при listing_intent = buy
pricenumeric NULLNULL / «договорная» допускается
currencyenum(UZS, USD)
region_id / city_idFK
locationgeography(Point,4326) NULLдля показа на карте/поиска рядом, опционально
statusenum(active, reserved, closed, archived)
listing_media — фото/видео товара (1:N)
ПолеТипОписание
listing_idFK → listings.id
media_typeenum(photo, video)
file_urlvarchar
listing_responses — отклики на объявление (1:N)
ПолеТипОписание
listing_idFK → listings.id
from_user_idFK → users.idкто откликнулся («интересует», предложение цены)
messagetext NULL
offered_pricenumeric NULLторг

Каждый новый отклик немедленно уведомляет listings.author_id (Telegram + in-app) — та же механика, что и уведомление заказчика о новой ставке в модуле 2 (см. 10.4).

2.9 Вакансии и резюме (найм персонала)

Двусторонняя доска: работодатель размещает вакансию, соискатель — резюме («ищу работу»); каждая сторона может откликнуться на объявление другой стороны, инициатором может быть как работодатель, так и работник.

vacancies — вакансия от работодателя (1:N к users)
ПолеТипОписание
employer_idFK → users.idлюбой пользователь — цех/завод (executor) или предприятие (customer)
profession_categoryFK → profession_categoriesтокарь, фрезеровщик, сварщик, наладчик ЧПУ, слесарь-ремонтник, инженер-технолог…
title / descriptionvarchar / textтребования, обязанности, условия
employment_typeenum(full_time, part_time, shift, contract)
salary_min / salary_maxnumeric NULLвилка зарплаты
region_id / city_idFK
statusenum(active, closed, archived)
resumes — резюме соискателя (1:N к users)
ПолеТипОписание
candidate_idFK → users.idобычный зарегистрированный пользователь (не обязательно customer/executor)
profession_categoryFK → profession_categories
experience_yearsint NULL
abouttextопыт, навыки, станки, с которыми умеет работать
expected_salarynumeric NULL
region_id / city_idFK
statusenum(active, hidden)
job_responses — отклик в любую сторону (1:N)
ПолеТипОписание
vacancy_idFK → vacancies.id NULLзаполнено, если отклик на вакансию
resume_idFK → resumes.id NULLзаполнено, если приглашение по резюме
from_user_id / to_user_idFK → users.idинициатор и получатель отклика
directionenum(candidate_to_employer, employer_to_candidate)кто начал: соискатель откликнулся на вакансию, либо работодатель пригласил по резюме
messagetext NULL

Ровно один из vacancy_id/resume_id заполнен (CHECK-ограничение). Как и в listing_responses, каждая запись немедленно порождает уведомление получателю (см. 10.4).

2.10 Сводка связей (ER, кратко)

  • users 1—1 customer_profiles либо executor_profiles
  • executor_profiles 1—N executor_equipment, 1—N executor_capabilities
  • customer_profiles 1—N orders 1—N order_media
  • orders 1—N order_matches N—1 executor_profiles
  • orders 1—N bids N—1 executor_profiles; orders 1—1 order_assignments → bids
  • orders 1—N reviews (по одной с каждой стороны)
  • orders 1—1 (опц.) disputes 1—N dispute_evidence, 1—N dispute_messages
  • executor_profiles 1—N subscriptions N—1 subscription_plans; subscriptions 1—N payments
  • users 1—1 telegram_links; users 1—N notifications
  • users 1—N listings 1—N listing_media, 1—N listing_responses
  • users 1—N vacancies; users 1—N resumes; оба 1—N job_responses
Индексы: GIST по всем полям geography(Point) для радиус-поиска (ST_DWithin); составной индекс (service_category, region_id, status) на orders для быстрого матчинга; полнотекстовый индекс (pg_trgm/tsvector) на executor_profiles.description и orders.title/description для поиска по каталогу.

3 Структура интерфейса (карта разделов)

3.1 Открытая зона (без авторизации)

  • Главная (/) — оффер, «как это работает» в 3 шага, преимущества, витрина из 4–6 обезличенных карточек исполнителей/выполненных проектов (без клика), кнопки «Разместить заказ» и «Стать исполнителем» — обе ведут к paywall-модалке.
  • О платформе, Тарифы для исполнителей (маркетинговое описание, без входа в личный кабинет), Контакты/Поддержка.
  • /auth/login, /auth/register — форма email + кнопка «Войти через Google».

3.2 Закрытая зона (после регистрации)

РазделДля когоНазначение
/orders/new — Конструктор заказаЗаказчикмультимедийная форма создания заявки (модуль 2)
/orders/:idЗаказчик, приглашённые исполнителикарточка заказа, статус, ставки аукциона, чат
/orders (мои заказы)Заказчиксписок со статусами (open/в аукционе/в работе/завершён/спор)
/catalogЗаказчиккаталог исполнителей с фильтрами по услуге, региону, рейтингу
/mapЗаказчикинтерактивная карта Узбекистана с исполнителями (модуль 4)
/executor/:idЗаказчикпубличная карточка исполнителя: станочный парк, габариты, отзывы, портфолио
/dashboard (лента заказов)Исполнительлента подобранных заказов + активные аукционы, где он участвует
/profile/equipmentИсполнительредактирование станочного парка и габаритов (модуль 3)
/subscriptionИсполнительтариф, оплата, история платежей (модуль 7)
/reviewsОбаоставленные/полученные отзывы (модуль 5)
/disputes/:idОба + админкарточка спора с загрузкой доказательств (модуль 6)
/marketplaceОбабарахолка: объявления о продаже/покупке станков и инструмента (модуль 8)
/marketplace/new, /marketplace/:idОбасоздание объявления, карточка товара с откликами
/jobsОбалента вакансий (модуль 9)
/jobs/newРаботодательсоздание вакансии
/resumesОбалента резюме — работодатель ищет кандидатов
/resumes/newСоискательсоздание/редактирование своего резюме
/settingsОбаязык интерфейса, уведомления, Telegram-привязка, смена пароля
/admin/*Админверификация исполнителей, модерация, разбор споров, тарифы

3.3 Навигация исполнителя (нижнее/верхнее меню)

Лента заказов · Мои ставки · Профиль и оборудование · Барахолка · Вакансии · Отзывы · Подписка · Сообщения

3.4 Навигация заказчика

Мои заказы · Новый заказ · Каталог · Карта · Барахолка · Вакансии · Отзывы · Сообщения

4 Модуль 1 — Авторизация и «жёсткий» Paywall

Логика доступа: публичны только маркетинговые страницы (главная, о платформе, тарифы, auth). Любой переход на защищённый роут при отсутствии активной сессии перехватывается route-guard'ом (middleware), который вместо загрузки страницы показывает модальное окно и не выполняет навигацию (в адресной строке остаётся исходный URL или редирект на /?authRequired=1&next=/orders/new).

User Flow: попытка неавторизованного доступа

  1. Гость открывает главную страницу — доступна полностью, без ограничений.
  2. Гость кликает «Разместить заказ» / «Каталог» / «Личный кабинет».
  3. Фронтенд проверяет наличие валидного JWT/сессии → сессии нет.
  4. Показывается модальное окно: «Для продолжения работы необходима регистрация» с двумя вкладками — «Войти» / «Зарегистрироваться», и кнопкой «Войти через Google».
  5. Целевой URL сохраняется как next — после успешной авторизации пользователь автоматически попадает туда, куда изначально пытался перейти.

User Flow: регистрация через Email

  1. Пользователь выбирает роль: «Я заказчик» / «Я исполнитель».
  2. Вводит email, пароль, подтверждение пароля, выбирает язык интерфейса (RU / Oʻzbekcha (lotin) / Ўзбекча (кирилл)).
  3. Система создаёт users со статусом pending_verification, отправляет письмо со ссылкой подтверждения.
  4. Пользователь переходит по ссылке → is_email_verified = true, status = active.
  5. Онбординг: заказчика ведут в конструктор заказа; исполнителя — на обязательное заполнение профиля (модуль 3), без которого он не появляется в матчинге.

User Flow: вход через Google

  1. Клик «Войти через Google» → OAuth-редирект → согласие пользователя.
  2. Бэкенд получает email и google_id. Если email уже существует — аккаунты связываются (google_id прикрепляется к существующему users); если нет — создаётся новый аккаунт, is_email_verified = true сразу (email подтверждён Google).
  3. Если это первый вход — короткий шаг выбора роли (заказчик/исполнитель), т.к. Google не сообщает эту информацию.
Восстановление пароля — стандартный email-flow. Для пользователей, вошедших только через Google (password_hash IS NULL), кнопка «Забыли пароль» скрыта — предлагается «Войти через Google».

5 Модуль 2 — Конструктор заказа и автоподбор исполнителей

Мультимедийный конструктор — шаги формы

  1. Тип и категория: «Изготовление» или «Ремонт» → выбор категории работ (токарные, фрезерные, лазерная резка, сварка, ремонт редуктора и т.д. — из справочника service_categories).
  2. Описание: текстовое поле (в родном языке пользователя, content_language проставляется автоматически по preferred_language с возможностью переключить).
  3. Медиа: drag-and-drop загрузка фото, чертежей (PDF/JPG/DWG-превью), видео (до N МБ, с клиентским сжатием изображений перед отправкой).
  4. Параметры детали: материал, габариты (длина/диаметр/вес) — опционально, но повышают точность матчинга и обязательны, если заявка ищет исполнителя по станочным ограничениям.
  5. Локация и бюджет: адрес объекта (для ремонта) или регион доставки заготовки (для изготовления), опциональная вилка бюджета, желаемый срок и длительность аукциона (24 / 48 / 72 часа).
  6. Публикация → orders.status = published, запускается матчинг.

Логика автоматического подбора исполнителей

Сразу после публикации фоновая задача формирует ранжированный список кандидатов (order_matches) по формуле взвешенной суммы:

КритерийВесУсловие
Совпадение категории услугобязательноеservice_category входит в executor_capabilities.service_categories — исполнители без совпадения исключаются
Габариты/вес деталиобязательное (если указано)order.dimensions ≤ executor_capabilities.max_*
Материалвысокийсовпадение с materials
Гео-близостьвысокийобратно пропорционален distance_km (ST_Distance), приоритет своему региону/городу
Рейтинг исполнителясреднийrating_avg, при отсутствии отзывов — нейтральный вес
Активная подпискаобязательноеисполнитель без активной подписки в матчинг не попадает (модуль 7)
Приоритет тарифабонусsubscription_plans.catalog_priority поднимает исполнителя в списке
  1. Отбираются все исполнители, прошедшие обязательные фильтры.
  2. Оставшиеся ранжируются по сумме взвешенных критериев → топ-N (например, 15–20) получают уведомление.
  3. Уведомления рассылаются одновременно: Telegram-бот (модуль 7) + push в личном кабинете («Лента заказов»).
  4. Если через 25% времени аукциона ставок меньше 3 — система автоматически расширяет радиус поиска и/или ослабляет негеографические веса, дозаявляя дополнительных исполнителей.
  5. Если кандидатов не найдено вовсе — заказчику показывается уведомление «Пока нет подходящих исполнителей, мы оповестим вас, как только появится совпадение» и заказ остаётся в статусе matching с фоновым пересчётом при регистрации новых исполнителей.

6 Модуль 3 — Профиль и техвозможности исполнителя и заказчика

Профиль исполнителя не считается «опубликованным» (не участвует в матчинге и не виден в каталоге/на карте), пока не заполнен обязательный минимум: тип организации, регион/адрес с координатами, хотя бы одна позиция станочного парка и хотя бы одна запись в executor_capabilities с указанием габаритов и услуг.

User Flow: онбординг заказчика (полностью самостоятельный, без участия администратора)

  1. Сразу после подтверждения email (или первого входа через Google) заказчик попадает на экран «Заполните карточку компании» — минимум полей, чтобы не отпугнуть, но достаточно для будущих заказов.
  2. Кто вы: физ. лицо или компания (юр. лицо), название/ФИО, контактный телефон (совпадает с полем users.phone), опционально ИНН/СТИР.
  3. Локация объекта: адрес текстом + перетаскивание маркера на карте — та же механика геокодирования, что и у исполнителя (модуль 4), нужна для гео-поиска исполнителей «рядом» и для полей объявлений в модулях 8/9.
  4. Дальше — сразу шаг «Опубликовать первый заказ» (необязательный, можно пропустить и зайти в каталог/на карту). Никаких данных за заказчика никто не вводит — все поля заполняются им самим в личном кабинете, без обращения в поддержку или к администратору.
  5. Профиль можно донаполнить позже в /settings//profile в любой момент — обязательные поля блокируют только публикацию заказа, а не доступ в кабинет.
Тот же принцип действует и для объявлений барахолки (модуль 8), и для вакансий/резюме (модуль 9): автор объявления заполняет карточку сам через форму — администратор в этот процесс не вовлекается, кроме постмодерации по жалобам.

User Flow: онбординг исполнителя

  1. После регистрации — экран «Заполните профиль, чтобы начать получать заказы» с прогресс-баром (Основная информация → Локация → Станочный парк → Габариты и материалы → Подписка).
  2. Основная информация: тип (мастер/цех/завод), название, описание, контакты, ИНН/СТИР (для верификации).
  3. Локация: адрес вводится текстом + подтверждается перетаскиванием маркера на карте (геокодирование с ручной корректировкой), регион/город определяются автоматически по координатам.
  4. Станочный парк: мультивыбор из справочника оборудования + возможность добавить произвольную позицию с модерацией админом.
  5. Габариты и допуски: форма макс. длины, диаметра, ширины/высоты, веса; мультивыбор материалов и категорий услуг (то, что реально сверяется алгоритмом матчинга).
  6. Портфолио (опц.): фото выполненных работ — повышает конверсию в каталоге, не обязателен для матчинга.
  7. Заявка на верификацию уходит админу (бейдж «Проверено» после ручной/полуавтоматической проверки ИНН); профиль уже участвует в матчинге и до верификации, но с меньшим весом ранжирования.
  8. Выбор тарифа подписки → оплата (модуль 7) → профиль полностью активен.
Редактирование станочного парка и габаритов доступно в любой момент из /profile/equipment; изменения пересчитывают матчинг для новых заказов немедленно, но не затрагивают уже отправленные order_matches.

7 Модуль 4 — Карта и гео-поиск

  1. Заказчик открывает раздел «Карта» — интерактивная карта Узбекистана (Leaflet/MapLibre + тайлы, напр. 2GIS/OSM) со всеми верифицированными и активными (подписка не истекла) исполнителями в виде кластеризованных маркеров.
  2. Фильтры на карте: категория услуг, тип организации (мастер/цех/завод), минимальный рейтинг, только с активной подпиской.
  3. Клик по маркеру → всплывающая карточка с кратким описанием, рейтингом, ключевым оборудованием и ссылкой на полный профиль /executor/:id.
  4. Геопоиск «рядом»: кнопка «Показать рядом со мной» — браузерная геолокация или ручной ввод адреса объекта → SQL-запрос ST_DWithin(executor.location, point, radius) с выбором радиуса (5/10/25/50/100 км) — список сортируется по расстоянию.
  5. Из карты можно сразу перейти в конструктор заказа с предзаполненной локацией объекта или отправить точечный запрос конкретному исполнителю в обход общего аукциона («прямой заказ»).
Та же гео-логика используется в модуле 2 (автоподбор): координаты заказа сравниваются с координатами исполнителей через тот же индекс GIST, поэтому «Карта» и «Автоподбор» дают согласованные результаты.

8 Модуль 5 — Отзывы и рейтинги

  1. Отзыв становится доступен только когда orders.status = completed (заказчик подтвердил приёмку работы либо истёк срок автоматического подтверждения — 7 дней без возражений).
  2. Обеим сторонам приходит уведомление «Оцените сотрудничество» со ссылкой на форму отзыва по шкале 1–5 + детализация (для исполнителя: качество, соблюдение сроков, коммуникация; для заказчика: чёткость ТЗ, своевременность оплаты, коммуникация) + текстовый комментарий.
  3. Отзывы скрыты друг от друга до момента, пока не оставлены обе стороны, либо не истекло 14 дней с завершения заказа — после чего публикуются одновременно (защита от предвзятости «жду его оценку, чтобы ответить тем же»).
  4. Опубликованный отзыв пересчитывает rating_avg/reviews_count в профиле, влияет на позицию в каталоге, на карте и в весах матчинга (модуль 2).
  5. Жалоба на недостоверный отзыв → эскалация админу (не путать с арбитражем по заказу, модуль 6) с возможностью скрыть отзыв до проверки.

9 Модуль 6 — Арбитраж и медиация споров

Статусы спора: open evidence_collection in_review resolved_customer / resolved_executor / resolved_partial closed

  1. Любая сторона на заказе в статусе in_progress или completed (в течение 14 дней после завершения) нажимает «Открыть спор», указывает причину (брак / задержка / претензия по качеству / расхождение по цене / другое) и текстовое описание.
  2. orders.status → disputed; вторая сторона получает уведомление и обязана ответить в течение 48 часов.
  3. Сбор доказательств: обе стороны загружают фото/видео в dispute_evidence — заказчик подтверждает брак/несоответствие, исполнитель — процесс и результат работы, соответствие ТЗ. Возможна переписка в dispute_messages.
  4. Спор автоматически или вручную (по кнопке «Готово к рассмотрению» от обеих сторон, либо по истечении 5 дней сбора доказательств) переходит в in_review и назначается администратору из очереди (round-robin / по загрузке).
  5. Администратор изучает доказательства, при необходимости запрашивает дополнительные материалы (внутренние заметки/сообщения сторонам), выносит решение с обоснованием.
  6. Решение фиксируется (resolution_text, итоговый статус), обе стороны уведомляются, заказ переводится в финальный статус (completed с пометкой о споре или cancelled).
  7. Решение влияет на рейтинг: спор, решённый в пользу заказчика, может блокировать публикацию заведомо положительного отзыва исполнителя по этому заказу (анти-накрутка).
Рекомендация на будущее (вне текущего ТЗ, но важно для зрелости платформы): ввести эскроу — оплата исполнителю удерживается платформой до подтверждения приёмки заказчиком, а при открытии спора автоматически замораживается до решения администратора. Это резко повышает эффективность арбитража, так как решение админа получает реальный денежный вес, а не только репутационный.

10 Модуль 7 — Telegram-бот, аукцион и монетизация

Привязка Telegram

  1. В разделе «Настройки» исполнитель нажимает «Подключить Telegram» → бот генерирует одноразовый deep-link (t.me/PlatformBot?start=<token>).
  2. Исполнитель открывает бота, нажимает Start → бэкенд по токену связывает telegram_chat_id с users.id в таблице telegram_links.

Тарифные планы (пример)

ТарифСтавок в мес.Скорость Telegram-алертовПриоритет в матчинге
Basicдо 10с задержкой ~2 часабазовый
Standardдо 40мгновенносредний
Proбез лимитамгновенно + первым в очередимаксимальный

Оплата — Payme / Click / Uzcard (локальные для Узбекистана), автопродление ежемесячно; при subscription.status = expired исполнитель исключается из автоподбора (модуль 2) и скрывается из каталога/карты, но сохраняет доступ к своему кабинету и истории заказов для продления.

User Flow: аукцион заказа целиком

  1. Заказ опубликован → топ-N исполнителей получают Telegram-сообщение: краткое описание, категория, регион, фото (первое из вложений), кнопка «Открыть заказ на платформе».
  2. Исполнитель переходит по ссылке (deep-link открывает конкретный /orders/:id в мини-приложении/браузере), изучает полное описание, чертежи, видео.
  3. Исполнитель подаёт ставку: цена, срок выполнения, комментарий/условия → bids, статус active. Проверяется лимит max_bids_per_month тарифа.
  4. Заказчик в реальном времени видит список ставок (сортировка по цене/сроку/рейтингу), может открыть чат с конкретным исполнителем для уточнений — чат разблокируется при первом сообщении заказчика.
  5. Исполнители, чью ставку «обошли» более выгодным предложением, получают Telegram-уведомление «Вас обошли по цене» с возможностью подать новую ставку (пока аукцион открыт) — механика реального тендера.
  6. По истечении auction_deadline_at либо по ручному выбору заказчика раньше срока — заказчик нажимает «Выбрать исполнителя» на понравившейся ставке → order_assignments создаётся, orders.status → assigned, остальные ставки помечаются rejected, все участники получают итоговое уведомление (Telegram + in-app).
  7. Исполнитель отмечает начало работ (in_progress) и завершение (completed, с подтверждением заказчика) → запускаются модуль 5 (отзывы) и, при необходимости, модуль 6 (спор).

Команды Telegram-бота (минимальный набор)

  • /start — привязка аккаунта
  • /orders — список актуальных заказов, подобранных под профиль
  • /mybids — статус поданных ставок
  • /subscription — статус подписки, ссылка на продление
  • Настройка уведомлений (вкл/выкл по категориям) прямо в чате бота

10.4 Единый принцип: любой отклик = мгновенное Telegram-уведомление второй стороне

Бот не ограничен только заказами и ставками — правило одинаково применяется ко всем «объявленческим» модулям платформы (2, 8, 9): у каждого объявления есть автор и есть тот, кто на него откликается; как только в БД появляется новая запись отклика, фоновый воркер немедленно (либо с задержкой тарифа, см. 10.2) отправляет уведомление стороне-получателю через все привязанные каналы (Telegram + in-app).

Событие (кто откликнулся)Кто получает Telegram-уведомлениеТаблица-источник
Исполнитель подал ставку на заказЗаказчик — автор заказаbids
Заказчик выбрал/отклонил ставкуИсполнитель — автор ставкиorder_assignments / bids.status
Кто-то откликнулся на объявление барахолкиАвтор объявления (продавец или покупатель)listing_responses
Соискатель откликнулся на вакансиюРаботодатель — автор вакансииjob_responses (direction = candidate_to_employer)
Работодатель пригласил по резюмеСоискатель — автор резюмеjob_responses (direction = employer_to_candidate)

Иными словами: сторона, которая разместила заказ, объявление, вакансию или резюме, всегда узнаёт об отклике первой и мгновенно — независимо от того, заказчик она, исполнитель, продавец, покупатель, работодатель или работник. Это одна и та же нотификационная шина (notifications + telegram_links), просто с разными значениями поля type (см. 2.7).

11 Модуль 8 — Барахолка: купля-продажа станков и инструмента

Отдельная от заявок на изготовление/ремонт витрина объявлений: продажа и покупка конкретной физической единицы оборудования, инструмента, запчастей или расходников. Доступна и заказчикам, и исполнителям — по сути это классифайд внутри той же аудитории, что снижает трение (не нужно уходить на сторонние площадки, чтобы продать списанный станок или найти б/у пресс).

User Flow: разместить объявление «Продаю»

  1. Раздел «Барахолка» → «Разместить объявление» → тип «Продаю».
  2. Категория (станки с ЧПУ, ручной инструмент, сварочное оборудование, запчасти, расходники…), название, описание, состояние (новое/б.у./на запчасти).
  3. Фото/видео товара, цена (или пометка «Договорная»), регион/город, при желании — точка на карте.
  4. Публикация → listings.status = active, объявление сразу видно в общей ленте и на карте (если указана локация); дополнительная модерация не блокирует публикацию, а работает постфактум по жалобам.

User Flow: разместить объявление «Куплю» и откликнуться на чужое объявление

  1. Тип «Куплю» — та же форма, но без поля «состояние»: пользователь описывает, какой станок/инструмент ищет, и в каком бюджете.
  2. Любой пользователь может как откликнуться на «Продаю» (написать продавцу, предложить свою цену), так и на «Куплю» (предложить свой товар тому, кто ищет) — форма отклика одна и та же, различается лишь то, кто выступает инициатором.
  3. Отклик создаёт запись в listing_responses → автору объявления мгновенно приходит Telegram-уведомление + уведомление в кабинете (см. 10.4), открывается чат по объявлению.
  4. Автор объявления помечает его «Зарезервировано» либо «Закрыто» (сделка состоялась) — listings.status; объявление уходит из активной ленты, но остаётся в истории пользователя.
Модуль намеренно не включает встроенный эскроу/оплату на MVP — платформа сводит стороны и уведомляет, а расчёт происходит вне системы (как в классических классифайдах); это можно пересмотреть на более поздних этапах по аналогии с рекомендацией по эскроу в модуле 6.

12 Модуль 9 — Вакансии и резюме (найм персонала)

Двусторонняя доска объявлений о работе: работодатели (цеха, заводы, предприятия-заказчики) публикуют вакансии, а соискатели — резюме. Отклик возможен инициативой любой стороны, поэтому Telegram-бот должен одинаково уведомлять и работодателя, и соискателя — не только «работодатель ищет», но и «работника заметили».

User Flow: работодатель размещает вакансию

  1. Раздел «Вакансии» → «Разместить вакансию»: профессия (из справочника profession_categories — токарь, фрезеровщик, сварщик, наладчик ЧПУ, слесарь-ремонтник и т.д.), описание обязанностей/требований.
  2. Тип занятости, вилка зарплаты, регион/город → публикация, vacancies.status = active.
  3. Вакансия автоматически показывается в ленте всем соискателям региона/профессии (без отдельного алгоритма матчинга — простая фильтрация по profession_category + region_id, в отличие от сложного матчинга заказов в модуле 2).
  4. Работодатель также может зайти в раздел «Резюме», отфильтровать по профессии/региону и первым пригласить понравившегося кандидата — это создаёт job_responses с direction = employer_to_candidate.

User Flow: соискатель откликается на вакансию

  1. Любой зарегистрированный пользователь может создать резюме в разделе «Резюме» — это не требует роли customer/executor, достаточно базовой учётной записи (модуль 1).
  2. Соискатель просматривает ленту вакансий, открывает подходящую, нажимает «Откликнуться» → создаётся job_responses с direction = candidate_to_employer, можно приложить сообщение.
  3. Работодатель мгновенно получает Telegram-уведомление «Новый отклик на вакансию “…”» (см. 10.4) и переходит либо к резюме кандидата, либо сразу в чат.
  4. Симметрично: если работодатель первым пригласил кандидата по резюме, уведомление о приглашении мгновенно получает сам соискатель — платформа не делает работника «вторым сортом» по сравнению с работодателем.
Справочник profession_categories частично пересекается со справочником услуг заказов (service_categories из модуля 2) — токарь/фрезеровщик/сварщик актуальны и там, и там, поэтому их стоит вести как связанные, но разные таблицы: одна описывает компетенцию организации (что умеет цех), другая — компетенцию человека (кого ищет работодатель).

13 Нефункциональные требования

🔒 Безопасность

  • Хеширование паролей (bcrypt/argon2), OAuth 2.0 для Google, JWT + refresh-токены.
  • Права доступа проверяются и на фронтенде (route-guard/paywall), и обязательно на бэкенде (RLS/сервисный слой) — фронтовая блокировка не является security-контролем.
  • Подписанные временные URL для загруженных медиа (не публичные пути к бакету).
  • Rate-limiting на подачу ставок и создание заказов — анти-спам/анти-накрутка.

🌍 Мультиязычность

  • UI-строки — статические словари ru / uz_latin / uz_cyrillic, переключатель языка в шапке, сохраняется в users.preferred_language.
  • Латиница и кириллица узбекского — не автоматическая транслитерация «на лету» для UGC (риск ошибок), а выбор языка ввода самим пользователем + опциональный машинный перевод (content_translations) для контента другого языка.

📈 Масштабируемость

  • Матчинг и рассылка уведомлений — асинхронные фоновые задачи (очередь), не блокируют публикацию заказа.
  • Геозапросы — индексы PostGIS, кэш каталога/карты (Redis) с инвалидацией по изменению профиля.

⚖️ Соответствие законодательству РУз

  • Закон «О персональных данных» РУз — согласие на обработку данных при регистрации, хранение данных с учётом требований локализации.
  • Интеграция локальных платёжных систем (Payme, Click, Uzcard/Humo) для подписки исполнителей.

14 Рекомендуемый технологический стек

СлойТехнологияКомментарий
BackendNode.js (NestJS) или Python (FastAPI/Django)REST/GraphQL API, фоновые задачи через очередь (BullMQ/Celery)
БДPostgreSQL + PostGISгео-запросы, полнотекстовый поиск
Кэш/очередиRedisкэш каталога/карты, очередь уведомлений
Хранилище файловS3-совместимое (MinIO / облако)фото, чертежи, видео, доказательства споров
Frontend WebReact/Next.js или Vue/NuxtSSR для главной (SEO), SPA для закрытой зоны
КартаMapLibre GL / Leaflet + тайлы 2ГИС или OSM2ГИС даёт лучшее покрытие адресов Узбекистана
TelegramTelegram Bot API (webhook)уведомления, deep-links, мини-приложение (Telegram Web App) — опционально для подачи ставки прямо в боте
ПлатежиPayme Business API, Click Merchant API, Uzcardподписочные платежи
AuthEmail/пароль + Google OAuth 2.0как указано в ТЗ

15 Дорожная карта

MVP (этап 1)

  • Регистрация email/Google + paywall, полностью самостоятельное заполнение профиля заказчиком и исполнителем (без ручного ввода админом)
  • Конструктор заказа (текст+фото), профиль исполнителя (базовые габариты/оборудование)
  • Автоподбор + карта + гео-поиск
  • Ставки/аукцион без Telegram (только in-app уведомления)
  • Барахолка и вакансии/резюме — упрощённая версия (без Telegram, только in-app отклики)
  • Отзывы, простая подписка (ручная активация админом)

Этап 2

  • Telegram-бот с уведомлениями и deep-links — распространяется на все виды откликов (заказы, барахолка, вакансии/резюме) по единой шине уведомлений
  • Онлайн-оплата подписки (Payme/Click)
  • Модуль арбитража с загрузкой доказательств
  • Видео в заявках, машинный перевод UGC

Этап 3

  • Telegram Mini App для подачи ставок и откликов прямо в боте
  • Эскроу-платежи по заказам
  • Расширенная аналитика/рейтинги для админа, антифрод-скоринг отзывов

↑ Наверх