Invoice fix and new inbound invoice extension etc
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { getCategoryAccountMapping, getExpenseAccountForCategory } from '../category-mapping'
|
||||
import {
|
||||
getCategoryAccountMapping,
|
||||
getExpenseAccountForCategory,
|
||||
getDefaultAccountForCategory,
|
||||
getDefaultVatTreatmentForCategory,
|
||||
} from '../category-mapping'
|
||||
|
||||
describe('getCategoryAccountMapping', () => {
|
||||
describe('income_products uses correct account', () => {
|
||||
@@ -43,3 +48,62 @@ describe('getExpenseAccountForCategory', () => {
|
||||
expect(getExpenseAccountForCategory('expense_bank_fees')).toBe('6570')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDefaultAccountForCategory', () => {
|
||||
it('returns expense account for expense categories', () => {
|
||||
expect(getDefaultAccountForCategory('expense_equipment')).toBe('5410')
|
||||
expect(getDefaultAccountForCategory('expense_software')).toBe('5420')
|
||||
expect(getDefaultAccountForCategory('expense_travel')).toBe('5800')
|
||||
expect(getDefaultAccountForCategory('expense_bank_fees')).toBe('6570')
|
||||
})
|
||||
|
||||
it('returns income account for income categories', () => {
|
||||
expect(getDefaultAccountForCategory('income_services')).toBe('3001')
|
||||
expect(getDefaultAccountForCategory('income_products')).toBe('3001')
|
||||
expect(getDefaultAccountForCategory('income_other')).toBe('3900')
|
||||
})
|
||||
|
||||
it('returns private account for enskild firma', () => {
|
||||
expect(getDefaultAccountForCategory('private', 'enskild_firma')).toBe('2013')
|
||||
})
|
||||
|
||||
it('returns private account for aktiebolag', () => {
|
||||
expect(getDefaultAccountForCategory('private', 'aktiebolag')).toBe('2893')
|
||||
})
|
||||
|
||||
it('returns entity-specific education account', () => {
|
||||
expect(getDefaultAccountForCategory('expense_education', 'enskild_firma')).toBe('6991')
|
||||
expect(getDefaultAccountForCategory('expense_education', 'aktiebolag')).toBe('7610')
|
||||
})
|
||||
|
||||
it('returns fallback for uncategorized', () => {
|
||||
expect(getDefaultAccountForCategory('uncategorized')).toBe('6991')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDefaultVatTreatmentForCategory', () => {
|
||||
it('returns standard_25 for regular expense categories', () => {
|
||||
expect(getDefaultVatTreatmentForCategory('expense_equipment')).toBe('standard_25')
|
||||
expect(getDefaultVatTreatmentForCategory('expense_software')).toBe('standard_25')
|
||||
expect(getDefaultVatTreatmentForCategory('expense_travel')).toBe('standard_25')
|
||||
})
|
||||
|
||||
it('returns standard_25 for income categories', () => {
|
||||
expect(getDefaultVatTreatmentForCategory('income_services')).toBe('standard_25')
|
||||
expect(getDefaultVatTreatmentForCategory('income_products')).toBe('standard_25')
|
||||
})
|
||||
|
||||
it('returns null for VAT-exempt categories', () => {
|
||||
expect(getDefaultVatTreatmentForCategory('expense_bank_fees')).toBeNull()
|
||||
expect(getDefaultVatTreatmentForCategory('expense_card_fees')).toBeNull()
|
||||
expect(getDefaultVatTreatmentForCategory('expense_currency_exchange')).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null for private transactions', () => {
|
||||
expect(getDefaultVatTreatmentForCategory('private')).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null for uncategorized', () => {
|
||||
expect(getDefaultVatTreatmentForCategory('uncategorized')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -257,3 +257,69 @@ export function getExpenseAccountForCategory(category: TransactionCategory): str
|
||||
}
|
||||
return mapping[category] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default account number for a category.
|
||||
* For expense categories: returns the expense account (debit side).
|
||||
* For income categories: returns the revenue account (credit side).
|
||||
* For private/uncategorized: returns the entity-specific private or fallback account.
|
||||
*/
|
||||
export function getDefaultAccountForCategory(
|
||||
category: TransactionCategory,
|
||||
entityType: EntityType = 'enskild_firma'
|
||||
): string {
|
||||
if (category === 'private') {
|
||||
return PRIVATE_ACCOUNTS[entityType] || PRIVATE_ACCOUNTS.enskild_firma
|
||||
}
|
||||
|
||||
const expenseMapping: Record<string, string> = {
|
||||
expense_equipment: '5410',
|
||||
expense_software: '5420',
|
||||
expense_travel: '5800',
|
||||
expense_office: '5010',
|
||||
expense_marketing: '5910',
|
||||
expense_professional_services: '6530',
|
||||
expense_education: entityType === 'aktiebolag' ? '7610' : '6991',
|
||||
expense_bank_fees: '6570',
|
||||
expense_card_fees: '6570',
|
||||
expense_currency_exchange: '7960',
|
||||
expense_other: '6991',
|
||||
}
|
||||
|
||||
if (category.startsWith('expense_')) {
|
||||
return expenseMapping[category] || '6991'
|
||||
}
|
||||
|
||||
const incomeMapping: Record<string, string> = {
|
||||
income_services: '3001',
|
||||
income_products: '3001',
|
||||
income_other: '3900',
|
||||
}
|
||||
|
||||
if (category.startsWith('income_')) {
|
||||
return incomeMapping[category] || '3900'
|
||||
}
|
||||
|
||||
// uncategorized
|
||||
return '6991'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default VAT treatment for a category.
|
||||
* Bank fees, card fees, and currency exchange are VAT-exempt.
|
||||
* All other business categories default to standard 25%.
|
||||
*/
|
||||
export function getDefaultVatTreatmentForCategory(
|
||||
category: TransactionCategory
|
||||
): VatTreatment | null {
|
||||
if (category === 'private' || category === 'uncategorized') {
|
||||
return null
|
||||
}
|
||||
|
||||
const vatExemptCategories = ['expense_bank_fees', 'expense_card_fees', 'expense_currency_exchange']
|
||||
if (vatExemptCategories.includes(category)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return 'standard_25'
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import type {
|
||||
CAMT054Notification,
|
||||
AuditSecurityEvent,
|
||||
ReconciliationMethod,
|
||||
InvoiceInboxItem,
|
||||
SupplierInvoice,
|
||||
} from '@/types'
|
||||
|
||||
// ============================================================
|
||||
@@ -62,6 +64,10 @@ export type CoreEvent =
|
||||
privateTotal: number;
|
||||
userId: string;
|
||||
}}
|
||||
// Supplier Invoice Inbox
|
||||
| { type: 'supplier_invoice.received'; payload: { inboxItem: InvoiceInboxItem; userId: string } }
|
||||
| { type: 'supplier_invoice.extracted'; payload: { inboxItem: InvoiceInboxItem; confidence: number; userId: string } }
|
||||
| { type: 'supplier_invoice.confirmed'; payload: { inboxItem: InvoiceInboxItem; supplierInvoice: SupplierInvoice; userId: string } }
|
||||
// Audit
|
||||
| { type: 'audit.security_event'; payload: { event: AuditSecurityEvent; userId: string } }
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('sectors registry', () => {
|
||||
expect(SECTORS.length).toBe(6)
|
||||
})
|
||||
|
||||
it('should have 17 total extensions', () => {
|
||||
expect(getAllExtensions().length).toBe(17)
|
||||
it('should have 18 total extensions', () => {
|
||||
expect(getAllExtensions().length).toBe(18)
|
||||
})
|
||||
|
||||
it('should have unique slugs within each sector', () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { pushNotificationsExtension } from '@/extensions/general/push-notificati
|
||||
import { sruExportExtension } from '@/extensions/sru-export'
|
||||
import { neBilagaExtension } from '@/extensions/ne-bilaga'
|
||||
import { aiChatExtension } from '@/extensions/general/ai-chat'
|
||||
import { invoiceInboxExtension } from '@/extensions/general/invoice-inbox'
|
||||
import type { Extension } from './types'
|
||||
|
||||
// ── Enable Banking (PSD2) — opt-in extension ───────────────────────────
|
||||
@@ -26,6 +27,7 @@ const FIRST_PARTY_EXTENSIONS: Extension[] = [
|
||||
sruExportExtension,
|
||||
neBilagaExtension,
|
||||
aiChatExtension,
|
||||
invoiceInboxExtension,
|
||||
// enableBankingExtension, // Uncomment to activate PSD2 bank sync
|
||||
]
|
||||
|
||||
|
||||
@@ -64,6 +64,18 @@ export const SECTORS: Sector[] = [
|
||||
longDescription:
|
||||
'Få push-notiser direkt i webbläsaren när viktiga händelser sker — nya fakturor, förfallna betalningar, slutförda bokföringar med mera.',
|
||||
},
|
||||
{
|
||||
slug: 'invoice-inbox',
|
||||
name: 'Leverantörsfaktura-inbox',
|
||||
sector: 'general',
|
||||
category: 'import',
|
||||
icon: 'Inbox',
|
||||
dataPattern: 'manual',
|
||||
hasOwnData: true,
|
||||
description: 'Ta emot leverantörsfakturor via e-post eller uppladdning',
|
||||
longDescription:
|
||||
'Skicka leverantörsfakturor till en dedikerad e-postadress eller ladda upp manuellt. AI extraherar automatiskt leverantörsdata, belopp och moms. Granska och bekräfta med ett klick för att skapa leverantörsfakturor.',
|
||||
},
|
||||
{
|
||||
slug: 'enable-banking',
|
||||
name: 'Bankintegration (PSD2)',
|
||||
|
||||
Reference in New Issue
Block a user