Fix/vat parent accounts (#438)
* feat(enable-banking): add support for account selection and syncing - Updated StoredAccount interface to include an 'enabled' flag for account syncing preferences. - Enhanced ensureFiscalPeriod function to handle overlapping fiscal periods with posted entries and opening balances. - Added tests for fiscal period validation and account syncing logic. - Implemented AccountPickerDialog component for user account selection. - Created API routes for PATCH /accounts and POST /sync to manage account syncing. - Introduced 'pending_selection' status for bank connections to allow user account selection before syncing. - Updated database migration to support new connection status and backfill existing accounts with enabled=true. * feat(enable-banking): implement account selection and consent event logging --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d0fbc2b616
commit
a53a119a2e
@@ -918,3 +918,135 @@ describe('SKV §4.1.1.4 cross-field contracts', () => {
|
||||
expect(r.ruta49).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// Parent/summary BAS accounts — 2610/2620/2630 (output),
|
||||
// 2618/2628/2638 (vilande), 2640 (input parent).
|
||||
//
|
||||
// Users who post directly to the group account (manual entries, SIE imports,
|
||||
// alternate templates) had their balances silently dropped before this fix
|
||||
// because only the leaf accounts were mapped.
|
||||
// ============================================================
|
||||
|
||||
describe('calculateVatDeclaration — parent/summary accounts', () => {
|
||||
it('maps 2610 (parent) to ruta10 when posted directly', async () => {
|
||||
results = [
|
||||
{
|
||||
data: [
|
||||
{ account_number: '1910', debit_amount: 12500, credit_amount: 0 },
|
||||
{ account_number: '3001', debit_amount: 0, credit_amount: 10000 },
|
||||
{ account_number: '2610', debit_amount: 0, credit_amount: 2500 },
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
{ data: [], error: null },
|
||||
]
|
||||
|
||||
const result = await calculateVatDeclaration(supabase, 'company-1', 'monthly', 2024, 1)
|
||||
|
||||
expect(result.rutor.ruta05).toBe(10000)
|
||||
expect(result.rutor.ruta10).toBe(2500)
|
||||
expect(result.rutor.ruta49).toBe(2500) // owed, not refund
|
||||
})
|
||||
|
||||
it('maps 2620 (parent) to ruta11 and 2630 (parent) to ruta12', async () => {
|
||||
results = [
|
||||
{
|
||||
data: [
|
||||
{ account_number: '2620', debit_amount: 0, credit_amount: 600 },
|
||||
{ account_number: '2630', debit_amount: 0, credit_amount: 180 },
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
{ data: [], error: null },
|
||||
]
|
||||
|
||||
const result = await calculateVatDeclaration(supabase, 'company-1', 'monthly', 2024, 1)
|
||||
|
||||
expect(result.rutor.ruta11).toBe(600)
|
||||
expect(result.rutor.ruta12).toBe(180)
|
||||
})
|
||||
|
||||
it('maps vilande output VAT (2618/2628/2638) to ruta10/11/12', async () => {
|
||||
// Vilande accounts hold output VAT for invoices that have been sent but not
|
||||
// yet paid, used by cash-method bookkeepers per BFNAR 2006:1.
|
||||
results = [
|
||||
{
|
||||
data: [
|
||||
{ account_number: '2618', debit_amount: 0, credit_amount: 500 },
|
||||
{ account_number: '2628', debit_amount: 0, credit_amount: 120 },
|
||||
{ account_number: '2638', debit_amount: 0, credit_amount: 60 },
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
{ data: [], error: null },
|
||||
]
|
||||
|
||||
const result = await calculateVatDeclaration(supabase, 'company-1', 'monthly', 2024, 1)
|
||||
|
||||
expect(result.rutor.ruta10).toBe(500)
|
||||
expect(result.rutor.ruta11).toBe(120)
|
||||
expect(result.rutor.ruta12).toBe(60)
|
||||
})
|
||||
|
||||
it('sums parent and sub-account balances on the same ruta', async () => {
|
||||
// If a ledger has activity on both the parent and the sub-accounts (mixed
|
||||
// bookkeeping practice, SIE imports, etc.), the ruta reflects the literal
|
||||
// ledger total — accounting truth wins.
|
||||
results = [
|
||||
{
|
||||
data: [
|
||||
{ account_number: '2610', debit_amount: 0, credit_amount: 1000 },
|
||||
{ account_number: '2611', debit_amount: 0, credit_amount: 500 },
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
{ data: [], error: null },
|
||||
]
|
||||
|
||||
const result = await calculateVatDeclaration(supabase, 'company-1', 'monthly', 2024, 1)
|
||||
|
||||
expect(result.rutor.ruta10).toBe(1500)
|
||||
})
|
||||
|
||||
it('maps 2640 (input VAT parent) to ruta48', async () => {
|
||||
results = [
|
||||
{
|
||||
data: [
|
||||
{ account_number: '2640', debit_amount: 200, credit_amount: 0 },
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
{ data: [], error: null },
|
||||
]
|
||||
|
||||
const result = await calculateVatDeclaration(supabase, 'company-1', 'monthly', 2024, 1)
|
||||
|
||||
expect(result.rutor.ruta48).toBe(200)
|
||||
expect(result.rutor.ruta49).toBe(-200) // refund
|
||||
})
|
||||
|
||||
it('reproduces the user-reported bug: 2610 balance now reaches ruta10', async () => {
|
||||
// Customer screenshot scenario (simplified): 3001 + 2610 booked with the
|
||||
// correct VAT amount on the parent account. Before the fix, ruta10 read 0
|
||||
// and ruta49 incorrectly showed a refund.
|
||||
results = [
|
||||
{
|
||||
data: [
|
||||
{ account_number: '3001', debit_amount: 0, credit_amount: 21600 },
|
||||
{ account_number: '2610', debit_amount: 0, credit_amount: 9768 },
|
||||
{ account_number: '2641', debit_amount: 7048.45, credit_amount: 0 },
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
{ data: [], error: null },
|
||||
]
|
||||
|
||||
const result = await calculateVatDeclaration(supabase, 'company-1', 'yearly', 2025, 1)
|
||||
|
||||
expect(result.rutor.ruta05).toBe(21600)
|
||||
expect(result.rutor.ruta10).toBe(9768)
|
||||
expect(result.rutor.ruta48).toBe(7048.45)
|
||||
expect(result.rutor.ruta49).toBe(2719.55) // 9768 − 7048.45, owed (was −7048.45 pre-fix)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,9 +28,12 @@ import type {
|
||||
* (`.claude/skills/swedish-vat/references/vat-compliance-reference.md` §7).
|
||||
*
|
||||
* Output VAT (261x/262x/263x) → ruta 10/11/12 per rate (credit balance)
|
||||
* Includes parent/summary accounts (2610/2620/2630) for users who post
|
||||
* directly to the group account, and vilande accounts (2618/2628/2638)
|
||||
* used by cash-method bookkeepers for invoices not yet paid.
|
||||
* Reverse charge output (2614/2624/2634) → ruta 30/31/32 (credit)
|
||||
* Import VAT (2615/2625/2635) → ruta 60/61/62 (credit)
|
||||
* Input VAT (2641-2649) → ruta 48 (debit)
|
||||
* Input VAT (2640-2649) → ruta 48 (debit), incl. parent 2640
|
||||
* Domestic taxable sales (3001-3003) → ruta 05 (credit)
|
||||
* Uttag (3401-3403) → ruta 06 (credit)
|
||||
* EU goods (3108) → ruta 35; EU services (3308) → ruta 39 (credit)
|
||||
@@ -46,25 +49,32 @@ import type {
|
||||
*/
|
||||
const ACCOUNT_RUTA: Record<string, { box: keyof VatDeclarationRutor; side: 'credit' | 'debit' }> = {
|
||||
// Output VAT 25% → ruta 10
|
||||
'2610': { box: 'ruta10', side: 'credit' }, // Utgående moms 25% (summary/parent)
|
||||
'2611': { box: 'ruta10', side: 'credit' }, // Försäljning inom Sverige
|
||||
'2612': { box: 'ruta10', side: 'credit' }, // Egna uttag
|
||||
'2613': { box: 'ruta10', side: 'credit' }, // Uthyrning (frivillig skattskyldighet)
|
||||
'2616': { box: 'ruta10', side: 'credit' }, // Vinstmarginalbeskattning
|
||||
'2618': { box: 'ruta10', side: 'credit' }, // Vilande utgående moms 25%
|
||||
// Output VAT 12% → ruta 11
|
||||
'2620': { box: 'ruta11', side: 'credit' }, // Utgående moms 12% (summary/parent)
|
||||
'2621': { box: 'ruta11', side: 'credit' },
|
||||
'2622': { box: 'ruta11', side: 'credit' }, // Egna uttag
|
||||
'2623': { box: 'ruta11', side: 'credit' }, // Uthyrning
|
||||
'2626': { box: 'ruta11', side: 'credit' }, // VMB
|
||||
'2628': { box: 'ruta11', side: 'credit' }, // Vilande utgående moms 12%
|
||||
// Output VAT 6% → ruta 12
|
||||
'2630': { box: 'ruta12', side: 'credit' }, // Utgående moms 6% (summary/parent)
|
||||
'2631': { box: 'ruta12', side: 'credit' },
|
||||
'2632': { box: 'ruta12', side: 'credit' }, // Egna uttag
|
||||
'2633': { box: 'ruta12', side: 'credit' }, // Uthyrning
|
||||
'2636': { box: 'ruta12', side: 'credit' }, // VMB
|
||||
'2638': { box: 'ruta12', side: 'credit' }, // Vilande utgående moms 6%
|
||||
// Reverse charge output VAT → ruta 30/31/32
|
||||
'2614': { box: 'ruta30', side: 'credit' },
|
||||
'2624': { box: 'ruta31', side: 'credit' },
|
||||
'2634': { box: 'ruta32', side: 'credit' },
|
||||
// Input VAT → ruta 48
|
||||
'2640': { box: 'ruta48', side: 'debit' }, // Ingående moms (summary/parent)
|
||||
'2641': { box: 'ruta48', side: 'debit' }, // Debiterad ingående moms
|
||||
'2642': { box: 'ruta48', side: 'debit' }, // Frivillig skattskyldighet
|
||||
'2645': { box: 'ruta48', side: 'debit' }, // Förvärv utlandet (EU/non-EU RC)
|
||||
|
||||
Reference in New Issue
Block a user