From c8c6bd2e392f88e988fc9386fcb6784b01e1ad10 Mon Sep 17 00:00:00 2001 From: whekin Date: Wed, 11 Mar 2026 15:24:00 +0400 Subject: [PATCH] feat(bot): redesign household status output --- apps/bot/src/finance-commands.test.ts | 22 +++++++++----- apps/bot/src/finance-commands.ts | 44 +++++++++++++++++++-------- apps/bot/src/i18n/locales/en.ts | 14 ++++++--- apps/bot/src/i18n/locales/ru.ts | 15 ++++++--- apps/bot/src/i18n/types.ts | 15 ++++++--- 5 files changed, 76 insertions(+), 34 deletions(-) diff --git a/apps/bot/src/finance-commands.test.ts b/apps/bot/src/finance-commands.test.ts index b1eb45f..87ac8fb 100644 --- a/apps/bot/src/finance-commands.test.ts +++ b/apps/bot/src/finance-commands.test.ts @@ -125,8 +125,8 @@ function createDashboard(): NonNullable< period: '2026-03', currency: 'GEL', totalDue: Money.fromMajor('400', 'GEL'), - totalPaid: Money.fromMajor('150', 'GEL'), - totalRemaining: Money.fromMajor('250', 'GEL'), + totalPaid: Money.fromMajor('100', 'GEL'), + totalRemaining: Money.fromMajor('300', 'GEL'), rentSourceAmount: Money.fromMajor('700', 'USD'), rentDisplayAmount: Money.fromMajor('1890', 'GEL'), rentFxRateMicros: 2_700_000n, @@ -150,8 +150,8 @@ function createDashboard(): NonNullable< utilityShare: Money.fromMajor('20', 'GEL'), purchaseOffset: Money.fromMajor('10', 'GEL'), netDue: Money.fromMajor('190', 'GEL'), - paid: Money.fromMajor('50', 'GEL'), - remaining: Money.fromMajor('140', 'GEL'), + paid: Money.zero('GEL'), + remaining: Money.fromMajor('190', 'GEL'), explanations: [] } ], @@ -238,7 +238,7 @@ function createFinanceService(): FinanceCommandService { } describe('createFinanceCommandsService', () => { - test('replies with a compact localized household status summary', async () => { + test('replies with a clearer localized household status summary', async () => { const repository = createRepository() const financeService = createFinanceService() const bot = createTelegramBot('000000:test-token', undefined, repository) @@ -282,12 +282,18 @@ describe('createFinanceCommandsService', () => { const payload = calls[0]?.payload as { text?: string } | undefined expect(payload?.text).toContain('Статус на март 2026') + expect(payload?.text).toContain('\n\nНачисления\n') expect(payload?.text).toContain('Аренда: 700.00 USD (~1890.00 GEL)') expect(payload?.text).toContain('Коммуналка: 82.00 GEL') expect(payload?.text).toContain('Общие покупки: 30.00 GEL') expect(payload?.text).toContain('Срок оплаты аренды: до 20 марта') - expect(payload?.text).toContain( - '- Стас: баланс 210.00 GEL, оплачено 100.00 GEL, остаток 110.00 GEL' - ) + expect(payload?.text).toContain('Расчёты') + expect(payload?.text).toContain('Общий баланс: 400.00 GEL') + expect(payload?.text).toContain('Уже оплачено: 100.00 GEL') + expect(payload?.text).toContain('Осталось оплатить: 300.00 GEL') + expect(payload?.text).toContain('Участники') + expect(payload?.text).toContain('- Ион: остаток 190.00 GEL') + expect(payload?.text).toContain('- Стас: остаток 110.00 GEL (210.00 баланс, 100.00 оплачено)') + expect(payload?.text).not.toContain('- Ион: остаток 190.00 GEL (') }) }) diff --git a/apps/bot/src/finance-commands.ts b/apps/bot/src/finance-commands.ts index f2eb6ee..8a47d36 100644 --- a/apps/bot/src/finance-commands.ts +++ b/apps/bot/src/finance-commands.ts @@ -111,27 +111,45 @@ export function createFinanceCommandsService(options: { dashboard.currency ) + const memberLines = [...dashboard.members] + .sort((left, right) => right.remaining.compare(left.remaining)) + .map((member) => + member.paid.isZero() + ? t.householdStatusMemberCompact( + member.displayName, + member.remaining.toMajorString(), + dashboard.currency + ) + : t.householdStatusMemberDetailed( + member.displayName, + member.remaining.toMajorString(), + member.netDue.toMajorString(), + member.paid.toMajorString(), + dashboard.currency + ) + ) + return [ t.householdStatusTitle(formatBillingPeriodLabel(locale, dashboard.period)), t.householdStatusDueDate(formatCycleDueDate(locale, dashboard.period, dueDay)), + '', + t.householdStatusChargesHeading, rentLine, t.householdStatusUtilities(utilityTotal.toMajorString(), dashboard.currency), t.householdStatusPurchases(purchaseTotal.toMajorString(), dashboard.currency), - ...dashboard.members.map((member) => - t.householdStatusMember( - member.displayName, - member.netDue.toMajorString(), - member.paid.toMajorString(), - member.remaining.toMajorString(), - dashboard.currency - ) - ), - t.householdStatusTotals( - dashboard.totalDue.toMajorString(), - dashboard.totalPaid.toMajorString(), + '', + t.householdStatusSettlementHeading, + t.householdStatusSettlementBalance(dashboard.totalDue.toMajorString(), dashboard.currency), + ...(!dashboard.totalPaid.isZero() + ? [t.householdStatusSettlementPaid(dashboard.totalPaid.toMajorString(), dashboard.currency)] + : []), + t.householdStatusSettlementRemaining( dashboard.totalRemaining.toMajorString(), dashboard.currency - ) + ), + '', + t.householdStatusMembersHeading, + ...memberLines ].join('\n') } diff --git a/apps/bot/src/i18n/locales/en.ts b/apps/bot/src/i18n/locales/en.ts index acf706f..e41931c 100644 --- a/apps/bot/src/i18n/locales/en.ts +++ b/apps/bot/src/i18n/locales/en.ts @@ -231,15 +231,21 @@ export const enBotTranslations: BotTranslationCatalog = { noStatementCycle: 'No cycle found for statement.', householdStatusTitle: (period) => `Household status for ${period}`, householdStatusDueDate: (dueDate) => `Rent due by ${dueDate}`, + householdStatusChargesHeading: 'Charges', householdStatusRentDirect: (amount, currency) => `Rent: ${amount} ${currency}`, householdStatusRentConverted: (sourceAmount, sourceCurrency, displayAmount, displayCurrency) => `Rent: ${sourceAmount} ${sourceCurrency} (~${displayAmount} ${displayCurrency})`, householdStatusUtilities: (amount, currency) => `Utilities: ${amount} ${currency}`, householdStatusPurchases: (amount, currency) => `Shared purchases: ${amount} ${currency}`, - householdStatusMember: (displayName, balance, paid, remaining, currency) => - `- ${displayName}: balance ${balance} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`, - householdStatusTotals: (balance, paid, remaining, currency) => - `Household total: balance ${balance} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`, + householdStatusSettlementHeading: 'Settlement', + householdStatusSettlementBalance: (amount, currency) => `Gross balance: ${amount} ${currency}`, + householdStatusSettlementPaid: (amount, currency) => `Paid so far: ${amount} ${currency}`, + householdStatusSettlementRemaining: (amount, currency) => `Remaining: ${amount} ${currency}`, + householdStatusMembersHeading: 'Members', + householdStatusMemberCompact: (displayName, remaining, currency) => + `- ${displayName}: remaining ${remaining} ${currency}`, + householdStatusMemberDetailed: (displayName, remaining, balance, paid, currency) => + `- ${displayName}: remaining ${remaining} ${currency} (${balance} balance, ${paid} paid)`, statementTitle: (period) => `Statement for ${period}`, statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`, statementTotal: (amount, currency) => `Total: ${amount} ${currency}`, diff --git a/apps/bot/src/i18n/locales/ru.ts b/apps/bot/src/i18n/locales/ru.ts index 66e786e..020e817 100644 --- a/apps/bot/src/i18n/locales/ru.ts +++ b/apps/bot/src/i18n/locales/ru.ts @@ -234,15 +234,22 @@ export const ruBotTranslations: BotTranslationCatalog = { noStatementCycle: 'Для выписки период не найден.', householdStatusTitle: (period) => `Статус на ${period}`, householdStatusDueDate: (dueDate) => `Срок оплаты аренды: до ${dueDate}`, + householdStatusChargesHeading: 'Начисления', householdStatusRentDirect: (amount, currency) => `Аренда: ${amount} ${currency}`, householdStatusRentConverted: (sourceAmount, sourceCurrency, displayAmount, displayCurrency) => `Аренда: ${sourceAmount} ${sourceCurrency} (~${displayAmount} ${displayCurrency})`, householdStatusUtilities: (amount, currency) => `Коммуналка: ${amount} ${currency}`, householdStatusPurchases: (amount, currency) => `Общие покупки: ${amount} ${currency}`, - householdStatusMember: (displayName, balance, paid, remaining, currency) => - `- ${displayName}: баланс ${balance} ${currency}, оплачено ${paid} ${currency}, остаток ${remaining} ${currency}`, - householdStatusTotals: (balance, paid, remaining, currency) => - `Итого по дому: баланс ${balance} ${currency}, оплачено ${paid} ${currency}, остаток ${remaining} ${currency}`, + householdStatusSettlementHeading: 'Расчёты', + householdStatusSettlementBalance: (amount, currency) => `Общий баланс: ${amount} ${currency}`, + householdStatusSettlementPaid: (amount, currency) => `Уже оплачено: ${amount} ${currency}`, + householdStatusSettlementRemaining: (amount, currency) => + `Осталось оплатить: ${amount} ${currency}`, + householdStatusMembersHeading: 'Участники', + householdStatusMemberCompact: (displayName, remaining, currency) => + `- ${displayName}: остаток ${remaining} ${currency}`, + householdStatusMemberDetailed: (displayName, remaining, balance, paid, currency) => + `- ${displayName}: остаток ${remaining} ${currency} (${balance} баланс, ${paid} оплачено)`, statementTitle: (period) => `Выписка за ${period}`, statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`, statementTotal: (amount, currency) => `Итого: ${amount} ${currency}`, diff --git a/apps/bot/src/i18n/types.ts b/apps/bot/src/i18n/types.ts index 5afb4f4..6f97064 100644 --- a/apps/bot/src/i18n/types.ts +++ b/apps/bot/src/i18n/types.ts @@ -206,6 +206,7 @@ export interface BotTranslationCatalog { noStatementCycle: string householdStatusTitle: (period: string) => string householdStatusDueDate: (dueDate: string) => string + householdStatusChargesHeading: string householdStatusRentDirect: (amount: string, currency: string) => string householdStatusRentConverted: ( sourceAmount: string, @@ -215,17 +216,21 @@ export interface BotTranslationCatalog { ) => string householdStatusUtilities: (amount: string, currency: string) => string householdStatusPurchases: (amount: string, currency: string) => string - householdStatusMember: ( + householdStatusSettlementHeading: string + householdStatusSettlementBalance: (amount: string, currency: string) => string + householdStatusSettlementPaid: (amount: string, currency: string) => string + householdStatusSettlementRemaining: (amount: string, currency: string) => string + householdStatusMembersHeading: string + householdStatusMemberCompact: ( displayName: string, - balance: string, - paid: string, remaining: string, currency: string ) => string - householdStatusTotals: ( + householdStatusMemberDetailed: ( + displayName: string, + remaining: string, balance: string, paid: string, - remaining: string, currency: string ) => string statementTitle: (period: string) => string