refactor: simplify KPI to 4 metrics and update CLAUDE.md (#83)
KPI page: removed operational grid (Intäktstillväxt, Kostnadsandel, Snittbetaltid — all usually empty or redundant). Now shows 4 cards (Resultat, Kassa, Kundfordringar, Moms) + trend chart. Removed unused fields from KPIReport type and simplified API route. CLAUDE.md: documented MCP server extension, API key infrastructure, OAuth 2.1 flow, gnubok-mcp npm package, KPI page, cookieless Supabase client, and updated migration count (65 → 70). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,7 @@ import { generateIncomeStatement } from '@/lib/reports/income-statement'
|
||||
import { generateTrialBalance } from '@/lib/reports/trial-balance'
|
||||
import { generateARLedger } from '@/lib/reports/ar-ledger'
|
||||
import { generateMonthlyBreakdown } from '@/lib/reports/monthly-breakdown'
|
||||
import {
|
||||
calculateGrossMargin,
|
||||
calculateCashPosition,
|
||||
calculateRevenueGrowth,
|
||||
calculateExpenseRatio,
|
||||
calculateAvgPaymentDays,
|
||||
} from '@/lib/reports/kpi'
|
||||
import { calculateCashPosition } from '@/lib/reports/kpi'
|
||||
import type { KPIReport } from '@/types'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
@@ -24,7 +18,6 @@ export async function GET(request: Request) {
|
||||
return NextResponse.json({ error: 'period_id is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Fetch fiscal period info
|
||||
const { data: period, error: periodError } = await supabase
|
||||
.from('fiscal_periods')
|
||||
.select('*')
|
||||
@@ -36,31 +29,15 @@ export async function GET(request: Request) {
|
||||
return NextResponse.json({ error: 'Fiscal period not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Run independent queries in parallel
|
||||
const [
|
||||
incomeStatement,
|
||||
trialBalanceResult,
|
||||
arLedger,
|
||||
monthlyBreakdown,
|
||||
paidInvoicesResult,
|
||||
] = await Promise.all([
|
||||
generateIncomeStatement(supabase, user.id, periodId),
|
||||
generateTrialBalance(supabase, user.id, periodId),
|
||||
generateARLedger(supabase, user.id),
|
||||
generateMonthlyBreakdown(supabase, user.id, periodId),
|
||||
supabase
|
||||
.from('invoices')
|
||||
.select('invoice_date, paid_at')
|
||||
.eq('user_id', user.id)
|
||||
.eq('status', 'paid')
|
||||
.not('paid_at', 'is', null)
|
||||
.gte('invoice_date', period.period_start)
|
||||
.lte('invoice_date', period.period_end),
|
||||
])
|
||||
const [incomeStatement, trialBalanceResult, arLedger, monthlyBreakdown] =
|
||||
await Promise.all([
|
||||
generateIncomeStatement(supabase, user.id, periodId),
|
||||
generateTrialBalance(supabase, user.id, periodId),
|
||||
generateARLedger(supabase, user.id),
|
||||
generateMonthlyBreakdown(supabase, user.id, periodId),
|
||||
])
|
||||
|
||||
const paidInvoices = (paidInvoicesResult.data || []) as { invoice_date: string; paid_at: string }[]
|
||||
|
||||
// Calculate VAT liability from trial balance (output VAT - input VAT)
|
||||
// VAT liability from trial balance (output VAT - input VAT)
|
||||
const vatOutputAccounts = ['2611', '2621', '2631']
|
||||
const vatInputAccounts = ['2641', '2645']
|
||||
const outputVat = trialBalanceResult.rows
|
||||
@@ -69,33 +46,13 @@ export async function GET(request: Request) {
|
||||
const inputVat = trialBalanceResult.rows
|
||||
.filter((r) => vatInputAccounts.includes(r.account_number))
|
||||
.reduce((sum, r) => sum + (r.closing_debit - r.closing_credit), 0)
|
||||
const vatLiability = Math.round((outputVat - inputVat) * 100) / 100
|
||||
|
||||
// Revenue growth: only for closed periods, compare with previous period
|
||||
let revenueGrowth: number | null = null
|
||||
if (period.is_closed && period.previous_period_id) {
|
||||
const prevStatement = await generateIncomeStatement(
|
||||
supabase,
|
||||
user.id,
|
||||
period.previous_period_id
|
||||
)
|
||||
revenueGrowth = calculateRevenueGrowth(
|
||||
incomeStatement.total_revenue,
|
||||
prevStatement.total_revenue
|
||||
)
|
||||
}
|
||||
|
||||
const report: KPIReport = {
|
||||
grossMargin: calculateGrossMargin(incomeStatement),
|
||||
netResult: incomeStatement.net_result,
|
||||
cashPosition: calculateCashPosition(trialBalanceResult.rows),
|
||||
outstandingReceivables: arLedger.total_outstanding,
|
||||
overdueReceivables: arLedger.total_overdue,
|
||||
revenueGrowth,
|
||||
expenseRatio: calculateExpenseRatio(incomeStatement),
|
||||
avgPaymentDays: calculateAvgPaymentDays(paidInvoices),
|
||||
paidInvoiceCount: paidInvoices.length,
|
||||
vatLiability,
|
||||
vatLiability: Math.round((outputVat - inputVat) * 100) / 100,
|
||||
totalRevenue: incomeStatement.total_revenue,
|
||||
totalExpenses: incomeStatement.total_expenses,
|
||||
periodComplete: period.is_closed,
|
||||
|
||||
Reference in New Issue
Block a user