Merge remote-tracking branch 'origin/main' into logger

This commit is contained in:
Emil
2026-02-24 10:06:42 +01:00
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: [
{
+1 -1
View File
@@ -110,7 +110,7 @@ export default function JournalEntryForm({
}
// Auto-fill line description from account name when selecting an account
if (field === 'account_number' && value && !updated[index].line_description) {
if (field === 'account_number' && value) {
const account = accounts.find((a) => a.account_number === value)
if (account) {
updated[index].line_description = account.account_name
@@ -27,8 +27,8 @@ function buildInitialLines(transaction: TransactionWithInvoice): FormLine[] {
if (isExpense) {
return [
{ account_number: '', debit_amount: amountStr, credit_amount: '', line_description: '' },
{ account_number: '1930', debit_amount: '', credit_amount: amountStr, line_description: 'Företagskonto' },
{ account_number: '', debit_amount: amountStr, credit_amount: '', line_description: '' },
]
}
+3 -2
View File
@@ -129,8 +129,9 @@ function buildLineInserts(
amount_in_currency: line.amount_in_currency ? Math.round(line.amount_in_currency * 100) / 100 : null,
exchange_rate: line.exchange_rate || null,
line_description: line.line_description || null,
cost_center_id: line.cost_center || null,
project_id: line.project || null,
tax_code: line.tax_code || null,
cost_center: line.cost_center || null,
project: line.project || null,
sort_order: index,
}))
}
+1 -1
View File
@@ -116,5 +116,5 @@ export async function sendEmail(options: SendEmailOptions): Promise<SendEmailRes
* Check if Resend is properly configured
*/
export function isResendConfigured(): boolean {
return !!process.env.RESEND_API_KEY
return !!process.env.RESEND_API_KEY && !!process.env.RESEND_FROM_EMAIL && process.env.RESEND_FROM_EMAIL !== 'noreply@localhost'
}
+1 -1
View File
@@ -115,7 +115,7 @@ export async function sendReminder(
subject: generateReminderEmailSubject(emailData),
html: generateReminderEmailHtml(emailData),
text: generateReminderEmailText(emailData),
replyTo: (company as CompanySettings & { email?: string }).email || undefined,
replyTo: company.email || undefined,
fromName: company.company_name || undefined
})
+2
View File
@@ -423,6 +423,8 @@ export function makeCompanySettings(
postal_code: '111 22',
city: 'Stockholm',
country: 'SE',
phone: null,
email: null,
f_skatt: true,
vat_registered: true,
vat_number: null,
+4
View File
@@ -93,6 +93,10 @@ export interface CompanySettings {
city: string | null
country: string
// Contact
phone: string | null
email: string | null
// Tax registration
f_skatt: boolean
vat_registered: boolean