Merge remote-tracking branch 'origin/main' into logger
This commit is contained in:
+42
-23
@@ -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
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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: '' },
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user