Add patient list age fields

- Include Age and BirthdateConversion in patient list responses
- Keep Sex label transformation unchanged
- Preserve existing patient audit/update behavior
This commit is contained in:
mahdahar 2026-04-21 11:22:17 +07:00
parent 8fdaf0ade7
commit 4f87be295b

View File

@ -13,9 +13,9 @@ use App\Services\AuditService;
class PatientModel extends BaseModel { class PatientModel extends BaseModel {
protected $table = 'patient'; protected $table = 'patient';
protected $primaryKey = 'InternalPID'; protected $primaryKey = 'InternalPID';
protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Sex', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3', 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', 'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship',
'isDead', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ]; 'isDead', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ];
protected $useTimestamps = true; protected $useTimestamps = true;
protected $createdField = 'CreateDate'; protected $createdField = 'CreateDate';
@ -48,6 +48,16 @@ class PatientModel extends BaseModel {
$rows = ValueSet::transformLabels($rows, [ $rows = ValueSet::transformLabels($rows, [
'Sex' => 'sex', '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; return $rows;
} }
@ -82,14 +92,14 @@ class PatientModel extends BaseModel {
unset($patient['Identifier']); unset($patient['Identifier']);
unset($patient['Comment']); unset($patient['Comment']);
$patient = ValueSet::transformLabels([$patient], [ $patient = ValueSet::transformLabels([$patient], [
'Sex' => 'sex', 'Sex' => 'sex',
'Country' => 'country', 'Country' => 'country',
'Race' => 'race', 'Race' => 'race',
'Religion' => 'religion', 'Religion' => 'religion',
'Ethnic' => 'ethnic', 'Ethnic' => 'ethnic',
'MaritalStatus' => 'marital_status', 'MaritalStatus' => 'marital_status',
])[0]; ])[0];
$patient['PatIdt'] = null; $patient['PatIdt'] = null;
$patient['PatAtt'] = []; $patient['PatAtt'] = [];
@ -151,24 +161,24 @@ class PatientModel extends BaseModel {
$newInternalPID = $this->getInsertID(); $newInternalPID = $this->getInsertID();
$this->checkDbError($db, 'Insert patient'); $this->checkDbError($db, 'Insert patient');
$auditDiff = $this->buildAuditDiff([], $input); $auditDiff = $this->buildAuditDiff([], $input);
AuditService::logData( AuditService::logData(
'PATIENT_REGISTERED', 'PATIENT_REGISTERED',
'CREATE', 'CREATE',
'patient', 'patient',
(string) $newInternalPID, (string) $newInternalPID,
'patient', 'patient',
null, null,
null, null,
null, null,
'Patient registration', 'Patient registration',
[ [
'diff' => $auditDiff, 'diff' => $auditDiff,
'patient_id' => $input['PatientID'] ?? null, 'patient_id' => $input['PatientID'] ?? null,
'validation_profile' => 'patient.create', 'validation_profile' => 'patient.create',
], ],
['entity_version' => 1] ['entity_version' => 1]
); );
if (!empty($patIdt)) { if (!empty($patIdt)) {
$modelPatIdt->createPatIdt($patIdt, $newInternalPID); $modelPatIdt->createPatIdt($patIdt, $newInternalPID);
@ -195,7 +205,7 @@ class PatientModel extends BaseModel {
} }
} }
public function updatePatient($input) { public function updatePatient($input) {
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$modelPatIdt = new PatIdtModel(); $modelPatIdt = new PatIdtModel();
$modelPatCom = new PatComModel(); $modelPatCom = new PatComModel();
@ -219,30 +229,30 @@ class PatientModel extends BaseModel {
$db->transBegin(); $db->transBegin();
try { try {
$InternalPID = $input['InternalPID']; $InternalPID = $input['InternalPID'];
$previousData = $this->find($InternalPID) ?? []; $previousData = $this->find($InternalPID) ?? [];
$this->where('InternalPID',$InternalPID)->set($input)->update(); $this->where('InternalPID',$InternalPID)->set($input)->update();
$this->checkDbError($db, 'Update patient'); $this->checkDbError($db, 'Update patient');
$changedFields = array_keys(array_diff_assoc((array) $previousData, (array) $input)); $changedFields = array_keys(array_diff_assoc((array) $previousData, (array) $input));
$auditDiff = $this->buildAuditDiff((array) $previousData, $input); $auditDiff = $this->buildAuditDiff((array) $previousData, $input);
AuditService::logData( AuditService::logData(
'PATIENT_DEMOGRAPHICS_UPDATED', 'PATIENT_DEMOGRAPHICS_UPDATED',
'UPDATE', 'UPDATE',
'patient', 'patient',
(string) $InternalPID, (string) $InternalPID,
'patient', 'patient',
null, null,
null, null,
null, null,
'Patient data updated', 'Patient data updated',
[ [
'diff' => $auditDiff, 'diff' => $auditDiff,
'changed_fields' => $changedFields, 'changed_fields' => $changedFields,
'validation_profile' => 'patient.update', 'validation_profile' => 'patient.update',
], ],
['entity_version' => 1] ['entity_version' => 1]
); );
if (!empty($input['PatIdt'])) { if (!empty($input['PatIdt'])) {
$modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID); $modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID);
@ -348,170 +358,170 @@ class PatientModel extends BaseModel {
} }
} }
private function formatedDateForDisplay($dateString) { private function formatedDateForDisplay($dateString) {
$date = \DateTime::createFromFormat('Y-m-d H:i', $dateString); $date = \DateTime::createFromFormat('Y-m-d H:i', $dateString);
if (!$date) { if (!$date) {
$timestamp = strtotime($dateString); $timestamp = strtotime($dateString);
if ($timestamp) { if ($timestamp) {
return date('j M Y', $timestamp); return date('j M Y', $timestamp);
} }
return null; return null;
} }
return $date->format('j M Y'); return $date->format('j M Y');
} }
public function updatePatientPartial(int $InternalPID, array $input): ?int { public function updatePatientPartial(int $InternalPID, array $input): ?int {
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$modelPatIdt = new PatIdtModel(); $modelPatIdt = new PatIdtModel();
$modelPatCom = new PatComModel(); $modelPatCom = new PatComModel();
$modelPatAtt = new PatAttModel(); $modelPatAtt = new PatAttModel();
$patient = $this->find($InternalPID); $patient = $this->find($InternalPID);
if (!$patient) { if (!$patient) {
return null; return null;
} }
$hasPatIdt = array_key_exists('PatIdt', $input); $hasPatIdt = array_key_exists('PatIdt', $input);
$hasPatCom = array_key_exists('PatCom', $input); $hasPatCom = array_key_exists('PatCom', $input);
$hasPatAtt = array_key_exists('PatAtt', $input); $hasPatAtt = array_key_exists('PatAtt', $input);
$patIdt = $hasPatIdt ? $input['PatIdt'] : null; $patIdt = $hasPatIdt ? $input['PatIdt'] : null;
$patCom = $hasPatCom ? $input['PatCom'] : null; $patCom = $hasPatCom ? $input['PatCom'] : null;
$patAtt = $hasPatAtt ? $input['PatAtt'] : null; $patAtt = $hasPatAtt ? $input['PatAtt'] : null;
unset($input['PatIdt'], $input['PatCom'], $input['PatAtt']); unset($input['PatIdt'], $input['PatCom'], $input['PatAtt']);
if (array_key_exists('Custodian', $input)) { if (array_key_exists('Custodian', $input)) {
if (is_array($input['Custodian'])) { if (is_array($input['Custodian'])) {
$input['Custodian'] = $input['Custodian']['InternalPID'] ?? null; $input['Custodian'] = $input['Custodian']['InternalPID'] ?? null;
} }
if ($input['Custodian'] !== null && $input['Custodian'] !== '') { if ($input['Custodian'] !== null && $input['Custodian'] !== '') {
$input['Custodian'] = (int) $input['Custodian']; $input['Custodian'] = (int) $input['Custodian'];
} else { } else {
$input['Custodian'] = null; $input['Custodian'] = null;
} }
} }
if (array_key_exists('LinkTo', $input)) { if (array_key_exists('LinkTo', $input)) {
if (is_array($input['LinkTo'])) { if (is_array($input['LinkTo'])) {
$internalPids = array_column($input['LinkTo'], 'InternalPID'); $internalPids = array_column($input['LinkTo'], 'InternalPID');
$internalPids = array_filter($internalPids, static fn($pid) => $pid !== null && $pid !== ''); $internalPids = array_filter($internalPids, static fn($pid) => $pid !== null && $pid !== '');
$input['LinkTo'] = empty($internalPids) ? null : implode(',', $internalPids); $input['LinkTo'] = empty($internalPids) ? null : implode(',', $internalPids);
} }
} }
$allowedMap = array_flip($this->allowedFields); $allowedMap = array_flip($this->allowedFields);
$patientUpdate = array_intersect_key($input, $allowedMap); $patientUpdate = array_intersect_key($input, $allowedMap);
unset($patientUpdate['CreateDate'], $patientUpdate['DelDate']); unset($patientUpdate['CreateDate'], $patientUpdate['DelDate']);
$db->transBegin(); $db->transBegin();
try { try {
$previousData = $this->find($InternalPID) ?? []; $previousData = $this->find($InternalPID) ?? [];
if ($patientUpdate !== []) { if ($patientUpdate !== []) {
$this->where('InternalPID', $InternalPID)->set($patientUpdate)->update(); $this->where('InternalPID', $InternalPID)->set($patientUpdate)->update();
$this->checkDbError($db, 'Update patient'); $this->checkDbError($db, 'Update patient');
} }
if ($hasPatIdt) { if ($hasPatIdt) {
if ($patIdt === null || $patIdt === []) { if ($patIdt === null || $patIdt === []) {
$modelPatIdt->deletePatIdt((string) $InternalPID); $modelPatIdt->deletePatIdt((string) $InternalPID);
$this->checkDbError($db, 'Delete patidt'); $this->checkDbError($db, 'Delete patidt');
} else { } else {
$modelPatIdt->updatePatIdt($patIdt, (string) $InternalPID); $modelPatIdt->updatePatIdt($patIdt, (string) $InternalPID);
$this->checkDbError($db, 'Update patidt'); $this->checkDbError($db, 'Update patidt');
} }
} }
if ($hasPatCom) { if ($hasPatCom) {
if ($patCom === null) { if ($patCom === null) {
$modelPatCom->deletePatCom((string) $InternalPID); $modelPatCom->deletePatCom((string) $InternalPID);
$this->checkDbError($db, 'Delete patcom'); $this->checkDbError($db, 'Delete patcom');
} else { } else {
$modelPatCom->updatePatCom((string) $patCom, (string) $InternalPID); $modelPatCom->updatePatCom((string) $patCom, (string) $InternalPID);
$this->checkDbError($db, 'Update PatCom'); $this->checkDbError($db, 'Update PatCom');
} }
} }
if ($hasPatAtt) { if ($hasPatAtt) {
if ($patAtt === null) { if ($patAtt === null) {
$modelPatAtt->deletePatAtt((string) $InternalPID); $modelPatAtt->deletePatAtt((string) $InternalPID);
$this->checkDbError($db, 'Delete patatt'); $this->checkDbError($db, 'Delete patatt');
} else { } else {
$modelPatAtt->updatePatAtt((array) $patAtt, (string) $InternalPID); $modelPatAtt->updatePatAtt((array) $patAtt, (string) $InternalPID);
$this->checkDbError($db, 'Update/Delete patatt'); $this->checkDbError($db, 'Update/Delete patatt');
} }
} }
$afterData = array_merge((array) $previousData, $patientUpdate); $afterData = array_merge((array) $previousData, $patientUpdate);
$auditDiff = $this->buildAuditDiff((array) $previousData, $afterData); $auditDiff = $this->buildAuditDiff((array) $previousData, $afterData);
$changedFields = array_column($auditDiff, 'field'); $changedFields = array_column($auditDiff, 'field');
if ($hasPatIdt) { if ($hasPatIdt) {
$changedFields[] = 'PatIdt'; $changedFields[] = 'PatIdt';
} }
if ($hasPatCom) { if ($hasPatCom) {
$changedFields[] = 'PatCom'; $changedFields[] = 'PatCom';
} }
if ($hasPatAtt) { if ($hasPatAtt) {
$changedFields[] = 'PatAtt'; $changedFields[] = 'PatAtt';
} }
AuditService::logData( AuditService::logData(
'PATIENT_DEMOGRAPHICS_UPDATED', 'PATIENT_DEMOGRAPHICS_UPDATED',
'UPDATE', 'UPDATE',
'patient', 'patient',
(string) $InternalPID, (string) $InternalPID,
'patient', 'patient',
null, null,
null, null,
null, null,
'Patient data updated', 'Patient data updated',
[ [
'diff' => $auditDiff, 'diff' => $auditDiff,
'changed_fields' => array_values(array_unique($changedFields)), 'changed_fields' => array_values(array_unique($changedFields)),
'validation_profile' => 'patient.patch', 'validation_profile' => 'patient.patch',
], ],
['entity_version' => 1] ['entity_version' => 1]
); );
$db->transCommit(); $db->transCommit();
return $InternalPID; return $InternalPID;
} catch (\Exception $e) { } catch (\Exception $e) {
$db->transRollback(); $db->transRollback();
throw $e; throw $e;
} }
} }
private function buildAuditDiff(array $before, array $after): array { private function buildAuditDiff(array $before, array $after): array {
$diff = []; $diff = [];
$fields = array_unique(array_merge(array_keys($before), array_keys($after))); $fields = array_unique(array_merge(array_keys($before), array_keys($after)));
foreach ($fields as $field) { foreach ($fields as $field) {
$prev = $before[$field] ?? null; $prev = $before[$field] ?? null;
$next = $after[$field] ?? null; $next = $after[$field] ?? null;
if ($prev === $next) { if ($prev === $next) {
continue; continue;
} }
$diff[] = [ $diff[] = [
'field' => $field, 'field' => $field,
'previous' => $prev, 'previous' => $prev,
'new' => $next, 'new' => $next,
]; ];
} }
return $diff; return $diff;
} }
private function checkDbError($db, string $context) { private function checkDbError($db, string $context) {
$error = $db->error(); $error = $db->error();
if (!empty($error['code'])) { if (!empty($error['code'])) {
throw new \Exception( throw new \Exception(
"{$context} failed: {$error['code']} - {$error['message']}" "{$context} failed: {$error['code']} - {$error['message']}"
); );
} }
} }
@ -529,23 +539,23 @@ class PatientModel extends BaseModel {
$this->delete($InternalPID); $this->delete($InternalPID);
$this->checkDbError($db, 'Delete patient'); $this->checkDbError($db, 'Delete patient');
$auditDiff = $this->buildAuditDiff((array) $previousData, []); $auditDiff = $this->buildAuditDiff((array) $previousData, []);
AuditService::logData( AuditService::logData(
'PATIENT_DELETED', 'PATIENT_DELETED',
'DELETE', 'DELETE',
'patient', 'patient',
(string) $InternalPID, (string) $InternalPID,
'patient', 'patient',
null, null,
$previousData, $previousData,
null, null,
'Patient deleted', 'Patient deleted',
[ [
'diff' => $auditDiff, 'diff' => $auditDiff,
'patient_id' => $previousData['PatientID'] ?? null, 'patient_id' => $previousData['PatientID'] ?? null,
], ],
['entity_version' => 1] ['entity_version' => 1]
); );
$db->transCommit(); $db->transCommit();
return $InternalPID; return $InternalPID;