fix(bot): improve utilities reminder template flow

This commit is contained in:
2026-03-12 02:54:42 +04:00
parent ce8f0b5b93
commit e56f1194e9
4 changed files with 69 additions and 19 deletions

View File

@@ -270,7 +270,8 @@ export const enBotTranslations: BotTranslationCatalog = {
`I could not read that amount for ${categoryName}. Reply with a number in ${currency}, or send 0 / "skip".`,
templateIntro: (currency) =>
`Fill in the utility amounts below in ${currency}, then send the completed message back in this topic.`,
templateInstruction: 'Use 0 or skip for any category you want to leave empty.',
templateInstruction:
'For any category you do not want to add, leave it blank, remove the line entirely, or send 0 / "skip".',
templateInvalid:
'I could not read any utility amounts from that template. Send the filled template back with at least one amount.',
summaryTitle: (period) => `Utility charges for ${period}`,

View File

@@ -275,7 +275,7 @@ export const ruBotTranslations: BotTranslationCatalog = {
templateIntro: (currency) =>
`Заполните суммы по коммуналке ниже в ${currency}, затем отправьте заполненное сообщение обратно в этот топик.`,
templateInstruction:
'Для любой категории, которую не нужно добавлять, укажите 0 или слово «пропуск».',
'Для любой категории, которую не нужно добавлять, оставьте поле пустым, удалите строку целиком или укажите 0 / «пропуск».',
templateInvalid:
'Не удалось распознать ни одной суммы в этом шаблоне. Отправьте заполненный шаблон хотя бы с одной суммой.',
summaryTitle: (period) => `Коммунальные начисления за ${period}`,

View File

@@ -375,7 +375,8 @@ describe('registerReminderTopicUtilities', () => {
expect(calls[1]).toMatchObject({
method: 'sendMessage',
payload: {
text: expect.stringContaining('Electricity:'),
text: expect.stringContaining('<pre>Electricity: \nWater: </pre>'),
parse_mode: 'HTML',
message_thread_id: 555
}
})
@@ -391,6 +392,34 @@ describe('registerReminderTopicUtilities', () => {
})
})
test('treats blank or removed template lines as skipped categories', async () => {
const { bot, calls } = setupBot()
await bot.handleUpdate(reminderCallbackUpdate(REMINDER_UTILITY_TEMPLATE_CALLBACK) as never)
calls.length = 0
await bot.handleUpdate(reminderMessageUpdate('Electricity: 22\nWater: ') as never)
expect(calls[0]).toMatchObject({
method: 'sendMessage',
payload: {
text: expect.stringContaining('- Electricity: 22.00 GEL')
}
})
calls.length = 0
await bot.handleUpdate(reminderCallbackUpdate(REMINDER_UTILITY_TEMPLATE_CALLBACK) as never)
calls.length = 0
await bot.handleUpdate(reminderMessageUpdate('Electricity: 22') as never)
expect(calls[0]).toMatchObject({
method: 'sendMessage',
payload: {
text: expect.stringContaining('- Electricity: 22.00 GEL')
}
})
})
test('treats expired pending reminder submissions as unavailable', async () => {
const { bot, calls, promptRepository } = setupBot()

View File

@@ -182,20 +182,32 @@ function parseTemplateEntries(
return entries.length > 0 ? entries : null
}
function escapeHtml(raw: string): string {
return raw.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
}
function buildTemplateText(
locale: BotLocale,
currency: 'GEL' | 'USD',
categories: readonly string[]
): string {
): {
text: string
parseMode: 'HTML'
} {
const t = getBotTranslations(locale).reminders
return [
t.templateIntro(currency),
const templateLines = categories.map((category) => `${category}: `).join('\n')
return {
text: [
escapeHtml(t.templateIntro(currency)),
'',
...categories.map((category) => `${category}: `),
`<pre>${escapeHtml(templateLines)}</pre>`,
'',
t.templateInstruction
].join('\n')
escapeHtml(t.templateInstruction)
].join('\n'),
parseMode: 'HTML'
}
}
function reminderUtilitySummaryText(
@@ -261,7 +273,10 @@ function buildReminderConfirmationPayload(input: {
async function replyInTopic(
ctx: Context,
text: string,
replyMarkup?: InlineKeyboardMarkup
replyMarkup?: InlineKeyboardMarkup,
options?: {
parseMode?: 'HTML'
}
): Promise<void> {
const message = ctx.msg
if (!ctx.chat || !message) {
@@ -286,6 +301,11 @@ async function replyInTopic(
? {
reply_markup: replyMarkup as InlineKeyboardMarkup
}
: {}),
...(options?.parseMode
? {
parse_mode: options.parseMode
}
: {})
})
}
@@ -481,14 +501,14 @@ export function registerReminderTopicUtilities(options: {
await ctx.answerCallbackQuery({
text: t.templateToast
})
await replyInTopic(
ctx,
buildTemplateText(
const template = buildTemplateText(
reminderContext.locale,
reminderContext.currency,
reminderContext.categories
)
)
await replyInTopic(ctx, template.text, undefined, {
parseMode: template.parseMode
})
}
options.bot.callbackQuery(REMINDER_UTILITY_GUIDED_CALLBACK, async (ctx) => {