- Added a comprehensive manual testing guide for the countdown functionality in `test_countdown_manual.md`. - Developed a detailed Selenium test suite in `test_countdown_selenium.py` to verify countdown behavior, including server accessibility, login, countdown progression, and UI elements. - Created a minimal Selenium test version in `test_countdown_selenium_minimal.py` for quick checks without complex setup. - Introduced a verification script in `test_countdown_verification.py` to analyze code correctness, including server checks, build file existence, and countdown logic.
260 lines
9.4 KiB
JavaScript
260 lines
9.4 KiB
JavaScript
require('dotenv').config();
|
|
const mysql = require('mysql2/promise');
|
|
const axios = require('axios');
|
|
|
|
async function getMaterialByName(allMaterials, searchTerms) {
|
|
for (const term of searchTerms) {
|
|
const found = allMaterials.find(m =>
|
|
m.name && m.name.toLowerCase().includes(term.toLowerCase()) &&
|
|
m.price && parseFloat(m.price) > 0
|
|
);
|
|
if (found) return found;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function createSpecialPackage(conn, allMaterials, packageData) {
|
|
const [existing] = await conn.execute(
|
|
'SELECT id FROM material_packages WHERE name = ? AND is_active = 1',
|
|
[packageData.name]
|
|
);
|
|
|
|
if (existing.length > 0) {
|
|
console.log(` ⏭️ Pakke "${packageData.name}" findes allerede`);
|
|
return;
|
|
}
|
|
|
|
const [result] = await conn.execute(
|
|
`INSERT INTO material_packages
|
|
(name, description, category, difficulty_level, estimated_hours, hourly_rate,
|
|
installation_notes, created_by, total_estimated_price, is_active, is_template,
|
|
area_based, time_per_sqm, auto_calculated)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
packageData.name,
|
|
packageData.description,
|
|
'Tag',
|
|
packageData.difficulty || 'mellem',
|
|
packageData.hours,
|
|
packageData.hourlyRate || 580,
|
|
packageData.notes,
|
|
'System',
|
|
0,
|
|
1, 1, 1,
|
|
packageData.timePerSqm,
|
|
1
|
|
]
|
|
);
|
|
|
|
const packageId = result.insertId;
|
|
console.log(` ✓ ${packageData.name} (ID ${packageId})`);
|
|
|
|
// Tilføj materialer
|
|
for (const matDef of packageData.materials) {
|
|
const material = await getMaterialByName(allMaterials, matDef.searchTerms);
|
|
if (material) {
|
|
await conn.execute(
|
|
`INSERT INTO package_materials
|
|
(package_id, material_name, material_category, quantity, unit, unit_price,
|
|
total_price, supplier, notes, base_quantity)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
packageId,
|
|
material.name,
|
|
material.category || 'Tag',
|
|
matDef.quantity,
|
|
material.enhed || 'stk',
|
|
parseFloat(material.price),
|
|
matDef.quantity * parseFloat(material.price),
|
|
material.leverandoer || 'Bygma',
|
|
matDef.notes || '',
|
|
matDef.baseQty || matDef.quantity
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
// Tilføj opgaver
|
|
for (const task of packageData.tasks) {
|
|
await conn.execute(
|
|
`INSERT INTO package_tasks
|
|
(package_id, task_name, task_description, estimated_hours, task_order, task_phase)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[packageId, task.name, task.desc, task.hours, task.order, task.phase]
|
|
);
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
const conn = await mysql.createConnection({
|
|
host: process.env.DB_HOST,
|
|
port: process.env.DB_PORT,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD,
|
|
database: process.env.DB_NAME
|
|
});
|
|
|
|
const response = await axios.get('http://localhost:4031/api/materials');
|
|
const allMaterials = response.data.materials || [];
|
|
|
|
console.log('🏗️ OPRETTER SPECIALISEREDE TAGPAKKER\n');
|
|
|
|
// TAGSKIFT - HURTIG (kun plader)
|
|
await createSpecialPackage(conn, allMaterials, {
|
|
name: 'Tagskift B7 - Kun plader (express)',
|
|
description: `Hurtig udskiftning af B7 tagplader - eksisterende lægter bibeholdes.
|
|
|
|
⚡ EXPRESS LØSNING:
|
|
• Kun udskiftning af slidte/beskadigede plader
|
|
• Eksisterende lægter genbruges (hvis OK)
|
|
• Hurtig løsning til presserende behov
|
|
|
|
⏱ TID: 2 timer per 10m²
|
|
💰 TIMEPRIS: 580 kr/time
|
|
|
|
⚠️ KRAV:
|
|
- Eksisterende lægter skal være i god stand
|
|
- Kun egnet til nyere konstruktioner
|
|
- Spær skal være OK`,
|
|
hours: 2.0,
|
|
timePerSqm: 0.2,
|
|
difficulty: 'let',
|
|
notes: 'Hurtig løsning hvor kun plader skiftes. Kræver OK lægter.',
|
|
materials: [
|
|
{ searchTerms: ['B7 tagplader'], quantity: 11, baseQty: 11, notes: 'Med 10% reserve' },
|
|
{ searchTerms: ['Skruer til tagplader', 'tagskrue'], quantity: 160, baseQty: 16, notes: 'Rustfri skruer' }
|
|
],
|
|
tasks: [
|
|
{ name: 'Inspektion', desc: 'Kontrollere lægter og konstruktion', hours: 0.3, phase: 'forberedelse', order: 1 },
|
|
{ name: 'Nedtagning af gamle plader', desc: 'Fjerne slidte B7 plader', hours: 0.5, phase: 'hovedarbejde', order: 1 },
|
|
{ name: 'Montering af nye plader', desc: 'Montere nye B7 plader', hours: 1.0, phase: 'hovedarbejde', order: 2 },
|
|
{ name: 'Kontrol', desc: 'Kvalitetskontrol', hours: 0.2, phase: 'inspektion', order: 1 }
|
|
]
|
|
});
|
|
|
|
// TAG REPARATION - MINDRE SKADER
|
|
await createSpecialPackage(conn, allMaterials, {
|
|
name: 'Tag Reparation - Mindre skader',
|
|
description: `Reparation af mindre tagskader uden komplet udskiftning.
|
|
|
|
🔧 INKLUDERER:
|
|
• Udskiftning af enkelte beskadigede elementer
|
|
• Reparation af lækager
|
|
• Fornyelse af tætninger
|
|
• Mindre spærreparationer
|
|
|
|
⏱ TID: 3-4 timer typisk
|
|
💰 TIMEPRIS: 580 kr/time
|
|
|
|
✓ EGNET TIL:
|
|
- Enkelte utætte steder
|
|
- Revnede/ødelagte enkelte plader
|
|
- Mindre lækager`,
|
|
hours: 3.5,
|
|
timePerSqm: 0,
|
|
difficulty: 'let',
|
|
notes: 'Reparation uden komplet udskiftning. Pris per job, ikke per m².',
|
|
materials: [
|
|
{ searchTerms: ['B7 tagplader'], quantity: 3, baseQty: 3, notes: 'Reserve plader' },
|
|
{ searchTerms: ['Skruer til tagplader'], quantity: 50, baseQty: 50, notes: 'Diverse skruer' },
|
|
{ searchTerms: ['Tagpap underlag', 'pap'], quantity: 2, baseQty: 2, notes: 'Til tætning' },
|
|
{ searchTerms: ['Lægter 38x63'], quantity: 5, baseQty: 5, notes: 'Til mindre reparationer' }
|
|
],
|
|
tasks: [
|
|
{ name: 'Fejlfinding', desc: 'Lokalisere skader og lækager', hours: 0.5, phase: 'forberedelse', order: 1 },
|
|
{ name: 'Reparation af skader', desc: 'Udbedre identificerede skader', hours: 2.5, phase: 'hovedarbejde', order: 1 },
|
|
{ name: 'Tætning', desc: 'Tætne alle samlinger', hours: 0.5, phase: 'afslutning', order: 1 }
|
|
]
|
|
});
|
|
|
|
// GRØNT TAG / SEDUM
|
|
await createSpecialPackage(conn, allMaterials, {
|
|
name: 'Grønt Tag - Sedum (fladtag)',
|
|
description: `Etablering af grønt sedum-tag på fladtag. Miljøvenlig og moderne løsning.
|
|
|
|
🌱 INKLUDERER:
|
|
• Tagisolering 200mm
|
|
• Vandtæt membran
|
|
• Dræneringslag
|
|
• Substrat til sedum
|
|
• Sedum planter/måtter
|
|
|
|
⏱ TID: 10 timer per 10m²
|
|
💰 TIMEPRIS: 580 kr/time
|
|
|
|
✨ FORDELE:
|
|
- Isolerende effekt
|
|
- Nedsætter regnvand-afstrømning
|
|
- Forbedrer bymiljø
|
|
- Moderne look`,
|
|
hours: 10.0,
|
|
timePerSqm: 1.0,
|
|
difficulty: 'svær',
|
|
notes: 'Grønt tag kræver speciel konstruktion. Kun fladtag/lav hældning.',
|
|
materials: [
|
|
{ searchTerms: ['Tagisolering 200mm', 'isolering'], quantity: 10, baseQty: 10, notes: 'Isolering' },
|
|
{ searchTerms: ['Tagpap'], quantity: 12, baseQty: 12, notes: 'Vandtæt membran' },
|
|
{ searchTerms: ['Lægter'], quantity: 15, baseQty: 15, notes: 'Til dræneringslag' }
|
|
],
|
|
tasks: [
|
|
{ name: 'Forberedelse af underlag', desc: 'Klargøre og kontrollere fladtag', hours: 1.5, phase: 'forberedelse', order: 1 },
|
|
{ name: 'Isolering', desc: 'Montere tagisolering', hours: 2.0, phase: 'hovedarbejde', order: 1 },
|
|
{ name: 'Membran og tætning', desc: 'Lægge vandtæt membran', hours: 2.5, phase: 'hovedarbejde', order: 2 },
|
|
{ name: 'Dræneringslag', desc: 'Etablere drænering', hours: 1.5, phase: 'hovedarbejde', order: 3 },
|
|
{ name: 'Substrat', desc: 'Udlægge substrat til planter', hours: 1.5, phase: 'hovedarbejde', order: 4 },
|
|
{ name: 'Beplantning', desc: 'Etablere sedum', hours: 1.0, phase: 'afslutning', order: 1 }
|
|
]
|
|
});
|
|
|
|
// TAGTERRASSE
|
|
await createSpecialPackage(conn, allMaterials, {
|
|
name: 'Tagterrasse - Komplet anlæg',
|
|
description: `Etablering af tagterrasse på fladtag med trægulv.
|
|
|
|
🏡 INKLUDERER:
|
|
• Tagisolering
|
|
• Vandtæt membran
|
|
• Underlagskonstruktion
|
|
• Trægulv (trykimprægneret)
|
|
• Gelænder (efter behov)
|
|
|
|
⏱ TID: 12 timer per 10m²
|
|
💰 TIMEPRIS: 580 kr/time
|
|
|
|
✓ PERFEKT TIL:
|
|
- Byboliger
|
|
- Penthouse
|
|
- Udnyttelse af fladtag`,
|
|
hours: 12.0,
|
|
timePerSqm: 1.2,
|
|
difficulty: 'svær',
|
|
notes: 'Tagterrasse kræver solid konstruktion og vandtætning.',
|
|
materials: [
|
|
{ searchTerms: ['Tagisolering 200mm'], quantity: 10, baseQty: 10, notes: 'Isolering' },
|
|
{ searchTerms: ['Tagpap'], quantity: 12, baseQty: 12, notes: 'Vandtæt membran' },
|
|
{ searchTerms: ['Lægter 38x63 trykimprægneret', 'lægte trykim'], quantity: 30, baseQty: 30, notes: 'Underlagskonstruktion' },
|
|
{ searchTerms: ['SPÅNSKRUE', 'skrue'], quantity: 2, baseQty: 2, notes: 'Beslag og skruer' }
|
|
],
|
|
tasks: [
|
|
{ name: 'Forberedelse og planlægning', desc: 'Opmåle og planlægge terrasse', hours: 1.5, phase: 'forberedelse', order: 1 },
|
|
{ name: 'Isolering', desc: 'Montere isolering', hours: 2.0, phase: 'hovedarbejde', order: 1 },
|
|
{ name: 'Membran', desc: 'Lægge vandtæt membran', hours: 2.5, phase: 'hovedarbejde', order: 2 },
|
|
{ name: 'Underlagskonstruktion', desc: 'Bygge bærende konstruktion', hours: 3.0, phase: 'hovedarbejde', order: 3 },
|
|
{ name: 'Gulvlægning', desc: 'Montere trægulv', hours: 2.5, phase: 'hovedarbejde', order: 4 },
|
|
{ name: 'Afslutning', desc: 'Montere afslutninger og finish', hours: 0.5, phase: 'afslutning', order: 1 }
|
|
]
|
|
});
|
|
|
|
console.log('\n✅ FÆRDIG! Specialpakker oprettet\n');
|
|
|
|
// Vis samlet oversigt
|
|
const [allPackages] = await conn.execute(
|
|
'SELECT id, name, category FROM material_packages WHERE id >= 126 ORDER BY id'
|
|
);
|
|
|
|
console.log('📊 ALLE NYE TAGPAKKER:\n');
|
|
allPackages.forEach(p => console.log(' • ' + p.name + ' (ID ' + p.id + ')'));
|
|
|
|
await conn.end();
|
|
})();
|