194 lines
5.4 KiB
TypeScript
194 lines
5.4 KiB
TypeScript
import { createClient } from '@/lib/supabase/server'
|
|
import { calculateLightTax } from '@/lib/tax/light-calculator'
|
|
import type { Gift, GiftClassification } from '@/types'
|
|
|
|
export interface LightDashboardData {
|
|
firstName: string | null
|
|
bankBalance: number | null
|
|
giftTaxDebt: number
|
|
taxableGiftCount: number
|
|
effectiveRate: number
|
|
daysSinceLastPayout: number | null
|
|
hobbyReserve: number
|
|
recentEntries: Array<{
|
|
id: string
|
|
date: string
|
|
description: string | null
|
|
gross_amount: number
|
|
net_amount: number
|
|
service_fee: number
|
|
pension_deduction: number
|
|
social_fees: number
|
|
income_tax_withheld: number
|
|
platform_fee: number
|
|
type: string
|
|
provider: string | null
|
|
}>
|
|
}
|
|
|
|
export async function fetchLightDashboardData(
|
|
userId: string
|
|
): Promise<LightDashboardData> {
|
|
const supabase = await createClient()
|
|
|
|
const currentYear = new Date().getFullYear()
|
|
const startOfYear = `${currentYear}-01-01`
|
|
const endOfYear = `${currentYear}-12-31`
|
|
|
|
// Run all queries in parallel
|
|
const [
|
|
profileResult,
|
|
settingsResult,
|
|
bankResult,
|
|
giftsResult,
|
|
entriesResult,
|
|
lastPayoutResult,
|
|
] = await Promise.all([
|
|
// 1. Fetch profile for name
|
|
supabase
|
|
.from('profiles')
|
|
.select('full_name')
|
|
.eq('id', userId)
|
|
.single(),
|
|
|
|
// 2. Fetch company settings for tax rates and umbrella config
|
|
supabase
|
|
.from('company_settings')
|
|
.select(
|
|
'municipal_tax_rate, church_tax, church_tax_rate, umbrella_provider, umbrella_fee_percent, umbrella_pension_percent'
|
|
)
|
|
.eq('user_id', userId)
|
|
.single(),
|
|
|
|
// 3. Fetch bank balance
|
|
supabase
|
|
.from('bank_connections')
|
|
.select('accounts, status')
|
|
.eq('user_id', userId)
|
|
.eq('status', 'active')
|
|
.limit(1),
|
|
|
|
// 4. Fetch gifts for current year (not returned)
|
|
supabase
|
|
.from('gifts')
|
|
.select('estimated_value, classification, returned')
|
|
.eq('user_id', userId)
|
|
.eq('returned', false)
|
|
.gte('date', startOfYear)
|
|
.lte('date', endOfYear),
|
|
|
|
// 5. Fetch shadow ledger entries for current year (recent first, for the card)
|
|
supabase
|
|
.from('shadow_ledger_entries')
|
|
.select(
|
|
'id, date, description, gross_amount, net_amount, service_fee, pension_deduction, social_fees, income_tax_withheld, platform_fee, type, provider'
|
|
)
|
|
.eq('user_id', userId)
|
|
.gte('date', startOfYear)
|
|
.lte('date', endOfYear)
|
|
.order('date', { ascending: false })
|
|
.limit(5),
|
|
|
|
// 6. Fetch last payout date for SGI shield
|
|
supabase
|
|
.from('shadow_ledger_entries')
|
|
.select('date')
|
|
.eq('user_id', userId)
|
|
.eq('type', 'payout')
|
|
.order('date', { ascending: false })
|
|
.limit(1),
|
|
])
|
|
|
|
// Parse profile
|
|
const firstName = profileResult.data?.full_name?.split(' ')[0] || null
|
|
|
|
// Parse settings
|
|
const settings = settingsResult.data
|
|
const municipalTaxRate = Number(settings?.municipal_tax_rate) || 0.3238
|
|
const churchTaxRate = settings?.church_tax
|
|
? Number(settings?.church_tax_rate) || 0.01
|
|
: 0
|
|
const effectiveRate = municipalTaxRate + churchTaxRate
|
|
|
|
// Parse bank balance
|
|
let bankBalance: number | null = null
|
|
const bankConnections = bankResult.data
|
|
if (bankConnections && bankConnections.length > 0) {
|
|
const accounts = bankConnections[0].accounts as
|
|
| { balance: number }[]
|
|
| null
|
|
if (accounts && accounts.length > 0) {
|
|
bankBalance = accounts.reduce(
|
|
(sum, acc) => sum + (acc.balance || 0),
|
|
0
|
|
)
|
|
}
|
|
}
|
|
|
|
// Parse gifts and compute taxable value
|
|
const gifts = giftsResult.data || []
|
|
let taxableGiftValue = 0
|
|
let taxableGiftCount = 0
|
|
|
|
for (const gift of gifts as Pick<
|
|
Gift,
|
|
'estimated_value' | 'classification' | 'returned'
|
|
>[]) {
|
|
const classification = gift.classification as GiftClassification | null
|
|
if (classification?.taxable) {
|
|
taxableGiftCount++
|
|
taxableGiftValue += Number(gift.estimated_value)
|
|
}
|
|
}
|
|
|
|
// Calculate light tax to get gift tax debt and hobby reserve
|
|
const lightTax = calculateLightTax({
|
|
taxableGiftValue,
|
|
municipalTaxRate,
|
|
churchTaxRate,
|
|
bankBalance,
|
|
})
|
|
|
|
const giftTaxDebt = lightTax.gift_tax
|
|
const hobbyReserve = lightTax.hobby_tax
|
|
|
|
// Parse shadow ledger entries
|
|
const recentEntries = (entriesResult.data || []).map((entry) => ({
|
|
id: entry.id as string,
|
|
date: entry.date as string,
|
|
description: entry.description as string | null,
|
|
gross_amount: Number(entry.gross_amount),
|
|
net_amount: Number(entry.net_amount),
|
|
service_fee: Number(entry.service_fee),
|
|
pension_deduction: Number(entry.pension_deduction),
|
|
social_fees: Number(entry.social_fees),
|
|
income_tax_withheld: Number(entry.income_tax_withheld),
|
|
platform_fee: Number(entry.platform_fee),
|
|
type: entry.type as string,
|
|
provider: entry.provider as string | null,
|
|
}))
|
|
|
|
// Calculate days since last payout
|
|
let daysSinceLastPayout: number | null = null
|
|
const lastPayout = lastPayoutResult.data
|
|
if (lastPayout && lastPayout.length > 0) {
|
|
const lastPayoutDate = new Date(lastPayout[0].date)
|
|
const today = new Date()
|
|
today.setHours(0, 0, 0, 0)
|
|
lastPayoutDate.setHours(0, 0, 0, 0)
|
|
const diffMs = today.getTime() - lastPayoutDate.getTime()
|
|
daysSinceLastPayout = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
|
}
|
|
|
|
return {
|
|
firstName,
|
|
bankBalance,
|
|
giftTaxDebt,
|
|
taxableGiftCount,
|
|
effectiveRate,
|
|
daysSinceLastPayout,
|
|
hobbyReserve,
|
|
recentEntries,
|
|
}
|
|
}
|