import type { ClientRequestKind, ClientRequestStatus, SubscriptionPlan } from '@barev/db'; import { DomainError, type Ctx } from './context.ts'; const DAY = 24 * 3600_000; /** * Заявка из приложения оператору: подписка (онлайн-оплаты пока нет — оператор договаривается и включает) * или «перезвоните мне» по услуге, которую ещё нельзя заказать с выбором времени. * Повторная заявка на ту же подписку или услугу, пока первая открыта, не создаётся. */ export async function createClientRequest( ctx: Ctx, clientId: string, input: { kind: ClientRequestKind; plan?: SubscriptionPlan; serviceCode?: string; comment?: string }, ) { if (input.kind === 'SUBSCRIPTION' && !input.plan) throw new DomainError('INVALID_PARAMS'); if (input.kind === 'SERVICE') { if (!input.serviceCode) throw new DomainError('INVALID_PARAMS'); const service = await ctx.db.service.findUnique({ where: { code: input.serviceCode } }); if (!service) throw new DomainError('NOT_FOUND'); } const open = await ctx.db.clientRequest.findFirst({ where: { clientId, status: 'NEW', kind: input.kind, plan: input.plan ?? null, serviceCode: input.serviceCode ?? null }, }); if (open) return open; const request = await ctx.db.clientRequest.create({ data: { clientId, kind: input.kind, plan: input.plan, serviceCode: input.serviceCode, comment: input.comment?.trim() || undefined }, }); await ctx.notify.emit({ type: 'clientRequest.created', requestId: request.id }); return request; } /** Открытые заявки клиента — приложение показывает «заявка отправлена» */ export function openClientRequests(ctx: Ctx, clientId: string) { return ctx.db.clientRequest.findMany({ where: { clientId, status: 'NEW' }, orderBy: { createdAt: 'desc' } }); } export function listClientRequests(ctx: Ctx, input: { status?: ClientRequestStatus }) { return ctx.db.clientRequest.findMany({ where: input.status ? { status: input.status } : {}, include: { client: { include: { user: { select: { firstName: true, lastName: true, username: true, phone: true, locale: true } } } } }, orderBy: { createdAt: 'desc' }, take: 200, }); } export async function updateClientRequest(ctx: Ctx, input: { id: string; status?: ClientRequestStatus; adminNote?: string }) { return ctx.db.clientRequest.update({ where: { id: input.id }, data: { status: input.status, adminNote: input.adminNote === undefined ? undefined : input.adminNote.trim() || null }, }); } /** * Включить подписку вручную на N дней. Тот же план продлевается с даты окончания; * переход на другой план начинается сразу (Elite важнее Barev+, пока обе активны). */ export async function grantSubscription(ctx: Ctx, input: { clientId: string; plan: SubscriptionPlan; days: number; requestId?: string }) { const now = ctx.now(); const current = await ctx.db.subscription.findFirst({ where: { clientId: input.clientId, plan: input.plan, status: 'ACTIVE', endsAt: { gt: now } }, orderBy: { endsAt: 'desc' }, }); const startsAt = current?.endsAt ?? now; const sub = await ctx.db.subscription.create({ data: { clientId: input.clientId, plan: input.plan, source: 'MANUAL', startsAt, endsAt: new Date(startsAt.getTime() + input.days * DAY) }, }); if (input.requestId) await ctx.db.clientRequest.update({ where: { id: input.requestId }, data: { status: 'DONE' } }); await ctx.notify.emit({ type: 'subscription.activated', subscriptionId: sub.id }); return sub; }