From d0475743f8e2256fda7aa00a144d9f7b0c2ffc6f Mon Sep 17 00:00:00 2001 From: whekin Date: Sun, 15 Mar 2026 01:38:54 +0400 Subject: [PATCH] fix(finance): gracefully handle initial state without rent rules Allow building the finance dashboard even if no rent rule is configured for the cycle. Defaults to 0 rent in the cycle's currency. Added regression tests in application service and miniapp dashboard handler. --- apps/bot/src/miniapp-dashboard.test.ts | 69 +++++++++++++++++++ .../src/finance-command-service.test.ts | 30 ++++++++ .../src/finance-command-service.ts | 7 +- 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/apps/bot/src/miniapp-dashboard.test.ts b/apps/bot/src/miniapp-dashboard.test.ts index f4866c7..ccc8fc5 100644 --- a/apps/bot/src/miniapp-dashboard.test.ts +++ b/apps/bot/src/miniapp-dashboard.test.ts @@ -608,4 +608,73 @@ describe('createMiniAppDashboardHandler', () => { error: 'Invalid JSON body' }) }) + + test('returns a dashboard even if rent rule is missing', async () => { + const authDate = Math.floor(Date.now() / 1000) + const householdRepository = onboardingRepository() + const financeRepository = repository({ + id: 'member-1', + telegramUserId: '123456', + displayName: 'Stan', + rentShareWeight: 1, + isAdmin: true + }) + + // Simulate missing rent rule + financeRepository.getRentRuleForPeriod = async () => null + + const financeService = createFinanceCommandService({ + householdId: 'household-1', + repository: financeRepository, + householdConfigurationRepository: householdRepository, + exchangeRateProvider + }) + + householdRepository.listHouseholdMembersByTelegramUserId = async () => [ + { + id: 'member-1', + householdId: 'household-1', + telegramUserId: '123456', + displayName: 'Stan', + status: 'active', + preferredLocale: null, + householdDefaultLocale: 'ru', + rentShareWeight: 1, + isAdmin: true + } + ] + + const dashboard = createMiniAppDashboardHandler({ + allowedOrigins: ['http://localhost:5173'], + botToken: 'test-bot-token', + financeServiceForHousehold: () => financeService, + onboardingService: createHouseholdOnboardingService({ + repository: householdRepository + }) + }) + + const response = await dashboard.handler( + new Request('http://localhost/api/miniapp/dashboard', { + method: 'POST', + headers: { + origin: 'http://localhost:5173', + 'content-type': 'application/json' + }, + body: JSON.stringify({ + initData: buildMiniAppInitData('test-bot-token', authDate, { + id: 123456, + first_name: 'Stan', + username: 'stanislav', + language_code: 'ru' + }) + }) + }) + ) + + expect(response.status).toBe(200) + const data = (await response.json()) as any + expect(data.ok).toBe(true) + expect(data.dashboard.rentSourceAmountMajor).toBe('0.00') + expect(data.dashboard.rentDisplayAmountMajor).toBe('0.00') + }) }) diff --git a/packages/application/src/finance-command-service.test.ts b/packages/application/src/finance-command-service.test.ts index e524dff..7d42bf4 100644 --- a/packages/application/src/finance-command-service.test.ts +++ b/packages/application/src/finance-command-service.test.ts @@ -842,4 +842,34 @@ describe('createFinanceCommandService', () => { // Alice paid 1000n and her share is 1000n -> offset 0n expect(aliceLine?.purchaseOffset.amountMinor).toBe(0n) }) + + test('generateDashboard succeeds even if rent rule is missing', async () => { + const repository = new FinanceRepositoryStub() + repository.members = [ + { + id: 'alice', + telegramUserId: '1', + displayName: 'Alice', + rentShareWeight: 1, + isAdmin: true + } + ] + repository.openCycleRecord = { + id: 'cycle-2026-03', + period: '2026-03', + currency: 'GEL' + } + + // Simulate missing rent rule + repository.rentRule = null + + const service = createService(repository) + const dashboard = await service.generateDashboard() + + expect(dashboard).not.toBeNull() + expect(dashboard?.period).toBe('2026-03') + expect(dashboard?.rentSourceAmount.amountMinor).toBe(0n) + expect(dashboard?.rentDisplayAmount.amountMinor).toBe(0n) + expect(dashboard?.totalDue.amountMinor).toBe(0n) + }) }) diff --git a/packages/application/src/finance-command-service.ts b/packages/application/src/finance-command-service.ts index b73f9f6..8ce90b0 100644 --- a/packages/application/src/finance-command-service.ts +++ b/packages/application/src/finance-command-service.ts @@ -312,9 +312,8 @@ async function buildFinanceDashboard( throw new Error('No household members configured') } - if (!rentRule) { - throw new Error('No rent rule configured for this cycle period') - } + const rentAmountMinor = rentRule?.amountMinor ?? 0n + const rentCurrency = rentRule?.currency ?? cycle.currency const period = BillingPeriod.fromString(cycle.period) const { start, end } = monthRange(period) @@ -344,7 +343,7 @@ async function buildFinanceDashboard( period, lockDay: settings.rentWarningDay, timezone: settings.timezone, - amount: Money.fromMinor(rentRule.amountMinor, rentRule.currency) + amount: Money.fromMinor(rentAmountMinor, rentCurrency) }) const convertedUtilityBills = await Promise.all(