clqms-be/app/Controllers/Patient/PatientController.php
mahdahar bb7df6b70c 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

218 lines
9.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controllers\Patient;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Controller;
use App\Models\Patient\PatientModel;
class PatientController extends Controller {
use ResponseTrait;
protected $db;
protected $model;
protected $rules;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new PatientModel();
$this->rules = [
'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]',
'Sex' => 'required',
'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'),
];
try {
$rows = $this->model->getPatients($filters);
return $this->respond([ 'status' => 'success', 'message'=> "data fetched successfully", 'data' => $rows ], 200);
} catch (\Exception $e) {
return $this->failServerError('Exception : '.$e->getMessage());
}
}
public function show($InternalPID = null) {
try {
$row = $this->model->getPatient($InternalPID);
if (empty($row)) { return $this->respond([ 'status' => 'success', 'message' => "data not found.", 'data' => null ], 200); }
return $this->respond([ 'status' => 'success', 'message' => "data fetched successfully", 'data' => $row ], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
// 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
];
if ($type === null || $type === '' || !is_string($type)) {
$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;
}
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); }
try {
$InternalPID = $this->model->createPatient($input);
return $this->respondCreated([ 'status' => 'success', 'message' => "data $InternalPID created successfully" ]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update() {
$input = $this->request->getJSON(true);
// 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}$/]',
];
if ($type === null || $type === '' || !is_string($type)) {
$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;
}
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); }
try {
$InternalPID = $this->model->updatePatient($input);
return $this->respondCreated([ 'status' => 'success', 'message' => "data $InternalPID update successfully" ]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$InternalPID = $input["InternalPID"];
// 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);
}
$patient = $this->db->table('patient')->where('InternalPID', $InternalPID)->get()->getRow();
if (!$patient) {
return $this->failNotFound("Patient ID with {$InternalPID} not found.");
}
$this->db->table('patient')->where('InternalPID', $InternalPID)->update(['DelDate' => date('Y-m-d H:i:s')]);
return $this->respondDeleted([
'status' => 'success',
'message' => "Patient ID with {$InternalPID} deleted successfully."
]);
} catch (\Exception $e) {
return $this->failServerError("Internal server error: " . $e->getMessage());
}
}
public function patientCheck() {
try {
$PatientID = $this->request->getVar('PatientID');
$EmailAddress1 = $this->request->getVar('EmailAddress1');
$tableName = '';
$searchName = '';
if (!empty($PatientID)){
$tableName = 'PatientID';
$searchName = $PatientID;
} elseif (!empty($EmailAddress1)){
$tableName = 'EmailAddress1';
$searchName = $EmailAddress1;
} else {
return $this->respond([
'status' => 'error',
'message' => 'PatientID or EmailAddress1 parameter is required.',
'data' => null
], 400);
}
$patient = $this->db->table('patient')
->where($tableName, $searchName)
->get()
->getRowArray();
if (!$patient) {
return $this->respond([
'status' => 'success',
'message' => "$tableName not found.",
'data' => true,
], 200);
}
return $this->respond([
'status' => 'success',
'message' => "$tableName already exists.",
'data' => false,
], 200);
} catch (\Exception $e) {
// Error Server Mengembalikan 500
return $this->failServerError('Something went wrong.'.$e->getMessage());
}
}
}