Skip to main content
Glama
generator.ts5.22 kB
import { ParsedRFP, CostEstimate, QuoteDocument, QuoteLine } from './types'; /** * Generate a quote document from RFP and estimate */ export function generateQuote( parsedRfp: ParsedRFP, estimate: CostEstimate ): QuoteDocument { const lines: QuoteLine[] = []; // Main line item const desc = buildDescription(parsedRfp); lines.push({ desc, qty: parsedRfp.qty, unit: estimate.pricePerUnit, total: Number((estimate.pricePerUnit * parsedRfp.qty).toFixed(2)), }); // Add process details if available if (parsedRfp.processes && parsedRfp.processes.length > 0) { const processDesc = ` Processes: ${parsedRfp.processes.join(', ')}`; lines.push({ desc: processDesc, qty: 0, unit: 0, total: 0, }); } // Add notes for tolerances if (parsedRfp.tolerances) { lines.push({ desc: ` Tolerances: ${parsedRfp.tolerances}`, qty: 0, unit: 0, total: 0, }); } // Generate quote ID const quoteId = generateQuoteId(); // Calculate valid until date (typically 30 days) const validUntil = new Date(); validUntil.setDate(validUntil.getDate() + 30); // Build terms const terms = buildTerms(estimate); return { quoteId, to: parsedRfp.contactEmail || 'unknown', customerName: parsedRfp.customerName, lines, terms, confidence: estimate.confidence, validUntil: validUntil.toISOString().split('T')[0], paymentTerms: 'Net 30', createdAt: new Date().toISOString(), status: 'draft', }; } /** * Build item description */ function buildDescription(rfp: ParsedRFP): string { let desc = `Manufacture ${rfp.qty} x `; if (rfp.partNumber) { desc += `Part #${rfp.partNumber}`; } else { desc += 'custom part'; } if (rfp.material && rfp.material !== 'UNKNOWN') { desc += ` (${rfp.material})`; } if (rfp.finish) { desc += `, ${rfp.finish} finish`; } return desc; } /** * Build terms section */ function buildTerms(estimate: CostEstimate): string { let terms = `Lead time: ${estimate.leadDays} business days from receipt of order`; if (estimate.bestCase && estimate.worstCase) { terms += ` (${estimate.bestCase.leadDays}-${estimate.worstCase.leadDays} days range based on ${estimate.confidence} confidence)`; } terms += '. Payment terms: Net 30. '; terms += 'F.O.B. Origin. '; if (estimate.confidence === 'low') { terms += 'NOTE: This is a preliminary estimate. Final pricing subject to review of drawings/specifications. '; } terms += 'Quote valid for 30 days.'; return terms; } /** * Generate unique quote ID */ function generateQuoteId(): string { const date = new Date(); const year = date.getFullYear().toString().slice(-2); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0'); return `Q${year}${month}${day}-${random}`; } /** * Format quote document as text */ export function formatQuoteDocument(doc: QuoteDocument): string { let output = ''; output += '='.repeat(70) + '\n'; output += `QUOTATION: ${doc.quoteId}\n`; output += '='.repeat(70) + '\n\n'; output += `Date: ${new Date(doc.createdAt).toLocaleDateString()}\n`; output += `To: ${doc.to}\n`; if (doc.customerName) { output += `Customer: ${doc.customerName}\n`; } output += `Valid Until: ${doc.validUntil}\n`; output += `Status: ${doc.status.toUpperCase()}\n\n`; output += 'LINE ITEMS:\n'; output += '-'.repeat(70) + '\n'; for (const line of doc.lines) { if (line.qty === 0) { // Detail line output += `${line.desc}\n`; } else { // Priced line output += `${line.desc}\n`; output += ` Quantity: ${line.qty} @ $${line.unit.toFixed(2)}/ea\n`; output += ` Total: $${line.total.toFixed(2)}\n`; } } output += '-'.repeat(70) + '\n'; const total = doc.lines.reduce((sum, line) => sum + line.total, 0); output += `TOTAL: $${total.toFixed(2)}\n\n`; output += 'TERMS & CONDITIONS:\n'; output += doc.terms + '\n\n'; if (doc.paymentTerms) { output += `Payment Terms: ${doc.paymentTerms}\n`; } if (doc.confidence === 'low' || doc.confidence === 'medium') { output += '\n'; output += '⚠️ NOTICE: This quote has ' + doc.confidence.toUpperCase() + ' confidence.\n'; output += 'Please review carefully and provide additional details if available.\n'; } output += '\n'; output += '='.repeat(70) + '\n'; return output; } /** * Format quote for email */ export function formatQuoteEmail(doc: QuoteDocument): { subject: string; body: string } { const subject = `Quotation ${doc.quoteId}${doc.customerName ? ` - ${doc.customerName}` : ''}`; let body = `Dear Customer,\n\n`; body += `Thank you for your request for quotation. Please find our quote below:\n\n`; body += formatQuoteDocument(doc); body += `\n`; body += `If you have any questions or would like to proceed with this order, please contact us.\n\n`; body += `Best regards,\n`; body += `Sales Team\n`; return { subject, body }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/r-long/mcp-quoting-system'

If you have feedback or need assistance with the MCP directory API, please join our Discord server