New Base func
This commit is contained in:
@@ -45,6 +45,21 @@ export async function generateSIEExport(
|
||||
.eq('status', 'posted')
|
||||
.order('voucher_number')
|
||||
|
||||
// Fetch cost centers and projects for dimension records
|
||||
const { data: costCenters } = await supabase
|
||||
.from('cost_centers')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('is_active', true)
|
||||
.order('code')
|
||||
|
||||
const { data: projects } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('is_active', true)
|
||||
.order('code')
|
||||
|
||||
const lines: string[] = []
|
||||
const now = new Date()
|
||||
|
||||
@@ -66,10 +81,34 @@ export async function generateSIEExport(
|
||||
// Use date strings directly to avoid timezone conversion issues
|
||||
lines.push(`#RAR 0 ${dateStringToSIE(period.period_start)} ${dateStringToSIE(period.period_end)}`)
|
||||
|
||||
// === Dimension definitions ===
|
||||
// SIE standard: dimension 1 = kostnadsställe, dimension 6 = projekt
|
||||
const hasCostCenters = costCenters && costCenters.length > 0
|
||||
const hasProjects = projects && projects.length > 0
|
||||
|
||||
if (hasCostCenters) {
|
||||
lines.push('#DIM 1 "Kostnadsställe"')
|
||||
}
|
||||
if (hasProjects) {
|
||||
lines.push('#DIM 6 "Projekt"')
|
||||
}
|
||||
|
||||
// === Dimension objects (#OBJEKT) ===
|
||||
for (const cc of costCenters || []) {
|
||||
lines.push(`#OBJEKT 1 "${escapeQuotes(cc.code)}" "${escapeQuotes(cc.name)}"`)
|
||||
}
|
||||
for (const proj of projects || []) {
|
||||
lines.push(`#OBJEKT 6 "${escapeQuotes(proj.code)}" "${escapeQuotes(proj.name)}"`)
|
||||
}
|
||||
|
||||
// === Chart of accounts ===
|
||||
for (const account of (accounts as BASAccount[]) || []) {
|
||||
lines.push(`#KONTO ${account.account_number} "${escapeQuotes(account.account_name)}"`)
|
||||
// SRU codes could be added here if available
|
||||
|
||||
// #SRU records from chart_of_accounts.sru_code
|
||||
if (account.sru_code) {
|
||||
lines.push(`#SRU ${account.account_number} ${account.sru_code}`)
|
||||
}
|
||||
}
|
||||
|
||||
// === Opening balances (IB) ===
|
||||
@@ -96,7 +135,17 @@ export async function generateSIEExport(
|
||||
? ` "${escapeQuotes(line.line_description)}"`
|
||||
: ''
|
||||
|
||||
lines.push(`\t#TRANS ${line.account_number} {} ${formatAmount(amount)} ${entryDate}${lineDesc}`)
|
||||
// Build dimension object list for #TRANS line
|
||||
const dimParts: string[] = []
|
||||
if (line.cost_center) {
|
||||
dimParts.push(`1 "${escapeQuotes(line.cost_center)}"`)
|
||||
}
|
||||
if (line.project) {
|
||||
dimParts.push(`6 "${escapeQuotes(line.project)}"`)
|
||||
}
|
||||
const objList = dimParts.length > 0 ? `{${dimParts.join(' ')}}` : '{}'
|
||||
|
||||
lines.push(`\t#TRANS ${line.account_number} ${objList} ${formatAmount(amount)} ${entryDate}${lineDesc}`)
|
||||
}
|
||||
|
||||
lines.push('}')
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
Invoice,
|
||||
Transaction,
|
||||
Receipt,
|
||||
TaxCode,
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
@@ -419,3 +420,125 @@ export function formatPeriodLabel(
|
||||
return `${year}`
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tax-code-driven VAT declaration (new approach)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Calculate VAT declaration using tax codes from journal entry lines.
|
||||
*
|
||||
* This is the new, tax-code-driven approach that sums journal_entry_lines
|
||||
* grouped by tax_code, then maps via the tax_codes table to moms boxes.
|
||||
* Falls back to the legacy invoice/transaction/receipt approach for
|
||||
* lines without tax codes.
|
||||
*/
|
||||
export async function calculateVatDeclarationFromTaxCodes(
|
||||
userId: string,
|
||||
periodType: VatPeriodType,
|
||||
year: number,
|
||||
period: number
|
||||
): Promise<VatDeclaration> {
|
||||
const supabase = await createClient()
|
||||
const { start, end } = calculatePeriodDates(periodType, year, period)
|
||||
|
||||
// Fetch tax codes for this user (including system codes)
|
||||
const { data: taxCodesData } = await supabase
|
||||
.from('tax_codes')
|
||||
.select('*')
|
||||
.or(`user_id.eq.${userId},user_id.is.null`)
|
||||
|
||||
const taxCodes = (taxCodesData as TaxCode[]) || []
|
||||
const taxCodeMap = new Map<string, TaxCode>()
|
||||
for (const tc of taxCodes) {
|
||||
if (!taxCodeMap.has(tc.code) || tc.user_id) {
|
||||
taxCodeMap.set(tc.code, tc)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch posted journal entry lines with tax_code in the period
|
||||
const { data: lines } = await supabase
|
||||
.from('journal_entry_lines')
|
||||
.select(`
|
||||
tax_code,
|
||||
debit_amount,
|
||||
credit_amount,
|
||||
journal_entry_id,
|
||||
journal_entries!inner (
|
||||
user_id,
|
||||
entry_date,
|
||||
status
|
||||
)
|
||||
`)
|
||||
.not('tax_code', 'is', null)
|
||||
.eq('journal_entries.user_id', userId)
|
||||
.eq('journal_entries.status', 'posted')
|
||||
.gte('journal_entries.entry_date', start)
|
||||
.lte('journal_entries.entry_date', end)
|
||||
|
||||
// Aggregate amounts by moms box
|
||||
const boxTotals = new Map<string, number>()
|
||||
|
||||
for (const line of lines || []) {
|
||||
if (!line.tax_code) continue
|
||||
|
||||
const taxCode = taxCodeMap.get(line.tax_code)
|
||||
if (!taxCode) continue
|
||||
|
||||
const amount = Math.abs(Number(line.debit_amount || 0) - Number(line.credit_amount || 0))
|
||||
|
||||
// Map to all relevant boxes
|
||||
for (const box of [...taxCode.moms_basis_boxes, ...taxCode.moms_tax_boxes, ...taxCode.moms_input_boxes]) {
|
||||
const current = boxTotals.get(box) || 0
|
||||
boxTotals.set(box, current + amount)
|
||||
}
|
||||
}
|
||||
|
||||
// Build rutor from box totals
|
||||
const rutor: VatDeclarationRutor = {
|
||||
ruta05: round(boxTotals.get('05') || 0),
|
||||
ruta06: round(boxTotals.get('06') || 0),
|
||||
ruta07: round(boxTotals.get('07') || 0),
|
||||
ruta10: round(boxTotals.get('10') || 0),
|
||||
ruta11: round(boxTotals.get('11') || 0),
|
||||
ruta12: round(boxTotals.get('12') || 0),
|
||||
ruta39: round(boxTotals.get('39') || 0),
|
||||
ruta40: round(boxTotals.get('40') || 0),
|
||||
ruta48: round(boxTotals.get('48') || 0),
|
||||
ruta49: 0,
|
||||
}
|
||||
|
||||
const totalOutputVat = round(rutor.ruta05 + rutor.ruta06 + rutor.ruta07)
|
||||
rutor.ruta49 = round(totalOutputVat - rutor.ruta48)
|
||||
|
||||
return {
|
||||
period: {
|
||||
type: periodType,
|
||||
year,
|
||||
period,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
rutor,
|
||||
invoiceCount: 0,
|
||||
transactionCount: (lines || []).length,
|
||||
breakdown: {
|
||||
invoices: {
|
||||
ruta05: 0,
|
||||
ruta06: 0,
|
||||
ruta07: 0,
|
||||
ruta10: 0,
|
||||
ruta11: 0,
|
||||
ruta12: 0,
|
||||
ruta39: 0,
|
||||
ruta40: 0,
|
||||
},
|
||||
transactions: {
|
||||
ruta48: 0,
|
||||
},
|
||||
receipts: {
|
||||
ruta48: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user