Add STO booking and maintenance automation

This commit is contained in:
VPN SaaS Dev
2026-05-15 05:17:54 +09:00
parent 2be7ba2099
commit fec9635079
12 changed files with 2178 additions and 5 deletions

View File

@@ -88,6 +88,28 @@ class ApiClient:
async def public_service_centers(self, telegram_id: int) -> list[dict[str, Any]]:
return await self.request("GET", "/api/service-centers/public", telegram_id=telegram_id)
async def sto_catalog(self, telegram_id: int) -> list[dict[str, Any]]:
return await self.request("GET", "/api/sto/catalog", telegram_id=telegram_id)
async def my_appointments(self, telegram_id: int) -> list[dict[str, Any]]:
return await self.request("GET", "/api/appointments/my", telegram_id=telegram_id)
async def sto_dashboard(self, telegram_id: int, service_center_id: int) -> dict[str, Any]:
return await self.request(
"GET",
"/api/sto/dashboard",
telegram_id=telegram_id,
params={"service_center_id": service_center_id},
)
async def sto_appointments(self, telegram_id: int, service_center_id: int) -> list[dict[str, Any]]:
return await self.request(
"GET",
"/api/sto/appointments",
telegram_id=telegram_id,
params={"service_center_id": service_center_id, "status": "requested"},
)
async def my_service_centers(self, telegram_id: int) -> list[dict[str, Any]]:
return await self.request("GET", "/api/service-centers/my", telegram_id=telegram_id)

View File

@@ -271,7 +271,7 @@ async def analytics(message: Message) -> None:
async def sto(message: Message) -> None:
await upsert(message)
try:
centers = await api.public_service_centers(message.from_user.id)
centers = await api.sto_catalog(message.from_user.id)
except httpx.HTTPStatusError:
centers = []
if not centers:
@@ -281,12 +281,66 @@ async def sto(message: Message) -> None:
)
return
text = "Проверенные СТО:\n" + "\n".join(
f"{item['id']}. {item.get('display_name') or item.get('name')}{item.get('city') or 'город не указан'}"
(
f"{item['id']}. {item.get('display_name') or item.get('name')}{item.get('city') or 'город не указан'}"
f"{' · ближайшее окно ' + item['nearest_slot_at'][:16].replace('T', ' ') if item.get('nearest_slot_at') else ''}"
)
for item in centers[:10]
)
await message.answer(text, reply_markup=webapp_inline_keyboard("Каталог СТО"))
@dp.message(Command("appointments"))
async def appointments(message: Message) -> None:
await upsert(message)
try:
items = await api.my_appointments(message.from_user.id)
except httpx.HTTPStatusError as error:
await message.answer(f"Записи не загрузились: {error.response.text}")
return
if not items:
await message.answer(
"Активных записей пока нет. В Mini App можно выбрать авто, СТО и свободное окно.",
reply_markup=webapp_inline_keyboard("Записаться в СТО"),
)
return
lines = ["Ваши записи:"]
for item in items[:10]:
lines.append(
f"#{item['id']} {item['service_name']}{item['requested_start_at'][:16].replace('T', ' ')} · {item['status']}"
)
await message.answer("\n".join(lines), reply_markup=webapp_inline_keyboard("Мои записи"))
@dp.message(Command("sto_bookings"))
async def sto_bookings(message: Message) -> None:
await upsert(message)
try:
centers = await api.my_service_centers(message.from_user.id)
except httpx.HTTPStatusError as error:
await message.answer(f"Кабинет СТО не доступен: {error.response.text}")
return
if not centers:
await message.answer("У вас пока нет СТО. Подайте заявку через /register_sto или Mini App.")
return
center = centers[0]
try:
dashboard = await api.sto_dashboard(message.from_user.id, center["id"])
pending = await api.sto_appointments(message.from_user.id, center["id"])
except httpx.HTTPStatusError as error:
await message.answer(f"Заявки СТО не загрузились: {error.response.text}")
return
lines = [
f"Кабинет СТО: {center.get('display_name') or center.get('name')}",
f"Авто: {dashboard['connected_vehicles']}",
f"Новые заявки: {dashboard['pending_appointments']}",
f"Подтверждено: {dashboard['confirmed_appointments']}",
]
for item in pending[:8]:
lines.append(f"#{item['id']} {item['service_name']}{item['requested_start_at'][:16].replace('T', ' ')}")
await message.answer("\n".join(lines), reply_markup=webapp_inline_keyboard("Кабинет СТО"))
@dp.message(Command("register_sto"))
async def register_sto(message: Message, command: CommandObject) -> None:
await upsert(message)
@@ -464,7 +518,11 @@ async def help_message(message: Message) -> None:
"• /insurance, /tax, /fine — регулярные и разовые расходы;\n"
"• /analytics — стоимость владения и расход;\n"
"• /sto — каталог проверенных СТО;\n"
"• /appointments — мои записи в СТО;\n"
"• /sto_bookings — заявки и календарь для владельца СТО;\n"
"• /register_sto — заявка на СТО.\n\n"
"Для ТО: в карточке авто Mini App показывает рекомендации, доступные СТО, свободные окна, запись и согласование времени.\n"
"Для СТО: настрой график, принимай заявки, создавай заказ-наряд из подтвержденной записи и отправляй клиенту результат работ.\n\n"
"Mini App открывай только кнопкой под сообщением: Telegram передает initData, и авторизация проходит корректно.",
reply_markup=menu_inline_keyboard(),
)