Relocations and requests in the new admin; star rating in the client bot
Orders section gets tabs: Orders / Relocations / Requests (concierge sees the last two). Relocation list with statuses, plan progress and current step; relocation card with per-client steps (status, client comment, rename, add, remove), documents, quiz answers, team note, write/call. Requests: website cleaning requests and app requests (membership, call back) with notes, create order, turn on membership. Dashboard links point here instead of the old admin. Fix: leads with an unanswered 'already moved?' (NULL) were skipped by 'moved not true' filters — plan sync and linking to the app client. Client bot: after an order is done, asks for 1-5 stars; low rating asks what went wrong, the reply is saved as the rating comment and goes to the team chat. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bbb25d3d0d
commit
dcba7c200c
19 files changed
+1422
-22
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
DomainError,
|
||||
getClientByTelegramId,
|
||||
giveConsent,
|
||||
rateOrder,
|
||||
linkWebRequest,
|
||||
setClientChannel,
|
||||
setLocale,
|
||||
@@ -40,6 +41,8 @@ const bot = new Bot(token || 'disabled:token');
|
||||
|
||||
/** Ожидаем ввод кода агентства от этих чатов */
|
||||
const awaitingCode = new Set<number>();
|
||||
/** Низкая оценка: следующий текст — комментарий к ней (chatId → заказ и оценка) */
|
||||
const awaitingRatingComment = new Map<number, { orderId: string; score: number }>();
|
||||
|
||||
async function localeOf(from: Context['from']): Promise<Locale> {
|
||||
if (!from) return 'ru';
|
||||
@@ -193,11 +196,38 @@ bot.on('message:document', async (c) => {
|
||||
await toTeam(c, c.message.caption ?? '', { tgFileId: d.file_id, tgBot: 'client', fileName: d.file_name ?? 'file', mime: d.mime_type ?? 'application/octet-stream', size: d.file_size ?? null });
|
||||
});
|
||||
|
||||
/** Оценка заказа кнопкой со звёздами (сообщение приходит, когда заказ выполнен) */
|
||||
bot.callbackQuery(/^rate:(\w+):([1-5])$/, async (c) => {
|
||||
await c.answerCallbackQuery();
|
||||
const client = await getClientByTelegramId(db, BigInt(c.from.id));
|
||||
if (!client) return;
|
||||
const t = createT(client.user.locale as Locale);
|
||||
const score = Number(c.match[2]);
|
||||
try {
|
||||
await rateOrder(ctx, { orderId: c.match[1]!, clientId: client.id, score });
|
||||
} catch (e) {
|
||||
if (e instanceof DomainError) return void (await c.reply(t(`errors.${e.code}`)));
|
||||
throw e;
|
||||
}
|
||||
await c.editMessageText(t('bot.client.rated', { stars: '★'.repeat(score) + '☆'.repeat(5 - score) }));
|
||||
if (score <= 3) {
|
||||
awaitingRatingComment.set(c.chat!.id, { orderId: c.match[1]!, score });
|
||||
await c.reply(t('bot.client.ratedLow'));
|
||||
}
|
||||
});
|
||||
|
||||
/** ONB-6: ручной ввод кода агентства; любой другой текст — в чат команды */
|
||||
bot.on('message:text', async (c) => {
|
||||
const locale = await localeOf(c.from);
|
||||
const t = createT(locale);
|
||||
const client = await getClientByTelegramId(db, BigInt(c.from.id));
|
||||
// комментарий к низкой оценке: сохраняем в оценку и передаём команде в чат
|
||||
const pending = awaitingRatingComment.get(c.chat.id);
|
||||
if (pending && client) {
|
||||
awaitingRatingComment.delete(c.chat.id);
|
||||
await rateOrder(ctx, { orderId: pending.orderId, clientId: client.id, score: pending.score, comment: c.message.text.slice(0, 1000) }).catch(() => undefined);
|
||||
return toTeam(c, c.message.text);
|
||||
}
|
||||
if (!awaitingCode.has(c.chat.id)) return toTeam(c, c.message.text);
|
||||
if (!client) return;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user