select("InternalPID, PatientID, $qname as FullName, Sex, Birthdate, EmailAddress1 as Email, MobilePhone"); if (!empty($filters['Name'])) { $this->like($qname, $filters['Name'], 'both'); } if (!empty($filters['InternalPID'])) { $this->where('InternalPID', $filters['InternalPID']); } if (!empty($filters['PatientID'])) { $this->like('PatientID', $filters['PatientID'], 'both'); } if (!empty($filters['Birthdate'])) { $this->where('Birthdate', $filters['Birthdate']); } $rows = $this->findAll(); $rows = ValueSet::transformLabels($rows, [ 'Sex' => 'sex', ]); return $rows; } public function getPatient($InternalPID) { $rows = $this->select(" patient.*, patcom.Comment as Comment, patidt.IdentifierType, patidt.Identifier, patatt.Address, patient.Province as Province, areageo1.AreaName as ProvinceLabel, patient.City as City, areageo2.AreaName as CityLabel ") ->join('patcom', 'patcom.InternalPID = patient.InternalPID', 'left') ->join('patidt', 'patidt.InternalPID = patient.InternalPID', 'left') ->join('patatt', 'patatt.InternalPID = patient.InternalPID and patatt.DelDate is null', 'left') ->join('areageo areageo1', 'areageo1.AreaGeoID = patient.Province', 'left') ->join('areageo areageo2', 'areageo2.AreaGeoID = patient.City', 'left') ->where('patient.InternalPID', (int) $InternalPID) ->findAll(); if (empty($rows)) { return null; } $patient = $rows[0]; if (method_exists($this, 'transformPatientData')) { $patient = $this->transformPatientData($patient); } unset($patient['Address']); unset($patient['IdentifierType']); unset($patient['Identifier']); unset($patient['Comment']); $patient = ValueSet::transformLabels([$patient], [ 'Sex' => 'sex', 'Country' => 'country', 'Race' => 'race', 'Religion' => 'religion', 'Ethnic' => 'ethnic', 'DeathIndicator' => 'death_indicator', 'MaritalStatus' => 'marital_status', ])[0]; $patient['PatIdt'] = null; $patient['PatAtt'] = []; foreach ($rows as $row) { if ($row['IdentifierType'] && $row['Identifier'] && !$patient['PatIdt']) { $patient['PatIdt'] = [ 'IdentifierType' => $row['IdentifierType'], 'Identifier' => $row['Identifier'], ]; } if ($row['Address']) { $patient['PatAtt'][] = ['Address' => $row['Address']]; } } if (empty($patient['PatIdt'])) { $patient['PatIdt'] = null; } if (empty($patient['PatAtt'])) { $patient['PatAtt'] = null; } return $patient; } public function createPatient($input) { $db = \Config\Database::connect(); $modelPatAtt = new PatAttModel(); $modelPatCom = new PatComModel(); $modelPatIdt = new PatIdtModel(); // Extract nested data before filtering $patIdt = $input['PatIdt'] ?? null; $patCom = $input['PatCom'] ?? null; $patAtt = $input['PatAtt'] ?? null; // Remove nested arrays that don't belong to patient table unset($input['PatIdt'], $input['PatCom'], $input['PatAtt']); if (!empty($input['Custodian'])) { if (is_array($input['Custodian'])) { $input['Custodian'] = $input['Custodian']['InternalPID'] ?? null; if ($input['Custodian'] !== null) { $input['Custodian'] = (int) $input['Custodian']; } } } if (!empty($input['LinkTo']) && is_array($input['LinkTo'])) { $internalPids = array_column($input['LinkTo'], 'InternalPID'); $input['LinkTo'] = implode(',', $internalPids); } $input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo']; $db->transBegin(); try { $this->insert($input); $newInternalPID = $this->getInsertID(); $this->checkDbError($db, 'Insert patient'); if (!empty($patIdt)) { $modelPatIdt->createPatIdt($patIdt, $newInternalPID); $this->checkDbError($db, 'Insert PatIdt'); } if (!empty($patCom)) { $modelPatCom->createPatCom($patCom, $newInternalPID); $this->checkDbError($db, 'Insert PatCom'); } if (!empty($patAtt)) { $modelPatAtt->createPatAtt($patAtt, $newInternalPID); $this->checkDbError($db, 'Insert PatAtt'); } $db->transCommit(); return $newInternalPID; } catch (\Exception $e) { $db->transRollback(); throw $e; } } public function updatePatient($input) { $db = \Config\Database::connect(); $modelPatIdt = new PatIdtModel(); $modelPatCom = new PatComModel(); $modelPatAtt = new PatAttModel(); if (!empty($input['Custodian'])) { if (is_array($input['Custodian'])) { $input['Custodian'] = $input['Custodian']['InternalPID'] ?? null; if ($input['Custodian'] !== null) { $input['Custodian'] = (int) $input['Custodian']; } } } if (!empty($input['LinkTo']) && is_array($input['LinkTo'])) { $internalPids = array_column($input['LinkTo'], 'InternalPID'); $input['LinkTo'] = implode(',', $internalPids); } $input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo']; $db->transBegin(); try { $InternalPID = $input['InternalPID']; $this->where('InternalPID',$InternalPID)->set($input)->update(); $this->checkDbError($db, 'Update patient'); if (!empty($input['PatIdt'])) { $modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID); $this->checkDbError($db, 'Update patIdt'); } else { $modelPatIdt->deletePatIdt($InternalPID); $this->checkDbError($db, 'Update patidt'); } if (!empty($input['PatCom'])) { $modelPatCom->updatePatCom($input['PatCom'], $InternalPID); $this->checkDbError($db, 'Update PatCom'); } else { $modelPatCom->deletePatCom($InternalPID); $this->checkDbError($db, 'Update patcom'); } if (!empty($input['PatAtt'])) { $modelPatAtt->updatePatAtt($input['PatAtt'], $InternalPID); $this->checkDbError($db, 'Update PatAtt'); } else { $modelPatAtt->deletePatAtt($InternalPID); $this->checkDbError($db, 'Update/Delete patatt'); } $db->transCommit(); return $InternalPID; } catch (\Exception $e) { $db->transRollback(); throw $e; } } private function transformPatientData(array $patient): array { $patient["Age"] = $this->calculateAgeFromBirthdate($patient["Birthdate"], $patient["TimeOfDeath"]); $patient["TimeOfDeath"] = $this->formattedDate($patient["TimeOfDeath"]); $patient["CreateDate"] = $this->formattedDate($patient["CreateDate"]); $patient["BirthdateConversion"] = $this->formatedDateForDisplay($patient["Birthdate"]); $patient["LinkTo"] = $this->getLinkedPatients($patient['LinkTo']); $patient["Custodian"] = $this->getCustodian($patient['Custodian']); $patient['PatCom'] = $patient['Comment']; return $patient; } private function getLinkedPatients(?string $linkTo): ?array { if (empty($linkTo)) { return null; } $ids = array_filter(explode(',', $linkTo)); return $this->db->table('patient') ->select('InternalPID, PatientID') ->whereIn('InternalPID', $ids) ->get() ->getResultArray() ?: null; } private function getCustodian($custodianId): ?array { if (empty($custodianId)) { return null; } return $this->select('InternalPID, PatientID') ->where('InternalPID', (int) $custodianId) ->first() ?: null; } private function calculateAgeFromBirthdate($birthdate, $deathdate) { $dob = new \DateTime($birthdate); if ($deathdate == null) { $today = new \DateTime(); } else { $deathdate = new \DateTime($deathdate); $today = $deathdate; } $diff = $today->diff($dob); $formattedAge = ""; if ($diff->y > 0){ $formattedAge .= "{$diff->y} Years "; } if ($diff->m > 0){ $formattedAge .= "{$diff->m} Months "; } if ($diff->d > 0){ $formattedAge .= "{$diff->d} Days"; } return $formattedAge; } private function formattedDate(?string $dateString): ?string { try { if (empty($dateString)) {return null;} $dt = new \DateTime($dateString, new \DateTimeZone("UTC")); return $dt->format('Y-m-d\TH:i:s\Z'); } catch (\Exception $e) { return null; } } private function formatedDateForDisplay($dateString) { $date = \DateTime::createFromFormat('Y-m-d H:i', $dateString); if (!$date) { $timestamp = strtotime($dateString); if ($timestamp) { return date('j M Y', $timestamp); } return null; } return $date->format('j M Y'); } private function checkDbError($db, string $context) { $error = $db->error(); if (!empty($error['code'])) { throw new \Exception( "{$context} failed: {$error['code']} - {$error['message']}" ); } } private function isValidDateTime($datetime) { if (empty($datetime) || $datetime=="") {return null; } try { if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $datetime)) { $dt = \DateTime::createFromFormat('Y-m-d', $datetime); return $dt ? $dt->format('Y-m-d') : null; } $dt = new \DateTime($datetime); return $dt->format('Y-m-d H:i:s'); } catch (\Exception $e) { return null; } } }