121 lines
3.6 KiB
JavaScript
121 lines
3.6 KiB
JavaScript
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
|
|
};
|