Supp/04 27 (#361)
* fix: update fiscal period validation to account for locked periods * fix: restrict receipt alerts and visibility to development environment
This commit is contained in:
@@ -65,10 +65,12 @@ function buildMockSupabase(options: {
|
||||
|
||||
chainable.select = vi.fn().mockImplementation((_sel: string, opts?: { count?: string }) => {
|
||||
if (opts?.count === 'exact') {
|
||||
// openCount query
|
||||
// openCount query: .eq(company_id).eq(is_closed=false).is(locked_at, null)
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
eq: vi.fn().mockResolvedValue({ count: openCount }),
|
||||
eq: vi.fn().mockReturnValue({
|
||||
is: vi.fn().mockResolvedValue({ count: openCount }),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -160,7 +162,7 @@ describe('POST /api/bookkeeping/fiscal-periods', () => {
|
||||
expect(body.error).toMatch(/must start on 2026-01-01/)
|
||||
})
|
||||
|
||||
it('rejects forward period when unclosed period exists', async () => {
|
||||
it('rejects forward period when an unlocked open period exists', async () => {
|
||||
buildMockSupabase({
|
||||
allPeriods: [{ id: 'p1', period_start: '2025-01-01', period_end: '2025-12-31', is_closed: false }],
|
||||
openCount: 1,
|
||||
@@ -169,7 +171,26 @@ describe('POST /api/bookkeeping/fiscal-periods', () => {
|
||||
const res = await POST(req)
|
||||
expect(res.status).toBe(409)
|
||||
const body = await res.json()
|
||||
expect(body.error).toMatch(/unclosed period/)
|
||||
expect(body.error).toMatch(/unlocked period/)
|
||||
})
|
||||
|
||||
// Regression: BFL 6 kap allows löpande bokföring of the new year in parallel
|
||||
// with bokslut work on the prior year (6-month deadline for årsbokslut, 7
|
||||
// months for AB årsredovisning). A locked-but-not-yet-closed prior period is
|
||||
// the normal state during that window and must not block creation of the
|
||||
// next räkenskapsår. The .is('locked_at', null) filter excludes locked
|
||||
// periods from the openCount, so the mock returns 0 here.
|
||||
it('allows forward period creation when prior period is locked-but-not-closed', async () => {
|
||||
buildMockSupabase({
|
||||
allPeriods: [{ id: 'p1', period_start: '2024-01-01', period_end: '2024-12-31', is_closed: false }],
|
||||
openCount: 0,
|
||||
overlapping: [],
|
||||
})
|
||||
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)
|
||||
const body = await res.json()
|
||||
expect(body.data).toBeDefined()
|
||||
})
|
||||
|
||||
it('allows backward period creation', async () => {
|
||||
@@ -256,7 +277,13 @@ describe('POST /api/bookkeeping/fiscal-periods', () => {
|
||||
// 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 }) }) }
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
eq: vi.fn().mockReturnValue({
|
||||
is: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
if (callNum === 1) {
|
||||
return {
|
||||
@@ -313,7 +340,13 @@ describe('POST /api/bookkeeping/fiscal-periods', () => {
|
||||
// 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 }) }) }
|
||||
return {
|
||||
eq: vi.fn().mockReturnValue({
|
||||
eq: vi.fn().mockReturnValue({
|
||||
is: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
if (callNum === 1) {
|
||||
return {
|
||||
|
||||
@@ -100,16 +100,20 @@ export async function POST(request: Request) {
|
||||
)
|
||||
}
|
||||
|
||||
// Enforce: max one unclosed period (no skipping ahead) — forward only
|
||||
// Enforce: max one editable prior period (no skipping ahead) — forward only.
|
||||
// Locked periods are write-blocked by enforce_period_lock and so don't
|
||||
// represent skipping ahead; they're the normal state during bokslut work,
|
||||
// which BFL 6 kap allows in parallel with löpande bokföring of the new year.
|
||||
const { count: openCount } = await supabase
|
||||
.from('fiscal_periods')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('company_id', companyId)
|
||||
.eq('is_closed', false)
|
||||
.is('locked_at', null)
|
||||
|
||||
if (openCount && openCount > 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Cannot create a new period while an unclosed period exists' },
|
||||
{ error: 'Cannot create a new period while an unlocked period exists' },
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user