backups/2026-03-28-pre-sidebar-refactor/konfigurator-solar.js

Code: DEV-63421496 Size: 18.8 KB Lines: 364 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/backups/2026-03-28-pre-sidebar-refactor/konfigurator-solar.js

Task / Comment

Open report form
// konfigurator-solar.js - Solar configurator

// === SOLAR CONFIGURATOR ===
// Pricing from SimCRM: full price 116,250 for 8 panels (before green tech deduction)
// Green tech = 20% → customer pays 93,000 (=116,250 × 0.80)
const SOL_FULL_BASE_PRICE = 116250; // Full price before deduction for 8 panels
const SOL_BASE_PANELS = 8;
const SOL_FULL_PRICE_PER_PANEL = 3688; // Full price per extra panel
const SOL_MATERIAL_RATIO = 0.65; // 65% material, 35% installation
const SOL_LABOR_RATIO = 0.35;
const SOL_PANEL_AREA = 1.87; // m² per panel
const SOL_ELECTRICITY_PRICE = 1.50; // kr/kWh
const SOL_SELF_USE = 0.70;
const SOL_SELL_PRICE = 0.50; // kr/kWh for surplus
const SOL_FINANCE_RATE = 0.049; // 4.9% annual
let SOL_FINANCE_YEARS = 15;
const GREEN_TECH_RATE = 0.20; // 20% of total (material+labor+battery+charger)
const ROT_RATE = 0.30; // 30% of labor only

let solSelectedPanel = null;
let solSelectedBattery = 0;
let solSelectedBatteryPrice = 0;
let solSelectedCharger = null;
let solSelectedChargerPrice = 0;
let solOwnerCount = 1;
let solGreenTechMax = 50000;
let solDeductionType = 'green'; // 'green', 'rot', 'both', 'none'

// Current calc state for Affär
let currentCalcState = {};
let kalkylOrigin = 'konfigurator';

// EV chargers from SimCRM
const evChargers = [
    {brand:'Garo',model:'22kW',price:26000,desc:'Inkl. lastbalans'},
    {brand:'Zaptec',model:'GO',price:26000,desc:'Inkl. lastbalans'},
    {brand:'Zaptec',model:'PRO 22kW',price:33000,desc:'Inkl. lastbalans'},
    {brand:'Charge Amps',model:'Aura 22kW (2 uttag)',price:40000,desc:'Inkl. lastbalans'}
];

// Battery options from SimCRM
const batteryOptions = [
    {kwh:5,price:35000,label:'5 kWh'},
    {kwh:10,price:50000,label:'10 kWh'},
    {kwh:15,price:65000,label:'15 kWh'},
    {kwh:20,price:85000,label:'20 kWh'}
];

