Business Trip Cost Estimator

Trip Overview & Settings

Expense Details

Transportation

$
$
$
$

Accommodation

$

Meals & Per Diems

$

Other Standard Expenses

$
$
$
$
$
$

Custom Expenses

No custom expenses added.

Estimated Cost Summary

Complete trip and expense details in previous tabs to see the summary.

Average Cost Per Traveler: ${currency}${(numTravelers > 0 ? grandTotal / numTravelers : 0).toFixed(2)}

Average Cost Per Day (Overall): ${currency}${(numDays > 0 ? grandTotal / numDays : 0).toFixed(2)}

`; summaryOutput.innerHTML = html; downloadPdfButton.disabled = grandTotal === 0 && detailedExpenses.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 primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-primary-color').trim(); const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-secondary-color').trim(); const textColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-text-color').trim(); const buttonTextColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-button-text-color').trim(); doc.setFontSize(18); doc.setTextColor(primaryColor); doc.text('Business Trip Cost Estimate', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' }); doc.setFontSize(12); doc.setTextColor(textColor); let lastY = 30; doc.text(`Business Name: ${getVal('btce-business-name') || 'N/A'}`, 14, lastY); lastY += 7; doc.text(`Trip Name/Purpose: ${getVal('btce-trip-name') || 'N/A'}`, 14, lastY); lastY += 7; doc.text(`Destination: ${getVal('btce-destination') || 'N/A'}`, 14, lastY); lastY += 7; doc.text(`Duration: ${getNum('btce-num-days')} Days, ${getNum('btce-num-nights')} Nights`, 14, lastY); lastY += 7; doc.text(`Number of Travelers: ${getNum('btce-num-travelers')}`, 14, lastY); lastY += 10; const categoryTotalsPdf = {}; const detailedExpensesPdf = []; let grandTotalPdf = 0; // Re-run calculation logic to ensure data consistency for PDF // Transportation let flightsTotal = getNum('btce-flights-cost') * getNum('btce-num-travelers'); if(flightsTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Flights', amount: flightsTotal }); const localTransType = getRadioVal('localTransType'); const localTransAmount = getNum('btce-localtrans-amount'); let localTransTotal = 0; if (localTransType === 'perDayPerTraveler') localTransTotal = localTransAmount * getNum('btce-num-days') * getNum('btce-num-travelers'); else localTransTotal = localTransAmount; if(localTransTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Local Transport', amount: localTransTotal }); let carRentalTotal = getNum('btce-carrental-total'); if(carRentalTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Car Rental', amount: carRentalTotal }); let fuelTotal = getNum('btce-carrental-fuel'); if(fuelTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Fuel for Rental', amount: fuelTotal }); // Accommodation const hotelCostNight = getNum('btce-hotel-cost-night'); const hotelBasis = getRadioVal('hotelBasis'); let accommodationTotal = 0; if (hotelBasis === 'perTraveler') accommodationTotal = hotelCostNight * getNum('btce-num-nights') * getNum('btce-num-travelers'); else accommodationTotal = hotelCostNight * getNum('btce-num-nights') * (getNum('btce-num-rooms') || 1); if(accommodationTotal > 0) detailedExpensesPdf.push({ category: 'Accommodation', name: 'Hotel/Lodging', amount: accommodationTotal }); // Meals const mealsType = getRadioVal('mealsType'); const mealsAmount = getNum('btce-meals-amount'); let mealsTotal = 0; if (mealsType === 'perDayPerTraveler') mealsTotal = mealsAmount * getNum('btce-num-days') * getNum('btce-num-travelers'); else mealsTotal = mealsAmount; if(mealsTotal > 0) detailedExpensesPdf.push({ category: 'Meals & Per Diems', name: 'Meals/Per Diems', amount: mealsTotal }); // Other Standard let visaCostTotal = getNum('btce-visa-cost') * getNum('btce-num-travelers'); if(visaCostTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Visa Costs', amount: visaCostTotal }); const insuranceType = getRadioVal('insuranceType'); const insuranceAmount = getNum('btce-insurance-amount'); let insuranceTotal = 0; if (insuranceType === 'perTraveler') insuranceTotal = insuranceAmount * getNum('btce-num-travelers'); else insuranceTotal = insuranceAmount; if(insuranceTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Travel Insurance', amount: insuranceTotal }); let eventFeesTotal = getNum('btce-event-fees') * getNum('btce-num-travelers'); if(eventFeesTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Conference/Event Fees', amount: eventFeesTotal }); let entertainmentTotal = getNum('btce-entertainment-total'); if(entertainmentTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Client Entertainment', amount: entertainmentTotal }); let commsTotal = getNum('btce-communication-total'); if(commsTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Communication', amount: commsTotal }); let baggageTotal = getNum('btce-baggage-fees') * getNum('btce-num-travelers'); if(baggageTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Baggage Fees', amount: baggageTotal }); // Custom Expenses document.querySelectorAll('.btce-custom-expense-item').forEach(item => { const name = item.querySelector('.btce-custom-name').value || 'Custom Expense'; let amount = parseFloat(item.querySelector('.btce-custom-amount').value) || 0; const basis = item.querySelector('.btce-custom-cost-basis').value; const type = item.querySelector('.btce-custom-cost-type').value; let calculatedAmount = 0; if (basis === 'perTraveler') calculatedAmount = amount * getNum('btce-num-travelers'); else calculatedAmount = amount; if (type === 'perDay') calculatedAmount *= getNum('btce-num-days'); if(calculatedAmount > 0) detailedExpensesPdf.push({ category: 'Custom Expenses', name, amount: calculatedAmount }); }); const tableBody = []; detailedExpensesPdf.sort((a,b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name)); let currentCategoryPdf = ""; detailedExpensesPdf.forEach(exp => { if (exp.amount > 0) { categoryTotalsPdf[exp.category] = (categoryTotalsPdf[exp.category] || 0) + exp.amount; grandTotalPdf += exp.amount; } }); detailedExpensesPdf.forEach(exp => { if (currentCategoryPdf !== exp.category) { // Add subtotal for previous category if it existed and had items if (currentCategoryPdf !== "" && categoryTotalsPdf[currentCategoryPdf] !== undefined) { tableBody.push([ { content: `Subtotal for ${currentCategoryPdf}`, colSpan: 2, styles: { halign: 'right', fontStyle: 'bold' } }, { content: `${currency}${categoryTotalsPdf[currentCategoryPdf].toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold' } } ]); } currentCategoryPdf = exp.category; tableBody.push([{ content: exp.category, colSpan: 3, styles: { fontStyle: 'bold', fillColor: '#f0f0f0' } }]); } tableBody.push(['', exp.name, `${currency}${exp.amount.toFixed(2)}`]); }); // Add subtotal for the very last category if (currentCategoryPdf !== "" && categoryTotalsPdf[currentCategoryPdf] !== undefined) { tableBody.push([ { content: `Subtotal for ${currentCategoryPdf}`, colSpan: 2, styles: { halign: 'right', fontStyle: 'bold' } }, { content: `${currency}${categoryTotalsPdf[currentCategoryPdf].toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold' } } ]); } tableBody.push([ { content: 'GRAND TOTAL ESTIMATED COST', colSpan: 2, styles: { halign: 'right', fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } }, { content: `${currency}${grandTotalPdf.toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } } ]); doc.autoTable({ startY: lastY, head: [['Category/Details', 'Item', 'Estimated Cost']], body: tableBody, theme: 'grid', headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 10 }, styles: { fontSize: 9, cellPadding: 2 }, columnStyles: { 2: { halign: 'right' } }, didDrawCell: (data) => { // For rowspan-like appearance for category header if (data.body[data.row.index] && data.body[data.row.index][0] && data.body[data.row.index][0].colSpan === 3) { // Handled by autotable's colSpan } } }); lastY = doc.lastAutoTable.finalY + 10; const numTravelersPdf = getNum('btce-num-travelers'); const numDaysPdf = getNum('btce-num-days'); doc.setFontSize(10); doc.setTextColor(textColor); doc.text(`Average Cost Per Traveler: ${currency}${(numTravelersPdf > 0 ? grandTotalPdf / numTravelersPdf : 0).toFixed(2)}`, 14, lastY); lastY += 7; doc.text(`Average Cost Per Day (Overall): ${currency}${(numDaysPdf > 0 ? grandTotalPdf / numDaysPdf : 0).toFixed(2)}`, 14, lastY); doc.save(`Business_Trip_Estimate_${getVal('btce-trip-name').replace(/\s+/g, '_') || 'Report'}.pdf`); }); checkEmptyState('#btce-custom-expenses-container', 'btce-no-items-text'); showTab(0); updateAllCurrencyPrefixes(); // Initial call });
Scroll to Top