fix: correct engine column names, dashboard revenue calc, email config, and UX issues

- Fix buildLineInserts() using wrong DB column names (cost_center_id → cost_center,
  project_id → project) and add missing tax_code field
- Replace transaction-based dashboard income/expense with journal-entry-based
  calculation using account classes (3xxx revenue, 4-7xxx expenses)
- Add email/phone fields to CompanySettings type, validate RESEND_FROM_EMAIL
  in isResendConfigured(), remove unsafe type casts in invoice send/reminders
- Always auto-fill line description from account name in JournalEntryForm
- Reorder expense lines in TransactionBookingDialog so 1930 is always first row

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-24 10:02:54 +01:00
co-authored by Claude Opus 4.6
parent bbb82866ee
commit 4d2140bcf8
9 changed files with 56 additions and 30 deletions
+42 -23
View File
@@ -59,34 +59,53 @@ export default async function DashboardPage() {
hasBankConnected: (transactionCount || 0) > 0,
}
// Fetch current year transactions summary
const startOfYear = new Date(new Date().getFullYear(), 0, 1).toISOString()
const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString()
// Fetch current year date boundaries
const startOfYearStr = new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0]
const startOfMonthStr = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0]
const { data: transactions } = await supabase
.from('transactions')
.select('amount, amount_sek, is_business, category, date')
.eq('user_id', user.id)
.gte('date', startOfYear.split('T')[0])
// Fetch posted journal entry lines for revenue/expense accounts (classes 3-7)
const { data: journalLines } = await supabase
.from('journal_entry_lines')
.select('account_number, debit_amount, credit_amount, journal_entry:journal_entries!inner(entry_date, status)')
.eq('journal_entry.status', 'posted')
.gte('journal_entry.entry_date', startOfYearStr)
// Calculate summaries
const ytdTransactions = transactions || []
const mtdTransactions = ytdTransactions.filter(
(t) => t.date >= startOfMonth.split('T')[0]
)
// Calculate totals from journal entry lines using account classes
const calculateTotals = (lines: typeof journalLines, fromDate: string) => {
const filtered = (lines || []).filter((l) => {
const entry = l.journal_entry as unknown as { entry_date: string; status: string }
return entry.entry_date >= fromDate
})
const calculateTotals = (txns: typeof ytdTransactions) => {
const income = txns
.filter((t) => t.is_business && t.amount > 0)
.reduce((sum, t) => sum + Number(t.amount_sek || t.amount), 0)
const expenses = txns
.filter((t) => t.is_business && t.amount < 0)
.reduce((sum, t) => sum + Math.abs(Number(t.amount_sek || t.amount)), 0)
return { income, expenses, net: income - expenses }
let revenue = 0
let expenses = 0
for (const line of filtered) {
const acct = line.account_number
if (acct.startsWith('3')) {
// Revenue: class 3 — credit-normal accounts
revenue += Math.round(((line.credit_amount || 0) - (line.debit_amount || 0)) * 100) / 100
} else if (acct.startsWith('4') || acct.startsWith('5') || acct.startsWith('6') || acct.startsWith('7')) {
// Expenses: classes 4-7 — debit-normal accounts
expenses += Math.round(((line.debit_amount || 0) - (line.credit_amount || 0)) * 100) / 100
}
}
revenue = Math.round(revenue * 100) / 100
expenses = Math.round(expenses * 100) / 100
return { income: revenue, expenses, net: Math.round((revenue - expenses) * 100) / 100 }
}
const ytdTotals = calculateTotals(ytdTransactions)
const mtdTotals = calculateTotals(mtdTransactions)
const ytdTotals = calculateTotals(journalLines, startOfYearStr)
const mtdTotals = calculateTotals(journalLines, startOfMonthStr)
// Fetch uncategorized transaction counts (still useful for the alert)
const { data: transactions } = await supabase
.from('transactions')
.select('amount, amount_sek, is_business')
.eq('user_id', user.id)
.gte('date', startOfYearStr)
const uncategorizedTxns = (transactions || []).filter(
(t) => t.is_business === null
+1 -1
View File
@@ -134,7 +134,7 @@ export async function POST(
subject: generateInvoiceEmailSubject(emailData),
html: generateInvoiceEmailHtml(emailData),
text: generateInvoiceEmailText(emailData),
replyTo: (company as CompanySettings & { email?: string }).email || undefined,
replyTo: company.email || undefined,
fromName: company.company_name,
attachments: [
{