Chats: show which order a provider writes about, or that there is no order

- Provider messages store the order that was current when they wrote: a started
  order first, otherwise the nearest assigned one within -4h..+12h; none — "no order".
- Admin chat: order tag above each provider message, "now / next" order strip in
  the conversation header, order number (or "no order") in the thread list.
- Telegram notification to the team gets an order line or "no current order".
- Test for the order-picking rule; demo shows both cases.

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:02:31 +04:00
1 parent 23ffb2bf1d
commit 2772837e29
14 files changed
+321 -13

No files matched your search

+26 -3
View File
@@ -7,6 +7,7 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { getDb } from '@barev/db';
import { providerCurrentOrder, silentNotifier, type Ctx } from '@barev/services';
import { putFile } from '@barev/services/storage';
import { env } from '../src/env.ts';
@@ -44,10 +45,23 @@ const katya = await clientByTg(100001004n); // Barev+
const natalia = await clientByTg(100001007n); // без подписки
const lilit = await clientByTg(100001025n); // Barev+
const provider = await db.provider.findFirst({ where: { userId: { not: null }, active: true }, orderBy: { createdAt: 'asc' } });
// второй исполнитель — с заказом в работе, чтобы было видно «пишет по заказу №…»
const busy = await db.provider.findFirst({ where: { userId: { not: null }, active: true, orders: { some: { status: { in: ['EN_ROUTE', 'CLEANING', 'ON_SITE', 'REPAIRING'] } } } } });
const providerIds = [provider?.id, busy?.id].filter((x): x is string => !!x);
const svc: Ctx = { db, notify: silentNotifier, now: () => new Date() };
// демо-даты: у «занятого» исполнителя заказ идёт сейчас, у первого — следующий заказ завтра
if (busy) {
const o = await db.order.findFirst({ where: { providerId: busy.id, status: { in: ['EN_ROUTE', 'CLEANING', 'ON_SITE', 'REPAIRING'] } } });
if (o) await db.order.update({ where: { id: o.id }, data: { scheduledAt: ago(90) } });
}
if (provider) {
const o = await db.order.findFirst({ where: { providerId: provider.id, status: 'ASSIGNED' } });
if (o) await db.order.update({ where: { id: o.id }, data: { scheduledAt: new Date(now + 20 * 60 * MIN) } });
}
const clientIds = [arsen, dmitry, maria, sarah, alex, katya, natalia, lilit].map((c) => c.id);
await db.chatThread.deleteMany({ where: { OR: [{ clientId: { in: clientIds } }, ...(provider ? [{ providerId: provider.id }] : [])] } });
await db.conciergeMessage.deleteMany({ where: { OR: [{ clientId: { in: clientIds } }, ...(provider ? [{ providerId: provider.id }] : [])] } });
await db.chatThread.deleteMany({ where: { OR: [{ clientId: { in: clientIds } }, { providerId: { in: providerIds } }] } });
await db.conciergeMessage.deleteMany({ where: { OR: [{ clientId: { in: clientIds } }, { providerId: { in: providerIds } }] } });
await db.client.updateMany({ where: { id: { in: clientIds } }, data: { chatChannel: null } });
await db.client.update({ where: { id: sarah.id }, data: { chatChannel: 'SUPPORT' } });
@@ -93,10 +107,13 @@ async function thread(
});
for (const m of messages) {
const incoming = m.author === 'CLIENT' || m.author === 'PROVIDER';
// исполнитель: заказ, который был текущим в момент сообщения
const order = 'providerId' in peer && m.author === 'PROVIDER' ? await providerCurrentOrder(svc, peer.providerId, ago(m.at)) : null;
await db.conciergeMessage.create({
data: {
threadId: t.id,
...peer,
orderId: order?.id ?? null,
author: m.author,
adminUserId: m.by,
text: m.text,
@@ -175,6 +192,12 @@ await thread({ clientId: alex.id }, 'SUPPORT', { assigneeId: gor.id, status: 'CL
{ author: 'SYSTEM', text: 'closed', at: 60 * 29 - 12, by: gor.id },
]);
if (busy && busy.id !== provider?.id) {
await thread({ providerId: busy.id }, 'SUPPORT', { waiting: 3 }, [
{ author: 'PROVIDER', text: 'Клиентка просит дополнительно помыть балкон — это входит в заказ или считать отдельно?', at: 3 },
]);
}
if (provider) {
await thread({ providerId: provider.id }, 'SUPPORT', { waiting: 7 }, [
{ author: 'PROVIDER', text: 'Здравствуйте, по заказу на Абовяна нет кода от подъезда, клиент не отвечает. Что делать?', at: 7 },
@@ -191,5 +214,5 @@ const TEMPLATES: { channel: 'SUPPORT' | 'CONCIERGE' | null; title: string; text:
await db.chatTemplate.deleteMany({ where: { title: { in: TEMPLATES.map((t) => t.title) } } });
for (const t of TEMPLATES) await db.chatTemplate.create({ data: { ...t, createdById: owner.id } });
console.log(`Демо-чаты готовы: ${provider ? 9 : 8} диалогов, ${TEMPLATES.length} шаблона. Сотрудники: ${anna.name} (консьерж), ${gor.name} (поддержка).`);
console.log(`Демо-чаты готовы: ${8 + providerIds.length} диалогов, ${TEMPLATES.length} шаблона. Сотрудники: ${anna.name} (консьерж), ${gor.name} (поддержка).`);
process.exit(0);