feat: BOC ledger-integration frontend + backend fixes

- Uppdatera robust_client.go med rätt tabellnamn (boc_chart_of_accounts, boc_journal_lines, boc_journal_entries)
- Uppdatera kolumnnamn (entry_id istället för journal_entry_id, account_code istället för code)
- Hantera både stora och små bokstäver för account_type
- Lägg till ledgerApi i frontend med BalanceSheet, IncomeStatement, MomsReport
- Uppdatera DashboardPage med Ledger KPI-kort
- Lägg till LEDGER_DB_URL i docker-compose.yml
- Uppdatera Dockerfile till golang:1.25-alpine
This commit is contained in:
Bernt
2026-07-29 19:53:39 +00:00
parent f367eaadb8
commit bd28b60338
14 changed files with 671 additions and 488 deletions
+34
View File
@@ -104,6 +104,40 @@ export const financeApi = {
cashflow: () => api.get<{ cashflow: unknown[] }>('/finance/cashflow'),
}
// Ledger (aamos-ledger integration)
export interface LedgerAccount {
code: string
name: string
account_type: string
balance: number
}
export interface BalanceSheet {
assets: LedgerAccount[]
liabilities: LedgerAccount[]
equity: LedgerAccount[]
total_assets: number
total_liabilities: number
total_equity: number
period: string
}
export interface IncomeStatement {
revenues: LedgerAccount[]
expenses: LedgerAccount[]
total_revenue: number
total_expense: number
net_income: number
period: string
}
export const ledgerApi = {
accounts: () => api.get<{ accounts: LedgerAccount[] }>('/ledger/accounts'),
balanceSheet: (period?: string) => api.get<BalanceSheet>(`/ledger/balance-sheet?period=${period || ''}`),
incomeStatement: (period?: string) => api.get<IncomeStatement>(`/ledger/income-statement?period=${period || ''}`),
momsReport: (period?: string) => api.get<unknown>(`/ledger/moms?period=${period || ''}`),
}
// HR
export const hrApi = {
employees: () => api.get<{ employees: unknown[] }>('/hr/employees'),
+61 -2
View File
@@ -14,7 +14,7 @@ import {
} from '@/components/ui/Table'
import { Badge } from '@/components/ui/Badge'
import { formatCurrency, formatDate } from '@/lib/utils'
import { financeApi, salesApi, crmApi } from '@/lib/api'
import { financeApi, salesApi, crmApi, ledgerApi } from '@/lib/api'
import {
DollarSign,
Users,
@@ -60,17 +60,25 @@ export function DashboardPage() {
const [customerCount, setCustomerCount] = useState(0)
const [deals, setDeals] = useState<Deal[]>([])
const [dealTotal, setDealTotal] = useState(0)
// Ledger state
const [ledgerAssets, setLedgerAssets] = useState(0)
const [ledgerLiabilities, setLedgerLiabilities] = useState(0)
const [ledgerEquity, setLedgerEquity] = useState(0)
const [netIncome, setNetIncome] = useState(0)
useEffect(() => {
async function fetchData() {
setLoading(true)
setError('')
try {
const [balanceRes, mrrRes, customersRes, dealsRes] = await Promise.all([
const [balanceRes, mrrRes, customersRes, dealsRes, ledgerBs, ledgerIs] = await Promise.all([
financeApi.balance(),
salesApi.mrr(),
crmApi.customers(),
salesApi.deals(),
ledgerApi.balanceSheet('2026-01').catch(() => null),
ledgerApi.incomeStatement('2026-01').catch(() => null),
])
const b = balanceRes as { total_assets?: number }
@@ -84,6 +92,16 @@ export function DashboardPage() {
const d = dealsRes as { deals: Deal[]; total: number }
setDeals(d.deals || [])
setDealTotal(d.total || 0)
// Ledger data
if (ledgerBs) {
setLedgerAssets(ledgerBs.total_assets || 0)
setLedgerLiabilities(ledgerBs.total_liabilities || 0)
setLedgerEquity(ledgerBs.total_equity || 0)
}
if (ledgerIs) {
setNetIncome(ledgerIs.net_income || 0)
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load dashboard data')
} finally {
@@ -124,6 +142,37 @@ export function DashboardPage() {
icon: <ShoppingCart size={18} />,
},
]
const ledgerKpis = [
{
label: 'Ledger Assets',
value: formatCurrency(ledgerAssets),
change: 0,
changeLabel: 'per balansräkning',
icon: <DollarSign size={18} />,
},
{
label: 'Ledger Liabilities',
value: formatCurrency(ledgerLiabilities),
change: 0,
changeLabel: 'per balansräkning',
icon: <TrendingUp size={18} />,
},
{
label: 'Net Income',
value: formatCurrency(netIncome),
change: 0,
changeLabel: 'per resultaträkning',
icon: <TrendingUp size={18} />,
},
{
label: 'Equity',
value: formatCurrency(ledgerEquity),
change: 0,
changeLabel: 'per balansräkning',
icon: <DollarSign size={18} />,
},
]
if (loading) {
return (
@@ -158,6 +207,16 @@ export function DashboardPage() {
<KPICard key={kpi.label} {...kpi} index={i} />
))}
</div>
{/* Ledger KPI Cards */}
<div className="mt-8">
<h2 className="text-lg font-semibold mb-4">Ledger (Bokföring)</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-5">
{ledgerKpis.map((kpi, i) => (
<KPICard key={kpi.label} {...kpi} index={i} />
))}
</div>
</div>
{/* Infrastructure Health — Grafana Integration */}
<Card>