Fix/balance inconsitency (#306)
* feat: implement fiscal period date fields component and validation logic * feat: update fiscal period validation and naming logic * feat: implement RPC for computing prior opening balances - Added `compute_prior_opening_balances` RPC to aggregate opening balances for balance-sheet accounts when no opening balance entry is set. - Updated tests across various reports to utilize the new RPC for fetching prior balances. - Refactored `getOpeningBalances` to call the RPC when necessary, improving performance and reliability. - Introduced a script to repair fiscal period chains for companies with broken periods, ensuring proper linking and continuity. - Enhanced error handling and validation in the repair script to ensure data integrity during the process. * feat: implement duplicate opening-balance repair for multi-year SIE imports * feat: enhance SIE entry listing and deduplication logic for opening balances * fix: refine companyHasPriorActivity logic to exclude storno entries and improve balance counting
This commit is contained in:
@@ -234,4 +234,114 @@ describe('POST /api/bookkeeping/fiscal-periods', () => {
|
||||
const res = await POST(req)
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('sets previous_period_id when chaining forward', async () => {
|
||||
// Build a mock that captures the insert payload.
|
||||
const insertSpy = vi.fn().mockReturnValue({
|
||||
select: vi.fn().mockReturnValue({
|
||||
single: vi.fn().mockResolvedValue({
|
||||
data: { id: 'new-period', name: 'FY 2026' },
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
let fpCallIndex = 0
|
||||
const supabase = {
|
||||
auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'user-1' } } }) },
|
||||
from: vi.fn().mockImplementation(() => {
|
||||
fpCallIndex++
|
||||
const callNum = fpCallIndex
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
select: vi.fn().mockImplementation((_sel: string, opts?: any) => {
|
||||
if (opts?.count === 'exact') {
|
||||
return { eq: vi.fn().mockReturnValue({ eq: vi.fn().mockResolvedValue({ count: 0 }) }) }
|
||||
}
|
||||
if (callNum === 1) {
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
order: vi.fn().mockResolvedValue({
|
||||
data: [{ id: 'prior-period-id', period_start: '2025-01-01', period_end: '2025-12-31', is_closed: true, closing_entry_id: null }],
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
lte: vi.fn().mockReturnValue({
|
||||
gte: vi.fn().mockReturnValue({ limit: vi.fn().mockResolvedValue({ data: [], error: null }) }),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}),
|
||||
insert: insertSpy,
|
||||
update: vi.fn().mockReturnValue({ eq: vi.fn().mockReturnValue({ eq: vi.fn().mockResolvedValue({ error: null }) }) }),
|
||||
}
|
||||
}),
|
||||
}
|
||||
;(createClient as ReturnType<typeof vi.fn>).mockResolvedValue(supabase)
|
||||
|
||||
const req = createMockRequest({ name: 'FY 2026', period_start: '2026-01-01', period_end: '2026-12-31' })
|
||||
const res = await POST(req)
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(insertSpy).toHaveBeenCalledTimes(1)
|
||||
const insertArg = insertSpy.mock.calls[0][0]
|
||||
expect(insertArg.previous_period_id).toBe('prior-period-id')
|
||||
expect(insertArg.period_start).toBe('2026-01-01')
|
||||
})
|
||||
|
||||
it('does not set previous_period_id for the first period', async () => {
|
||||
const insertSpy = vi.fn().mockReturnValue({
|
||||
select: vi.fn().mockReturnValue({
|
||||
single: vi.fn().mockResolvedValue({
|
||||
data: { id: 'new-period', name: 'FY 2025' },
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
let fpCallIndex = 0
|
||||
const supabase = {
|
||||
auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'user-1' } } }) },
|
||||
from: vi.fn().mockImplementation(() => {
|
||||
fpCallIndex++
|
||||
const callNum = fpCallIndex
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
select: vi.fn().mockImplementation((_sel: string, opts?: any) => {
|
||||
if (opts?.count === 'exact') {
|
||||
return { eq: vi.fn().mockReturnValue({ eq: vi.fn().mockResolvedValue({ count: 0 }) }) }
|
||||
}
|
||||
if (callNum === 1) {
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
order: vi.fn().mockResolvedValue({ data: [], error: null }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
lte: vi.fn().mockReturnValue({
|
||||
gte: vi.fn().mockReturnValue({ limit: vi.fn().mockResolvedValue({ data: [], error: null }) }),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}),
|
||||
insert: insertSpy,
|
||||
update: vi.fn().mockReturnValue({ eq: vi.fn().mockReturnValue({ eq: vi.fn().mockResolvedValue({ error: null }) }) }),
|
||||
}
|
||||
}),
|
||||
}
|
||||
;(createClient as ReturnType<typeof vi.fn>).mockResolvedValue(supabase)
|
||||
|
||||
const req = createMockRequest({ name: 'FY 2025', period_start: '2025-01-01', period_end: '2025-12-31' })
|
||||
const res = await POST(req)
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(insertSpy).toHaveBeenCalledTimes(1)
|
||||
expect(insertSpy.mock.calls[0][0].previous_period_id).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -132,6 +132,19 @@ export async function POST(request: Request) {
|
||||
)
|
||||
}
|
||||
|
||||
// Resolve previous_period_id for forward chaining so the new period is
|
||||
// linked to the period it follows. Without this, balance-sheet/trial-balance
|
||||
// reports fall back to scanning every prior journal line (BFNAR 2013:2
|
||||
// continuity chain is broken). Backward chaining sets previous_period_id
|
||||
// on the old earliest period instead (see below), not on the new one.
|
||||
let previousPeriodId: string | null = null
|
||||
if (allPeriods && allPeriods.length > 0) {
|
||||
const latest = allPeriods[allPeriods.length - 1]
|
||||
if (body.period_start > latest.period_end) {
|
||||
previousPeriodId = latest.id
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('fiscal_periods')
|
||||
.insert({
|
||||
@@ -140,6 +153,7 @@ export async function POST(request: Request) {
|
||||
name: body.name,
|
||||
period_start: body.period_start,
|
||||
period_end: body.period_end,
|
||||
previous_period_id: previousPeriodId,
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
|
||||
Reference in New Issue
Block a user