feat(bot): redesign household status output

This commit is contained in:
2026-03-11 15:24:00 +04:00
parent 086e521ce7
commit c8c6bd2e39
5 changed files with 76 additions and 34 deletions

View File

@@ -125,8 +125,8 @@ function createDashboard(): NonNullable<
period: '2026-03', period: '2026-03',
currency: 'GEL', currency: 'GEL',
totalDue: Money.fromMajor('400', 'GEL'), totalDue: Money.fromMajor('400', 'GEL'),
totalPaid: Money.fromMajor('150', 'GEL'), totalPaid: Money.fromMajor('100', 'GEL'),
totalRemaining: Money.fromMajor('250', 'GEL'), totalRemaining: Money.fromMajor('300', 'GEL'),
rentSourceAmount: Money.fromMajor('700', 'USD'), rentSourceAmount: Money.fromMajor('700', 'USD'),
rentDisplayAmount: Money.fromMajor('1890', 'GEL'), rentDisplayAmount: Money.fromMajor('1890', 'GEL'),
rentFxRateMicros: 2_700_000n, rentFxRateMicros: 2_700_000n,
@@ -150,8 +150,8 @@ function createDashboard(): NonNullable<
utilityShare: Money.fromMajor('20', 'GEL'), utilityShare: Money.fromMajor('20', 'GEL'),
purchaseOffset: Money.fromMajor('10', 'GEL'), purchaseOffset: Money.fromMajor('10', 'GEL'),
netDue: Money.fromMajor('190', 'GEL'), netDue: Money.fromMajor('190', 'GEL'),
paid: Money.fromMajor('50', 'GEL'), paid: Money.zero('GEL'),
remaining: Money.fromMajor('140', 'GEL'), remaining: Money.fromMajor('190', 'GEL'),
explanations: [] explanations: []
} }
], ],
@@ -238,7 +238,7 @@ function createFinanceService(): FinanceCommandService {
} }
describe('createFinanceCommandsService', () => { 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 repository = createRepository()
const financeService = createFinanceService() const financeService = createFinanceService()
const bot = createTelegramBot('000000:test-token', undefined, repository) const bot = createTelegramBot('000000:test-token', undefined, repository)
@@ -282,12 +282,18 @@ describe('createFinanceCommandsService', () => {
const payload = calls[0]?.payload as { text?: string } | undefined const payload = calls[0]?.payload as { text?: string } | undefined
expect(payload?.text).toContain('Статус на март 2026') 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('Аренда: 700.00 USD (~1890.00 GEL)')
expect(payload?.text).toContain('Коммуналка: 82.00 GEL') expect(payload?.text).toContain('Коммуналка: 82.00 GEL')
expect(payload?.text).toContain('Общие покупки: 30.00 GEL') expect(payload?.text).toContain('Общие покупки: 30.00 GEL')
expect(payload?.text).toContain('Срок оплаты аренды: до 20 марта') expect(payload?.text).toContain('Срок оплаты аренды: до 20 марта')
expect(payload?.text).toContain( expect(payload?.text).toContain('Расчёты')
'- Стас: баланс 210.00 GEL, оплачено 100.00 GEL, остаток 110.00 GEL' 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 (')
}) })
}) })

View File

