Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
www
/
mudeerapi.abasa.com
/
nodetest-backup30April26
/
src
/
services
/
Filename :
invoicePdfService.js
back
Copy
import { marked } from 'marked'; import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import fs from 'fs'; const s3Client = new S3Client({ region: process.env.AWS_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }, }); const BUCKET = process.env.AWS_BUCKET_NAME || 'mudeer-bucket'; function wrapHtml(bodyHtml) { return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Invoice</title> <style> @page { size: A4; margin: 10px; } body { font-family: system-ui, -apple-system, sans-serif; max-width: 720px; margin: 0 auto; padding: 0; color: #000; line-height: 1.35; font-size: 13px; } h1 { color: #5D2D83; font-size: 52px; font-weight: 900; letter-spacing: -1px; text-transform: uppercase; line-height: 0.92; margin: 0 0 5px; } h2 { color: #234297; font-size: 20px; font-weight: 800; line-height: 1; margin: 0 0 2px; } p { margin: 0 0 4px; white-space: pre-line; color: #000; } hr { border: none; border-top: 1px solid #5D2D83; margin: 14px 0; } table { width: 100%; border-collapse: collapse; margin: 1px 0; page-break-inside: avoid; } th, td { text-align: left; padding: 2px 0; } th { color: #234297; font-weight: 800; font-size: 14px; border-bottom: 2px solid #5D2D83; padding-left: 0; padding-right: 0; } td { padding-left: 0; padding-right: 0; vertical-align: top; color: #000; } .invoice-meta-row { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; } .bill-to-block { flex: 1 1 auto; max-width: 62%; } .invoice-number-block { min-width: 160px; } .meta-line { display: flex; justify-content: space-between; gap: 6px; font-size: 16px; line-height: 1.1; } .meta-line strong { min-width: 120px; color: #234297; font-weight: 800; } .description-cell { white-space: pre-line; color: #000; } .amount-cell { text-align: right; white-space: nowrap; color: #000; } .total-label { text-align: right; font-weight: 800; font-size: 20px; padding-top: 4px; color: #234297; } .total-value { text-align: right; font-weight: 800; font-size: 20px; padding-top: 4px; white-space: nowrap; color: #234297; } .total { font-weight: bold; font-size: 1.2rem; color: #234297; } .muted { color: #64748b; font-size: 0.875rem; } .page-break { page-break-before: always; break-before: page; height: 1px; } .att-title { color: #234297; font-size: 18px; font-weight: 800; margin: 10px 0 6px; } .att-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 11px; } .att-table th { text-align: left; font-size: 11px; padding: 6px 6px; border-bottom: 2px solid #5D2D83; color: #234297; } .att-table td { padding: 6px 6px; border-bottom: 1px solid #e5e7eb; vertical-align: top; } .att-table tbody tr:nth-child(even) { background: #f8fafc; } .att-status { display: inline-block; padding: 2px 6px; border-radius: 999px; font-weight: 700; font-size: 10px; } .att-status-present { background: #dcfce7; color: #166534; } .att-status-late { background: #fef9c3; color: #854d0e; } .att-status-absent { background: #fee2e2; color: #991b1b; } .att-status-daily-off { background: #f3e8ff; color: #6b21a8; } .att-status-public-holiday { background: #fed7aa; color: #9a3412; } .att-status-paid-leave { background: #dbeafe; color: #1e40af; } .att-status-unpaid-leave { background: #e5e7eb; color: #374151; } .att-status-no-record { background: #f3f4f6; color: #6b7280; } </style> </head> <body> ${bodyHtml} </body> </html>`; } export async function markdownToHtml(markdown) { if (!markdown) return ''; const raw = await Promise.resolve(marked.parse(markdown)); return typeof raw === 'string' ? raw : String(raw); } export async function renderInvoiceMarkdownToPdf(markdown) { const puppeteer = await import('puppeteer'); const html = wrapHtml(markdown || ''); const baseLaunchOptions = { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], }; const configuredPath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_PATH || process.env.GOOGLE_CHROME_BIN; const fallbackPaths = process.platform === 'win32' ? [ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', ] : process.platform === 'darwin' ? ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'] : ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium-browser', '/usr/bin/chromium']; const candidatePaths = [configuredPath, ...fallbackPaths].filter(Boolean); let browser = null; let lastError = null; // First try Puppeteer's default resolution (works when bundled browser exists). try { browser = await puppeteer.default.launch(baseLaunchOptions); } catch (err) { lastError = err; } // Fallback to explicit Chrome paths when Puppeteer cache/browser is missing. if (!browser) { for (const executablePath of candidatePaths) { try { if (!fs.existsSync(executablePath)) continue; browser = await puppeteer.default.launch({ ...baseLaunchOptions, executablePath, }); break; } catch (err) { lastError = err; } } } if (!browser) { throw new Error( `Unable to launch Chrome for invoice PDF rendering. Set PUPPETEER_EXECUTABLE_PATH or install Chrome. ${lastError ? `Last error: ${lastError.message}` : ''}` ); } try { const page = await browser.newPage(); await page.setContent(html, { waitUntil: 'networkidle0' }); const buffer = await page.pdf({ format: 'A4', margin: { top: '10px', right: '10px', bottom: '10px', left: '10px' }, printBackground: true, }); return buffer; } finally { await browser.close(); } } export async function uploadInvoicePdfToS3(buffer, key) { await s3Client.send( new PutObjectCommand({ Bucket: BUCKET, Key: key, Body: buffer, ContentType: 'application/pdf', }) ); const region = process.env.AWS_REGION || 'us-east-1'; const url = `https://${BUCKET}.s3.${region}.amazonaws.com/${key}`; return url; } export function getInvoicePdfKey(year, month, invoiceId) { return `invoices/${year}/${month}/${invoiceId}.pdf`; }