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:
parent
8fdaf0ade7
commit
4f87be295b
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user