mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 17:54:02 +00:00
feat(bot): add resident payment confirmation command
This commit is contained in:
@@ -61,6 +61,7 @@ export function createTelegramBot(
|
||||
await ctx.reply(
|
||||
formatTelegramHelpText(locale, {
|
||||
includePrivateCommands: ctx.chat?.type === 'private',
|
||||
includeGroupCommands: ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup',
|
||||
includeAdminCommands
|
||||
})
|
||||
)
|
||||
|
||||
@@ -233,6 +233,71 @@ export function createFinanceCommandsService(options: {
|
||||
}
|
||||
})
|
||||
|
||||
bot.command('payment_add', async (ctx) => {
|
||||
const locale = await resolveReplyLocale({
|
||||
ctx,
|
||||
repository: options.householdConfigurationRepository
|
||||
})
|
||||
const t = getBotTranslations(locale).finance
|
||||
const resolved = await requireMember(ctx)
|
||||
if (!resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
const args = commandArgs(ctx)
|
||||
const kind = args[0]
|
||||
if (kind !== 'rent' && kind !== 'utilities') {
|
||||
await ctx.reply(t.paymentAddUsage)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const dashboard = await resolved.service.generateDashboard()
|
||||
if (!dashboard) {
|
||||
await ctx.reply(t.paymentNoCycle)
|
||||
return
|
||||
}
|
||||
|
||||
const currentMember = dashboard.members.find(
|
||||
(member) => member.memberId === resolved.member.id
|
||||
)
|
||||
if (!currentMember) {
|
||||
await ctx.reply(t.notMember)
|
||||
return
|
||||
}
|
||||
|
||||
const inferredAmount =
|
||||
kind === 'rent'
|
||||
? currentMember.rentShare
|
||||
: currentMember.netDue.subtract(currentMember.rentShare)
|
||||
|
||||
if (args[1] === undefined && inferredAmount.amountMinor <= 0n) {
|
||||
await ctx.reply(t.paymentNoBalance)
|
||||
return
|
||||
}
|
||||
|
||||
const amountArg = args[1] ?? inferredAmount.toMajorString()
|
||||
const currencyArg = args[2]
|
||||
const result = await resolved.service.addPayment(
|
||||
resolved.member.id,
|
||||
kind,
|
||||
amountArg,
|
||||
currencyArg
|
||||
)
|
||||
|
||||
if (!result) {
|
||||
await ctx.reply(t.paymentNoCycle)
|
||||
return
|
||||
}
|
||||
|
||||
await ctx.reply(
|
||||
t.paymentAdded(kind, result.amount.toMajorString(), result.currency, result.period)
|
||||
)
|
||||
} catch (error) {
|
||||
await ctx.reply(t.paymentAddFailed((error as Error).message))
|
||||
}
|
||||
})
|
||||
|
||||
bot.command('statement', async (ctx) => {
|
||||
const locale = await resolveReplyLocale({
|
||||
ctx,
|
||||
|
||||
@@ -12,12 +12,14 @@ export const enBotTranslations: BotTranslationCatalog = {
|
||||
bind_feedback_topic: 'Bind the current topic as feedback',
|
||||
bind_reminders_topic: 'Bind the current topic as reminders',
|
||||
bind_payments_topic: 'Bind the current topic as payments',
|
||||
payment_add: 'Record your rent or utilities payment',
|
||||
pending_members: 'List pending household join requests',
|
||||
approve_member: 'Approve a pending household member'
|
||||
},
|
||||
help: {
|
||||
intro: 'Household bot is live.',
|
||||
privateChatHeading: 'Private chat:',
|
||||
groupHeading: 'Group chat:',
|
||||
groupAdminsHeading: 'Group admins:'
|
||||
},
|
||||
bot: {
|
||||
@@ -135,6 +137,12 @@ export const enBotTranslations: BotTranslationCatalog = {
|
||||
utilityAdded: (name, amount, currency, period) =>
|
||||
`Utility bill added: ${name} ${amount} ${currency} for ${period}`,
|
||||
utilityAddFailed: (message) => `Failed to add utility bill: ${message}`,
|
||||
paymentAddUsage: 'Usage: /payment_add <rent|utilities> [amount] [USD|GEL]',
|
||||
paymentNoCycle: 'No billing cycle is ready yet.',
|
||||
paymentNoBalance: 'There is no payable balance for that payment type right now.',
|
||||
paymentAdded: (kind, amount, currency, period) =>
|
||||
`Payment recorded: ${kind === 'rent' ? 'rent' : 'utilities'} ${amount} ${currency} for ${period}`,
|
||||
paymentAddFailed: (message) => `Failed to record payment: ${message}`,
|
||||
noStatementCycle: 'No cycle found for statement.',
|
||||
statementTitle: (period) => `Statement for ${period}`,
|
||||
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
|
||||
|
||||
@@ -12,12 +12,14 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
||||
bind_feedback_topic: 'Назначить текущий топик для анонимных сообщений',
|
||||
bind_reminders_topic: 'Назначить текущий топик для напоминаний',
|
||||
bind_payments_topic: 'Назначить текущий топик для оплат',
|
||||
payment_add: 'Подтвердить оплату аренды или коммуналки',
|
||||
pending_members: 'Показать ожидающие заявки на вступление',
|
||||
approve_member: 'Подтвердить участника дома'
|
||||
},
|
||||
help: {
|
||||
intro: 'Бот для дома подключен.',
|
||||
privateChatHeading: 'Личный чат:',
|
||||
groupHeading: 'Группа дома:',
|
||||
groupAdminsHeading: 'Админы группы:'
|
||||
},
|
||||
bot: {
|
||||
@@ -138,6 +140,12 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
||||
utilityAdded: (name, amount, currency, period) =>
|
||||
`Коммунальный счёт добавлен: ${name} ${amount} ${currency} за ${period}`,
|
||||
utilityAddFailed: (message) => `Не удалось добавить коммунальный счёт: ${message}`,
|
||||
paymentAddUsage: 'Использование: /payment_add <rent|utilities> [amount] [USD|GEL]',
|
||||
paymentNoCycle: 'Биллинг-цикл пока не готов.',
|
||||
paymentNoBalance: 'Сейчас для этого типа оплаты нет суммы к подтверждению.',
|
||||
paymentAdded: (kind, amount, currency, period) =>
|
||||
`Оплата сохранена: ${kind === 'rent' ? 'аренда' : 'коммуналка'} ${amount} ${currency} за ${period}`,
|
||||
paymentAddFailed: (message) => `Не удалось сохранить оплату: ${message}`,
|
||||
noStatementCycle: 'Для выписки период не найден.',
|
||||
statementTitle: (period) => `Выписка за ${period}`,
|
||||
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
|
||||
|
||||
@@ -10,6 +10,7 @@ export type TelegramCommandName =
|
||||
| 'bind_feedback_topic'
|
||||
| 'bind_reminders_topic'
|
||||
| 'bind_payments_topic'
|
||||
| 'payment_add'
|
||||
| 'pending_members'
|
||||
| 'approve_member'
|
||||
|
||||
@@ -23,6 +24,7 @@ export interface BotCommandDescriptions {
|
||||
bind_feedback_topic: string
|
||||
bind_reminders_topic: string
|
||||
bind_payments_topic: string
|
||||
payment_add: string
|
||||
pending_members: string
|
||||
approve_member: string
|
||||
}
|
||||
@@ -39,6 +41,7 @@ export interface BotTranslationCatalog {
|
||||
help: {
|
||||
intro: string
|
||||
privateChatHeading: string
|
||||
groupHeading: string
|
||||
groupAdminsHeading: string
|
||||
}
|
||||
bot: {
|
||||
@@ -140,6 +143,16 @@ export interface BotTranslationCatalog {
|
||||
utilityNoOpenCycle: string
|
||||
utilityAdded: (name: string, amount: string, currency: string, period: string) => string
|
||||
utilityAddFailed: (message: string) => string
|
||||
paymentAddUsage: string
|
||||
paymentNoCycle: string
|
||||
paymentNoBalance: string
|
||||
paymentAdded: (
|
||||
kind: 'rent' | 'utilities',
|
||||
amount: string,
|
||||
currency: string,
|
||||
period: string
|
||||
) => string
|
||||
paymentAddFailed: (message: string) => string
|
||||
noStatementCycle: string
|
||||
statementTitle: (period: string) => string
|
||||
statementLine: (displayName: string, amount: string, currency: string) => string
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ScopedTelegramCommands {
|
||||
|
||||
export interface TelegramHelpOptions {
|
||||
includePrivateCommands?: boolean
|
||||
includeGroupCommands?: boolean
|
||||
includeAdminCommands?: boolean
|
||||
}
|
||||
|
||||
@@ -26,8 +27,12 @@ const PRIVATE_CHAT_COMMAND_NAMES = [
|
||||
'cancel'
|
||||
] as const satisfies readonly TelegramCommandName[]
|
||||
const GROUP_CHAT_COMMAND_NAMES = DEFAULT_COMMAND_NAMES
|
||||
const GROUP_ADMIN_COMMAND_NAMES = [
|
||||
const GROUP_MEMBER_COMMAND_NAMES = [
|
||||
...GROUP_CHAT_COMMAND_NAMES,
|
||||
'payment_add'
|
||||
] as const satisfies readonly TelegramCommandName[]
|
||||
const GROUP_ADMIN_COMMAND_NAMES = [
|
||||
...GROUP_MEMBER_COMMAND_NAMES,
|
||||
'setup',
|
||||
'bind_purchase_topic',
|
||||
'bind_feedback_topic',
|
||||
@@ -61,7 +66,7 @@ export function getTelegramCommandScopes(locale: BotLocale): readonly ScopedTele
|
||||
},
|
||||
{
|
||||
scope: 'all_group_chats',
|
||||
commands: mapCommands(locale, GROUP_CHAT_COMMAND_NAMES)
|
||||
commands: mapCommands(locale, GROUP_MEMBER_COMMAND_NAMES)
|
||||
},
|
||||
{
|
||||
scope: 'all_chat_administrators',
|
||||
@@ -76,14 +81,18 @@ export function formatTelegramHelpText(
|
||||
): string {
|
||||
const t = getBotTranslations(locale)
|
||||
const defaultCommands = new Set<TelegramCommandName>(DEFAULT_COMMAND_NAMES)
|
||||
const groupMemberCommands = new Set<TelegramCommandName>(GROUP_MEMBER_COMMAND_NAMES)
|
||||
const includePrivateCommands = options.includePrivateCommands ?? true
|
||||
const includeGroupCommands = options.includeGroupCommands ?? false
|
||||
const includeAdminCommands = options.includeAdminCommands ?? false
|
||||
const privateCommands = includePrivateCommands
|
||||
? mapCommands(locale, PRIVATE_CHAT_COMMAND_NAMES)
|
||||
: []
|
||||
const groupCommands = includeGroupCommands ? mapCommands(locale, GROUP_MEMBER_COMMAND_NAMES) : []
|
||||
const adminCommands = includeAdminCommands
|
||||
? mapCommands(locale, GROUP_ADMIN_COMMAND_NAMES).filter(
|
||||
(command) => !defaultCommands.has(command.command)
|
||||
(command) =>
|
||||
!defaultCommands.has(command.command) && !groupMemberCommands.has(command.command)
|
||||
)
|
||||
: []
|
||||
|
||||
@@ -96,6 +105,13 @@ export function formatTelegramHelpText(
|
||||
)
|
||||
}
|
||||
|
||||
if (groupCommands.length > 0) {
|
||||
sections.push(
|
||||
t.help.groupHeading,
|
||||
...groupCommands.map((command) => `/${command.command} - ${command.description}`)
|
||||
)
|
||||
}
|
||||
|
||||
if (adminCommands.length > 0) {
|
||||
sections.push(
|
||||
t.help.groupAdminsHeading,
|
||||
|
||||
Reference in New Issue
Block a user