583 lines
23 KiB
PHP
583 lines
23 KiB
PHP
<?php
|
||
|
||
namespace App\Libraries;
|
||
|
||
use App\Models\ClientModel;
|
||
use App\Models\LeadsModel;
|
||
use App\Models\RFQModel;
|
||
use Mpdf\Mpdf;
|
||
use Mpdf\Output\Destination;
|
||
|
||
/**
|
||
* Builds the GMC Quote Comparison Report PDF:
|
||
* cover (client) + static front + dynamic QCR pages + static back.
|
||
*/
|
||
class GmcQcrPdfService
|
||
{
|
||
public const GMC_POLICY_TYPE_IDS = [2, 3, 4, 5];
|
||
|
||
private const PAGE_W_MM = 338.6667; // 960 pt
|
||
private const PAGE_H_MM = 190.5; // 540 pt
|
||
private const ROWS_PER_PAGE = 18;
|
||
private const COLS_PER_PAGE = 5;
|
||
|
||
private RFQModel $rfqModel;
|
||
private LeadsModel $leadsModel;
|
||
private ClientModel $clientModel;
|
||
|
||
public function __construct(?RFQModel $rfqModel = null, ?LeadsModel $leadsModel = null, ?ClientModel $clientModel = null)
|
||
{
|
||
$this->rfqModel = $rfqModel ?? new RFQModel();
|
||
$this->leadsModel = $leadsModel ?? new LeadsModel();
|
||
$this->clientModel = $clientModel ?? new ClientModel();
|
||
}
|
||
|
||
public static function isGmc(int $policyTypeId): bool
|
||
{
|
||
return in_array($policyTypeId, self::GMC_POLICY_TYPE_IDS, true);
|
||
}
|
||
|
||
/**
|
||
* @return array{status:bool,message:string,file_name?:string,file_path?:string,download_name?:string}
|
||
*/
|
||
/**
|
||
* @param int $leadId
|
||
* @param array|null $selectedProposals If provided, only include these proposal names
|
||
*/
|
||
public function generate(int $leadId, ?array $selectedProposals = null): array
|
||
{
|
||
$lead = $this->leadsModel
|
||
->select('leads.*, policy_type.policy_type, policy_type.long_name')
|
||
->join('policy_type', 'leads.policy_type_id = policy_type.id', 'left')
|
||
->where('leads.id', $leadId)
|
||
->where('leads.is_active', 1)
|
||
->first();
|
||
|
||
if (! $lead) {
|
||
return ['status' => false, 'message' => 'Opportunity not found'];
|
||
}
|
||
|
||
if (! self::isGmc((int) ($lead['policy_type_id'] ?? 0))) {
|
||
return ['status' => false, 'message' => 'GMC QCR PDF is only available for GMC policies'];
|
||
}
|
||
|
||
// Primary: explicit QCR row (type=2)
|
||
$rfq = $this->rfqModel
|
||
->where('lead_id', $leadId)
|
||
->where('type', 2)
|
||
->where('is_active', 1)
|
||
->orderBy('id', 'desc')
|
||
->first();
|
||
|
||
// Fallback: some older leads/pages show latest active RFQ row on QCR view.
|
||
// If explicit QCR is missing, use latest active saved table JSON.
|
||
if (! $rfq || empty($rfq['json'])) {
|
||
$rfq = $this->rfqModel
|
||
->where('lead_id', $leadId)
|
||
->where('is_active', 1)
|
||
->orderBy('id', 'desc')
|
||
->first();
|
||
}
|
||
|
||
if (! $rfq || empty($rfq['json'])) {
|
||
return ['status' => false, 'message' => 'No saved proposal table data found. Please save this page first.'];
|
||
}
|
||
|
||
$qcrJson = json_decode($rfq['json'], true);
|
||
if (! is_array($qcrJson) || empty($qcrJson['table_data'])) {
|
||
return ['status' => false, 'message' => 'QCR data is invalid or empty'];
|
||
}
|
||
|
||
$clientName = trim((string) ($lead['client_name'] ?? 'Client'));
|
||
$logoPath = $this->resolveClientLogoPath((int) ($lead['client_id'] ?? 0));
|
||
$nhanceLogo = ROOTPATH . 'public/assets/images/Nhance-Logo-Final.png';
|
||
|
||
$coveragePages = $this->buildCoveragePages($qcrJson, $selectedProposals);
|
||
$premiumSections = $this->buildPremiumSections($qcrJson, $selectedProposals);
|
||
|
||
if (empty($coveragePages) && empty($premiumSections)) {
|
||
return ['status' => false, 'message' => 'No QCR-enabled proposals found to include in the PDF'];
|
||
}
|
||
|
||
$frontPdf = ROOTPATH . 'public/assets/qcr_gmc/static_front.pdf';
|
||
$backPdf = ROOTPATH . 'public/assets/qcr_gmc/static_back.pdf';
|
||
if (! is_file($frontPdf) || ! is_file($backPdf)) {
|
||
return ['status' => false, 'message' => 'Static GMC QCR template PDFs are missing'];
|
||
}
|
||
|
||
$tempDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'gmc_qcr_mpdf';
|
||
if (! is_dir($tempDir)) {
|
||
@mkdir($tempDir, 0775, true);
|
||
}
|
||
$mpdfNested = $tempDir . DIRECTORY_SEPARATOR . 'mpdf';
|
||
if (! is_dir($mpdfNested)) {
|
||
@mkdir($mpdfNested, 0775, true);
|
||
}
|
||
|
||
$outDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'qcr_pdf' . DIRECTORY_SEPARATOR;
|
||
if (! is_dir($outDir)) {
|
||
@mkdir($outDir, 0775, true);
|
||
}
|
||
|
||
$safeClient = preg_replace('/[^A-Za-z0-9_\- ]+/', '', $clientName) ?: 'Client';
|
||
$safeClient = trim(preg_replace('/\s+/', ' ', $safeClient));
|
||
$downloadName = $safeClient . ' GMC Quote comparison.-' . date('d-F-Y') . '.pdf';
|
||
$storedName = 'gmc_qcr_' . $leadId . '_' . time() . '_' . bin2hex(random_bytes(3)) . '.pdf';
|
||
$outPath = $outDir . $storedName;
|
||
|
||
try {
|
||
$mpdf = new Mpdf([
|
||
'tempDir' => $tempDir,
|
||
'mode' => 'utf-8',
|
||
'format' => [self::PAGE_W_MM, self::PAGE_H_MM],
|
||
'orientation' => 'P',
|
||
'margin_left' => 0,
|
||
'margin_right' => 0,
|
||
'margin_top' => 0,
|
||
'margin_bottom'=> 0,
|
||
]);
|
||
|
||
// 1) Cover
|
||
$mpdf->AddPageByArray($this->pageArray());
|
||
$mpdf->WriteHTML($this->renderCoverHtml($clientName, $logoPath, $nhanceLogo));
|
||
|
||
// 2) Static front (pages 2–7 from master)
|
||
$this->importPdfPages($mpdf, $frontPdf, $tempDir);
|
||
|
||
// 3) Dynamic coverage pages — one set per proposal (Quote Asked only)
|
||
foreach ($coveragePages as $page) {
|
||
$rowChunks = array_chunk($page['rows'], self::ROWS_PER_PAGE);
|
||
foreach ($rowChunks as $chunkIdx => $chunk) {
|
||
$mpdf->AddPageByArray($this->pageArray());
|
||
$mpdf->WriteHTML($this->renderCoverageHtml(
|
||
$page['title'],
|
||
$chunk,
|
||
$nhanceLogo,
|
||
$chunkIdx === 0
|
||
));
|
||
}
|
||
}
|
||
|
||
// 4) Quote Comparison — insurers with premium data
|
||
if (! empty($premiumSections)) {
|
||
$mpdf->AddPageByArray($this->pageArray());
|
||
$mpdf->WriteHTML($this->renderPremiumHtml($premiumSections, $nhanceLogo));
|
||
}
|
||
|
||
// 5) Static back (last 3)
|
||
$this->importPdfPages($mpdf, $backPdf, $tempDir);
|
||
|
||
$mpdf->Output($outPath, Destination::FILE);
|
||
} catch (\Throwable $e) {
|
||
log_message('error', 'GmcQcrPdfService::generate failed | lead_id=' . $leadId . ' | ' . $e->getMessage());
|
||
return ['status' => false, 'message' => 'PDF generation failed: ' . $e->getMessage()];
|
||
}
|
||
|
||
if (! is_file($outPath)) {
|
||
return ['status' => false, 'message' => 'PDF file was not created'];
|
||
}
|
||
|
||
$this->persistGeneratedPath($lead, $storedName, $downloadName);
|
||
|
||
return [
|
||
'status' => true,
|
||
'message' => 'GMC QCR PDF generated successfully',
|
||
'file_name' => $storedName,
|
||
'file_path' => $outPath,
|
||
'download_name' => $downloadName,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @return array{status:bool,message:string,file_path?:string,download_name?:string}
|
||
*/
|
||
public function resolveDownload(int $leadId): array
|
||
{
|
||
$lead = $this->leadsModel->where('id', $leadId)->where('is_active', 1)->first();
|
||
if (! $lead) {
|
||
return ['status' => false, 'message' => 'Opportunity not found'];
|
||
}
|
||
if (! self::isGmc((int) ($lead['policy_type_id'] ?? 0))) {
|
||
return ['status' => false, 'message' => 'GMC QCR PDF is only available for GMC policies'];
|
||
}
|
||
|
||
$misc = [];
|
||
if (! empty($lead['misc'])) {
|
||
$decoded = json_decode($lead['misc'], true);
|
||
if (is_array($decoded)) {
|
||
$misc = $decoded;
|
||
}
|
||
}
|
||
|
||
$storedName = $misc['gmc_qcr_pdf'] ?? null;
|
||
$downloadName = $misc['gmc_qcr_pdf_download_name'] ?? null;
|
||
if (! $storedName) {
|
||
return ['status' => false, 'message' => 'No generated PDF found. Please click Generate first.'];
|
||
}
|
||
|
||
$path = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'qcr_pdf' . DIRECTORY_SEPARATOR . $storedName;
|
||
if (! is_file($path)) {
|
||
return ['status' => false, 'message' => 'Generated PDF file is missing. Please Generate again.'];
|
||
}
|
||
|
||
if (! $downloadName) {
|
||
$clientName = trim((string) ($lead['client_name'] ?? 'Client'));
|
||
$downloadName = $clientName . ' GMC Quote comparison.-' . date('d-F-Y') . '.pdf';
|
||
}
|
||
|
||
return [
|
||
'status' => true,
|
||
'message' => 'OK',
|
||
'file_path' => $path,
|
||
'download_name' => $downloadName,
|
||
];
|
||
}
|
||
|
||
private function pageArray(): array
|
||
{
|
||
return [
|
||
'orientation' => 'P',
|
||
'sheet-size' => [self::PAGE_W_MM, self::PAGE_H_MM],
|
||
'margin-left' => 0,
|
||
'margin-right' => 0,
|
||
'margin-top' => 0,
|
||
'margin-bottom'=> 0,
|
||
];
|
||
}
|
||
|
||
private function resolveClientLogoPath(int $clientId): ?string
|
||
{
|
||
if ($clientId <= 0) {
|
||
return null;
|
||
}
|
||
$client = $this->clientModel->select('client_logo')->where('id', $clientId)->where('is_active', 1)->first();
|
||
if (empty($client['client_logo'])) {
|
||
return null;
|
||
}
|
||
$path = ROOTPATH . 'public/uploads/logo/' . $client['client_logo'];
|
||
return is_file($path) ? $path : null;
|
||
}
|
||
|
||
/**
|
||
* Build one coverage page set per QCR-enabled proposal.
|
||
* Each page shows only the "Quote Asked" column (2-col table like original).
|
||
*
|
||
* @return list<array{title:string,rows:list<array{particular:string,value:string}>}>
|
||
*/
|
||
private function buildCoveragePages(array $qcrJson, ?array $selectedProposals = null): array
|
||
{
|
||
$proposalMeta = $qcrJson['proposal_data']['over_all_column_data'] ?? [];
|
||
$headers = $qcrJson['table_data']['headers'] ?? [];
|
||
$dataRows = $qcrJson['table_data']['data'] ?? [];
|
||
|
||
// Collect QCR-enabled proposals that have a Quote Asked column
|
||
$proposals = [];
|
||
foreach ($headers as $header) {
|
||
$parent = (string) ($header['parentHeader'] ?? '');
|
||
if (in_array($parent, ['Sno', 'Item Key', 'Particulars', 'Action', ''], true)) {
|
||
continue;
|
||
}
|
||
$meta = $proposalMeta[$parent] ?? null;
|
||
$proposalQcr = $meta === null ? 1 : (int) ($meta['qcr'] ?? 0);
|
||
if ($proposalQcr !== 1) {
|
||
continue;
|
||
}
|
||
// Filter by user-selected proposals if provided
|
||
if ($selectedProposals !== null && ! in_array($parent, $selectedProposals, true)) {
|
||
continue;
|
||
}
|
||
$subs = $header['subHeaders'] ?? [];
|
||
if (in_array('Quote Asked', $subs, true) && ! isset($proposals[$parent])) {
|
||
$proposals[$parent] = true;
|
||
}
|
||
}
|
||
|
||
$pages = [];
|
||
foreach (array_keys($proposals) as $proposalName) {
|
||
$rows = [];
|
||
foreach ($dataRows as $row) {
|
||
$actionQcr = 1;
|
||
$particular = '';
|
||
$value = '';
|
||
|
||
foreach (($row['data'] ?? []) as $cell) {
|
||
$parent = (string) ($cell['parentth'] ?? '');
|
||
$sub = (string) ($cell['subth'] ?? '');
|
||
$raw = $cell['value'] ?? $cell['input_value'] ?? '';
|
||
if (is_array($raw)) {
|
||
if (isset($raw['qcr'])) {
|
||
$actionQcr = (int) $raw['qcr'];
|
||
}
|
||
$raw = '';
|
||
}
|
||
$text = trim(html_entity_decode(strip_tags((string) $raw)));
|
||
|
||
if ($parent === 'Particulars') {
|
||
$particular = $text;
|
||
} elseif ($parent === $proposalName && $sub === 'Quote Asked') {
|
||
$value = $text;
|
||
} elseif ($parent === 'Action' && is_array($cell['value'] ?? $cell['input_value'] ?? null)) {
|
||
$actionQcr = (int) (($cell['value'] ?? $cell['input_value'] ?? [])['qcr'] ?? 1);
|
||
}
|
||
}
|
||
|
||
if ($actionQcr !== 1 || $particular === '') {
|
||
continue;
|
||
}
|
||
$rows[] = ['particular' => $particular, 'value' => $value];
|
||
}
|
||
|
||
if (! empty($rows)) {
|
||
$pages[] = [
|
||
'title' => 'GMC Policy Coverage ' . $proposalName,
|
||
'rows' => $rows,
|
||
];
|
||
}
|
||
}
|
||
|
||
return $pages;
|
||
}
|
||
|
||
/**
|
||
* Build Quote Comparison sections from premium_data — insurers only (not Quote Asked).
|
||
*
|
||
* @return list<array{title:string,rows:list<array{insurer:string,premium:string,gst:string,total:string}>}>
|
||
*/
|
||
private function buildPremiumSections(array $qcrJson, ?array $selectedProposals = null): array
|
||
{
|
||
$premiumData = $qcrJson['premium_data']['data'] ?? [];
|
||
$proposalMeta = $qcrJson['proposal_data']['over_all_column_data'] ?? [];
|
||
if (! is_array($premiumData) || empty($premiumData)) {
|
||
return [];
|
||
}
|
||
|
||
$sections = [];
|
||
foreach ($proposalMeta as $proposalName => $meta) {
|
||
if ((int) ($meta['qcr'] ?? 0) !== 1) {
|
||
continue;
|
||
}
|
||
if ($selectedProposals !== null && ! in_array($proposalName, $selectedProposals, true)) {
|
||
continue;
|
||
}
|
||
$insMap = $premiumData[$proposalName] ?? [];
|
||
if (! is_array($insMap)) {
|
||
continue;
|
||
}
|
||
$sectionRows = [];
|
||
foreach ($insMap as $insurerName => $metrics) {
|
||
if ($insurerName === 'Quote Asked' || $insurerName === '' || ! is_array($metrics)) {
|
||
continue;
|
||
}
|
||
$premium = trim((string) ($metrics['Premium'] ?? ''));
|
||
$gst = trim((string) ($metrics['GST Amount (₹)'] ?? $metrics['GST Amount'] ?? ''));
|
||
$total = trim((string) ($metrics['Total'] ?? ''));
|
||
if ($premium === '' && $total === '') {
|
||
continue;
|
||
}
|
||
if ($premium === 'Premium') {
|
||
continue;
|
||
}
|
||
$sectionRows[] = [
|
||
'insurer' => $insurerName,
|
||
'premium' => $premium,
|
||
'gst' => $gst,
|
||
'total' => $total,
|
||
];
|
||
}
|
||
if (! empty($sectionRows)) {
|
||
$sections[] = [
|
||
'title' => 'GMC Quote – ' . $proposalName,
|
||
'rows' => $sectionRows,
|
||
];
|
||
}
|
||
}
|
||
|
||
return $sections;
|
||
}
|
||
|
||
private function importPdfPages(Mpdf $mpdf, string $pdfPath, string $tempDir): void
|
||
{
|
||
helper('merge_pdf');
|
||
try {
|
||
merge_ticket_pdf_add_pdf_pages($mpdf, $pdfPath, $tempDir);
|
||
} catch (\Throwable $e) {
|
||
// Fallback: import page-by-page with explicit sheet size
|
||
$pageCount = $mpdf->setSourceFile($pdfPath);
|
||
for ($p = 1; $p <= $pageCount; $p++) {
|
||
$tplId = $mpdf->importPage($p);
|
||
$mpdf->AddPageByArray($this->pageArray());
|
||
$mpdf->useTemplate($tplId, 0, 0, self::PAGE_W_MM, self::PAGE_H_MM, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
private function persistGeneratedPath(array $lead, string $storedName, string $downloadName): void
|
||
{
|
||
$misc = [];
|
||
if (! empty($lead['misc'])) {
|
||
$decoded = json_decode($lead['misc'], true);
|
||
if (is_array($decoded)) {
|
||
$misc = $decoded;
|
||
}
|
||
}
|
||
|
||
// Remove previous file if present
|
||
if (! empty($misc['gmc_qcr_pdf'])) {
|
||
$old = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'qcr_pdf' . DIRECTORY_SEPARATOR . $misc['gmc_qcr_pdf'];
|
||
if (is_file($old) && $misc['gmc_qcr_pdf'] !== $storedName) {
|
||
@unlink($old);
|
||
}
|
||
}
|
||
|
||
$misc['gmc_qcr_pdf'] = $storedName;
|
||
$misc['gmc_qcr_pdf_download_name'] = $downloadName;
|
||
$misc['gmc_qcr_pdf_generated_at'] = date('Y-m-d H:i:s');
|
||
|
||
$this->leadsModel->update((int) $lead['id'], [
|
||
'misc' => json_encode($misc),
|
||
]);
|
||
}
|
||
|
||
private function escape(string $value): string
|
||
{
|
||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
|
||
private function renderCoverHtml(string $clientName, ?string $logoPath, string $nhanceLogo): string
|
||
{
|
||
$logoHtml = '';
|
||
if ($logoPath) {
|
||
$logoHtml = '<div style="margin-bottom:8mm;"><img src="' . $this->escape($logoPath) . '" style="max-height:55mm;max-width:100mm;" /></div>';
|
||
}
|
||
|
||
$nhance = is_file($nhanceLogo)
|
||
? '<img src="' . $this->escape($nhanceLogo) . '" style="height:10mm;" />'
|
||
: '<span style="color:#0aa3a0;font-size:14pt;font-weight:bold;">Nhance</span>';
|
||
|
||
return '
|
||
<html><head><style>
|
||
@page { margin: 0; }
|
||
body { margin:0; padding:0; font-family: freeserif, DejaVu Serif, serif; }
|
||
.wrap { width:100%; height:190mm; position:relative; text-align:center; }
|
||
.spacer { height:48mm; }
|
||
.client { color:#0b7f7c; font-size:16pt; margin-bottom:6mm; font-family: freeserif, DejaVu Serif, serif; }
|
||
.title { font-size:24pt; color:#1a2744; font-family: freeserif, DejaVu Serif, serif; }
|
||
.title em { color:#0b7f7c; font-style:italic; }
|
||
.footer-logo { position:absolute; left:12mm; bottom:8mm; }
|
||
.footer-bar { position:absolute; right:0; bottom:0; width:28mm; height:8mm; background:#0aa3a0; }
|
||
</style></head><body>
|
||
<div class="wrap">
|
||
<div class="spacer"> </div>
|
||
' . $logoHtml . '
|
||
<div class="client">' . $this->escape($clientName) . '</div>
|
||
<div class="title"><strong>Group</strong> <em>Medical Insurance</em> <strong>Quote</strong></div>
|
||
<div class="footer-logo">' . $nhance . '</div>
|
||
<div class="footer-bar"></div>
|
||
</div>
|
||
</body></html>';
|
||
}
|
||
|
||
/**
|
||
* Render a coverage page (2-column: Particulars + value) matching original style.
|
||
*
|
||
* @param list<array{particular:string,value:string}> $rows
|
||
*/
|
||
private function renderCoverageHtml(string $title, array $rows, string $nhanceLogo, bool $showTitle): string
|
||
{
|
||
$nhance = is_file($nhanceLogo)
|
||
? '<img src="' . $this->escape($nhanceLogo) . '" style="height:8mm;" />'
|
||
: '<span style="color:#0aa3a0;font-size:11pt;font-weight:bold;">Nhance</span>';
|
||
|
||
$thead = '<tr><th style="width:55%;">Particulars</th><th style="width:45%;">Expiring</th></tr>';
|
||
|
||
$tbody = '';
|
||
$i = 0;
|
||
foreach ($rows as $row) {
|
||
$bg = ($i % 2 === 0) ? '#ffffff' : '#f7fafa';
|
||
$tbody .= '<tr style="background:' . $bg . ';">';
|
||
$tbody .= '<td class="part">' . $this->escape($row['particular']) . '</td>';
|
||
$tbody .= '<td>' . $this->escape($row['value']) . '</td>';
|
||
$tbody .= '</tr>';
|
||
$i++;
|
||
}
|
||
|
||
$titleBlock = $showTitle
|
||
? '<div class="title"><span class="bar"></span><strong>GMC</strong> <em>' . $this->escape(str_replace('GMC ', '', $title)) . '</em></div>'
|
||
: '<div class="title-sm"><strong>GMC</strong> <em>' . $this->escape(str_replace('GMC ', '', $title)) . '</em> <span style="color:#666;font-size:9pt;">(continued)</span></div>';
|
||
|
||
return '
|
||
<html><head><style>
|
||
body { margin:0; padding:0; font-family: dejavusans, sans-serif; color:#222; }
|
||
.page { padding:8mm 14mm 14mm 14mm; }
|
||
.title { font-family: freeserif, DejaVu Serif, serif; color:#1a2744; font-size:20pt; margin:0 0 5mm 0; padding-left:5mm; border-left:3mm solid #0aa3a0; }
|
||
.title em { color:#0aa3a0; font-style:italic; }
|
||
.title-sm { font-family: freeserif, DejaVu Serif, serif; color:#1a2744; font-size:16pt; margin:0 0 4mm 0; padding-left:5mm; border-left:3mm solid #0aa3a0; }
|
||
.title-sm em { color:#0aa3a0; font-style:italic; }
|
||
table { width:100%; border-collapse:collapse; table-layout:fixed; margin-top:2mm; }
|
||
th { background:#1a7a6e; color:#fff; font-size:9pt; padding:2.8mm 3mm; text-align:center; vertical-align:middle; font-family: dejavusans, sans-serif; }
|
||
td { font-size:8pt; padding:2.2mm 3mm; border-bottom:0.15mm solid #e0eded; text-align:center; vertical-align:middle; word-wrap:break-word; }
|
||
td.part { text-align:center; color:#333; }
|
||
.footer { position:absolute; left:12mm; bottom:5mm; }
|
||
</style></head><body>
|
||
<div class="page">
|
||
' . $titleBlock . '
|
||
<table><thead>' . $thead . '</thead><tbody>' . $tbody . '</tbody></table>
|
||
</div>
|
||
<div class="footer">' . $nhance . '</div>
|
||
</body></html>';
|
||
}
|
||
|
||
/**
|
||
* @param list<array{title:string,rows:list<array{insurer:string,premium:string,gst:string,total:string}>}> $sections
|
||
*/
|
||
private function renderPremiumHtml(array $sections, string $nhanceLogo): string
|
||
{
|
||
$nhance = is_file($nhanceLogo)
|
||
? '<img src="' . $this->escape($nhanceLogo) . '" style="height:8mm;" />'
|
||
: '<span style="color:#0aa3a0;font-size:11pt;font-weight:bold;">Nhance</span>';
|
||
|
||
$body = '';
|
||
foreach ($sections as $section) {
|
||
$body .= '<div class="section-title">' . $this->escape($section['title']) . '</div>';
|
||
$body .= '<table><thead><tr>
|
||
<th style="width:40%;">Insurer Name</th>
|
||
<th style="width:20%;">Premium</th>
|
||
<th style="width:20%;">GST</th>
|
||
<th style="width:20%;">Total Premium</th>
|
||
</tr></thead><tbody>';
|
||
$i = 0;
|
||
foreach ($section['rows'] as $row) {
|
||
$bg = ($i % 2 === 0) ? '#ffffff' : '#f7fafa';
|
||
$body .= '<tr style="background:' . $bg . ';">
|
||
<td class="left">' . $this->escape($row['insurer']) . '</td>
|
||
<td>' . $this->escape($row['premium']) . '</td>
|
||
<td>' . $this->escape($row['gst']) . '</td>
|
||
<td>' . $this->escape($row['total']) . '</td>
|
||
</tr>';
|
||
$i++;
|
||
}
|
||
$body .= '</tbody></table>';
|
||
}
|
||
|
||
return '
|
||
<html><head><style>
|
||
body { margin:0; padding:0; font-family: dejavusans, sans-serif; color:#222; }
|
||
.page { padding:8mm 14mm 14mm 14mm; }
|
||
.title { font-family: freeserif, DejaVu Serif, serif; color:#1a2744; font-size:20pt; margin:0 0 6mm 0; border-left:3mm solid #0aa3a0; padding-left:5mm; }
|
||
.title em { color:#0aa3a0; font-style:italic; }
|
||
.section-title { font-size:10pt; font-weight:bold; margin:5mm 0 2mm 0; font-family: dejavusans, sans-serif; }
|
||
table { width:100%; border-collapse:collapse; margin-bottom:4mm; }
|
||
th { background:#1a7a6e; color:#fff; font-size:9pt; padding:2.8mm 3mm; text-align:center; font-family: dejavusans, sans-serif; }
|
||
td { font-size:8.5pt; padding:2.5mm 3mm; border-bottom:0.15mm solid #e0eded; text-align:center; }
|
||
td.left { text-align:left; }
|
||
.footer { position:absolute; left:12mm; bottom:5mm; }
|
||
</style></head><body>
|
||
<div class="page">
|
||
<div class="title"><strong>Quote</strong> <em>Comparison</em></div>
|
||
' . $body . '
|
||
</div>
|
||
<div class="footer">' . $nhance . '</div>
|
||
</body></html>';
|
||
}
|
||
}
|