844 lines
39 KiB
PHP
844 lines
39 KiB
PHP
<?php
|
||
|
||
namespace App\Controllers;
|
||
|
||
use CodeIgniter\RESTful\ResourceController;
|
||
use App\Controllers\BaseController;
|
||
|
||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||
|
||
use App\Models\AgentIncentiveFileModel;
|
||
use App\Models\PartnerInsurancePayoutGridModel;
|
||
use App\Models\AgentModel;
|
||
|
||
|
||
class AgentIncentiveController extends ResourceController
|
||
{
|
||
protected $AgentIncentiveFileModel;
|
||
protected $PayoutGridModel;
|
||
protected $PartnerAgentModel;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->AgentIncentiveFileModel = new AgentIncentiveFileModel();
|
||
$this->PayoutGridModel = new PartnerInsurancePayoutGridModel();
|
||
$this->PartnerAgentModel = new AgentModel();
|
||
}
|
||
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Upload Grid File (file_type = 'grid')
|
||
// Parses Excel and UPSERTs rows into partner_insurance_payout_grid
|
||
// -------------------------------------------------------------------------
|
||
public function uploadPayoutGridFile()
|
||
{
|
||
try {
|
||
/* ====================================================================
|
||
* STEP 1 — Validate POST input
|
||
* ==================================================================== */
|
||
$data = $this->request->getPost();
|
||
$agentId = $data['agent_id'] ?? null;
|
||
$month = $data['incentive_month'] ?? null;
|
||
$createdBy = $data['created_by'] ?? null;
|
||
|
||
if (empty($agentId) || empty($month)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 400,
|
||
'data' => 'agent_id and incentive_month are required.',
|
||
], 200);
|
||
}
|
||
|
||
/* ====================================================================
|
||
* STEP 2 — Duplicate check on partner_agent_incentive_file
|
||
* ==================================================================== */
|
||
$duplicate = $this->AgentIncentiveFileModel
|
||
->where('agent_id', $agentId)
|
||
->where('incentive_month', $month)
|
||
->where('file_type', 'grid')
|
||
->first();
|
||
|
||
if (!empty($duplicate)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 200,
|
||
'data' => 'Duplicate Entry.',
|
||
], 200);
|
||
}
|
||
|
||
/* ====================================================================
|
||
* STEP 3 — Fetch retention_rate from partner_agent using agent_id
|
||
*
|
||
* retention_rate is stored as decimal(4,2) e.g. 2.50
|
||
* Used later to calculate partner_comp / partner_tp / partner_od
|
||
* by subtracting from broker_excel values.
|
||
* If agent not found or retention_rate is NULL → partner fields = null
|
||
* ==================================================================== */
|
||
$agent = $this->PartnerAgentModel->find($agentId);
|
||
$retentionRate = (!empty($agent) && $agent['retention_rate'] !== null)
|
||
? (float) $agent['retention_rate']
|
||
: null;
|
||
|
||
/* ====================================================================
|
||
* STEP 4 — Validate uploaded file (extension check)
|
||
* ==================================================================== */
|
||
$gridFile = $this->request->getFile('incentive_file_name');
|
||
|
||
if (!$gridFile || !$gridFile->isValid()) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 400,
|
||
'data' => 'No valid file uploaded.',
|
||
], 200);
|
||
}
|
||
|
||
$extension = strtolower($gridFile->getClientExtension());
|
||
if (!in_array($extension, ['xlsx', 'xls', 'csv'], true)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 400,
|
||
'data' => 'Only xlsx, xls, csv grid files are allowed.',
|
||
], 200);
|
||
}
|
||
|
||
/* ====================================================================
|
||
* STEP 5 — Move file to upload directory
|
||
* ==================================================================== */
|
||
$uploadPath = WRITEPATH . 'uploads/agent/incentive_file/';
|
||
if (!is_dir($uploadPath)) {
|
||
mkdir($uploadPath, 0777, true);
|
||
}
|
||
|
||
$gridFileName = time() . '_' . $gridFile->getRandomName();
|
||
$gridFile->move($uploadPath, $gridFileName);
|
||
|
||
/* ====================================================================
|
||
* STEP 6 — Insert record into partner_agent_incentive_file
|
||
* (same as uploadAgentIncentiveFile but file_type = 'grid')
|
||
* ==================================================================== */
|
||
$this->AgentIncentiveFileModel->insert([
|
||
'agent_id' => $agentId,
|
||
'incentive_month' => $month,
|
||
'incentive_file_name' => $gridFileName,
|
||
'file_type' => 'grid',
|
||
'is_active' => 1,
|
||
'created_by' => $createdBy,
|
||
]);
|
||
|
||
/* ====================================================================
|
||
* STEP 7 — Parse Excel sheet into a flat array of rows
|
||
* ==================================================================== */
|
||
$spreadsheet = IOFactory::load($uploadPath . $gridFileName);
|
||
$rows = $spreadsheet->getActiveSheet()
|
||
->toArray(null, true, true, false);
|
||
|
||
if (empty($rows)) {
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'code' => 200,
|
||
'data' => [
|
||
'inserted' => 0,
|
||
'updated' => 0,
|
||
'message' => 'File saved but grid sheet is empty — no rows processed.',
|
||
],
|
||
], 200);
|
||
}
|
||
|
||
/* ====================================================================
|
||
* STEP 8 — Helper: normalise a cell value
|
||
* Strips spaces & non-alphanumeric chars, returns lowercase.
|
||
* Used to safely compare header/section values.
|
||
* ==================================================================== */
|
||
$normalize = static function ($value): string {
|
||
$value = strtolower(trim((string) $value));
|
||
return preg_replace('/[^a-z0-9]+/', '', $value) ?? '';
|
||
};
|
||
|
||
/* ====================================================================
|
||
* STEP 9 — Helper: extract numeric value from broker_excel cell
|
||
*
|
||
* Broker excel cells come in mixed formats:
|
||
* "22" → 22.0 (plain number)
|
||
* "4.8" → 4.8
|
||
* "5.5X" → 5.5 (strip trailing X)
|
||
* "NET 1.9X" → 1.9 (strip prefix text + X)
|
||
* "OD 1.5X" → 1.5 (strip OD prefix + X)
|
||
* "1.5 X" → 1.5 (space before X)
|
||
* "OD25+TP10" → 35.0 (compound format, splits and sums up)
|
||
* "OD 20+TP 15"→ 35.0 (compound format, splits and sums up)
|
||
* "" / null → null
|
||
*
|
||
* Returns float|null
|
||
* ==================================================================== */
|
||
$extractNumeric = static function ($value): ?float {
|
||
$str = strtolower(trim((string) $value));
|
||
|
||
// Blank or nan → null
|
||
if ($str === '' || $str === 'nan') {
|
||
return null;
|
||
}
|
||
|
||
// Compound values like "OD25+TP10" or "OD 20+TP 15"
|
||
if (str_contains($str, '+')) {
|
||
$sum = 0.0;
|
||
$hasValid = false;
|
||
foreach (explode('+', $str) as $part) {
|
||
$cleanPart = preg_replace('/^(net|od|tp)\s*/i', '', trim($part));
|
||
$cleanPart = rtrim(trim($cleanPart), 'xX ');
|
||
if (is_numeric($cleanPart)) {
|
||
$sum += (float) $cleanPart;
|
||
$hasValid = true;
|
||
}
|
||
}
|
||
return $hasValid ? $sum : null;
|
||
}
|
||
|
||
// Strip known text prefixes: "net", "od", "tp", spaces
|
||
$str = preg_replace('/^(net|od|tp)\s*/i', '', $str);
|
||
|
||
// Strip trailing "x" or "X" and any surrounding spaces
|
||
$str = rtrim(trim($str), 'xX ');
|
||
|
||
// Now try to parse as float
|
||
if (is_numeric($str)) {
|
||
return (float) $str;
|
||
}
|
||
|
||
return null;
|
||
};
|
||
|
||
/* ====================================================================
|
||
* STEP 10 — Helper: calculate partner rate
|
||
*
|
||
* Formula: partner_value = broker_excel_value - retention_rate
|
||
*
|
||
* Rules:
|
||
* - If broker_excel_value is null → return null
|
||
* - If retention_rate is null → return null
|
||
* - Result rounded to 2 decimal places
|
||
* - Stored as string to match varchar column type
|
||
* ==================================================================== */
|
||
$calcPartnerRate = static function (
|
||
?string $brokerExcelRaw,
|
||
?float $retentionRate,
|
||
callable $extractNumeric
|
||
): ?string {
|
||
// Either side missing → cannot compute partner rate
|
||
if ($brokerExcelRaw === null || $retentionRate === null) {
|
||
return null;
|
||
}
|
||
|
||
$brokerValue = $extractNumeric($brokerExcelRaw);
|
||
|
||
// Could not parse a clean number from the broker value
|
||
if ($brokerValue === null) {
|
||
return null;
|
||
}
|
||
|
||
// partner rate = broker excel value − retention rate
|
||
$partnerValue = round($brokerValue - $retentionRate, 2);
|
||
|
||
return (string) $partnerValue;
|
||
};
|
||
|
||
/* ====================================================================
|
||
* STEP 11 — Walk rows: detect section headers → column headers → data
|
||
*
|
||
* Excel has two column layouts depending on section:
|
||
*
|
||
* Layout A — TWO WHEELER / PCV / GCV / LCV / HCV / BUS / TAXI etc.
|
||
* [0] INSURER | [1] RTO | [2] SEGMENT | [3] COMP | [4] TP
|
||
* [5] REMARKS | [6] BROKER | [7] BROKER_EXCEL_COMP | [8] BROKER_EXCEL_TP
|
||
*
|
||
* Layout B — PRIVATE CAR-TP / fuel-separated sections
|
||
* [0] INSURER | [1] RTO | [2] SEGMENT | [3] TP | [4] FUEL
|
||
* [5] REMARKS | [6] BROKER | [7] BROKER_EXCEL_TP
|
||
* ==================================================================== */
|
||
$currentVehicleType = null;
|
||
$layoutHasComp = true; // true = Layout A, false = Layout B
|
||
$insertedCount = 0;
|
||
$updatedCount = 0;
|
||
|
||
foreach ($rows as $row) {
|
||
|
||
// Pad row to 9 columns so every index access is always safe
|
||
while (count($row) < 9) {
|
||
$row[] = null;
|
||
}
|
||
|
||
$col0 = trim((string) ($row[0] ?? ''));
|
||
$col1 = trim((string) ($row[1] ?? ''));
|
||
$col2 = trim((string) ($row[2] ?? ''));
|
||
$col3 = trim((string) ($row[3] ?? ''));
|
||
$col4 = trim((string) ($row[4] ?? ''));
|
||
|
||
// Consider col1 empty when it is blank or the string "nan"
|
||
$isCol1Empty = ($col1 === '' || strtolower($col1) === 'nan');
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Row type A — Date row (very first row of the sheet) → skip
|
||
* ------------------------------------------------------------------ */
|
||
if ($col0 !== '' && $isCol1Empty && strtotime($col0) !== false) {
|
||
continue;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Row type B — Section-header row
|
||
* Condition: col0 has text, col1 is empty, col0 is NOT "INSURER"
|
||
* Action: set currentVehicleType, reset layout flag
|
||
* ------------------------------------------------------------------ */
|
||
if (
|
||
$col0 !== ''
|
||
&& $isCol1Empty
|
||
&& strtoupper($col0) !== 'INSURER'
|
||
&& $normalize($col0) !== 'nan'
|
||
) {
|
||
$currentVehicleType = strtoupper($col0);
|
||
$layoutHasComp = true; // will be re-detected from next header row
|
||
continue;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Row type C — Column-header row (col0 == "INSURER")
|
||
* Detect layout from col[4]:
|
||
* FUEL / PETROL / DIESEL / TP → Layout B (no comp column)
|
||
* anything else → Layout A (has comp column)
|
||
* ------------------------------------------------------------------ */
|
||
if (strtoupper($col0) === 'INSURER') {
|
||
$col4Upper = strtoupper($col4);
|
||
$layoutHasComp = !(
|
||
str_contains($col4Upper, 'FUEL')
|
||
|| str_contains($col4Upper, 'PETROL')
|
||
|| str_contains($col4Upper, 'DIESEL')
|
||
|| $col4Upper === 'TP'
|
||
);
|
||
continue;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Row type D — Completely blank row → skip
|
||
* ------------------------------------------------------------------ */
|
||
if ($col0 === '' && $col1 === '' && $col2 === '') {
|
||
continue;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
* Row type E — Data row before any section header was seen → skip
|
||
* ------------------------------------------------------------------ */
|
||
if ($currentVehicleType === null) {
|
||
continue;
|
||
}
|
||
|
||
/* ==================================================================
|
||
* MAP COLUMNS TO FIELDS based on detected layout
|
||
* ================================================================== */
|
||
if ($layoutHasComp) {
|
||
/* ----------------------------------------------------------------
|
||
* LAYOUT A (COMP + TP both present)
|
||
* col[3] = COMP (broker rate for comprehensive)
|
||
* col[4] = TP (broker rate for third-party)
|
||
* col[7] = BROKER_EXCEL_COMP
|
||
* col[8] = BROKER_EXCEL_TP
|
||
* ---------------------------------------------------------------- */
|
||
$insurer = $col0 !== '' ? strtoupper($col0) : null;
|
||
$rto = $col1 !== '' ? strtoupper($col1) : null;
|
||
$segment = $col2 !== '' ? strtoupper($col2) : null;
|
||
$comp = $col3 !== '' ? strtoupper($col3) : null;
|
||
$tp = $col4 !== '' ? strtoupper($col4) : null;
|
||
$fuel = null;
|
||
$remarks = trim((string) ($row[5] ?? '')) ?: null;
|
||
$brokerName = trim((string) ($row[6] ?? '')) ?: null;
|
||
$brokerExcelComp = trim((string) ($row[7] ?? '')) ?: null;
|
||
$brokerExcelTp = trim((string) ($row[8] ?? '')) ?: null;
|
||
$brokerExcelOd = null;
|
||
$od = null;
|
||
|
||
/* partner_comp = broker_excel_comp − retention_rate
|
||
partner_tp = broker_excel_tp − retention_rate
|
||
partner_od = null (no OD broker value in Layout A)
|
||
Any of these will be null if broker_excel or retention_rate is null */
|
||
$partnerComp = $calcPartnerRate($brokerExcelComp, $retentionRate, $extractNumeric);
|
||
$partnerTp = $calcPartnerRate($brokerExcelTp, $retentionRate, $extractNumeric);
|
||
$partnerOd = null;
|
||
|
||
} else {
|
||
/* ----------------------------------------------------------------
|
||
* LAYOUT B (TP only, with FUEL column)
|
||
* col[3] = TP (broker rate for third-party)
|
||
* col[4] = FUEL type
|
||
* col[7] = BROKER_EXCEL_TP
|
||
* No COMP or OD broker excel column in this layout
|
||
* ---------------------------------------------------------------- */
|
||
$insurer = $col0 !== '' ? strtoupper($col0) : null;
|
||
$rto = $col1 !== '' ? strtoupper($col1) : null;
|
||
$segment = $col2 !== '' ? strtoupper($col2) : null;
|
||
$comp = null;
|
||
$tp = $col3 !== '' ? strtoupper($col3) : null;
|
||
$fuel = $col4 !== '' ? strtoupper($col4) : null;
|
||
$remarks = trim((string) ($row[5] ?? '')) ?: null;
|
||
$brokerName = trim((string) ($row[6] ?? '')) ?: null;
|
||
$brokerExcelComp = null;
|
||
$brokerExcelTp = trim((string) ($row[7] ?? '')) ?: null;
|
||
$brokerExcelOd = null;
|
||
$od = null;
|
||
|
||
/* partner_comp = null (no comp in Layout B)
|
||
partner_tp = broker_excel_tp − retention_rate
|
||
partner_od = null (no OD broker value in Layout B) */
|
||
$partnerComp = null;
|
||
$partnerTp = $calcPartnerRate($brokerExcelTp, $retentionRate, $extractNumeric);
|
||
$partnerOd = null;
|
||
}
|
||
|
||
// Skip rows that have no essential identifiers
|
||
if (empty($insurer) || empty($segment)) {
|
||
continue;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
* OD-only row detection
|
||
* Some rows encode OD rate inside the comp column (e.g. "OD 1.5X")
|
||
* when tp is empty. Promote comp → od and clear comp.
|
||
* Recalculate partner_od from broker_excel_comp in this case.
|
||
* ------------------------------------------------------------------ */
|
||
if ($comp !== null && stripos($comp, 'OD') === 0 && $tp === null) {
|
||
$od = $comp;
|
||
$comp = null;
|
||
$brokerExcelOd = $brokerExcelComp; // broker excel comp was OD value
|
||
$brokerExcelComp = null;
|
||
|
||
// partner_od = broker_excel_od − retention_rate
|
||
$partnerOd = $calcPartnerRate($brokerExcelOd, $retentionRate, $extractNumeric);
|
||
$partnerComp = null;
|
||
}
|
||
|
||
/* ==================================================================
|
||
* UPSERT into partner_insurance_payout_grid
|
||
* Natural key: vehicle_type + insurer + rto + segment
|
||
* UPDATE if record exists, INSERT otherwise.
|
||
* ================================================================== */
|
||
$existing = $this->PayoutGridModel
|
||
->where('vehicle_type', $currentVehicleType)
|
||
->where('insurer', $insurer)
|
||
->where('rto', $rto ?? '')
|
||
->where('segment', $segment)
|
||
->first();
|
||
|
||
$gridRow = [
|
||
'vehicle_type' => $currentVehicleType,
|
||
'fuel' => $fuel,
|
||
'insurer' => $insurer,
|
||
'rto' => $rto,
|
||
'broker_name' => $brokerName,
|
||
'segment' => $segment,
|
||
'comp' => $comp,
|
||
'tp' => $tp,
|
||
'od' => $od,
|
||
'remarks' => $remarks,
|
||
'broker_excel_comp' => $brokerExcelComp,
|
||
'broker_excel_tp' => $brokerExcelTp,
|
||
'broker_excel_od' => $brokerExcelOd,
|
||
// partner_* = broker_excel_* − retention_rate
|
||
// null when either broker value or retention_rate is missing
|
||
'partner_comp' => $partnerComp,
|
||
'partner_tp' => $partnerTp,
|
||
'partner_od' => $partnerOd,
|
||
];
|
||
|
||
if (!empty($existing)) {
|
||
// Record already exists → UPDATE, stamp updated_by
|
||
$gridRow['updated_by'] = $createdBy;
|
||
$this->PayoutGridModel->update($existing['id'], $gridRow);
|
||
$updatedCount++;
|
||
} else {
|
||
// New record → INSERT, stamp created_by
|
||
$gridRow['created_by'] = $createdBy;
|
||
$this->PayoutGridModel->insert($gridRow);
|
||
$insertedCount++;
|
||
}
|
||
}
|
||
|
||
/* ====================================================================
|
||
* STEP 12 — Return summary response
|
||
* ==================================================================== */
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'code' => 200,
|
||
'data' => [
|
||
'file' => $gridFileName,
|
||
'inserted' => $insertedCount,
|
||
'updated' => $updatedCount,
|
||
'retention_rate' => $retentionRate,
|
||
'message' => "Grid processed: {$insertedCount} inserted, {$updatedCount} updated.",
|
||
],
|
||
], 200);
|
||
|
||
} catch (\Exception $e) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 500,
|
||
'data' => $e->getMessage(),
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// List all grid records (optional utility endpoint)
|
||
// -------------------------------------------------------------------------
|
||
// GET /agent/payoutGrid?role=Manager means all list
|
||
// GET /agent/payoutGrid?role=Manager&insurer=HDFC&vehicle_type=TWO%20WHEELER&plan_type=comp
|
||
// GET /agent/payoutGrid?role=Manager&insurer=HDFC&rto=TN&segment=BIKE&plan_type=comp
|
||
// GET /agent/payoutGrid?role=Manager&insurer=HDFC&segment=BIKE&vehicle_type=TWO%20WHEELER&plan_type=comp
|
||
public function getPayoutGrid()
|
||
{
|
||
try {
|
||
$request = $this->request;
|
||
$role = $request->getGet('role');
|
||
|
||
// 1. Check if Role is provided
|
||
if (!$role) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 400,
|
||
'data' => 'Role is required to fetch payout data.'
|
||
], 400);
|
||
}
|
||
|
||
// 2. Extract Filters
|
||
$insurer = $request->getGet('insurer');
|
||
$rto = $request->getGet('rto');
|
||
$segment = $request->getGet('segment');
|
||
$vehicle_type = $request->getGet('vehicle_type');
|
||
$plan_type = $request->getGet('plan_type');
|
||
|
||
// 3. Build the Grid Query
|
||
$builder = $this->PayoutGridModel->builder();
|
||
$builder->orderBy('vehicle_type', 'ASC')->orderBy('insurer', 'ASC');
|
||
|
||
// Apply filters only for authorized roles
|
||
if (in_array($role, ['Manager', 'Accounts'])) {
|
||
if (!empty($insurer)) $builder->where('insurer', $insurer);
|
||
if (!empty($rto)) $builder->where('rto', $rto);
|
||
if (!empty($segment)) $builder->where('segment', $segment);
|
||
if (!empty($vehicle_type)) $builder->where('vehicle_type', $vehicle_type);
|
||
|
||
// Handle dynamic column selection (comp/tp/od)
|
||
if (!empty($plan_type) && in_array($plan_type, ['comp', 'tp', 'od'])) {
|
||
$builder->select("id, insurer, vehicle_type, segment, rto, $plan_type as payout,fuel,broker_name,comp,tp,od,remarks,broker_excel_comp,
|
||
broker_excel_tp,broker_excel_od,partner_comp,partner_tp,partner_od,created_by,created_at,updated_by,updated_at");
|
||
} else {
|
||
// Default selection if no plan_type or invalid plan_type
|
||
$builder->select('id, insurer, vehicle_type, segment, rto, comp, tp, od, fuel,broker_name,comp,tp,od,remarks,broker_excel_comp,
|
||
broker_excel_tp,broker_excel_od,partner_comp,partner_tp,partner_od,created_by,created_at,updated_by,updated_at');
|
||
}
|
||
}
|
||
|
||
$gridResults = $builder->get()->getResult();
|
||
|
||
// 4. Combine Grid Results with Dropdown Meta-data
|
||
$responseData = [
|
||
'grid' => $gridResults,
|
||
'rtos' => $this->getUniqueColumnValues('rto'),
|
||
'segments' => $this->getUniqueColumnValues('segment'),
|
||
'vehicle_types' => $this->getUniqueColumnValues('vehicle_type'),
|
||
'insurers' => $this->getUniqueColumnValues('insurer'),
|
||
'plan_types' => [
|
||
['value' => 'comp', 'label' => 'Comprehensive'],
|
||
['value' => 'tp', 'label' => 'Third Party'],
|
||
['value' => 'od', 'label' => 'Own Damage']
|
||
]
|
||
];
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'code' => 200,
|
||
'data' => $responseData
|
||
], 200);
|
||
|
||
} catch (\Exception $e) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 500,
|
||
'data' => $e->getMessage()
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Helper function to fetch unique non-empty values for a column
|
||
*/
|
||
private function getUniqueColumnValues($column)
|
||
{
|
||
$results = $this->PayoutGridModel->select($column)
|
||
->distinct()
|
||
->where("$column IS NOT NULL")
|
||
->where("$column !=", '')
|
||
->orderBy($column, 'ASC')
|
||
->findAll();
|
||
|
||
return array_column($results, $column);
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Update grid row by id
|
||
// Route: POST /agent/updateGrid
|
||
// -------------------------------------------------------------------------
|
||
public function updateGrid()
|
||
{
|
||
try {
|
||
$jsonData = (array) ($this->request->getJSON(true) ?? []);
|
||
$postData = $this->request->getPost() ?? [];
|
||
$data = !empty($jsonData) ? $jsonData : $postData;
|
||
|
||
$id = isset($data['id']) ? (int) $data['id'] : 0;
|
||
if ($id <= 0) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 400,
|
||
'data' => 'id is required.',
|
||
], 200);
|
||
}
|
||
|
||
$existing = $this->PayoutGridModel->find($id);
|
||
if (empty($existing)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 404,
|
||
'data' => 'Grid record not found.',
|
||
], 200);
|
||
}
|
||
|
||
$updateData = [
|
||
'vehicle_type' => array_key_exists('vehicle_type', $data) ? $data['vehicle_type'] : null,
|
||
'fuel' => array_key_exists('fuel', $data) ? $data['fuel'] : null,
|
||
'rto' => array_key_exists('rto', $data) ? $data['rto'] : null,
|
||
'segment' => array_key_exists('segment', $data) ? $data['segment'] : null,
|
||
'broker_name' => array_key_exists('broker_name', $data) ? $data['broker_name'] : null,
|
||
'comp' => array_key_exists('comp', $data) ? $data['comp'] : null,
|
||
'tp' => array_key_exists('tp', $data) ? $data['tp'] : null,
|
||
'od' => array_key_exists('od', $data) ? $data['od'] : null,
|
||
'broker_excel_comp' => array_key_exists('broker_excel_comp', $data) ? $data['broker_excel_comp'] : null,
|
||
'broker_excel_tp' => array_key_exists('broker_excel_tp', $data) ? $data['broker_excel_tp'] : null,
|
||
'broker_excel_od' => array_key_exists('broker_excel_od', $data) ? $data['broker_excel_od'] : null,
|
||
'partner_comp' => array_key_exists('partner_comp', $data) ? $data['partner_comp'] : null,
|
||
'partner_tp' => array_key_exists('partner_tp', $data) ? $data['partner_tp'] : null,
|
||
'partner_od' => array_key_exists('partner_od', $data) ? $data['partner_od'] : null,
|
||
'remarks' => array_key_exists('remarks', $data) ? $data['remarks'] : null,
|
||
];
|
||
|
||
$hasAnyField = false;
|
||
foreach (array_keys($updateData) as $field) {
|
||
if (array_key_exists($field, $data)) {
|
||
$hasAnyField = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$hasAnyField) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 400,
|
||
'data' => 'No update fields provided.',
|
||
], 200);
|
||
}
|
||
|
||
if (array_key_exists('updated_by', $data)) {
|
||
$updateData['updated_by'] = $data['updated_by'];
|
||
}
|
||
|
||
$this->PayoutGridModel->updateGridById($id, $updateData);
|
||
$updated = $this->PayoutGridModel->find($id);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'code' => 200,
|
||
'data' => $updated,
|
||
], 200);
|
||
} catch (\Exception $e) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 500,
|
||
'data' => $e->getMessage(),
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 2. Download Filtered Grid in Excel
|
||
// Route: $routes->get('agent/downloadGrid', 'AgentIncentiveController::downloadGridInExcel');
|
||
// Purpose: Applies the exact same filters as getPayoutGrid(), but instead
|
||
// of returning JSON, it generates and downloads an Excel file.
|
||
// -------------------------------------------------------------------------
|
||
public function downloadGridInExcel()
|
||
{
|
||
try {
|
||
$request = $this->request;
|
||
|
||
// Get GET parameters for filtering
|
||
$role = $request->getGet('role');
|
||
$insurer = $request->getGet('insurer');
|
||
$rto = $request->getGet('rto');
|
||
$segment = $request->getGet('segment');
|
||
$vehicle_type = $request->getGet('vehicle_type');
|
||
$plan_type = $request->getGet('plan_type'); // comp, tp, od
|
||
|
||
$builder = $this->PayoutGridModel->builder();
|
||
$builder->orderBy('vehicle_type', 'ASC')->orderBy('insurer', 'ASC');
|
||
|
||
// Apply filters if the user is a Manager or Accounts role
|
||
if (in_array($role, ['Manager', 'Accounts'])) {
|
||
if (!empty($insurer)) $builder->where('insurer', $insurer);
|
||
if (!empty($rto)) $builder->where('rto', $rto);
|
||
if (!empty($segment)) $builder->where('segment', $segment);
|
||
if (!empty($vehicle_type)) $builder->where('vehicle_type', $vehicle_type);
|
||
}
|
||
|
||
// Fetch the filtered records
|
||
$records = $builder->get()->getResultArray();
|
||
|
||
if (empty($records)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 404,
|
||
'data' => 'No records found to export based on your filters.'
|
||
], 404);
|
||
}
|
||
|
||
// --- Start Excel Generation ---
|
||
$spreadsheet = new Spreadsheet();
|
||
$sheet = $spreadsheet->getActiveSheet();
|
||
|
||
// Setup Header Row
|
||
$sheet->setCellValue('A1', 'Vehicle Type');
|
||
$sheet->setCellValue('B1', 'Insurer');
|
||
$sheet->setCellValue('C1', 'RTO');
|
||
$sheet->setCellValue('D1', 'Segment');
|
||
$sheet->setCellValue('E1', 'Broker Name'); // Changed from 'ID' for clarity
|
||
|
||
// Set the dynamic column header based on the selected plan type
|
||
if ($plan_type === 'comp') {
|
||
$sheet->setCellValue('F1', 'Comp');
|
||
$sheet->setCellValue('G1', 'Comp');
|
||
} elseif ($plan_type === 'tp') {
|
||
$sheet->setCellValue('F1', 'TP');
|
||
$sheet->setCellValue('G1', 'TP');
|
||
} elseif ($plan_type === 'od') {
|
||
$sheet->setCellValue('F1', 'OD');
|
||
$sheet->setCellValue('G1', 'OD');
|
||
} else {
|
||
// If no specific plan type is selected, show all
|
||
$sheet->setCellValue('F1', 'Comp');
|
||
$sheet->setCellValue('G1', 'TP');
|
||
$sheet->setCellValue('H1', 'OD');
|
||
|
||
$sheet->setCellValue('I1', 'Comp');
|
||
$sheet->setCellValue('J1', 'TP');
|
||
$sheet->setCellValue('K1', 'OD');
|
||
}
|
||
|
||
// Populate Excel Rows
|
||
$rowNumber = 2; // Start on row 2 (row 1 is headers)
|
||
foreach ($records as $row) {
|
||
$sheet->setCellValue('A' . $rowNumber, $row['vehicle_type']);
|
||
$sheet->setCellValue('B' . $rowNumber, $row['insurer']);
|
||
$sheet->setCellValue('C' . $rowNumber, $row['rto']);
|
||
$sheet->setCellValue('D' . $rowNumber, $row['segment']);
|
||
$sheet->setCellValue('E' . $rowNumber, $row['broker_name']);
|
||
|
||
// Output dynamic columns based on selected plan type
|
||
if ($plan_type === 'comp') {
|
||
$sheet->setCellValue('F' . $rowNumber, $row['comp']);
|
||
$sheet->setCellValue('G' . $rowNumber, $row['partner_comp']);
|
||
} elseif ($plan_type === 'tp') {
|
||
$sheet->setCellValue('F' . $rowNumber, $row['tp']);
|
||
$sheet->setCellValue('G' . $rowNumber, $row['partner_tp']);
|
||
} elseif ($plan_type === 'od') {
|
||
$sheet->setCellValue('F' . $rowNumber, $row['od']);
|
||
$sheet->setCellValue('G' . $rowNumber, $row['partner_od']);
|
||
} else {
|
||
$sheet->setCellValue('F' . $rowNumber, $row['comp']);
|
||
$sheet->setCellValue('G' . $rowNumber, $row['tp']);
|
||
$sheet->setCellValue('H' . $rowNumber, $row['od']);
|
||
$sheet->setCellValue('I' . $rowNumber, $row['partner_comp']);
|
||
$sheet->setCellValue('J' . $rowNumber, $row['partner_tp']);
|
||
$sheet->setCellValue('K' . $rowNumber, $row['partner_od']);
|
||
}
|
||
$rowNumber++;
|
||
}
|
||
|
||
// Auto-size columns for better readability
|
||
foreach (range('A', $sheet->getHighestColumn()) as $columnID) {
|
||
$sheet->getColumnDimension($columnID)->setAutoSize(true);
|
||
}
|
||
|
||
// Write the file to a temporary location
|
||
$writer = new Xlsx($spreadsheet);
|
||
$fileName = 'Filtered_Payout_Grid_' . date('Y-m-d_H-i') . '.xlsx';
|
||
$tempFile = tempnam(sys_get_temp_dir(), 'grid_export');
|
||
$writer->save($tempFile);
|
||
|
||
// Return the file as a direct download response
|
||
return $this->response->download($tempFile, null)->setFileName($fileName);
|
||
|
||
} catch (\Exception $e) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 500,
|
||
'data' => 'Failed to generate Excel file: ' . $e->getMessage()
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 3. Download the Originally Uploaded Reference Grid
|
||
// Route: $routes->get('agent/downloadExistGrid', 'AgentIncentiveController::downloadLastestGrid');
|
||
// Purpose: Finds the most recently uploaded physical Excel file (where file_type='grid')
|
||
// from the partner_agent_incentive_file table and initiates a download.
|
||
// -------------------------------------------------------------------------
|
||
public function downloadLastestGrid()
|
||
{
|
||
try {
|
||
// Find the latest file entry in the database where file_type is 'grid'
|
||
$latestFileRecord = $this->AgentIncentiveFileModel
|
||
->where('file_type', 'grid')
|
||
->orderBy('created_on', 'DESC')
|
||
->first();
|
||
|
||
if (empty($latestFileRecord)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 404,
|
||
'data' => 'No uploaded reference grid file found in the database.'
|
||
], 404);
|
||
}
|
||
|
||
// Construct the exact file path where it was saved during upload
|
||
$fileName = $latestFileRecord['incentive_file_name'];
|
||
$filePath = WRITEPATH . 'uploads/agent/incentive_file/' . $fileName;
|
||
|
||
// Check if the physical file actually exists on the server
|
||
if (!file_exists($filePath)) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 404,
|
||
'data' => 'The file record exists, but the physical file is missing from the server.'
|
||
], 404);
|
||
}
|
||
|
||
// Initiate the download of the physical file
|
||
return $this->response->download($filePath, null)->setFileName('Reference_Grid_' . $fileName);
|
||
|
||
} catch (\Exception $e) {
|
||
return $this->respond([
|
||
'status' => 'failed',
|
||
'code' => 500,
|
||
'data' => 'Error attempting to download file: ' . $e->getMessage()
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
}
|