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
@@ -0,0 +1,63 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PartnerDeal" AS ENUM ('PROFIT_SHARE', 'COMMISSION');
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "WebRequest" DROP CONSTRAINT "WebRequest_districtId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Agency" ADD COLUMN "giftMonths" INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN "giftPlan" "SubscriptionPlan" DEFAULT 'BAREV_PLUS';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Provider" ADD COLUMN "maxHoursPerDay" INTEGER,
|
||||
ADD COLUMN "maxOrdersPerDay" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ProviderCompany" ADD COLUMN "autoAssign" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "commissionPct" INTEGER NOT NULL DEFAULT 30,
|
||||
ADD COLUMN "deal" "PartnerDeal" NOT NULL DEFAULT 'COMMISSION',
|
||||
ADD COLUMN "fotPct" INTEGER,
|
||||
ADD COLUMN "note" TEXT,
|
||||
ADD COLUMN "profitSharePct" INTEGER NOT NULL DEFAULT 50,
|
||||
ADD COLUMN "taxPct" INTEGER,
|
||||
ADD COLUMN "travelGapMinutes" INTEGER NOT NULL DEFAULT 60;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "WebRequest" ALTER COLUMN "preferredAt" DROP NOT NULL,
|
||||
ALTER COLUMN "districtId" DROP NOT NULL,
|
||||
ALTER COLUMN "addressText" DROP NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PartnerEarning" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"orderId" TEXT NOT NULL,
|
||||
"deal" "PartnerDeal" NOT NULL,
|
||||
"paid" INTEGER NOT NULL,
|
||||
"fot" INTEGER NOT NULL DEFAULT 0,
|
||||
"tax" INTEGER NOT NULL DEFAULT 0,
|
||||
"pct" INTEGER NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"period" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PartnerEarning_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PartnerEarning_orderId_key" ON "PartnerEarning"("orderId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PartnerEarning_companyId_period_idx" ON "PartnerEarning"("companyId", "period");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PartnerEarning" ADD CONSTRAINT "PartnerEarning_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "ProviderCompany"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PartnerEarning" ADD CONSTRAINT "PartnerEarning_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "WebRequest" ADD CONSTRAINT "WebRequest_districtId_fkey" FOREIGN KEY ("districtId") REFERENCES "District"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- Действующие компании: их ставка «% компании от заказа» превращается в нашу комиссию
|
||||
UPDATE "ProviderCompany" SET "commissionPct" = GREATEST(0, LEAST(100, 100 - "rateValue")) WHERE "rateType" = 'PERCENT';
|
||||
@@ -0,0 +1,26 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProWebSession" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"codeHash" TEXT,
|
||||
"codeExpiresAt" TIMESTAMP(3),
|
||||
"tokenHash" TEXT,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"lastUsedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ProWebSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ProWebSession_codeHash_key" ON "ProWebSession"("codeHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ProWebSession_tokenHash_key" ON "ProWebSession"("tokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ProWebSession_userId_idx" ON "ProWebSession"("userId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProWebSession" ADD CONSTRAINT "ProWebSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PointsKind" AS ENUM ('ORDER', 'BONUS');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PointsStatus" AS ENUM ('PENDING', 'ACTIVE', 'SPENT', 'EXPIRED', 'REVERSED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PointsChannel" AS ENUM ('WEB', 'BOT', 'CSV', 'AUTO', 'CLIENT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RedemptionStatus" AS ENUM ('REQUESTED', 'CONFIRMED', 'REJECTED', 'REVERSED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SuggestionStatus" AS ENUM ('OPEN', 'ACCEPTED', 'REJECTED');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Agency" ADD COLUMN "housewarmingDays" INTEGER NOT NULL DEFAULT 60,
|
||||
ADD COLUMN "housewarmingPoints" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Client" ADD COLUMN "pointsActive" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "pointsActivityAt" TIMESTAMP(3),
|
||||
ADD COLUMN "pointsDebt" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "pointsPending" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "pointsWarn14At" TIMESTAMP(3),
|
||||
ADD COLUMN "pointsWarn3At" TIMESTAMP(3),
|
||||
ADD COLUMN "streakCount" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "streakLastAt" TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Order" ADD COLUMN "pointsUsed" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Service" ADD COLUMN "pointsCategoryId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsCategory" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" JSONB NOT NULL,
|
||||
"rateBarev" INTEGER NOT NULL,
|
||||
"ratePlus" INTEGER NOT NULL,
|
||||
"rateElite" INTEGER NOT NULL,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "PointsCategory_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsBonusType" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" JSONB NOT NULL,
|
||||
"defaultAmount" INTEGER NOT NULL,
|
||||
"ttlDays" INTEGER,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "PointsBonusType_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsAccrual" (
|
||||
"id" TEXT NOT NULL,
|
||||
"clientId" TEXT NOT NULL,
|
||||
"kind" "PointsKind" NOT NULL,
|
||||
"categoryId" TEXT,
|
||||
"bonusTypeId" TEXT,
|
||||
"orderId" TEXT,
|
||||
"orderRef" TEXT,
|
||||
"orderAmount" INTEGER,
|
||||
"excludedAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"paidWithPoints" INTEGER NOT NULL DEFAULT 0,
|
||||
"tier" TEXT,
|
||||
"ratePct" INTEGER,
|
||||
"streakBonusPct" INTEGER NOT NULL DEFAULT 0,
|
||||
"suggestedAmount" INTEGER,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"remaining" INTEGER NOT NULL,
|
||||
"expiredAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"status" "PointsStatus" NOT NULL,
|
||||
"serviceDate" TIMESTAMP(3) NOT NULL,
|
||||
"availableAt" TIMESTAMP(3) NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"reason" TEXT,
|
||||
"commentForClient" TEXT,
|
||||
"createdById" TEXT,
|
||||
"channel" "PointsChannel" NOT NULL,
|
||||
"idempotencyKey" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PointsAccrual_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsRedemption" (
|
||||
"id" TEXT NOT NULL,
|
||||
"clientId" TEXT NOT NULL,
|
||||
"orderId" TEXT,
|
||||
"orderRef" TEXT,
|
||||
"orderAmount" INTEGER,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"status" "RedemptionStatus" NOT NULL,
|
||||
"channel" "PointsChannel" NOT NULL,
|
||||
"note" TEXT,
|
||||
"requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"confirmedById" TEXT,
|
||||
"confirmedAt" TIMESTAMP(3),
|
||||
"reason" TEXT,
|
||||
"idempotencyKey" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PointsRedemption_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsRedemptionItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"redemptionId" TEXT NOT NULL,
|
||||
"accrualId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "PointsRedemptionItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsReversal" (
|
||||
"id" TEXT NOT NULL,
|
||||
"targetType" TEXT NOT NULL,
|
||||
"targetId" TEXT NOT NULL,
|
||||
"clientId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"debt" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdById" TEXT,
|
||||
"channel" "PointsChannel" NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PointsReversal_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PointsSuggestion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"clientId" TEXT NOT NULL,
|
||||
"mechanic" TEXT NOT NULL,
|
||||
"dedupeKey" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL,
|
||||
"suggestedAmount" INTEGER NOT NULL,
|
||||
"status" "SuggestionStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"resolvedById" TEXT,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"reason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PointsSuggestion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PointsCategory_code_key" ON "PointsCategory"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PointsBonusType_code_key" ON "PointsBonusType"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PointsAccrual_idempotencyKey_key" ON "PointsAccrual"("idempotencyKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsAccrual_clientId_status_idx" ON "PointsAccrual"("clientId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsAccrual_status_availableAt_idx" ON "PointsAccrual"("status", "availableAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsAccrual_orderId_idx" ON "PointsAccrual"("orderId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsAccrual_createdAt_idx" ON "PointsAccrual"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PointsRedemption_idempotencyKey_key" ON "PointsRedemption"("idempotencyKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsRedemption_clientId_status_idx" ON "PointsRedemption"("clientId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsRedemption_status_requestedAt_idx" ON "PointsRedemption"("status", "requestedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsRedemptionItem_accrualId_idx" ON "PointsRedemptionItem"("accrualId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsReversal_targetType_targetId_idx" ON "PointsReversal"("targetType", "targetId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsReversal_clientId_idx" ON "PointsReversal"("clientId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsReversal_createdAt_idx" ON "PointsReversal"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PointsSuggestion_dedupeKey_key" ON "PointsSuggestion"("dedupeKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PointsSuggestion_status_createdAt_idx" ON "PointsSuggestion"("status", "createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Service" ADD CONSTRAINT "Service_pointsCategoryId_fkey" FOREIGN KEY ("pointsCategoryId") REFERENCES "PointsCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsAccrual" ADD CONSTRAINT "PointsAccrual_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "Client"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsAccrual" ADD CONSTRAINT "PointsAccrual_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "PointsCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsAccrual" ADD CONSTRAINT "PointsAccrual_bonusTypeId_fkey" FOREIGN KEY ("bonusTypeId") REFERENCES "PointsBonusType"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsAccrual" ADD CONSTRAINT "PointsAccrual_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsRedemption" ADD CONSTRAINT "PointsRedemption_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "Client"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsRedemption" ADD CONSTRAINT "PointsRedemption_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsRedemptionItem" ADD CONSTRAINT "PointsRedemptionItem_redemptionId_fkey" FOREIGN KEY ("redemptionId") REFERENCES "PointsRedemption"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsRedemptionItem" ADD CONSTRAINT "PointsRedemptionItem_accrualId_fkey" FOREIGN KEY ("accrualId") REFERENCES "PointsAccrual"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PointsSuggestion" ADD CONSTRAINT "PointsSuggestion_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "Client"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- номер заказа уникален среди неотменённых начислений; автоначисление — один раз на заказ
|
||||
CREATE UNIQUE INDEX "PointsAccrual_orderRef_live_key" ON "PointsAccrual"("orderRef") WHERE "orderRef" IS NOT NULL AND "status" <> 'REVERSED';
|
||||
CREATE UNIQUE INDEX "PointsAccrual_orderId_live_key" ON "PointsAccrual"("orderId") WHERE "orderId" IS NOT NULL AND "status" <> 'REVERSED';
|
||||
|
||||
-- категории кэшбека (ставки по ТЗ; Elite = Barev+ +2)
|
||||
INSERT INTO "PointsCategory" ("id", "code", "name", "rateBarev", "ratePlus", "rateElite", "sortOrder") VALUES
|
||||
('pc_cleaning', 'cleaning', '{"ru":"Клининг","en":"Cleaning","zh":"保洁","hy":"Մաքրում","es":"Limpieza"}', 5, 8, 10, 1),
|
||||
('pc_documents', 'documents', '{"ru":"Документы","en":"Documents","zh":"证件","hy":"Փաստաթղթեր","es":"Documentos"}', 5, 10, 12, 2),
|
||||
('pc_learning', 'learning', '{"ru":"Обучение и карьера","en":"Learning and career","zh":"学习与职业","hy":"Ուսում և կարիերա","es":"Formación y carrera"}', 3, 4, 6, 3),
|
||||
('pc_repair', 'repair', '{"ru":"Ремонт","en":"Repairs","zh":"维修","hy":"Վերանորոգում","es":"Reparaciones"}', 2, 4, 6, 4),
|
||||
('pc_transfer', 'transfer', '{"ru":"Трансфер","en":"Transfer","zh":"接送","hy":"Տրանսֆեր","es":"Traslado"}', 2, 4, 6, 5);
|
||||
|
||||
UPDATE "Service" SET "pointsCategoryId" = 'pc_cleaning' WHERE "code" = 'cleaning';
|
||||
UPDATE "Service" SET "pointsCategoryId" = 'pc_repair' WHERE "code" = 'repair';
|
||||
UPDATE "Service" SET "pointsCategoryId" = 'pc_transfer' WHERE "code" = 'transfer';
|
||||
UPDATE "Service" s SET "pointsCategoryId" = 'pc_documents' FROM "Category" c WHERE c."id" = s."categoryId" AND c."code" = 'DOCS';
|
||||
UPDATE "Service" s SET "pointsCategoryId" = 'pc_learning' FROM "Category" c WHERE c."id" = s."categoryId" AND c."code" = 'LEARNING';
|
||||
|
||||
-- типы бонусов
|
||||
INSERT INTO "PointsBonusType" ("id", "code", "name", "defaultAmount", "ttlDays", "sortOrder") VALUES
|
||||
('pb_housewarming', 'housewarming', '{"ru":"Новоселье","en":"Housewarming","zh":"乔迁之喜","hy":"Բնակարանամուտ","es":"Mudanza"}', 5000, 60, 1),
|
||||
('pb_route', 'first_month_route', '{"ru":"Маршрут первого месяца","en":"First-month route","zh":"首月路线","hy":"Առաջին ամսվա երթուղի","es":"Ruta del primer mes"}', 3000, NULL, 2),
|
||||
('pb_ref_inviter', 'referral_inviter', '{"ru":"Пригласил друга","en":"Invited a friend","zh":"邀请好友","hy":"Հրավիրել է ընկերոջ","es":"Invitó a un amigo"}', 3000, NULL, 3),
|
||||
('pb_ref_invitee', 'referral_invitee', '{"ru":"По приглашению друга","en":"Invited by a friend","zh":"好友邀请","hy":"Ընկերոջ հրավերով","es":"Invitado por un amigo"}', 3000, NULL, 4),
|
||||
('pb_photo', 'photo_review', '{"ru":"Отзыв с фото","en":"Review with a photo","zh":"带图评价","hy":"Կարծիք լուսանկարով","es":"Reseña con foto"}', 500, NULL, 5),
|
||||
('pb_lease', 'lease_renewal', '{"ru":"Продление аренды","en":"Lease renewal","zh":"续租","hy":"Վարձակալության երկարաձգում","es":"Renovación del alquiler"}', 2000, 60, 6),
|
||||
('pb_complaint', 'complaint', '{"ru":"Компенсация","en":"Compensation","zh":"补偿","hy":"Փոխհատուցում","es":"Compensación"}', 1000, NULL, 7),
|
||||
('pb_promo', 'promo', '{"ru":"Промо","en":"Promo","zh":"促销","hy":"Պրոմո","es":"Promoción"}', 1000, 30, 8);
|
||||
@@ -0,0 +1,4 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OrderChecklistItem" ADD COLUMN "checkedByAdminId" TEXT,
|
||||
ADD COLUMN "photoWaivedReason" TEXT;
|
||||
|
||||
@@ -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