From 114f697fb9d7253624ed4adef98253a84922cc15 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Fri, 3 Apr 2026 10:25:09 +0530 Subject: [PATCH] GWM : commission amount update api --- app/Config/Routes.php | 4 +- app/Controllers/PolicyController.php | 321 ++++++++++++++++++++++++++- 2 files changed, 323 insertions(+), 2 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 388edbe..a0377d1 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -130,7 +130,9 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) { $routes->get("policy/vehicleTypeMaster", "PolicyController::vehicleTypeMaster"); $routes->get("policy/fuelTypeMaster", "PolicyController::fuelTypeMaster"); $routes->get("policy/softdelete", "PolicyController::softdelete"); - + $routes->get('policy/downloadPendingCommissionExcel', 'PolicyController::downloadPendingCommissionExcel'); + $routes->post('policy/uploadCommissionExcel', 'PolicyController::uploadCommissionExcel'); + $routes->post('policy/payoutInilneEditUpdate', 'PolicyController::payoutInilneEditUpdate'); //claims $routes->get('claim/ClaimList', 'ClaimController::ClaimList'); diff --git a/app/Controllers/PolicyController.php b/app/Controllers/PolicyController.php index 855ade8..213088c 100644 --- a/app/Controllers/PolicyController.php +++ b/app/Controllers/PolicyController.php @@ -5,6 +5,8 @@ use CodeIgniter\RESTful\ResourceController; use App\Models\PolicyModel; use App\Models\EnquiryModel; use App\Models\QuotationModel; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\IOFactory; class PolicyController extends ResourceController { @@ -1411,14 +1413,331 @@ class PolicyController extends ResourceController } } + /** + * Stream a simple XLSX (title row, header row, data rows) without totals row. + */ + private function streamPendingCommissionExcel(array $header, array $data, string $title, string $fileName): void + { + while (ob_get_level() > 0) { + ob_end_clean(); + } + $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $columnCount = count($header); + $lastCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($columnCount); - + $sheet->mergeCells("A1:{$lastCol}1"); + $sheet->setCellValue('A1', $title); + $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14); + $sheet->getStyle('A1')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER); + $sheet->fromArray($header, null, 'A2'); + $sheet->getStyle("A2:{$lastCol}2")->getFont()->setBold(true); + $sheet->getStyle("A2:{$lastCol}2")->getFill() + ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) + ->getStartColor()->setARGB('F2F2F2'); + if ($data !== []) { + $sheet->fromArray($data, null, 'A3'); + } + for ($i = 1; $i <= $columnCount; $i++) { + $col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i); + $sheet->getColumnDimension($col)->setAutoSize(true); + } + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header("Content-Disposition: attachment; filename=\"{$fileName}\""); + header('Cache-Control: max-age=0'); + + $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); + $writer->save('php://output'); + exit(); + } + + /** + * GET: from_date, to_date (optional), agent_code (optional) — at least one filter required. + * Exports policies where commission_amount is NULL or 0 (active policies only). + */ + public function downloadPendingCommissionExcel() + { + try { + $fromDate = $this->request->getGet('from_date'); + $toDate = $this->request->getGet('to_date'); + $agentCode = $this->request->getGet('agent_code'); + $agentCode = $agentCode !== null && $agentCode !== '' ? trim((string) $agentCode) : null; + + if ($fromDate === null || $fromDate === '') { + $fromDate = null; + } + if ($toDate === null || $toDate === '') { + $toDate = null; + } + + if ($fromDate === null && $toDate === null && $agentCode === null) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'Provide at least one of: from_date, to_date, or agent_code', + ], 400); + } + + $db = \Config\Database::connect(); + $builder = $db->table('partner_policy pp'); + $builder->select('pa.agent_code, pp.policy_number'); + $builder->join('partner_agent pa', 'pa.id = pp.agent_id', 'left'); + $builder->where('pp.is_active', 1); + $builder->groupStart() + ->where('pp.commission_amount', null) + ->orWhere('pp.commission_amount', 0) + ->groupEnd(); + + if ($fromDate !== null) { + $builder->where('pp.issued_date >=', date('Y-m-d', strtotime((string) $fromDate))); + } + if ($toDate !== null) { + $builder->where('pp.issued_date <=', date('Y-m-d', strtotime((string) $toDate))); + } + if ($agentCode !== null) { + $builder->where('pa.agent_code', $agentCode); + } + + $builder->orderBy('pa.agent_code', 'ASC'); + $builder->orderBy('pp.policy_number', 'ASC'); + + $rows = $builder->get()->getResultArray(); + + $dataRows = []; + foreach ($rows as $row) { + $dataRows[] = [ + $row['agent_code'] ?? '', + $row['policy_number'] ?? '', + '', + ]; + } + + $header = ['Agent Code', 'Policy Number', 'Payout Amount']; + + $fromPart = $fromDate !== null ? date('Y-m-d', strtotime((string) $fromDate)) : 'na'; + $toPart = $toDate !== null ? date('Y-m-d', strtotime((string) $toDate)) : 'na'; + $agentPart = $agentCode !== null + ? preg_replace('/[^A-Za-z0-9_-]+/', '_', $agentCode) + : 'all_agents'; + $fileName = "commission_pending_{$fromPart}_{$toPart}_{$agentPart}.xlsx"; + + $title = 'Pending commission payout (fill Payout Amount and re-upload)'; + + $this->streamPendingCommissionExcel($header, $dataRows, $title, $fileName); + } catch (\Throwable $e) { + return $this->respond([ + 'status' => 'failed', + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + + /** + * POST multipart field: commission_excel (xlsx). Updates partner_policy.commission_amount by policy number. + */ + public function uploadCommissionExcel() + { + try { + $file = $this->request->getFile('commission_excel'); + if ($file === null || ! $file->isValid()) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'commission_excel file is required (xlsx)', + ], 400); + } + + $ext = strtolower((string) $file->getClientExtension()); + if ($ext !== 'xlsx') { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'Only .xlsx files are supported', + ], 400); + } + + $spreadsheet = IOFactory::load($file->getTempName()); + $sheet = $spreadsheet->getActiveSheet(); + $highestRow = (int) $sheet->getHighestDataRow(); + + $headerRowNum = null; + for ($r = 1; $r <= min(5, $highestRow); $r++) { + $a = strtolower(trim((string) $sheet->getCell(Coordinate::stringFromColumnIndex(1) . $r)->getValue())); + $b = strtolower(trim((string) $sheet->getCell(Coordinate::stringFromColumnIndex(2) . $r)->getValue())); + if ($a === 'agent code' && str_contains($b, 'policy')) { + $headerRowNum = $r; + break; + } + } + + if ($headerRowNum === null) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'Could not find header row (Agent Code, Policy Number)', + ], 400); + } + + $db = \Config\Database::connect(); + $db->transStart(); + + $updated = 0; + $skipped = 0; + $errors = []; + + for ($r = $headerRowNum + 1; $r <= $highestRow; $r++) { + $policyNumber = trim((string) $sheet->getCell('B' . $r)->getFormattedValue()); + $payoutRaw = $sheet->getCell('C' . $r)->getCalculatedValue(); + + if ($policyNumber === '') { + continue; + } + + if ($payoutRaw === null || $payoutRaw === '') { + $skipped++; + continue; + } + + if (is_numeric($payoutRaw)) { + $amount = (float) $payoutRaw; + } else { + $amount = (float) str_replace([',', ' '], '', (string) $payoutRaw); + } + + if ($amount < 0 || is_nan($amount)) { + $errors[] = "Row {$r}: invalid payout for policy {$policyNumber}"; + continue; + } + + $policy = $this->PolicyModel->where('policy_number', $policyNumber)->where('is_active', 1)->first(); + if (! $policy) { + $errors[] = "Row {$r}: policy not found: {$policyNumber}"; + continue; + } + + $this->PolicyModel->update((int) $policy['id'], ['commission_amount' => $amount]); + $updated++; + } + + $db->transComplete(); + + if ($db->transStatus() === false) { + return $this->respond([ + 'status' => 'failed', + 'code' => 500, + 'data' => 'Transaction failed', + ], 500); + } + + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => [ + 'updated_rows' => $updated, + 'skipped_empty_payout' => $skipped, + 'errors' => $errors, + ], + ], 200); + } catch (\Throwable $e) { + return $this->respond([ + 'status' => 'failed', + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + + /** + * POST JSON: id (partner_policy.id), commission_amount (number or null). + */ + public function payoutInilneEditUpdate() + { + try { + $data = $this->request->getJSON(true); + if (! is_array($data)) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'Invalid JSON body', + ], 400); + } + + if (! isset($data['id']) || $data['id'] === '' || $data['id'] === null) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'id is required', + ], 400); + } + + if (! array_key_exists('commission_amount', $data)) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'commission_amount is required', + ], 400); + } + + $id = (int) $data['id']; + $policy = $this->PolicyModel->find($id); + if (! $policy) { + return $this->respond([ + 'status' => 'failed', + 'code' => 404, + 'data' => 'Data Not Found', + ], 404); + } + + $raw = $data['commission_amount']; + if ($raw === null || $raw === '') { + $commission = null; + } elseif (is_numeric($raw)) { + $commission = (float) $raw; + } else { + $clean = str_replace([',', ' '], '', (string) $raw); + if ($clean === '' || ! is_numeric($clean)) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'commission_amount must be numeric or null', + ], 400); + } + $commission = (float) $clean; + } + + if ($commission !== null && ($commission < 0 || is_nan($commission))) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => 'commission_amount cannot be negative', + ], 400); + } + + $this->PolicyModel->update($id, ['commission_amount' => $commission]); + + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => [ + 'id' => $id, + 'commission_amount' => $commission, + ], + ], 200); + } catch (\Throwable $e) { + return $this->respond([ + 'status' => 'failed', + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + }