From b4de45df168e49e31e5db1b5380fecdb6589068d Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Thu, 25 Jun 2026 14:21:09 +0530 Subject: [PATCH 1/9] FEAT_NETWORK_HOSPITAL_LINK_FOR_HR --- app/Controllers/EmployeeRestController.php | 44 ++++++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 176fba86..b88825dd 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -1677,19 +1677,55 @@ class EmployeeRestController extends AdminController } } + $responseData = [ + 'level_1' => $level_1, + 'level_2' => $level_2, + ]; + + $tpaNetworkHospitals = $this->getTpaNetworkHospitalsByClientId($client_id); + + if (! empty($tpaNetworkHospitals)) { + $responseData['tpa_network_hospitals'] = $tpaNetworkHospitals; + } + return $this->respond([ 'status' => 'success', 'code' => 200, - 'data' => [ - 'level_1' => $level_1, - 'level_2' => $level_2, - ], + 'data' => $responseData, ], 200); + } catch (\Exception $e) { return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500); } } + private function getTpaNetworkHospitalsByClientId($client_id) + { + $tpaRecords = $this->clientPolicyModel + ->select('tpa.name as tpa_name, tpa.network_hospitals') + ->join('tpa', 'client_policy.tpa_id = tpa.id', 'left') + ->where('MD5(client_policy.client_id)', $client_id) + ->where('client_policy.is_active', 1) + ->where('tpa.is_active', 1) + ->where('tpa.network_hospitals IS NOT NULL', null, false) + ->where('tpa.network_hospitals !=', '') + ->groupBy('tpa.id') + ->findAll(); + + $tpaNetworkHospitals = []; + + foreach ($tpaRecords as $tpaRecord) { + $tpaName = trim((string) ($tpaRecord['tpa_name'] ?? '')); + $networkHospitals = trim((string) ($tpaRecord['network_hospitals'] ?? '')); + + if ($tpaName !== '' && $networkHospitals !== '') { + $tpaNetworkHospitals[$tpaName] = $networkHospitals; + } + } + + return $tpaNetworkHospitals; + } + public function getAddOnPolicy() { try { From d0e29cc5acad4cdf54550fd268b4d8f0eb599857 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Thu, 25 Jun 2026 17:57:39 +0530 Subject: [PATCH 2/9] FIX_CORS_ERROR --- app/Filters/AuthJWT.php | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/app/Filters/AuthJWT.php b/app/Filters/AuthJWT.php index 45f4517a..6bde4421 100755 --- a/app/Filters/AuthJWT.php +++ b/app/Filters/AuthJWT.php @@ -3,6 +3,7 @@ namespace App\Filters; use App\Helpers\JWTToken; +use App\Filters\Cors; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -18,6 +19,13 @@ use App\Models\LevelContactModel; class AuthJWT implements FilterInterface { + protected Cors $corsFilter; + + public function __construct() + { + $this->corsFilter = new Cors(); + } + // public function before(RequestInterface $request, $arguments = null) // { // $jwt = $request->getHeader('Authorization'); @@ -84,20 +92,20 @@ class AuthJWT implements FilterInterface $authHeader = $request->getHeaderLine('Authorization'); if (!$authHeader) { - return $this->reject(403, 'Access Forbidden'); + return $this->reject($request, 403, 'Access Forbidden'); } $result = JWTToken::validateJWT($authHeader); if ($result['status'] !== true) { - return $this->reject(401, $result['message']); + return $this->reject($request, 401, $result['message']); } $decoded = $result['decoded']; $id = $decoded['id'] ?? null; if (!$id) { - return $this->reject(401, 'Invalid token payload'); + return $this->reject($request, 401, 'Invalid token payload'); } if (isset($decoded['emp_code'])) { @@ -111,7 +119,7 @@ class AuthJWT implements FilterInterface if (!$user || $user['token_time_out'] <= time()) { $model->update($id, ['token_time_out' => null]); - return $this->reject(401, 'Token expired'); + return $this->reject($request, 401, 'Token expired'); } // Refresh sliding expiration @@ -132,12 +140,20 @@ class AuthJWT implements FilterInterface return true; } - private function reject(int $code, string $message) + private function reject(RequestInterface $request, int $code, string $message): ResponseInterface { - return service('response') - ->setStatusCode($code) - ->setJSON(['status' => $code, 'message' => $message]) - ->send(); + $response = service('response'); + $response->setStatusCode($code); + $response->setContentType('application/json'); + $response->setBody(json_encode([ + 'status' => $code, + 'message' => $message, + ])); + + // Early before-filter returns skip global Cors after(), so attach CORS here. + $this->corsFilter->after($request, $response); + + return $response; } From 8718004f06324dad6859f664cf28deb6db4ac744 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Fri, 26 Jun 2026 08:29:38 +0530 Subject: [PATCH 3/9] FEAT_E_CARD_MAIL_SEND_OPTION_FOR_HR --- app/Config/Routes.php | 1 + app/Controllers/EmployeeRestController.php | 26 +++ send-mail-individual-employee-ecard-api.md | 180 +++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 send-mail-individual-employee-ecard-api.md diff --git a/app/Config/Routes.php b/app/Config/Routes.php index e9d2aea9..c16dd299 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -737,6 +737,7 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel $routes->get("getFEContent", "EmployeeRestController::getFEContent"); $routes->get("getAdvertisementImage", "EmployeeRestController::getAdvertisementImage"); $routes->get("getEcardURL", "EmployeeRestController::getEcardURL"); + $routes->post("sendMailForIndividualEmployeeEcard", "EmployeeRestController::sendMailForIndividualEmployeeEcard"); diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index b88825dd..bb1677bb 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -5904,4 +5904,30 @@ class EmployeeRestController extends AdminController ], 200); } + public function sendMailForIndividualEmployeeEcard() + { + $received_data = $this->request->getJSON(true) ?? []; + + $id = $received_data['emp_policy_id'] ?? null; + $client_policy_id = $received_data['client_policy_id'] ?? null; + + if (empty($id)) { + return $this->respond(['status' => false, 'code' => 404, 'message' => 'emp_policy_id is required'], 200); + } + + if (empty($client_policy_id)) { + return $this->respond(['status' => false, 'code' => 404, 'message' => 'client_policy_id is required'], 200); + } + + $ids[] = $id; + $empDataServiceController = new EmpDataServiceController(); + $result = $empDataServiceController->sendMailForDownloadingECard(['ids' => $ids, 'client_policy_id' => $client_policy_id], 1); + + if (isset($result['status']) && $result['status'] == true) { + return $this->respond(['status' => true, 'code' => 200, 'message' => 'Mail sent successfully', 'result' => $result], 200); + } + + return $this->respond(['status' => false, 'code' => 404, 'message' => 'Failed to sent mail.', 'result' => $result], 200); + } + } diff --git a/send-mail-individual-employee-ecard-api.md b/send-mail-individual-employee-ecard-api.md new file mode 100644 index 00000000..1c961abe --- /dev/null +++ b/send-mail-individual-employee-ecard-api.md @@ -0,0 +1,180 @@ +# Send Individual Employee E-Card Mail API + +Sends the E-Card download mail to a single employee (Self relationship only). + +**Controller:** `EmployeeRestController::sendMailForIndividualEmployeeEcard` +**Method:** `POST` + +--- + +## Endpoint + +``` +POST /employeeRest/sendMailForIndividualEmployeeEcard +``` + +--- + +## Authentication + +**JWT token is required.** This endpoint is available only in the authenticated route group. + +| Filter | Description | +|--------|-------------| +| `GlobalPostFileUploadGuard` | POST upload guard | +| `ratelimit` | Rate limiting | +| `appSignature` | App signature validation | +| `authJWT` | JWT token validation | + +### Required Headers + +| Header | Required | Description | +|--------|----------|-------------| +| `Content-Type` | Yes | `application/json` | +| `App-Signature` | Yes | Must match server `APP_SIGNATURE` from `.env` | +| `Authorization` | Yes | JWT token in format `Bearer ` | + +### Example Headers + +``` +Content-Type: application/json +App-Signature: +Authorization: Bearer +``` + +--- + +## Request Body (JSON) + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `emp_policy_id` | integer | Yes | Employee policy ID (`employee_polices.id`) | +| `client_policy_id` | integer | Yes | Client policy ID | + +### Example Request + +```json +{ + "emp_policy_id": 12345, + "client_policy_id": 678 +} +``` + +### cURL Example + +```bash +curl -X POST "https:///employeeRest/sendMailForIndividualEmployeeEcard" \ + -H "Content-Type: application/json" \ + -H "App-Signature: " \ + -H "Authorization: Bearer " \ + -d '{ + "emp_policy_id": 12345, + "client_policy_id": 678 + }' +``` + +--- + +## Success Response + +**HTTP Status:** `200` + +```json +{ + "status": true, + "code": 200, + "message": "Mail sent successfully", + "result": {} +} +``` + +--- + +## Error Responses + +### Missing `emp_policy_id` + +**HTTP Status:** `200` + +```json +{ + "status": false, + "code": 404, + "message": "emp_policy_id is required" +} +``` + +### Missing `client_policy_id` + +**HTTP Status:** `200` + +```json +{ + "status": false, + "code": 404, + "message": "client_policy_id is required" +} +``` + +### Mail Send Failed + +**HTTP Status:** `200` + +```json +{ + "status": false, + "code": 404, + "message": "Failed to sent mail.", + "result": {} +} +``` + +### Missing / Invalid Token + +**HTTP Status:** `401` or `403` + +```json +{ + "status": 401, + "message": "Token is Invalid" +} +``` + +### Invalid App Signature + +**HTTP Status:** `403` + +```json +{ + "status": false, + "message": "Forbidden: Invalid App Signature" +} +``` + +--- + +## Notes + +- `emp_policy_id` is the **employee policy record ID**, not the employee master ID. +- Mail is sent only when E-Card notification is enabled for the client. +- Employee must be active with a valid corporate email and TPA ID. +- This API uses the same logic as `EmployeeController::send_mail_for_individual_employee_ecard`. + +--- + +## Route Registration + +Defined in `app/Config/Routes.php`: + +```php +$routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratelimit', 'appSignature', 'authJWT']], function ($routes) { + $routes->post("sendMailForIndividualEmployeeEcard", "EmployeeRestController::sendMailForIndividualEmployeeEcard"); +}); +``` + +--- + +## Source + +Implementation: `app/Controllers/EmployeeRestController.php` → `sendMailForIndividualEmployeeEcard()` +Service: `app/Controllers/EmpDataServiceController.php` → `sendMailForDownloadingECard()` From 0a3a02b836a07d47bd9bc47c23597592bd0d9848 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Fri, 26 Jun 2026 09:00:04 +0530 Subject: [PATCH 4/9] CHANGE_MASK_THE_REAL_NAME_TO_THE_SAMPLE_EXCEL --- public/sample_excel/sample_addition.xls | Bin 9728 -> 8704 bytes public/sample_excel/sample_correction.xls | Bin 7168 -> 7168 bytes public/sample_excel/sample_deletion.xls | Bin 6656 -> 6656 bytes public/sample_excel/sample_inception.xls | Bin 9728 -> 8704 bytes .../sample_excel/sample_multievent_file.xlsx | Bin 7890 -> 8205 bytes public/sample_excel/sample_si_enhancement.xls | Bin 6656 -> 6656 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/sample_excel/sample_addition.xls b/public/sample_excel/sample_addition.xls index ae5b59e278f16ea9667144099846df482f8edd60..9adc6a03929287fd36f3e9e24e969d3a6c267a1c 100755 GIT binary patch delta 1256 zcmZvc-%FEW6vw~M`(t~z`R?6x+iRO`?<_U5A|enZ!$7>LAc*ir&|zbaoC{M@C^wA6 zi-htCx{ZP&yHbfHqM$pW!n%y=59p?wx+to3p8fFMbkE^^dCqx0=e*}R=WO{i`TpBS zbuY@x2Q;kqerNi>LZMK3jP+B}Yj$TuTT`Mg>4J+}dfvIn4wH|?AL~Co8Zc8{e8n5h zHQIV#Dw9Z$9~sSN9Kb+gYAiX)XhX``tWaok{&IHuN|IvqC#F+lQ^`r0Y}pCD!dvMH zYdmq4^pZWFO{E`M0GX6J`pb!_fBHcuU!=sD2F*55V0Vd}Z(`J}|}26|Z&{}||G&HPVbu&arP zImu16kP*j&S%$wfGcq;11AFMKq^YEZ`CQQEl_G!4Clv`X95%1eXjq6WhiqIe;seVi zT&=;yBz2&L&xZynwNIf=wwEiW*bkYVEXo&(`>$ris3fEnJz`vI?#{i!ewr*GMq<$w zERq-wU?1Ab(vEf-6~T$Z?c`dE)IoaNoG5066H#Lnt;C6#?dYO-Omfg$=0o9O#`w!{ zctm7HLj;XPRa2UYRQbn7W|}HSe-{O@-C)avF`8!`Tbtizf4z9`zcr5)oPOdF!zX4| z^kt@2_*SGNew^$XB#_29rjSKXErS`NOy1RUZ7td(ySvdxJRaTr!29EtmRh}kGNOKA YJgx+IKK_I0e7g09wAi%%yVW882ds^{`Tzg` delta 1840 zcmZ`)O>7%g5dPl!C-!dabsQ&-)5hK;xK0Qp6(9;#Dy1~-fr6+&A`%?R6Si#Q_(yBI z$VgzTZ~=i-^2DX5(n|%Lh*hZq(L;Oc0mK!=p&a3YDwPAE66U?N*EWW=*1I$F&CEAH z&+cq@p1X836hZHzm++S-eq)JAQiSOK`{3Z9e@{udds6$F?R%3!ZABMWs59}Zwn_R8 zaV_+F*Z^MSW$T>HOT1nX*TTtm46u2*=4iI^WnSD}f4y1`JIJaF_5){lqmZ+sz%DP< z3+19k)}A9HDo;uQ~J zsue2gLatKcja9SvYiNAnuf(ZI%ZG~hAa#IWvsFERCW3>f?m z9nFhM86`+l9n;B!zzElUC3=hbE`_xFOf1GuusLx)w#pLX{n!qh7eB`inTH7ZfX3#b zo@a1a7&AL-3#5BT(`hOmEaI_I&KfB77#K-i$EPkQ)hs1Pzhowa+K42739!JC{@nh|85QXeV3P;p;lFRxe?;s7$ zqR0;8pki>@<1h#k0}FJPdq~N6#zp!m+Qs*hV49zzOkf30mH?Kqe_KNuOLY2z(zi_y zofIo>D1)R`k~~_NY?CAbcbh+5{p+MdDnvvdc2k8(lDj1n$PuQLErmfDx+-I>=OM?j zh?hTgO4QLFu0vOyPq^Xwb*vzIN25A!QVJabEDRF=qItrqkh>OicB>fcG%cLt}7Y$T#{QuC8-(Vk5tAo_g z5Wc_jYwM4%zMJ~+B0k62&kQ+t`O72!hd4#ZAxocDKg+_=u`Ht~A+97|8@iv~pFi%= tpTHLRbu!5zM4qO)+HnWC9|SKFBF{+aZ9{sEXOJc|GT diff --git a/public/sample_excel/sample_correction.xls b/public/sample_excel/sample_correction.xls index 744769c18b1fcf3e1cc4c45823d00911eca0e97d..17af876c5d28e8a0031566548355d30b6d61ec5c 100755 GIT binary patch delta 744 zcmZXRyK5A25XZm2$3C)~+f8UWkB#z1@af9vJoOWzs+6NBeSq~-+6szX1k4U&T^R zk<5}r2v?49SHQ)fk4HM?98`Ze?*iGR?F&Y*jqN|Fulzga*||Hcub;BG<361kpmQo|0$BceNHi} z)(~XxoT3IO=F}ky^gWsg^J$goLsmSccpkbCmuLEP&GZt!^mfz(be&ao;xnqk)z9Kh z-{x$yoei6U;v7BKGwDL{vhe1yz=7_Sqg(Vesk>Ky9)2FP_61GoV!E3&lyK7DXC3tq Dq1bLH delta 820 zcmZWnOKTHR7(MsiNiv-@nPid)mBeWtP;^mgePD1^rA4qtXau3yh0_>mku;zcsrZQC zACOyhtthx~q0Ue)>vylyOhfN1X3n|iyXSo0onF0Hf4OP$j|e`Q z2bdhQCM372W1caLr$_)NXU@ztkDvAo2hWfKrl%Li8;!=o_&lxPruCX1V%P_aAY2>e zugGJF)S531@)n&s$7*si<%&60m20U7?7pG|$PSU})$Ho^uB{4Esv`WRwVA7Ki@*5&ie(dL%cfY~s4hE4@c z404op7f1c<;%Ek%latwpcZ&20{GmP=MJv&Gw~;}J8pmy}_F~wJC+~QujMHASuS^h9 zt-pbzgL%5n!-sQf9O`T}hEa9NAri3vJ6+P71`0@rR(!yUW2Y6T|0c3h{d&4?rXtRt8rF@C(S$$M_u?ERj7^=<7llNBQ$Y`As7CW2oh3Golg CYI*_y diff --git a/public/sample_excel/sample_deletion.xls b/public/sample_excel/sample_deletion.xls index 7bada1b5140593b51ff0f29dd17e8edc9c849496..2a331b4dbe71ab2afd34fdab0c38f6866620a672 100755 GIT binary patch delta 475 zcmZutO)mpc6g_V`Go4Je(~ePM)h@cI`i$E6NV>4IVkNO69Ulo5Op_u^ExIpvZ6kgF zYDrS!2iS=PcK!j0jk;4}G;v?vz4x5=&VBd3a<-g3J5`gPjYcB_z>D7Q=X~G?4|(x? zdQ4nsSw%QB5VCNgq5vnaj zI)po^8Y~9(&<4S4P4lq?2`8qH0|?+a=3^N$9>Vcoy#fUjf6i5?XgB=(K|oYP-xyNF z)6u(;o*5U-62gEcMfpu2h*mD}4<|Q8sTM8I%9awe5q%XwnvKn|zl@~~o#}l^=K)k0 zP)Rd)B09Iv9r0gqCR5c+)avG_(1(#@9+j78lC6YRzpXrWD^G%wvQ^7jbwT%5?kHXo lW!jFr!ljS+uIQm+rz?8tjeQ>#yIf_EOE`36P0^iv^#z?&TYLZj delta 523 zcmZusyG{a85IuLWyEg@tb=*r*8>#zGPcEsY7Ns3giJJQ5#KptCg04^UuY zr-4=zOzdqnb{77Dg^lYh64Aum%$;-2%$a+q94p6e>gwPKfKSJ9#yZ)>lZ>Z2Jlech z=R`w`DdL=FjEeUXel8hwXY9mfU@cQfX~1@RKV8V!S;d8^!XD>D@gSAcfn|Fy8`-q; zTQ>LQ()rA8HofgYhtXW=4U5u}YVttf6g?1pY^}>BNT>(9+yf74fi9OJ6X4n>QOtdh8<59>sXQn9}z#_65!2`UbhlxcPs Ls1i!hgM9l1@v&sC diff --git a/public/sample_excel/sample_inception.xls b/public/sample_excel/sample_inception.xls index 695c681a5b536f72eb09bd28327c8f02516bc4ec..85619a24f897f64e3876f70b77ee85b1243afa49 100755 GIT binary patch delta 1202 zcmZvc&ubGw6vw}_Ka$O+o82@&woPiXiMF){ZSYbR3l&OF3W}6o3N@6N7MezCixsp9 zDk7e0$BPFqA_zSQwg(G}ig!W02!j0+^r9Eh#m-)W=yf<&>y`*x!GQOVG z0;mrI=z2B!lWBX^YPIp`+vkM$?9mlvOG@~JOFnLC74JQEoO~?)Li_FW0ISa6n}I}` zV${@Fp_rRrI5Ar)5-^clp35&WT9CL^jR$9~=yfwdQ zE#z*HuDR586)G%jdMR5M*z=N%TtG)2zgJCxP75LixRj6eYHP^upKc48W?xLhx57eMRHr)LH0&V*`bUbZI z$bf-i%}pSPAYQikchd*5b-oiEDW9bar@?e6f+2PXWO5;KQj5^>q=hWkHe!Z#J_}xSP%ajp2~!XSQAvb z3lS+iyUFm6x<;g852Ba$YDy**<7*L@mmL12AvlEH94;?WYgmYHL|t5V@QIBYF1O)! zCk>*H--%99YTu$m>=2jCNzp=xPn$z5!S9>)&aN5P8!0J{Pm(J=BjtBELcIkfXEH-Cf-F`0ZP{-))a`tsLI6G>RqSZJ%<^VDjj|m=ni+nmZ!@%Fh zVp$b-2^pP2E2W^AC^Ua?puS@CcTpt*JM1B0j6aX3{HthA{yBbnpEh}>@4;5$jHsT% onCf#L|D-+Qf6e5rH2?qr delta 1777 zcmZ`(OKcle6g@L`{F#Y8j%z1zTias?Hwl5H3)G^DiUy)C5EX@pMxu-I6c4dU{E|lmgg`o-t+bF6v)tJHcF3pE0Bz+X`RRpQ3+3 zeir+qX96#Yntiz?)4%CrB<~W?Js1Gi$q*%C`6BGnyu=luwU>23@$et-L^v| zZ#FhX1XvQ?wMxfnjR2yxDe3`28g*8C-KnorS-WDlM1!`M*RIy>4e`dY1MY`WH8;0H zRS07XMCr^P*$e?1jy6iYjvFn&JC+&)B*K!Sk50&W)Vrd%95+uL_Zpjn9M3CaX49QAPkLq3jc$j5;V z`8eMpU-+=^T0b#+Is}aVhmYf8DMtlzs6#$=LNwxbuO#njBik{3Pm|fyd2Lc&P0eZ< z`Ce*8J1&1tov=<2`8UTFV9%#W6 z>iDcylpl@19GJzF{FT-t(wbPAD>1{{x`7CD6!5*KyZIuV`7{o)M_i^;^5coKy|0mk zsy&ZW?mH(`ElOjQ1G2fw~3Iqf4uWP9gE4w@b!OBgfovP3Y2 zoo{rMP~=IAs8K3T;ju|As)mg_~rcj+NQk1LSwM%%6EACRN z0h8%VloWGkVDO}P@|1Lgd(Oh+eRj2v@|b_s{S4HauG)Ee)ey}iYE3ph?+?**#hxTm z)o$|8dQG45#VT(^7ShK`CWXwoeNsY9Ux><~Hm`*+#BjjCgpdNS?!kmZL%<-2H%1}4NdpE&fjfRq{vLjCo+lBlZ@Bst6~EkG;r?! z(BUt&51B72A50cZL6n0s@6A3_@gd(Qay!%PKee6X=K{tVPyODD@5`mE9l6}Qar({= aKWefNOv$b6o@U8z?p=LheD}{>DDW>)W-^Na diff --git a/public/sample_excel/sample_multievent_file.xlsx b/public/sample_excel/sample_multievent_file.xlsx index 552e48881c80db622387507bbe04ee2e30987427..1c5efbff1eb7461290bec624301e6abdb9d7ca21 100755 GIT binary patch delta 7080 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 diff --git a/public/sample_excel/sample_si_enhancement.xls b/public/sample_excel/sample_si_enhancement.xls index 3149edc9498a354d4f5f4f7eec1a7c605daefdbb..6930b191b844c747e40dd0004367cab8e37d2fc3 100755 GIT binary patch delta 475 zcmZusze~eF7=3q1Y>Y{p{v5Q8)uKqD4pNGWicrKsPz&N99SpQv>!6DTtHIH+_Ya7) zgF`{gD>B&hOH&aMhuFs9;6FIJEx$xGSFi#gTZj=cL>j_;s?Q;8fI8#8 zgRzm=MBi{w_TU6|puc~nDUnFbG)+?&+u9OmjpD@?Yh$PfN|1}I&=Y(pB9Fx@{&J0K zkF#3QYWGXytVV3w)7-0wtX)@Dpi-_Xb{u1+@r~uVRT-FET%Xf`q13`kD4ANDPZ@U( z7mF7o4I3phWQv_u15$1_HnN1ymp-BCgt=FN^7{3@Ud~RiFSr z49saU{tuIKOzgY#(Q>i?m++8XLIrj&8c>O&Yfeo~Y8D8BcA@)r${hWJlsR%03Uf|Q zPUnwP%)0An75WCtZLUHuZyl%<2kt$aIilG|0%IcL={9sv7t=b?OB^?+1LE2nQqlgp Vt!zCqQ7!w Date: Fri, 26 Jun 2026 09:26:32 +0530 Subject: [PATCH 5/9] FEAT_HANDLE_ALL_EVENT --- app/Controllers/EmployeeRestController.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index bb1677bb..ee3caadd 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -5169,14 +5169,12 @@ class EmployeeRestController extends AdminController try { //for inception upload - if($type == 'EB'){ + if ($type == 'EB') { - $data['actions'] = ['addition' => 'Addition (Employee + Dependents)', 'missed_inception' => 'Missed Inception (Employee + Dependents)', 'dependent_addition' => 'Dependent Addition (Only Dependents)', 'deletion' => 'Deletion', 'correction' => 'Correction', 'si_enhancement' => 'SI Enhancement']; - } - else - { - $data['actions'] = ['addition' => 'Adding New Assets', 'deletion' => 'Removing Assets', 'correction' => 'Correction of assets details']; - } + $data['actions'] = ['all' => 'All', 'addition' => 'Addition (Employee + Dependents)', 'missed_inception' => 'Missed Inception (Employee + Dependents)', 'dependent_addition' => 'Dependent Addition (Only Dependents)', 'deletion' => 'Deletion', 'correction' => 'Correction', 'si_enhancement' => 'SI Enhancement']; + } else { + $data['actions'] = ['addition' => 'Adding New Assets', 'deletion' => 'Removing Assets', 'correction' => 'Correction of assets details']; + } return $this->respond([ 'status' => true, From 4b8f7cb45978ed112b18eaa793b32be990365359 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Fri, 26 Jun 2026 16:51:07 +0530 Subject: [PATCH 6/9] FEAT_DELETION_CLAIM_STATUS_FETCH_UTOMATICALLY --- app/Controllers/EmployeeServiceController.php | 208 ++++++++++-------- .../PolicyTransactionController.php | 61 +++++ app/Helpers/excel_util_helper.php | 4 + ...e_deletion_excel_optional_claim_status.php | 203 +++++++++++++++++ ...oke_employee_claim_status_for_deletion.php | 146 ++++++++++++ tests/smoke_inception_policy_no_sync.php | 152 +++++++++++++ .../unit/PolicyTransactionControllerTest.php | 76 +++++++ 7 files changed, 757 insertions(+), 93 deletions(-) create mode 100644 tests/smoke_deletion_excel_optional_claim_status.php create mode 100644 tests/smoke_employee_claim_status_for_deletion.php create mode 100644 tests/smoke_inception_policy_no_sync.php diff --git a/app/Controllers/EmployeeServiceController.php b/app/Controllers/EmployeeServiceController.php index ecf2d49a..6db0be9d 100755 --- a/app/Controllers/EmployeeServiceController.php +++ b/app/Controllers/EmployeeServiceController.php @@ -19,6 +19,7 @@ use App\Models\MessageModel; use App\Models\ClientBranchModel; use App\Models\NotificationModel; use App\Models\InsurerModel; +use App\Models\TicketMasterModel; use App\Helpers\sendMailNotification; @@ -46,6 +47,7 @@ class EmployeeServiceController extends AdminController protected $clientBranchModel; protected $notificationModel; protected $insurerModel; + protected $ticketMasterModel; protected $general_relationships = [ 'self' => [ @@ -355,10 +357,13 @@ class EmployeeServiceController extends AdminController 'col_idx' => 6, 'col_cell_name' => 'G', 'col_name' => 'Claim status', - 'is_mandatory' => ['D'], + 'is_mandatory' => false, + // 'is_mandatory' => ['D'], + 'is_column_optional' => true, 'data_type' => 'str', 'format' => null, - 'allowed_values' => [0,1] + 'allowed_values' => null + // 'allowed_values' => [0,1] ] ]; @@ -779,6 +784,7 @@ class EmployeeServiceController extends AdminController $this->clientBranchModel = new ClientBranchModel(); $this->notificationModel = new NotificationModel(); $this->insurerModel = new InsurerModel(); + $this->ticketMasterModel = new TicketMasterModel(); } public function excelFileFormatValidation($params) @@ -842,8 +848,22 @@ class EmployeeServiceController extends AdminController $excel_columns = ($excel_data[0]); // var_dump($total_defined_columns);die(); $excel_columns_count = count($excel_columns); - if($total_defined_columns != $excel_columns_count) - { + $optional_column_count = 0; + foreach ($columns_to_check as $column_config) { + if (!empty($column_config['is_column_optional'])) { + $optional_column_count++; + } + } + $min_required_columns = $total_defined_columns - $optional_column_count; + + if ($optional_column_count > 0) { + if ($excel_columns_count < $min_required_columns || $excel_columns_count > $total_defined_columns) { + $message = "File columns count mismatch. Expected between $min_required_columns and $total_defined_columns and received - $excel_columns_count"; + $this->myLogger->logme('error',($message . ' for file id ' . $file_id)); + $this->fileModel->where('id', $file_id)->set(['status' => 'failed','reason' => json_encode(['error_summary' => array_count_values([5]),'error_data' => $message])])->update(); + return array('error_summary' => [5], 'error_data' => $message); + } + } elseif ($total_defined_columns != $excel_columns_count) { //columns count mismatch $message = "File columns count mismatch. Expected - $total_defined_columns and received - $excel_columns_count"; // echo $message; @@ -1532,38 +1552,52 @@ class EmployeeServiceController extends AdminController return (count($employee_data_group_by_family)); } + /** + * Returns claim_status for deletion: 1 if an active claim exists in ticket_master for the employee and policy, else 0. + */ + protected function getEmployeeClaimStatusForDeletion(int $emp_id, int $client_policy_id): int + { + $claim = $this->ticketMasterModel + ->where('emp_id', $emp_id) + ->where('client_policy_id', $client_policy_id) + ->where('is_active', 1) + ->first(); + + return $claim ? 1 : 0; + } + //deletion of emp public function employeeDisembark($params) { - helper('excel_util_helper'); + helper('excel_util_helper'); //get file name $file_id = $params['file_id']; $file = $this->fileModel->find((int)$file_id); // dd($file); - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $sheet = $spreadsheet->getActiveSheet(); - + $highestRowAndColumn = $sheet->getHighestRowAndColumn(); $allowedHighestColumn = end($this->deletion_excel_columns); $excel_data = $sheet->rangeToArray('A1:' . $allowedHighestColumn['col_cell_name'] . $highestRowAndColumn['row']); // $excel_data = $sheet->rangeToArray('A1:' . $highestRowAndColumn['column'] . $highestRowAndColumn['row']); - + // dd($excel_data); unset($excel_data[0]); // kint::dump($excel_data); $endorsement_data = []; - $policy_terms = $this->clientPolicyModel->getPolicyDetails($file['client_id'],$file['policy_id']); + $policy_terms = $this->clientPolicyModel->getPolicyDetails($file['client_id'], $file['policy_id']); $insurer = new InsurerModel(); $insurer = ($insurer->find((int)$policy_terms[0]->insurer_id)); // kint::dump($insurer); //make closure funciton which is going to use only by this method - $endorsement = function($data,$file,$row) use ($insurer){ + $endorsement = function ($data, $file, $row) use ($insurer) { $group_key = rand(100000, 999999); - $row[4] = change_date_format($row[4],'d-M-Y','Y-m-d');// date of exit from excel + $row[4] = change_date_format($row[4], 'd-M-Y', 'Y-m-d'); // date of exit from excel // Kint::dump($row[4]); // check if insurer configured with add one day for deletion // if($insurer['deletion_add_day'] == true) @@ -1572,115 +1606,103 @@ class EmployeeServiceController extends AdminController // } // dd($row[4]); //for emp table - // $this->empEndorsementModel->save(['pk' => (int)$data['emp_id'],'emp_code' => $data['emp_code'],'table_name' => 'employees','actions' => 'd','name' => $data['name'],'field_name' => 'emp_status','old_value' => $data['emp_status'],'new_value' => 'deactivate','created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); + // $this->empEndorsementModel->save(['pk' => (int)$data['emp_id'],'emp_code' => $data['emp_code'],'table_name' => 'employees','actions' => 'd','name' => $data['name'],'field_name' => 'emp_status','old_value' => $data['emp_status'],'new_value' => 'deactivate','created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); // dd( $this->empEndorsementModel->getLastQuery()); // $this->empEndorsementModel->save(['pk' => (int)$data['emp_id'],'emp_code' => $data['emp_code'],'table_name' => 'employees','actions' => 'd','name' => $data['name'],'field_name' => 'change_event','old_value' => $data['change_event'],'new_value' => 'deletion','created_by' => $file['created_by'],'remarks' => 'general deletion']); // for employee policy table - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'date_of_exit','old_value' => $data['date_of_exit'],'new_value' => $row[4],'created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'reason_for_exit','old_value' => $data['reason_for_exit'],'new_value' => $row[5],'created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'status','old_value' => $data['status'],'new_value' => 'inactive','created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'claim_status','old_value' => $data['claim_status'],'new_value' => $row[6],'created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'date_of_exit', 'old_value' => $data['date_of_exit'], 'new_value' => $row[4], 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'reason_for_exit', 'old_value' => $data['reason_for_exit'], 'new_value' => $row[5], 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'status', 'old_value' => $data['status'], 'new_value' => 'inactive', 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); + $claim_status = $this->getEmployeeClaimStatusForDeletion((int) $data['emp_id'], (int) $file['policy_id']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'claim_status', 'old_value' => $data['claim_status'], 'new_value' => $claim_status, 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); }; //iterate each row - foreach ($excel_data as $col_key => $row) - { - if(check_row_is_empty_or_null($row)) - { - break; + foreach ($excel_data as $col_key => $row) { + if (check_row_is_empty_or_null($row)) { + break; } // echo '
START- ' . $row[2]; $employee = $this->employeeModel - ->select('employees.*, employee_polices.id as emp_policy_pk') - ->join('employee_polices', 'employees.id = employee_polices.employee_id') - ->where('employees.emp_code', $row[1]) - ->where('employees.name',$row[2]) - ->where('employees.client_id',$file['client_id']) - ->where('employees.client_branch_id',$file['client_branch_id']) - ->where('employee_polices.client_policy_id',$file['policy_id']) - ->where('employees.emp_status','active') - ->where('employees.is_active', 1) - ->where('employee_polices.status','active') - ->where('employee_polices.is_active', 1) - ->first(); + ->select('employees.*, employee_polices.id as emp_policy_pk') + ->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->where('employees.emp_code', $row[1]) + ->where('employees.name', $row[2]) + ->where('employees.client_id', $file['client_id']) + ->where('employees.client_branch_id', $file['client_branch_id']) + ->where('employee_polices.client_policy_id', $file['policy_id']) + ->where('employees.emp_status', 'active') + ->where('employees.is_active', 1) + ->where('employee_polices.status', 'active') + ->where('employee_polices.is_active', 1) + ->first(); // $employee = $this->employeeModel->where('emp_code', $row[1])->where('name',$row[2])->where('client_id',$file['client_id'])->first(); // dd($employee); // kint::dump($employee); - if(is_array($employee) && count($employee)) - { + if (is_array($employee) && count($employee)) { // dd($employee); - $existing_endorsements = $this->empEndorsementModel->where('actions','d') - ->where('table_name','employee_polices') - ->where('endorsement_id is null') - ->where('emp_code',$employee['emp_code']) - ->where('name',$employee['name']) - ->where('pk',$employee['emp_policy_pk']) - ->where('field_name','status') - ->where('status !=','truncated') - ->where('is_active', 1) - ->findAll(); + $existing_endorsements = $this->empEndorsementModel->where('actions', 'd') + ->where('table_name', 'employee_polices') + ->where('endorsement_id is null') + ->where('emp_code', $employee['emp_code']) + ->where('name', $employee['name']) + ->where('pk', $employee['emp_policy_pk']) + ->where('field_name', 'status') + ->where('status !=', 'truncated') + ->where('is_active', 1) + ->findAll(); - // dd($existing_endorsements); - if(!count($existing_endorsements)) - { - // dd($employee); - if(strtolower($employee['relationship']) != 'self') - { - - if(!in_array($employee['id'],$endorsement_data))//make entry in endorsement firsttime only with checking that PK of emp exisintg in variable $endorsement_data - { - $employee_policy = $this->employeePolicyModel - ->where('employee_id',$employee['id']) - ->where('client_policy_id',$file['policy_id']) - ->where('status','active') - ->where('is_active', 1) - ->first(); + // dd($existing_endorsements); + if (!count($existing_endorsements)) { + // dd($employee); + if (strtolower($employee['relationship']) != 'self') { - $data = ['emp_id' => $employee['id'],'name' => $employee['name'],'emp_status' => $employee['emp_status'],'emp_policy_id' => $employee_policy['id'],'emp_code' => $employee['emp_code'],'change_event' => $employee['change_event'],'date_of_exit' => $employee_policy['date_of_exit'],'reason_for_exit' => $employee_policy['reason_for_exit'],'status' => $employee_policy['status'],'claim_status' => $employee_policy['claim_status']]; - $endorsement($data,$file,$row); - $endorsement_data[] = $employee['id']; - } + if (!in_array($employee['id'], $endorsement_data)) //make entry in endorsement firsttime only with checking that PK of emp exisintg in variable $endorsement_data + { + $employee_policy = $this->employeePolicyModel + ->where('employee_id', $employee['id']) + ->where('client_policy_id', $file['policy_id']) + ->where('status', 'active') + ->where('is_active', 1) + ->first(); + + $data = ['emp_id' => $employee['id'], 'name' => $employee['name'], 'emp_status' => $employee['emp_status'], 'emp_policy_id' => $employee_policy['id'], 'emp_code' => $employee['emp_code'], 'change_event' => $employee['change_event'], 'date_of_exit' => $employee_policy['date_of_exit'], 'reason_for_exit' => $employee_policy['reason_for_exit'], 'status' => $employee_policy['status'], 'claim_status' => $employee_policy['claim_status']]; + $endorsement($data, $file, $row); + $endorsement_data[] = $employee['id']; + } + } else { + + $family = $this->employeeModel->getEmpFamilybyEmpCode(emp_code: $row[1], client_id: $file['client_id'], client_policy_id: $file['policy_id'], emp_status: ['active'], policy_status: ['active']); + // Kint::dump($family); + foreach ($family as $key => $emp) { + + if (!in_array($emp['emp_id'], $endorsement_data)) { + $endorsement($emp, $file, $row); + + $endorsement_data[] = $emp['emp_id']; } - else - { - - $family = $this->employeeModel->getEmpFamilybyEmpCode(emp_code: $row[1],client_id: $file['client_id'],client_policy_id: $file ['policy_id'],emp_status: ['active'],policy_status: ['active']); - // Kint::dump($family); - foreach ($family as $key => $emp) { - - if(!in_array($emp['emp_id'],$endorsement_data)) - { - $endorsement($emp,$file,$row); - - $endorsement_data[] = $emp['emp_id']; - - } - } - - } - }else{ - $this->myLogger->logme("error",'Existing endorsement pending for this employee : emp_code : {emp_code} - emp_name : {name}',['emp_code' => $row[1],'name' => $row[2]]); + } } - } - else - { - $this->myLogger->logme("error",'{emp_code} - {name} not found',['emp_code' => $row[1],'name' => $row[2]]); + } else { + $this->myLogger->logme("error", 'Existing endorsement pending for this employee : emp_code : {emp_code} - emp_name : {name}', ['emp_code' => $row[1], 'name' => $row[2]]); + } + } else { + $this->myLogger->logme("error", '{emp_code} - {name} not found', ['emp_code' => $row[1], 'name' => $row[2]]); } // print_r($endorsement_data); } - - $this->fileModel->where('id', $file_id)->set(['status' => 'success','reason' => ''])->update(); - $this->myLogger->logme("error",'{file_id} uploaded success',['file_id' => $file_id]); + + $this->fileModel->where('id', $file_id)->set(['status' => 'success', 'reason' => ''])->update(); + $this->myLogger->logme("error", '{file_id} uploaded success', ['file_id' => $file_id]); //set success msg to pull notifications - $this->setPullNotification($this->getFileMetaDataByFileId($file_id,'success')); - return $endorsement_data; - + $this->setPullNotification($this->getFileMetaDataByFileId($file_id, 'success')); + return $endorsement_data; } //correction of emp diff --git a/app/Controllers/PolicyTransactionController.php b/app/Controllers/PolicyTransactionController.php index a381b23f..8c2436d7 100644 --- a/app/Controllers/PolicyTransactionController.php +++ b/app/Controllers/PolicyTransactionController.php @@ -1319,6 +1319,14 @@ class PolicyTransactionController extends BaseController $old_pt_co_share_data = $this->PTCOShareDetailsModel->where('is_active', 1)->where("id", $id)->first(); if ($this->policyTransactionModel->update($id, $data)) { + if (!empty($old_pt_data) && isset($data['policy_no'])) { + $this->updateRelatedPolicyTransactionPolicyNo( + (int) $id, + (string) ($old_pt_data['policy_no'] ?? ''), + (string) $data['policy_no'] + ); + } + $this->insertTransactionStatus($id, $data, 1); $emp_data = $this->processInsertIndividualMemberInEmpTable($data); @@ -1376,6 +1384,59 @@ class PolicyTransactionController extends BaseController return $this->respondError("Failed to update policy transaction"); } + /** + * Sync policy_no on related policy_transaction rows when inception policy number changes. + */ + private function updateRelatedPolicyTransactionPolicyNo( + int $inceptionPtId, + string $oldPolicyNo, + string $newPolicyNo + ): array { + $oldPolicyNo = trim($oldPolicyNo); + $newPolicyNo = trim($newPolicyNo); + + if ($oldPolicyNo === '' || $newPolicyNo === '' || $oldPolicyNo === $newPolicyNo) { + return ['status' => false, 'message' => 'No policy number change detected', 'updated_ids' => []]; + } + + $relatedRecords = $this->policyTransactionModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->findAll(); + + if (empty($relatedRecords)) { + return ['status' => true, 'message' => 'No related policy transactions found', 'updated_ids' => []]; + } + + $updatedIds = []; + $updatedBy = get_session_userid(); + + foreach ($relatedRecords as $record) { + $this->policyTransactionModel + ->where('id', $record['id']) + ->set(['policy_no' => $newPolicyNo, 'updated_by' => $updatedBy]) + ->update(); + + if ($this->policyTransactionModel->affectedRows() > 0) { + $updatedIds[] = $record['id']; + } + } + + $this->myLogger->logme('error', 'Updated policy_no on related policy_transaction records: ' . json_encode([ + 'inception_pt_id' => $inceptionPtId, + 'old_policy_no' => $oldPolicyNo, + 'new_policy_no' => $newPolicyNo, + 'updated_ids' => $updatedIds, + ])); + + return [ + 'status' => true, + 'message' => 'Related policy transaction policy numbers updated', + 'updated_ids' => $updatedIds, + ]; + } + private function insertOrUpdateCoShareDetails($data, $pt_id) { // Prepare data for insertion and updating diff --git a/app/Helpers/excel_util_helper.php b/app/Helpers/excel_util_helper.php index 5aa68990..5b474732 100755 --- a/app/Helpers/excel_util_helper.php +++ b/app/Helpers/excel_util_helper.php @@ -41,6 +41,10 @@ if (!function_exists('check_columns_name')) { $definedColIdx = $definedCol['col_idx']; $definedColName = $definedCol['col_name']; + if (!empty($definedCol['is_column_optional']) && !array_key_exists($definedColIdx, $excelColumns)) { + continue; + } + if (strtolower(strip_tags(trim($excelColumns[$definedColIdx]))) !== strtolower($definedColName)) { $mismatchedColumns[] = "Column order conflict. Column order no " . ($definedColIdx + 1) . " expected : " . $definedColName . " and received : " . $excelColumns[$definedColIdx] . "
"; } diff --git a/tests/smoke_deletion_excel_optional_claim_status.php b/tests/smoke_deletion_excel_optional_claim_status.php new file mode 100644 index 00000000..fb0ea890 --- /dev/null +++ b/tests/smoke_deletion_excel_optional_claim_status.php @@ -0,0 +1,203 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); + +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +helper('excel_util_helper'); + +use App\Controllers\EmployeeServiceController; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xls; + +$pass = 0; +$fail = 0; +$results = []; + +function ok(string $label, bool $cond, string $detail = ''): void +{ + global $pass, $fail, $results; + if ($cond) { + $pass++; + $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); + } else { + $fail++; + $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); + } +} + +function getDeletionExcelColumns(EmployeeServiceController $controller): array +{ + $property = new ReflectionProperty($controller, 'deletion_excel_columns'); + $property->setAccessible(true); + + return $property->getValue($controller); +} + +function deletionColumnCountIsValid(array $columnsToCheck, array $excelHeaderRow): bool +{ + $totalDefinedColumns = count($columnsToCheck); + $excelColumnsCount = count($excelHeaderRow); + $optionalColumnCount = 0; + + foreach ($columnsToCheck as $columnConfig) { + if (!empty($columnConfig['is_column_optional'])) { + $optionalColumnCount++; + } + } + + $minRequiredColumns = $totalDefinedColumns - $optionalColumnCount; + + if ($optionalColumnCount > 0) { + return $excelColumnsCount >= $minRequiredColumns && $excelColumnsCount <= $totalDefinedColumns; + } + + return $excelColumnsCount === $totalDefinedColumns; +} + +function validateDeletionRowClaimStatus(array $columnsToCheck, array $row, string $currentColumnAction = 'D'): array +{ + $result = ['error_summary' => [], 'error_data' => []]; + $keys = array_keys($columnsToCheck); + $rowKey = 2; + + foreach ($row as $colKey => $col) { + if (!isset($keys[$colKey])) { + continue; + } + + $isMandatory = $columnsToCheck[$keys[$colKey]]['is_mandatory']; + $allowedValues = $columnsToCheck[$keys[$colKey]]['allowed_values']; + $columnName = $columnsToCheck[$keys[$colKey]]['col_name']; + $columnIndex = $columnsToCheck[$keys[$colKey]]['col_idx']; + + if (is_bool($isMandatory) && $isMandatory === true && ($col === '' || $col === null)) { + $result['error_summary'][] = 1; + $result['error_data'][$rowKey][$keys[$colKey]]['error'][] = 'Value is mandatory'; + } + + if ( + $currentColumnAction !== null + && is_array($isMandatory) + && in_array(strtoupper(trim($currentColumnAction)), $isMandatory, true) + && ($col === '' || $col === null) + ) { + $result['error_summary'][] = 1; + $result['error_data'][$rowKey][$keys[$colKey]]['error'][] = 'Value is mandatory for this action/event'; + } + + if ( + ($isMandatory === true && isset($allowedValues) && is_array($allowedValues)) + || (is_array($isMandatory) && isset($allowedValues) && is_array($allowedValues)) + ) { + if (!in_array(trim((string) $col), $allowedValues, true)) { + $result['error_summary'][] = 3; + $result['error_data'][$rowKey][$keys[$colKey]]['col_name'] = $columnName; + $result['error_data'][$rowKey][$keys[$colKey]]['col_idx'] = $columnIndex; + $result['error_data'][$rowKey][$keys[$colKey]]['error'][] = 'Value not allowed'; + } + } + } + + return $result; +} + +$controller = new EmployeeServiceController(); +$deletionColumns = getDeletionExcelColumns($controller); +$claimStatusColumn = $deletionColumns['claim_status'] ?? []; +$headerWithClaim = ['S.No', 'EMP ID', 'NAME OF EMP/DEP', 'Change event', 'Date of exit', 'Reason for exit', 'Claim status']; +$headerWithoutClaim = ['S.No', 'EMP ID', 'NAME OF EMP/DEP', 'Change event', 'Date of exit', 'Reason for exit']; +$sampleRowWithEmptyClaim = ['1', 'EMP001', 'Test Employee', 'deletion', '19-May-2026', 'Resigned', '']; +$sampleRowWithoutClaimCol = ['1', 'EMP001', 'Test Employee', 'deletion', '19-May-2026', 'Resigned']; + +ok('claim_status is not mandatory', ($claimStatusColumn['is_mandatory'] ?? null) === false); +ok('claim_status column is optional', !empty($claimStatusColumn['is_column_optional'])); +ok( + 'claim_status has no allowed_values constraint', + !array_key_exists('allowed_values', $claimStatusColumn) || $claimStatusColumn['allowed_values'] === null +); + +ok('7-column header passes column count check', deletionColumnCountIsValid($deletionColumns, $headerWithClaim)); +ok('6-column header passes column count check', deletionColumnCountIsValid($deletionColumns, $headerWithoutClaim)); +ok('5-column header fails column count check', !deletionColumnCountIsValid($deletionColumns, array_slice($headerWithoutClaim, 0, 5))); +ok('8-column header fails column count check', !deletionColumnCountIsValid($deletionColumns, array_merge($headerWithClaim, ['Extra']))); + +$mismatchWithoutClaim = check_columns_name($deletionColumns, $headerWithoutClaim); +ok('header without Claim status has no column-name mismatch', count($mismatchWithoutClaim) === 0); + +$mismatchWithClaim = check_columns_name($deletionColumns, $headerWithClaim); +ok('header with Claim status has no column-name mismatch', count($mismatchWithClaim) === 0); + +$wrongHeader = $headerWithoutClaim; +$wrongHeader[5] = 'Wrong reason column'; +$mismatchWrong = check_columns_name($deletionColumns, $wrongHeader); +ok('wrong required header is still detected', count($mismatchWrong) > 0); + +$rowErrorsEmptyClaim = validateDeletionRowClaimStatus($deletionColumns, $sampleRowWithEmptyClaim); +ok( + 'empty Claim status value does not fail row validation', + count($rowErrorsEmptyClaim['error_summary']) === 0, + 'errors=' . count($rowErrorsEmptyClaim['error_summary']) +); + +$rowErrorsMissingClaimCol = validateDeletionRowClaimStatus($deletionColumns, $sampleRowWithoutClaimCol); +ok( + 'missing Claim status column does not fail row validation', + count($rowErrorsMissingClaimCol['error_summary']) === 0, + 'errors=' . count($rowErrorsMissingClaimCol['error_summary']) +); + +$tempDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR; +$tempFileSixCols = $tempDir . 'smoke_deletion_6cols_' . date('Ymd_His') . '.xls'; +$spreadsheet = new Spreadsheet(); +$sheet = $spreadsheet->getActiveSheet(); + +foreach ($headerWithoutClaim as $idx => $header) { + $sheet->setCellValue(chr(65 + $idx) . '1', $header); +} +foreach ($sampleRowWithoutClaimCol as $idx => $value) { + $sheet->setCellValue(chr(65 + $idx) . '2', $value); +} + +$writer = new Xls($spreadsheet); +$writer->save($tempFileSixCols); + +$loadedSpreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($tempFileSixCols); +$loadedSheet = $loadedSpreadsheet->getActiveSheet(); +$loadedHeader = $loadedSheet->rangeToArray('A1:F1')[0]; +$loadedRow = $loadedSheet->rangeToArray('A2:F2')[0]; + +ok('generated 6-column xls header count is 6', count($loadedHeader) === 6); +ok('generated 6-column xls passes column count rule', deletionColumnCountIsValid($deletionColumns, $loadedHeader)); +ok('generated 6-column xls passes header-name check', count(check_columns_name($deletionColumns, $loadedHeader)) === 0); +ok( + 'generated 6-column xls row has no claim_status validation errors', + count(validateDeletionRowClaimStatus($deletionColumns, $loadedRow)['error_summary']) === 0 +); + +@unlink($tempFileSixCols); + +echo PHP_EOL . implode(PHP_EOL, $results) . PHP_EOL; +echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL; + +exit($fail > 0 ? 1 : 0); diff --git a/tests/smoke_employee_claim_status_for_deletion.php b/tests/smoke_employee_claim_status_for_deletion.php new file mode 100644 index 00000000..f634c92b --- /dev/null +++ b/tests/smoke_employee_claim_status_for_deletion.php @@ -0,0 +1,146 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); + +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +use App\Controllers\EmployeeServiceController; + +$pass = 0; +$fail = 0; +$results = []; + +function ok(string $label, bool $cond, string $detail = ''): void +{ + global $pass, $fail, $results; + if ($cond) { + $pass++; + $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); + } else { + $fail++; + $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); + } +} + +function invokeClaimStatus(EmployeeServiceController $controller, int $empId, int $clientPolicyId): int +{ + $method = new ReflectionMethod($controller, 'getEmployeeClaimStatusForDeletion'); + $method->setAccessible(true); + + return (int) $method->invoke($controller, $empId, $clientPolicyId); +} + +$db = db_connect('default'); +$controller = new EmployeeServiceController(); + +$withClaim = $db->query( + 'SELECT emp_id, client_policy_id + FROM ticket_master + WHERE is_active = 1 AND emp_id > 0 AND client_policy_id > 0 + LIMIT 1' +)->getRowArray(); + +$withoutClaim = $db->query( + 'SELECT e.id AS emp_id, ep.client_policy_id + FROM employees e + JOIN employee_polices ep ON ep.employee_id = e.id + WHERE e.is_active = 1 + AND ep.is_active = 1 + AND e.id > 0 + AND ep.client_policy_id > 0 + AND NOT EXISTS ( + SELECT 1 + FROM ticket_master tm + WHERE tm.emp_id = e.id + AND tm.client_policy_id = ep.client_policy_id + AND tm.is_active = 1 + ) + LIMIT 1' +)->getRowArray(); + +$wrongPolicy = $db->query( + 'SELECT id FROM client_policy WHERE id > 0 ORDER BY id DESC LIMIT 1' +)->getRowArray(); + +ok('fixture with claim found', is_array($withClaim) && ! empty($withClaim)); +ok('fixture without claim found', is_array($withoutClaim) && ! empty($withoutClaim)); + +if ($withClaim) { + $empId = (int) $withClaim['emp_id']; + $policyId = (int) $withClaim['client_policy_id']; + $result = invokeClaimStatus($controller, $empId, $policyId); + + ok( + 'returns 1 when active claim exists for emp_id + client_policy_id', + $result === 1, + "emp_id={$empId}, client_policy_id={$policyId}, got={$result}" + ); + + if (isset($argv[1], $argv[2])) { + $manualEmp = (int) $argv[1]; + $manualPolicy = (int) $argv[2]; + $manualResult = invokeClaimStatus($controller, $manualEmp, $manualPolicy); + $expected = $db->table('ticket_master') + ->where('emp_id', $manualEmp) + ->where('client_policy_id', $manualPolicy) + ->where('is_active', 1) + ->countAllResults() > 0 ? 1 : 0; + + ok( + 'manual args match ticket_master lookup', + $manualResult === $expected, + "emp_id={$manualEmp}, client_policy_id={$manualPolicy}, got={$manualResult}, expected={$expected}" + ); + } +} + +if ($withClaim && $wrongPolicy) { + $empId = (int) $withClaim['emp_id']; + $wrongPolicyId = (int) $wrongPolicy['id']; + + if ($wrongPolicyId !== (int) $withClaim['client_policy_id']) { + $result = invokeClaimStatus($controller, $empId, $wrongPolicyId); + ok( + 'returns 0 when emp has claim on a different client_policy_id', + $result === 0, + "emp_id={$empId}, client_policy_id={$wrongPolicyId}, got={$result}" + ); + } +} + +if ($withoutClaim) { + $empId = (int) $withoutClaim['emp_id']; + $policyId = (int) $withoutClaim['client_policy_id']; + $result = invokeClaimStatus($controller, $empId, $policyId); + + ok( + 'returns 0 when no active claim exists for emp_id + client_policy_id', + $result === 0, + "emp_id={$empId}, client_policy_id={$policyId}, got={$result}" + ); +} + +echo PHP_EOL . implode(PHP_EOL, $results) . PHP_EOL; +echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL; + +exit($fail > 0 ? 1 : 0); diff --git a/tests/smoke_inception_policy_no_sync.php b/tests/smoke_inception_policy_no_sync.php new file mode 100644 index 00000000..f08b8ed8 --- /dev/null +++ b/tests/smoke_inception_policy_no_sync.php @@ -0,0 +1,152 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); + +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +use App\Controllers\PolicyTransactionController; +use App\Models\PolicyTransactionModel; + +$pass = 0; +$fail = 0; +$results = []; + +function ok(string $label, bool $cond, string $detail = ''): void +{ + global $pass, $fail, $results; + if ($cond) { + $pass++; + $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); + } else { + $fail++; + $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); + } +} + +function invokePolicyNoSync(PolicyTransactionController $controller, int $inceptionPtId, string $oldPolicyNo, string $newPolicyNo): array +{ + $method = new ReflectionMethod($controller, 'updateRelatedPolicyTransactionPolicyNo'); + $method->setAccessible(true); + + return $method->invoke($controller, $inceptionPtId, $oldPolicyNo, $newPolicyNo); +} + +$db = db_connect('default'); +$ptModel = new PolicyTransactionModel(); +$controller = new PolicyTransactionController(); + +$inceptionPtId = isset($argv[1]) ? (int) $argv[1] : 0; + +if ($inceptionPtId <= 0) { + $seed = $db->query( + "SELECT pt.id + FROM policy_transaction pt + WHERE pt.is_active = 1 + AND pt.action_type = 'inception' + AND pt.policy_no IS NOT NULL + AND pt.policy_no <> '' + AND EXISTS ( + SELECT 1 + FROM policy_transaction other + WHERE other.is_active = 1 + AND other.policy_no = pt.policy_no + AND other.id <> pt.id + ) + ORDER BY pt.id ASC + LIMIT 1" + )->getRowArray(); + + $inceptionPtId = (int) ($seed['id'] ?? 0); +} + +ok('seed inception row found', $inceptionPtId > 0, "pt_id={$inceptionPtId}"); + +$inception = $ptModel->where('is_active', 1)->where('id', $inceptionPtId)->first(); +ok('inception row loaded', !empty($inception), 'id=' . ($inception['id'] ?? 'n/a')); + +$oldPolicyNo = trim((string) ($inception['policy_no'] ?? '')); +ok('inception has policy_no', $oldPolicyNo !== '', $oldPolicyNo); + +$relatedBefore = $ptModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->findAll(); + +ok('related rows exist before sync', count($relatedBefore) > 0, 'count=' . count($relatedBefore)); + +$newPolicyNo = $oldPolicyNo . '_SMOKE_' . time(); +$relatedIds = array_map(static fn(array $row): int => (int) $row['id'], $relatedBefore); + +// No-op cases +$noChange = invokePolicyNoSync($controller, $inceptionPtId, $oldPolicyNo, $oldPolicyNo); +ok('no-op when policy_no unchanged', ($noChange['status'] ?? null) === false); + +$emptyOld = invokePolicyNoSync($controller, $inceptionPtId, '', $newPolicyNo); +ok('no-op when old policy_no empty', ($emptyOld['status'] ?? null) === false); + +// Apply sync on related rows only (inception row still has old policy_no for this call) +$result = invokePolicyNoSync($controller, $inceptionPtId, $oldPolicyNo, $newPolicyNo); +ok('sync returns success', ($result['status'] ?? null) === true, json_encode($result)); +ok('sync updated expected row count', count($result['updated_ids'] ?? []) === count($relatedIds), 'updated=' . count($result['updated_ids'] ?? [])); + +foreach ($relatedIds as $relatedId) { + $row = $ptModel->where('id', $relatedId)->first(); + ok("related row {$relatedId} has new policy_no", ($row['policy_no'] ?? '') === $newPolicyNo, $row['policy_no'] ?? 'missing'); +} + +$inceptionAfter = $ptModel->where('id', $inceptionPtId)->first(); +ok('inception row unchanged by sync helper', ($inceptionAfter['policy_no'] ?? '') === $oldPolicyNo, $inceptionAfter['policy_no'] ?? 'missing'); + +$stillOld = $ptModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->countAllResults(); +ok('no related rows left with old policy_no', $stillOld === 0, "remaining={$stillOld}"); + +// Restore test data +foreach ($relatedIds as $relatedId) { + $ptModel->where('id', $relatedId)->set(['policy_no' => $oldPolicyNo])->update(); +} + +$restored = $ptModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->countAllResults(); +ok('related rows restored', $restored === count($relatedIds), "restored={$restored}"); + +echo PHP_EOL . '=== smoke_inception_policy_no_sync ===' . PHP_EOL; +echo 'inception_pt_id: ' . $inceptionPtId . PHP_EOL; +echo 'policy_no: ' . $oldPolicyNo . PHP_EOL; +echo 'related_rows: ' . count($relatedIds) . PHP_EOL; +echo PHP_EOL; + +foreach ($results as $line) { + echo $line . PHP_EOL; +} + +echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL; + +exit($fail > 0 ? 1 : 0); diff --git a/tests/unit/PolicyTransactionControllerTest.php b/tests/unit/PolicyTransactionControllerTest.php index 50abd143..448fc3e4 100644 --- a/tests/unit/PolicyTransactionControllerTest.php +++ b/tests/unit/PolicyTransactionControllerTest.php @@ -112,6 +112,82 @@ class PolicyTransactionControllerTest extends CIUnitTestCase * * This reuses the same transformation logic as cronDailyBDSReport. */ + protected function invokePolicyNoSync(PolicyTransactionController $controller, int $inceptionPtId, string $oldPolicyNo, string $newPolicyNo): array + { + $method = new \ReflectionMethod($controller, 'updateRelatedPolicyTransactionPolicyNo'); + $method->setAccessible(true); + + return $method->invoke($controller, $inceptionPtId, $oldPolicyNo, $newPolicyNo); + } + + protected function injectPolicyNoSyncStubModel(PolicyTransactionController $controller, array $relatedRecords): void + { + $stubModel = new class($relatedRecords) { + private array $relatedRecords; + public array $updated = []; + + public function __construct(array $relatedRecords) + { + $this->relatedRecords = $relatedRecords; + } + + public function where($field, $value = null) + { + return $this; + } + + public function findAll(): array + { + return $this->relatedRecords; + } + + public function set(array $data) + { + $this->pending = $data; + return $this; + } + + public function update() + { + $this->updated[] = $this->pending ?? []; + return true; + } + + public function affectedRows(): int + { + return 1; + } + }; + + $refClass = new \ReflectionClass($controller); + $prop = $refClass->getProperty('policyTransactionModel'); + $prop->setAccessible(true); + $prop->setValue($controller, $stubModel); + } + + public function testUpdateRelatedPolicyTransactionPolicyNoSkipsWhenUnchanged(): void + { + $controller = $this->makeController(); + $result = $this->invokePolicyNoSync($controller, 10, 'POL-001', 'POL-001'); + + $this->assertFalse($result['status']); + $this->assertSame([], $result['updated_ids']); + } + + public function testUpdateRelatedPolicyTransactionPolicyNoUpdatesMatchingRows(): void + { + $controller = $this->makeController(); + $this->injectPolicyNoSyncStubModel($controller, [ + ['id' => 101], + ['id' => 102], + ]); + + $result = $this->invokePolicyNoSync($controller, 10, 'POL-OLD', 'POL-NEW'); + + $this->assertTrue($result['status']); + $this->assertSame([101, 102], $result['updated_ids']); + } + public function testGenerateDailyBdsReportExcelToDownloads(): void { helper(['excel_import_export_helper', 'utility_helper']); From 29ac2e4087a1a88136c2dd76784ee7fb2615444a Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Mon, 29 Jun 2026 17:00:08 +0530 Subject: [PATCH 7/9] FIX_POLICY_START_END_RANGE_AGREED_PERCENTAGE_DOT_AALOWED --- app/Views/view_rfq.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/app/Views/view_rfq.php b/app/Views/view_rfq.php index f768f740..c1d4c104 100644 --- a/app/Views/view_rfq.php +++ b/app/Views/view_rfq.php @@ -907,7 +907,7 @@
- "> + ">
@@ -1345,6 +1345,12 @@ var policy_start_date_datePicker = flatpickr("#policy_start_date", { dateFormat: "d/m/Y", allowInput: false, + onChange: function(selectedDates, dateStr, instance) { + var endDate = new Date(selectedDates[0]); + endDate.setFullYear(endDate.getFullYear() + 1); + endDate.setDate(endDate.getDate() - 1); + policy_end_date_datePicker.setDate(endDate); + } }); var policy_end_date_datePicker = flatpickr("#policy_end_date", { @@ -7838,8 +7844,32 @@ function appendMultiFileData(data) { } $('#agreed_percentage').on('input', function () { + let val = this.value.replace(/[^\d.]/g, ''); + const parts = val.split('.'); + if (parts.length > 2) { + val = parts[0] + '.' + parts.slice(1).join(''); + } + if (val !== '' && !val.endsWith('.')) { + let v = parseFloat(val); + if (!isNaN(v)) { + if (v < 0) val = '0'; + else if (v > 100) val = '100'; + } + } + this.value = val; + }); + + $('#agreed_percentage').on('blur', function () { + if (this.value === '' || this.value === '.') { + this.value = ''; + return; + } let v = parseFloat(this.value); - this.value = (v < 0) ? 0 : (v > 100 ? 100 : v); + if (isNaN(v)) { + this.value = ''; + } else { + this.value = (v < 0) ? 0 : (v > 100 ? 100 : v); + } }); From 00b1afd8fbd00def2ac181c2f27749779305670b Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Mon, 29 Jun 2026 18:27:58 +0530 Subject: [PATCH 8/9] FIX_Opportunities_LIVE_ISSUES --- app/Controllers/ClientController.php | 4 ++-- app/Controllers/LeadsController.php | 5 +++++ app/Models/LeadsModel.php | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index 0af6898a..beadfa73 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -471,7 +471,7 @@ class ClientController extends AdminController $headerData['page_name'] = 'Clients'; // Both Browser Tab name And Page name are same. // $data['clientList'] = $this->clientModel->getCreatedByUserName(1); // passing client_type $data['client_rm'] = $this->clientRMModel->getAllClientRM(); - $data['lead_data'] = $this->leadsModel->getLeadForInsertClientList(); + $data['lead_data'] = $this->leadsModel->getLeadForInsertClientList([1, 3]); $rawList = $this->clientModel->getCreatedByUserName(1); $clientRM = $data['client_rm']; @@ -1037,7 +1037,7 @@ class ClientController extends AdminController $editData['client_branch'] = $this->clientBranchModel->where(['client_id' => $id, 'is_active' => 1])->findAll(); $editData['client_relation'] = $this->clientRMModel->where(['client_id' => $id, 'is_active' => 1])->findAll(); $editData['client_branch']['role'] = get_role_id(); - $editData['lead_data'] = $this->leadsModel->getLeadForInsertClientList(null, $id); + $editData['lead_data'] = $this->leadsModel->getLeadForInsertClientList([2], $id); $clientPoliceData = $this->clientPolicyModel->getClientPolicyByClientId($id); foreach ($clientPoliceData as $key => $value) { diff --git a/app/Controllers/LeadsController.php b/app/Controllers/LeadsController.php index d6cd0df5..41f4252e 100644 --- a/app/Controllers/LeadsController.php +++ b/app/Controllers/LeadsController.php @@ -2895,6 +2895,11 @@ class LeadsController extends BaseController $filename = "{$rfq_data['client_name']}_{$rfq_data['policy_type']}_{$string}_" . $policy_year . '_' . '.xlsx'; } + $filenameBase = pathinfo($filename, PATHINFO_FILENAME); + $filenameBase = preg_replace('/[^a-zA-Z0-9\-_ ]/u', '', $filenameBase); + $filenameBase = preg_replace('/\s+/', ' ', trim($filenameBase)); + $filename = ($filenameBase !== '' ? $filenameBase : 'export') . '.xlsx'; + //claim history new sheet; if ($claim_history == 1 && !empty($rfq_data['fin_years_claims'])) { diff --git a/app/Models/LeadsModel.php b/app/Models/LeadsModel.php index ef88ba21..ba841687 100644 --- a/app/Models/LeadsModel.php +++ b/app/Models/LeadsModel.php @@ -205,7 +205,7 @@ class LeadsModel extends Model ->groupEnd(); if ($type) { - $query->where('leads.lead_type', $type); + $query->whereIn('leads.lead_type', $type); } if ($client_id) { $query->where('leads.client_id', $client_id); From f72bbfe79974b17bbde25155b89e402cb5df5c28 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Tue, 30 Jun 2026 11:17:03 +0530 Subject: [PATCH 9/9] FIX_MULTIPLE_POLICY_CREATE_IN_THE_LEAD_NEED_TO_LIST_IN_THE_DROPDOWN --- app/Controllers/ClientController.php | 2 +- app/Models/LeadsModel.php | 40 +++++++++++++++++++--------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index beadfa73..d9f3ea9c 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -1037,7 +1037,7 @@ class ClientController extends AdminController $editData['client_branch'] = $this->clientBranchModel->where(['client_id' => $id, 'is_active' => 1])->findAll(); $editData['client_relation'] = $this->clientRMModel->where(['client_id' => $id, 'is_active' => 1])->findAll(); $editData['client_branch']['role'] = get_role_id(); - $editData['lead_data'] = $this->leadsModel->getLeadForInsertClientList([2], $id); + $editData['lead_data'] = $this->leadsModel->getLeadForInsertClientList(null, $id, 'update'); $clientPoliceData = $this->clientPolicyModel->getClientPolicyByClientId($id); foreach ($clientPoliceData as $key => $value) { diff --git a/app/Models/LeadsModel.php b/app/Models/LeadsModel.php index ba841687..5ebd7f13 100644 --- a/app/Models/LeadsModel.php +++ b/app/Models/LeadsModel.php @@ -187,30 +187,46 @@ class LeadsModel extends Model return $data->orderBy('leads.id', 'desc')->findAll(); } - public function getLeadForInsertClientList($type = null, $client_id = null) + public function getLeadForInsertClientList($type = null, $client_id = null, $from = null) { $query = $this->db->table('leads') ->select('leads.*, user_profiles.first_name as user_name, policy_type.allocg') ->join('user_profiles', 'leads.created_by = user_profiles.id') ->join('policy_type', 'leads.policy_type_id = policy_type.id', 'left') ->where('leads.is_active', 1) - ->where('leads.status', 'won') - ->groupStart() + ->where('leads.status', 'won'); + + if ($from === 'update') { + $query->where("(leads.is_policy_created = '' OR leads.is_policy_created IS NULL)"); + + if ($client_id) { + $query->join('clients', 'clients.id = ' . (int) $client_id) + ->join('client_branch', 'client_branch.client_id = clients.id AND client_branch.is_active = 1') + ->where('leads.client_name = clients.client_name', null, false) + ->where('leads.client_short_name = clients.short_name', null, false) + ->where('leads.branch_name = client_branch.branch_name', null, false) + ->where('leads.branch_code = client_branch.branch_code', null, false) + ->groupBy('leads.id'); + } + } else { + $query->groupStart() ->where("(leads.is_client_created = '' OR leads.is_client_created IS NULL)") ->orWhere("(leads.is_policy_created = '' OR leads.is_policy_created IS NULL)") - ->groupEnd() - ->groupStart() - ->where("policy_type.allocg != 'EB'") - ->orWhere("(policy_type.allocg = 'EB' AND leads.proposel_data IS NOT NULL AND leads.proposel_data <> '')") ->groupEnd(); - if ($type) { - $query->whereIn('leads.lead_type', $type); - } - if ($client_id) { - $query->where('leads.client_id', $client_id); + if ($type) { + $query->whereIn('leads.lead_type', $type); + } + if ($client_id) { + $query->where('leads.client_id', $client_id); + } } + $query->groupStart() + ->where("policy_type.allocg != 'EB'") + ->orWhere("(policy_type.allocg = 'EB' AND leads.proposel_data IS NOT NULL AND leads.proposel_data <> '')") + ->groupEnd(); + $result = $query->get()->getResultArray(); return $result; }