function initSolarConfig(){ var _lbl=document.getElementById('cfgFileLabel');if(_lbl)_lbl.textContent='konfigurator-solar.js';
    // Get solar panels from catalog
    const panels = catalogProducts.filter(p => p.watt && p.watt > 0 && p.cat === 'solceller');
    const container = document.getElementById('solarPanelCards');
    if(!panels.length){
        // Retry after catalog loads
        container.innerHTML='<div style="padding:20px;color:#94a3b8;text-align:center"><div class="spinner" style="display:inline-block;width:20px;height:20px;border:2px solid #e5e7eb;border-top-color:#024550;border-radius:50%;animation:bgspin .6s linear infinite"></div><p style="margin-top:6px;font-size:12px">Laddar paneler...</p></div>';
        if(!window._solarRetryCount) window._solarRetryCount = 0;
        if(window._solarRetryCount < 10){
            window._solarRetryCount++;
            setTimeout(initSolarConfig, 500);
        } else {
            container.innerHTML='<div style="padding:20px;color:#94a3b8;text-align:center">Inga solpaneler i katalogen. Lägg till via Produkter.</div>';
        }
        // Still run updateSolarCalc with defaults so price shows
        updateSolarCalc();
        return;
    }
    window._solarRetryCount = 0;

    // Calculate per-panel system cost (inkl. arbete/installation)
    const avgSystemPricePerPanel = Math.round(SOL_FULL_BASE_PRICE / SOL_BASE_PANELS);

    container.innerHTML = panels.map((p,i) => {
        const sel = i===0 ? 'border-color:#3b82f6;background:#f0f9ff' : 'border-color:#e5e7eb;background:#fff';
        const dot = i===0 ? '<div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div>' : '';
        const greenTag = p.greenTechEligible ? '<span style="background:#dcfce7;color:#166534;font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;margin-left:6px">Grönt avdrag</span>' : '';
        return '<div class="sol-panel-card" data-panel-id="'+p.id+'" onclick="selectPanel(this,\''+p.id+'\')" style="padding:14px 16px;border:2px solid;border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px;'+sel+'">'
            +'<div style="width:20px;height:20px;border:2px solid '+(i===0?'#3b82f6':'#cbd5e1')+';border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0">'+dot+'</div>'
            +(p.img?'<img src="'+p.img+'" style="width:48px;height:48px;object-fit:cover;border-radius:6px;flex-shrink:0">':'<div style="width:48px;height:48px;background:#f1f5f9;border-radius:6px;flex-shrink:0"></div>')
            +'<div style="flex:1"><div style="font-weight:600;font-size:14px">'+p.name+greenTag+'</div><div style="font-size:12px;color:#64748b">'+p.watt+'W per panel'+(p.desc?' — '+p.desc:'')+'</div></div>'
            +'<div style="text-align:right"><div style="font-weight:700;font-size:14px;color:#1a1a1a">'+avgSystemPricePerPanel.toLocaleString('sv-SE')+' kr/st</div><div style="font-size:10px;color:#94a3b8">inkl. installation</div></div>'
            +'</div>';
    }).join('');
    if(panels.length) solSelectedPanel = panels[0];

    // Render battery options
    const batContainer = document.getElementById('solarBatteryCards');
    batContainer.innerHTML = '<div class="sol-addon-card" data-battery="0" onclick="selectBattery(this)" style="padding:14px 16px;border:2px solid #3b82f6;border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px;background:#f0f9ff">'
        +'<div style="width:20px;height:20px;border:2px solid #3b82f6;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div></div>'
        +'<div style="flex:1"><div style="font-weight:600;font-size:14px">Inget batteri</div></div>'
        +'<div style="font-weight:700;font-size:14px;color:#1a1a1a">0 kr</div></div>'
        + batteryOptions.map(b => '<div class="sol-addon-card" data-battery="'+b.kwh+'" data-price="'+b.price+'" onclick="selectBattery(this)" style="padding:14px 16px;border:2px solid #e5e7eb;border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px">'
            +'<div style="width:20px;height:20px;border:2px solid #cbd5e1;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0"></div>'
            +'<div style="flex:1"><div style="font-weight:600;font-size:14px">'+b.label+' batteri</div><div style="font-size:12px;color:#64748b">Lagra överskottsel'+
            '<span style="background:#dcfce7;color:#166534;font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;margin-left:6px">Grönt avdrag</span></div></div>'
            +'<div style="font-weight:700;font-size:14px;color:#1a1a1a">'+b.price.toLocaleString('sv-SE')+' kr</div></div>').join('');

    // Render EV charger options
    const chgContainer = document.getElementById('solarChargerCards');
    chgContainer.innerHTML = '<div class="sol-addon-card" data-charger="0" onclick="selectCharger(this)" style="padding:14px 16px;border:2px solid #3b82f6;border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px;background:#f0f9ff">'
        +'<div style="width:20px;height:20px;border:2px solid #3b82f6;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div></div>'
        +'<div style="flex:1"><div style="font-weight:600;font-size:14px">Ingen laddare</div></div>'
        +'<div style="font-weight:700;font-size:14px;color:#1a1a1a">0 kr</div></div>'
        + evChargers.map(c => '<div class="sol-addon-card" data-charger="'+c.brand+'-'+c.model+'" data-price="'+c.price+'" onclick="selectCharger(this)" style="padding:14px 16px;border:2px solid #e5e7eb;border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px">'
            +'<div style="width:20px;height:20px;border:2px solid #cbd5e1;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0"></div>'
            +'<div style="flex:1"><div style="font-weight:600;font-size:14px">'+c.brand+' '+c.model+'</div><div style="font-size:12px;color:#64748b">'+c.desc+
            '<span style="background:#dcfce7;color:#166534;font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;margin-left:6px">Grönt avdrag</span></div></div>'
            +'<div style="font-weight:700;font-size:14px;color:#1a1a1a">'+c.price.toLocaleString('sv-SE')+' kr</div></div>').join('');

    // Sync owner count from customer data if available
    if(pendingKalkylCustomer){
        const ot = pendingKalkylCustomer.ownerType;
        if(ot === '2' || ot === 2) setOwnerCount(2);
        else if(ot === 'brf') { setOwnerCount(1); document.getElementById('solGreenSlider').value = 0; updateGreenSlider(); }
    }

    updateSolarCalc();
}

