Added logger and tests
This commit is contained in:
@@ -114,7 +114,8 @@ describe('Supplier Invoice Core Handler', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith(
|
expect(consoleSpy).toHaveBeenCalledWith(
|
||||||
'[supplier-invoice-handler] Failed to create registration journal entry:',
|
'[supplier-invoice-handler]',
|
||||||
|
'Failed to create registration journal entry:',
|
||||||
expect.any(Error)
|
expect.any(Error)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import { eventBus } from '@/lib/events/bus'
|
|||||||
import type { EventPayload } from '@/lib/events/types'
|
import type { EventPayload } from '@/lib/events/types'
|
||||||
import { createClient } from '@/lib/supabase/server'
|
import { createClient } from '@/lib/supabase/server'
|
||||||
import { createSupplierInvoiceRegistrationEntry } from '@/lib/bookkeeping/supplier-invoice-entries'
|
import { createSupplierInvoiceRegistrationEntry } from '@/lib/bookkeeping/supplier-invoice-entries'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type { SupplierInvoiceItem } from '@/types'
|
import type { SupplierInvoiceItem } from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('supplier-invoice-handler')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core event handler: creates a registration journal entry when a supplier
|
* Core event handler: creates a registration journal entry when a supplier
|
||||||
* invoice is confirmed (accrual method only).
|
* invoice is confirmed (accrual method only).
|
||||||
@@ -36,7 +39,7 @@ async function handleSupplierInvoiceConfirmed(
|
|||||||
.order('sort_order')
|
.order('sort_order')
|
||||||
|
|
||||||
if (itemsError || !items || items.length === 0) {
|
if (itemsError || !items || items.length === 0) {
|
||||||
console.error('[supplier-invoice-handler] Failed to fetch invoice items:', itemsError)
|
log.error('Failed to fetch invoice items:', itemsError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +67,7 @@ async function handleSupplierInvoiceConfirmed(
|
|||||||
.eq('id', supplierInvoice.id)
|
.eq('id', supplierInvoice.id)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[supplier-invoice-handler] Failed to create registration journal entry:', err)
|
log.error('Failed to create registration journal entry:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createJournalEntry, findFiscalPeriod } from './engine'
|
import { createJournalEntry, findFiscalPeriod } from './engine'
|
||||||
import { generateSalesVatLines, generateReverseChargeLines } from './vat-entries'
|
import { generateSalesVatLines, generateReverseChargeLines } from './vat-entries'
|
||||||
import { getVatTreatmentForRate } from '@/lib/invoices/vat-rules'
|
import { getVatTreatmentForRate } from '@/lib/invoices/vat-rules'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type {
|
import type {
|
||||||
CreateJournalEntryInput,
|
CreateJournalEntryInput,
|
||||||
CreateJournalEntryLineInput,
|
CreateJournalEntryLineInput,
|
||||||
@@ -11,6 +12,8 @@ import type {
|
|||||||
VatTreatment,
|
VatTreatment,
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('invoice-entries')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group invoice items by VAT rate and generate per-rate revenue + VAT lines.
|
* Group invoice items by VAT rate and generate per-rate revenue + VAT lines.
|
||||||
* Returns credit lines only (revenue + VAT). The caller adds the debit side.
|
* Returns credit lines only (revenue + VAT). The caller adds the debit side.
|
||||||
@@ -115,7 +118,7 @@ export async function createInvoiceJournalEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, invoice.invoice_date)
|
const fiscalPeriodId = await findFiscalPeriod(userId, invoice.invoice_date)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for invoice date:', invoice.invoice_date)
|
log.warn('No open fiscal period found for invoice date:', invoice.invoice_date)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +183,7 @@ export async function createInvoicePaymentJournalEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for payment date:', paymentDate)
|
log.warn('No open fiscal period found for payment date:', paymentDate)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +229,7 @@ export async function createCreditNoteJournalEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, creditNote.invoice_date)
|
const fiscalPeriodId = await findFiscalPeriod(userId, creditNote.invoice_date)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for credit note date:', creditNote.invoice_date)
|
log.warn('No open fiscal period found for credit note date:', creditNote.invoice_date)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +308,7 @@ export async function createInvoiceCashEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for payment date:', paymentDate)
|
log.warn('No open fiscal period found for payment date:', paymentDate)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,6 @@ export async function saveUserMappingRule(
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Failed to save user mapping rule:', error)
|
// Silently fail — saving learned rules is non-critical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createJournalEntry, findFiscalPeriod } from './engine'
|
import { createJournalEntry, findFiscalPeriod } from './engine'
|
||||||
import { generateReverseChargeLines } from './vat-entries'
|
import { generateReverseChargeLines } from './vat-entries'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type {
|
import type {
|
||||||
CreateJournalEntryInput,
|
CreateJournalEntryInput,
|
||||||
CreateJournalEntryLineInput,
|
CreateJournalEntryLineInput,
|
||||||
@@ -8,6 +9,8 @@ import type {
|
|||||||
SupplierInvoiceItem,
|
SupplierInvoiceItem,
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('supplier-invoice-entries')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create journal entry when a supplier invoice is registered (accrual method)
|
* Create journal entry when a supplier invoice is registered (accrual method)
|
||||||
*
|
*
|
||||||
@@ -30,7 +33,7 @@ export async function createSupplierInvoiceRegistrationEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, invoice.invoice_date)
|
const fiscalPeriodId = await findFiscalPeriod(userId, invoice.invoice_date)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for invoice date:', invoice.invoice_date)
|
log.warn('No open fiscal period found for invoice date:', invoice.invoice_date)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +115,7 @@ export async function createSupplierInvoicePaymentEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for payment date:', paymentDate)
|
log.warn('No open fiscal period found for payment date:', paymentDate)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +207,7 @@ export async function createSupplierInvoiceCashEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
const fiscalPeriodId = await findFiscalPeriod(userId, paymentDate)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for payment date:', paymentDate)
|
log.warn('No open fiscal period found for payment date:', paymentDate)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +281,7 @@ export async function createSupplierCreditNoteEntry(
|
|||||||
): Promise<JournalEntry | null> {
|
): Promise<JournalEntry | null> {
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, creditNote.invoice_date)
|
const fiscalPeriodId = await findFiscalPeriod(userId, creditNote.invoice_date)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for credit note date:', creditNote.invoice_date)
|
log.warn('No open fiscal period found for credit note date:', creditNote.invoice_date)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createJournalEntry, findFiscalPeriod } from './engine'
|
import { createJournalEntry, findFiscalPeriod } from './engine'
|
||||||
import { generateInputVatLine, generateReverseChargeLines, extractNetAmount, extractVatAmount } from './vat-entries'
|
import { generateInputVatLine, generateReverseChargeLines, extractNetAmount, extractVatAmount } from './vat-entries'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type {
|
import type {
|
||||||
CreateJournalEntryInput,
|
CreateJournalEntryInput,
|
||||||
CreateJournalEntryLineInput,
|
CreateJournalEntryLineInput,
|
||||||
@@ -8,6 +9,8 @@ import type {
|
|||||||
Transaction,
|
Transaction,
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('transaction-entries')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a journal entry from a bank transaction using mapping engine result
|
* Create a journal entry from a bank transaction using mapping engine result
|
||||||
*
|
*
|
||||||
@@ -47,7 +50,7 @@ export async function createTransactionJournalEntry(
|
|||||||
|
|
||||||
const fiscalPeriodId = await findFiscalPeriod(userId, transaction.date)
|
const fiscalPeriodId = await findFiscalPeriod(userId, transaction.date)
|
||||||
if (!fiscalPeriodId) {
|
if (!fiscalPeriodId) {
|
||||||
console.warn('No open fiscal period found for transaction date:', transaction.date)
|
log.warn('No open fiscal period found for transaction date:', transaction.date)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type { Currency, ExchangeRate } from '@/types'
|
import type { Currency, ExchangeRate } from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('riksbanken')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch exchange rates from Riksbanken API
|
* Fetch exchange rates from Riksbanken API
|
||||||
* Uses their public API for daily exchange rates
|
* Uses their public API for daily exchange rates
|
||||||
@@ -31,7 +34,7 @@ export async function fetchExchangeRate(
|
|||||||
|
|
||||||
const seriesId = seriesIds[currency]
|
const seriesId = seriesIds[currency]
|
||||||
if (!seriesId) {
|
if (!seriesId) {
|
||||||
console.error(`Unknown currency: ${currency}`)
|
log.error(`Unknown currency: ${currency}`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +87,7 @@ export async function fetchExchangeRate(
|
|||||||
|
|
||||||
return null
|
return null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching exchange rate:', error)
|
log.error('Error fetching exchange rate:', error)
|
||||||
// Return fallback rates for development/testing
|
// Return fallback rates for development/testing
|
||||||
return getFallbackRate(currency)
|
return getFallbackRate(currency)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SupabaseClient } from '@supabase/supabase-js'
|
import { SupabaseClient } from '@supabase/supabase-js'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type { DeadlineStatus } from '@/types'
|
import type { DeadlineStatus } from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('deadline-status')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of days before deadline when status changes to action_needed
|
* Number of days before deadline when status changes to action_needed
|
||||||
*/
|
*/
|
||||||
@@ -116,7 +119,7 @@ export async function updateDeadlineStatuses(
|
|||||||
.select('id')
|
.select('id')
|
||||||
|
|
||||||
if (overdueError) {
|
if (overdueError) {
|
||||||
console.error('Error updating overdue deadlines:', overdueError)
|
log.error('Error updating overdue deadlines:', overdueError)
|
||||||
} else {
|
} else {
|
||||||
newlyOverdue = overdueDeadlines?.length || 0
|
newlyOverdue = overdueDeadlines?.length || 0
|
||||||
updated += newlyOverdue
|
updated += newlyOverdue
|
||||||
@@ -136,7 +139,7 @@ export async function updateDeadlineStatuses(
|
|||||||
.select('id')
|
.select('id')
|
||||||
|
|
||||||
if (actionNeededError) {
|
if (actionNeededError) {
|
||||||
console.error('Error updating action_needed deadlines:', actionNeededError)
|
log.error('Error updating action_needed deadlines:', actionNeededError)
|
||||||
} else {
|
} else {
|
||||||
newlyActionNeeded = actionNeededDeadlines?.length || 0
|
newlyActionNeeded = actionNeededDeadlines?.length || 0
|
||||||
updated += newlyActionNeeded
|
updated += newlyActionNeeded
|
||||||
@@ -218,7 +221,7 @@ export async function getDeadlinesNeedingAttention(
|
|||||||
.order('due_date', { ascending: true })
|
.order('due_date', { ascending: true })
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error fetching deadlines needing attention:', error)
|
log.error('Error fetching deadlines needing attention:', error)
|
||||||
return { actionNeeded: [], overdue: [] }
|
return { actionNeeded: [], overdue: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -1,4 +1,7 @@
|
|||||||
import { Resend } from 'resend'
|
import { Resend } from 'resend'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
|
|
||||||
|
const log = createLogger('email')
|
||||||
|
|
||||||
// Default sender configuration
|
// Default sender configuration
|
||||||
// Using a fixed From address with dynamic Reply-To
|
// Using a fixed From address with dynamic Reply-To
|
||||||
@@ -88,7 +91,7 @@ export async function sendEmail(options: SendEmailOptions): Promise<SendEmailRes
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.error('Resend error:', response.error)
|
log.error('Resend error:', response.error)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: response.error.message
|
error: response.error.message
|
||||||
@@ -100,7 +103,7 @@ export async function sendEmail(options: SendEmailOptions): Promise<SendEmailRes
|
|||||||
messageId: response.data?.id
|
messageId: response.data?.id
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to send email:', error)
|
log.error('Failed to send email:', error)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'Unknown error'
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ describe('createExtensionContext', () => {
|
|||||||
ctx.log.warn('caution')
|
ctx.log.warn('caution')
|
||||||
ctx.log.error('oops')
|
ctx.log.error('oops')
|
||||||
|
|
||||||
expect(logSpy).toHaveBeenCalledWith('[my-ext]', 'hello', 42)
|
expect(logSpy).toHaveBeenCalledWith('[ext:my-ext]', 'hello', 42)
|
||||||
expect(warnSpy).toHaveBeenCalledWith('[my-ext]', 'caution')
|
expect(warnSpy).toHaveBeenCalledWith('[ext:my-ext]', 'caution')
|
||||||
expect(errorSpy).toHaveBeenCalledWith('[my-ext]', 'oops')
|
expect(errorSpy).toHaveBeenCalledWith('[ext:my-ext]', 'oops')
|
||||||
|
|
||||||
logSpy.mockRestore()
|
logSpy.mockRestore()
|
||||||
warnSpy.mockRestore()
|
warnSpy.mockRestore()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { SupabaseClient } from '@supabase/supabase-js'
|
|||||||
import type { CoreEvent } from '@/lib/events/types'
|
import type { CoreEvent } from '@/lib/events/types'
|
||||||
import { eventBus } from '@/lib/events/bus'
|
import { eventBus } from '@/lib/events/bus'
|
||||||
import { ingestTransactions } from '@/lib/transactions/ingest'
|
import { ingestTransactions } from '@/lib/transactions/ingest'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type {
|
import type {
|
||||||
ExtensionContext,
|
ExtensionContext,
|
||||||
ExtensionLogger,
|
ExtensionLogger,
|
||||||
@@ -13,12 +14,12 @@ import type {
|
|||||||
/**
|
/**
|
||||||
* Create a prefixed logger for an extension.
|
* Create a prefixed logger for an extension.
|
||||||
*/
|
*/
|
||||||
function createLogger(extensionId: string): ExtensionLogger {
|
function createExtLogger(extensionId: string): ExtensionLogger {
|
||||||
const prefix = `[${extensionId}]`
|
const logger = createLogger(`ext:${extensionId}`)
|
||||||
return {
|
return {
|
||||||
info: (message: string, ...args: unknown[]) => console.log(prefix, message, ...args),
|
info: (message: string, ...args: unknown[]) => logger.info(message, ...args),
|
||||||
warn: (message: string, ...args: unknown[]) => console.warn(prefix, message, ...args),
|
warn: (message: string, ...args: unknown[]) => logger.warn(message, ...args),
|
||||||
error: (message: string, ...args: unknown[]) => console.error(prefix, message, ...args),
|
error: (message: string, ...args: unknown[]) => logger.error(message, ...args),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ export function createExtensionContext(
|
|||||||
emit: (event: CoreEvent) => eventBus.emit(event),
|
emit: (event: CoreEvent) => eventBus.emit(event),
|
||||||
settings: createSettings(supabase, userId, extensionId),
|
settings: createSettings(supabase, userId, extensionId),
|
||||||
storage: createStorage(supabase),
|
storage: createStorage(supabase),
|
||||||
log: createLogger(extensionId),
|
log: createExtLogger(extensionId),
|
||||||
services: createServices(),
|
services: createServices(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class ExtensionRegistry {
|
|||||||
*/
|
*/
|
||||||
register(extension: Extension): void {
|
register(extension: Extension): void {
|
||||||
if (this.extensions.has(extension.id)) {
|
if (this.extensions.has(extension.id)) {
|
||||||
console.warn(`[ExtensionRegistry] Extension "${extension.id}" already registered, skipping`)
|
// Extension already registered — skip silently (expected during hot reloads)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export async function findMatchingInvoices(
|
|||||||
.order('due_date', { ascending: true })
|
.order('due_date', { ascending: true })
|
||||||
|
|
||||||
if (error || !invoices) {
|
if (error || !invoices) {
|
||||||
console.error('Failed to fetch invoices for matching:', error)
|
// Failed to fetch invoices — return empty matches
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import {
|
|||||||
generateReminderEmailSubject,
|
generateReminderEmailSubject,
|
||||||
getReminderDaysConfig
|
getReminderDaysConfig
|
||||||
} from '@/lib/email/reminder-templates'
|
} from '@/lib/email/reminder-templates'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type { Invoice, Customer, CompanySettings } from '@/types'
|
import type { Invoice, Customer, CompanySettings } from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('reminder-processor')
|
||||||
|
|
||||||
// Create a service client for cron jobs (no cookie access needed)
|
// Create a service client for cron jobs (no cookie access needed)
|
||||||
function createServiceClient() {
|
function createServiceClient() {
|
||||||
return createServerClient(
|
return createServerClient(
|
||||||
@@ -144,16 +147,16 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
.order('due_date', { ascending: true })
|
.order('due_date', { ascending: true })
|
||||||
|
|
||||||
if (invoiceError) {
|
if (invoiceError) {
|
||||||
console.error('Error fetching overdue invoices:', invoiceError)
|
log.error('Error fetching overdue invoices:', invoiceError)
|
||||||
return { processed: 0, sent: 0, failed: 0, results: [] }
|
return { processed: 0, sent: 0, failed: 0, results: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overdueInvoices || overdueInvoices.length === 0) {
|
if (!overdueInvoices || overdueInvoices.length === 0) {
|
||||||
console.log('No overdue invoices found')
|
log.info('No overdue invoices found')
|
||||||
return { processed: 0, sent: 0, failed: 0, results: [] }
|
return { processed: 0, sent: 0, failed: 0, results: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${overdueInvoices.length} overdue invoices to process`)
|
log.info(`Found ${overdueInvoices.length} overdue invoices to process`)
|
||||||
|
|
||||||
// Process each invoice
|
// Process each invoice
|
||||||
for (const invoice of overdueInvoices) {
|
for (const invoice of overdueInvoices) {
|
||||||
@@ -161,7 +164,7 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
|
|
||||||
// Skip if customer has no email
|
// Skip if customer has no email
|
||||||
if (!customer?.email) {
|
if (!customer?.email) {
|
||||||
console.log(`Skipping invoice ${invoice.invoice_number}: customer has no email`)
|
log.info(`Skipping invoice ${invoice.invoice_number}: customer has no email`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +180,7 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
|
|
||||||
// Skip if no reminder needed
|
// Skip if no reminder needed
|
||||||
if (!reminderLevel) {
|
if (!reminderLevel) {
|
||||||
console.log(`Skipping invoice ${invoice.invoice_number}: no reminder needed (${daysOverdue} days overdue, existing levels: ${existingLevels.join(', ')})`)
|
log.info(`Skipping invoice ${invoice.invoice_number}: no reminder needed (${daysOverdue} days overdue, existing levels: ${existingLevels.join(', ')})`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +192,7 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
.single()
|
.single()
|
||||||
|
|
||||||
if (companyError || !company) {
|
if (companyError || !company) {
|
||||||
console.error(`Skipping invoice ${invoice.invoice_number}: company settings not found`)
|
log.error(`Skipping invoice ${invoice.invoice_number}: company settings not found`)
|
||||||
results.push({
|
results.push({
|
||||||
invoiceId: invoice.id,
|
invoiceId: invoice.id,
|
||||||
invoiceNumber: invoice.invoice_number,
|
invoiceNumber: invoice.invoice_number,
|
||||||
@@ -214,7 +217,7 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
.single()
|
.single()
|
||||||
|
|
||||||
if (reminderError || !reminderRecord) {
|
if (reminderError || !reminderRecord) {
|
||||||
console.error(`Failed to create reminder record for invoice ${invoice.invoice_number}:`, reminderError)
|
log.error(`Failed to create reminder record for invoice ${invoice.invoice_number}:`, reminderError)
|
||||||
results.push({
|
results.push({
|
||||||
invoiceId: invoice.id,
|
invoiceId: invoice.id,
|
||||||
invoiceNumber: invoice.invoice_number,
|
invoiceNumber: invoice.invoice_number,
|
||||||
@@ -235,7 +238,7 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (sendResult.success) {
|
if (sendResult.success) {
|
||||||
console.log(`Sent level ${reminderLevel} reminder for invoice ${invoice.invoice_number} to ${customer.email}`)
|
log.info(`Sent level ${reminderLevel} reminder for invoice ${invoice.invoice_number} to ${customer.email}`)
|
||||||
|
|
||||||
// Update invoice status to overdue if not already
|
// Update invoice status to overdue if not already
|
||||||
if (invoice.status === 'sent') {
|
if (invoice.status === 'sent') {
|
||||||
@@ -245,7 +248,7 @@ export async function processOverdueReminders(): Promise<ProcessRemindersResult>
|
|||||||
.eq('id', invoice.id)
|
.eq('id', invoice.id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(`Failed to send reminder for invoice ${invoice.invoice_number}:`, sendResult.error)
|
log.error(`Failed to send reminder for invoice ${invoice.invoice_number}:`, sendResult.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Lightweight structured logger for server-side code.
|
||||||
|
*
|
||||||
|
* Wraps console.* with module prefixes and environment-aware filtering.
|
||||||
|
* Suppresses info/warn in test environment to reduce noise.
|
||||||
|
* Can be swapped for an external logging service (e.g. Axiom, Datadog) later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type LogLevel = 'info' | 'warn' | 'error'
|
||||||
|
|
||||||
|
function shouldLog(level: LogLevel): boolean {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
return level === 'error'
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLogger(module: string) {
|
||||||
|
const prefix = `[${module}]`
|
||||||
|
return {
|
||||||
|
info(message: string, ...args: unknown[]) {
|
||||||
|
if (shouldLog('info')) console.log(prefix, message, ...args)
|
||||||
|
},
|
||||||
|
warn(message: string, ...args: unknown[]) {
|
||||||
|
if (shouldLog('warn')) console.warn(prefix, message, ...args)
|
||||||
|
},
|
||||||
|
error(message: string, ...args: unknown[]) {
|
||||||
|
if (shouldLog('error')) console.error(prefix, message, ...args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SupabaseClient } from '@supabase/supabase-js'
|
import { SupabaseClient } from '@supabase/supabase-js'
|
||||||
|
import { createLogger } from '@/lib/logger'
|
||||||
import type { TaxDeadlineType, DeadlineStatus, CreateDeadlineInput } from '@/types'
|
import type { TaxDeadlineType, DeadlineStatus, CreateDeadlineInput } from '@/types'
|
||||||
|
|
||||||
|
const log = createLogger('deadline-generator')
|
||||||
import {
|
import {
|
||||||
TAX_DEADLINE_CONFIGS,
|
TAX_DEADLINE_CONFIGS,
|
||||||
getApplicableDeadlineConfigs,
|
getApplicableDeadlineConfigs,
|
||||||
@@ -81,7 +84,7 @@ export async function generateTaxDeadlinesForUser(
|
|||||||
.select('id')
|
.select('id')
|
||||||
|
|
||||||
if (deleteError) {
|
if (deleteError) {
|
||||||
console.error('Error deleting existing deadlines:', deleteError)
|
log.error('Error deleting existing deadlines:', deleteError)
|
||||||
throw deleteError
|
throw deleteError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +164,7 @@ export async function generateTaxDeadlinesForUser(
|
|||||||
.insert(deadlines)
|
.insert(deadlines)
|
||||||
|
|
||||||
if (insertError) {
|
if (insertError) {
|
||||||
console.error('Error inserting deadlines:', insertError)
|
log.error('Error inserting deadlines:', insertError)
|
||||||
throw insertError
|
throw insertError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +237,7 @@ export async function generateNewYearDeadlines(
|
|||||||
.select('user_id, entity_type, moms_period, f_skatt, vat_registered, pays_salaries, fiscal_year_start_month')
|
.select('user_id, entity_type, moms_period, f_skatt, vat_registered, pays_salaries, fiscal_year_start_month')
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error fetching company settings:', error)
|
log.error('Error fetching company settings:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +262,7 @@ export async function generateNewYearDeadlines(
|
|||||||
usersProcessed++
|
usersProcessed++
|
||||||
totalCreated += result.created
|
totalCreated += result.created
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error generating deadlines for user ${settings.user_id}:`, err)
|
log.error(`Error generating deadlines for user ${settings.user_id}:`, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user