feat: BAS data overhaul, currency revaluation, expenses, UI polish, and cleanup
- Update BAS account catalog with comprehensive SRU codes and K2 flags - Add currency revaluation service with tests and API route - Add expenses page and account deletion API - Enhance booking templates with new patterns and improved tests - Improve transaction categorization with template picker and description matching - Polish dashboard, onboarding, import, and transaction UIs - Refactor year-end service for multi-step closing - Move SRU generator to ne-bilaga, remove standalone SRU export - Remove unused dev docs, mock data, and extension hooks - Add invoice delivery note sequences migration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a9bf24ce0b
commit
66a4027f1e
@@ -2,7 +2,6 @@
|
||||
|
||||
export const ENABLED_EXTENSION_IDS: ReadonlySet<string> = new Set([
|
||||
'enable-banking',
|
||||
'ai-categorization',
|
||||
'ai-chat',
|
||||
'email',
|
||||
])
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// AUTO-GENERATED — do not edit. Run `npm run setup:extensions` to regenerate.
|
||||
import type { Extension } from '../types'
|
||||
import { enableBankingExtension } from '@/extensions/general/enable-banking'
|
||||
import { aiCategorizationExtension } from '@/extensions/general/ai-categorization'
|
||||
import { aiChatExtension } from '@/extensions/general/ai-chat'
|
||||
import { emailExtension } from '@/extensions/general/email'
|
||||
|
||||
export const FIRST_PARTY_EXTENSIONS: Extension[] = [
|
||||
enableBankingExtension,
|
||||
aiCategorizationExtension,
|
||||
aiChatExtension,
|
||||
emailExtension,
|
||||
]
|
||||
|
||||
@@ -15,19 +15,6 @@ export const EXTENSION_DEFINITIONS: Record<string, ExtensionDefinition[]> = {
|
||||
"hasOwnData": true,
|
||||
"subscriptionNotice": "Denna integration kräver ett aktivt Enable Banking-abonnemang. Utan abonnemang kommer bankintegration inte att fungera."
|
||||
},
|
||||
{
|
||||
"slug": "ai-categorization",
|
||||
"name": "AI-kategorisering",
|
||||
"sector": "general",
|
||||
"category": "operations",
|
||||
"icon": "Sparkles",
|
||||
"dataPattern": "core",
|
||||
"description": "AI-drivna kategoriförslag för transaktioner",
|
||||
"longDescription": "Använder AI för att automatiskt föreslå BAS-kontokategorier för dina banktransaktioner. Lär sig från dina tidigare bokföringsval.",
|
||||
"readsCoreTables": [
|
||||
"transactions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "ai-chat",
|
||||
"name": "AI-assistent",
|
||||
|
||||
@@ -5,6 +5,5 @@ import type { WorkspaceComponentProps } from '../workspace-registry'
|
||||
|
||||
export const WORKSPACES: Record<string, ComponentType<WorkspaceComponentProps>> = {
|
||||
'general/enable-banking': dynamic(() => import('@/components/extensions/general/EnableBankingWorkspace')),
|
||||
'general/ai-categorization': dynamic(() => import('@/components/extensions/general/AiCategorizationWorkspace')),
|
||||
'general/ai-chat': dynamic(() => import('@/components/extensions/general/AiChatWorkspace')),
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
|
||||
interface AccountTotal {
|
||||
account_number: string
|
||||
debit: number
|
||||
credit: number
|
||||
net: number
|
||||
}
|
||||
|
||||
interface MonthlyTotal {
|
||||
month: string
|
||||
account_number: string
|
||||
debit: number
|
||||
credit: number
|
||||
net: number
|
||||
}
|
||||
|
||||
interface UseAccountTotalsOptions {
|
||||
from: string
|
||||
to: string
|
||||
dateFrom?: string
|
||||
dateTo?: string
|
||||
groupBy?: 'month'
|
||||
}
|
||||
|
||||
export function useAccountTotals(options: UseAccountTotalsOptions) {
|
||||
const [totals, setTotals] = useState<AccountTotal[]>([])
|
||||
const [monthly, setMonthly] = useState<MonthlyTotal[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const mountedRef = useRef(true)
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true
|
||||
return () => { mountedRef.current = false }
|
||||
}, [])
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
from: options.from,
|
||||
to: options.to,
|
||||
})
|
||||
if (options.dateFrom) params.set('date_from', options.dateFrom)
|
||||
if (options.dateTo) params.set('date_to', options.dateTo)
|
||||
if (options.groupBy) params.set('group_by', options.groupBy)
|
||||
|
||||
const res = await fetch(`/api/bookkeeping/account-totals?${params}`)
|
||||
if (res.ok) {
|
||||
const json = await res.json()
|
||||
if (mountedRef.current) {
|
||||
setTotals(json.totals ?? [])
|
||||
setMonthly(json.monthly ?? [])
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) setIsLoading(false)
|
||||
}
|
||||
}, [options.from, options.to, options.dateFrom, options.dateTo, options.groupBy])
|
||||
|
||||
useEffect(() => {
|
||||
refresh()
|
||||
}, [refresh])
|
||||
|
||||
const totalDebit = totals.reduce((sum, t) => sum + t.debit, 0)
|
||||
const totalCredit = totals.reduce((sum, t) => sum + t.credit, 0)
|
||||
const totalNet = totals.reduce((sum, t) => sum + t.net, 0)
|
||||
|
||||
return {
|
||||
totals,
|
||||
monthly,
|
||||
isLoading,
|
||||
totalDebit: Math.round(totalDebit * 100) / 100,
|
||||
totalCredit: Math.round(totalCredit * 100) / 100,
|
||||
totalNet: Math.round(totalNet * 100) / 100,
|
||||
refresh,
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useExtensionData } from './use-extension-data'
|
||||
|
||||
interface MockMeta {
|
||||
importedAt: string
|
||||
source: 'csv' | 'json'
|
||||
fileName: string
|
||||
rowCount: number
|
||||
}
|
||||
|
||||
interface UseMockDataResult<T> {
|
||||
mockReport: T | null
|
||||
isMockActive: boolean
|
||||
isLoading: boolean
|
||||
importedAt: string | null
|
||||
meta: MockMeta | null
|
||||
saveMockData: (report: T, meta: Omit<MockMeta, 'importedAt'>) => Promise<void>
|
||||
clearMockData: () => Promise<void>
|
||||
}
|
||||
|
||||
export function useMockData<T>(sector: string, slug: string): UseMockDataResult<T> {
|
||||
const { getByKey, save, remove, isLoading } = useExtensionData(sector, slug)
|
||||
|
||||
const [mockReport, setMockReport] = useState<T | null>(null)
|
||||
const [isMockActive, setIsMockActive] = useState(false)
|
||||
const [meta, setMeta] = useState<MockMeta | null>(null)
|
||||
|
||||
// Read mock state from extension data on load
|
||||
useEffect(() => {
|
||||
if (isLoading) return
|
||||
|
||||
const enabledRecord = getByKey('mock:enabled')
|
||||
const reportRecord = getByKey('mock:report')
|
||||
const metaRecord = getByKey('mock:meta')
|
||||
|
||||
if (enabledRecord && (enabledRecord.value as { enabled?: boolean }).enabled && reportRecord) {
|
||||
setIsMockActive(true)
|
||||
setMockReport(reportRecord.value as T)
|
||||
if (metaRecord) {
|
||||
setMeta(metaRecord.value as unknown as MockMeta)
|
||||
}
|
||||
} else {
|
||||
setIsMockActive(false)
|
||||
setMockReport(null)
|
||||
setMeta(null)
|
||||
}
|
||||
}, [isLoading, getByKey])
|
||||
|
||||
const saveMockData = useCallback(async (report: T, metaInput: Omit<MockMeta, 'importedAt'>) => {
|
||||
const fullMeta: MockMeta = {
|
||||
...metaInput,
|
||||
importedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
await save('mock:enabled', { enabled: true })
|
||||
await save('mock:report', report as unknown as Record<string, unknown>)
|
||||
await save('mock:meta', fullMeta as unknown as Record<string, unknown>)
|
||||
|
||||
setIsMockActive(true)
|
||||
setMockReport(report)
|
||||
setMeta(fullMeta)
|
||||
}, [save])
|
||||
|
||||
const clearMockData = useCallback(async () => {
|
||||
await remove('mock:enabled')
|
||||
await remove('mock:report')
|
||||
await remove('mock:meta')
|
||||
|
||||
setIsMockActive(false)
|
||||
setMockReport(null)
|
||||
setMeta(null)
|
||||
}, [remove])
|
||||
|
||||
return {
|
||||
mockReport,
|
||||
isMockActive,
|
||||
isLoading,
|
||||
importedAt: meta?.importedAt ?? null,
|
||||
meta,
|
||||
saveMockData,
|
||||
clearMockData,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user