mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 13: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(
|
await ctx.reply(
|
||||||
formatTelegramHelpText(locale, {
|
formatTelegramHelpText(locale, {
|
||||||
includePrivateCommands: ctx.chat?.type === 'private',
|
includePrivateCommands: ctx.chat?.type === 'private',
|
||||||
|
includeGroupCommands: ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup',
|
||||||
includeAdminCommands
|
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) => {
|
bot.command('statement', async (ctx) => {
|
||||||
const locale = await resolveReplyLocale({
|
const locale = await resolveReplyLocale({
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ export const enBotTranslations: BotTranslationCatalog = {
|
|||||||
bind_feedback_topic: 'Bind the current topic as feedback',
|
bind_feedback_topic: 'Bind the current topic as feedback',
|
||||||
bind_reminders_topic: 'Bind the current topic as reminders',
|
bind_reminders_topic: 'Bind the current topic as reminders',
|
||||||
bind_payments_topic: 'Bind the current topic as payments',
|
bind_payments_topic: 'Bind the current topic as payments',
|
||||||
|
payment_add: 'Record your rent or utilities payment',
|
||||||
pending_members: 'List pending household join requests',
|
pending_members: 'List pending household join requests',
|
||||||
approve_member: 'Approve a pending household member'
|
approve_member: 'Approve a pending household member'
|
||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
intro: 'Household bot is live.',
|
intro: 'Household bot is live.',
|
||||||
privateChatHeading: 'Private chat:',
|
privateChatHeading: 'Private chat:',
|
||||||
|
groupHeading: 'Group chat:',
|
||||||
groupAdminsHeading: 'Group admins:'
|
groupAdminsHeading: 'Group admins:'
|
||||||
},
|
},
|
||||||
bot: {
|
bot: {
|
||||||
@@ -135,6 +137,12 @@ export const enBotTranslations: BotTranslationCatalog = {
|
|||||||
utilityAdded: (name, amount, currency, period) =>
|
utilityAdded: (name, amount, currency, period) =>
|
||||||
`Utility bill added: ${name} ${amount} ${currency} for ${period}`,
|
`Utility bill added: ${name} ${amount} ${currency} for ${period}`,
|
||||||
utilityAddFailed: (message) => `Failed to add utility bill: ${message}`,
|
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.',
|
noStatementCycle: 'No cycle found for statement.',
|
||||||
statementTitle: (period) => `Statement for ${period}`,
|
statementTitle: (period) => `Statement for ${period}`,
|
||||||
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
|
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
|||||||
bind_feedback_topic: 'Назначить текущий топик для анонимных сообщений',
|
bind_feedback_topic: 'Назначить текущий топик для анонимных сообщений',
|
||||||
bind_reminders_topic: 'Назначить текущий топик для напоминаний',
|
bind_reminders_topic: 'Назначить текущий топик для напоминаний',
|
||||||
bind_payments_topic: 'Назначить текущий топик для оплат',
|
bind_payments_topic: 'Назначить текущий топик для оплат',
|
||||||
|
payment_add: 'Подтвердить оплату аренды или коммуналки',
|
||||||
pending_members: 'Показать ожидающие заявки на вступление',
|
pending_members: 'Показать ожидающие заявки на вступление',
|
||||||
approve_member: 'Подтвердить участника дома'
|
approve_member: 'Подтвердить участника дома'
|
||||||
},
|
},
|
||||||
help: {
|
help: {
|
||||||
intro: 'Бот для дома подключен.',
|
intro: 'Бот для дома подключен.',
|
||||||
privateChatHeading: 'Личный чат:',
|
privateChatHeading: 'Личный чат:',
|
||||||
|
groupHeading: 'Группа дома:',
|
||||||
groupAdminsHeading: 'Админы группы:'
|
groupAdminsHeading: 'Админы группы:'
|
||||||
},
|
},
|
||||||
bot: {
|
bot: {
|
||||||
@@ -138,6 +140,12 @@ export const ruBotTranslations: BotTranslationCatalog = {
|
|||||||
utilityAdded: (name, amount, currency, period) =>
|
utilityAdded: (name, amount, currency, period) =>
|
||||||
`Коммунальный счёт добавлен: ${name} ${amount} ${currency} за ${period}`,
|
`Коммунальный счёт добавлен: ${name} ${amount} ${currency} за ${period}`,
|
||||||
utilityAddFailed: (message) => `Не удалось добавить коммунальный счёт: ${message}`,
|
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: 'Для выписки период не найден.',
|
noStatementCycle: 'Для выписки период не найден.',
|
||||||
statementTitle: (period) => `Выписка за ${period}`,
|
statementTitle: (period) => `Выписка за ${period}`,
|
||||||
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
|
statementLine: (displayName, amount, currency) => `- ${displayName}: ${amount} ${currency}`,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type TelegramCommandName =
|
|||||||
| 'bind_feedback_topic'
|
| 'bind_feedback_topic'
|
||||||
| 'bind_reminders_topic'
|
| 'bind_reminders_topic'
|
||||||
| 'bind_payments_topic'
|
| 'bind_payments_topic'
|
||||||
|
| 'payment_add'
|
||||||
| 'pending_members'
|
| 'pending_members'
|
||||||
| 'approve_member'
|
| 'approve_member'
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ export interface BotCommandDescriptions {
|
|||||||
bind_feedback_topic: string
|
bind_feedback_topic: string
|
||||||
bind_reminders_topic: string
|
bind_reminders_topic: string
|
||||||
bind_payments_topic: string
|
bind_payments_topic: string
|
||||||
|
payment_add: string
|
||||||
pending_members: string
|
pending_members: string
|
||||||
approve_member: string
|
approve_member: string
|
||||||
}
|
}
|
||||||
@@ -39,6 +41,7 @@ export interface BotTranslationCatalog {
|
|||||||
help: {
|
help: {
|
||||||
intro: string
|
intro: string
|
||||||
privateChatHeading: string
|
privateChatHeading: string
|
||||||
|
groupHeading: string
|
||||||
groupAdminsHeading: string
|
groupAdminsHeading: string
|
||||||
}
|
}
|
||||||
bot: {
|
bot: {
|
||||||
@@ -140,6 +143,16 @@ export interface BotTranslationCatalog {
|
|||||||
utilityNoOpenCycle: string
|
utilityNoOpenCycle: string
|
||||||
utilityAdded: (name: string, amount: string, currency: string, period: string) => string
|
utilityAdded: (name: string, amount: string, currency: string, period: string) => string
|
||||||
utilityAddFailed: (message: 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
|
noStatementCycle: string
|
||||||
statementTitle: (period: string) => string
|
statementTitle: (period: string) => string
|
||||||
statementLine: (displayName: string, amount: string, currency: string) => string
|
statementLine: (displayName: string, amount: string, currency: string) => string
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface ScopedTelegramCommands {
|
|||||||
|
|
||||||
export interface TelegramHelpOptions {
|
export interface TelegramHelpOptions {
|
||||||
includePrivateCommands?: boolean
|
includePrivateCommands?: boolean
|
||||||
|
includeGroupCommands?: boolean
|
||||||
includeAdminCommands?: boolean
|
includeAdminCommands?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,8 +27,12 @@ const PRIVATE_CHAT_COMMAND_NAMES = [
|
|||||||
'cancel'
|
'cancel'
|
||||||
] as const satisfies readonly TelegramCommandName[]
|
] as const satisfies readonly TelegramCommandName[]
|
||||||
const GROUP_CHAT_COMMAND_NAMES = DEFAULT_COMMAND_NAMES
|
const GROUP_CHAT_COMMAND_NAMES = DEFAULT_COMMAND_NAMES
|
||||||
const GROUP_ADMIN_COMMAND_NAMES = [
|
const GROUP_MEMBER_COMMAND_NAMES = [
|
||||||
...GROUP_CHAT_COMMAND_NAMES,
|
...GROUP_CHAT_COMMAND_NAMES,
|
||||||
|
'payment_add'
|
||||||
|
] as const satisfies readonly TelegramCommandName[]
|
||||||
|
const GROUP_ADMIN_COMMAND_NAMES = [
|
||||||
|
...GROUP_MEMBER_COMMAND_NAMES,
|
||||||
'setup',
|
'setup',
|
||||||
'bind_purchase_topic',
|
'bind_purchase_topic',
|
||||||
'bind_feedback_topic',
|
'bind_feedback_topic',
|
||||||
@@ -61,7 +66,7 @@ export function getTelegramCommandScopes(locale: BotLocale): readonly ScopedTele
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
scope: 'all_group_chats',
|
scope: 'all_group_chats',
|
||||||
commands: mapCommands(locale, GROUP_CHAT_COMMAND_NAMES)
|
commands: mapCommands(locale, GROUP_MEMBER_COMMAND_NAMES)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scope: 'all_chat_administrators',
|
scope: 'all_chat_administrators',
|
||||||
@@ -76,14 +81,18 @@ export function formatTelegramHelpText(
|
|||||||
): string {
|
): string {
|
||||||
const t = getBotTranslations(locale)
|
const t = getBotTranslations(locale)
|
||||||
const defaultCommands = new Set<TelegramCommandName>(DEFAULT_COMMAND_NAMES)
|
const defaultCommands = new Set<TelegramCommandName>(DEFAULT_COMMAND_NAMES)
|
||||||
|
const groupMemberCommands = new Set<TelegramCommandName>(GROUP_MEMBER_COMMAND_NAMES)
|
||||||
const includePrivateCommands = options.includePrivateCommands ?? true
|
const includePrivateCommands = options.includePrivateCommands ?? true
|
||||||
|
const includeGroupCommands = options.includeGroupCommands ?? false
|
||||||
const includeAdminCommands = options.includeAdminCommands ?? false
|
const includeAdminCommands = options.includeAdminCommands ?? false
|
||||||
const privateCommands = includePrivateCommands
|
const privateCommands = includePrivateCommands
|
||||||
? mapCommands(locale, PRIVATE_CHAT_COMMAND_NAMES)
|
? mapCommands(locale, PRIVATE_CHAT_COMMAND_NAMES)
|
||||||
: []
|
: []
|
||||||
|
const groupCommands = includeGroupCommands ? mapCommands(locale, GROUP_MEMBER_COMMAND_NAMES) : []
|
||||||
const adminCommands = includeAdminCommands
|
const adminCommands = includeAdminCommands
|
||||||
? mapCommands(locale, GROUP_ADMIN_COMMAND_NAMES).filter(
|
? 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) {
|
if (adminCommands.length > 0) {
|
||||||
sections.push(
|
sections.push(
|
||||||
t.help.groupAdminsHeading,
|
t.help.groupAdminsHeading,
|
||||||
|
|||||||
Reference in New Issue
Block a user