FEAT_COMMISSION_CALC

This commit is contained in:
velz 2025-11-14 15:30:23 +05:30
parent 5f99271492
commit fbd532258d
5 changed files with 336 additions and 1 deletions

View File

@ -13,6 +13,7 @@ use App\Filters\AuthMVC;
use App\Filters\HttpRequestLog;
use App\Filters\CloseDbConnection;
use App\Filters\AuthClientApi;
use App\Filters\CommissionApiFilter;
use App\Filters\AuthJWT;
@ -36,7 +37,8 @@ class Filters extends BaseConfig
'HttpRequestLog' => HttpRequestLog::class,
'authJWT' => AuthJWT::class,
'AuthClientApi' => AuthClientApi::class,
'CloseDbConnection' => CloseDbConnection::class
'CloseDbConnection' => CloseDbConnection::class,
'CommissionApiFilter' => CommissionApiFilter::class
];
/**

View File

@ -34,6 +34,7 @@ $routes->get("updateRenewalData", "ClientController::updateRenewalData");
$routes->get("updateRenewalDataNotExistingClient", "ClientController::updateRenewalDataNotExistingClient");
$routes->get("updateRenewalInsurerData", "ClientController::updateRenewalInsurerData");
$routes->get("sendMutipleToEmails", "MasterController::sendMutipleToEmails");
$routes->post("getCommission", "InsuranceCommissionController::initiateCommissionCalc",['filter' => 'CommissionApiFilter']);
// $routes->post("iAgreeForAddOn", "EmployeeRestController::iAgreeForAddOn");
// $routes->get("sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail");
// $routes->post("employeeUpload", "EmployeeRestController::employeeUpload");

View File

@ -0,0 +1,274 @@
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\API\ResponseTrait;
class InsuranceCommissionController extends AdminController
{
use ResponseTrait;
private $rules = [];
public function __construct()
{
set_session_context('Client');
$this->myLogger = \Config\Services::mylogger();
// Load rules file if present in writable config path
// $rulesPath = WRITEPATH . 'config/insurance_rules.json';
// if (file_exists($rulesPath)) {
// $this->loadRulesFromFile($rulesPath);
// }
}
/**
* POST /insurance/calculate
* Accepts JSON body with policy data and returns commission calculation
*/
public function initiateCommissionCalc()
{
// Accept POST params (JSON, form-data, x-www-form-urlencoded)
$input = $this->request->getPost();
if (empty($input)) {
$json = $this->request->getJSON(true);
if ($json) {
$input = $json;
}
}
if (empty($input)) {
return $this->failValidationError('No input data received');
}
// -------- Required Params Check --------
if (empty($input['policy_issue_date'])) {
return $this->failValidationError('policy_issue_date is required');
}
if (empty($input['department'])) {
return $this->failValidationError('department is required');
}
if (empty($input['insurer_id'])) {
return $this->failValidationError('insurer_id is required');
}
// -------- Build Dynamic Rules Path --------
$policyDate = strtotime($input['policy_issue_date']);
if (!$policyDate) {
return $this->failValidationError('Invalid policy_issue_date');
}
$month = strtoupper(date('M', $policyDate)); // SEP
$year = date('Y', $policyDate); // 2025
$folderName = $month . $year; // SEP2025
$insurerId = $input['insurer_id']; // 5
$department = ucfirst(strtolower($input['department'])); // Motor, Health, Fire
// Final Path: WRITEPATH/rules/SEP2025/5_Motor.json
$rulesPath = WRITEPATH . "uploads/commission/rules/{$folderName}/{$insurerId}_{$department}.json";
// echo $rulesPath;die();
if (!file_exists($rulesPath)) {
return $this->fail("Rules file not found at: {$rulesPath}");
}
// Load the dynamic rule set
$this->loadRulesFromFile($rulesPath);
// -------- Execute Rule Matching & Commission Calculation --------
try {
$result = $this->calculateCommission($input);
$comment = isset($result['rule']['name'])
? "Matched rule: " . $result['rule']['name']
: "Matched rule: (unnamed rule)";
return $this->respond([
'success' => true,
'data' => [
'payout' => $result['payout'],
'rule' => $result['rule'],
'comment' => $comment,
// 'rules_path_used' => $rulesPath
]
]);
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
}
/**
* Load rules JSON and normalise department keys to lowercase for lookups
*/
private function loadRulesFromFile(string $filePath)
{
if (!file_exists($filePath)) {
throw new \Exception("Rules file not found: {$filePath}");
}
$json = file_get_contents($filePath);
$parsed = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Invalid JSON in rules file: ' . json_last_error_msg());
}
// Normalise department keys to lowercase for consistent lookups
$this->rules = [];
foreach ($parsed as $dept => $rules) {
$this->rules[strtolower($dept)] = $rules;
}
}
public function calculateCommission(array $policyData)
{
$department = $policyData['department'] ?? '';
$deptKey = strtolower($department);
if (!isset($this->rules[$deptKey])) {
throw new \Exception("No rules found for department: {$department}");
}
$matchingRules = [];
foreach ($this->rules[$deptKey] as $rule) {
if ($this->evaluateConditions($rule['conditions'] ?? [], $policyData)) {
$matchingRules[] = $rule;
}
}
if (empty($matchingRules)) {
throw new \Exception('No matching rules found for the policy data');
}
// Use the first matching rule. In future you can implement priority/weighting
$applicableRule = $matchingRules[0];
$payout = $this->applyCalculation($applicableRule['calculation'], $policyData);
return ['rule' => $applicableRule, 'payout' => $payout];
}
private function evaluateConditions(array $conditions, array $data): bool
{
foreach ($conditions as $condition) {
$field = $condition['field'];
$operator = $condition['operator'];
$expectedValue = $condition['value'];
if (!array_key_exists($field, $data)) {
return false;
}
$actualValue = $data[$field];
if (!$this->compareValues($actualValue, $operator, $expectedValue)) {
return false;
}
}
return true;
}
private function compareValues($actual, string $operator, $expected): bool
{
switch ($operator) {
case '==':
return $actual == $expected;
case '!=':
return $actual != $expected;
case '>':
return $actual > $expected;
case '>=':
return $actual >= $expected;
case '<':
return $actual < $expected;
case '<=':
return $actual <= $expected;
case 'between':
return is_array($expected) && $actual >= $expected[0] && $actual <= $expected[1];
case 'in':
return is_array($expected) && in_array($actual, $expected);
default:
throw new \Exception("Unsupported operator: {$operator}");
}
}
private function applyCalculation(array $calculation, array $policyData)
{
$type = $calculation['type'] ?? null;
switch ($type) {
case 'percentage':
$percentage = $calculation['value'] ?? 0;
$base = $calculation['on'] ?? null;
if ($base === null || !isset($policyData[$base])) {
throw new \Exception("Base value for calculation not found: {$base}");
}
return ($percentage / 100) * $policyData[$base];
case 'composite':
$total = 0;
foreach ($calculation['components'] as $component) {
$percentage = $component['percentage'] ?? 0;
$base = $component['on'] ?? null;
if ($base === null || !isset($policyData[$base])) {
throw new \Exception("Base value for calculation not found: {$base}");
}
if (!empty($component['only_first_year'])) {
if (!empty($policyData['is_renewal'])) {
continue; // Skip this component for renewals
}
}
$total += ($percentage / 100) * $policyData[$base];
}
return $total;
case 'fixed':
$fixedAmount = $calculation['value'] ?? 0;
// If 'on' specified but not needed, return fixed amount as-is
return $fixedAmount;
default:
throw new \Exception('Unsupported calculation type: ' . $type);
}
}
public function getVolumeReward(array $premiumData)
{
$annualPremium = $premiumData['annual_premium'] ?? 0;
$department = $premiumData['department'] ?? '';
if ($department === 'Fire' || $department === 'Marine' || $department === 'Engineering') {
if ($annualPremium > 20000000) {
return 0.01 * $annualPremium;
} elseif ($annualPremium > 10000000) {
return 0.005 * $annualPremium;
} elseif ($annualPremium > 5000000) {
return 0.0025 * $annualPremium;
}
} elseif ($department === 'Motor') {
if ($annualPremium > 15000000) {
return 0.02 * $annualPremium;
} elseif ($annualPremium > 7500000) {
return 0.01 * $annualPremium;
}
}
return 0;
}
}

