Backoffice stage 3: orders and providers, auto-assignment

- Auto-assignment: a new order is offered to the best free provider (same ranking as
  the dispatcher hint); a decline or timeout passes it to the next one; nobody is
  offered the same order twice; after N tries it waits for a dispatcher and the team
  is notified once. The worker retries waiting orders every minute. Settings
  assign.auto / assign.maxAutoOffers (toggle on the Orders page, checkbox in old admin).
- Orders: list by day with groups (need a provider, offered, assigned, in progress,
  done, cancelled), periods and search; order card with progress, provider or ranked
  candidates ("offer" / "pick automatically"), details, checklist and photos,
  history in plain words, client, money (superadmin), payment, status change,
  reschedule (slots or custom time), cancel with fee.
- New order for a client (from the client card or the Orders page): params form,
  address, free slots for 14 days, price with the client's discount.
- Providers: list with rating, orders, declines, low ratings, next order; card with
  quality, ratings, upcoming orders, schedule and districts, admission documents,
  block/unblock with reason, new bot link, money and payouts (superadmin).
- Payout gets paidAt/note/createdById; Provider gets blockedReason/blockedAt/adminNote
  and documents (ProviderDoc).
- Tests for auto-assignment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zabudko-ruandClaude Opus 5.5 committed 2026-10-21 02:25:41 +04:00
1 parent 2772837e29
commit e7de4f5893
33 files changed
+4521 -45

No files matched your search

+28
View File
@@ -290,10 +290,16 @@ model Provider {
/// пресет ставок (джун / мидл / прайват); если задан — заменяет rateType/rateValue
payoutPresetId String?
payoutPreset PayoutPreset? @relation(fields: [payoutPresetId], references: [id])
/// допуск: почему заблокирован и когда (active = false)
blockedReason String?
blockedAt DateTime?
/// заметка команды об исполнителе (он не видит)
adminNote String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
districts ProviderDistrict[]
docs ProviderDoc[]
schedule ProviderSchedule[]
orders Order[]
offers OrderOffer[]
@@ -305,6 +311,22 @@ model Provider {
chatThreads ChatThread[]
}
/// Документы исполнителя для допуска: паспорт, договор, справка… Файл — в приватном хранилище
model ProviderDoc {
id String @id @default(cuid())
providerId String
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
title String
fileKey String
fileName String
mime String
size Int
uploadedById String?
createdAt DateTime @default(now())
@@index([providerId])
}
/// Пресет ставок исполнителя: для каждой строки цены — фикс или % (см. packages/core/src/payout.ts)
model PayoutPreset {
id String @id @default(cuid())
@@ -832,7 +854,13 @@ model Payout {
periodEnd DateTime
amount Int
status PayoutStatus @default(DRAFT)
/// когда выплачено и как (наличные, перевод…), кто отметил
paidAt DateTime?
note String?
createdById String?
createdAt DateTime @default(now())
@@index([providerId, createdAt])
}
// ───────────── Админка ─────────────