.txt
function exportQuote() {
const q = calcQuote();
const lines = [
'═══════════════════════════════════════════════════════',
' SVS MSP — Managed Services Quote',
'═══════════════════════════════════════════════════════',
` Client: ${q.clientName || '(not specified)'}`,
` Ref: ${document.getElementById('quoteRef')?.textContent || ''}`,
` Date: ${document.getElementById('headerDate')?.textContent || ''}`,
'───────────────────────────────────────────────────────',
'',
' SERVICES SUMMARY',
'',
];
if (q.users > 0) {
lines.push(` User Package (${q.users} users @ ${fmt(q.totalUserRate)}/user)`);
lines.push(` Base (${q.byol ? 'BYOL' : 'M365 Incl.'}): ${fmt(q.userBase)}/mo`);
if (q.userExt) lines.push(` Extended Hours: ${fmt(q.userExt)}/mo`);
if (q.userPWM) lines.push(` 1Password: ${fmt(q.userPWM)}/mo`);
if (q.userINKY) lines.push(` INKY Pro: ${fmt(q.userINKY)}/mo`);
if (q.userZT) lines.push(` Zero Trust: ${fmt(q.userZT)}/mo`);
lines.push(` Subtotal: ${fmt(q.userTotal)}/mo`);
lines.push('');
}
if (q.endpoints > 0 || q.servers > 0) {
lines.push(` Endpoint Package (${q.endpoints} endpoints @ $35)`);
if (q.endpointBMB) lines.push(` + Bare Metal Backup: ${fmt(q.endpointBMB)}/mo`);
if (q.endpointUSB) lines.push(` + USB Blocking: ${fmt(q.endpointUSB)}/mo`);
if (q.servers > 0) lines.push(` + ${q.servers} Server(s) @ $120: ${fmt(q.serverBase)}/mo`);
lines.push(` Subtotal: ${fmt(q.endpointTotal)}/mo`);
lines.push('');
}
lines.push(` Site Admin Fee: ${fmt(q.adminFeeNet)}/mo`);
lines.push('');
if (q.ztNetTotal > 0) {
lines.push(` Zero Trust Networking: ${fmt(q.ztNetTotal)}/mo`);
lines.push('');
}
if (q.voipTotal > 0) {
lines.push(` VoIP / UCaaS (${q.voipSeats} seats, ${q.voipTier}): ${fmt(q.voipTotal)}/mo`);
lines.push('');
}
lines.push('───────────────────────────────────────────────────────');
if (q.discountPct > 0) {
const termLabel = q.contractTerm === '12mo' ? '12-Month' : '24-Month';
lines.push(` BASE MRR: ${fmt(q.MRR)}`);
lines.push(` ${termLabel} DISCOUNT (${Math.round(q.discountPct*100)}%): −${fmt(q.discountAmt)}`);
}
lines.push(` MONTHLY RECURRING (MRR): ${fmt(q.effectiveMrr)}`);
if (q.hstEnabled) lines.push(` + HST (13%): ${fmt(q.hstAmt)}`);
lines.push(` ANNUAL PROJECTION: ${fmt(q.effectiveAnnual)}`);
if (q.oneTimeFee > 0) lines.push(` ONBOARDING FEE: ${fmt(q.oneTimeFee)} (one-time, not recurring)`);
if (q.users > 0) lines.push(` AVG. COST PER USER: ${fmt(q.effectiveMrr / q.users)}/user/mo (all services)`);
lines.push(` HST: ${q.hstEnabled ? 'Included in figures above' : 'Not included — HST applies at 13% on invoice'}`);
lines.push('───────────────────────────────────────────────────────');
lines.push('');
lines.push('Prepared by SVS MSP | This quote is valid for 30 days from date of issue.');
lines.push('Questions? Contact your SVS MSP account representative.');
const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const client = (q.clientName || 'Quote').replace(/[^a-z0-9]/gi,'_');
a.href = url;
a.download = `SVS_MSP_Quote_${client}.txt`;
a.click();
URL.revokeObjectURL(url);
}
// ── printInvoice() ────────────────────────────────────────────────
// Generates a clean invoice-style HTML document in a new window
// and triggers the browser print dialog (Save as PDF works perfectly).
function printInvoice() {
const q = calcQuote();
const waived = document.getElementById('onboardingWaived')?.checked || false;
const feeEl = document.getElementById('oneTimeFee');
const onboardingFee = waived ? 0 : (parseFloat(feeEl?.value) || Math.round(q.MRR / 2));
const waivedAmt = Math.round(q.MRR / 2);
const quoteRef = document.getElementById('quoteRef')?.textContent || '—';
const quoteDate = document.getElementById('headerDate')?.textContent || '—';
const client = q.clientName || 'Client';
const termLabel = q.contractTerm === '12mo' ? '12-Month Contract — 3% off MRR'
: q.contractTerm === '24mo' ? '24-Month Contract — 5% off MRR'
: 'Month-to-Month';
// ── Build line items ───────────────────────────────────────────
const rows = [];
const row = (label, detail, amt, sub) => rows.push({label, detail, amt, sub: !!sub});
if (q.users > 0) {
const pkg = q.byol ? 'BYOL — Bring Your Own License' : 'M365 Included (Identity, Email & Protection)';
row(`User Package — ${pkg}`, `${q.users} user${q.users!==1?'s':''} × ${fmt(q.baseUserRate)}/mo`, fmt(q.userBase));
if (q.userExt > 0) row('↳ Extended Hours (+$25/user)', '', fmt(q.userExt), true);
if (q.userPWM > 0) row('↳ 1Password Business (+$9/user)', '', fmt(q.userPWM), true);
if (q.userINKY > 0) row('↳ Inky Email Security (+$5/user)', '', fmt(q.userINKY), true);
if (q.userZT > 0) row('↳ Zero Trust User (+$55/user)', '', fmt(q.userZT), true);
}
if (q.endpoints > 0) {
row('Endpoint Management', `${q.endpoints} endpoint${q.endpoints!==1?'s':''} × $35/mo`, fmt(q.endpointBase));
if (q.endpointUSB > 0) row('↳ USB Blocking (+$4/endpoint)', '', fmt(q.endpointUSB), true);
if (q.endpointBMB > 0) row('↳ Bare Metal Backup (+$25/endpoint)', '', fmt(q.endpointBMB), true);
}
if (q.servers > 0) {
row('Server Management', `${q.servers} server${q.servers!==1?'s':''} × $120/mo`, fmt(q.serverBase));
}
if (q.ztNetTotal > 0) {
row('Zero Trust Networking — HaaS', '', fmt(q.ztNetTotal));
if (q.ztNetSeats > 0) row(`↳ ZT Seats (${q.ztSeats} × $25/mo)`, '', fmt(q.ztNetSeats), true);
if (q.ztNetRouters > 0) row(`↳ HaaS Routers (${q.ztRouters} × $100/mo)`, '', fmt(q.ztNetRouters), true);
}
if (q.voipTotal > 0) {
const tier = {basic:'Basic',standard:'Standard',premium:'Premium'}[q.voipTier] || 'Basic';
row(`VoIP / UCaaS — ${tier}`, `${q.voipSeats} seat${q.voipSeats!==1?'s':''} × $${VOIP_RATES[q.voipTier]}/mo`, fmt(q.voipSeatsAmt));
if (q.voipPhoneAmt > 0) row('↳ Desk Phone HaaS (+$15/seat)', '', fmt(q.voipPhoneAmt), true);
if (q.voipFaxAmt > 0) row('↳ Virtual Fax (+$10/mo)', '', fmt(q.voipFaxAmt), true);
}
row('Site Admin Fee', 'Tenant, network, documentation & vendor management', fmt(q.adminFeeNet));
const itemsHTML = rows.map(r => `
| ${r.label} |
${r.detail} |
${r.amt}/mo |
`).join('');
// ── Build totals ───────────────────────────────────────────────
let totals = '';
if (q.discountPct > 0) {
totals += `| Base MRR | ${fmt(q.MRR)}/mo |
`;
totals += `| Term Discount (${Math.round(q.discountPct*100)}% off) | −${fmt(q.discountAmt)}/mo |
`;
}
totals += `| Monthly Recurring (MRR) | ${fmt(q.effectiveMrr)}/mo |
`;
if (q.hstEnabled) {
totals += `| Ontario HST (13%) | +${fmt(q.hstAmt)}/mo |
`;
totals += `| Total Monthly | ${fmt(q.mrrWithHst)}/mo |
`;
}
if (waived && waivedAmt > 0) {
totals += `| Onboarding Fee WAIVED${q.contractTerm!=='m2m'?' — included with '+termLabel.split(' —')[0]:''} | ${fmt(waivedAmt)} saved |
`;
} else if (onboardingFee > 0) {
totals += `| Onboarding Fee (one-time) | ${fmt(onboardingFee)} |
`;
}
totals += `| Annual Projection | ${fmt(q.effectiveAnnual)}/yr |
`;
// ── SVG logo (inline) ──────────────────────────────────────────
const logo = ``;
const html = `
SVS MSP Quote — ${client}
Prepared for
${client}
${termLabel} · ${q.hstEnabled ? 'HST included in figures' : 'Prices in CAD, excl. HST'}
Service Breakdown
Quote Summary