function selectPanel(el, id){
    const p = catalogProducts.find(x => x.id === id);
    if(!p) return;
    solSelectedPanel = p;
    document.querySelectorAll('.sol-panel-card').forEach(c => {
        const isSel = c.dataset.panelId === id;
        c.style.borderColor = isSel ? '#3b82f6' : '#e5e7eb';
        c.style.background = isSel ? '#f0f9ff' : '#fff';
        const dot = c.querySelector('div > div');
        dot.style.borderColor = isSel ? '#3b82f6' : '#cbd5e1';
        dot.innerHTML = isSel ? '<div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div>' : '';
    });
    updateSolarCalc();
}

function selectBattery(el){
    const kwh = parseInt(el.dataset.battery)||0;
    solSelectedBattery = kwh;
    solSelectedBatteryPrice = parseInt(el.dataset.price)||0;
    document.querySelectorAll('#solarBatteryCards .sol-addon-card').forEach(c => {
        const isSel = c === el;
        c.style.borderColor = isSel ? '#3b82f6' : '#e5e7eb';
        c.style.background = isSel ? '#f0f9ff' : '#fff';
        const dot = c.firstElementChild;
        dot.style.borderColor = isSel ? '#3b82f6' : '#cbd5e1';
        dot.innerHTML = isSel ? '<div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div>' : '';
    });
    updateSolarCalc();
}

function selectCharger(el){
    const val = el.dataset.charger;
    solSelectedCharger = val === '0' ? null : val;
    solSelectedChargerPrice = parseInt(el.dataset.price)||0;
    document.querySelectorAll('#solarChargerCards .sol-addon-card').forEach(c => {
        const isSel = c === el;
        c.style.borderColor = isSel ? '#3b82f6' : '#e5e7eb';
        c.style.background = isSel ? '#f0f9ff' : '#fff';
        const dot = c.firstElementChild;
        dot.style.borderColor = isSel ? '#3b82f6' : '#cbd5e1';
        dot.innerHTML = isSel ? '<div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div>' : '';
    });
    updateSolarCalc();
}

