feat(bot): quiet finance topics and support purchase payers

This commit is contained in:
2026-03-22 20:27:43 +04:00
parent 7d706eba07
commit 7665af0268
22 changed files with 1044 additions and 81 deletions

View File

@@ -355,6 +355,7 @@ export function createDbFinanceRepository(
id: purchaseId,
householdId,
senderMemberId: input.payerMemberId,
payerMemberId: input.payerMemberId,
senderTelegramUserId: 'miniapp',
senderDisplayName: member?.displayName ?? 'Mini App',
telegramChatId: 'miniapp',
@@ -388,7 +389,7 @@ export function createDbFinanceRepository(
const rows = await db
.select({
id: schema.purchaseMessages.id,
payerMemberId: schema.purchaseMessages.senderMemberId,
payerMemberId: schema.purchaseMessages.payerMemberId,
amountMinor: schema.purchaseMessages.parsedAmountMinor,
currency: schema.purchaseMessages.parsedCurrency,
description: schema.purchaseMessages.parsedItemDescription,
@@ -443,7 +444,8 @@ export function createDbFinanceRepository(
: {}),
...(input.payerMemberId
? {
senderMemberId: input.payerMemberId
senderMemberId: input.payerMemberId,
payerMemberId: input.payerMemberId
}
: {}),
needsReview: 0,
@@ -458,7 +460,7 @@ export function createDbFinanceRepository(
)
.returning({
id: schema.purchaseMessages.id,
payerMemberId: schema.purchaseMessages.senderMemberId,
payerMemberId: schema.purchaseMessages.payerMemberId,
amountMinor: schema.purchaseMessages.parsedAmountMinor,
currency: schema.purchaseMessages.parsedCurrency,
description: schema.purchaseMessages.parsedItemDescription,
@@ -763,7 +765,7 @@ export function createDbFinanceRepository(
const rows = await db
.select({
id: schema.purchaseMessages.id,
payerMemberId: schema.purchaseMessages.senderMemberId,
payerMemberId: schema.purchaseMessages.payerMemberId,
amountMinor: schema.purchaseMessages.parsedAmountMinor,
currency: schema.purchaseMessages.parsedCurrency,
description: schema.purchaseMessages.parsedItemDescription,
@@ -774,7 +776,7 @@ export function createDbFinanceRepository(
.where(
and(
eq(schema.purchaseMessages.householdId, householdId),
isNotNull(schema.purchaseMessages.senderMemberId),
isNotNull(schema.purchaseMessages.payerMemberId),
isNotNull(schema.purchaseMessages.parsedAmountMinor),
isNotNull(schema.purchaseMessages.parsedCurrency),
or(

View File

@@ -15,6 +15,23 @@ import type {
import { createFinanceCommandService } from './finance-command-service'
function expectedCurrentCyclePeriod(timezone: string, rentDueDay: number): string {
const parts = new Intl.DateTimeFormat('en-CA', {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).formatToParts(new Date())
const year = Number(parts.find((part) => part.type === 'year')?.value ?? '0')
const month = Number(parts.find((part) => part.type === 'month')?.value ?? '1')
const day = Number(parts.find((part) => part.type === 'day')?.value ?? '1')
const carryMonth = day > rentDueDay ? month + 1 : month
const normalizedYear = carryMonth > 12 ? year + 1 : year
const normalizedMonth = carryMonth > 12 ? 1 : carryMonth
return `${normalizedYear}-${String(normalizedMonth).padStart(2, '0')}`
}
class FinanceRepositoryStub implements FinanceRepository {
householdId = 'household-1'
member: FinanceMemberRecord | null = null
@@ -428,9 +445,10 @@ describe('createFinanceCommandService', () => {
const service = createService(repository)
const result = await service.addUtilityBill('Electricity', '55.20', 'member-1')
const expectedPeriod = expectedCurrentCyclePeriod('Asia/Tbilisi', 20)
expect(result).not.toBeNull()
expect(result?.period).toBe('2026-03')
expect(result?.period).toBe(expectedPeriod)
expect(repository.lastUtilityBill).toEqual({
cycleId: 'opened-cycle',
billName: 'Electricity',

View File

@@ -22,6 +22,7 @@
"0018_nimble_kojori.sql": "818738e729119c6de8049dcfca562926a5dc6e321ecbbf9cf38e02bc70b5a0dc",
"0019_faithful_madame_masque.sql": "38711341799b04a7c47fcc64fd19faf5b26e6f183d6a4c01d492b9929cd63641",
"0020_natural_mauler.sql": "a80a4a0196a3b4931040850089346d1bc99b34a5afca77d6d62478ee4b8902c1",
"0020_silver_payments.sql": "9686235c75453f1eaa016f2f4ab7fce8fe964c76a4e3515987a2b9f90bd7b1ad"
"0020_silver_payments.sql": "9686235c75453f1eaa016f2f4ab7fce8fe964c76a4e3515987a2b9f90bd7b1ad",
"0021_sharp_payer.sql": "973596e154382984ba7769979ea58298b6d93c5139540854be01e8b283ddb4f1"
}
}

View File

@@ -0,0 +1,7 @@
ALTER TABLE "purchase_messages"
ADD COLUMN "payer_member_id" uuid REFERENCES "members"("id") ON DELETE SET NULL;
UPDATE "purchase_messages"
SET "payer_member_id" = "sender_member_id"
WHERE "payer_member_id" IS NULL
AND "sender_member_id" IS NOT NULL;

View File

@@ -148,6 +148,13 @@
"when": 1773590603863,
"tag": "0020_natural_mauler",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1774200000000,
"tag": "0021_sharp_payer",
"breakpoints": true
}
]
}

View File

@@ -417,6 +417,9 @@ export const purchaseMessages = pgTable(
senderMemberId: uuid('sender_member_id').references(() => members.id, {
onDelete: 'set null'
}),
payerMemberId: uuid('payer_member_id').references(() => members.id, {
onDelete: 'set null'
}),
senderTelegramUserId: text('sender_telegram_user_id').notNull(),
senderDisplayName: text('sender_display_name'),
rawText: text('raw_text').notNull(),