clqms-be/app/Controllers/Patient/PatientController.php

228 lines
9.6 KiB
PHP
Raw Normal View History

<?php
2025-10-16 10:28:40 +07:00
namespace App\Controllers\Patient;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Controller;
use App\Libraries\ValueSet;
2025-10-16 10:28:40 +07:00
use App\Models\Patient\PatientModel;
class PatientController extends Controller {
use ResponseTrait;
protected $db;
2025-10-13 11:08:26 +07:00
protected $model;
protected $rules;
public function __construct() {
$this->db = \Config\Database::connect();
2025-10-16 13:22:28 +07:00
$this->model = new PatientModel();
2025-10-13 11:08:26 +07:00
$this->rules = [
2025-11-24 14:11:02 +07:00
'PatientID' => 'required|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
'AlternatePID' => 'permit_empty|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
'Prefix' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|max_length[10]',
feat(valueset): refactor from ID-based to name-based lookups Complete overhaul of the valueset system to use human-readable names instead of numeric IDs for improved maintainability and API consistency. - PatientController: Renamed 'Gender' field to 'Sex' in validation rules - ValuesetController: Changed API endpoints from ID-based (/:num) to name-based (/:any) - TestsController: Refactored to use ValueSet library instead of direct valueset queries - Added ValueSet library (app/Libraries/ValueSet.php) with static lookup methods: - getOptions() - returns dropdown format [{value, label}] - getLabel(, ) - returns label for a value - transformLabels(, ) - batch transform records - get() and getRaw() for Lookups compatibility - Added ValueSetApiController for public valueset API endpoints - Added ValueSet refresh endpoint (POST /api/valueset/refresh) - Added DemoOrderController for testing order creation without auth - 2026-01-12-000001: Convert valueset references from VID to VValue - 2026-01-12-000002: Rename patient.Gender column to Sex - OrderTestController: Now uses OrderTestModel with proper model pattern - TestsController: Uses ValueSet library for all lookup operations - ValueSetController: Simplified to use name-based lookups - Updated all organization (account/site/workstation) dialogs and index views - Updated specimen container dialogs and index views - Updated tests_index.php with ValueSet integration - Updated patient dialog form and index views - Removed .factory/config.json and CLAUDE.md (replaced by AGENTS.md) - Consolidated lookups in Lookups.php (removed inline valueset constants) - Updated all test files to match new field names - 32 modified files, 17 new files, 2 deleted files - Net: +661 insertions, -1443 deletions (significant cleanup)
2026-01-12 16:53:41 +07:00
'Sex' => 'required',
2025-11-24 14:11:02 +07:00
'NameFirst' => 'required|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
'NameMiddle' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
'NameMaiden' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
'NameLast' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
'Suffix' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|max_length[10]',
'PlaceOfBirth' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|max_length[100]',
'Citizenship' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|max_length[100]',
'Street_1' => 'permit_empty|regex_match[/^[A-Za-z0-9\'.,\/\- ]+$/]|max_length[255]',
'Street_2' => 'permit_empty|regex_match[/^[A-Za-z0-9\'.,\/\- ]+$/]|max_length[255]',
'Street_3' => 'permit_empty|regex_match[/^[A-Za-z0-9\'.,\/\- ]+$/]|max_length[255]',
'EmailAddress1' => 'permit_empty|valid_email|max_length[100]',
'EmailAddress2' => 'permit_empty|valid_email|max_length[100]',
'Birthdate' => 'required',
'PatIdt.IdentifierType' => 'permit_empty',
'PatIdt.Identifier' => 'permit_empty|max_length[255]',
'ZIP' => 'permit_empty|is_natural|max_length[10]',
'Phone' => 'permit_empty|regex_match[/^\\+?[0-9]{8,15}$/]',
'MobilePhone' => 'permit_empty|regex_match[/^\\+?[0-9]{8,15}$/]'
];
}
public function index() {
$filters = [
'InternalPID' => $this->request->getVar('InternalPID'),
'PatientID' => $this->request->getVar('PatientID'),
'Name' => $this->request->getVar('Name'),
'Birthdate' => $this->request->getVar('Birthdate'),
];
2025-07-23 11:03:46 +07:00
try {
2025-10-13 11:08:26 +07:00
$rows = $this->model->getPatients($filters);
$rows = ValueSet::transformLabels($rows, [
'Sex' => 'sex',
]);
return $this->respond([ 'status' => 'success', 'message'=> "data fetched successfully", 'data' => $rows ], 200);
2025-07-23 11:03:46 +07:00
} catch (\Exception $e) {
return $this->failServerError('Exception : '.$e->getMessage());
2025-07-23 11:03:46 +07:00
}
}
2025-08-01 22:18:45 +07:00
public function show($InternalPID = null) {
2025-07-23 11:03:46 +07:00
try {
$row = $this->model->getPatient($InternalPID);
if (empty($row)) { return $this->respond([ 'status' => 'success', 'message' => "data not found.", 'data' => null ], 200); }
$row = ValueSet::transformLabels([$row], [
'Sex' => 'sex',
])[0];
return $this->respond([ 'status' => 'success', 'message' => "data fetched successfully", 'data' => $row ], 200);
2025-07-23 11:03:46 +07:00
} catch (\Exception $e) {
2025-09-08 15:56:38 +07:00
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
2025-07-23 11:03:46 +07:00
public function create() {
$input = $this->request->getJSON(true);
2025-11-24 14:11:02 +07:00
// Khusus untuk Override PATIDT
$type = $input['PatIdt']['IdentifierType'] ?? null;
$identifierRulesMap = [
'KTP' => 'required|regex_match[/^[0-9]{16}$/]', // 16 pas digit numeric
'PASS' => 'required|regex_match[/^[A-Za-z0-9]{1,9}$/]', // alphanumeric max 9
'SSN' => 'required|regex_match[/^[0-9]{9}$/]', // numeric, pas 9 digit
'SIM' => 'required|regex_match[/^[0-9]{19,20}$/]', // numeric 1920 digit
'KTAS' => 'required|regex_match[/^[0-9]{11}$/]', // numeric, pas 11 digit
2025-11-24 14:11:02 +07:00
];
2025-12-17 15:19:55 +07:00
if ($type === null || $type === '' || !is_string($type)) {
2025-11-24 14:11:02 +07:00
$identifierRule = 'permit_empty|max_length[255]';
$this->rules['PatIdt.IdentifierType'] = 'permit_empty';
$this->rules['PatIdt.Identifier'] = $identifierRule;
} else {
$identifierRule = $identifierRulesMap[$type] ?? 'permit_empty|max_length[255]';
$this->rules['PatIdt.IdentifierType'] = 'required';
$this->rules['PatIdt.Identifier'] = $identifierRule;
}
2025-10-16 13:22:28 +07:00
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); }
2025-07-23 11:03:46 +07:00
try {
2025-10-13 11:08:26 +07:00
$InternalPID = $this->model->createPatient($input);
2025-10-01 15:36:55 +07:00
return $this->respondCreated([ 'status' => 'success', 'message' => "data $InternalPID created successfully" ]);
2025-07-23 11:03:46 +07:00
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
2025-07-23 11:03:46 +07:00
}
}
public function update() {
2025-10-01 15:36:55 +07:00
$input = $this->request->getJSON(true);
2025-11-24 14:14:44 +07:00
// Khusus untuk Override PATIDT
$type = $input['PatIdt']['IdentifierType'] ?? null;
$identifierRulesMap = [
'KTP' => 'required|regex_match[/^[0-9]{16}$/]',
'PASS' => 'required|regex_match[/^[A-Za-z0-9]{6,9}$/]',
'SSN' => 'required|regex_match[/^[0-9]{3}-[0-9]{2}-[0-9]{4}$/]',
'SIM' => 'required|regex_match[/^[A-Za-z0-9]{12,14}$/]',
'KTAS' => 'required|regex_match[/^[A-Za-z0-9]{12,15}$/]',
];
2025-12-17 15:19:55 +07:00
if ($type === null || $type === '' || !is_string($type)) {
2025-11-24 14:14:44 +07:00
$identifierRule = 'permit_empty|max_length[255]';
$this->rules['PatIdt.IdentifierType'] = 'permit_empty';
$this->rules['PatIdt.Identifier'] = $identifierRule;
} else {
$identifierRule = $identifierRulesMap[$type] ?? 'permit_empty|max_length[255]';
$this->rules['PatIdt.IdentifierType'] = 'required';
$this->rules['PatIdt.Identifier'] = $identifierRule;
}
2025-10-16 13:22:28 +07:00
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); }
2025-07-23 11:03:46 +07:00
try {
2025-10-13 11:08:26 +07:00
$InternalPID = $this->model->updatePatient($input);
2025-10-01 15:36:55 +07:00
return $this->respondCreated([ 'status' => 'success', 'message' => "data $InternalPID update successfully" ]);
2025-07-23 11:03:46 +07:00
} catch (\Exception $e) {
2025-08-12 09:19:10 +07:00
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
2025-10-14 15:50:22 +07:00
public function delete() {
2025-07-23 11:03:46 +07:00
try {
$input = $this->request->getJSON(true);
$InternalPID = $input["InternalPID"];
2025-09-25 14:01:33 +07:00
// Mencegah Inputan 0, [], null, sql injection
if (empty($InternalPID) || !ctype_digit((string) $InternalPID)) {
return $this->respond([
'status' => 'error',
'message' => "Patient ID must be a valid integer."
], 400);
2025-07-23 11:03:46 +07:00
}
$patient = $this->db->table('patient')->where('InternalPID', $InternalPID)->get()->getRow();
2025-07-23 11:03:46 +07:00
if (!$patient) {
2025-08-01 22:18:45 +07:00
return $this->failNotFound("Patient ID with {$InternalPID} not found.");
2025-07-23 11:03:46 +07:00
}
2025-08-05 10:03:33 +07:00
$this->db->table('patient')->where('InternalPID', $InternalPID)->update(['DelDate' => date('Y-m-d H:i:s')]);
2025-07-23 11:03:46 +07:00
return $this->respondDeleted([
'status' => 'success',
2025-08-01 22:18:45 +07:00
'message' => "Patient ID with {$InternalPID} deleted successfully."
2025-07-23 11:03:46 +07:00
]);
} catch (\Exception $e) {
return $this->failServerError("Internal server error: " . $e->getMessage());
}
}
public function patientCheck() {
2025-08-14 09:17:15 +07:00
try {
$PatientID = $this->request->getVar('PatientID');
$EmailAddress1 = $this->request->getVar('EmailAddress1');
2025-12-17 15:19:55 +07:00
$tableName = '';
$searchName = '';
if (!empty($PatientID)){
$tableName = 'PatientID';
$searchName = $PatientID;
2025-12-17 15:19:55 +07:00
} elseif (!empty($EmailAddress1)){
$tableName = 'EmailAddress1';
$searchName = $EmailAddress1;
2025-12-17 15:19:55 +07:00
} else {
return $this->respond([
'status' => 'error',
'message' => 'PatientID or EmailAddress1 parameter is required.',
'data' => null
], 400);
2025-08-14 09:17:15 +07:00
}
$patient = $this->db->table('patient')
->where($tableName, $searchName)
2025-08-14 09:17:15 +07:00
->get()
2025-09-25 14:01:33 +07:00
->getRowArray();
2025-08-14 09:17:15 +07:00
if (!$patient) {
return $this->respond([
'status' => 'success',
'message' => "$tableName not found.",
2025-08-14 09:17:15 +07:00
'data' => true,
], 200);
}
return $this->respond([
'status' => 'success',
'message' => "$tableName already exists.",
2025-08-14 09:17:15 +07:00
'data' => false,
], 200);
} catch (\Exception $e) {
// Error Server Mengembalikan 500
return $this->failServerError('Something went wrong.'.$e->getMessage());
2025-08-14 09:17:15 +07:00
}
}
}