All files / services quoteTemplateService.js

0% Statements 0/64
0% Branches 0/24
0% Functions 0/14
0% Lines 0/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193                                                                                                                                                                                                                                                                                                                                                                                                 
const fs = require('fs');
const path = require('path');
 
class QuoteTemplateService {
  constructor() {
    this.templatePath = path.join(__dirname, '../../../backend/templates/quote-template.html');
    this.logoPath = path.join(__dirname, '../../../frontend/firmadata/firmabilleder');
  }
 
  // Convert image to base64
  async imageToBase64(imagePath) {
    try {
      const imageBuffer = fs.readFileSync(imagePath);
      return imageBuffer.toString('base64');
    } catch (error) {
      console.error('Error converting image to base64:', error);
      return '';
    }
  }
 
  // Load and encode all logos
  async loadLogos() {
    const logos = {
      mikhaelLogo: await this.imageToBase64(path.join(this.logoPath, 'MIKHOL_logo.png')),
      bygGarantiLogo: await this.imageToBase64(path.join(this.logoPath, 'byggarenti_logo.png')),
      eliteLogo: await this.imageToBase64(path.join(this.logoPath, 'elitehaandvaerker_logo.png'))
    };
    
    return logos;
  }
 
  // Generate work description based on materials and geometry
  generateWorkDescription(data) {
    const { project, geometry, materials } = data;
    let description = [];
 
    // Standard start
    description.push('Ansøgning om anvisningsplads for asbest affald udføres.');
    description.push('Stillads opstilles til opgaven');
 
    // Material-based descriptions
    if (materials.some(m => m.material_name?.toLowerCase().includes('tagplade'))) {
      description.push('Eksisterende B7 plader demonteres og håndteres med teleskoplæsser');
    }
 
    if (materials.some(m => m.material_name?.toLowerCase().includes('lægte'))) {
      description.push('Lægter demonteres, og nye godkendte T1 taglægter monteres.');
    }
 
    description.push('Tagflader afdækkes med presenninger i perioden inden nye plader monteres.');
 
    if (geometry?.roof_type === 'skraat_tag') {
      description.push('Inddækninger til skorsten demonteres og nye monteres efter forskrifter.');
      description.push('Ventileret tagfod og rygning udføres.');
    }
 
    if (materials.some(m => m.material_name?.toLowerCase().includes('rende'))) {
      description.push('Tagrender udskiftes til nye Plastmo aluzink 11" tagrender og nye rendejern monteres samt 2 nedløb.');
    }
 
    if (materials.some(m => m.material_name?.toLowerCase().includes('tagplade'))) {
      description.push('Nye sorte B7 sort/blå Cembrit tagplader leveres og monteres efter forskrifter.');
      description.push('Rygnings plader leveres og monteres som ventileret med DE flex profiler.');
    }
 
    if (materials.some(m => m.material_name?.toLowerCase().includes('taghætte'))) {
      description.push('Taghætter udskiftes til nye Cembrit 3 stk.');
    }
 
    description.push('sternbrædder og vindskeder skiftes til nye sortmalede i samme dimension som eksisterende');
 
    return description.join('\n');
  }
 
  // Generate HTML quote
  async generateHtmlQuote(data) {
    try {
      // Load template
      const template = fs.readFileSync(this.templatePath, 'utf8');
      
      // Load logos
      const logos = await this.loadLogos();
      
      // Prepare data
      const { project, geometry, labor, materials, totals } = data;
      const quoteNumber = `${Date.now().toString().slice(-4)}`;
      const currentDate = new Date().toLocaleDateString('da-DK');
 
      // Company information
      const COMPANY_INFO = {
        name: "Tømrer- og Snedkermester Mikael Holck ApS",
        phone: "42 46 81 10",
        cvr: "38178059",
        address: "Hornumvej 7",
        city: "4600 Køge",
        bank: "Nordea",
        email: "mikael@holck.dk",
        account: "2558 6284200630",
        website: "www.mikaelholck.dk"
      };
 
      // Format materials and labor items
      const items = [];
      
      // Add labor
      if (labor && labor.length > 0) {
        const workDescription = geometry?.roof_type === 'skraat_tag' ? 'Tag m²' : 'Arbejdsløn';
        items.push({
          description: workDescription,
          amount: totals.laborTotal.toLocaleString('da-DK', {minimumFractionDigits: 2, maximumFractionDigits: 2})
        });
      }
 
      // Add materials
      materials.forEach(m => {
        const amount = (parseFloat(m.unit_price) * parseFloat(m.quantity));
        items.push({
          description: m.material_name,
          amount: amount.toLocaleString('da-DK', {minimumFractionDigits: 2, maximumFractionDigits: 2})
        });
      });
 
      // Template variables
      const templateVars = {
        // Logos
        ...logos,
        
        // Quote info
        quoteNumber,
        quoteDate: currentDate,
        customerNumber: project.id,
        
        // Customer info
        customerName: project.customer_name,
        customerAddress: project.customer_email || '',
        
        // Project info
        projectAddress: project.project_address || `${project.customer_name} adresse`,
        projectTitle: project.project_name,
        workDescription: this.generateWorkDescription(data),
        
        // Items - convert to simple replacement
        itemsHtml: items.map(item => 
          `<tr><td>${item.description}</td><td>${item.amount}</td></tr>`
        ).join(''),
        
        // Totals
        subtotal: totals.subtotal.toLocaleString('da-DK', {minimumFractionDigits: 2, maximumFractionDigits: 2}),
        vat: totals.vat.toLocaleString('da-DK', {minimumFractionDigits: 2, maximumFractionDigits: 2}),
        total: totals.total.toLocaleString('da-DK', {minimumFractionDigits: 2, maximumFractionDigits: 2}),
        
        // Company info
        companyName: COMPANY_INFO.name,
        companyAddress: COMPANY_INFO.address,
        companyCity: COMPANY_INFO.city,
        companyPhone: COMPANY_INFO.phone,
        companyCvr: COMPANY_INFO.cvr,
        companyBank: COMPANY_INFO.bank,
        companyEmail: COMPANY_INFO.email,
        companyAccount: COMPANY_INFO.account,
        companyWebsite: COMPANY_INFO.website
      };
 
      // Simple template replacement (since we don't have handlebars)
      let html = template;
      
      // Replace all template variables
      Object.keys(templateVars).forEach(key => {
        const regex = new RegExp(`{{${key}}}`, 'g');
        html = html.replace(regex, templateVars[key] || '');
      });
 
      // Handle items loop manually
      const itemsSection = items.map(item => 
        `            <tr>
                <td>${item.description}</td>
                <td>${item.amount}</td>
            </tr>`
      ).join('\n');
      
      html = html.replace('            {{#each items}}\n            <tr>\n                <td>{{this.description}}</td>\n                <td>{{this.amount}}</td>\n            </tr>\n            {{/each}}', itemsSection);
 
      return html;
      
    } catch (error) {
      console.error('Error generating HTML quote:', error);
      throw error;
    }
  }
}
 
module.exports = QuoteTemplateService;