const { parse } = require('csv-parse/sync'); const UnitOfWork = require('../../database/UnitOfWork'); const TaxRepository = require('../../repositories/tax/taxRepository'); const HEADERS = [ 'Country', 'Super-Reduced Rate (%)', 'Reduced Rate (%)', 'Parking Rate (%)', 'Standard Rate (%)' ]; function parseRate(raw) { if (!raw) return null; const cleaned = String(raw).replace(/"/g, '').replace(/%/g, '').trim(); if (!cleaned || cleaned === '-') return null; const parts = cleaned.split('/').map((p) => p.trim().replace(',', '.')); const nums = parts.map((p) => Number(p)).filter((n) => !Number.isNaN(n)); if (!nums.length) return null; return Math.max(...nums); } function extractCountry(value) { const str = String(value || '').trim(); const match = str.match(/^(.*)\((\w{2,3})\)\s*$/); if (match) return { name: match[1].trim(), code: match[2].trim().toUpperCase() }; return { name: str, code: str.slice(0, 2).toUpperCase() }; } function normalizeRows(rows) { return rows.map((row) => { const country = extractCountry(row['Country']); return { country_name: country.name, country_code: country.code, rates: { standard_rate: parseRate(row['Standard Rate (%)']), reduced_rate: parseRate(row['Reduced Rate (%)']), super_reduced_rate: parseRate(row['Super-Reduced Rate (%)']), parking_rate: parseRate(row['Parking Rate (%)']) } }; }); } async function importRates(entries, actorUserId) { const uow = new UnitOfWork(); await uow.start(); const repo = new TaxRepository(uow); try { const uniqueCountries = []; const seen = new Set(); for (const e of entries) { if (!seen.has(e.country_code)) { uniqueCountries.push({ code: e.country_code, name: e.country_name }); seen.add(e.country_code); } } const countryStats = await repo.upsertCountries(uniqueCountries, actorUserId); const codeMap = await repo.getCountriesByCodes(Array.from(seen)); const payloads = entries .map((e) => ({ country_id: codeMap[e.country_code]?.id, rates: e.rates })) .filter((p) => p.country_id); const rateStats = await repo.bulkImportVatRates(payloads, actorUserId); await uow.commit(); return { totalRows: entries.length, countriesCreated: countryStats.created, countriesUpdated: countryStats.updated, ratesUpdated: rateStats.ratesUpdated, ratesSkipped: rateStats.ratesSkipped }; } catch (err) { await uow.rollback(); throw err; } } async function listCurrentRates() { const uow = new UnitOfWork(); await uow.start(); const repo = new TaxRepository(uow); try { const data = await repo.getAllCurrentVatRates(); await uow.commit(); return data; } catch (err) { await uow.rollback(); throw err; } } async function getHistory(countryCode) { const uow = new UnitOfWork(); await uow.start(); const repo = new TaxRepository(uow); try { const country = await repo.getCountryByCode(countryCode); if (!country) throw new Error('Country not found'); const history = await repo.getVatHistory(country.id); await uow.commit(); return { country, history }; } catch (err) { await uow.rollback(); throw err; } } module.exports = { parseCsv(buffer) { const records = parse(buffer, { columns: true, skip_empty_lines: true, trim: true }); const headers = Object.keys(records[0] || {}); const missing = HEADERS.filter((h) => !headers.includes(h)); if (missing.length) throw new Error(`Missing headers: ${missing.join(', ')}`); return normalizeRows(records); }, importRates, listCurrentRates, getHistory };