MERGE_TEST_SALES_CR

This commit is contained in:
Ubuntu 2026-05-12 11:26:53 +05:30
commit aff74592d2
5 changed files with 85 additions and 33 deletions

View File

@ -1710,8 +1710,11 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
// leads.type: 1 = EB, else = Non-EB
$wonLeads = $db->query("
SELECT
sal.lead_id,
l.id AS opportunities_id,
l.actual_lead_id,
l.lead_type AS lead_type_id,
sal.company_name AS company,
l.client_name AS client_name,
CASE WHEN l.lead_type = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_type,
l.created_at AS created_at,
l.status
@ -1725,6 +1728,7 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
ORDER BY l.created_at DESC
", [$uid, $fyStart, $fyEnd])->getResultArray();
// -- NOTE: AND TRIM(sal.company_name) = TRIM(l.client_name)
$result[$uid] = [
'total_policies' => $totalPolicies,
'total_exp_amt' => $totalExpAmt,

View File

@ -122,6 +122,10 @@ if (! function_exists('merge_ticket_pdfs')) {
if (! is_dir($tempDir)) {
@mkdir($tempDir, 0775, true);
}
$mpdfCacheDir = $tempDir . DIRECTORY_SEPARATOR . 'mpdf';
if (! is_dir($mpdfCacheDir)) {
@mkdir($mpdfCacheDir, 0775, true);
}
$mergedName = 'merged_' . $ticket_master_id . '_' . time() . '_' . bin2hex(random_bytes(5)) . '.pdf';
$mergedPath = $uploadDir . $mergedName;
@ -271,15 +275,13 @@ if (! function_exists('merge_ticket_pdf_add_image_page')) {
function merge_ticket_pdf_add_image_page(Mpdf $mpdf, string $imagePath): bool
{
$imageInfo = @getimagesize($imagePath);
if (empty($imageInfo[0]) || empty($imageInfo[1])) {
log_message('error', "merge_ticket_pdfs | invalid image | {$imagePath}");
return false;
}
$ext = strtolower(pathinfo($imagePath, PATHINFO_EXTENSION));
$imageType = $ext === 'png' || ((int) ($imageInfo[2] ?? 0) === IMAGETYPE_PNG) ? 'png' : 'jpg';
$imageWidthPx = (int) $imageInfo[0];
$imageHeightPx = (int) $imageInfo[1];
$imageType = ((int) ($imageInfo[2] ?? 0) === IMAGETYPE_PNG) ? 'png' : 'jpg';
$isLandscape = $imageWidthPx > $imageHeightPx;
$imageWidthPx = (int) ($imageInfo[0] ?? 0);
$imageHeightPx = (int) ($imageInfo[1] ?? 0);
$hasSize = $imageWidthPx > 0 && $imageHeightPx > 0;
$isLandscape = $hasSize && $imageWidthPx > $imageHeightPx;
$pageWidth = $isLandscape ? 297 : 210;
$pageHeight = $isLandscape ? 210 : 297;
@ -287,11 +289,21 @@ if (! function_exists('merge_ticket_pdf_add_image_page')) {
$availableWidth = $pageWidth - ($margin * 2);
$availableHeight = $pageHeight - ($margin * 2);
$scale = min($availableWidth / $imageWidthPx, $availableHeight / $imageHeightPx);
$drawWidth = $imageWidthPx * $scale;
$drawHeight = $imageHeightPx * $scale;
$x = ($pageWidth - $drawWidth) / 2;
$y = ($pageHeight - $drawHeight) / 2;
if ($hasSize) {
$scale = min($availableWidth / $imageWidthPx, $availableHeight / $imageHeightPx);
$drawWidth = $imageWidthPx * $scale;
$drawHeight = $imageHeightPx * $scale;
$x = ($pageWidth - $drawWidth) / 2;
$y = ($pageHeight - $drawHeight) / 2;
} else {
// Some PNGs fail getimagesize(), but mPDF can still render them.
// Use a portrait A4 fallback and let mPDF calculate image height.
log_message('error', "merge_ticket_pdfs | image size unavailable, using fallback page | {$imagePath}");
$drawWidth = $availableWidth;
$drawHeight = 0;
$x = $margin;
$y = $margin;
}
$mpdf->AddPageByArray([
'orientation' => $isLandscape ? 'L' : 'P',

View File

@ -21,20 +21,28 @@
/* Leads Grid */
.lead-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; /* responsive */ gap: 15px; }
.lead-actions { display: flex; align-items: center; gap: 10px; }
.sales-toolbar { flex-wrap: nowrap; padding: 16px 30px; gap: 12px; overflow-x: auto; }
.sales-toolbar .filter-tabs { padding: 0; flex: 1 1 auto; min-width: 0; overflow-x: auto; }
.sales-toolbar .lead-actions { flex: 0 0 auto; flex-wrap: nowrap; }
.sales-toolbar .search-input { width: 230px !important; }
.sales-toolbar { flex-wrap: nowrap; padding: 16px 15px; gap: 12px; overflow-x: auto; }
.sales-toolbar .filter-tabs { padding: 0; flex: 0 0 auto; min-width: max-content; overflow: visible; }
.sales-toolbar .lead-actions { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; justify-content: flex-end; }
.sales-toolbar .search-input { width: 230px !important; height: 38px; padding: 9px 12px; border: 1px solid #ddd !important; border-radius: 6px !important; flex: 0 0 230px; }
.sales-toolbar .toolbar-select { width: 175px !important; min-width: 175px; padding: 9px 12px; }
.sales-toolbar .toolbar-select + .select2-container { width: 175px !important; min-width: 175px; }
.sales-toolbar .financial-year-select { width: 108px !important; min-width: 108px; }
.sales-toolbar .financial-year-select { width: 122px !important; min-width: 122px; }
.sales-toolbar .financial-year-select + .select2-container { width: 122px !important; min-width: 122px; flex: 0 0 122px; }
.sales-toolbar .select2-container .select2-selection--single { height: 38px; border: 1px solid #ddd; border-radius: 6px; display: flex; align-items: center; }
.sales-toolbar .select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 36px; }
.sales-toolbar .select2-container--default .select2-selection--single .select2-selection__arrow { height: 36px; }
.sales-toolbar #memberFilter + .select2-container { width: 175px !important; min-width: 175px; flex: 0 0 175px; }
.sales-toolbar #memberFilter + .select2-container .select2-selection--single { height: 38px !important; background: #fff !important; border: 1px solid #ddd !important; border-radius: 6px !important; display: flex; align-items: center; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__rendered { flex: 1; line-height: 36px !important; padding-left: 12px; padding-right: 28px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__arrow { height: 25px !important; width: 20px; top: 1px; right: 6px; }
.sales-toolbar #memberFilter + .select2-container .select2-selection--single,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection--single { height: 38px !important; background: #fff !important; border: 1px solid #ddd !important; border-radius: 6px !important; display: flex; align-items: center; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__rendered,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection__rendered { flex: 1; line-height: 36px !important; padding-left: 12px; padding-right: 28px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__arrow,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection__arrow { height: 25px !important; width: 20px; top: 1px; right: 6px; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__arrow b,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection__arrow b { border-width: 4px 3px 0 3px !important; margin-left: -3px; margin-top: -2px; }
.sales-toolbar #memberFilter + .select2-container--open .select2-selection__arrow b,
.sales-toolbar #financialYearFilter + .select2-container--open .select2-selection__arrow b { border-width: 0 3px 4px 3px !important; }
.toolbar-icon-btn { width: 38px; height: 38px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border: none; border-radius: 8px; color: #fff; cursor: pointer; transition: all 0.2s; }
.toolbar-icon-btn:hover { transform: translateY(-1px); filter: brightness(0.96); }
.toolbar-icon-btn:focus { outline: none; box-shadow: 0 0 0 2px rgba(2, 168, 181, 0.25); }
@ -211,7 +219,7 @@
<option value="<?= $sm['id'] ?>"><?= esc($sm['first_name']) ?></option>
<?php endforeach; ?>
</select>
<select id="financialYearFilter" class="toolbar-select financial-year-select" onchange="fetchActivities(false)">
<select id="financialYearFilter" class="toolbar-select financial-year-select searchable" onchange="fetchActivities(false)">
<?php foreach(($fin_years ?? []) as $year): ?>
<option value="<?= esc($year) ?>" <?= (($current_fin_year ?? '') === $year) ? 'selected' : '' ?>><?= esc($year) ?></option>
<?php endforeach; ?>

View File

@ -104,6 +104,8 @@
th { text-align: left; padding: 10px 12px; color: #8896aa; font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; border-bottom: 1px solid #edf2f7; background: white; font-weight: 700; }
td { padding: 13px 12px; border-bottom: 1px solid #f4f6fb; font-size: 14px; vertical-align: middle; }
tbody tr:hover { background: #fafbfe; }
.won-opportunity-row { cursor: pointer; }
.won-opportunity-row:hover { background: #eff6ff !important; }
/* ── Status Pills ── */
.status-pill { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 700; display: inline-block; }
@ -528,11 +530,21 @@ const progColor= p => p >= 100 ? 'linear-gradient(90deg,#10b981,#34d399)' : p >=
const SALES_LEADS_URL = '<?= base_url('sales') ?>';
const SALES_ACTIVITIES_URL = '<?= base_url('sales/loadactivities') ?>';
const SALES_TARGETS_URL = '<?= base_url('sales/targets') ?>';
const OPPORTUNITY_DETAIL_BASE = '<?= base_url('/util/getLeadNonEB/') ?>';
function getSelectedDashboardFY() {
return document.getElementById('financial_year')?.value || getCurrentFY();
}
function redirectToOpportunity(leadTypeId, actualLeadId, opportunityId) {
if (!leadTypeId || !actualLeadId || !opportunityId) return;
window.location.href = OPPORTUNITY_DETAIL_BASE
+ encodeURIComponent(leadTypeId) + '/'
+ encodeURIComponent(actualLeadId) + '/'
+ encodeURIComponent(opportunityId);
}
function goSalesPage(page, status, memberId) {
const url = new URL(page === 'activities' ? SALES_ACTIVITIES_URL : SALES_LEADS_URL, window.location.origin);
url.searchParams.set('fy', getSelectedDashboardFY());
@ -933,7 +945,15 @@ function openModal(id) {
const typeBadgeClass = l.lead_type === 'EB'
? 'style="background:#eff6ff;color:#2563eb;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"'
: 'style="background:#f0fdf4;color:#16a34a;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"';
return '<tr>'
const canRedirect = l.lead_type_id && l.actual_lead_id && l.opportunities_id;
const rowAttrs = canRedirect
? ' class="won-opportunity-row" title="Open opportunity" onclick=\'redirectToOpportunity('
+ JSON.stringify(String(l.lead_type_id)) + ','
+ JSON.stringify(String(l.actual_lead_id)) + ','
+ JSON.stringify(String(l.opportunities_id)) + ')\''
: '';
return '<tr' + rowAttrs + '>'
+ '<td style="font-weight:700;color:#0f172a;">' + (l.company || '—') + '</td>'
+ '<td><span ' + typeBadgeClass + '>' + (l.lead_type || '—') + '</span></td>'
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'

View File

@ -18,20 +18,28 @@
/* Leads Grid */
.lead-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; /* responsive */ gap: 15px; }
.lead-actions { display: flex; align-items: center; gap: 4px; }
.sales-toolbar { flex-wrap: nowrap; padding: 16px 30px; gap: 12px; overflow-x: auto; }
.sales-toolbar .filter-tabs { padding: 0; flex: 1 1 auto; min-width: 0; overflow-x: auto; }
.sales-toolbar .lead-actions { flex: 0 0 auto; flex-wrap: nowrap; }
.sales-toolbar .search-input { width: 230px !important; }
.sales-toolbar { flex-wrap: nowrap; padding: 16px 15px; gap: 12px; overflow-x: auto; }
.sales-toolbar .filter-tabs { padding: 0; flex: 0 0 auto; min-width: max-content; overflow: visible; }
.sales-toolbar .lead-actions { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; justify-content: flex-end; }
.sales-toolbar .search-input { width: 230px !important; height: 38px; padding: 9px 12px; border: 1px solid #ddd !important; border-radius: 6px !important; flex: 0 0 230px; }
.sales-toolbar .toolbar-select { width: 175px !important; min-width: 175px; padding: 9px 12px; }
.sales-toolbar .toolbar-select + .select2-container { width: 175px !important; min-width: 175px; }
.sales-toolbar .financial-year-select { width: 108px !important; min-width: 108px; }
.sales-toolbar .financial-year-select { width: 122px !important; min-width: 122px; }
.sales-toolbar .financial-year-select + .select2-container { width: 122px !important; min-width: 122px; flex: 0 0 122px; }
.sales-toolbar .select2-container .select2-selection--single { height: 38px; border: 1px solid #ddd; border-radius: 6px; display: flex; align-items: center; }
.sales-toolbar .select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 36px; }
.sales-toolbar .select2-container--default .select2-selection--single .select2-selection__arrow { height: 36px; }
.sales-toolbar #memberFilter + .select2-container { width: 175px !important; min-width: 175px; flex: 0 0 175px; }
.sales-toolbar #memberFilter + .select2-container .select2-selection--single { height: 38px !important; background: #fff !important; border: 1px solid #ddd !important; border-radius: 6px !important; display: flex; align-items: center; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__rendered { flex: 1; line-height: 36px !important; padding-left: 12px; padding-right: 28px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__arrow { height: 25px !important; width: 20px; top: 1px; right: 6px; }
.sales-toolbar #memberFilter + .select2-container .select2-selection--single,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection--single { height: 38px !important; background: #fff !important; border: 1px solid #ddd !important; border-radius: 6px !important; display: flex; align-items: center; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__rendered,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection__rendered { flex: 1; line-height: 36px !important; padding-left: 12px; padding-right: 28px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__arrow,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection__arrow { height: 25px !important; width: 20px; top: 1px; right: 6px; }
.sales-toolbar #memberFilter + .select2-container .select2-selection__arrow b,
.sales-toolbar #financialYearFilter + .select2-container .select2-selection__arrow b { border-width: 4px 3px 0 3px !important; margin-left: -3px; margin-top: -2px; }
.sales-toolbar #memberFilter + .select2-container--open .select2-selection__arrow b,
.sales-toolbar #financialYearFilter + .select2-container--open .select2-selection__arrow b { border-width: 0 3px 4px 3px !important; }
.toolbar-icon-btn { width: 38px; height: 38px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border: none; border-radius: 8px; color: #fff; cursor: pointer; transition: all 0.2s; }
.toolbar-icon-btn:hover { transform: translateY(-1px); filter: brightness(0.96); }
.toolbar-icon-btn:focus { outline: none; box-shadow: 0 0 0 2px rgba(2, 168, 181, 0.25); }
@ -187,7 +195,7 @@
<option value="<?= $sm['id'] ?>"><?= esc($sm['first_name']) ?></option>
<?php endforeach; ?>
</select>
<select id="financialYearFilter" class="toolbar-select financial-year-select" onchange="fetchLeads(false)">
<select id="financialYearFilter" class="toolbar-select financial-year-select searchable" onchange="fetchLeads(false)">
<?php foreach(($fin_years ?? []) as $year): ?>
<option value="<?= esc($year) ?>" <?= (($current_fin_year ?? '') === $year) ? 'selected' : '' ?>><?= esc($year) ?></option>
<?php endforeach; ?>