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]; unset($patient['Address']); unset($patient['IdentifierType']); unset($patient['Identifier']); unset($patient['Comment']); if (method_exists($this, 'transformPatientData')) { $patient = $this->transformPatientData($patient); } // Default nested structures $patient['PatIdt'] = null; $patient['PatAtt'] = []; $patient['PatCom'] = []; foreach ($rows as $row) { if ($row['IdentifierType'] && $row['Identifier'] && !$patient['PatIdt']) { $patient['PatIdt'] = [ 'IdentifierType' => $row['IdentifierType'], 'Identifier' => $row['Identifier'], ]; } if ($row['Comment']) { $patient['PatCom'][] = ['Comment' => $row['Comment']]; } 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(); $patidt = $input['PatIdt'] ?? []; $patatt = $input['PatAtt'] ?? []; $patcom = $input['PatCom'] ?? []; $input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo']; try { $db->transStart(); $this->insert($input); $newInternalPID = $this->getInsertID(); if (!empty($patidt)) { $patidt['InternalPID'] = $newInternalPID; $db->table('patidt')->insert($patidt); } if (!empty($patcom)) { $patcom['InternalPID'] = $newInternalPID; $db->table('patcom')->insert($patcom); } if (!empty($patatt)) { foreach ($patatt as &$row) { $row['InternalPID'] = $newInternalPID; } $db->table('patatt')->upsertBatch($patatt); } $db->transComplete(); if ($db->transStatus() === false) { $error = $db->error(); throw new \Exception('Transaction failed: ' . ($error['message'] ?? 'Unknown DB error')); } return $newInternalPID; } catch (\Exception $e) { $db->transRollback(); throw $e; } } public function updatePatient($input) { $db = \Config\Database::connect(); $patidt = $input['PatIdt'] ?? []; $patatt = $input['PatAtt'] ?? []; $patcom = $input['PatCom'] ?? []; $input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo']; try { $db->transStart(); $InternalPID = $input['InternalPID']; $this->where('InternalPID',$InternalPID)->set($input)->update(); $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); } } else { $db->table('patidt')->where('InternalPID', $InternalPID)->delete(); } if (!empty($patcom)) { $exists = $db->table('patcom')->where('InternalPID', $InternalPID)->get()->getRowArray(); if ($exists) { $db->table('patcom')->where('InternalPID', $InternalPID)->update($patcom); } else { $patcom['InternalPID'] = $InternalPID; $db->table('patcom')->insert($patcom); } } else { $db->table('patcom')->where('InternalPID', $InternalPID)->delete(); } 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(); } // 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(); if ($db->affectedRows() === 0) { // Tidak ada baris soft-deleted untuk alamat ini → INSERT baru $db->table('patatt')->insert($data); } } } else { // Input kosong → semua yang masih aktif di-soft delete $db->table('patatt')->where('InternalPID', $InternalPID)->where('DelDate', null)->set('DelDate', $now)->update(); } $db->transComplete(); if ($db->transStatus() === false) { throw new \Exception('Failed to sync patient relations'); } return $InternalPID; } catch (\Exception $e) { $db->transRollback(); throw $e; } } private function transformPatientData(array $patient): array { $patient["Age"] = $this->calculateAgeFromBirthdate($patient["Birthdate"], $patient["DeathDateTime"]); $patient["Birthdate"] = $this->formatedDate($patient["Birthdate"]); $patient["CreateDate"] = $this->formatedDate($patient["CreateDate"]); $patient["DelDate"] = $this->formatedDate($patient["DelDate"]); $patient["DeathDateTime"] = $this->formatedDate($patient["DeathDateTime"]); $patient["BirthdateConversion"] = $this->formatedDateForDisplay($patient["Birthdate"]); $patient["LinkTo"] = $this->getLinkedPatients($patient['LinkTo']); $patient["Custodian"] = $this->getCustodian($patient['Custodian']); 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) 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 H:i private function formatedDate($dateString) { $date = \DateTime::createFromFormat('Y-m-d H:i', $dateString); if (!$date) { $timestamp = strtotime($dateString); if ($timestamp) { return date('Y-m-d H:i', $timestamp); } return null; } return $date->format('Y-m-d H:i'); } // Conversion Time to Format j M Y 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'); } }