Fiscal period and multi bank (#228)
* feat: add fiscal period backward chaining and entry date validation Support creating fiscal periods before the earliest existing period (backward chaining) for backfill scenarios, alongside the existing forward chaining. The engine now validates that entry dates fall within the selected fiscal period, with a Swedish error message. The journal entry form auto-selects the matching period and shows a warning with a CreatePeriodDialog when no period covers the entry date. * feat: support multi-bank-account for imports and reconciliation Plumb a configurable settlement account through the entire bank import pipeline — mapping engine, transaction entries, ingest, and reconciliation — so secondary bank accounts (e.g. 1931, 1932) work correctly instead of hardcoding 1930. Adds a get_unlinked_bank_lines RPC that generalizes the existing get_unlinked_1930_lines with a fallback for backwards compatibility. The bank file import UI now shows a bank account selector when multiple 19xx accounts exist. Also adds default_vat_code/sru_code to account creation and fixes uploadDocument argument order in enable-banking sync.
This commit is contained in:
@@ -412,7 +412,7 @@ describe('manualLink', () => {
|
||||
const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1')
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toBe('Journal entry has no line on account 1930')
|
||||
expect(result.error).toBe('Verifikationen saknar rad på bankkonto (19xx)')
|
||||
})
|
||||
|
||||
it('succeeds when all validations pass', async () => {
|
||||
|
||||
@@ -206,13 +206,14 @@ export async function runReconciliation(
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Compare bank transaction totals vs GL 1930 balance.
|
||||
* Compare bank transaction totals vs GL bank account balance.
|
||||
*/
|
||||
export async function getReconciliationStatus(
|
||||
supabase: SupabaseClient,
|
||||
companyId: string,
|
||||
dateFrom?: string,
|
||||
dateTo?: string
|
||||
dateTo?: string,
|
||||
bankAccount = '1930'
|
||||
): Promise<ReconciliationStatus> {
|
||||
// Get all transactions in range
|
||||
let txQuery = supabase
|
||||
@@ -226,11 +227,11 @@ export async function getReconciliationStatus(
|
||||
|
||||
const { data: transactions } = await txQuery
|
||||
|
||||
// Get GL 1930 lines (all, not just unlinked)
|
||||
// Get GL bank account lines (all, not just unlinked)
|
||||
let glQuery = supabase
|
||||
.from('journal_entry_lines')
|
||||
.select('debit_amount, credit_amount, journal_entries!inner(company_id, entry_date, status)')
|
||||
.eq('account_number', '1930')
|
||||
.eq('account_number', bankAccount)
|
||||
.eq('journal_entries.company_id', companyId)
|
||||
.eq('journal_entries.status', 'posted')
|
||||
|
||||
@@ -280,7 +281,7 @@ export async function getReconciliationStatus(
|
||||
|
||||
/**
|
||||
* Manually link a transaction to an existing journal entry.
|
||||
* Validates that the journal entry has a 1930 line and amounts are directionally compatible.
|
||||
* Validates that the journal entry has a bank account line and amounts are directionally compatible.
|
||||
*/
|
||||
export async function manualLink(
|
||||
supabase: SupabaseClient,
|
||||
@@ -321,15 +322,16 @@ export async function manualLink(
|
||||
return { success: false, error: 'Journal entry is not posted' }
|
||||
}
|
||||
|
||||
// Check for 1930 line
|
||||
// Check for a bank account line (19xx class accounts)
|
||||
const { data: lines } = await supabase
|
||||
.from('journal_entry_lines')
|
||||
.select('debit_amount, credit_amount')
|
||||
.select('debit_amount, credit_amount, account_number')
|
||||
.eq('journal_entry_id', journalEntryId)
|
||||
.eq('account_number', '1930')
|
||||
.gte('account_number', '1900')
|
||||
.lte('account_number', '1999')
|
||||
|
||||
if (!lines || lines.length === 0) {
|
||||
return { success: false, error: 'Journal entry has no line on account 1930' }
|
||||
return { success: false, error: 'Verifikationen saknar rad på bankkonto (19xx)' }
|
||||
}
|
||||
|
||||
// Check that no other transaction is already linked to this entry
|
||||
@@ -434,19 +436,32 @@ export async function unlinkReconciliation(
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
/** Fetch unlinked 1930 GL lines via the RPC function */
|
||||
/** Fetch unlinked bank GL lines via the RPC function */
|
||||
export async function fetchUnlinkedGLLines(
|
||||
supabase: SupabaseClient,
|
||||
companyId: string,
|
||||
dateFrom?: string,
|
||||
dateTo?: string
|
||||
dateTo?: string,
|
||||
bankAccount = '1930'
|
||||
): Promise<UnlinkedGLLine[]> {
|
||||
const { data, error } = await supabase.rpc('get_unlinked_1930_lines', {
|
||||
const { data, error } = await supabase.rpc('get_unlinked_bank_lines', {
|
||||
p_company_id: companyId,
|
||||
p_date_from: dateFrom || null,
|
||||
p_date_to: dateTo || null,
|
||||
p_account_number: bankAccount,
|
||||
})
|
||||
|
||||
// Fall back to legacy RPC if the new one doesn't exist yet
|
||||
if (error && bankAccount === '1930') {
|
||||
const { data: fallbackData, error: fallbackError } = await supabase.rpc('get_unlinked_1930_lines', {
|
||||
p_company_id: companyId,
|
||||
p_date_from: dateFrom || null,
|
||||
p_date_to: dateTo || null,
|
||||
})
|
||||
if (fallbackError || !fallbackData) return []
|
||||
return fallbackData as UnlinkedGLLine[]
|
||||
}
|
||||
|
||||
if (error || !data) return []
|
||||
return data as UnlinkedGLLine[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user