Bariki cashback, order edit, and current work in progress
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
dda917fc71
commit
45477fb018
94 files changed
+10955
-650
No files matched your search
@@ -97,6 +97,14 @@ enum PaymentStatus {
|
||||
REFUNDED
|
||||
}
|
||||
|
||||
/// Модель сотрудничества с партнёром услуг
|
||||
enum PartnerDeal {
|
||||
/// расходы наши: платим ФОТ и налог, партнёру — % от прибыли
|
||||
PROFIT_SHARE
|
||||
/// как Uber: забираем свой % с заказа, остальное партнёру, расходы — его
|
||||
COMMISSION
|
||||
}
|
||||
|
||||
enum RateType {
|
||||
FIXED
|
||||
PERCENT
|
||||
@@ -190,6 +198,7 @@ model User {
|
||||
dispatcher CompanyDispatcher[]
|
||||
partnerMembers PartnerMember[]
|
||||
messages Message[]
|
||||
proWebSessions ProWebSession[]
|
||||
}
|
||||
|
||||
model Client {
|
||||
@@ -231,6 +240,25 @@ model Client {
|
||||
/// null — по умолчанию: подписчик → консьерж, остальные → поддержка
|
||||
chatChannel ChatChannel?
|
||||
|
||||
// ── барики (кэшбек) — кэш баланса; источник правды — журнал PointsAccrual / PointsRedemption ──
|
||||
/// доступно: остатки активных начислений минус долг
|
||||
pointsActive Int @default(0)
|
||||
/// в ожидании (станут доступны позже)
|
||||
pointsPending Int @default(0)
|
||||
/// долг после отмены уже потраченного начисления; гасится следующими начислениями
|
||||
pointsDebt Int @default(0)
|
||||
/// последнее начисление или списание — от неё считается сгорание
|
||||
pointsActivityAt DateTime?
|
||||
/// предупреждения о сгорании уже отправлены (сбрасываются при активности)
|
||||
pointsWarn14At DateTime?
|
||||
pointsWarn3At DateTime?
|
||||
/// серия уборок: сколько подряд с перерывами не больше N дней
|
||||
streakCount Int @default(0)
|
||||
streakLastAt DateTime?
|
||||
pointsAccruals PointsAccrual[]
|
||||
pointsRedemptions PointsRedemption[]
|
||||
pointsSuggestions PointsSuggestion[]
|
||||
|
||||
@@index([agencyId])
|
||||
}
|
||||
|
||||
@@ -245,11 +273,28 @@ model ProviderCompany {
|
||||
rateType RateType @default(PERCENT)
|
||||
rateValue Int @default(70)
|
||||
contactPhone String?
|
||||
/// ссылка менеджера: t.me/<рабочий бот>?start=<inviteCode>
|
||||
inviteCode String @unique
|
||||
active Boolean @default(true)
|
||||
/// модель сотрудничества (rateType/rateValue — устаревшая ставка, не используется)
|
||||
deal PartnerDeal @default(COMMISSION)
|
||||
/// COMMISSION: наш % с оплаченной суммы заказа
|
||||
commissionPct Int @default(30)
|
||||
/// PROFIT_SHARE: % партнёра от (оплачено − ФОТ − налог)
|
||||
profitSharePct Int @default(50)
|
||||
/// PROFIT_SHARE: свой ФОТ клинера, % от заказа; пусто — из настроек
|
||||
fotPct Int?
|
||||
/// PROFIT_SHARE: свой налог, %; пусто — из настроек
|
||||
taxPct Int?
|
||||
/// менеджер включил автораспределение по сотрудникам
|
||||
autoAssign Boolean @default(false)
|
||||
/// перерыв между заказами сотрудника на дорогу, мин (не меньше минимума из настроек)
|
||||
travelGapMinutes Int @default(60)
|
||||
note String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
earnings PartnerEarning[]
|
||||
employees Provider[]
|
||||
dispatchers CompanyDispatcher[]
|
||||
orders Order[]
|
||||
@@ -295,6 +340,9 @@ model Provider {
|
||||
blockedAt DateTime?
|
||||
/// заметка команды об исполнителе (он не видит)
|
||||
adminNote String?
|
||||
/// нагрузка (задаёт менеджер партнёра): максимум заказов и часов в день; пусто — без ограничения
|
||||
maxOrdersPerDay Int?
|
||||
maxHoursPerDay Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -391,6 +439,9 @@ model Service {
|
||||
code String @unique
|
||||
categoryId String
|
||||
category Category @relation(fields: [categoryId], references: [id])
|
||||
/// категория кэшбека «Барики» (ставки Barev / Barev+ / Elite)
|
||||
pointsCategoryId String?
|
||||
pointsCategory PointsCategory? @relation(fields: [pointsCategoryId], references: [id])
|
||||
flow ServiceFlow
|
||||
providerRole ProviderRole
|
||||
/// правила цены (см. packages/core/src/pricing)
|
||||
@@ -462,6 +513,8 @@ model Order {
|
||||
discountPct Int @default(0)
|
||||
discountAmount Int @default(0)
|
||||
totalPrice Int
|
||||
/// оплачено бариками (к оплате деньгами: totalPrice − pointsUsed)
|
||||
pointsUsed Int @default(0)
|
||||
status OrderStatus @default(NEW)
|
||||
providerId String?
|
||||
provider Provider? @relation(fields: [providerId], references: [id])
|
||||
@@ -493,6 +546,9 @@ model Order {
|
||||
lessons LessonPackage?
|
||||
payments Payment[]
|
||||
commission AgencyCommission?
|
||||
partnerEarning PartnerEarning?
|
||||
pointsAccruals PointsAccrual[]
|
||||
pointsRedemptions PointsRedemption[]
|
||||
tasks OperatorTask[]
|
||||
webRequest WebRequest?
|
||||
|
||||
@@ -548,6 +604,10 @@ model OrderChecklistItem {
|
||||
checkedAt DateTime?
|
||||
fileId String? @unique
|
||||
file OrderFile? @relation(fields: [fileId], references: [id])
|
||||
/// пункт отметила команда за исполнителя (id сотрудника админки)
|
||||
checkedByAdminId String?
|
||||
/// отмечено без фото — причина (команда)
|
||||
photoWaivedReason String?
|
||||
|
||||
@@unique([orderId, code])
|
||||
}
|
||||
@@ -662,6 +722,12 @@ model Agency {
|
||||
name String
|
||||
/// вознаграждение, % от оплаченных заказов клиентов агентства
|
||||
commissionPct Int @default(10)
|
||||
/// подарок новым клиентам: уровень (пусто — без подарка) и срок в месяцах
|
||||
giftPlan SubscriptionPlan? @default(BAREV_PLUS)
|
||||
giftMonths Int @default(1)
|
||||
/// барики на новоселье: сумма (0 — нет) и срок жизни в днях
|
||||
housewarmingPoints Int @default(0)
|
||||
housewarmingDays Int @default(60)
|
||||
contact String?
|
||||
active Boolean @default(true)
|
||||
/// продажник, который подключил агентство (бонус в CRM)
|
||||
@@ -694,6 +760,30 @@ model AgencyCommission {
|
||||
@@index([agencyId, period])
|
||||
}
|
||||
|
||||
/// Доход партнёра услуг с выполненного заказа — фиксируется при завершении, чтобы смена условий не меняла прошлое
|
||||
model PartnerEarning {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company ProviderCompany @relation(fields: [companyId], references: [id])
|
||||
orderId String @unique
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
deal PartnerDeal
|
||||
/// фактически оплачено клиентом
|
||||
paid Int
|
||||
/// PROFIT_SHARE: ФОТ и налог, которые несём мы
|
||||
fot Int @default(0)
|
||||
tax Int @default(0)
|
||||
/// % партнёра (PROFIT_SHARE) или наша комиссия (COMMISSION)
|
||||
pct Int
|
||||
/// доход партнёра
|
||||
amount Int
|
||||
/// YYYY-MM
|
||||
period String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([companyId, period])
|
||||
}
|
||||
|
||||
model PromoCode {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
@@ -998,11 +1088,11 @@ model WebRequest {
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
params Json
|
||||
/// желаемое время, оператор подтверждает
|
||||
preferredAt DateTime
|
||||
preferredAt DateTime?
|
||||
durationMinutes Int
|
||||
districtId String
|
||||
district District @relation(fields: [districtId], references: [id])
|
||||
addressText String
|
||||
districtId String?
|
||||
district District? @relation(fields: [districtId], references: [id])
|
||||
addressText String?
|
||||
note String?
|
||||
phone String
|
||||
name String?
|
||||
@@ -1435,3 +1525,218 @@ model BroadcastRecipient {
|
||||
@@unique([broadcastId, clientId])
|
||||
@@index([broadcastId, status])
|
||||
}
|
||||
|
||||
/// Вход в кабинет менеджера партнёра с компьютера: бот выдаёт одноразовую ссылку (code), браузер меняет её на токен
|
||||
model ProWebSession {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
/// sha256 одноразового кода из ссылки; после входа обнуляется
|
||||
codeHash String? @unique
|
||||
codeExpiresAt DateTime?
|
||||
/// sha256 токена браузера
|
||||
tokenHash String? @unique
|
||||
expiresAt DateTime
|
||||
lastUsedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// ───────────── Барики: кэшбек клуба (1 барик = 1 ֏) ─────────────
|
||||
|
||||
enum PointsKind {
|
||||
ORDER
|
||||
BONUS
|
||||
}
|
||||
|
||||
enum PointsStatus {
|
||||
/// создано, тратить нельзя до availableAt
|
||||
PENDING
|
||||
ACTIVE
|
||||
/// израсходовано полностью
|
||||
SPENT
|
||||
/// сгорело
|
||||
EXPIRED
|
||||
/// отменено
|
||||
REVERSED
|
||||
}
|
||||
|
||||
enum PointsChannel {
|
||||
WEB
|
||||
BOT
|
||||
CSV
|
||||
AUTO
|
||||
CLIENT
|
||||
}
|
||||
|
||||
enum RedemptionStatus {
|
||||
REQUESTED
|
||||
CONFIRMED
|
||||
REJECTED
|
||||
REVERSED
|
||||
}
|
||||
|
||||
enum SuggestionStatus {
|
||||
OPEN
|
||||
ACCEPTED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
/// Категория кэшбека со ставками по уровням, %
|
||||
model PointsCategory {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
/// { ru, en, zh, hy, es }
|
||||
name Json
|
||||
rateBarev Int
|
||||
ratePlus Int
|
||||
rateElite Int
|
||||
active Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
services Service[]
|
||||
accruals PointsAccrual[]
|
||||
}
|
||||
|
||||
/// Тип бонуса: новоселье, отзыв, компенсация…
|
||||
model PointsBonusType {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
name Json
|
||||
defaultAmount Int
|
||||
/// срок жизни бонуса в днях; пусто — общее правило сгорания
|
||||
ttlDays Int?
|
||||
active Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
accruals PointsAccrual[]
|
||||
}
|
||||
|
||||
/// Начисление бариков (журнал: не удаляется и не правится, кроме остатка и статуса)
|
||||
model PointsAccrual {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
kind PointsKind
|
||||
categoryId String?
|
||||
category PointsCategory? @relation(fields: [categoryId], references: [id])
|
||||
bonusTypeId String?
|
||||
bonusType PointsBonusType? @relation(fields: [bonusTypeId], references: [id])
|
||||
/// заказ в системе (автоначисление)
|
||||
orderId String?
|
||||
order Order? @relation(fields: [orderId], references: [id])
|
||||
/// номер заказа для сверки; среди неотменённых начислений уникален (индекс в миграции)
|
||||
orderRef String?
|
||||
orderAmount Int?
|
||||
excludedAmount Int @default(0)
|
||||
paidWithPoints Int @default(0)
|
||||
/// BAREV | BAREV_PLUS | ELITE — на дату услуги
|
||||
tier String?
|
||||
ratePct Int?
|
||||
/// надбавка за серию уборок, п.п.
|
||||
streakBonusPct Int @default(0)
|
||||
suggestedAmount Int?
|
||||
amount Int
|
||||
remaining Int
|
||||
/// сколько сгорело
|
||||
expiredAmount Int @default(0)
|
||||
status PointsStatus
|
||||
serviceDate DateTime
|
||||
availableAt DateTime
|
||||
/// свой срок жизни (бонусы); пусто — общее правило сгорания
|
||||
expiresAt DateTime?
|
||||
reason String?
|
||||
commentForClient String?
|
||||
createdById String?
|
||||
channel PointsChannel
|
||||
idempotencyKey String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
items PointsRedemptionItem[]
|
||||
|
||||
@@index([clientId, status])
|
||||
@@index([status, availableAt])
|
||||
@@index([orderId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
/// Списание (оплата части заказа бариками)
|
||||
model PointsRedemption {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
orderId String?
|
||||
order Order? @relation(fields: [orderId], references: [id])
|
||||
orderRef String?
|
||||
orderAmount Int?
|
||||
amount Int
|
||||
status RedemptionStatus
|
||||
channel PointsChannel
|
||||
/// комментарий клиента к запросу
|
||||
note String?
|
||||
requestedAt DateTime @default(now())
|
||||
expiresAt DateTime?
|
||||
confirmedById String?
|
||||
confirmedAt DateTime?
|
||||
reason String?
|
||||
idempotencyKey String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
items PointsRedemptionItem[]
|
||||
|
||||
@@index([clientId, status])
|
||||
@@index([status, requestedAt])
|
||||
}
|
||||
|
||||
/// Из каких начислений списано (FIFO)
|
||||
model PointsRedemptionItem {
|
||||
id String @id @default(cuid())
|
||||
redemptionId String
|
||||
redemption PointsRedemption @relation(fields: [redemptionId], references: [id])
|
||||
accrualId String
|
||||
accrual PointsAccrual @relation(fields: [accrualId], references: [id])
|
||||
amount Int
|
||||
|
||||
@@index([accrualId])
|
||||
}
|
||||
|
||||
/// Отмена операции — обратная запись со ссылкой на исходную
|
||||
model PointsReversal {
|
||||
id String @id @default(cuid())
|
||||
/// ACCRUAL | REDEMPTION
|
||||
targetType String
|
||||
targetId String
|
||||
clientId String
|
||||
amount Int
|
||||
/// сколько ушло в долг (отмена уже потраченного начисления)
|
||||
debt Int @default(0)
|
||||
createdById String?
|
||||
channel PointsChannel
|
||||
reason String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([targetType, targetId])
|
||||
@@index([clientId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
/// Предложено системой: начислить бонус или подтвердить операцию оператора сверх лимита
|
||||
model PointsSuggestion {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
/// route | photo_review | over_limit | …
|
||||
mechanic String
|
||||
/// чтобы одна и та же причина не создавала карточку дважды
|
||||
dedupeKey String @unique
|
||||
payload Json
|
||||
suggestedAmount Int
|
||||
status SuggestionStatus @default(OPEN)
|
||||
resolvedById String?
|
||||
resolvedAt DateTime?
|
||||
reason String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([status, createdAt])
|
||||
}
|
||||
Reference in new issue
Block a user