Fix/footer UI (#296)

* feat: enhance journal entry handling with follow-up entries and related RPC

* fix: improve validation for journal entry lines to ensure proper submission criteria

* feat: add commit_method and rubric_version columns to journal_entries for enhanced tracking

* fix: ensure conditional addition of commit_method and rubric_version columns in journal_entries

* Update supabase/migrations/20260421120000_journal_entries_with_related_rpc.sql

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Mattsson
2026-04-21 12:55:43 +02:00
committed by GitHub
co-authored by greptile-apps[bot]
parent dd920355b3
commit 64cd6a0989
10 changed files with 289 additions and 11 deletions
+4 -4
View File
@@ -418,7 +418,7 @@ export default function NewInvoicePage() {
</div>
)}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6 pb-28 md:pb-0">
<div className="grid gap-6 lg:grid-cols-3">
{/* Main content */}
<div className="lg:col-span-2 space-y-6">
@@ -771,10 +771,10 @@ export default function NewInvoicePage() {
</CardContent>
</Card>
{/* Actions — desktop only */}
{/* Actions — desktop/tablet only */}
<Button
type="submit"
className="w-full hidden lg:block"
className="w-full hidden md:block"
size="lg"
disabled={isSubmitting || !canWrite}
title={!canWrite ? 'Du har endast läsbehörighet i detta företag' : undefined}
@@ -786,7 +786,7 @@ export default function NewInvoicePage() {
</div>
{/* Mobile sticky total bar */}
<div className="lg:hidden fixed left-0 right-0 z-40 bg-card/98 backdrop-blur-sm border-t border-border/40 px-5 py-3" style={{ bottom: 'calc(4rem + env(safe-area-inset-bottom, 0px))' }}>
<div className="md:hidden fixed left-0 right-0 z-40 bg-card/98 backdrop-blur-sm border-t border-border/40 px-5 py-3" style={{ bottom: 'calc(4rem + env(safe-area-inset-bottom, 0px))' }}>
<div className="max-w-5xl mx-auto flex items-center justify-between gap-4">
<div>
<p className="text-xs text-muted-foreground">Totalt</p>
@@ -76,6 +76,8 @@ describe('GET /api/bookkeeping/journal-entries', () => {
date_to: '2024-12-31',
limit: '10',
offset: '5',
// Strict period filtering — exercises the PostgREST path, not the RPC.
include_related: 'false',
},
})
const response = await GET(request)
@@ -85,6 +87,42 @@ describe('GET /api/bookkeeping/journal-entries', () => {
expect(mockSupabase.from).toHaveBeenCalledWith('journal_entries')
})
it('uses RPC with include_related when period_id is set', async () => {
const rpcRows = [
{
entry: { ...makeJournalEntry({ id: 'je-1' }), out_of_period: false },
total_count: 2,
},
{
entry: { ...makeJournalEntry({ id: 'je-2' }), out_of_period: true },
total_count: 2,
},
]
enqueue({ data: rpcRows, error: null })
const request = createMockRequest('/api/bookkeeping/journal-entries', {
searchParams: { period_id: 'period-1' },
})
const response = await GET(request)
const { status, body } = await parseJsonResponse<{
data: Array<{ id: string; out_of_period?: boolean }>
count: number
}>(response)
expect(status).toBe(200)
expect(mockSupabase.rpc).toHaveBeenCalledWith(
'list_fiscal_period_entries_with_related',
expect.objectContaining({
p_company_id: 'company-1',
p_period_id: 'period-1',
p_include_related: true,
})
)
expect(body.data).toHaveLength(2)
expect(body.data[1].out_of_period).toBe(true)
expect(body.count).toBe(2)
})
it('returns 500 on database error', async () => {
enqueue({ data: null, error: { message: 'DB error' } })
@@ -27,8 +27,38 @@ export async function GET(request: Request) {
const dateFrom = searchParams.get('date_from')
const dateTo = searchParams.get('date_to')
const sortDate = searchParams.get('sort_date') // 'asc' | 'desc'
// Default on: when a fiscal period is selected, include follow-up entries
// booked in later periods whose source aggregate (invoice, supplier invoice)
// is dated inside the selected period. Pass include_related=false to
// restore strict fiscal_period_id filtering.
const includeRelated = searchParams.get('include_related') !== 'false'
const dateAscending = sortDate === 'asc'
const sortDateParam = sortDate === 'asc' || sortDate === 'desc' ? sortDate : 'desc'
if (periodId && includeRelated) {
const { data, error } = await supabase.rpc('list_fiscal_period_entries_with_related', {
p_company_id: companyId,
p_period_id: periodId,
p_include_related: true,
p_status: status,
p_date_from: dateFrom,
p_date_to: dateTo,
p_sort_date: sortDateParam,
p_limit: limit,
p_offset: offset,
})
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
const rows = data ?? []
const entries = rows.map((r: { entry: unknown }) => r.entry)
const count = rows.length > 0 ? Number((rows[0] as { total_count: number | string }).total_count) : 0
return NextResponse.json({ data: entries, count })
}
let query = supabase
.from('journal_entries')