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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<script type="module" crossorigin src="/assets/index-CbMx-xOV.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B0Irvkd7.css">
<script type="module" crossorigin src="/assets/index-BtifZyOD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-oHhMI01Z.css">
</head>
<body>
<div id="root"></div>
+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>