/** * Локальная разработка: демо партнёра услуг (клининг) — заказы у партнёра, сотрудники, доход, * и одноразовая ссылка на кабинет менеджера в браузере. * pnpm --filter @barev/api dev:partners-demo * Клиенты демо — telegramId 100003000+. Повторный запуск пересоздаёт их заказы. */ import { fromYerevan, toYerevan } from '@barev/core'; import { getDb } from '@barev/db'; import { createOrder, createProWebLogin, dispatcherAssign, giveConsent, markPaid, offerOrder, startClient, transitionOrder, type Ctx } from '@barev/services'; import { env } from '../src/env.ts'; if (env.isProd) throw new Error('Только для разработки'); const db = getDb(); const ctx: Ctx = { db, notify: { emit: async () => {} }, now: () => new Date() }; const TG_FROM = 100003000n; const PRO_APP = process.env.PRO_WEB_URL ?? 'http://localhost:5187'; // ── чистим прошлое демо ── const old = await db.user.findMany({ where: { telegramId: { gte: TG_FROM, lt: TG_FROM + 100n } }, include: { client: true } }); const oldClients = old.map((u) => u.client?.id).filter((x): x is string => !!x); const oldOrders = (await db.order.findMany({ where: { clientId: { in: oldClients } }, select: { id: true } })).map((o) => o.id); await db.partnerEarning.deleteMany({ where: { orderId: { in: oldOrders } } }); await db.agencyCommission.deleteMany({ where: { orderId: { in: oldOrders } } }); await db.order.deleteMany({ where: { id: { in: oldOrders } } }); let company = await db.providerCompany.findFirst({ where: { active: true, roles: { has: 'CLEANER' } } }); if (!company) throw new Error('Нет компании-партнёра — запустите seed'); company = await db.providerCompany.update({ where: { id: company.id }, data: { deal: 'PROFIT_SHARE', profitSharePct: 40, fotPct: null, taxPct: null } }); const manager = await db.companyDispatcher.findFirstOrThrow({ where: { companyId: company.id } }); const staff = await db.provider.findMany({ where: { companyId: company.id, active: true } }); // график всем сотрудникам: каждый день 9–20, лимит 3 заказа в день у первого for (const [i, p] of staff.entries()) { await db.providerSchedule.deleteMany({ where: { providerId: p.id } }); await db.providerSchedule.createMany({ data: [0, 1, 2, 3, 4, 5, 6].map((weekday) => ({ providerId: p.id, weekday, startMinute: 9 * 60, endMinute: 20 * 60 })) }); await db.provider.update({ where: { id: p.id }, data: { maxOrdersPerDay: i === 0 ? 3 : null, maxHoursPerDay: i === 0 ? 8 : null } }); } const kentron = await db.district.findUniqueOrThrow({ where: { code: 'kentron' } }); const admin = await db.adminUser.findFirstOrThrow({ where: { email: 'owner@barev.local' } }); const at = (dayOffset: number, hour: number) => { const p = toYerevan(new Date(Date.now() + dayOffset * 24 * 3600_000)); return fromYerevan(p.year, p.month, p.day, hour * 60); }; const NAMES = ['Ольга', 'James', 'Ирина', 'Arpi', 'Лилит']; async function demoOrder(i: number, when: Date, rooms: number) { const s = await startClient(ctx, { id: Number(TG_FROM) + i, first_name: NAMES[i % NAMES.length]!, language_code: i % 2 ? 'en' : 'ru' }); await giveConsent(ctx, s.clientId); return createOrder(ctx, { clientId: s.clientId, serviceCode: 'cleaning', params: { cleaningType: 'regular', rooms, bathrooms: 1 }, scheduledAt: when, addressText: `Абовяна ${10 + i}`, districtId: kentron.id, allowBusySlot: true }); } // 1) два заказа ждут назначения у партнёра for (const [i, d] of [ [0, 2], [1, 3], ] as const) { const o = await demoOrder(i, at(d, 11), 2 + i); await offerOrder(ctx, { orderId: o.id, adminId: admin.id, target: { kind: 'company', id: company.id } }); } // 2) один назначен на сотрудника const assigned = await demoOrder(2, at(2, 15), 1); await offerOrder(ctx, { orderId: assigned.id, adminId: admin.id, target: { kind: 'company', id: company.id } }); const linked = staff.find((p) => p.userId) ?? staff[0]!; await dispatcherAssign(ctx, { orderId: assigned.id, companyId: company.id, providerId: linked.id, dispatcherUserId: manager.userId }); // 3) один выполнен и оплачен — доход партнёра const done = await demoOrder(3, at(1, 10), 3); await offerOrder(ctx, { orderId: done.id, adminId: admin.id, target: { kind: 'company', id: company.id } }); await dispatcherAssign(ctx, { orderId: done.id, companyId: company.id, providerId: linked.id, dispatcherUserId: manager.userId }); for (const to of ['EN_ROUTE', 'CLEANING'] as const) await transitionOrder(ctx, { orderId: done.id, to, actor: 'ADMIN', actorId: admin.id, event: 'demo' }); await db.orderChecklistItem.updateMany({ where: { orderId: done.id }, data: { checkedAt: new Date() } }); await db.order.update({ where: { id: done.id }, data: { status: 'DONE', completedAt: new Date() } }); await markPaid(ctx, { orderId: done.id, by: { type: 'ADMIN', id: admin.id } }); // 4) новый заказ без исполнителя — оператор выбирает, кому отдать (видно, что выгоднее) const fresh = await demoOrder(4, at(4, 12), 2); console.log(`Заказ для назначения: #${fresh.number} (${fresh.id})`); const code = await createProWebLogin(ctx, manager.userId); console.log(`Партнёр: ${company.name}`); console.log(`Кабинет менеджера в браузере (ссылка одноразовая, 15 минут):\n${PRO_APP}/?login=${code}#/company`); process.exit(0);