mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 15:54:03 +00:00
refactor(bot): refine household status wording
This commit is contained in:
@@ -275,12 +275,13 @@ describe('createFinanceCommandsService', () => {
|
|||||||
await bot.handleUpdate(householdStatusUpdate('ru') as never)
|
await bot.handleUpdate(householdStatusUpdate('ru') as never)
|
||||||
|
|
||||||
const payload = calls[0]?.payload as { text?: string } | undefined
|
const payload = calls[0]?.payload as { text?: string } | undefined
|
||||||
expect(payload?.text).toContain('Статус дома за 2026-03')
|
expect(payload?.text).toContain('Статус на март 2026')
|
||||||
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(
|
expect(payload?.text).toContain(
|
||||||
'- Стас: должен 210.00 GEL, оплачено 100.00 GEL, осталось 110.00 GEL'
|
'- Стас: баланс 210.00 GEL, оплачено 100.00 GEL, остаток 110.00 GEL'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,51 @@ function commandArgs(ctx: Context): string[] {
|
|||||||
return raw.split(/\s+/).filter(Boolean)
|
return raw.split(/\s+/).filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatBillingPeriodLabel(
|
||||||
|
locale: Parameters<typeof getBotTranslations>[0],
|
||||||
|
period: string
|
||||||
|
): string {
|
||||||
|
const [yearRaw, monthRaw] = period.split('-')
|
||||||
|
const year = Number(yearRaw)
|
||||||
|
const month = Number(monthRaw)
|
||||||
|
|
||||||
|
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
|
||||||
|
return period
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatter = new Intl.DateTimeFormat(locale === 'ru' ? 'ru-RU' : 'en-US', {
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
timeZone: 'UTC'
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatter.format(new Date(Date.UTC(year, month - 1, 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCycleDueDate(
|
||||||
|
locale: Parameters<typeof getBotTranslations>[0],
|
||||||
|
period: string,
|
||||||
|
dueDay: number
|
||||||
|
): string {
|
||||||
|
const [yearRaw, monthRaw] = period.split('-')
|
||||||
|
const year = Number(yearRaw)
|
||||||
|
const month = Number(monthRaw)
|
||||||
|
|
||||||
|
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
|
||||||
|
return period
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxDay = new Date(Date.UTC(year, month, 0)).getUTCDate()
|
||||||
|
const day = Math.min(Math.max(dueDay, 1), maxDay)
|
||||||
|
const formatter = new Intl.DateTimeFormat(locale === 'ru' ? 'ru-RU' : 'en-US', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
timeZone: 'UTC'
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatter.format(new Date(Date.UTC(year, month - 1, day)))
|
||||||
|
}
|
||||||
|
|
||||||
function isGroupChat(ctx: Context): boolean {
|
function isGroupChat(ctx: Context): boolean {
|
||||||
return ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup'
|
return ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup'
|
||||||
}
|
}
|
||||||
@@ -42,7 +87,8 @@ export function createFinanceCommandsService(options: {
|
|||||||
|
|
||||||
function formatHouseholdStatus(
|
function formatHouseholdStatus(
|
||||||
locale: Parameters<typeof getBotTranslations>[0],
|
locale: Parameters<typeof getBotTranslations>[0],
|
||||||
dashboard: NonNullable<Awaited<ReturnType<FinanceCommandService['generateDashboard']>>>
|
dashboard: NonNullable<Awaited<ReturnType<FinanceCommandService['generateDashboard']>>>,
|
||||||
|
dueDay: number
|
||||||
): string {
|
): string {
|
||||||
const t = getBotTranslations(locale).finance
|
const t = getBotTranslations(locale).finance
|
||||||
const utilityTotal = dashboard.ledger
|
const utilityTotal = dashboard.ledger
|
||||||
@@ -66,7 +112,8 @@ export function createFinanceCommandsService(options: {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
t.householdStatusTitle(dashboard.period),
|
t.householdStatusTitle(formatBillingPeriodLabel(locale, dashboard.period)),
|
||||||
|
t.householdStatusDueDate(formatCycleDueDate(locale, dashboard.period, dueDay)),
|
||||||
rentLine,
|
rentLine,
|
||||||
t.householdStatusUtilities(utilityTotal.toMajorString(), dashboard.currency),
|
t.householdStatusUtilities(utilityTotal.toMajorString(), dashboard.currency),
|
||||||
t.householdStatusPurchases(purchaseTotal.toMajorString(), dashboard.currency),
|
t.householdStatusPurchases(purchaseTotal.toMajorString(), dashboard.currency),
|
||||||
@@ -184,7 +231,11 @@ export function createFinanceCommandsService(options: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.reply(formatHouseholdStatus(locale, dashboard))
|
const settings = await options.householdConfigurationRepository.getHouseholdBillingSettings(
|
||||||
|
resolved.householdId
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.reply(formatHouseholdStatus(locale, dashboard, settings.rentDueDay))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await ctx.reply(t.statementFailed((error as Error).message))
|
await ctx.reply(t.statementFailed((error as Error).message))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,15 +142,16 @@ export const enBotTranslations: BotTranslationCatalog = {
|
|||||||
paymentAddFailed: (message) => `Failed to record payment: ${message}`,
|
paymentAddFailed: (message) => `Failed to record payment: ${message}`,
|
||||||
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}`,
|
||||||
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, due, paid, remaining, currency) =>
|
householdStatusMember: (displayName, balance, paid, remaining, currency) =>
|
||||||
`- ${displayName}: due ${due} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`,
|
`- ${displayName}: balance ${balance} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`,
|
||||||
householdStatusTotals: (due, paid, remaining, currency) =>
|
householdStatusTotals: (balance, paid, remaining, currency) =>
|
||||||
`Totals: due ${due} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`,
|
`Household total: balance ${balance} ${currency}, paid ${paid} ${currency}, remaining ${remaining} ${currency}`,
|
||||||
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}`,
|
||||||
|
|||||||
@@ -144,16 +144,17 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
|||||||
`Оплата сохранена: ${kind === 'rent' ? 'аренда' : 'коммуналка'} ${amount} ${currency} за ${period}`,
|
`Оплата сохранена: ${kind === 'rent' ? 'аренда' : 'коммуналка'} ${amount} ${currency} за ${period}`,
|
||||||
paymentAddFailed: (message) => `Не удалось сохранить оплату: ${message}`,
|
paymentAddFailed: (message) => `Не удалось сохранить оплату: ${message}`,
|
||||||
noStatementCycle: 'Для выписки период не найден.',
|
noStatementCycle: 'Для выписки период не найден.',
|
||||||
householdStatusTitle: (period) => `Статус дома за ${period}`,
|
householdStatusTitle: (period) => `Статус на ${period}`,
|
||||||
|
householdStatusDueDate: (dueDate) => `Срок оплаты аренды: до ${dueDate}`,
|
||||||
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, due, paid, remaining, currency) =>
|
householdStatusMember: (displayName, balance, paid, remaining, currency) =>
|
||||||
`- ${displayName}: должен ${due} ${currency}, оплачено ${paid} ${currency}, осталось ${remaining} ${currency}`,
|
`- ${displayName}: баланс ${balance} ${currency}, оплачено ${paid} ${currency}, остаток ${remaining} ${currency}`,
|
||||||
householdStatusTotals: (due, paid, remaining, currency) =>
|
householdStatusTotals: (balance, paid, remaining, currency) =>
|
||||||
`Итого: должен ${due} ${currency}, оплачено ${paid} ${currency}, осталось ${remaining} ${currency}`,
|
`Итого по дому: баланс ${balance} ${currency}, оплачено ${paid} ${currency}, остаток ${remaining} ${currency}`,
|
||||||
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}`,
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ export interface BotTranslationCatalog {
|
|||||||
paymentAddFailed: (message: string) => string
|
paymentAddFailed: (message: string) => string
|
||||||
noStatementCycle: string
|
noStatementCycle: string
|
||||||
householdStatusTitle: (period: string) => string
|
householdStatusTitle: (period: string) => string
|
||||||
|
householdStatusDueDate: (dueDate: string) => string
|
||||||
householdStatusRentDirect: (amount: string, currency: string) => string
|
householdStatusRentDirect: (amount: string, currency: string) => string
|
||||||
householdStatusRentConverted: (
|
householdStatusRentConverted: (
|
||||||
sourceAmount: string,
|
sourceAmount: string,
|
||||||
@@ -163,13 +164,13 @@ export interface BotTranslationCatalog {
|
|||||||
householdStatusPurchases: (amount: string, currency: string) => string
|
householdStatusPurchases: (amount: string, currency: string) => string
|
||||||
householdStatusMember: (
|
householdStatusMember: (
|
||||||
displayName: string,
|
displayName: string,
|
||||||
due: string,
|
balance: string,
|
||||||
paid: string,
|
paid: string,
|
||||||
remaining: string,
|
remaining: string,
|
||||||
currency: string
|
currency: string
|
||||||
) => string
|
) => string
|
||||||
householdStatusTotals: (
|
householdStatusTotals: (
|
||||||
due: string,
|
balance: string,
|
||||||
paid: string,
|
paid: string,
|
||||||
remaining: string,
|
remaining: string,
|
||||||
currency: string
|
currency: string
|
||||||
|
|||||||
Reference in New Issue
Block a user