feat(finance): add billing correction APIs and cycle rollover

This commit is contained in:
2026-03-10 22:03:30 +04:00
parent 05561a397d
commit 753286a1f6
11 changed files with 943 additions and 26 deletions

View File

@@ -68,7 +68,7 @@ class FinanceRepositoryStub implements FinanceRepository {
}
async getCycleByPeriod(): Promise<FinanceCycleRecord | null> {
return this.cycleByPeriodRecord
return this.cycleByPeriodRecord ?? this.openCycleRecord ?? this.latestCycleRecord
}
async getLatestCycle(): Promise<FinanceCycleRecord | null> {
@@ -76,11 +76,14 @@ class FinanceRepositoryStub implements FinanceRepository {
}
async openCycle(period: string, currency: 'USD' | 'GEL'): Promise<void> {
this.openCycleRecord = {
const cycle = {
id: 'opened-cycle',
period,
currency
}
this.openCycleRecord = cycle
this.cycleByPeriodRecord = cycle
this.latestCycleRecord = cycle
}
async closeCycle(): Promise<void> {}
@@ -129,6 +132,40 @@ class FinanceRepositoryStub implements FinanceRepository {
return false
}
async updateParsedPurchase() {
return null
}
async deleteParsedPurchase() {
return false
}
async addPaymentRecord(input: {
cycleId: string
memberId: string
kind: 'rent' | 'utilities'
amountMinor: bigint
currency: 'USD' | 'GEL'
recordedAt: Instant
}) {
return {
id: 'payment-record-1',
memberId: input.memberId,
kind: input.kind,
amountMinor: input.amountMinor,
currency: input.currency,
recordedAt: input.recordedAt
}
}
async updatePaymentRecord() {
return null
}
async deletePaymentRecord() {
return false
}
async getRentRuleForPeriod(): Promise<FinanceRentRuleRecord | null> {
return this.rentRule
}
@@ -304,14 +341,21 @@ describe('createFinanceCommandService', () => {
})
})
test('addUtilityBill returns null when no open cycle exists', async () => {
test('addUtilityBill auto-opens the expected cycle when none is active', async () => {
const repository = new FinanceRepositoryStub()
const service = createService(repository)
const result = await service.addUtilityBill('Electricity', '55.20', 'member-1')
expect(result).toBeNull()
expect(repository.lastUtilityBill).toBeNull()
expect(result).not.toBeNull()
expect(result?.period).toBe('2026-03')
expect(repository.lastUtilityBill).toEqual({
cycleId: 'opened-cycle',
billName: 'Electricity',
amountMinor: 5520n,
currency: 'GEL',
createdByMemberId: 'member-1'
})
})
test('generateStatement settles into cycle currency and persists snapshot', async () => {