View File

@ -1976,6 +1976,9 @@ class MasterController extends AdminController
'lead_files' => WRITEPATH . 'uploads/lead_files/',
'claim_files' => WRITEPATH . 'uploads/claim_files/',
'claim_dump_excel' => WRITEPATH . 'uploads/claim_dump_excel/',
'commission' => WRITEPATH . 'uploads/commission/',
'files' => WRITEPATH . 'uploads/commission/files',
'rules' => WRITEPATH . 'uploads/commission/rules',
'claim_sample_forms' => ROOTPATH . 'public/claim_sample_forms/',
];

View File

@ -0,0 +1,55 @@
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class CommissionApiFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
// Read API key from header
// $authHeader = $request->getHeaderLine('X');
$authHeader = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
// echo $authHeader;die();
if (empty($authHeader)) {
return service('response')->setJSON([
'success' => false,
'error' => 'Authorization header missing'
])->setStatusCode(403);
}
// Expected format: Bearer YOUR_API_KEY
if (stripos($authHeader, 'Bearer ') !== 0) {
return service('response')->setJSON([
'success' => false,
'error' => 'Invalid Authorization format. Expected: Bearer <token>'
])->setStatusCode(403);
}
$apiKey = trim(substr($authHeader, 7)); // extract token after 'Bearer '
$envKeys = getenv('ALLOWED_COMMISSION_API_KEYS');
// Convert CSV -> Array
$allowedKeys = array_map('trim', explode(',', $envKeys));
// print_r($allowedKeys);die();
// Validate
if (!in_array($apiKey, $allowedKeys, true)) {
return service('response')->setJSON([
'success' => false,
'error' => 'Invalid API Key'
])->setStatusCode(403);
}
// Allow request to proceed
return null;
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Not needed
}
}