FIX_Changes and Additional Requirements 3,4,5
This commit is contained in:
parent
d9f533ac74
commit
ce7033654a
@ -73,6 +73,10 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
|
||||
$routes->post('agent/uploadAgentIncentiveFile', 'AgentController::uploadAgentIncentiveFile');
|
||||
$routes->get('agent/deleteAgentIncentiveFile', 'AgentController::deleteAgentIncentiveFile');
|
||||
$routes->get('agent/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile');
|
||||
$routes->get('agent/downloadSamplePartnerGridExcel', 'AgentController::downloadSamplePartnerGridExcel');
|
||||
$routes->get('agent/monthlyCommissionGridFilters', 'AgentController::monthlyCommissionGridFilters');
|
||||
$routes->get('agent/monthlyCommissionGridList', 'AgentController::monthlyCommissionGridList');
|
||||
$routes->get('agent/downloadMonthlyCommissionGrid', 'AgentController::downloadMonthlyCommissionGrid');
|
||||
|
||||
|
||||
//Staff
|
||||
@ -201,9 +205,14 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
|
||||
$routes->get('invoice/list', 'InvoiceController::invoiceList');
|
||||
$routes->get('invoice/details', 'InvoiceController::findInvoiceWithItems');
|
||||
$routes->post('invoice/create-or-update', 'InvoiceController::createOrUpdateInvoice');
|
||||
$routes->post('invoice/add-payment', 'InvoiceController::addInvoicePayment');
|
||||
$routes->get('invoice/delete', 'InvoiceController::deleteInvoice');
|
||||
$routes->post('invoice/commission-rate-list', 'InvoiceController::getCommissionRateList');
|
||||
$routes->get('invoice/getAgentUnusedCommissionList', 'InvoiceController::getAgentUnusedCommissionList');
|
||||
$routes->post('invoice/bulk-upload-commission', 'InvoiceController::bulkUploadCommission');
|
||||
$routes->post('invoice/bulk-upload-commission/proceed', 'InvoiceController::bulkUploadCommissionProceed');
|
||||
$routes->post('invoice/bulk-upload-commission', 'InvoiceController::bulkUploadCommission');
|
||||
$routes->post('invoice/bulk-upload-commission/proceed', 'InvoiceController::bulkUploadCommissionProceed');
|
||||
|
||||
|
||||
// SALES EXECUTIVE
|
||||
|
||||
@ -5,16 +5,41 @@ use CodeIgniter\RESTful\ResourceController;
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\AgentModel;
|
||||
use App\Models\AgentIncentiveFileModel;
|
||||
use App\Models\PartnerGridDetailsModel;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
class AgentController extends ResourceController
|
||||
{
|
||||
protected $AgentModel;
|
||||
protected $AgentIncentiveFileModel;
|
||||
protected $PartnerGridDetailsModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
helper('jwt_helper');
|
||||
$this->AgentModel = new AgentModel();
|
||||
$this->AgentIncentiveFileModel = new AgentIncentiveFileModel();
|
||||
$this->PartnerGridDetailsModel = new PartnerGridDetailsModel();
|
||||
}
|
||||
|
||||
private function getAuthenticatedUserData(): ?object
|
||||
{
|
||||
$header = $this->request->getHeaderLine('Authorization');
|
||||
if (!$header || !preg_match('/Bearer\s(\S+)/', $header, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decodedToken = validateJWT($matches[1]);
|
||||
if (!$decodedToken || !isset($decodedToken['data'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $decodedToken['data'];
|
||||
}
|
||||
|
||||
private function canManagePartnerGrid(?string $role): bool
|
||||
{
|
||||
return in_array((string) $role, ['1', '4'], true);
|
||||
}
|
||||
|
||||
// List of all agents
|
||||
@ -267,10 +292,30 @@ class AgentController extends ResourceController
|
||||
public function agentIncentiveFileList()
|
||||
{
|
||||
try{
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$agent_id = $this->request->getGet('agent_id');
|
||||
$roleId = (string) ($authUser->role_id ?? 'agent');
|
||||
$agentIdFromRequest = $this->request->getGet('agent_id');
|
||||
$fileType = trim((string) ($this->request->getGet('file_type') ?? ''));
|
||||
|
||||
$data = $this->AgentIncentiveFileModel->where('agent_id',$agent_id)->where('is_active',1)->findAll();
|
||||
// Partner can only view own files; manager/accounts can view all.
|
||||
$agentId = $this->canManagePartnerGrid($roleId) ? $agentIdFromRequest : ($authUser->id ?? null);
|
||||
if (empty($agentId)) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'agent_id is required'], 200);
|
||||
}
|
||||
|
||||
$builder = $this->AgentIncentiveFileModel
|
||||
->where('agent_id', (int) $agentId)
|
||||
->where('is_active', 1);
|
||||
|
||||
if ($fileType !== '') {
|
||||
$builder->where('file_type', $fileType);
|
||||
}
|
||||
|
||||
$data = $builder->orderBy('id', 'DESC')->findAll();
|
||||
|
||||
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
|
||||
|
||||
@ -285,13 +330,39 @@ class AgentController extends ResourceController
|
||||
{
|
||||
try{
|
||||
$data = $this->request->getPost();
|
||||
|
||||
//duplicate check
|
||||
$duplicateData = $this->AgentIncentiveFileModel->where('agent_id',$data['agent_id'])->where('incentive_month',$data['incentive_month'])->first();
|
||||
if(!empty($duplicateData)){
|
||||
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Duplicate Entry.'], 200);
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$roleId = (string) ($authUser->role_id ?? 'agent');
|
||||
$isGridUpload = isset($data['file_type']) && $data['file_type'] === 'grid';
|
||||
|
||||
if ($isGridUpload && !$this->canManagePartnerGrid($roleId)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 403,
|
||||
'data' => 'Only Manager and Accounts can upload/edit partner grid files'
|
||||
], 403);
|
||||
}
|
||||
|
||||
if ($isGridUpload) {
|
||||
$this->uploadGridFile($data);
|
||||
} else {
|
||||
|
||||
$duplicateData = $this->AgentIncentiveFileModel
|
||||
->where('agent_id', $data['agent_id'])
|
||||
->where('incentive_month', $data['incentive_month'])
|
||||
->first();
|
||||
|
||||
if (!empty($duplicateData)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 200,
|
||||
'data' => 'Duplicate Entry.'
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
// handle file uploads
|
||||
$incentiveFile = $this->request->getFile('incentive_file_name');
|
||||
|
||||
@ -311,6 +382,7 @@ class AgentController extends ResourceController
|
||||
'agent_id' => $data['agent_id'],
|
||||
'incentive_month' => $data['incentive_month'],
|
||||
'incentive_file_name' => $incentiveFileName,
|
||||
'file_type' => $data['file_type'] ?? 'incentive',
|
||||
'created_by' => $data['created_by'] ?? null
|
||||
];
|
||||
|
||||
@ -323,10 +395,292 @@ class AgentController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Grid Functionality
|
||||
// ─────────────────────────────────────────
|
||||
public function uploadGridFile($data){
|
||||
$gridFile = $this->request->getFile('incentive_file_name');
|
||||
|
||||
if (!$gridFile || !$gridFile->isValid()) {
|
||||
throw new \RuntimeException('Valid grid file is required');
|
||||
}
|
||||
|
||||
$extension = strtolower((string) $gridFile->getExtension());
|
||||
if (!in_array($extension, ['xlsx', 'xls', 'csv'], true)) {
|
||||
throw new \RuntimeException('Only xlsx, xls, csv grid files are allowed');
|
||||
}
|
||||
|
||||
$spreadsheet = IOFactory::load($gridFile->getTempName());
|
||||
$rows = $spreadsheet->getActiveSheet()->toArray(null, true, true, false);
|
||||
|
||||
if (empty($rows)) {
|
||||
throw new \RuntimeException('Grid file is empty');
|
||||
}
|
||||
|
||||
$normalize = static function ($value): string {
|
||||
$value = strtolower(trim((string) $value));
|
||||
return preg_replace('/[^a-z0-9]+/', '', $value) ?? '';
|
||||
};
|
||||
$toNumber = static function ($value): float {
|
||||
if (is_numeric($value)) {
|
||||
return (float) $value;
|
||||
}
|
||||
if (preg_match('/-?\d+(\.\d+)?/', (string) $value, $matches)) {
|
||||
return (float) $matches[0];
|
||||
}
|
||||
return 0.0;
|
||||
};
|
||||
$toNullable = static function ($value): ?string {
|
||||
$value = trim((string) $value);
|
||||
return $value === '' ? null : $value;
|
||||
};
|
||||
$formatNumber = static function (float $value): string {
|
||||
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
||||
};
|
||||
$parsePremium = static function ($value) use ($formatNumber): ?string {
|
||||
$source = trim((string) $value);
|
||||
if ($source === '') {
|
||||
return null;
|
||||
}
|
||||
if (preg_match('/-?\d+(?:\.\d+)?/', $source, $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
return $formatNumber((float) $matches[0]);
|
||||
};
|
||||
|
||||
// Fixed format: first row is header and data starts from row 2.
|
||||
$headerRowIndex = 0;
|
||||
$headerMap = [];
|
||||
$headerRow = $rows[$headerRowIndex] ?? [];
|
||||
foreach ($headerRow as $colIndex => $cell) {
|
||||
$normalizedHeader = $normalize($cell);
|
||||
if ($normalizedHeader === 'type') {
|
||||
$headerMap['vehicle_type_id'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'insurer') {
|
||||
$headerMap['insurer_id'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'rto') {
|
||||
$headerMap['rto_id'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'segment') {
|
||||
$headerMap['segment_id'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'comp') {
|
||||
$headerMap['comp'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'tp') {
|
||||
$headerMap['tp'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'fuel') {
|
||||
$headerMap['fuel'] = $colIndex;
|
||||
} elseif ($normalizedHeader === 'remarks') {
|
||||
$headerMap['remarks'] = $colIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($headerMap['insurer_id'], $headerMap['rto_id'], $headerMap['segment_id'], $headerMap['vehicle_type_id'])) {
|
||||
throw new \RuntimeException('Invalid grid header. Required: TYPE, INSURER, RTO, SEGMENT');
|
||||
}
|
||||
|
||||
$defaultPartnerId = !empty($data['agent_id']) ? (int) $data['agent_id'] : null;
|
||||
$createdBy = $data['created_by'] ?? null;
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
$vehicleTypeMaster = $db->table('vehicle_type')->select('id, vehicle_type')->where('is_active', 1)->get()->getResultArray();
|
||||
|
||||
$insurerMaster = $db->table('insurers')->select('id, name, short_name')->where('is_active', 1)->get()->getResultArray();
|
||||
|
||||
$rtoMaster = $db->table('rto_master')->select('id, rto_code, rto_name')->where('is_active', 1)->get()->getResultArray();
|
||||
|
||||
$partnerMaster = $db->table('partner_agent')->select('id, name, agent_code, retention_rate')->where('is_active', 1)->get()->getResultArray();
|
||||
|
||||
|
||||
$insurerMap = [];
|
||||
|
||||
foreach ($insurerMaster as $item) {
|
||||
$insurerMap[$normalize($item['name'])] = (int) $item['id'];
|
||||
$insurerMap[$normalize($item['short_name'])] = (int) $item['id'];
|
||||
$insurerMap[(string) $item['id']] = (int) $item['id'];
|
||||
}
|
||||
|
||||
$rtoMap = [];
|
||||
foreach ($rtoMaster as $item) {
|
||||
$rtoMap[$normalize($item['rto_code'])] = (int) $item['id'];
|
||||
$rtoMap[$normalize($item['rto_name'])] = (int) $item['id'];
|
||||
$rtoMap[(string) $item['id']] = (int) $item['id'];
|
||||
}
|
||||
|
||||
$partnerMap = [];
|
||||
$partnerRetentionMap = [];
|
||||
foreach ($partnerMaster as $item) {
|
||||
$id = (int) $item['id'];
|
||||
$partnerMap[$normalize($item['name'])] = $id;
|
||||
$partnerMap[$normalize($item['agent_code'])] = $id;
|
||||
$partnerMap[(string) $id] = $id;
|
||||
$partnerRetentionMap[$id] = $toNumber($item['retention_rate'] ?? 0);
|
||||
}
|
||||
|
||||
$insertCount = 0;
|
||||
$updateCount = 0;
|
||||
$skipStats = [
|
||||
'empty_row' => 0,
|
||||
'missing_required_columns' => 0,
|
||||
'master_mapping_failed' => 0,
|
||||
'partner_not_found' => 0,
|
||||
];
|
||||
$skipSamples = [];
|
||||
|
||||
for ($i = $headerRowIndex + 1, $count = count($rows); $i < $count; $i++) {
|
||||
$row = $rows[$i];
|
||||
$hasAnyData = false;
|
||||
|
||||
foreach ($row as $cellValue) {
|
||||
if (trim((string) $cellValue) !== '') {
|
||||
$hasAnyData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasAnyData) {
|
||||
$skipStats['empty_row']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$insurerRaw = trim((string) ($row[$headerMap['insurer_id']] ?? ''));
|
||||
$rtoRaw = trim((string) ($row[$headerMap['rto_id']] ?? ''));
|
||||
$segmentRaw = trim((string) ($row[$headerMap['segment_id']] ?? ''));
|
||||
$vehicleTypeRaw = trim((string) ($row[$headerMap['vehicle_type_id']] ?? $segmentRaw));
|
||||
|
||||
if ($insurerRaw === '' || $rtoRaw === '' || $segmentRaw === '' || $vehicleTypeRaw === '') {
|
||||
$skipStats['missing_required_columns']++;
|
||||
if (count($skipSamples) < 15) {
|
||||
$skipSamples[] = [
|
||||
'row' => $i + 1,
|
||||
'reason' => 'missing_required_columns',
|
||||
'insurer' => $insurerRaw,
|
||||
'rto' => $rtoRaw,
|
||||
'segment' => $segmentRaw,
|
||||
'vehicle_type' => $vehicleTypeRaw,
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$insurerId = $insurerMap[$normalize($insurerRaw)] ?? null;
|
||||
if ($insurerId === null && ctype_digit($insurerRaw) && isset($insurerMap[$insurerRaw])) {
|
||||
$insurerId = $insurerMap[$insurerRaw];
|
||||
}
|
||||
|
||||
$rtoId = $rtoMap[$normalize($rtoRaw)] ?? null;
|
||||
if ($rtoId === null && ctype_digit($rtoRaw) && isset($rtoMap[$rtoRaw])) {
|
||||
$rtoId = $rtoMap[$rtoRaw];
|
||||
}
|
||||
|
||||
if (empty($insurerId) || empty($rtoId)) {
|
||||
$skipStats['master_mapping_failed']++;
|
||||
if (count($skipSamples) < 15) {
|
||||
$skipSamples[] = [
|
||||
'row' => $i + 1,
|
||||
'reason' => 'master_mapping_failed',
|
||||
'insurer' => $insurerRaw,
|
||||
'rto' => $rtoRaw,
|
||||
'segment' => $segmentRaw,
|
||||
'vehicle_type' => $vehicleTypeRaw,
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$comp = trim((string) ($row[$headerMap['comp']] ?? ''));
|
||||
$tp = trim((string) ($row[$headerMap['tp']] ?? ''));
|
||||
$remarks = trim((string) ($row[$headerMap['remarks']] ?? ''));
|
||||
$fuelRaw = trim((string) ($row[$headerMap['fuel']] ?? ''));
|
||||
$fuel = $toNullable($fuelRaw);
|
||||
$partnerRaw = trim((string) ($row[$headerMap['partner_id']] ?? ''));
|
||||
|
||||
$partnerId = null;
|
||||
if ($partnerRaw !== '') {
|
||||
$partnerId = $partnerMap[$normalize($partnerRaw)] ?? null;
|
||||
if ($partnerId === null && ctype_digit($partnerRaw) && isset($partnerMap[$partnerRaw])) {
|
||||
$partnerId = $partnerMap[$partnerRaw];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($partnerId) && !empty($defaultPartnerId)) {
|
||||
$partnerId = $defaultPartnerId;
|
||||
}
|
||||
|
||||
if (empty($partnerId)) {
|
||||
$skipStats['partner_not_found']++;
|
||||
if (count($skipSamples) < 15) {
|
||||
$skipSamples[] = [
|
||||
'row' => $i + 1,
|
||||
'reason' => 'partner_not_found',
|
||||
'partner' => $partnerRaw,
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$retentionRate = $partnerRetentionMap[(int) $partnerId] ?? 0.0;
|
||||
$compSanitized = $parsePremium($comp);
|
||||
$tpSanitized = $parsePremium($tp);
|
||||
$partnerComp = $compSanitized !== null ? $formatNumber($retentionRate + (float) $compSanitized) : null;
|
||||
$partnerTp = $tpSanitized !== null ? $formatNumber($retentionRate + (float) $tpSanitized) : null;
|
||||
|
||||
$recordData = [
|
||||
'vehicle_type_id' => $vehicleTypeRaw,
|
||||
'insurer_id' => (string) $insurerId,
|
||||
'rto_id' => (string) $rtoId,
|
||||
'segment_id' => $segmentRaw,
|
||||
'comp' => $compSanitized,
|
||||
'tp' => $tpSanitized,
|
||||
'fuel' => $fuel,
|
||||
'remarks' => $toNullable($remarks),
|
||||
'partner_id' => (string) $partnerId,
|
||||
'partner_comp' => $partnerComp,
|
||||
'partner_tp' => $partnerTp,
|
||||
];
|
||||
|
||||
$existing = $this->PartnerGridDetailsModel
|
||||
->where('vehicle_type_id', $vehicleTypeRaw)
|
||||
->where('insurer_id', (string) $insurerId)
|
||||
->where('rto_id', (string) $rtoId)
|
||||
->where('segment_id', $segmentRaw)
|
||||
->where('partner_id', (string) $partnerId)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
$recordData['updated_by'] = $createdBy;
|
||||
$this->PartnerGridDetailsModel->update((int) $existing['id'], $recordData);
|
||||
$updateCount++;
|
||||
} else {
|
||||
$recordData['created_by'] = $createdBy;
|
||||
$this->PartnerGridDetailsModel->insert($recordData);
|
||||
$insertCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($insertCount === 0 && $updateCount === 0) {
|
||||
$debugData = [
|
||||
'message' => 'No valid rows found in grid file',
|
||||
'header_row' => $headerRowIndex + 1,
|
||||
'detected_headers' => array_keys($headerMap),
|
||||
'skip_stats' => $skipStats,
|
||||
'skip_samples' => $skipSamples,
|
||||
];
|
||||
throw new \RuntimeException(json_encode($debugData, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Delete agent incentive file
|
||||
public function deleteAgentIncentiveFile()
|
||||
{
|
||||
try {
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
$roleId = (string) ($authUser->role_id ?? 'agent');
|
||||
|
||||
$id = $this->request->getGet('id');
|
||||
|
||||
@ -337,6 +691,14 @@ class AgentController extends ResourceController
|
||||
return $this->respond(['status' => 'failed','code' => 200, 'data' => 'File not found'], 200);
|
||||
}
|
||||
|
||||
if (($file['file_type'] ?? '') === 'grid' && !$this->canManagePartnerGrid($roleId)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 403,
|
||||
'data' => 'Only Manager and Accounts can edit/delete partner grid files'
|
||||
], 403);
|
||||
}
|
||||
|
||||
// update status
|
||||
$this->AgentIncentiveFileModel->update($id, ['is_active' => 0]);
|
||||
|
||||
@ -352,6 +714,11 @@ class AgentController extends ResourceController
|
||||
public function downloadAgentIncentiveFile()
|
||||
{
|
||||
try {
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
$roleId = (string) ($authUser->role_id ?? 'agent');
|
||||
$id = $this->request->getGet('id');
|
||||
|
||||
if (!$id) {
|
||||
@ -365,6 +732,10 @@ class AgentController extends ResourceController
|
||||
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200);
|
||||
}
|
||||
|
||||
if (!$this->canManagePartnerGrid($roleId) && (int) $fileRecord['agent_id'] !== (int) ($authUser->id ?? 0)) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 403, 'data' => 'Access denied'], 403);
|
||||
}
|
||||
|
||||
$filePath = WRITEPATH . 'uploads/agent/incentive_file/' . $fileRecord['incentive_file_name'];
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
@ -379,10 +750,344 @@ class AgentController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
// Download sample partner grid excel
|
||||
public function downloadSamplePartnerGridExcel()
|
||||
{
|
||||
try {
|
||||
$filePath = WRITEPATH . 'uploads/sample_partner_grid_file.xlsx';
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'data' => 'Sample partner grid file not found on server',
|
||||
], 200);
|
||||
}
|
||||
|
||||
return $this->response->download($filePath, null);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'data' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Monthly commission grid filters
|
||||
public function monthlyCommissionGridFilters()
|
||||
{
|
||||
try {
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
$rtoMaster = $db->table('rto_master')
|
||||
->select('id, rto_code, rto_name')
|
||||
->where('is_active', 1)
|
||||
->orderBy('rto_code', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$segmentMaster = $db->table('vehicle_type')
|
||||
->select('id, vehicle_type')
|
||||
->where('is_active', 1)
|
||||
->orderBy('vehicle_type', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$planMaster = $db->table('partner_insurance_plan_type_master')
|
||||
->select('id, insurance_plan_type')
|
||||
->where('is_active', 1)
|
||||
->orderBy('insurance_plan_type', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$partnerMaster = $db->table('partner_agent')
|
||||
->select('id, name, agent_code, retention_rate')
|
||||
->where('is_active', 1)
|
||||
->orderBy('name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$monthRows = $db->table('partner_grid_details')
|
||||
->select("DATE_FORMAT(created_at, '%Y-%m') AS month_key")
|
||||
->where('created_at IS NOT NULL', null, false)
|
||||
->groupBy("DATE_FORMAT(created_at, '%Y-%m')")
|
||||
->orderBy('month_key', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$months = array_values(array_filter(array_map(static function ($row) {
|
||||
return $row['month_key'] ?? null;
|
||||
}, $monthRows)));
|
||||
|
||||
$latestMonth = !empty($months) ? $months[0] : date('Y-m');
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => [
|
||||
'rto_master' => $rtoMaster,
|
||||
'segment_master' => $segmentMaster,
|
||||
'plan_master' => $planMaster,
|
||||
'partner_master' => $partnerMaster,
|
||||
'months' => $months,
|
||||
'default_month' => $latestMonth,
|
||||
],
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Monthly commission grid list
|
||||
public function monthlyCommissionGridList()
|
||||
{
|
||||
try {
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$partnerId = (int) ($this->request->getGet('partner_id') ?? 0);
|
||||
$rtoId = (int) ($this->request->getGet('rto_id') ?? 0);
|
||||
$segmentId = (int) ($this->request->getGet('segment_id') ?? 0);
|
||||
$planType = trim((string) ($this->request->getGet('insurance_plan_type') ?? ''));
|
||||
$month = trim((string) ($this->request->getGet('month') ?? ''));
|
||||
|
||||
if ($partnerId <= 0 || $rtoId <= 0 || $segmentId <= 0 || $planType === '') {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 200,
|
||||
'data' => 'partner_id, rto_id, segment_id and insurance_plan_type are required',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$normalizedPlan = strtolower($planType);
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$latestRow = $db->table('partner_grid_details')
|
||||
->select("DATE_FORMAT(created_at, '%Y-%m') AS month_key")
|
||||
->where('created_at IS NOT NULL', null, false)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
|
||||
$effectiveMonth = $month !== '' ? $month : (($latestRow['month_key'] ?? '') ?: date('Y-m'));
|
||||
|
||||
$rows = $db->table('partner_grid_details pgd')
|
||||
->select("
|
||||
pgd.id,
|
||||
pgd.partner_id,
|
||||
pa.name AS partner_name,
|
||||
pa.agent_code,
|
||||
COALESCE(pa.retention_rate, 0) AS retention_rate,
|
||||
pgd.rto_id,
|
||||
rm.rto_code,
|
||||
rm.rto_name,
|
||||
pgd.segment,
|
||||
pgd.vehicle_type_id,
|
||||
vt.vehicle_type AS vehicle_type_name,
|
||||
pgd.comp,
|
||||
pgd.tp,
|
||||
pgd.fuel,
|
||||
pgd.remarks,
|
||||
DATE_FORMAT(pgd.created_at, '%Y-%m') AS month_key
|
||||
")
|
||||
->join('partner_agent pa', 'pa.id = pgd.partner_id', 'left')
|
||||
->join('rto_master rm', 'rm.id = pgd.rto_id', 'left')
|
||||
->join('vehicle_type vt', 'vt.id = pgd.vehicle_type_id', 'left')
|
||||
->where('pgd.partner_id', $partnerId)
|
||||
->where('pgd.rto_id', $rtoId)
|
||||
->where('pgd.segment_id', $segmentId)
|
||||
->where("DATE_FORMAT(pgd.created_at, '%Y-%m') =", $effectiveMonth)
|
||||
->orderBy('pgd.id', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$toPercent = static function ($value): float {
|
||||
if ($value === null) {
|
||||
return 0.0;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return (float) $value;
|
||||
}
|
||||
if (preg_match('/-?\d+(\.\d+)?/', (string) $value, $matches)) {
|
||||
return (float) $matches[0];
|
||||
}
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$retention = $toPercent($row['retention_rate'] ?? 0);
|
||||
$compRate = $toPercent($row['comp'] ?? 0);
|
||||
$tpRate = $toPercent($row['tp'] ?? 0);
|
||||
|
||||
if (strpos($normalizedPlan, 'third') !== false || $normalizedPlan === 'tp') {
|
||||
$gridRate = $tpRate;
|
||||
} else {
|
||||
// Comprehensive + Own Damage use COMP column.
|
||||
$gridRate = $compRate;
|
||||
}
|
||||
|
||||
$netRate = $gridRate - $retention;
|
||||
|
||||
$row['insurance_plan_type'] = $planType;
|
||||
$row['month'] = $effectiveMonth;
|
||||
$row['grid_rate_percentage'] = round($gridRate, 2);
|
||||
$row['retention_rate_percentage'] = round($retention, 2);
|
||||
$row['final_commission_percentage'] = round($netRate, 2);
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => $result,
|
||||
'meta' => [
|
||||
'month' => $effectiveMonth,
|
||||
'insurance_plan_type' => $planType,
|
||||
],
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Monthly commission grid download
|
||||
public function downloadMonthlyCommissionGrid()
|
||||
{
|
||||
try {
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$partnerId = (int) ($this->request->getGet('partner_id') ?? 0);
|
||||
$rtoId = (int) ($this->request->getGet('rto_id') ?? 0);
|
||||
$segmentId = (int) ($this->request->getGet('segment_id') ?? 0);
|
||||
$planType = trim((string) ($this->request->getGet('insurance_plan_type') ?? ''));
|
||||
$month = trim((string) ($this->request->getGet('month') ?? ''));
|
||||
|
||||
if ($partnerId <= 0 || $rtoId <= 0 || $segmentId <= 0 || $planType === '') {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 200,
|
||||
'data' => 'partner_id, rto_id, segment_id and insurance_plan_type are required',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$normalizedPlan = strtolower($planType);
|
||||
$db = \Config\Database::connect();
|
||||
$latestRow = $db->table('partner_grid_details')
|
||||
->select("DATE_FORMAT(created_at, '%Y-%m') AS month_key")
|
||||
->where('created_at IS NOT NULL', null, false)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
$effectiveMonth = $month !== '' ? $month : (($latestRow['month_key'] ?? '') ?: date('Y-m'));
|
||||
|
||||
$sourceRows = $db->table('partner_grid_details pgd')
|
||||
->select("
|
||||
pa.name AS partner_name,
|
||||
pa.agent_code,
|
||||
COALESCE(pa.retention_rate, 0) AS retention_rate,
|
||||
rm.rto_code,
|
||||
rm.rto_name,
|
||||
vt.vehicle_type AS segment_name,
|
||||
pgd.comp,
|
||||
pgd.tp
|
||||
")
|
||||
->join('partner_agent pa', 'pa.id = pgd.partner_id', 'left')
|
||||
->join('rto_master rm', 'rm.id = pgd.rto_id', 'left')
|
||||
->join('vehicle_type vt', 'vt.id = pgd.segment_id', 'left')
|
||||
->where('pgd.partner_id', $partnerId)
|
||||
->where('pgd.rto_id', $rtoId)
|
||||
->where('pgd.segment_id', $segmentId)
|
||||
->where("DATE_FORMAT(pgd.created_at, '%Y-%m') =", $effectiveMonth)
|
||||
->orderBy('pgd.id', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$toPercent = static function ($value): float {
|
||||
if ($value === null) {
|
||||
return 0.0;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return (float) $value;
|
||||
}
|
||||
if (preg_match('/-?\d+(\.\d+)?/', (string) $value, $matches)) {
|
||||
return (float) $matches[0];
|
||||
}
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
$rows = [];
|
||||
foreach ($sourceRows as $row) {
|
||||
$retention = $toPercent($row['retention_rate'] ?? 0);
|
||||
$compRate = $toPercent($row['comp'] ?? 0);
|
||||
$tpRate = $toPercent($row['tp'] ?? 0);
|
||||
$gridRate = (strpos($normalizedPlan, 'third') !== false || $normalizedPlan === 'tp') ? $tpRate : $compRate;
|
||||
|
||||
$row['month'] = $effectiveMonth;
|
||||
$row['insurance_plan_type'] = $planType;
|
||||
$row['grid_rate_percentage'] = round($gridRate, 2);
|
||||
$row['retention_rate_percentage'] = round($retention, 2);
|
||||
$row['final_commission_percentage'] = round($gridRate - $retention, 2);
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'data' => 'No data found for the selected filters',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$safePlan = preg_replace('/[^a-zA-Z0-9]+/', '_', strtolower($planType)) ?: 'plan';
|
||||
$fileName = "monthly_commission_{$effectiveMonth}_{$safePlan}.csv";
|
||||
$tmpFile = WRITEPATH . 'uploads/temp/' . $fileName;
|
||||
if (!is_dir(dirname($tmpFile))) {
|
||||
mkdir(dirname($tmpFile), 0777, true);
|
||||
}
|
||||
|
||||
$fp = fopen($tmpFile, 'w');
|
||||
fputcsv($fp, [
|
||||
'Month',
|
||||
'Partner',
|
||||
'Partner Code',
|
||||
'RTO',
|
||||
'Segment',
|
||||
'Insurance Plan',
|
||||
'Grid %',
|
||||
'Retention %',
|
||||
'Final Commission %',
|
||||
]);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($fp, [
|
||||
$row['month'] ?? $effectiveMonth,
|
||||
$row['partner_name'] ?? '',
|
||||
$row['agent_code'] ?? '',
|
||||
trim((string) (($row['rto_code'] ?? '') . ' - ' . ($row['rto_name'] ?? '')), ' -'),
|
||||
$row['segment_name'] ?? '',
|
||||
$row['insurance_plan_type'] ?? $planType,
|
||||
$row['grid_rate_percentage'] ?? 0,
|
||||
$row['retention_rate_percentage'] ?? 0,
|
||||
$row['final_commission_percentage'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
return $this->response->download($tmpFile, null)->setFileName($fileName);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ use App\Models\QuotationModel;
|
||||
use App\Models\InvoiceModel;
|
||||
use App\Models\InvoiceItemModel;
|
||||
use App\Models\InvoiceUtrModel;
|
||||
use App\Models\PartnerAccountHistoryModel;
|
||||
use CodeIgniter\Database\Exceptions\DataException;
|
||||
|
||||
class InvoiceController extends ResourceController
|
||||
@ -18,6 +19,7 @@ class InvoiceController extends ResourceController
|
||||
protected $InvoiceModel;
|
||||
protected $InvoiceItemModel;
|
||||
protected $InvoiceUtrModel;
|
||||
protected $PartnerAccountHistoryModel;
|
||||
protected $db;
|
||||
|
||||
public function __construct()
|
||||
@ -28,6 +30,7 @@ class InvoiceController extends ResourceController
|
||||
$this->InvoiceModel = new InvoiceModel();
|
||||
$this->InvoiceItemModel = new InvoiceItemModel();
|
||||
$this->InvoiceUtrModel = new InvoiceUtrModel();
|
||||
$this->PartnerAccountHistoryModel = new PartnerAccountHistoryModel();
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
@ -53,19 +56,37 @@ class InvoiceController extends ResourceController
|
||||
AND piu.is_active = 1
|
||||
) AS utr_numbers,
|
||||
partner_invoice.invoice_amount AS invoiced_amount,
|
||||
COALESCE((
|
||||
SELECT SUM(piu.amount)
|
||||
FROM partner_invoice_utr piu
|
||||
WHERE piu.invoice_id = partner_invoice.id
|
||||
AND piu.is_active = 1
|
||||
), 0.00) AS payout_amount,
|
||||
(
|
||||
partner_invoice.invoice_amount - COALESCE((
|
||||
COALESCE((
|
||||
SELECT SUM(pah.paid_amount)
|
||||
FROM partner_account_history pah
|
||||
WHERE pah.invoice_id = partner_invoice.id
|
||||
AND pah.is_active = 1
|
||||
), 0.00)
|
||||
+
|
||||
COALESCE((
|
||||
SELECT SUM(piu.amount)
|
||||
FROM partner_invoice_utr piu
|
||||
WHERE piu.invoice_id = partner_invoice.id
|
||||
AND piu.is_active = 1
|
||||
), 0.00)
|
||||
) AS payout_amount,
|
||||
(
|
||||
partner_invoice.invoice_amount - (
|
||||
COALESCE((
|
||||
SELECT SUM(pah.paid_amount)
|
||||
FROM partner_account_history pah
|
||||
WHERE pah.invoice_id = partner_invoice.id
|
||||
AND pah.is_active = 1
|
||||
), 0.00)
|
||||
+
|
||||
COALESCE((
|
||||
SELECT SUM(piu.amount)
|
||||
FROM partner_invoice_utr piu
|
||||
WHERE piu.invoice_id = partner_invoice.id
|
||||
AND piu.is_active = 1
|
||||
), 0.00)
|
||||
)
|
||||
) AS balance_amount',
|
||||
false)
|
||||
->join('partner_brokers PB', 'PB.id = partner_invoice.broker_id', 'left')
|
||||
@ -104,6 +125,29 @@ class InvoiceController extends ResourceController
|
||||
->where('partner_invoice_items.is_active', 1)
|
||||
->findAll();
|
||||
|
||||
$paymentHistory = $this->PartnerAccountHistoryModel
|
||||
->where('invoice_id', $id)
|
||||
->where('is_active', 1)
|
||||
->orderBy('paid_date', 'DESC')
|
||||
->orderBy('id', 'DESC')
|
||||
->findAll();
|
||||
|
||||
$paidAmount = 0.00;
|
||||
foreach ($paymentHistory as $payment) {
|
||||
$paidAmount += (float) ($payment['paid_amount'] ?? 0);
|
||||
}
|
||||
|
||||
$utrPaidAmount = (float) (
|
||||
$this->InvoiceUtrModel
|
||||
->selectSum('amount', 'total')
|
||||
->where('invoice_id', $id)
|
||||
->where('is_active', 1)
|
||||
->first()['total'] ?? 0
|
||||
);
|
||||
|
||||
$totalPaidAmount = $paidAmount + $utrPaidAmount;
|
||||
$invoiceAmount = (float) ($invoice['invoice_amount'] ?? 0);
|
||||
$balanceAmount = max($invoiceAmount - $totalPaidAmount, 0);
|
||||
|
||||
|
||||
return $this->respond([
|
||||
@ -111,7 +155,10 @@ class InvoiceController extends ResourceController
|
||||
'code' => 200,
|
||||
'data' => [
|
||||
'invoice' => $invoice,
|
||||
'items' => $items
|
||||
'items' => $items,
|
||||
'payment_history' => $paymentHistory,
|
||||
'paid_amount' => $totalPaidAmount,
|
||||
'balance_amount' => $balanceAmount
|
||||
]
|
||||
], 200);
|
||||
|
||||
@ -415,6 +462,461 @@ class InvoiceController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
public function addInvoicePayment()
|
||||
{
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
$input = $this->request->getJSON(true);
|
||||
|
||||
$invoiceId = (int)($input['invoice_id'] ?? 0);
|
||||
$paidAmount = (float)($input['paid_amount'] ?? 0);
|
||||
$paidDateRaw = $input['paid_date'] ?? null;
|
||||
|
||||
if ($invoiceId <= 0 || $paidAmount <= 0 || empty($paidDateRaw)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message'=> 'invoice_id, paid_amount and paid_date are required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$invoice = $this->InvoiceModel
|
||||
->where('id', $invoiceId)
|
||||
->where('is_active', 1)
|
||||
->first();
|
||||
|
||||
if (empty($invoice)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'message'=> 'Invoice not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$paidDateTs = strtotime($paidDateRaw);
|
||||
if ($paidDateTs === false) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message'=> 'Invalid paid_date format'
|
||||
], 400);
|
||||
}
|
||||
$paidDate = date('Y-m-d', $paidDateTs);
|
||||
|
||||
$historyPaidAmount = (float) (
|
||||
$this->PartnerAccountHistoryModel
|
||||
->selectSum('paid_amount', 'total')
|
||||
->where('invoice_id', $invoiceId)
|
||||
->where('is_active', 1)
|
||||
->first()['total'] ?? 0
|
||||
);
|
||||
|
||||
$utrPaidAmount = (float) (
|
||||
$this->InvoiceUtrModel
|
||||
->selectSum('amount', 'total')
|
||||
->where('invoice_id', $invoiceId)
|
||||
->where('is_active', 1)
|
||||
->first()['total'] ?? 0
|
||||
);
|
||||
|
||||
$currentTotalPaid = $historyPaidAmount + $utrPaidAmount;
|
||||
$invoiceAmount = (float) ($invoice['invoice_amount'] ?? 0);
|
||||
$remainingBalance = max($invoiceAmount - $currentTotalPaid, 0);
|
||||
|
||||
if ($paidAmount > $remainingBalance) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message'=> 'Paid amount exceeds invoice balance'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$historyData = [
|
||||
'invoice_id' => $invoiceId,
|
||||
'paid_amount' => $paidAmount,
|
||||
'paid_date' => $paidDate,
|
||||
'is_active' => 1,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'created_by' => (int)($input['created_by'] ?? 0),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'updated_by' => (int)($input['updated_by'] ?? 0),
|
||||
];
|
||||
|
||||
$historyId = $this->PartnerAccountHistoryModel->insert($historyData);
|
||||
|
||||
if ($historyId === false) {
|
||||
$this->db->transRollback();
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'message'=> 'Failed to record payment',
|
||||
'error' => $this->PartnerAccountHistoryModel->errors()
|
||||
], 500);
|
||||
}
|
||||
|
||||
$latestTotalPaid = $currentTotalPaid + $paidAmount;
|
||||
$newBalance = max($invoiceAmount - $latestTotalPaid, 0);
|
||||
$payoutStatus = $newBalance == 0.0 ? 2 : 1;
|
||||
|
||||
$this->InvoiceModel->update($invoiceId, [
|
||||
'payout_status' => $payoutStatus,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'updated_by' => (int)($input['updated_by'] ?? 0),
|
||||
]);
|
||||
|
||||
if ($this->db->transStatus() === false) {
|
||||
$this->db->transRollback();
|
||||
throw new DataException('Transaction failed');
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => [
|
||||
'history_id' => $historyId,
|
||||
'invoice_id' => $invoiceId,
|
||||
'paid_amount' => $latestTotalPaid,
|
||||
'balance_amount' => $newBalance
|
||||
]
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'message'=> $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function bulkUploadCommission()
|
||||
{
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
$input = $this->request->getJSON(true);
|
||||
$rows = $input['rows'] ?? [];
|
||||
$updatedBy = (int)($input['updated_by'] ?? 0);
|
||||
|
||||
if (empty($rows) || !is_array($rows)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message' => 'rows is required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$mismatchRows = [];
|
||||
$validRows = [];
|
||||
$skippedRows = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$policyNo = trim((string)($row['policy_number'] ?? ''));
|
||||
$invoiceNo = trim((string)($row['invoice_no'] ?? ''));
|
||||
$commissionAmount = (float)($row['commission_amount'] ?? 0);
|
||||
$lineNo = (int)($row['line_no'] ?? 0);
|
||||
|
||||
if ($policyNo === '' || $invoiceNo === '') {
|
||||
$skippedRows++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$record = $this->db->table('partner_invoice_items pii')
|
||||
->select('
|
||||
pii.id as item_id,
|
||||
pii.invoice_id,
|
||||
pii.policy_id,
|
||||
pii.policy_no,
|
||||
pi.invoice_no,
|
||||
pi.agent_id as invoice_agent_json,
|
||||
pe.agent_id as policy_agent_id
|
||||
')
|
||||
->join('partner_invoice pi', 'pi.id = pii.invoice_id', 'inner')
|
||||
->join('partner_policy pp', 'pp.id = pii.policy_id', 'left')
|
||||
->join('partner_enquiry pe', 'pe.id = pp.enquiry_id', 'left')
|
||||
->where('pii.is_active', 1)
|
||||
->where('pi.is_active', 1)
|
||||
->where('pi.invoice_no', $invoiceNo)
|
||||
->where('pii.policy_no', $policyNo)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (empty($record)) {
|
||||
$skippedRows++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoiceAgentIds = $this->extractAgentIdsFromInvoice($record['invoice_agent_json'] ?? '');
|
||||
$policyAgentId = isset($record['policy_agent_id']) ? (int)$record['policy_agent_id'] : 0;
|
||||
$targetAgentId = !empty($invoiceAgentIds) ? (int)$invoiceAgentIds[0] : 0;
|
||||
$isMismatch = $policyAgentId > 0 && !in_array($policyAgentId, $invoiceAgentIds, true);
|
||||
|
||||
if ($isMismatch) {
|
||||
$mismatchRows[] = [
|
||||
'line_no' => $lineNo,
|
||||
'item_id' => (int)$record['item_id'],
|
||||
'invoice_id' => (int)$record['invoice_id'],
|
||||
'policy_id' => (int)$record['policy_id'],
|
||||
'policy_number' => $policyNo,
|
||||
'invoice_no' => $invoiceNo,
|
||||
'commission_amount' => $commissionAmount,
|
||||
'policy_agent_id' => $policyAgentId,
|
||||
'target_agent_id' => $targetAgentId,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$validRows[] = [
|
||||
'item_id' => (int)$record['item_id'],
|
||||
'invoice_id' => (int)$record['invoice_id'],
|
||||
'policy_id' => (int)$record['policy_id'],
|
||||
'commission_amount' => $commissionAmount,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($mismatchRows)) {
|
||||
$this->ensureBulkUploadStagingTable();
|
||||
$token = $this->generateProceedToken();
|
||||
|
||||
$this->db->table('invoice_bulk_upload_staging')->insert([
|
||||
'proceed_token' => $token,
|
||||
'payload_json' => json_encode([
|
||||
'rows' => $rows,
|
||||
'updated_by' => $updatedBy,
|
||||
]),
|
||||
'mismatch_json' => json_encode($mismatchRows),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'created_by' => $updatedBy,
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
$this->db->transRollback();
|
||||
return $this->respond([
|
||||
'status' => 'partner_mismatch',
|
||||
'code' => 200,
|
||||
'message' => 'Partner mismatch detected',
|
||||
'mismatch_count' => count($mismatchRows),
|
||||
'mismatches' => $mismatchRows,
|
||||
'proceed_token' => $token,
|
||||
], 200);
|
||||
}
|
||||
|
||||
$updatedCount = $this->applyBulkCommissionRows($validRows, $updatedBy);
|
||||
|
||||
if ($this->db->transStatus() === false) {
|
||||
$this->db->transRollback();
|
||||
throw new DataException('Transaction failed');
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'message' => 'Bulk upload completed',
|
||||
'updated_count' => $updatedCount,
|
||||
'skipped_count' => $skippedRows,
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'message' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function bulkUploadCommissionProceed()
|
||||
{
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
$input = $this->request->getJSON(true);
|
||||
$proceedToken = trim((string)($input['proceed_token'] ?? ''));
|
||||
$forceReassign = (int)($input['force_reassign_partner'] ?? 0);
|
||||
$updatedBy = (int)($input['updated_by'] ?? 0);
|
||||
|
||||
if ($proceedToken === '' || $forceReassign !== 1) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message' => 'proceed_token and force_reassign_partner=1 are required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$this->ensureBulkUploadStagingTable();
|
||||
$staging = $this->db->table('invoice_bulk_upload_staging')
|
||||
->where('proceed_token', $proceedToken)
|
||||
->where('is_active', 1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (empty($staging)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'message' => 'Invalid or expired proceed token',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$payload = json_decode($staging['payload_json'] ?? '{}', true);
|
||||
$rows = $payload['rows'] ?? [];
|
||||
$mismatchRows = json_decode($staging['mismatch_json'] ?? '[]', true);
|
||||
|
||||
foreach ($mismatchRows as $mismatch) {
|
||||
$policyId = (int)($mismatch['policy_id'] ?? 0);
|
||||
$targetAgentId = (int)($mismatch['target_agent_id'] ?? 0);
|
||||
if ($policyId <= 0 || $targetAgentId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$policy = $this->PolicyModel->select('enquiry_id')->where('id', $policyId)->first();
|
||||
if (!empty($policy['enquiry_id'])) {
|
||||
$this->EnquiryModel->update((int)$policy['enquiry_id'], [
|
||||
'agent_id' => $targetAgentId,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'updated_by' => $updatedBy,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$finalRows = [];
|
||||
foreach ($rows as $row) {
|
||||
$policyNo = trim((string)($row['policy_number'] ?? ''));
|
||||
$invoiceNo = trim((string)($row['invoice_no'] ?? ''));
|
||||
$commissionAmount = (float)($row['commission_amount'] ?? 0);
|
||||
if ($policyNo === '' || $invoiceNo === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$record = $this->db->table('partner_invoice_items pii')
|
||||
->select('pii.id as item_id, pii.invoice_id, pii.policy_id')
|
||||
->join('partner_invoice pi', 'pi.id = pii.invoice_id', 'inner')
|
||||
->where('pii.is_active', 1)
|
||||
->where('pi.is_active', 1)
|
||||
->where('pi.invoice_no', $invoiceNo)
|
||||
->where('pii.policy_no', $policyNo)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (empty($record)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$finalRows[] = [
|
||||
'item_id' => (int)$record['item_id'],
|
||||
'invoice_id' => (int)$record['invoice_id'],
|
||||
'policy_id' => (int)$record['policy_id'],
|
||||
'commission_amount' => $commissionAmount,
|
||||
];
|
||||
}
|
||||
|
||||
$updatedCount = $this->applyBulkCommissionRows($finalRows, $updatedBy);
|
||||
|
||||
$this->db->table('invoice_bulk_upload_staging')
|
||||
->where('id', (int)$staging['id'])
|
||||
->update([
|
||||
'is_active' => 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'updated_by' => $updatedBy,
|
||||
]);
|
||||
|
||||
if ($this->db->transStatus() === false) {
|
||||
$this->db->transRollback();
|
||||
throw new DataException('Transaction failed');
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'message' => 'Bulk upload completed with partner reassignment',
|
||||
'updated_count' => $updatedCount,
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'message' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyBulkCommissionRows(array $rows, int $updatedBy): int
|
||||
{
|
||||
$updatedCount = 0;
|
||||
foreach ($rows as $row) {
|
||||
$itemId = (int)($row['item_id'] ?? 0);
|
||||
$invoiceId = (int)($row['invoice_id'] ?? 0);
|
||||
$policyId = (int)($row['policy_id'] ?? 0);
|
||||
$commissionAmount = (float)($row['commission_amount'] ?? 0);
|
||||
if ($itemId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$affected = $this->db->table('partner_invoice_items')
|
||||
->where('id', $itemId)
|
||||
->where('invoice_id', $invoiceId)
|
||||
->where('policy_id', $policyId)
|
||||
->where('is_active', 1)
|
||||
->update([
|
||||
'commission_amount' => $commissionAmount,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'updated_by' => $updatedBy,
|
||||
]);
|
||||
|
||||
if ($affected) {
|
||||
$updatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedCount;
|
||||
}
|
||||
|
||||
private function extractAgentIdsFromInvoice($agentJson): array
|
||||
{
|
||||
if (is_array($agentJson)) {
|
||||
return array_values(array_map('intval', $agentJson));
|
||||
}
|
||||
if ($agentJson === null || $agentJson === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode((string)$agentJson, true);
|
||||
if (is_array($decoded)) {
|
||||
return array_values(array_map('intval', $decoded));
|
||||
}
|
||||
|
||||
if (is_numeric($agentJson)) {
|
||||
return [(int)$agentJson];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function generateProceedToken(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
private function ensureBulkUploadStagingTable(): void
|
||||
{
|
||||
$sql = "CREATE TABLE IF NOT EXISTS invoice_bulk_upload_staging (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
proceed_token VARCHAR(64) NOT NULL UNIQUE,
|
||||
payload_json LONGTEXT NULL,
|
||||
mismatch_json LONGTEXT NULL,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NULL,
|
||||
created_by INT NULL,
|
||||
updated_at DATETIME NULL,
|
||||
updated_by INT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
|
||||
$this->db->query($sql);
|
||||
}
|
||||
|
||||
|
||||
// public function getCommissionRateList()
|
||||
// {
|
||||
|
||||
@ -15,6 +15,7 @@ class AgentIncentiveFileModel extends Model
|
||||
'agent_id',
|
||||
'incentive_month',
|
||||
'incentive_file_name',
|
||||
'file_type',
|
||||
'is_active',
|
||||
'created_by',
|
||||
'created_on',
|
||||
@ -34,6 +35,7 @@ class AgentIncentiveFileModel extends Model
|
||||
protected $validationRules = [
|
||||
'agent_id' => 'required|integer',
|
||||
'incentive_month' => 'required|valid_date',
|
||||
'incentive_file_name'=> 'required|string|max_length[150]'
|
||||
'incentive_file_name'=> 'required|string|max_length[150]',
|
||||
'file_type' => 'permit_empty|max_length[50]'
|
||||
];
|
||||
}
|
||||
|
||||
39
app/Models/PartnerAccountHistoryModel.php
Normal file
39
app/Models/PartnerAccountHistoryModel.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PartnerAccountHistoryModel extends Model
|
||||
{
|
||||
protected $table = 'partner_account_history';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
|
||||
protected $allowedFields = [
|
||||
'invoice_id',
|
||||
'paid_amount',
|
||||
'paid_date',
|
||||
'is_active',
|
||||
'created_at',
|
||||
'created_by',
|
||||
'updated_at',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
protected $useTimestamps = false;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
protected $validationRules = [
|
||||
'invoice_id' => 'required|integer',
|
||||
'paid_amount' => 'required|decimal',
|
||||
'paid_date' => 'required|valid_date',
|
||||
];
|
||||
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
}
|
||||
187
app/Models/PartnerGridDetailsModel.php
Normal file
187
app/Models/PartnerGridDetailsModel.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PartnerGridDetailsModel extends Model
|
||||
{
|
||||
// ─────────────────────────────────────────
|
||||
// Table Configuration
|
||||
// ─────────────────────────────────────────
|
||||
protected $table = 'partner_grid_details';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Allowed Fields
|
||||
// ─────────────────────────────────────────
|
||||
protected $allowedFields = [
|
||||
'vehicle_type_id',
|
||||
'insurer_id',
|
||||
'rto_id',
|
||||
'segment_id',
|
||||
'comp',
|
||||
'tp',
|
||||
'fuel',
|
||||
'remarks',
|
||||
'partner_id',
|
||||
'partner_comp',
|
||||
'partner_tp',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Timestamps
|
||||
// ─────────────────────────────────────────
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Validation Rules
|
||||
// ─────────────────────────────────────────
|
||||
protected $validationRules = [
|
||||
'vehicle_type_id' => 'permit_empty|max_length[50]',
|
||||
'insurer_id' => 'required|max_length[50]',
|
||||
'rto_id' => 'required|max_length[50]',
|
||||
'segment_id' => 'required|max_length[100]',
|
||||
'comp' => 'permit_empty|max_length[50]',
|
||||
'tp' => 'permit_empty|max_length[50]',
|
||||
'remarks' => 'permit_empty|max_length[255]',
|
||||
'partner_id' => 'required|max_length[50]',
|
||||
'fuel' => 'permit_empty|max_length[50]',
|
||||
'partner_comp' => 'permit_empty|max_length[20]',
|
||||
'partner_tp' => 'permit_empty|max_length[20]',
|
||||
'created_by' => 'permit_empty|integer',
|
||||
'updated_by' => 'permit_empty|integer',
|
||||
];
|
||||
|
||||
protected $validationMessages = [
|
||||
'insurer_id' => [
|
||||
'required' => 'Insurer is required.',
|
||||
'max_length' => 'Insurer name must not exceed 50 characters.',
|
||||
],
|
||||
'rto_id' => [
|
||||
'required' => 'RTO is required.',
|
||||
'max_length' => 'RTO code must not exceed 50 characters.',
|
||||
],
|
||||
'segment_id' => [
|
||||
'required' => 'Segment is required.',
|
||||
'max_length' => 'Segment must not exceed 100 characters.',
|
||||
],
|
||||
'partner_id' => [
|
||||
'required' => 'Partner is required.',
|
||||
'max_length' => 'Partner ID must not exceed 50 characters.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $skipValidation = false;
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Custom Methods
|
||||
// ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get all active partner grid records
|
||||
*/
|
||||
public function getAllRecords()
|
||||
{
|
||||
return $this->orderBy('id', 'ASC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Insurer ID
|
||||
*/
|
||||
public function getByInsurer(string $insurerId)
|
||||
{
|
||||
return $this->where('insurer_id', $insurerId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by RTO ID
|
||||
*/
|
||||
public function getByRTO(string $rtoId)
|
||||
{
|
||||
return $this->where('rto_id', $rtoId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Segment ID
|
||||
*/
|
||||
public function getBySegment(string $segmentId)
|
||||
{
|
||||
return $this->where('segment_id', $segmentId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Partner ID
|
||||
*/
|
||||
public function getByPartner(string $partnerId)
|
||||
{
|
||||
return $this->where('partner_id', $partnerId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Insurer and RTO
|
||||
*/
|
||||
public function getByInsurerAndRTO(string $insurerId, string $rtoId)
|
||||
{
|
||||
return $this->where('insurer_id', $insurerId)
|
||||
->where('rto_id', $rtoId)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search with multiple filters
|
||||
*/
|
||||
public function search(array $filters = [])
|
||||
{
|
||||
$builder = $this->builder();
|
||||
|
||||
if (!empty($filters['insurer_id'])) {
|
||||
$builder->where('insurer_id', $filters['insurer_id']);
|
||||
}
|
||||
if (!empty($filters['rto_id'])) {
|
||||
$builder->where('rto_id', $filters['rto_id']);
|
||||
}
|
||||
if (!empty($filters['segment_id'])) {
|
||||
$builder->where('segment_id', $filters['segment_id']);
|
||||
}
|
||||
if (!empty($filters['partner_id'])) {
|
||||
$builder->where('partner_id', $filters['partner_id']);
|
||||
}
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert with created_by
|
||||
*/
|
||||
public function insertRecord(array $data, int $userId)
|
||||
{
|
||||
$data['created_by'] = $userId;
|
||||
$data['updated_by'] = $userId;
|
||||
return $this->insert($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update with updated_by
|
||||
*/
|
||||
public function updateRecord(int $id, array $data, int $userId)
|
||||
{
|
||||
$data['updated_by'] = $userId;
|
||||
return $this->update($id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a record by ID
|
||||
*/
|
||||
public function deleteRecord(int $id)
|
||||
{
|
||||
return $this->delete($id);
|
||||
}
|
||||
}
|
||||
BIN
writable/uploads/sample_partner_grid_file.xls
Normal file
BIN
writable/uploads/sample_partner_grid_file.xls
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user