clqms-be/app/Models/Patient/PatientModel.php
2025-10-16 10:28:40 +07:00

351 lines
11 KiB
PHP

<?php
namespace App\Models\Patient;
use App\Models\BaseModel;
use App\Models\BaseUtcModel;
use App\Models\Patient\PatAttModel;
use App\Models\Patient\PatComModel;
use App\Models\Patient\PatIdtModel;
use CodeIgniter\Database\RawSql;
class PatientModel extends BaseModel {
protected $table = 'patient';
protected $primaryKey = 'InternalPID';
protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Gender', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3',
'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship',
'DeathIndicator', 'DeathDateTime', 'LinkTo', 'CreateDate', 'DelDate' ];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'DelDate';
protected $beforeInsert = ['normalizeDatesToUTC'];
protected $beforeUpdate = ['normalizeDatesToUTC'];
protected $afterFind = ['convertDatesToUTCISO'];
protected $afterInsert = ['convertDatesToUTCISO'];
protected $afterUpdate = ['convertDatesToUTCISO'];
public function getPatients($filters = []) {
$qname = "LOWER(CONCAT_WS(' ', IFNULL(Prefix,''), IFNULL(NameFirst,''), IFNULL(NameMiddle,''), IFNULL(NameLast,''), IFNULL(NameMaiden,''), IFNULL(Suffix,'')))";
$builder = $this->db->table($this->table);
$builder->select("InternalPID, PatientID, $qname as FullName, Gender, Birthdate, EmailAddress1 as Email, MobilePhone");
if (!empty($filters['Name'])) {
$rawSql = new RawSql($qname);
$builder->like($rawSql, $filters['Name'], 'both');
}
if (!empty($filters['InternalPID'])) {
$builder->where('InternalPID', $filters['InternalPID']);
}
if (!empty($filters['PatientID'])) {
$builder->like('PatientID', $filters['PatientID'], 'both');
}
if (!empty($filters['Birthdate'])) {
$builder->where('Birthdate', $filters['Birthdate']);
}
return $builder->get()->getResultArray();
}
public function getPatient($InternalPID) {
$rows = $this->db->table('patient p')
->select("
p.*,
country.VDesc as Country,
country.VID as CountryVID,
race.VDesc as Race,
race.VID as RaceVID,
religion.VDesc as Religion,
religion.VID as ReligionVID,
ethnic.VDesc as Ethnic,
ethnic.VID as EthnicVID,
gender.VDesc as Gender,
gender.VID as GenderVID,
deathindicator.VDesc as DeathIndicator,
deathindicator.VID as DeathIndicatorVID,
maritalstatus.VDesc as MaritalStatus,
maritalstatus.VID as MaritalStatusVID,
patcom.Comment as Comment,
patidt.IdentifierType,
patidt.Identifier,
patatt.Address
")
->join('valueset country', 'country.VID = p.Country', 'left')
->join('valueset race', 'race.VID = p.Race', 'left')
->join('valueset religion', 'religion.VID = p.Religion', 'left')
->join('valueset ethnic', 'ethnic.VID = p.Ethnic', 'left')
->join('valueset gender', 'gender.VID = p.Gender', 'left')
->join('valueset deathindicator', 'deathindicator.VID = p.DeathIndicator', 'left')
->join('valueset maritalstatus', 'maritalstatus.VID = p.MaritalStatus', 'left')
->join('patcom', 'patcom.InternalPID = p.InternalPID', 'left')
->join('patidt', 'patidt.InternalPID = p.InternalPID', 'left')
->join('patatt', 'patatt.InternalPID = p.InternalPID and patatt.DelDate is null', 'left')
->where('p.InternalPID', (int) $InternalPID)
->get()
->getResultArray();
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']);
// Default nested structures
$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();
$this->modelPatAtt = new PatAttModel();
$this->modelPatCom = new PatComModel();
$this->modelPatIdt = new PatIdtModel();
$input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo'];
$input['Birthdate'] = $this->isValidDateTime($input['Birthdate']);
$input['DeathDateTime'] = $this->isValidDateTime($input['DeathDateTime']);
$db->transBegin();
try {
// Insert Data ke Tabel Patient, get ID dan cek apa ada error
$this->insert($input);
$newInternalPID = $this->getInsertID();
$this->checkDbError($db, 'Insert patient');
// Insert Data ke Tabel PatIdt
if (!empty($input['PatIdt'])) {
$this->modelPatIdt->createPatIdt($input['PatIdt'], $newInternalPID);
$this->checkDbError($db, 'Insert PatIdt');
}
// Insert Data ke Tabel PatCom
if (!empty($input['PatCom'])) {
$this->modelPatCom->createPatCom($input['PatCom'], $newInternalPID);
$this->checkDbError($db, 'Insert PatCom');
}
// Insert Data ke Tabel PatAtt
if (!empty($input['PatAtt'])) {
$this->modelPatAtt->createPatAtt($input['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();
$this->modelPatIdt = new PatIdtModel();
$this->modelPatCom = new PatComModel();
$this->modelPatAtt = new PatAttModel();
$input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo'];
$input['Birthdate'] = $this->isValidDateTime($input['Birthdate']);
$input['DeathDateTime'] = $this->isValidDateTime($input['DeathDateTime']);
$db->transBegin();
try {
// Update Patient
$InternalPID = $input['InternalPID'];
$this->where('InternalPID',$InternalPID)->set($input)->update();
$this->checkDbError($db, 'Update patient');
// Update Patidt
if (!empty($input['PatIdt'])) {
$this->modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID);
$this->checkDbError($db, 'Update patIdt');
} else {
$this->modelPatIdt->deletePatIdt($InternalPID);
$this->checkDbError($db, 'Update patidt');
}
// Update Patcom
if (!empty($input['PatCom'])) {
$this->modelPatCom->updatePatCom($input['PatCom'], $InternalPID);
$this->checkDbError($db, 'Update PatCom');
} else {
$this->modelPatCom->deletePatCom($InternalPID);
$this->checkDbError($db, 'Update patcom');
}
// Update Patatt
if (!empty($input['PatAtt'])) {
$this->modelPatAtt->updatePatAtt($input['PatAtt'], $InternalPID);
$this->checkDbError($db, 'Update PatAtt');
} else {
$this->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["DeathDateTime"]);
$patient["DeathDateTime"] = $this->formattedDate($patient["DeathDateTime"]);
$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->db->table('patient')
->select('InternalPID, PatientID')
->where('InternalPID', (int) $custodianId)
->get()
->getRowArray() ?: null;
}
// Conversion to (Years Months Days) - For Age
private function calculateAgeFromBirthdate($birthdate, $deathdatetime) {
$dob = new \DateTime($birthdate);
// Cek DeathTime
if ($deathdatetime == null) {
$today = new \DateTime();
} else {
$deathdatetime = new \DateTime($deathdatetime);
$today = $deathdatetime;
}
$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;
}
// Conversion Time to Format Y-m-d\TH:i:s\Z
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'); // ISO 8601 UTC
} catch (\Exception $e) {
return null;
}
}
// Conversion Time to Format j M Y - For BirthdateConversion
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');
}
// Check Error and Send Spesific Messages
private function checkDbError($db, string $context) {
$error = $db->error();
if (!empty($error['code'])) {
throw new \Exception(
"{$context} failed: {$error['code']} - {$error['message']}"
);
}
}
// Preventif 0000-00-00
private function isValidDateTime($datetime) {
if (empty($datetime) || $datetime=="") {return null; }
try {
// Kalau input hanya Y-m-d (tanpa jam)
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; // hanya tanggal
}
// Selain itu (ISO 8601 atau datetime lain), format ke Y-m-d H:i:s
$dt = new \DateTime($datetime);
return $dt->format('Y-m-d H:i:s');
} catch (\Exception $e) {
return null;
}
}
}