function updateSolarCalc(){
    const count = parseInt(document.getElementById('solPanelCount').value)||14;
    document.getElementById('solPanelCountVal').textContent = count;

    const watt = solSelectedPanel ? solSelectedPanel.watt : 430;
    const kwp = (count * watt / 1000).toFixed(1);
    const roofArea = Math.round(count * SOL_PANEL_AREA);
    const yearlyKwh = Math.round(parseFloat(kwp) * 900);
    document.getElementById('solKwpVal').textContent = kwp + ' kWp';
    document.getElementById('solRoofVal').textContent = roofArea + ' m²';
    document.getElementById('solProdVal').textContent = '~' + yearlyKwh.toLocaleString('sv-SE') + ' kWh';

    // Full system price (before green tech deduction)
    let systemFullPrice;
    if(count <= SOL_BASE_PANELS){
        systemFullPrice = SOL_FULL_BASE_PRICE;
    } else {
        systemFullPrice = SOL_FULL_BASE_PRICE + (count - SOL_BASE_PANELS) * SOL_FULL_PRICE_PER_PANEL;
    }

    // Split into material and labor
    const materialCost = Math.round(systemFullPrice * SOL_MATERIAL_RATIO);
    const laborCost = systemFullPrice - materialCost;

    const batteryPrice = solSelectedBatteryPrice;
    const chargerPrice = solSelectedChargerPrice;
    const subtotal = systemFullPrice + batteryPrice + chargerPrice;

    // Deduction calculation based on type
    let sliderMax = parseInt(document.getElementById('solGreenSlider')?.value);
    if(isNaN(sliderMax)) sliderMax = solGreenTechMax;

    let totalDeduction = 0;
    let deductDetail = '';
    const totalLabor = laborCost; // Labor portion of system price

    if(solDeductionType === 'green'){
        const greenCalc = Math.round(subtotal * GREEN_TECH_RATE);
        totalDeduction = Math.min(greenCalc, sliderMax);
        deductDetail = '20% av ' + fmt(subtotal) + ' = ' + fmt(greenCalc) + (greenCalc > sliderMax ? ' (begränsat till ' + fmt(sliderMax) + ')' : '');
    } else if(solDeductionType === 'rot'){
        const rotCalc = Math.round(totalLabor * ROT_RATE);
        totalDeduction = Math.min(rotCalc, sliderMax);
        deductDetail = '30% av arbete ' + fmt(totalLabor) + ' = ' + fmt(rotCalc) + (rotCalc > sliderMax ? ' (begränsat till ' + fmt(sliderMax) + ')' : '');
    } else if(solDeductionType === 'both'){
        // Green tech on material+battery+charger, ROT on labor
        const greenBase = materialCost + batteryPrice + chargerPrice;
        const greenCalc = Math.round(greenBase * GREEN_TECH_RATE);
        const rotCalc = Math.round(totalLabor * ROT_RATE);
        const bothTotal = greenCalc + rotCalc;
        totalDeduction = Math.min(bothTotal, sliderMax);
        deductDetail = 'Grönt: ' + fmt(greenCalc) + ' + ROT: ' + fmt(rotCalc) + ' = ' + fmt(bothTotal);
    } else {
        totalDeduction = 0;
        deductDetail = 'Inget avdrag valt';
    }

    const total = subtotal - totalDeduction;

    document.getElementById('solPrMaterial').textContent = fmt(materialCost);
    document.getElementById('solPrLabor').textContent = fmt(laborCost);
    document.getElementById('solPrBattery').textContent = batteryPrice > 0 ? fmt(batteryPrice) : '0 kr';
    document.getElementById('solPrCharger').textContent = chargerPrice > 0 ? fmt(chargerPrice) : '0 kr';
    document.getElementById('solPrSubtotal').textContent = fmt(subtotal);
    document.getElementById('solPrGreenTech').textContent = totalDeduction > 0 ? '-' + fmt(totalDeduction) : '0 kr';
    const detailEl = document.getElementById('solDeductDetail');
    if(detailEl) detailEl.textContent = deductDetail;

    const el = document.getElementById('solPrTotal');
    if(el) el.textContent = fmt(total);

    // Monthly financing
    const r = SOL_FINANCE_RATE / 12;
    const n = SOL_FINANCE_YEARS * 12;
    const monthly = total > 0 ? Math.round(total * r * Math.pow(1+r,n) / (Math.pow(1+r,n)-1)) : 0;
    document.getElementById('solPrMonthly').textContent = monthly.toLocaleString('sv-SE') + ' kr/mån';

    // Yearly savings
    const selfUseKwh = yearlyKwh * SOL_SELF_USE;
    const surplusKwh = yearlyKwh * (1 - SOL_SELF_USE);
    const yearlySaving = Math.round(selfUseKwh * SOL_ELECTRICITY_PRICE + surplusKwh * SOL_SELL_PRICE);
    const payback = total > 0 ? (total / yearlySaving).toFixed(1) : 0;
    document.getElementById('solPrSaving').textContent = yearlySaving.toLocaleString('sv-SE') + ' kr/år';
    document.getElementById('solPrPayback').textContent = payback + ' år';

    // Store state for Affär
    currentCalcState = {
        panelId: solSelectedPanel?.id,
        panelName: solSelectedPanel?.name || 'Okänd',
        panelWatt: watt,
        panelCount: count,
        kwp: parseFloat(kwp),
        roofArea,
        yearlyKwh,
        materialCost,
        laborCost,
        systemFullPrice,
        batteryKwh: solSelectedBattery,
        batteryPrice,
        chargerName: solSelectedCharger,
        chargerPrice,
        subtotal,
        greenTechDeduction: totalDeduction,
        deductionType: solDeductionType,
        total,
        monthly,
        yearlySaving,
        payback: parseFloat(payback),
        financeYears: SOL_FINANCE_YEARS,
        financeRate: SOL_FINANCE_RATE,
        ownerCount: solOwnerCount
    };
    // Update shared summary
    if(typeof cfgAddItem==='function') {
        cfgAddItem('solceller', 'Solceller (' + count + ' paneler, ' + kwp + ' kWp)', subtotal, solDeductionType === 'green' ? 'green' : 'none', laborCost/Math.max(subtotal,1));
    }
}

