From fda7a14a5fe4bdde38d18078a49518dec8f20869 Mon Sep 17 00:00:00 2001 From: mikael-zakaria Date: Thu, 16 Oct 2025 10:28:40 +0700 Subject: [PATCH] Update Refactoring Patient --- app/Config/Routes.php | 12 +- app/Controllers/{ => Patient}/Patient.php | 23 +-- app/Models/Patient/PatAttModel.php | 88 ++++++++++ app/Models/Patient/PatComModel.php | 44 +++++ app/Models/Patient/PatIdtModel.php | 51 ++++++ app/Models/{ => Patient}/PatientModel.php | 172 +++++++------------ tests/feature/Patients/PatientCreateTest.php | 5 +- 7 files changed, 261 insertions(+), 134 deletions(-) rename app/Controllers/{ => Patient}/Patient.php (97%) create mode 100644 app/Models/Patient/PatAttModel.php create mode 100644 app/Models/Patient/PatComModel.php create mode 100644 app/Models/Patient/PatIdtModel.php rename app/Models/{ => Patient}/PatientModel.php (66%) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 1c36a5c..7201c30 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -23,12 +23,12 @@ $routes->post('/api/auth/register', 'Auth::register'); $routes->get('/api/auth/check', 'Auth::checkAuth'); $routes->post('/api/auth/logout', 'Auth::logout'); -$routes->get('/api/patient', 'Patient::index'); -$routes->post('/api/patient', 'Patient::create'); -$routes->get('/api/patient/(:num)', 'Patient::show/$1'); -$routes->delete('/api/patient', 'Patient::delete'); -$routes->patch('/api/patient', 'Patient::update'); -$routes->get('/api/patient/check', 'Patient::patientCheck'); +$routes->get('/api/patient', 'Patient\Patient::index'); +$routes->post('/api/patient', 'Patient\Patient::create'); +$routes->get('/api/patient/(:num)', 'Patient\Patient::show/$1'); +$routes->delete('/api/patient', 'Patient\Patient::delete'); +$routes->patch('/api/patient', 'Patient\Patient::update'); +$routes->get('/api/patient/check', 'Patient\Patient::patientCheck'); //$routes->get('/api/patvisit', 'Patient::index'); $routes->post('/api/patvisit', 'PatVisit::create'); diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient/Patient.php similarity index 97% rename from app/Controllers/Patient.php rename to app/Controllers/Patient/Patient.php index b6f4727..7218b77 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient/Patient.php @@ -1,9 +1,10 @@ db = \Config\Database::connect(); - $this->model = new PatientModel(); + $this->model = new PatientModel(); $this->rules = [ 'PatientID' => 'required|max_length[50]', 'AlternatePID' => 'permit_empty|max_length[50]', @@ -63,14 +64,6 @@ class Patient extends Controller { } } - private function validationError(string $context, array $errors) { - return $this->respond([ - 'status' => 'error', - 'message' => "Validation failed ({$context})", - 'errors' => $errors - ], 400); - } - public function update() { $input = $this->request->getJSON(true); if (!$this->validateData($input, $this->rules)) { return $this->validationError('patient', $this->validator->getErrors()); } @@ -154,4 +147,12 @@ class Patient extends Controller { } } + private function validationError(string $context, array $errors) { + return $this->respond([ + 'status' => 'error', + 'message' => "Validation failed ({$context})", + 'errors' => $errors + ], 400); + } + } \ No newline at end of file diff --git a/app/Models/Patient/PatAttModel.php b/app/Models/Patient/PatAttModel.php new file mode 100644 index 0000000..690a764 --- /dev/null +++ b/app/Models/Patient/PatAttModel.php @@ -0,0 +1,88 @@ +insertBatch($patatt); + } + + public function updatePatAtt(array $patatt, string $InternalPID) { + // Ambil daftar address aktif saat ini dari DB + $oldActive = $this->getActiveAddresses($InternalPID); + + // Normalisasi input baru (hapus duplikat & pastikan punya key 'Address') + $mapNew = []; + foreach ($patatt as $row) { + if (empty($row['Address'])) continue; + $mapNew[$row['Address']] = $row; // overwrite duplikat + } + + $newData = array_keys($mapNew); + + // Hitung data yang ditambah & dihapus + $added = array_diff($newData, $oldActive); // address baru + $removed = array_diff($oldActive, $newData); // address lama dihapus + + // Soft delete yang dihapus + if (!empty($removed)) { + $this->softDeleteByAddresses($InternalPID, $removed); + } + + // Tambahkan / re-activate yang baru + foreach ($added as $addr) { + $data = $mapNew[$addr]; + $data['InternalPID'] = $InternalPID; + + // Coba re-activate dulu + $this->reactivateAddress($InternalPID, $addr); + + // Cek apakah ada baris yang di-update (re-activate) + if ($this->db->affectedRows() === 0) { + // Tidak ada baris lama yang dihapus → insert baru + $this->insert($data); + } + } + } + + public function deletePatAtt(string $InternalPID) { + $this->where('InternalPID', $InternalPID)->delete(); + } + + // Dapatkan daftar alamat aktif berdasarkan InternalPID + public function getActiveAddresses(string $InternalPID): array { + $rows = $this->select('Address')->where('InternalPID', $InternalPID)->where('DelDate', null)->findAll(); + return array_column($rows, 'Address'); + } + // Soft delete beberapa address sekaligus + public function softDeleteByAddresses(string $InternalPID, array $addresses) { + if (empty($addresses)) return; + $this->where('InternalPID', $InternalPID)->whereIn('Address', $addresses)->delete(); + } + // Reactivate (hapus DelDate) 1 address jika pernah dihapus + // Return true jika berhasil re-activate + public function reactivateAddress(string $InternalPID, string $Address): bool { + return $this->where('InternalPID', $InternalPID)->where('Address', $Address)->where('DelDate IS NOT NULL', null, false)->set('DelDate', null)->orderBy('PatAttID', 'DESC')->limit(1)->update(); + } +} diff --git a/app/Models/Patient/PatComModel.php b/app/Models/Patient/PatComModel.php new file mode 100644 index 0000000..f3937e8 --- /dev/null +++ b/app/Models/Patient/PatComModel.php @@ -0,0 +1,44 @@ +insert(["InternalPID" => $newInternalPID, "Comment" => $patcom]); + } + + public function updatePatCom(string $patcom, string $InternalPID) { + $exists = $this->where('InternalPID', $InternalPID)->first(); + + if ($exists) { + //Update + $this->where('InternalPID', $InternalPID)->set('Comment', $patcom)->update(); + } else { + //Insert + $this->insert(['InternalPID' => $InternalPID, 'Comment' => $patcom]); + } + } + + public function deletePatCom(string $InternalPID) { + $this->where('InternalPID', $InternalPID)->delete(); + } + +} diff --git a/app/Models/Patient/PatIdtModel.php b/app/Models/Patient/PatIdtModel.php new file mode 100644 index 0000000..e6cd46f --- /dev/null +++ b/app/Models/Patient/PatIdtModel.php @@ -0,0 +1,51 @@ +insert(["InternalPID" => $newInternalPID, "IdentifierType" => $patidt['IdentifierType'], 'Identifier' => $patidt['Identifier']]); + } + + public function updatePatIdt($patidt, string $InternalPID) { + $exists = $this->where('InternalPID', $InternalPID)->first(); + // $exists = $db->table('patidt')->where('InternalPID', $InternalPID)->get()->getRowArray(); + + if ($exists) { + // Update + $this->where('InternalPID', $InternalPID)->set($patidt)->update(); + // $db->table('patidt')->where('InternalPID', $InternalPID)->update($patidt); + + } else { + // Insert + $patidt['InternalPID'] = $InternalPID; + $this->insert($patidt); + + // $patidt['InternalPID'] = $InternalPID; + // $db->table('patidt')->insert($patidt); + } + } + + public function deletePatIdt(string $InternalPID) { + $this->where('InternalPID', $InternalPID)->delete(); + } + +} diff --git a/app/Models/PatientModel.php b/app/Models/Patient/PatientModel.php similarity index 66% rename from app/Models/PatientModel.php rename to app/Models/Patient/PatientModel.php index af0cee8..57bd4f8 100644 --- a/app/Models/PatientModel.php +++ b/app/Models/Patient/PatientModel.php @@ -1,5 +1,12 @@ 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'); - if (!empty($patidt)) { - $patidt['InternalPID'] = $newInternalPID; - $db->table('patidt')->insert($patidt); - $this->checkDbError($db, 'Insert patidt'); + // Insert Data ke Tabel PatIdt + if (!empty($input['PatIdt'])) { + $this->modelPatIdt->createPatIdt($input['PatIdt'], $newInternalPID); + $this->checkDbError($db, 'Insert PatIdt'); } - if (!empty($patcom)) { - $patcom['InternalPID'] = $newInternalPID; - $db->table('patcom')->insert($patcom); - $this->checkDbError($db, 'Insert patcom'); + // Insert Data ke Tabel PatCom + if (!empty($input['PatCom'])) { + $this->modelPatCom->createPatCom($input['PatCom'], $newInternalPID); + $this->checkDbError($db, 'Insert PatCom'); } - - if (!empty($patatt)) { - foreach ($patatt as &$row) { - $row['InternalPID'] = $newInternalPID; - } - $db->table('patatt')->upsertBatch($patatt); - $this->checkDbError($db, 'Insert patatt'); + + // Insert Data ke Tabel PatAtt + if (!empty($input['PatAtt'])) { + $this->modelPatAtt->createPatAtt($input['PatAtt'], $newInternalPID); + $this->checkDbError($db, 'Insert PatAtt'); } $db->transCommit(); - // $db->transComplete(); - // if ($db->transStatus() === false) { - // throw new \Exception("Failed to sync patient relations"); - // } + return $newInternalPID; } catch (\Exception $e) { @@ -163,9 +174,10 @@ class PatientModel extends BaseModel { public function updatePatient($input) { $db = \Config\Database::connect(); - $patidt = $input['PatIdt'] ?? []; - $patatt = $input['PatAtt'] ?? []; - $patcom['Comment'] = $input['PatCom'] ?? null; + $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']); @@ -173,109 +185,41 @@ class PatientModel extends BaseModel { $db->transBegin(); try { + + // Update Patient $InternalPID = $input['InternalPID']; $this->where('InternalPID',$InternalPID)->set($input)->update(); $this->checkDbError($db, 'Update patient'); - $now = date('Y-m-d H:i:s'); - - if (!empty($patidt)) { - $exists = $db->table('patidt')->where('InternalPID', $InternalPID)->get()->getRowArray(); - if ($exists) { - $db->table('patidt')->where('InternalPID', $InternalPID)->update($patidt); - } else { - $patidt['InternalPID'] = $InternalPID; - $db->table('patidt')->insert($patidt); - $this->checkDbError($db, 'Update patidt'); - } + // Update Patidt + if (!empty($input['PatIdt'])) { + $this->modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID); + $this->checkDbError($db, 'Update patIdt'); } else { - $db->table('patidt')->where('InternalPID', $InternalPID)->delete(); + $this->modelPatIdt->deletePatIdt($InternalPID); $this->checkDbError($db, 'Update patidt'); } - if (!empty($patcom)) { - $exists = $db->table('patcom')->where('InternalPID', $InternalPID)->get()->getRowArray(); - if ($exists) { - $db->table('patcom')->where('InternalPID', $InternalPID)->update($patcom); - $this->checkDbError($db, 'Update patcom'); - } else { - $patcom['InternalPID'] = $InternalPID; - $db->table('patcom')->insert($patcom); - $this->checkDbError($db, 'Update patcom'); - } + // Update Patcom + if (!empty($input['PatCom'])) { + $this->modelPatCom->updatePatCom($input['PatCom'], $InternalPID); + $this->checkDbError($db, 'Update PatCom'); } else { - $db->table('patcom')->where('InternalPID', $InternalPID)->delete(); + $this->modelPatCom->deletePatCom($InternalPID); $this->checkDbError($db, 'Update patcom'); } - if (!empty($patatt)) { - // Ambil daftar address aktif (DelDate IS NULL) di DB - $oldActive = $db->table('patatt') - ->select('Address') - ->where('InternalPID', $InternalPID) - ->where('DelDate', null) - ->get()->getResultArray(); - $oldActive = array_column($oldActive, 'Address'); - - // Normalisasi & dedup input baru (berdasarkan Address) - $mapNew = []; - foreach ($patatt as $row) { - if (!isset($row['Address'])) continue; - $mapNew[$row['Address']] = $row; // overwrite duplikat di input - } - $newData = array_keys($mapNew); - - // Hitung yang perlu ditambah & dihapus - $added = array_diff($newData, $oldActive); // baru (belum aktif) - $removed = array_diff($oldActive, $newData); // dulu aktif tapi hilang di input - - - // 1) Soft delete yang dihapus - if (!empty($removed)) { - $db->table('patatt') - ->where('InternalPID', $InternalPID) - ->whereIn('Address', $removed) - ->set('DelDate', $now) - ->update(); - $this->checkDbError($db, 'Update/Delete patatt'); - } - - // 2) Tambahkan yang baru - foreach ($added as $addr) { - $data = $mapNew[$addr]; - $data['InternalPID'] = $InternalPID; - - // Coba REACTIVATE satu baris yang pernah di-soft delete (kalau ada) - $builder = $db->table('patatt'); - $builder->set('DelDate', null); - // Kalau ada kolom lain yang mau di-update saat re-activate, set di sini juga - // mis: $builder->set('Note', $data['Note'] ?? null); - - $builder->where('InternalPID', $InternalPID) - ->where('Address', $addr) - ->where('DelDate IS NOT NULL', null, false) - ->orderBy('PatAttID', 'DESC') - ->limit(1) - ->update(); - $this->checkDbError($db, 'Update/Insert patatt'); - - if ($db->affectedRows() === 0) { - // Tidak ada baris soft-deleted untuk alamat ini → INSERT baru - $db->table('patatt')->insert($data); - $this->checkDbError($db, 'Update/Insert patatt'); - } - } - + // Update Patatt + if (!empty($input['PatAtt'])) { + $this->modelPatAtt->updatePatAtt($input['PatAtt'], $InternalPID); + $this->checkDbError($db, 'Update PatAtt'); } else { - // Input kosong → semua yang masih aktif di-soft delete - $db->table('patatt')->where('InternalPID', $InternalPID)->where('DelDate', null)->set('DelDate', $now)->update(); - $this->checkDbError($db, 'Update/Delete patatt'); + $this->modelPatAtt->deletePatAtt($InternalPID); + $this->checkDbError($db, 'Update/Delete patatt'); } $db->transCommit(); - // if ($db->transStatus() === false) { - // throw new \Exception('Failed to sync patient relations'); - // } + return $InternalPID; } catch (\Exception $e) { diff --git a/tests/feature/Patients/PatientCreateTest.php b/tests/feature/Patients/PatientCreateTest.php index d9a6252..26ae8b4 100644 --- a/tests/feature/Patients/PatientCreateTest.php +++ b/tests/feature/Patients/PatientCreateTest.php @@ -50,7 +50,7 @@ class PatientCreateTest extends CIUnitTestCase $faker = Factory::create('id_ID'); - for ($i = 0; $i < 7; $i++) { + for ($i = 0; $i < 2; $i++) { $payload = [ "PatientID" => "DUM" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000), @@ -100,9 +100,8 @@ class PatientCreateTest extends CIUnitTestCase } $result = $this->withBodyFormat('json')->call('post', 'api/patient', $payload); - $result->assertStatus(201); - $result->assertJSONFragment(['status' => 'success']); + $result->assertStatus(201); } }