Files
tilbudgivern/backend/__tests__/smartPackageExcelImportService.test.js

113 lines
4.4 KiB
JavaScript

const {
parseNumber,
isRentalDuplicate,
parseWorkbook
} = require('../src/services/smartPackageExcelImportService');
const XLSX = require('xlsx');
const fs = require('fs');
const os = require('os');
const path = require('path');
describe('SmartPackageExcelImportService', () => {
test.each([
['1,007.97 kr.', 1007.97],
['1.007,97 kr.', 1007.97],
['600.00 kr.', 600],
['0.20', 0.2],
[null, 0]
])('parses Excel number %p', (value, expected) => {
expect(parseNumber(value)).toBe(expected);
});
test('removes zero-price rental duplicates covered by Byggeplads', () => {
const buildingSite = [
{ name: 'Leje af minigraver 1,5-2,0 ton' },
{ name: 'Mandskabsvogn 4 personer' },
{ name: 'Køreplader i stål' },
{ name: 'Leje Affaldscontainer åben 20 m³' }
];
expect(isRentalDuplicate({ name: 'Minigraver 1,8 ton', standardPrice: 0 }, buildingSite)).toBe(true);
expect(isRentalDuplicate({ name: 'Mandskabsvogn 4 pers.', standardPrice: 0 }, buildingSite)).toBe(true);
expect(isRentalDuplicate({ name: 'Køreplader stål', standardPrice: 0 }, buildingSite)).toBe(true);
expect(isRentalDuplicate({ name: '20 m3 åben', standardPrice: 0 }, buildingSite)).toBe(true);
expect(isRentalDuplicate({ name: '10 m3 åben', standardPrice: 0 }, buildingSite)).toBe(false);
expect(isRentalDuplicate({ name: '20 m3 lukket', standardPrice: 0 }, buildingSite)).toBe(false);
});
test('keeps priced and non-overlapping rental packages', () => {
const buildingSite = [{ name: 'Leje af saxlift 6 m' }];
expect(isRentalDuplicate({ name: 'Saxlift 6 m', standardPrice: 320 }, buildingSite)).toBe(false);
expect(isRentalDuplicate({ name: 'Saxlift 10 m', standardPrice: 0 }, buildingSite)).toBe(false);
});
test('maps an unknown workbook layout to canonical SmartPackage fields', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'excel-map-'));
const filePath = path.join(directory, 'custom.xlsx');
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet([
{ Ydelse: 'Opsætning af skur', Gruppe: 'Byggeplads', Pris: '1.250,00', Måleenhed: 'stk.' }
]), 'Ark 1');
XLSX.writeFile(workbook, filePath);
const parsed = parseWorkbook(filePath, {
sheets: [{
sourceSheet: 'Ark 1', targetSheet: 'Byggeplads', columns: [
{ source: 'Ydelse', target: 'name' },
{ source: 'Gruppe', target: 'category' },
{ source: 'Pris', target: 'unitPrice' },
{ source: 'Måleenhed', target: 'unit' }
]
}]
});
expect(parsed.packages).toHaveLength(1);
expect(parsed.packages[0]).toMatchObject({ name: 'Opsætning af skur', effectivePrice: 1250, unit: 'stk.' });
fs.rmSync(directory, { recursive: true, force: true });
});
test('rejects mappings without a name column', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'excel-map-'));
const filePath = path.join(directory, 'custom.xlsx');
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet([{ Pris: 100 }]), 'Data');
XLSX.writeFile(workbook, filePath);
expect(() => parseWorkbook(filePath, {
sheets: [{ sourceSheet: 'Data', targetSheet: 'Byggeplads', columns: [{ source: 'Pris', target: 'unitPrice' }] }]
})).toThrow('mangler pakkenavn');
fs.rmSync(directory, { recursive: true, force: true });
});
test('preserves the real Excel sheet and row when blank rows occur', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'excel-row-'));
const filePath = path.join(directory, 'custom.xlsx');
const workbook = XLSX.utils.book_new();
const sheet = XLSX.utils.aoa_to_sheet([
['Ydelse', 'Pris'],
[],
['Skurvogn', 750]
]);
XLSX.utils.book_append_sheet(workbook, sheet, 'Importdata');
XLSX.writeFile(workbook, filePath);
const parsed = parseWorkbook(filePath, {
sheets: [{
sourceSheet: 'Importdata',
targetSheet: 'Udlejning',
columns: [
{ source: 'Ydelse', target: 'name' },
{ source: 'Pris', target: 'standardPrice' }
]
}]
});
expect(parsed.packages[0]).toMatchObject({
sourceSheet: 'Udlejning',
excelSheet: 'Importdata',
sourceRow: 3
});
fs.rmSync(directory, { recursive: true, force: true });
});
});