FIX_Import And Export rate in Excel File
This commit is contained in:
parent
06cedd8c4e
commit
faf29f47b2
@ -76,6 +76,8 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
|
|||||||
$routes->post('agent/uploadAgentIncentiveFile', 'AgentController::uploadAgentIncentiveFile');
|
$routes->post('agent/uploadAgentIncentiveFile', 'AgentController::uploadAgentIncentiveFile');
|
||||||
$routes->get('agent/deleteAgentIncentiveFile', 'AgentController::deleteAgentIncentiveFile');
|
$routes->get('agent/deleteAgentIncentiveFile', 'AgentController::deleteAgentIncentiveFile');
|
||||||
$routes->get('agent/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile');
|
$routes->get('agent/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile');
|
||||||
|
$routes->get('agent/exportRetentionRateExcel', 'ExcelExportController::exportAgentRetentionRateExcel');
|
||||||
|
$routes->post('agent/importRetentionRateExcel', 'ExcelExportController::importAgentRetentionRateExcel');
|
||||||
|
|
||||||
$routes->get('grid', 'AgentIncentiveController::getGridData');
|
$routes->get('grid', 'AgentIncentiveController::getGridData');
|
||||||
$routes->post('grid/upload', 'AgentIncentiveController::uploadGridFile');
|
$routes->post('grid/upload', 'AgentIncentiveController::uploadGridFile');
|
||||||
|
|||||||
@ -330,6 +330,9 @@ class AgentIncentiveController extends ResourceController
|
|||||||
$builder->select('id, insurer, vehicle_type, segment, rto, comp, tp, od, fuel,broker_name,comp,tp,od,remarks,broker_comp,
|
$builder->select('id, insurer, vehicle_type, segment, rto, comp, tp, od, fuel,broker_name,comp,tp,od,remarks,broker_comp,
|
||||||
broker_tp,broker_od,created_by,created_at,updated_by,updated_at');
|
broker_tp,broker_od,created_by,created_at,updated_by,updated_at');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Agent (and any other role): always return vehicle_type + payout columns for retention / partner_VT matching
|
||||||
|
$builder->select('id, insurer, vehicle_type, segment, rto, comp, tp, od, fuel, broker_name, remarks, broker_comp, broker_tp, broker_od, partner_agent_incentive_file_id, created_at, updated_at', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file_id) {
|
if ($file_id) {
|
||||||
@ -361,12 +364,35 @@ class AgentIncentiveController extends ResourceController
|
|||||||
|
|
||||||
$gridResults = $builder->get()->getResultArray();
|
$gridResults = $builder->get()->getResultArray();
|
||||||
|
|
||||||
// Partner (Agent) login: per–vehicle-type retention from partner_retention_rate (+ partner_vehicle_type name match to grid vehicle_type)
|
// Manager / Accounts: comp/tp/od — no calculation; invalid → '-'
|
||||||
|
if (in_array($role, ['Manager', 'Accounts'], true)) {
|
||||||
|
$gridResults = PartnerPayoutGridRetention::applyManagerAccountsDisplayToRows($gridResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partner (Agent): partner_VT / partner_RR + adjusted comp/tp/od when logged_id is present
|
||||||
if (strtolower(trim((string) $role)) === 'agent') {
|
if (strtolower(trim((string) $role)) === 'agent') {
|
||||||
$agentId = (int) trim((string) ($request->getGet('logged_id') ?? $request->getGet('agent_id') ?? 0));
|
$agentId = (int) trim((string) ($request->getGet('logged_id') ?? $request->getGet('agent_id') ?? 0));
|
||||||
if ($agentId > 0) {
|
if ($agentId > 0) {
|
||||||
$db = \Config\Database::connect();
|
$db = \Config\Database::connect();
|
||||||
$gridResults = PartnerPayoutGridRetention::applyToRows($gridResults, $agentId, $db);
|
$gridResults = PartnerPayoutGridRetention::applyToRows($gridResults, $agentId, $db);
|
||||||
|
} else {
|
||||||
|
foreach ($gridResults as &$r) {
|
||||||
|
$r['oa'] = $r['comp'] ?? null;
|
||||||
|
$r['ob'] = $r['tp'] ?? null;
|
||||||
|
$r['oc'] = $r['od'] ?? null;
|
||||||
|
$r['partner_VT'] = '-';
|
||||||
|
$r['partner_RR'] = '-';
|
||||||
|
if (array_key_exists('comp', $r)) {
|
||||||
|
$r['comp'] = '-';
|
||||||
|
}
|
||||||
|
if (array_key_exists('tp', $r)) {
|
||||||
|
$r['tp'] = '-';
|
||||||
|
}
|
||||||
|
if (array_key_exists('od', $r)) {
|
||||||
|
$r['od'] = '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,8 @@ use CodeIgniter\RESTful\ResourceController;
|
|||||||
|
|
||||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; // <--- THIS MUST BE HERE
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; // <--- THIS MUST BE HERE
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
@ -2581,10 +2583,13 @@ class ExcelExportController extends ResourceController
|
|||||||
|
|
||||||
$isAgent = strtolower($role) === 'agent';
|
$isAgent = strtolower($role) === 'agent';
|
||||||
|
|
||||||
// ✅ Per–vehicle retention map (Agent only); partner_agent.retention_rate is unused
|
// ✅ partner_VT / partner_RR + Agent comp/tp/od = partner_RR − original (Agent only)
|
||||||
$retentionMap = [];
|
$metaMap = [];
|
||||||
|
$masterVt = [];
|
||||||
if ($isAgent && $loggedId !== '') {
|
if ($isAgent && $loggedId !== '') {
|
||||||
$retentionMap = PartnerPayoutGridRetention::buildRetentionMap($this->db, (int) $loggedId);
|
$agentPk = (int) $loggedId;
|
||||||
|
$metaMap = PartnerPayoutGridRetention::buildPartnerMetaMap($this->db, $agentPk);
|
||||||
|
$masterVt = PartnerPayoutGridRetention::buildMasterNormLabelToVehicleTypeIdMap($this->db);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Main query
|
// ✅ Main query
|
||||||
@ -2637,7 +2642,9 @@ class ExcelExportController extends ResourceController
|
|||||||
// =========================
|
// =========================
|
||||||
// ✅ HEADERS
|
// ✅ HEADERS
|
||||||
// =========================
|
// =========================
|
||||||
$headers = ['S.No', 'Insurer', 'Vehicle Type', 'Segment', 'RTO'];
|
$headers = ['S.No', 'Insurer', 'Vehicle Type'];
|
||||||
|
$headers[] = 'Segment';
|
||||||
|
$headers[] = 'RTO';
|
||||||
|
|
||||||
if ($isAgent) {
|
if ($isAgent) {
|
||||||
if ($planType === 'comp') {
|
if ($planType === 'comp') {
|
||||||
@ -2664,21 +2671,27 @@ class ExcelExportController extends ResourceController
|
|||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
|
|
||||||
$comp = isset($row['comp']) ? (float) $row['comp'] : 0;
|
$compRaw = $row['comp'] ?? null;
|
||||||
$tp = isset($row['tp']) ? (float) $row['tp'] : 0;
|
$tpRaw = $row['tp'] ?? null;
|
||||||
$od = isset($row['od']) ? (float) $row['od'] : 0;
|
$odRaw = $row['od'] ?? null;
|
||||||
|
|
||||||
$compOut = $comp;
|
$compOut = PartnerPayoutGridRetention::gridPayoutDisplayManagerAccounts($compRaw);
|
||||||
$tpOut = $tp;
|
$tpOut = PartnerPayoutGridRetention::gridPayoutDisplayManagerAccounts($tpRaw);
|
||||||
$odOut = $od;
|
$odOut = PartnerPayoutGridRetention::gridPayoutDisplayManagerAccounts($odRaw);
|
||||||
|
|
||||||
// ✅ Agent: subtract partner_retention_rate when grid vehicle_type matches partner_vehicle_type for this agent
|
$pm = null;
|
||||||
if ($isAgent && $retentionMap !== []) {
|
// ✅ Agent + logged_id: PRR match + per-column calc only when original value is positive
|
||||||
$rate = PartnerPayoutGridRetention::retentionForRow($row, $retentionMap);
|
if ($isAgent && $loggedId !== '') {
|
||||||
if ($rate !== null) {
|
$pm = PartnerPayoutGridRetention::resolvePartnerMetaForGridRow($row, $metaMap, $masterVt);
|
||||||
$compOut = PartnerPayoutGridRetention::adjustPayoutValue($row['comp'] ?? 0, $rate);
|
if ($pm === null) {
|
||||||
$tpOut = PartnerPayoutGridRetention::adjustPayoutValue($row['tp'] ?? 0, $rate);
|
$compOut = '-';
|
||||||
$odOut = PartnerPayoutGridRetention::adjustPayoutValue($row['od'] ?? 0, $rate);
|
$tpOut = '-';
|
||||||
|
$odOut = '-';
|
||||||
|
} else {
|
||||||
|
$rr = (float) $pm['partner_RR'];
|
||||||
|
$compOut = PartnerPayoutGridRetention::agentPayoutColumnFromPartnerRr($compRaw, $rr);
|
||||||
|
$tpOut = PartnerPayoutGridRetention::agentPayoutColumnFromPartnerRr($tpRaw, $rr);
|
||||||
|
$odOut = PartnerPayoutGridRetention::agentPayoutColumnFromPartnerRr($odRaw, $rr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2700,10 +2713,10 @@ class ExcelExportController extends ResourceController
|
|||||||
$line[] = $odOut;
|
$line[] = $odOut;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ✅ Manager / Accounts → always all
|
// ✅ Manager / Accounts → formatted comp/tp/od (invalid → '-')
|
||||||
$line[] = $comp;
|
$line[] = $compOut;
|
||||||
$line[] = $tp;
|
$line[] = $tpOut;
|
||||||
$line[] = $od;
|
$line[] = $odOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
$line[] = $row['remarks'] ?? '-';
|
$line[] = $row['remarks'] ?? '-';
|
||||||
@ -2725,6 +2738,388 @@ class ExcelExportController extends ResourceController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function exportAgentRetentionRateExcel()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$vehicleTypes = $this->db->table('partner_vehicle_type')
|
||||||
|
->select('id, vehicle_type')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->orderBy('vehicle_type', 'ASC')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
|
||||||
|
$agents = $this->db->table('partner_agent')
|
||||||
|
->select('id, agent_code')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->where('agent_code IS NOT NULL', null, false)
|
||||||
|
->where('agent_code !=', '')
|
||||||
|
->orderBy('agent_code', 'ASC')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
|
||||||
|
$rates = $this->db->table('partner_retention_rate')
|
||||||
|
->select('agent_id, vehicle_type_id, retention_rate')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
|
||||||
|
$rateMap = [];
|
||||||
|
foreach ($rates as $r) {
|
||||||
|
$aId = (int) ($r['agent_id'] ?? 0);
|
||||||
|
$vId = (int) ($r['vehicle_type_id'] ?? 0);
|
||||||
|
if ($aId <= 0 || $vId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$rateMap[$aId . '_' . $vId] = (float) ($r['retention_rate'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
|
||||||
|
$sheet->setCellValue('A1', 'Agent Code / Vehicle Type');
|
||||||
|
$colIndex = 2;
|
||||||
|
foreach ($vehicleTypes as $vt) {
|
||||||
|
$cell = Coordinate::stringFromColumnIndex($colIndex++) . '1';
|
||||||
|
$sheet->setCellValue($cell, (string) ($vt['vehicle_type'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowIndex = 2;
|
||||||
|
foreach ($agents as $agent) {
|
||||||
|
$agentId = (int) ($agent['id'] ?? 0);
|
||||||
|
$sheet->setCellValue('A' . $rowIndex, (string) ($agent['agent_code'] ?? ''));
|
||||||
|
|
||||||
|
$colIndex = 2;
|
||||||
|
foreach ($vehicleTypes as $vt) {
|
||||||
|
$vtId = (int) ($vt['id'] ?? 0);
|
||||||
|
$key = $agentId . '_' . $vtId;
|
||||||
|
$cell = Coordinate::stringFromColumnIndex($colIndex++) . $rowIndex;
|
||||||
|
$sheet->setCellValue($cell, (float) ($rateMap[$key] ?? 0));
|
||||||
|
}
|
||||||
|
$rowIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastCol = Coordinate::stringFromColumnIndex(max(1, count($vehicleTypes) + 1));
|
||||||
|
$lastRow = max(1, $rowIndex - 1);
|
||||||
|
$sheet->getStyle("A1:{$lastCol}1")->getFont()->setBold(true);
|
||||||
|
$sheet->getStyle("A1:{$lastCol}{$lastRow}")->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN);
|
||||||
|
|
||||||
|
// Input validation for retention cells:
|
||||||
|
// - Applies to B2:lastCol(lastRow)
|
||||||
|
// - Allows decimal value between 0 and 100 only
|
||||||
|
// - Blocks negatives, values > 100, and text/special characters
|
||||||
|
if (count($vehicleTypes) > 0 && $lastRow >= 2) {
|
||||||
|
$validation = $sheet->getCell('B2')->getDataValidation();
|
||||||
|
$validation->setType(DataValidation::TYPE_DECIMAL);
|
||||||
|
$validation->setErrorStyle(DataValidation::STYLE_STOP);
|
||||||
|
$validation->setAllowBlank(true);
|
||||||
|
$validation->setShowInputMessage(true);
|
||||||
|
$validation->setShowErrorMessage(true);
|
||||||
|
$validation->setOperator(DataValidation::OPERATOR_BETWEEN);
|
||||||
|
$validation->setFormula1('0');
|
||||||
|
$validation->setFormula2('100');
|
||||||
|
$validation->setPromptTitle('Valid retention rate');
|
||||||
|
$validation->setPrompt('Enter a number between 0 and 100');
|
||||||
|
$validation->setErrorTitle('Invalid value');
|
||||||
|
$validation->setError('Only numeric values from 0 to 100 are allowed.');
|
||||||
|
for ($row = 2; $row <= $lastRow; $row++) {
|
||||||
|
for ($col = 2; $col <= (count($vehicleTypes) + 1); $col++) {
|
||||||
|
$cell = Coordinate::stringFromColumnIndex($col) . $row;
|
||||||
|
$sheet->getCell($cell)->setDataValidation(clone $validation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i <= count($vehicleTypes) + 1; $i++) {
|
||||||
|
$col = Coordinate::stringFromColumnIndex($i);
|
||||||
|
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = 'RentationRate_' . date('Ymd_His') . '.xlsx';
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
header('Content-Disposition: attachment; filename="' . $fileName . '"');
|
||||||
|
header('Cache-Control: max-age=0');
|
||||||
|
$writer = new Xlsx($spreadsheet);
|
||||||
|
$writer->save('php://output');
|
||||||
|
exit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'status' => 'failed',
|
||||||
|
'code' => 500,
|
||||||
|
'data' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importAgentRetentionRateExcel()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$file = $this->request->getFile('retention_excel');
|
||||||
|
if (!$file || !$file->isValid()) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'failed',
|
||||||
|
'code' => 200,
|
||||||
|
'data' => 'Valid Excel file is required in retention_excel',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = IOFactory::load($file->getTempName());
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
$rows = $sheet->toArray(null, true, true, false);
|
||||||
|
|
||||||
|
if (empty($rows) || empty($rows[0])) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'failed',
|
||||||
|
'code' => 200,
|
||||||
|
'data' => 'Excel file is empty',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerRow = $rows[0];
|
||||||
|
$headerVehicleTypes = [];
|
||||||
|
for ($col = 1; $col < count($headerRow); $col++) {
|
||||||
|
$name = trim((string) ($headerRow[$col] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$headerVehicleTypes[$col] = strtolower($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($headerVehicleTypes)) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'failed',
|
||||||
|
'code' => 200,
|
||||||
|
'data' => 'Vehicle type headers are missing in row 1',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$vehicleTypeRows = $this->db->table('partner_vehicle_type')
|
||||||
|
->select('id, vehicle_type')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
$vehicleTypeMap = [];
|
||||||
|
foreach ($vehicleTypeRows as $vt) {
|
||||||
|
$key = strtolower(trim((string) ($vt['vehicle_type'] ?? '')));
|
||||||
|
if ($key !== '') {
|
||||||
|
$vehicleTypeMap[$key] = (int) $vt['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$agentRows = $this->db->table('partner_agent')
|
||||||
|
->select('id, agent_code')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->where('agent_code IS NOT NULL', null, false)
|
||||||
|
->where('agent_code !=', '')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
$agentMap = [];
|
||||||
|
foreach ($agentRows as $agent) {
|
||||||
|
$key = strtolower(trim((string) ($agent['agent_code'] ?? '')));
|
||||||
|
if ($key !== '') {
|
||||||
|
$agentMap[$key] = (int) $agent['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$unknownVehicleTypes = [];
|
||||||
|
$validHeaderVehicleTypes = [];
|
||||||
|
foreach ($headerVehicleTypes as $colIndex => $vehicleTypeName) {
|
||||||
|
if (isset($vehicleTypeMap[$vehicleTypeName])) {
|
||||||
|
$validHeaderVehicleTypes[$colIndex] = $vehicleTypeMap[$vehicleTypeName];
|
||||||
|
} else {
|
||||||
|
$unknownVehicleTypes[] = trim((string) ($headerRow[$colIndex] ?? ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($validHeaderVehicleTypes)) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'failed',
|
||||||
|
'code' => 200,
|
||||||
|
'data' => 'No valid vehicle type headers found in DB',
|
||||||
|
'report' => [
|
||||||
|
'unknown_vehicle_types' => array_values(array_unique($unknownVehicleTypes)),
|
||||||
|
],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingRows = $this->db->table('partner_retention_rate')
|
||||||
|
->select('id, agent_id, vehicle_type_id, retention_rate')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
|
||||||
|
$existingMap = [];
|
||||||
|
foreach ($existingRows as $er) {
|
||||||
|
$key = ((int) $er['agent_id']) . '_' . ((int) $er['vehicle_type_id']);
|
||||||
|
$existingMap[$key] = [
|
||||||
|
'id' => (int) $er['id'],
|
||||||
|
'rate' => (float) ($er['retention_rate'] ?? 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
$inserted = 0;
|
||||||
|
$updated = 0;
|
||||||
|
$skippedZero = 0;
|
||||||
|
$skippedSame = 0;
|
||||||
|
$skippedEmptyRow = 0;
|
||||||
|
$skippedEmptyCell = 0;
|
||||||
|
$skippedUnknownAgentRow = 0;
|
||||||
|
$invalidRange = 0;
|
||||||
|
$invalidNumber = 0;
|
||||||
|
$unknownAgentCodes = [];
|
||||||
|
$invalidCells = [];
|
||||||
|
$agentWiseSkippedCounts = [];
|
||||||
|
|
||||||
|
$this->db->transStart();
|
||||||
|
|
||||||
|
for ($r = 1; $r < count($rows); $r++) {
|
||||||
|
$row = $rows[$r];
|
||||||
|
$agentCode = strtolower(trim((string) ($row[0] ?? '')));
|
||||||
|
if ($agentCode === '') {
|
||||||
|
$skippedEmptyRow++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isset($agentMap[$agentCode])) {
|
||||||
|
$skippedUnknownAgentRow++;
|
||||||
|
$unknownAgentCodes[] = (string) ($row[0] ?? '');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$agentId = $agentMap[$agentCode];
|
||||||
|
$agentCodeOriginal = trim((string) ($row[0] ?? ''));
|
||||||
|
if ($agentCodeOriginal !== '' && !isset($agentWiseSkippedCounts[$agentCodeOriginal])) {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($validHeaderVehicleTypes as $colIndex => $vehicleTypeId) {
|
||||||
|
$rawRate = $row[$colIndex] ?? null;
|
||||||
|
$rawText = trim((string) $rawRate);
|
||||||
|
|
||||||
|
if ($rawText === '') {
|
||||||
|
$skippedEmptyCell++;
|
||||||
|
if ($agentCodeOriginal !== '') {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal]++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_numeric($rawText)) {
|
||||||
|
$invalidNumber++;
|
||||||
|
if ($agentCodeOriginal !== '') {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal]++;
|
||||||
|
}
|
||||||
|
if (count($invalidCells) < 20) {
|
||||||
|
$invalidCells[] = "R" . ($r + 1) . "C" . ($colIndex + 1) . " invalid number";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rate = (float) $rawText;
|
||||||
|
if (!is_finite($rate)) {
|
||||||
|
$invalidNumber++;
|
||||||
|
if ($agentCodeOriginal !== '') {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal]++;
|
||||||
|
}
|
||||||
|
if (count($invalidCells) < 20) {
|
||||||
|
$invalidCells[] = "R" . ($r + 1) . "C" . ($colIndex + 1) . " invalid number";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rate < 0 || $rate > 100) {
|
||||||
|
$invalidRange++;
|
||||||
|
if ($agentCodeOriginal !== '') {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal]++;
|
||||||
|
}
|
||||||
|
if (count($invalidCells) < 20) {
|
||||||
|
$invalidCells[] = "R" . ($r + 1) . "C" . ($colIndex + 1) . " out of range (0-100)";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((float) $rate == 0.0) {
|
||||||
|
$skippedZero++;
|
||||||
|
if ($agentCodeOriginal !== '') {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal]++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $agentId . '_' . $vehicleTypeId;
|
||||||
|
$existing = $existingMap[$key] ?? null;
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
if (round((float) $existing['rate'], 2) === round((float) $rate, 2)) {
|
||||||
|
$skippedSame++;
|
||||||
|
if ($agentCodeOriginal !== '') {
|
||||||
|
$agentWiseSkippedCounts[$agentCodeOriginal]++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->db->table('partner_retention_rate')
|
||||||
|
->where('id', (int) $existing['id'])
|
||||||
|
->update([
|
||||||
|
'retention_rate' => round($rate, 2),
|
||||||
|
'is_active' => 1,
|
||||||
|
'updated_on' => $now,
|
||||||
|
]);
|
||||||
|
$existingMap[$key]['rate'] = round($rate, 2);
|
||||||
|
$updated++;
|
||||||
|
} else {
|
||||||
|
$this->db->table('partner_retention_rate')
|
||||||
|
->insert([
|
||||||
|
'agent_id' => $agentId,
|
||||||
|
'vehicle_type_id' => $vehicleTypeId,
|
||||||
|
'retention_rate' => round($rate, 2),
|
||||||
|
'is_active' => 1,
|
||||||
|
'created_on' => $now,
|
||||||
|
]);
|
||||||
|
$existingMap[$key] = [
|
||||||
|
'id' => (int) $this->db->insertID(),
|
||||||
|
'rate' => round($rate, 2),
|
||||||
|
];
|
||||||
|
$inserted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->transComplete();
|
||||||
|
if ($this->db->transStatus() === false) {
|
||||||
|
throw new \RuntimeException('Import transaction failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'code' => 200,
|
||||||
|
'data' => [
|
||||||
|
'inserted_cells' => $inserted,
|
||||||
|
'updated_cells' => $updated,
|
||||||
|
'skipped_zero' => $skippedZero,
|
||||||
|
'skipped_same' => $skippedSame,
|
||||||
|
'skipped_empty_row' => $skippedEmptyRow,
|
||||||
|
'skipped_empty_cell' => $skippedEmptyCell,
|
||||||
|
'skipped_unknown_agent' => $skippedUnknownAgentRow,
|
||||||
|
'invalid_range' => $invalidRange,
|
||||||
|
'invalid_number' => $invalidNumber,
|
||||||
|
],
|
||||||
|
'report' => [
|
||||||
|
'unknown_agent_codes' => array_values(array_unique($unknownAgentCodes)),
|
||||||
|
'unknown_vehicle_types'=> array_values(array_unique($unknownVehicleTypes)),
|
||||||
|
'invalid_cells' => $invalidCells,
|
||||||
|
'agent_wise_skipped_counts' => $agentWiseSkippedCounts,
|
||||||
|
],
|
||||||
|
'message' => 'Retention rate import completed',
|
||||||
|
], 200);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'failed',
|
||||||
|
'code' => 500,
|
||||||
|
'data' => $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,39 @@ namespace App\Libraries;
|
|||||||
use CodeIgniter\Database\BaseConnection;
|
use CodeIgniter\Database\BaseConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partner (agent) grid: match payout grid vehicle_type to partner_vehicle_type via
|
* Partner (agent) grid: same data as
|
||||||
* partner_retention_rate (agent_id + vehicle_type_id), subtract retention from comp/tp/od.
|
* SELECT prr.retention_rate AS partner_RR, prr.vehicle_type_id, pvt.vehicle_type AS partner_VT
|
||||||
* Values <= 0 after subtraction are returned as '-'.
|
* FROM partner_retention_rate prr
|
||||||
|
* LEFT JOIN partner_vehicle_type pvt ON pvt.id = prr.vehicle_type_id
|
||||||
|
* WHERE prr.agent_id = ? AND prr.is_active = 1
|
||||||
|
* Matched grid row (vehicle type aligns with that PRR row): partner_VT, partner_RR set from DB;
|
||||||
|
* comp/tp/od = partner_RR − original (null/empty base → 0); result ≤ 0 → '-'.
|
||||||
|
* No matching PRR row for this grid vehicle type: partner_VT, partner_RR, comp, tp, od → '-'.
|
||||||
|
*
|
||||||
|
* Manager/Accounts: applyManagerAccountsDisplayToRows() — comp/tp/od null/empty/zero/negative → '-', no math.
|
||||||
*/
|
*/
|
||||||
class PartnerPayoutGridRetention
|
class PartnerPayoutGridRetention
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Active partner_retention_rate rows with joined partner_vehicle_type (one query for maps + agent grid meta).
|
||||||
|
*
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private static function fetchPrrPvtRows(BaseConnection $db, int $agentId): array
|
||||||
|
{
|
||||||
|
if ($agentId <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $db->table('partner_retention_rate prr')
|
||||||
|
->select('prr.vehicle_type_id, prr.retention_rate, pvt.vehicle_type')
|
||||||
|
->join('partner_vehicle_type pvt', 'pvt.id = prr.vehicle_type_id', 'left')
|
||||||
|
->where('prr.agent_id', $agentId)
|
||||||
|
->where('prr.is_active', 1)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
}
|
||||||
|
|
||||||
public static function normalizeVehicleTypeLabel(?string $s): string
|
public static function normalizeVehicleTypeLabel(?string $s): string
|
||||||
{
|
{
|
||||||
if ($s === null || $s === '') {
|
if ($s === null || $s === '') {
|
||||||
@ -21,24 +48,181 @@ class PartnerPayoutGridRetention
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, float> normalized name => rate, and id:{vehicle_type_id} => rate
|
* @param array<int, array<string, mixed>> $prrRows
|
||||||
|
*
|
||||||
|
* @return array<string, array{partner_VT: string, partner_RR: float}>
|
||||||
*/
|
*/
|
||||||
public static function buildRetentionMap(BaseConnection $db, int $agentId): array
|
private static function partnerMetaMapFromPrrRows(array $prrRows): array
|
||||||
{
|
{
|
||||||
if ($agentId <= 0) {
|
$meta = [];
|
||||||
return [];
|
foreach ($prrRows as $r) {
|
||||||
|
if (! array_key_exists('retention_rate', $r) || $r['retention_rate'] === null || $r['retention_rate'] === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$partnerRr = (float) $r['retention_rate'];
|
||||||
|
$vtRaw = isset($r['vehicle_type']) ? trim((string) $r['vehicle_type']) : '';
|
||||||
|
$partnerVt = $vtRaw !== '' ? $vtRaw : 'N/A';
|
||||||
|
$entry = [
|
||||||
|
'partner_VT' => $partnerVt,
|
||||||
|
'partner_RR' => $partnerRr,
|
||||||
|
];
|
||||||
|
if ($vtRaw !== '') {
|
||||||
|
$meta[self::normalizeVehicleTypeLabel($vtRaw)] = $entry;
|
||||||
|
}
|
||||||
|
$vid = isset($r['vehicle_type_id']) ? (int) $r['vehicle_type_id'] : 0;
|
||||||
|
if ($vid > 0) {
|
||||||
|
$meta['id:' . $vid] = $entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $db->table('partner_retention_rate prr')
|
return $meta;
|
||||||
->select('prr.vehicle_type_id, prr.retention_rate, pvt.vehicle_type')
|
}
|
||||||
->join('partner_vehicle_type pvt', 'pvt.id = prr.vehicle_type_id', 'left')
|
|
||||||
->where('prr.agent_id', $agentId)
|
/**
|
||||||
->where('prr.is_active', 1)
|
* partner_vehicle_type.vehicle_type as partner_VT, partner_retention_rate.retention_rate as partner_RR.
|
||||||
|
* Keys: normalized vehicle name, and id:{vehicle_type_id}. Rows without retention_rate are omitted.
|
||||||
|
*
|
||||||
|
* @return array<string, array{partner_VT: string, partner_RR: float}>
|
||||||
|
*/
|
||||||
|
public static function buildPartnerMetaMap(BaseConnection $db, int $agentId): array
|
||||||
|
{
|
||||||
|
return self::partnerMetaMapFromPrrRows(self::fetchPrrPvtRows($db, $agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalized master label → partner_vehicle_type.id (active rows only).
|
||||||
|
* Used when grid row text must be resolved to an id for partner_retention_rate lookup.
|
||||||
|
*
|
||||||
|
* @return array<string, int>
|
||||||
|
*/
|
||||||
|
public static function buildMasterNormLabelToVehicleTypeIdMap(BaseConnection $db): array
|
||||||
|
{
|
||||||
|
$rows = $db->table('partner_vehicle_type')
|
||||||
|
->select('id, vehicle_type')
|
||||||
|
->where('is_active', 1)
|
||||||
->get()
|
->get()
|
||||||
->getResultArray();
|
->getResultArray();
|
||||||
|
|
||||||
$map = [];
|
$out = [];
|
||||||
foreach ($rows as $r) {
|
foreach ($rows as $r) {
|
||||||
|
$k = self::normalizeVehicleTypeLabel($r['vehicle_type'] ?? '');
|
||||||
|
if ($k === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$out[$k] = (int) $r['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array{partner_VT: string, partner_RR: float}> $metaMap
|
||||||
|
* @param array<string, int>|null $masterNormLabelToVtId from buildMasterNormLabelToVehicleTypeIdMap()
|
||||||
|
*
|
||||||
|
* @return array{partner_VT: string, partner_RR: float}|null null = no row in partner_retention_rate for this grid vehicle type (agent)
|
||||||
|
*/
|
||||||
|
public static function resolvePartnerMetaForGridRow(array $row, array $metaMap, ?array $masterNormLabelToVtId = null): ?array
|
||||||
|
{
|
||||||
|
$key = self::normalizeVehicleTypeLabel(isset($row['vehicle_type']) ? (string) $row['vehicle_type'] : '');
|
||||||
|
if ($key !== '' && isset($metaMap[$key])) {
|
||||||
|
return $metaMap[$key];
|
||||||
|
}
|
||||||
|
$vid = isset($row['vehicle_type_id']) ? (int) $row['vehicle_type_id'] : 0;
|
||||||
|
if ($vid > 0 && isset($metaMap['id:' . $vid])) {
|
||||||
|
return $metaMap['id:' . $vid];
|
||||||
|
}
|
||||||
|
// Grid text matches master partner_vehicle_type but meta was only keyed by id (e.g. weak pvt join on prr)
|
||||||
|
if ($masterNormLabelToVtId !== null && $key !== '') {
|
||||||
|
$mid = $masterNormLabelToVtId[$key] ?? 0;
|
||||||
|
if ($mid > 0 && isset($metaMap['id:' . $mid])) {
|
||||||
|
return $metaMap['id:' . $mid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager / Accounts: no math — null, empty, '-', unparseable, zero, or negative → '-';
|
||||||
|
* else return formatted positive value.
|
||||||
|
*/
|
||||||
|
public static function gridPayoutDisplayManagerAccounts($raw): string
|
||||||
|
{
|
||||||
|
if ($raw === null || $raw === '' || $raw === '-') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
$v = self::toFloat($raw);
|
||||||
|
if ($v === null || $v <= 0) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::formatGridNumericDisplay($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent: only if original comp/tp/od is a positive number → partner_RR − value.
|
||||||
|
* Original null / empty / '-' / zero / negative / unparseable → '-'.
|
||||||
|
* Result ≤ 0 → '-'.
|
||||||
|
*/
|
||||||
|
public static function agentPayoutColumnFromPartnerRr($raw, float $partnerRr): string
|
||||||
|
{
|
||||||
|
if ($raw === null || $raw === '' || $raw === '-') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
$base = self::toFloat($raw);
|
||||||
|
if ($base === null || $base <= 0) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $partnerRr - $base;
|
||||||
|
if ($result < 0 || abs($result) < 0.00001) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::formatGridNumericDisplay($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $rows
|
||||||
|
*
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public static function applyManagerAccountsDisplayToRows(array $rows): array
|
||||||
|
{
|
||||||
|
foreach ($rows as &$row) {
|
||||||
|
if (array_key_exists('comp', $row)) {
|
||||||
|
$row['comp'] = self::gridPayoutDisplayManagerAccounts($row['comp'] ?? null);
|
||||||
|
}
|
||||||
|
if (array_key_exists('tp', $row)) {
|
||||||
|
$row['tp'] = self::gridPayoutDisplayManagerAccounts($row['tp'] ?? null);
|
||||||
|
}
|
||||||
|
if (array_key_exists('od', $row)) {
|
||||||
|
$row['od'] = self::gridPayoutDisplayManagerAccounts($row['od'] ?? null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function formatGridNumericDisplay(float $v): string
|
||||||
|
{
|
||||||
|
if (abs($v - round($v)) < 0.00001) {
|
||||||
|
return (string) (int) round($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim(rtrim(number_format($v, 2, '.', ''), '0'), '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $prrRows
|
||||||
|
*
|
||||||
|
* @return array<string, float> normalized name => rate, and id:{vehicle_type_id} => rate
|
||||||
|
*/
|
||||||
|
private static function retentionMapFromPrrRows(array $prrRows): array
|
||||||
|
{
|
||||||
|
$map = [];
|
||||||
|
foreach ($prrRows as $r) {
|
||||||
if (! array_key_exists('retention_rate', $r) || $r['retention_rate'] === null || $r['retention_rate'] === '') {
|
if (! array_key_exists('retention_rate', $r) || $r['retention_rate'] === null || $r['retention_rate'] === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -56,6 +240,14 @@ class PartnerPayoutGridRetention
|
|||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, float> normalized name => rate, and id:{vehicle_type_id} => rate
|
||||||
|
*/
|
||||||
|
public static function buildRetentionMap(BaseConnection $db, int $agentId): array
|
||||||
|
{
|
||||||
|
return self::retentionMapFromPrrRows(self::fetchPrrPvtRows($db, $agentId));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, array<string, mixed>> $rows
|
* @param array<int, array<string, mixed>> $rows
|
||||||
*
|
*
|
||||||
@ -67,46 +259,47 @@ class PartnerPayoutGridRetention
|
|||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
$map = self::buildRetentionMap($db, $agentId);
|
$prrRows = self::fetchPrrPvtRows($db, $agentId);
|
||||||
if ($map === []) {
|
$metaMap = self::partnerMetaMapFromPrrRows($prrRows);
|
||||||
return $rows;
|
$master = self::buildMasterNormLabelToVehicleTypeIdMap($db);
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($rows as &$row) {
|
foreach ($rows as &$row) {
|
||||||
$key = self::normalizeVehicleTypeLabel(isset($row['vehicle_type']) ? (string) $row['vehicle_type'] : '');
|
$oa = $row['comp'] ?? null;
|
||||||
$rate = null;
|
$ob = $row['tp'] ?? null;
|
||||||
if ($key !== '' && isset($map[$key])) {
|
$oc = $row['od'] ?? null;
|
||||||
$rate = $map[$key];
|
$row['oa'] = $oa;
|
||||||
} else {
|
$row['ob'] = $ob;
|
||||||
$vid = isset($row['vehicle_type_id']) ? (int) $row['vehicle_type_id'] : 0;
|
$row['oc'] = $oc;
|
||||||
if ($vid > 0 && isset($map['id:' . $vid])) {
|
|
||||||
$rate = $map['id:' . $vid];
|
$pm = self::resolvePartnerMetaForGridRow($row, $metaMap, $master);
|
||||||
|
if ($pm === null) {
|
||||||
|
$row['partner_VT'] = '-';
|
||||||
|
$row['partner_RR'] = '-';
|
||||||
|
if (array_key_exists('comp', $row)) {
|
||||||
|
$row['comp'] = '-';
|
||||||
}
|
}
|
||||||
|
if (array_key_exists('tp', $row)) {
|
||||||
|
$row['tp'] = '-';
|
||||||
}
|
}
|
||||||
if ($rate === null) {
|
if (array_key_exists('od', $row)) {
|
||||||
|
$row['od'] = '-';
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (['comp', 'tp', 'od'] as $col) {
|
$row['partner_VT'] = $pm['partner_VT'];
|
||||||
if (! array_key_exists($col, $row)) {
|
$row['partner_RR'] = $pm['partner_RR'];
|
||||||
continue;
|
$rr = (float) $pm['partner_RR'];
|
||||||
|
|
||||||
|
if (array_key_exists('comp', $row)) {
|
||||||
|
$row['comp'] = self::agentPayoutColumnFromPartnerRr($oa, $rr);
|
||||||
}
|
}
|
||||||
$raw = $row[$col];
|
if (array_key_exists('tp', $row)) {
|
||||||
if ($raw === null || $raw === '' || $raw === '-') {
|
$row['tp'] = self::agentPayoutColumnFromPartnerRr($ob, $rr);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$base = self::toFloat($raw);
|
|
||||||
if ($base === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$adj = $base - $rate;
|
|
||||||
if ($adj <= 0) {
|
|
||||||
$row[$col] = '-';
|
|
||||||
} elseif (abs($adj - round($adj)) < 0.00001) {
|
|
||||||
$row[$col] = (string) (int) round($adj);
|
|
||||||
} else {
|
|
||||||
$row[$col] = rtrim(rtrim(number_format($adj, 2, '.', ''), '0'), '.');
|
|
||||||
}
|
}
|
||||||
|
if (array_key_exists('od', $row)) {
|
||||||
|
$row['od'] = self::agentPayoutColumnFromPartnerRr($oc, $rr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($row);
|
unset($row);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user