feat: unified document inbox, full BAS 2026, and document-transaction matching
- Expand BAS reference from ~180 to ~1,276 accounts (full BAS Kontoplan 2026) with K2 exclusion flags, per-class data files, and computed SRU codes - Evolve invoice inbox into unified document inbox handling invoices, receipts, and government letters with AI-powered classification (Claude Haiku Vision) - Add multi-pass document-to-transaction matching engine with greedy assignment for both supplier invoices (reference/amount/date/name) and receipts (weighted amount/merchant/date scoring) - Add supplier invoice matching in transaction ingest pipeline - Inject booking template suggestions into AI extraction prompts - Surface matched documents in swipe categorization UI with one-tap booking - Auto-activate missing BAS accounts during SIE import against full reference - Add K2 filter toggle in Chart of Accounts manager - Add receipt confirmation route with BFNAR representation fields - Add database migrations for K2 support and document matching columns - Remove obsolete extension migration scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6956a757f3
commit
39e407644d
@@ -56,6 +56,7 @@ const TYPE_LABELS: Record<string, string> = {
|
||||
equity: 'EK',
|
||||
revenue: 'Intakt',
|
||||
expense: 'Kostnad',
|
||||
untaxed_reserves: 'Ob. reserver',
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -69,6 +70,7 @@ export default function ChartOfAccountsManager() {
|
||||
const [view, setView] = useState<'my-accounts' | 'bas-catalog'>('my-accounts')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [expandedClasses, setExpandedClasses] = useState<Set<number>>(new Set())
|
||||
const [hideK2Excluded, setHideK2Excluded] = useState<boolean | null>(null)
|
||||
|
||||
// Data state
|
||||
const [accounts, setAccounts] = useState<BASAccount[]>([])
|
||||
@@ -104,10 +106,25 @@ export default function ChartOfAccountsManager() {
|
||||
async function load() {
|
||||
setLoading(true)
|
||||
await Promise.all([fetchAccounts(), fetchReference()])
|
||||
// Set K2 filter default based on company settings (plan_type)
|
||||
if (hideK2Excluded === null) {
|
||||
try {
|
||||
const res = await fetch('/api/settings')
|
||||
if (res.ok) {
|
||||
const { data } = await res.json()
|
||||
// Default to hiding K2-excluded accounts if the company uses K2 (plan_type === 'k1')
|
||||
setHideK2Excluded(data?.plan_type === 'k1')
|
||||
} else {
|
||||
setHideK2Excluded(false)
|
||||
}
|
||||
} catch {
|
||||
setHideK2Excluded(false)
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
load()
|
||||
}, [fetchAccounts, fetchReference])
|
||||
}, [fetchAccounts, fetchReference, hideK2Excluded])
|
||||
|
||||
const refreshAll = useCallback(async () => {
|
||||
await Promise.all([fetchAccounts(), fetchReference()])
|
||||
@@ -221,12 +238,18 @@ export default function ChartOfAccountsManager() {
|
||||
}, [filteredAccounts])
|
||||
|
||||
const filteredReference = useMemo(() => {
|
||||
if (!searchQuery) return referenceAccounts
|
||||
const q = searchQuery.toLowerCase()
|
||||
return referenceAccounts.filter(
|
||||
(a) => a.account_number.includes(q) || a.account_name.toLowerCase().includes(q)
|
||||
)
|
||||
}, [referenceAccounts, searchQuery])
|
||||
let filtered = referenceAccounts
|
||||
if (hideK2Excluded) {
|
||||
filtered = filtered.filter((a) => !a.k2_excluded)
|
||||
}
|
||||
if (searchQuery) {
|
||||
const q = searchQuery.toLowerCase()
|
||||
filtered = filtered.filter(
|
||||
(a) => a.account_number.includes(q) || a.account_name.toLowerCase().includes(q)
|
||||
)
|
||||
}
|
||||
return filtered
|
||||
}, [referenceAccounts, searchQuery, hideK2Excluded])
|
||||
|
||||
const groupedReference = useMemo(() => {
|
||||
const grouped: Record<number, ReferenceAccount[]> = {}
|
||||
@@ -284,6 +307,17 @@ export default function ChartOfAccountsManager() {
|
||||
Eget konto
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{view === 'bas-catalog' && (
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<Switch
|
||||
checked={hideK2Excluded ?? false}
|
||||
onCheckedChange={setHideK2Excluded}
|
||||
className="scale-75"
|
||||
/>
|
||||
<span className="text-muted-foreground">Dolj K2-undantagna</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
|
||||
Reference in New Issue
Block a user