518 lines
14 KiB
JavaScript
518 lines
14 KiB
JavaScript
/**
|
||
* Script til at oprette erfaringsbaserede SmartPackages
|
||
* Baseret på kundens faktiske priser og erfaringer
|
||
*/
|
||
|
||
const axios = require('axios');
|
||
const mysql = require('mysql2/promise');
|
||
|
||
const LOCAL_API = 'http://localhost:4031';
|
||
|
||
// Database connection
|
||
const dbConfig = {
|
||
host: 'localhost',
|
||
user: 'tilbudgivern_service',
|
||
password: process.env.DB_PASSWORD,
|
||
database: 'tilbudgivern'
|
||
};
|
||
|
||
/**
|
||
* Hent pris fra database med 20% avance
|
||
*/
|
||
async function getPriceWithMarkup(varenr) {
|
||
const connection = await mysql.createConnection(dbConfig);
|
||
try {
|
||
const [rows] = await connection.execute(
|
||
`SELECT mp.price, mp.unit, m.name
|
||
FROM materials m
|
||
LEFT JOIN material_prices mp ON m.id = mp.material_id
|
||
WHERE m.sku = ? AND mp.is_active = 1
|
||
ORDER BY mp.valid_from DESC LIMIT 1`,
|
||
[varenr]
|
||
);
|
||
|
||
if (rows.length > 0) {
|
||
const basePrice = parseFloat(rows[0].price);
|
||
const priceWithMarkup = basePrice * 1.20; // 20% avance
|
||
return {
|
||
price: priceWithMarkup,
|
||
unit: rows[0].unit,
|
||
name: rows[0].name
|
||
};
|
||
}
|
||
// Returner placeholder hvis ikke fundet - tilføjes senere
|
||
return null;
|
||
} finally {
|
||
await connection.end();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Opret materiale entry (bruges til at oprette pakkerne nu, priser tilføjes senere)
|
||
*/
|
||
function createMaterialEntry(name, productNumber, quantity, unit, description = '') {
|
||
return {
|
||
name,
|
||
productNumber,
|
||
quantity: typeof quantity === 'string' ? 1 : quantity,
|
||
quantityFormula: typeof quantity === 'string' ? quantity : null,
|
||
unit,
|
||
unitPrice: 0, // Placeholder - opdateres når priser tilføjes
|
||
description,
|
||
needsPriceUpdate: true
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Opret SmartPackage via API
|
||
*/
|
||
async function createSmartPackage(packageData) {
|
||
try {
|
||
const response = await axios.post(`${LOCAL_API}/api/smart-packages`, packageData);
|
||
console.log(`✅ Oprettet pakke: ${packageData.name} (ID: ${response.data.id})`);
|
||
return response.data;
|
||
} catch (error) {
|
||
console.error(`❌ Fejl ved oprettelse af ${packageData.name}:`, error.response?.data || error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* PAKKE 1: Tagrende med Konsoljern i stål (Lindab materialer)
|
||
*/
|
||
async function createTagrendePakke() {
|
||
console.log('\n📦 Opretter Tagrende pakke...');
|
||
|
||
// Materialeliste med formler for beregning
|
||
const materials = [
|
||
createMaterialEntry(
|
||
'Tagrende 3 meter',
|
||
'077424',
|
||
'FORMULA:facadelængde/3',
|
||
'stk',
|
||
'Antal beregnes automatisk: facadelængde / 3 meter'
|
||
),
|
||
createMaterialEntry(
|
||
'Ende bund',
|
||
'107744',
|
||
4,
|
||
'stk',
|
||
'1 i hver ende - 4 stk generisk'
|
||
),
|
||
createMaterialEntry(
|
||
'Samle stykke',
|
||
'077419',
|
||
'FORMULA:(facadelængde/3)-1',
|
||
'stk',
|
||
'1 samle beslag pr 3 meter tagrende'
|
||
),
|
||
createMaterialEntry(
|
||
'Konsol jern',
|
||
'077407',
|
||
'FORMULA:facadelængde/0.6',
|
||
'stk',
|
||
'Beregnes automatisk: maks 60cm afstand'
|
||
),
|
||
createMaterialEntry(
|
||
'Hjørne indvendig',
|
||
'077426',
|
||
1,
|
||
'stk',
|
||
'Huskeliste - tilpas efter behov'
|
||
),
|
||
createMaterialEntry(
|
||
'Hjørne udvendig',
|
||
'077431',
|
||
1,
|
||
'stk',
|
||
'Huskeliste - tilpas efter behov'
|
||
)
|
||
];
|
||
|
||
const packageData = {
|
||
name: 'Tagrende med Konsoljern (Lindab)',
|
||
description: 'Komplet tagrende system med konsoljern i stål. Materialer fra Lindab.\n\nArbeidsberegning: 10 meter tager 5 timer (0,5 time pr meter)',
|
||
category: 'Tag - Tagrende',
|
||
estimatedHours: 'FORMULA:facadelængde*0.5', // 10m = 5 timer
|
||
materials,
|
||
requiresGeometry: true,
|
||
geometryFields: ['facadelængde']
|
||
};
|
||
|
||
return await createSmartPackage(packageData);
|
||
}
|
||
|
||
/**
|
||
* PAKKE 2: Nedløb
|
||
*/
|
||
async function createNedløbPakke() {
|
||
console.log('\n📦 Opretter Nedløb pakke...');
|
||
|
||
const materials = [
|
||
{
|
||
name: 'Nedløb 3 meter',
|
||
productNumber: '077438',
|
||
quantity: 'FORMULA:nedløb_antal', // Sættes af bruger
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Rørholder',
|
||
productNumber: '184937',
|
||
quantity: 'FORMULA:nedløb_antal*2', // 2 pr nedløb
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Tudstykke',
|
||
productNumber: '122214',
|
||
quantity: 'FORMULA:nedløb_antal', // 1 pr nedløb
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Silikone',
|
||
productNumber: '050043',
|
||
quantity: 'FORMULA:nedløb_antal', // 1 pr nedløb
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Brøndkrave',
|
||
productNumber: '061637',
|
||
quantity: 'FORMULA:nedløb_antal', // 1 pr nedløb
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Bøjning',
|
||
productNumber: '076623',
|
||
quantity: 'FORMULA:nedløb_antal*2', // 2 pr nedløb
|
||
unit: 'stk',
|
||
description: 'Bøjninger varierer - tilpas efter behov'
|
||
}
|
||
];
|
||
|
||
const materialsWithPrices = [];
|
||
for (const m of materials) {
|
||
const priceData = await getPriceWithMarkup(m.productNumber);
|
||
if (priceData) {
|
||
materialsWithPrices.push({
|
||
name: priceData.name || m.name,
|
||
quantity: 1, // Placeholder
|
||
quantityFormula: m.quantity,
|
||
unit: priceData.unit,
|
||
unitPrice: priceData.price,
|
||
description: m.description
|
||
});
|
||
} else {
|
||
console.warn(`⚠️ Kunne ikke finde pris for ${m.productNumber} - ${m.name}`);
|
||
}
|
||
}
|
||
|
||
const packageData = {
|
||
name: 'Nedløb komplet',
|
||
description: 'Komplet nedløbssystem med tilbehør.\n\nVælg antal nedløb - systemet beregner automatisk tilbehør.\n\nArbeidsberegning: 1 nedløb = 1 time',
|
||
category: 'Tag - Nedløb',
|
||
estimatedHours: 'FORMULA:nedløb_antal*1', // 1 stk = 1 time
|
||
materials: materialsWithPrices,
|
||
requiresGeometry: true,
|
||
geometryFields: ['nedløb_antal']
|
||
};
|
||
|
||
return await createSmartPackage(packageData);
|
||
}
|
||
|
||
/**
|
||
* PAKKE 3: Opretning af spær
|
||
*/
|
||
async function createSpærPakke() {
|
||
console.log('\n📦 Opretter Opretning af spær pakke...');
|
||
|
||
const materials = [
|
||
{
|
||
name: 'Spær træ 200mm',
|
||
productNumber: '146304720010450',
|
||
quantity: 'FORMULA:(huslængde+1)*husbredde', // LBM beregning
|
||
unit: 'løbende meter'
|
||
},
|
||
{
|
||
name: 'Spånskruer 5,0x100',
|
||
productNumber: '310615',
|
||
quantity: 'FORMULA:((huslængde+1)*husbredde)*5', // 5 skruer pr LBM
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Muresnor',
|
||
productNumber: '195408',
|
||
quantity: 1,
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Bits TX 20',
|
||
productNumber: '224770',
|
||
quantity: 1,
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Snorvatterpas',
|
||
productNumber: '064736',
|
||
quantity: 2,
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Rundsavsklinge',
|
||
productNumber: '182788',
|
||
quantity: 1,
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Stiksavsklinger',
|
||
productNumber: '147761',
|
||
quantity: 1,
|
||
unit: 'stk'
|
||
}
|
||
];
|
||
|
||
const materialsWithPrices = [];
|
||
for (const m of materials) {
|
||
const priceData = await getPriceWithMarkup(m.productNumber);
|
||
if (priceData) {
|
||
materialsWithPrices.push({
|
||
name: priceData.name || m.name,
|
||
quantity: typeof m.quantity === 'string' ? 1 : m.quantity,
|
||
quantityFormula: typeof m.quantity === 'string' ? m.quantity : null,
|
||
unit: priceData.unit,
|
||
unitPrice: priceData.price
|
||
});
|
||
} else {
|
||
console.warn(`⚠️ Kunne ikke finde pris for ${m.productNumber} - ${m.name}`);
|
||
}
|
||
}
|
||
|
||
const packageData = {
|
||
name: 'Opretning af spær',
|
||
description: 'Komplet opretning af tagspær med materialer og værktøj.\n\nLBM beregnes automatisk: (huslængde + 1) × husbredde\n\nArbeidsberegning: 5 meter = 0,5 time',
|
||
category: 'Tag - Spær',
|
||
estimatedHours: 'FORMULA:((huslængde+1)*husbredde)*0.1', // 5m = 0.5t => 1m = 0.1t
|
||
materials: materialsWithPrices,
|
||
requiresGeometry: true,
|
||
geometryFields: ['huslængde', 'husbredde']
|
||
};
|
||
|
||
return await createSmartPackage(packageData);
|
||
}
|
||
|
||
/**
|
||
* PAKKE 4: Montering af B7 inkl. lægter
|
||
*/
|
||
async function createB7Pakke() {
|
||
console.log('\n📦 Opretter B7 montering pakke...');
|
||
|
||
const materials = [
|
||
{
|
||
name: 'B7 Plade',
|
||
productNumber: '147072',
|
||
quantity: 'FORMULA:tagflade_m2*2.13', // 2,13 plade pr m²
|
||
unit: 'm²'
|
||
},
|
||
{
|
||
name: 'Lægter',
|
||
productNumber: '1632038073',
|
||
quantity: 'FORMULA:tagflade_m2*2.17', // 2,17 m pr m²
|
||
unit: 'løbende meter',
|
||
description: 'Lægteafstand 46 cm'
|
||
},
|
||
{
|
||
name: 'Tagskruer',
|
||
productNumber: '119229',
|
||
quantity: 'FORMULA:tagflade_m2*4.25', // 4,25 skrue pr m²
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'Ringsøm',
|
||
productNumber: '101933',
|
||
quantity: 'FORMULA:tagflade_m2*15', // 15 søm pr m²
|
||
unit: 'stk',
|
||
description: 'Pakke med 2880 stk'
|
||
},
|
||
{
|
||
name: 'EPDM bånd 30 meter',
|
||
productNumber: '029218',
|
||
quantity: 'FORMULA:tagflade_m2*2.17', // 2,17 m pr m²
|
||
unit: 'løbende meter'
|
||
}
|
||
];
|
||
|
||
const materialsWithPrices = [];
|
||
for (const m of materials) {
|
||
const priceData = await getPriceWithMarkup(m.productNumber);
|
||
if (priceData) {
|
||
materialsWithPrices.push({
|
||
name: priceData.name || m.name,
|
||
quantity: typeof m.quantity === 'string' ? 1 : m.quantity,
|
||
quantityFormula: typeof m.quantity === 'string' ? m.quantity : null,
|
||
unit: priceData.unit,
|
||
unitPrice: priceData.price,
|
||
description: m.description
|
||
});
|
||
} else {
|
||
console.warn(`⚠️ Kunne ikke finde pris for ${m.productNumber} - ${m.name}`);
|
||
}
|
||
}
|
||
|
||
const packageData = {
|
||
name: 'B7 montering inkl. lægter',
|
||
description: 'Komplet B7 tagbeklædning med lægter og fastgørelse.\n\nArbeidsberegning: 0,5 time pr m²',
|
||
category: 'Tag - B7',
|
||
estimatedHours: 'FORMULA:tagflade_m2*0.5', // 0,5 time pr m²
|
||
materials: materialsWithPrices,
|
||
requiresGeometry: true,
|
||
geometryFields: ['tagflade_m2']
|
||
};
|
||
|
||
return await createSmartPackage(packageData);
|
||
}
|
||
|
||
/**
|
||
* PAKKE 5: Montering af rygning B7
|
||
*/
|
||
async function createRygningB7Pakke() {
|
||
console.log('\n📦 Opretter Rygning B7 pakke...');
|
||
|
||
const materials = [
|
||
{
|
||
name: 'Rygning',
|
||
productNumber: '057437',
|
||
quantity: 'FORMULA:rygning_længde', // 1 stk pr meter
|
||
unit: 'løbende meter'
|
||
},
|
||
{
|
||
name: 'Rygningselement',
|
||
productNumber: '038218',
|
||
quantity: 'FORMULA:rygning_længde', // 1 stk pr meter
|
||
unit: 'løbende meter'
|
||
},
|
||
{
|
||
name: 'Tagskruer 100 stk',
|
||
productNumber: '144855',
|
||
quantity: 'FORMULA:rygning_længde*4', // 4 skrue pr meter
|
||
unit: 'stk'
|
||
},
|
||
{
|
||
name: 'EPDM bånd 30 meter',
|
||
productNumber: '029218',
|
||
quantity: 'FORMULA:rygning_længde*0.4', // 0,4 meter pr meter
|
||
unit: 'løbende meter'
|
||
}
|
||
];
|
||
|
||
const materialsWithPrices = [];
|
||
for (const m of materials) {
|
||
const priceData = await getPriceWithMarkup(m.productNumber);
|
||
if (priceData) {
|
||
materialsWithPrices.push({
|
||
name: priceData.name || m.name,
|
||
quantity: typeof m.quantity === 'string' ? 1 : m.quantity,
|
||
quantityFormula: typeof m.quantity === 'string' ? m.quantity : null,
|
||
unit: priceData.unit,
|
||
unitPrice: priceData.price
|
||
});
|
||
} else {
|
||
console.warn(`⚠️ Kunne ikke finde pris for ${m.productNumber} - ${m.name}`);
|
||
}
|
||
}
|
||
|
||
const packageData = {
|
||
name: 'Rygning B7',
|
||
description: 'Komplet rygningsløsning til B7 tag.\n\nArbeidsberegning: Tilføjes senere',
|
||
category: 'Tag - Rygning',
|
||
estimatedHours: 'FORMULA:rygning_længde*0.25', // Estimeret 15 min pr meter
|
||
materials: materialsWithPrices,
|
||
requiresGeometry: true,
|
||
geometryFields: ['rygning_længde']
|
||
};
|
||
|
||
return await createSmartPackage(packageData);
|
||
}
|
||
|
||
/**
|
||
* PAKKE 6: Montering af taghætte B7
|
||
*/
|
||
async function createTaghætteB7Pakke() {
|
||
console.log('\n📦 Opretter Taghætte B7 pakke...');
|
||
|
||
const materials = [
|
||
{
|
||
name: 'Taghætte',
|
||
productNumber: '126813',
|
||
quantity: 1,
|
||
unit: 'stk'
|
||
}
|
||
];
|
||
|
||
const materialsWithPrices = [];
|
||
for (const m of materials) {
|
||
const priceData = await getPriceWithMarkup(m.productNumber);
|
||
if (priceData) {
|
||
materialsWithPrices.push({
|
||
name: priceData.name || m.name,
|
||
quantity: m.quantity,
|
||
unit: priceData.unit,
|
||
unitPrice: priceData.price
|
||
});
|
||
} else {
|
||
console.warn(`⚠️ Kunne ikke finde pris for ${m.productNumber} - ${m.name}`);
|
||
}
|
||
}
|
||
|
||
const packageData = {
|
||
name: 'Taghætte B7',
|
||
description: 'Montering af taghætte til B7 tag.\n\nArbeidsberegning: Tilføjes senere',
|
||
category: 'Tag - Taghætte',
|
||
estimatedHours: 1, // Estimeret
|
||
materials: materialsWithPrices,
|
||
requiresGeometry: false
|
||
};
|
||
|
||
return await createSmartPackage(packageData);
|
||
}
|
||
|
||
/**
|
||
* Hovedfunktion
|
||
*/
|
||
async function main() {
|
||
console.log('🚀 Starter oprettelse af erfaringsbaserede SmartPackages...\n');
|
||
console.log('📌 Alle priser hentes fra database med 20% avance\n');
|
||
|
||
try {
|
||
// Opret alle pakker
|
||
await createTagrendePakke();
|
||
await createNedløbPakke();
|
||
await createSpærPakke();
|
||
await createB7Pakke();
|
||
await createRygningB7Pakke();
|
||
await createTaghætteB7Pakke();
|
||
|
||
console.log('\n✅ Alle pakker oprettet succesfuldt!');
|
||
console.log('\n📝 Næste skridt:');
|
||
console.log(' 1. Sikr at geometri gemmes i database (facadelængde, husbredde, m² osv.)');
|
||
console.log(' 2. Implementer beregningslogik for formler (FORMULA:...)');
|
||
console.log(' 3. Test pakkerne i frontend');
|
||
console.log(' 4. Få manglende pakker fra kunde (B6, Betontegl, Velux osv.)');
|
||
|
||
} catch (error) {
|
||
console.error('\n❌ Fejl under oprettelse:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Kør scriptet
|
||
if (require.main === module) {
|
||
main();
|
||
}
|
||
|
||
module.exports = {
|
||
createTagrendePakke,
|
||
createNedløbPakke,
|
||
createSpærPakke,
|
||
createB7Pakke,
|
||
createRygningB7Pakke,
|
||
createTaghætteB7Pakke
|
||
};
|