function setDeductionType(type){
    solDeductionType = type;
    const label = document.getElementById('solDeductLabel');
    const ownerSection = document.getElementById('solOwnerSection');
    const sliderSection = document.getElementById('solSliderSection');
    const colors = {green:'#059669', rot:'#2563eb', both:'#7c3aed', none:'#94a3b8'};
    const labels = {green:'GRÖNT TEKNIK-AVDRAG', rot:'ROT-AVDRAG', both:'GRÖNT TEKNIK + ROT', none:'INGET AVDRAG'};
    const col = colors[type] || '#059669';

    if(label){ label.textContent = labels[type] || ''; label.style.color = col; }
    document.getElementById('solPrGreenTech').style.color = col;

    // Show/hide owner + slider for all except 'none'
    if(ownerSection) ownerSection.style.display = type === 'none' ? 'none' : '';
    if(sliderSection) sliderSection.style.display = type === 'none' ? 'none' : '';

    // Update slider accent color
    const slider = document.getElementById('solGreenSlider');
    if(slider) slider.style.accentColor = col;

    // Update button styles
    document.querySelectorAll('.deduct-btn').forEach(b => {
        const dt = b.dataset.dt;
        if(dt === type){
            b.style.background = col; b.style.color = '#fff'; b.style.borderColor = col;
        } else {
            b.style.background = '#fff'; b.style.color = col; b.style.borderColor = '#e5e7eb';
        }
    });

    updateSolarCalc();
}

function setOwnerCount(count){
    solOwnerCount = count;
    const maxPerPerson = 50000;
    const newMax = count * maxPerPerson;
    const slider = document.getElementById('solGreenSlider');
    if(slider){
        slider.max = newMax;
        // If current value exceeds new max, or was at old max, set to new max
        if(parseInt(slider.value) > newMax || parseInt(slider.value) === solGreenTechMax){
            slider.value = newMax;
        }
    }
    solGreenTechMax = newMax;
    document.getElementById('solGreenSliderMax').textContent = newMax.toLocaleString('sv-SE') + ' kr';
    document.querySelectorAll('.owner-btn').forEach(b => {
        const oc = parseInt(b.dataset.oc);
        if(oc === count){
            b.style.background = '#059669'; b.style.color = '#fff'; b.style.borderColor = '#059669';
        } else {
            b.style.background = '#fff'; b.style.color = '#059669'; b.style.borderColor = '#bbf7d0';
        }
    });
    updateGreenSlider();
}

function updateGreenSlider(){
    const val = parseInt(document.getElementById('solGreenSlider').value)||0;
    document.getElementById('solGreenSliderVal').textContent = val.toLocaleString('sv-SE') + ' kr';
    updateSolarCalc();
}

function setFinanceYears(yrs){
    SOL_FINANCE_YEARS = yrs;
    document.querySelectorAll('.fin-yr-btn').forEach(b => {
        const y = parseInt(b.dataset.yr);
        if(y === yrs){
            b.style.background = '#3b82f6';
            b.style.color = '#fff';
            b.style.borderColor = '#3b82f6';
        } else {
            b.style.background = '#fff';
            b.style.color = '#3b82f6';
            b.style.borderColor = '#bfdbfe';
        }
    });
    const info = document.getElementById('solFinanceInfo');
    if(info) info.textContent = '('+yrs+' år, 4.9%)';
    updateSolarCalc();
}