Average Cost Per Employee (${overallBudgetPeriod}): ${currency}${(totalEmployees > 0 ? grandTotalCostForPeriod / totalEmployees : 0).toFixed(2)}
`;
if (totalLocations > 0) {
html += `
Average Cost Per Location (${overallBudgetPeriod}): ${currency}${(grandTotalCostForPeriod / totalLocations).toFixed(2)}
`;
}
html += '
';
html += '
Detailed Breakdown:
';
html += '
| Category | Expense Item | Basis & Frequency | Est. Cost for Period |
';
if (allItems.length === 0) {
html += '| No expenses entered. |
';
} else {
const groupedByCategory = allItems.reduce((acc, item) => {
(acc[item.category] = acc[item.category] || []).push(item);
return acc;
}, {});
for (const category in groupedByCategory) {
html += ``;
let categorySubtotal = 0;
groupedByCategory[category].forEach(item => {
html += `
|
${item.name} |
${item.basisDetails} |
${currency}${item.calculatedCost.toFixed(2)} |
`;
categorySubtotal += item.calculatedCost;
});
html += `| Subtotal for ${category}: | ${currency}${categorySubtotal.toFixed(2)} |
`;
}
}
html += `| GRAND TOTAL: | ${currency}${grandTotalCostForPeriod.toFixed(2)} |
`;
html += '
';
summaryOutput.innerHTML = html;
downloadPdfButton.disabled = allItems.length === 0;
}
// PDF Download
downloadPdfButton.addEventListener('click', function () {
if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library is not loaded.'); return;
}
const { jsPDF } = jspdf;
const doc = new jsPDF('p');
const currency = currencySymbolInput.value || '$';
const overallBudgetPeriod = getVal('wscb-budget-period');
const totalEmployees = getNum('wscb-num-employees', true);
const totalLocations = getNum('wscb-num-locations', true);
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--wscb-primary-color').trim();
const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--wscb-secondary-color').trim();
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--wscb-text-color').trim();
const buttonTextColor = getComputedStyle(document.documentElement).getPropertyValue('--wscb-button-text-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text('Workplace Safety & Compliance Budget', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
doc.setFontSize(12);
doc.setTextColor(textColor);
let lastY = 30;
doc.text(`Business Name: ${getVal('wscb-business-name') || 'N/A'}`, 14, lastY); lastY += 7;
doc.text(`Budget Period: ${overallBudgetPeriod}`, 14, lastY); lastY += 7;
doc.text(`Total Employees: ${totalEmployees}`, 14, lastY); lastY += 7;
if (totalLocations > 0) { doc.text(`Total Locations: ${totalLocations}`, 14, lastY); lastY += 7; }
doc.text(`Currency: ${currency}`, 14, lastY); lastY += 7;
const industry = getVal('wscb-industry-type');
if(industry) { doc.text(`Industry: ${industry}`, 14, lastY); lastY+=7;}
lastY += 3; // Extra space before summary totals
const allItemsPdf = []; // Re-collect for PDF
let grandTotalCostForPeriodPdf = 0;
// Simplified re-collection logic - assuming generateBudgetSummary() populates 'allItems' correctly
// For a truly robust PDF, recalculate everything here like in generateBudgetSummary()
document.querySelectorAll('.wscb-expense-item').forEach(itemEl => {
const itemName = itemEl.querySelector('.wscb-item-name').value || 'Unnamed Item';
const itemCategory = itemEl.dataset.itemCategory;
const costAmount = getNum(itemEl.querySelector('.wscb-item-cost-amount').value);
const costBasis = itemEl.querySelector('.wscb-item-cost-basis').value;
const itemFrequency = itemEl.querySelector('.wscb-item-frequency').value;
let unitsParticipantsInput = itemEl.querySelector('.wscb-item-units-participants').value;
let numUnitsOrRate = 0; let isRate = false;
if (costBasis === 'perUnitParticipant') {
if (unitsParticipantsInput.includes('%')) { numUnitsOrRate = getNum(unitsParticipantsInput.replace('%','')) / 100; isRate = true; }
else { numUnitsOrRate = getNum(unitsParticipantsInput); }
}
let baseMultiplier = 1;
if (costBasis === 'perEmployee') baseMultiplier = totalEmployees;
else if (costBasis === 'perLocation') baseMultiplier = totalLocations;
else if (costBasis === 'perUnitParticipant') baseMultiplier = isRate ? totalEmployees * numUnitsOrRate : numUnitsOrRate;
let itemAnnualCost = costAmount;
if (itemFrequency === 'Monthly') itemAnnualCost = costAmount * 12;
else if (itemFrequency === 'Quarterly') itemAnnualCost = costAmount * 4;
else if (itemFrequency === 'Annually') itemAnnualCost = costAmount * 1;
else if (itemFrequency === 'Biennially') itemAnnualCost = costAmount / 2;
else if (itemFrequency === 'Triennially') itemAnnualCost = costAmount / 3;
else if (itemFrequency === 'OneTime') {
if (overallBudgetPeriod === 'Monthly') itemAnnualCost = costAmount * 12;
else if (overallBudgetPeriod === 'Quarterly') itemAnnualCost = costAmount * 4;
else itemAnnualCost = costAmount;
}
let costForThisItemForPeriod = 0;
if (overallBudgetPeriod === 'Monthly') costForThisItemForPeriod = (itemAnnualCost / 12) * baseMultiplier;
else if (overallBudgetPeriod === 'Quarterly') costForThisItemForPeriod = (itemAnnualCost / 4) * baseMultiplier;
else if (overallBudgetPeriod === 'Annually') costForThisItemForPeriod = itemAnnualCost * baseMultiplier;
if (itemFrequency === 'OneTime' && costBasis === 'totalSetAmount') {
costForThisItemForPeriod = costAmount;
}
let basisDetails = `${currency}${costAmount.toFixed(2)}`;
if(costBasis !== 'totalSetAmount') basisDetails += ` / ${costBasis.replace('per','').toLowerCase()}`;
basisDetails += ` (${itemFrequency})`;
if(costBasis === 'perUnitParticipant') basisDetails += ` for ${unitsParticipantsInput}${isRate ? ' of emp.' : ' items'}`;
allItemsPdf.push({ category: itemCategory, name: itemName, basisDetails: basisDetails, calculatedCost: costForThisItemForPeriod });
grandTotalCostForPeriodPdf += costForThisItemForPeriod;
});
doc.setFontSize(10);
doc.text(`Total Estimated Budget (${overallBudgetPeriod}): ${currency}${grandTotalCostForPeriodPdf.toFixed(2)}`, 14, lastY); lastY += 7;
doc.text(`Average Cost Per Employee (${overallBudgetPeriod}): ${currency}${(totalEmployees > 0 ? grandTotalCostForPeriodPdf / totalEmployees : 0).toFixed(2)}`, 14, lastY); lastY += 7;
if (totalLocations > 0) {
doc.text(`Average Cost Per Location (${overallBudgetPeriod}): ${currency}${(grandTotalCostForPeriodPdf / totalLocations).toFixed(2)}`, 14, lastY); lastY += 7;
}
lastY += 3;
const tableBody = [];
const groupedByCategoryPdf = allItemsPdf.reduce((acc, item) => {
(acc[item.category] = acc[item.category] || []).push(item);
return acc;
}, {});
for (const category in groupedByCategoryPdf) {
tableBody.push([{ content: category, colSpan: 3, styles: { fontStyle: 'bold', fillColor: '#f0f0f0' } }]);
let categorySubtotalPdf = 0;
groupedByCategoryPdf[category].forEach(item => {
tableBody.push([item.name, item.basisDetails, `${currency}${item.calculatedCost.toFixed(2)}`]);
categorySubtotalPdf += item.calculatedCost;
});
tableBody.push([
{ content: `Subtotal for ${category}`, colSpan: 2, styles: { halign: 'right', fontStyle: 'bold'} },
{ content: `${currency}${categorySubtotalPdf.toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold' } }
]);
}
tableBody.push([
{ content: `GRAND TOTAL`, colSpan: 2, styles: { halign: 'right', fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } },
{ content: `${currency}${grandTotalCostForPeriodPdf.toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } }
]);
doc.autoTable({
startY: lastY,
head: [['Expense Item', 'Basis & Frequency', `Est. Cost for ${overallBudgetPeriod}`]],
body: tableBody,
theme: 'grid',
headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 9 },
styles: { fontSize: 8, cellPadding: 2 },
columnStyles: { 2: { halign: 'right' } },
didDrawCell: (data) => {
if (data.cell.raw && data.cell.raw.colSpan === 3) { // Category Header Row
// Handled by autotable's colSpan styling
}
}
});
doc.save(`Workplace_Safety_Budget_${getVal('wscb-business-name').replace(/\s+/g, '_') || 'Report'}.pdf`);
});
// Initial setup for empty states
const initialCategoriesContainers = [
"wscb-training-items-container", "wscb-ppe-items-container", "wscb-equipment-items-container",
"wscb-inspections-items-container", "wscb-health-env-items-container", "wscb-consultancy-items-container",
"wscb-software-items-container", "wscb-emergency-items-container", "wscb-custom-items-container"
];
initialCategoriesContainers.forEach(id => checkEmptyState(id));
showTab(0);
updateAllCurrencyPrefixes();
});