From 4f87be295bc2a64b8fa917c7a6384e2fe5bf0e9e Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Tue, 21 Apr 2026 11:22:17 +0700 Subject: [PATCH] Add patient list age fields - Include Age and BirthdateConversion in patient list responses - Keep Sex label transformation unchanged - Preserve existing patient audit/update behavior --- app/Models/Patient/PatientModel.php | 480 ++++++++++++++-------------- 1 file changed, 245 insertions(+), 235 deletions(-) diff --git a/app/Models/Patient/PatientModel.php b/app/Models/Patient/PatientModel.php index 1310c68..e83b1ad 100755 --- a/app/Models/Patient/PatientModel.php +++ b/app/Models/Patient/PatientModel.php @@ -13,9 +13,9 @@ use App\Services\AuditService; class PatientModel extends BaseModel { protected $table = 'patient'; protected $primaryKey = 'InternalPID'; - protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Sex', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3', - 'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship', - 'isDead', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ]; + protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Sex', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3', + 'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship', + 'isDead', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ]; protected $useTimestamps = true; protected $createdField = 'CreateDate'; @@ -48,6 +48,16 @@ class PatientModel extends BaseModel { $rows = ValueSet::transformLabels($rows, [ 'Sex' => 'sex', ]); + + foreach ($rows as &$row) { + $birthdate = $row['Birthdate'] ?? null; + $row['Age'] = empty($birthdate) + ? '' + : $this->calculateAgeFromBirthdate($birthdate, null); + $row['BirthdateConversion'] = $this->formatedDateForDisplay($birthdate); + } + unset($row); + return $rows; } @@ -82,14 +92,14 @@ class PatientModel extends BaseModel { unset($patient['Identifier']); unset($patient['Comment']); - $patient = ValueSet::transformLabels([$patient], [ - 'Sex' => 'sex', - 'Country' => 'country', - 'Race' => 'race', - 'Religion' => 'religion', - 'Ethnic' => 'ethnic', - 'MaritalStatus' => 'marital_status', - ])[0]; + $patient = ValueSet::transformLabels([$patient], [ + 'Sex' => 'sex', + 'Country' => 'country', + 'Race' => 'race', + 'Religion' => 'religion', + 'Ethnic' => 'ethnic', + 'MaritalStatus' => 'marital_status', + ])[0]; $patient['PatIdt'] = null; $patient['PatAtt'] = []; @@ -151,24 +161,24 @@ class PatientModel extends BaseModel { $newInternalPID = $this->getInsertID(); $this->checkDbError($db, 'Insert patient'); - $auditDiff = $this->buildAuditDiff([], $input); - AuditService::logData( - 'PATIENT_REGISTERED', - 'CREATE', - 'patient', - (string) $newInternalPID, - 'patient', - null, - null, - null, - 'Patient registration', - [ - 'diff' => $auditDiff, - 'patient_id' => $input['PatientID'] ?? null, - 'validation_profile' => 'patient.create', - ], - ['entity_version' => 1] - ); + $auditDiff = $this->buildAuditDiff([], $input); + AuditService::logData( + 'PATIENT_REGISTERED', + 'CREATE', + 'patient', + (string) $newInternalPID, + 'patient', + null, + null, + null, + 'Patient registration', + [ + 'diff' => $auditDiff, + 'patient_id' => $input['PatientID'] ?? null, + 'validation_profile' => 'patient.create', + ], + ['entity_version' => 1] + ); if (!empty($patIdt)) { $modelPatIdt->createPatIdt($patIdt, $newInternalPID); @@ -195,7 +205,7 @@ class PatientModel extends BaseModel { } } - public function updatePatient($input) { + public function updatePatient($input) { $db = \Config\Database::connect(); $modelPatIdt = new PatIdtModel(); $modelPatCom = new PatComModel(); @@ -219,30 +229,30 @@ class PatientModel extends BaseModel { $db->transBegin(); try { - $InternalPID = $input['InternalPID']; - $previousData = $this->find($InternalPID) ?? []; - $this->where('InternalPID',$InternalPID)->set($input)->update(); - $this->checkDbError($db, 'Update patient'); - - $changedFields = array_keys(array_diff_assoc((array) $previousData, (array) $input)); - $auditDiff = $this->buildAuditDiff((array) $previousData, $input); - AuditService::logData( - 'PATIENT_DEMOGRAPHICS_UPDATED', - 'UPDATE', - 'patient', - (string) $InternalPID, - 'patient', - null, - null, - null, - 'Patient data updated', - [ - 'diff' => $auditDiff, - 'changed_fields' => $changedFields, - 'validation_profile' => 'patient.update', - ], - ['entity_version' => 1] - ); + $InternalPID = $input['InternalPID']; + $previousData = $this->find($InternalPID) ?? []; + $this->where('InternalPID',$InternalPID)->set($input)->update(); + $this->checkDbError($db, 'Update patient'); + + $changedFields = array_keys(array_diff_assoc((array) $previousData, (array) $input)); + $auditDiff = $this->buildAuditDiff((array) $previousData, $input); + AuditService::logData( + 'PATIENT_DEMOGRAPHICS_UPDATED', + 'UPDATE', + 'patient', + (string) $InternalPID, + 'patient', + null, + null, + null, + 'Patient data updated', + [ + 'diff' => $auditDiff, + 'changed_fields' => $changedFields, + 'validation_profile' => 'patient.update', + ], + ['entity_version' => 1] + ); if (!empty($input['PatIdt'])) { $modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID); @@ -348,170 +358,170 @@ class PatientModel extends BaseModel { } } - 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'); - } - - public function updatePatientPartial(int $InternalPID, array $input): ?int { - $db = \Config\Database::connect(); - $modelPatIdt = new PatIdtModel(); - $modelPatCom = new PatComModel(); - $modelPatAtt = new PatAttModel(); - - $patient = $this->find($InternalPID); - if (!$patient) { - return null; - } - - $hasPatIdt = array_key_exists('PatIdt', $input); - $hasPatCom = array_key_exists('PatCom', $input); - $hasPatAtt = array_key_exists('PatAtt', $input); - - $patIdt = $hasPatIdt ? $input['PatIdt'] : null; - $patCom = $hasPatCom ? $input['PatCom'] : null; - $patAtt = $hasPatAtt ? $input['PatAtt'] : null; - - unset($input['PatIdt'], $input['PatCom'], $input['PatAtt']); - - if (array_key_exists('Custodian', $input)) { - if (is_array($input['Custodian'])) { - $input['Custodian'] = $input['Custodian']['InternalPID'] ?? null; - } - - if ($input['Custodian'] !== null && $input['Custodian'] !== '') { - $input['Custodian'] = (int) $input['Custodian']; - } else { - $input['Custodian'] = null; - } - } - - if (array_key_exists('LinkTo', $input)) { - if (is_array($input['LinkTo'])) { - $internalPids = array_column($input['LinkTo'], 'InternalPID'); - $internalPids = array_filter($internalPids, static fn($pid) => $pid !== null && $pid !== ''); - $input['LinkTo'] = empty($internalPids) ? null : implode(',', $internalPids); - } - } - - $allowedMap = array_flip($this->allowedFields); - $patientUpdate = array_intersect_key($input, $allowedMap); - unset($patientUpdate['CreateDate'], $patientUpdate['DelDate']); - - $db->transBegin(); - - try { - $previousData = $this->find($InternalPID) ?? []; - - if ($patientUpdate !== []) { - $this->where('InternalPID', $InternalPID)->set($patientUpdate)->update(); - $this->checkDbError($db, 'Update patient'); - } - - if ($hasPatIdt) { - if ($patIdt === null || $patIdt === []) { - $modelPatIdt->deletePatIdt((string) $InternalPID); - $this->checkDbError($db, 'Delete patidt'); - } else { - $modelPatIdt->updatePatIdt($patIdt, (string) $InternalPID); - $this->checkDbError($db, 'Update patidt'); - } - } - - if ($hasPatCom) { - if ($patCom === null) { - $modelPatCom->deletePatCom((string) $InternalPID); - $this->checkDbError($db, 'Delete patcom'); - } else { - $modelPatCom->updatePatCom((string) $patCom, (string) $InternalPID); - $this->checkDbError($db, 'Update PatCom'); - } - } - - if ($hasPatAtt) { - if ($patAtt === null) { - $modelPatAtt->deletePatAtt((string) $InternalPID); - $this->checkDbError($db, 'Delete patatt'); - } else { - $modelPatAtt->updatePatAtt((array) $patAtt, (string) $InternalPID); - $this->checkDbError($db, 'Update/Delete patatt'); - } - } - - $afterData = array_merge((array) $previousData, $patientUpdate); - $auditDiff = $this->buildAuditDiff((array) $previousData, $afterData); - $changedFields = array_column($auditDiff, 'field'); - - if ($hasPatIdt) { - $changedFields[] = 'PatIdt'; - } - if ($hasPatCom) { - $changedFields[] = 'PatCom'; - } - if ($hasPatAtt) { - $changedFields[] = 'PatAtt'; - } - - AuditService::logData( - 'PATIENT_DEMOGRAPHICS_UPDATED', - 'UPDATE', - 'patient', - (string) $InternalPID, - 'patient', - null, - null, - null, - 'Patient data updated', - [ - 'diff' => $auditDiff, - 'changed_fields' => array_values(array_unique($changedFields)), - 'validation_profile' => 'patient.patch', - ], - ['entity_version' => 1] - ); - - $db->transCommit(); - - return $InternalPID; - - } catch (\Exception $e) { - $db->transRollback(); - throw $e; - } - } - - private function buildAuditDiff(array $before, array $after): array { - $diff = []; - $fields = array_unique(array_merge(array_keys($before), array_keys($after))); - foreach ($fields as $field) { - $prev = $before[$field] ?? null; - $next = $after[$field] ?? null; - if ($prev === $next) { - continue; - } - $diff[] = [ - 'field' => $field, - 'previous' => $prev, - 'new' => $next, - ]; - } - return $diff; - } - - private function checkDbError($db, string $context) { - $error = $db->error(); - if (!empty($error['code'])) { - throw new \Exception( - "{$context} failed: {$error['code']} - {$error['message']}" + 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'); + } + + public function updatePatientPartial(int $InternalPID, array $input): ?int { + $db = \Config\Database::connect(); + $modelPatIdt = new PatIdtModel(); + $modelPatCom = new PatComModel(); + $modelPatAtt = new PatAttModel(); + + $patient = $this->find($InternalPID); + if (!$patient) { + return null; + } + + $hasPatIdt = array_key_exists('PatIdt', $input); + $hasPatCom = array_key_exists('PatCom', $input); + $hasPatAtt = array_key_exists('PatAtt', $input); + + $patIdt = $hasPatIdt ? $input['PatIdt'] : null; + $patCom = $hasPatCom ? $input['PatCom'] : null; + $patAtt = $hasPatAtt ? $input['PatAtt'] : null; + + unset($input['PatIdt'], $input['PatCom'], $input['PatAtt']); + + if (array_key_exists('Custodian', $input)) { + if (is_array($input['Custodian'])) { + $input['Custodian'] = $input['Custodian']['InternalPID'] ?? null; + } + + if ($input['Custodian'] !== null && $input['Custodian'] !== '') { + $input['Custodian'] = (int) $input['Custodian']; + } else { + $input['Custodian'] = null; + } + } + + if (array_key_exists('LinkTo', $input)) { + if (is_array($input['LinkTo'])) { + $internalPids = array_column($input['LinkTo'], 'InternalPID'); + $internalPids = array_filter($internalPids, static fn($pid) => $pid !== null && $pid !== ''); + $input['LinkTo'] = empty($internalPids) ? null : implode(',', $internalPids); + } + } + + $allowedMap = array_flip($this->allowedFields); + $patientUpdate = array_intersect_key($input, $allowedMap); + unset($patientUpdate['CreateDate'], $patientUpdate['DelDate']); + + $db->transBegin(); + + try { + $previousData = $this->find($InternalPID) ?? []; + + if ($patientUpdate !== []) { + $this->where('InternalPID', $InternalPID)->set($patientUpdate)->update(); + $this->checkDbError($db, 'Update patient'); + } + + if ($hasPatIdt) { + if ($patIdt === null || $patIdt === []) { + $modelPatIdt->deletePatIdt((string) $InternalPID); + $this->checkDbError($db, 'Delete patidt'); + } else { + $modelPatIdt->updatePatIdt($patIdt, (string) $InternalPID); + $this->checkDbError($db, 'Update patidt'); + } + } + + if ($hasPatCom) { + if ($patCom === null) { + $modelPatCom->deletePatCom((string) $InternalPID); + $this->checkDbError($db, 'Delete patcom'); + } else { + $modelPatCom->updatePatCom((string) $patCom, (string) $InternalPID); + $this->checkDbError($db, 'Update PatCom'); + } + } + + if ($hasPatAtt) { + if ($patAtt === null) { + $modelPatAtt->deletePatAtt((string) $InternalPID); + $this->checkDbError($db, 'Delete patatt'); + } else { + $modelPatAtt->updatePatAtt((array) $patAtt, (string) $InternalPID); + $this->checkDbError($db, 'Update/Delete patatt'); + } + } + + $afterData = array_merge((array) $previousData, $patientUpdate); + $auditDiff = $this->buildAuditDiff((array) $previousData, $afterData); + $changedFields = array_column($auditDiff, 'field'); + + if ($hasPatIdt) { + $changedFields[] = 'PatIdt'; + } + if ($hasPatCom) { + $changedFields[] = 'PatCom'; + } + if ($hasPatAtt) { + $changedFields[] = 'PatAtt'; + } + + AuditService::logData( + 'PATIENT_DEMOGRAPHICS_UPDATED', + 'UPDATE', + 'patient', + (string) $InternalPID, + 'patient', + null, + null, + null, + 'Patient data updated', + [ + 'diff' => $auditDiff, + 'changed_fields' => array_values(array_unique($changedFields)), + 'validation_profile' => 'patient.patch', + ], + ['entity_version' => 1] + ); + + $db->transCommit(); + + return $InternalPID; + + } catch (\Exception $e) { + $db->transRollback(); + throw $e; + } + } + + private function buildAuditDiff(array $before, array $after): array { + $diff = []; + $fields = array_unique(array_merge(array_keys($before), array_keys($after))); + foreach ($fields as $field) { + $prev = $before[$field] ?? null; + $next = $after[$field] ?? null; + if ($prev === $next) { + continue; + } + $diff[] = [ + 'field' => $field, + 'previous' => $prev, + 'new' => $next, + ]; + } + return $diff; + } + + private function checkDbError($db, string $context) { + $error = $db->error(); + if (!empty($error['code'])) { + throw new \Exception( + "{$context} failed: {$error['code']} - {$error['message']}" ); } } @@ -529,23 +539,23 @@ class PatientModel extends BaseModel { $this->delete($InternalPID); $this->checkDbError($db, 'Delete patient'); - $auditDiff = $this->buildAuditDiff((array) $previousData, []); - AuditService::logData( - 'PATIENT_DELETED', - 'DELETE', - 'patient', - (string) $InternalPID, - 'patient', - null, - $previousData, - null, - 'Patient deleted', - [ - 'diff' => $auditDiff, - 'patient_id' => $previousData['PatientID'] ?? null, - ], - ['entity_version' => 1] - ); + $auditDiff = $this->buildAuditDiff((array) $previousData, []); + AuditService::logData( + 'PATIENT_DELETED', + 'DELETE', + 'patient', + (string) $InternalPID, + 'patient', + null, + $previousData, + null, + 'Patient deleted', + [ + 'diff' => $auditDiff, + 'patient_id' => $previousData['PatientID'] ?? null, + ], + ['entity_version' => 1] + ); $db->transCommit(); return $InternalPID;