diff --git a/components/dashboard/DashboardContent.tsx b/components/dashboard/DashboardContent.tsx index 905f1c61..c2aa9112 100644 --- a/components/dashboard/DashboardContent.tsx +++ b/components/dashboard/DashboardContent.tsx @@ -11,11 +11,8 @@ import { TaxTodoWidget } from '@/components/deadlines/TaxTodoWidget' import NewUserChecklist from '@/components/onboarding/NewUserChecklist' import AttGoraSection from '@/components/dashboard/AttGoraSection' import { - Receipt, - ArrowLeftRight, ChevronRight, CheckCircle2, - Clock, ArrowRight, MessageCircle, } from 'lucide-react' @@ -114,67 +111,15 @@ export default function DashboardContent({ companyId, summary, worklist, suggest // header so the tile and the section never disagree. const todoCount = worklist.total + (summary.expiringBankConnections?.length ?? 0) - // Pick the single most-urgent next action so the launchpad surfaces one - // unambiguous CTA. Order matches the friction we actually want to remove - // first: stale → overdue → uncategorized → unpaid → all clear. - const nextBestAction = (() => { - if (summary.staleUncategorizedCount > 0) { - return { - href: '/transactions', - title: 'Gamla transaktioner väntar', - body: `${summary.staleUncategorizedCount} transaktion${summary.staleUncategorizedCount === 1 ? '' : 'er'} äldre än 14 dagar saknar bokföring.`, - cta: 'Bokför nu', - tone: 'destructive' as const, - icon: Clock, - } - } - if (summary.overdueInvoicesCount > 0) { - return { - href: '/invoices?status=unpaid', - title: 'Förfallna fakturor', - body: `${summary.overdueInvoicesCount} st · ${formatCurrency(summary.unpaidInvoicesTotal)}`, - cta: 'Gå till fakturor', - tone: 'destructive' as const, - icon: Receipt, - } - } - if (worklist.counts.book_transaction > 0) { - const n = worklist.counts.book_transaction - return { - href: '/transactions', - title: 'Transaktioner att bokföra', - body: `${n} obokförd${n === 1 ? '' : 'a'} transaktion${n === 1 ? '' : 'er'}.`, - cta: 'Bokför nu', - tone: 'primary' as const, - icon: ArrowLeftRight, - } - } - if (summary.unpaidInvoicesCount > 0) { - return { - href: '/invoices?status=unpaid', - title: 'Obetalda fakturor', - body: `${summary.unpaidInvoicesCount} st · ${formatCurrency(summary.unpaidInvoicesTotal)}`, - cta: 'Visa fakturor', - tone: 'primary' as const, - icon: Receipt, - } - } - return { - href: '/invoices/new', - title: 'Allt är ikapp', - body: 'Inga obokförda transaktioner och inga obetalda fakturor. Skicka nästa faktura?', - cta: 'Skapa faktura', - tone: 'neutral' as const, - icon: CheckCircle2, - } - })() - return (
- {!agentBuilt ? ( - /* Build-assistant hero — shown until the company has a verified - agent_profile. Takes the hero slot so existing/migrated users get a - clear prompt instead of a full-screen onboarding takeover. */ + {/* Build-assistant hero — shown only until the company has a verified + agent_profile, so existing/migrated users get a clear prompt instead + of a full-screen onboarding takeover. Once the assistant is built the + dashboard leads with the metrics + the unified "Att göra" worklist + below; we deliberately drop a next-best-action hero here so the page + has a single CTA surface instead of two that point at the same work. */} + {!agentBuilt && (
@@ -199,38 +144,6 @@ export default function DashboardContent({ companyId, summary, worklist, suggest
- ) : ( - /* Next best action — single hero card, shown for every agent-built - company regardless of nav density so there is always one clear CTA */ -
- - - -
- -
-
-

{nextBestAction.title}

-

{nextBestAction.body}

-
-
- {nextBestAction.cta} - -
-
-
- -
)} {/* Key metrics — 4 compact cards */} diff --git a/lib/bokslut/__tests__/asset-service.test.ts b/lib/bokslut/__tests__/asset-service.test.ts index cba4d210..1633a3bb 100644 --- a/lib/bokslut/__tests__/asset-service.test.ts +++ b/lib/bokslut/__tests__/asset-service.test.ts @@ -5,6 +5,7 @@ import { disposeAsset, updateAsset, } from '../assets/asset-service' +import { getBASReference } from '@/lib/bookkeeping/bas-reference' import type { Asset } from '@/types' vi.mock('@/lib/bookkeeping/engine', () => ({ @@ -23,9 +24,9 @@ describe('DEFAULT_ACCOUNTS_BY_CATEGORY', () => { land_improvement: { asset: '1150', accumulated: '1159', expense: '7824' }, machinery: { asset: '1210', accumulated: '1219', expense: '7831' }, equipment: { asset: '1220', accumulated: '1229', expense: '7832' }, - vehicle: { asset: '1240', accumulated: '1249', expense: '7834' }, - computer: { asset: '1250', accumulated: '1259', expense: '7833' }, - other_tangible: { asset: '1280', accumulated: '1289', expense: '7839' }, + vehicle: { asset: '1240', accumulated: '1249', expense: '7832' }, + computer: { asset: '1250', accumulated: '1259', expense: '7832' }, + other_tangible: { asset: '1290', accumulated: '1299', expense: '7839' }, } as const expect(DEFAULT_ACCOUNTS_BY_CATEGORY).toEqual(expected) }) @@ -48,6 +49,22 @@ describe('DEFAULT_ACCOUNTS_BY_CATEGORY', () => { expect(expense).toMatch(/^78\d{2}$/) } }) + + // Regression guard for #755: 7833/7834 were referenced here but absent from + // the BAS reference, so backfillStandardBASAccounts could not seed them and + // annual depreciation threw AccountsNotInChartError. Every account in the + // triple must resolve in BAS_REFERENCE — otherwise the lazy backfill silently + // can't add it and the depreciation posting fails on minimal charts. + it('every account in the triple exists in the BAS reference (backfillable)', () => { + for (const cat of Object.keys(DEFAULT_ACCOUNTS_BY_CATEGORY) as Array< + keyof typeof DEFAULT_ACCOUNTS_BY_CATEGORY + >) { + const { asset, accumulated, expense } = DEFAULT_ACCOUNTS_BY_CATEGORY[cat] + for (const account of [asset, accumulated, expense]) { + expect(getBASReference(account), `${cat}: ${account} missing from BAS reference`).toBeDefined() + } + } + }) }) describe('disposeAsset — gain/loss account selection', () => { diff --git a/lib/bokslut/assets/asset-service.ts b/lib/bokslut/assets/asset-service.ts index 0751dbb4..0ec95bf4 100644 --- a/lib/bokslut/assets/asset-service.ts +++ b/lib/bokslut/assets/asset-service.ts @@ -12,8 +12,16 @@ import type { /** * Default BAS account triples per category. The user can override at create - * time; these only kick in when the form doesn't specify accounts. Matches - * the seeded BAS 2020 chart (lib/bookkeeping/bas-data/). + * time; these only kick in when the form doesn't specify accounts. Every + * account here MUST exist in BAS_REFERENCE (lib/bookkeeping/bas-data/) so the + * engine's backfillStandardBASAccounts can seed it on a minimal chart — + * otherwise depreciation throws AccountsNotInChartError (#755). A guard test in + * asset-service.test.ts enforces that invariant. + * + * vehicle (1240) and computer (1250) both sit in the maskiner-och-inventarier + * asset range, so their depreciation maps to 7832 (Avskrivningar på + * inventarier, verktyg och installationer) — 7833/7834 are not in the standard + * BAS catalog (removed as non-standard in #463). */ export const DEFAULT_ACCOUNTS_BY_CATEGORY: Record< AssetCategory, @@ -24,9 +32,9 @@ export const DEFAULT_ACCOUNTS_BY_CATEGORY: Record< land_improvement: { asset: '1150', accumulated: '1159', expense: '7824' }, machinery: { asset: '1210', accumulated: '1219', expense: '7831' }, equipment: { asset: '1220', accumulated: '1229', expense: '7832' }, - vehicle: { asset: '1240', accumulated: '1249', expense: '7834' }, - computer: { asset: '1250', accumulated: '1259', expense: '7833' }, - other_tangible: { asset: '1280', accumulated: '1289', expense: '7839' }, + vehicle: { asset: '1240', accumulated: '1249', expense: '7832' }, + computer: { asset: '1250', accumulated: '1259', expense: '7832' }, + other_tangible: { asset: '1290', accumulated: '1299', expense: '7839' }, } export interface CreateAssetInput {