@@ -111,27 +111,45 @@ export function createFinanceCommandsService(options: {
dashboard.currency dashboard.currency
) )
return [ const memberLines = [...dashboard.members]
t.householdStatusTitle(formatBillingPeriodLabel(locale, dashboard.period)), .sort((left, right) => right.remaining.compare(left.remaining))
t.householdStatusDueDate(formatCycleDueDate(locale, dashboard.period, dueDay)), .map((member) =>
rentLine, member.paid.isZero()
t.householdStatusUtilities(utilityTotal.toMajorString(), dashboard.currency), ? t.householdStatusMemberCompact(
t.householdStatusPurchases(purchaseTotal.toMajorString(), dashboard.currency),
...dashboard.members.map((member) =>
t.householdStatusMember(
member.displayName, member.displayName,
member.netDue.toMajorString(),
member.paid.toMajorString(),
member.remaining.toMajorString(), member.remaining.toMajorString(),
dashboard.currency dashboard.currency
) )
), : t.householdStatusMemberDetailed(
t.householdStatusTotals( member.displayName,
dashboard.totalDue.toMajorString(), member.remaining.toMajorString(),
dashboard.totalPaid.toMajorString(), member.netDue.toMajorString(),
dashboard.totalRemaining.toMajorString(), member.paid.toMajorString(),
dashboard.currency 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),
'',
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') ].join('\n')
} }

View File

@@ -231,15 +231,21 @@ export const enBotTranslations: BotTranslationCatalog = {
noStatementCycle: 'No cycle found for statement.', noStatementCycle: 'No cycle found for statement.',
householdStatusTitle: (period) => `Household status for ${period}`, householdStatusTitle: (period) => `Household status for ${period}`,
householdStatusDueDate: (dueDate) => `Rent due by ${dueDate}`, householdStatusDueDate: (dueDate) => `Rent due by ${dueDate}`,
householdStatusChargesHeading: 'Charges',
householdStatusRentDirect: (amount, currency) => `Rent: ${amount} ${currency}`, householdStatusRentDirect: (amount, currency) => `Rent: ${amount} ${currency}`,
householdStatusRentConverted: (sourceAmount, sourceCurrency, displayAmount, displayCurrency) => householdStatusRentConverted: (sourceAmount, sourceCurrency, displayAmount, displayCurrency) =>
`Rent: ${sourceAmount} ${sourceCurrency} (~${displayAmount} ${displayCurrency})`, `Rent: ${sourceAmount} ${sourceCurrency} (~${displayAmount} ${displayCurrency})`,
householdStatusUtilities: (amount, currency) => `Utilities: ${amount} ${currency}`, householdStatusUtilities: (amount, currency) => `Utilities: ${amount} ${currency}`,
householdStatusPurchases: (amount, currency) => `Shared purchases: ${amount} ${currency}`, householdStatusPurchases: (amount, currency) => `Shared purchases: ${amount} ${currency}`,
householdStatusMember: (displayName, balance, paid, remaining, currency) => householdStatusSettlementHeading: 'Settlement',
`- ${displayName}: balance ${balance} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`, householdStatusSettlementBalance: (amount, currency) => `Gross balance: ${amount} ${currency}`,
householdStatusTotals: (balance, paid, remaining, currency) => householdStatusSettlementPaid: (amount, currency) => `Paid so far: ${amount} ${currency}`,
`Household total: balance ${balance} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${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}`, statementTitle: (period) => `Statement for ${period}`,
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`, statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
statementTotal: (amount, currency) => `Total: ${amount} ${currency}`, statementTotal: (amount, currency) => `Total: ${amount} ${currency}`,

View File

@@ -234,15 +234,22 @@ export const ruBotTranslations: BotTranslationCatalog = {
noStatementCycle: 'Для выписки период не найден.', noStatementCycle: 'Для выписки период не найден.',
householdStatusTitle: (period) => `Статус на ${period}`, householdStatusTitle: (period) => `Статус на ${period}`,
householdStatusDueDate: (dueDate) => `Срок оплаты аренды: до ${dueDate}`, householdStatusDueDate: (dueDate) => `Срок оплаты аренды: до ${dueDate}`,
householdStatusChargesHeading: 'Начисления',
householdStatusRentDirect: (amount, currency) => `Аренда: ${amount} ${currency}`, householdStatusRentDirect: (amount, currency) => `Аренда: ${amount} ${currency}`,
householdStatusRentConverted: (sourceAmount, sourceCurrency, displayAmount, displayCurrency) => householdStatusRentConverted: (sourceAmount, sourceCurrency, displayAmount, displayCurrency) =>
`Аренда: ${sourceAmount} ${sourceCurrency} (~${displayAmount} ${displayCurrency})`, `Аренда: ${sourceAmount} ${sourceCurrency} (~${displayAmount} ${displayCurrency})`,
householdStatusUtilities: (amount, currency) => `Коммуналка: ${amount} ${currency}`, householdStatusUtilities: (amount, currency) => `Коммуналка: ${amount} ${currency}`,
householdStatusPurchases: (amount, currency) => `Общие покупки: ${amount} ${currency}`, householdStatusPurchases: (amount, currency) => `Общие покупки: ${amount} ${currency}`,
householdStatusMember: (displayName, balance, paid, remaining, currency) => householdStatusSettlementHeading: 'Расчёты',
`- ${displayName}: баланс ${balance} ${currency}, оплачено ${paid} ${currency}, остаток ${remaining} ${currency}`, householdStatusSettlementBalance: (amount, currency) => `Общий баланс: ${amount} ${currency}`,
householdStatusTotals: (balance, paid, remaining, currency) => householdStatusSettlementPaid: (amount, currency) => `Уже оплачено: ${amount} ${currency}`,
`Итого по дому: баланс ${balance} ${currency}, оплачено ${paid} ${currency}, остаток ${remaining} ${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}`, statementTitle: (period) => `Выписка за ${period}`,
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`, statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
statementTotal: (amount, currency) => `Итого: ${amount} ${currency}`, statementTotal: (amount, currency) => `Итого: ${amount} ${currency}`,

View File

@@ -206,6 +206,7 @@ export interface BotTranslationCatalog {
noStatementCycle: string noStatementCycle: string
householdStatusTitle: (period: string) => string householdStatusTitle: (period: string) => string
householdStatusDueDate: (dueDate: string) => string householdStatusDueDate: (dueDate: string) => string
householdStatusChargesHeading: string
householdStatusRentDirect: (amount: string, currency: string) => string householdStatusRentDirect: (amount: string, currency: string) => string
householdStatusRentConverted: ( householdStatusRentConverted: (
sourceAmount: string, sourceAmount: string,
@@ -215,17 +216,21 @@ export interface BotTranslationCatalog {
) => string ) => string
householdStatusUtilities: (amount: string, currency: string) => string householdStatusUtilities: (amount: string, currency: string) => string
householdStatusPurchases: (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, displayName: string,
balance: string,
paid: string,
remaining: string, remaining: string,
currency: string currency: string
) => string ) => string
householdStatusTotals: ( householdStatusMemberDetailed: (
displayName: string,
remaining: string,
balance: string, balance: string,
paid: string, paid: string,
remaining: string,
currency: string currency: string
) => string ) => string
statementTitle: (period: string) => string statementTitle: (period: string) => string