# CLQMS Backend - Agent Instructions **Project:** Clinical Laboratory Quality Management System (CLQMS) Backend **Framework:** CodeIgniter 4 (PHP 8.1+) **Platform:** Windows - Use PowerShell or CMD for terminal commands **Frontend:** Alpine.js (views/v2 directory) ## Build / Test Commands ```bash # Install dependencies composer install # Run all tests composer test php vendor/bin/phpunit # Run single test file php vendor/bin/phpunit tests/feature/Patients/PatientIndexTest.php # Run single test method php vendor/bin/phpunit tests/feature/Patients/PatientIndexTest.php --filter=testIndexWithoutParams # Run tests with coverage php vendor/bin/phpunit --coverage-html build/logs/html # Run tests in verbose mode php vendor/bin/phpunit --verbose ``` **Test Structure:** - Feature tests: `tests/feature/` - API endpoint testing with `FeatureTestTrait` - Unit tests: `tests/unit/` - Model/Logic testing - Base test case: `Tests\Support\v2\MasterTestCase.php` - Provides JWT auth and helper methods ## Code Style Guidelines ### PHP Standards - **PHP Version:** 8.1 minimum - **PSR-4 Autoloading:** Follow namespace-to-path conventions (`App\Controllers\*`, `App\Models\*`) - **Line endings:** Unix-style (LF) - configure editor accordingly ### Naming Conventions | Element | Convention | Examples | |---------|------------|----------| | Classes | PascalCase | `PatientController`, `BaseModel` | | Methods | camelCase | `getPatient()`, `createPatient()` | | Variables | camelCase | `$internalPID`, `$patientData` | | Constants | UPPER_SNAKE_CASE | `ORDER_PRIORITY`, `TEST_TYPE` | | Table names | snake_case | `patient`, `pat_idt`, `valueset` | | Column names | PascalCase (original DB) | `InternalPID`, `PatientID` | ### File Organization ``` app/ ├── Controllers/{Domain}/ │ └── DomainController.php ├── Models/{Domain}/ │ └── DomainModel.php ├── Libraries/ │ └── ValueSet.php # Base lookup class (loads from JSON) │ └── Lookups.php # Extends ValueSet - use this for lookups └── Views/v2/ ``` ### Imports and Namespaces ```php db = \Config\Database::connect(); $this->model = new PatientModel(); $this->rules = [...]; // Validation rules } public function index() { try { $data = $this->model->findAll(); return $this->respond([...], 200); } catch (\Exception $e) { return $this->failServerError($e->getMessage()); } } } ``` ### Model Patterns ```php class PatientModel extends BaseModel { protected $table = 'patient'; protected $primaryKey = 'InternalPID'; protected $allowedFields = [...]; protected $useSoftDeletes = true; protected $deletedField = 'DelDate'; public function getPatients(array $filters = []): array { // Query builder chain $this->select('...'); $this->join(...); if (!empty($filters['key'])) { $this->where('key', $filters['key']); } return $this->findAll(); } } ``` ### Error Handling - Controllers: Use try-catch with `failServerError()`, `failValidationErrors()`, `failNotFound()` - Models: Throw `\Exception` with descriptive messages - Database errors: Check `$db->error()` after operations - Always validate input before DB operations ### Validation Rules ```php protected $rules = [ 'PatientID' => 'required|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]', 'EmailAddress' => 'permit_empty|valid_email|max_length[100]', 'Phone' => 'permit_empty|regex_match[/^\+?[0-9]{8,15}$/]', ]; ``` ### Date Handling - All dates stored/retrieved in UTC via `BaseModel` callbacks - Use `utc` helper functions: `convert_array_to_utc()`, `convert_array_to_utc_iso()` - Format: ISO 8601 (`Y-m-d\TH:i:s\Z`) for API responses ### API Response Format ```php // Success return $this->respond([ 'status' => 'success', 'message' => 'Data fetched successfully', 'data' => $rows ], 200); // Created return $this->respondCreated([ 'status' => 'success', 'message' => 'Record created' ]); // Error return $this->failServerError('Something went wrong: ' . $e->getMessage()); ``` ### Database Transactions ```php $db->transBegin(); try { $this->insert($data); $this->checkDbError($db, 'Insert operation'); $db->transCommit(); return $insertId; } catch (\Exception $e) { $db->transRollback(); throw $e; } ``` ### Frontend Integration (Alpine.js) - API calls use `BASEURL` global variable - Include `credentials: 'include'` for authenticated requests - Modals use `x-show` with `@click.self` backdrop close ### Lookups Library Use `App\Libraries\Lookups` (extends `ValueSet`) for all static lookup values. Data is loaded from JSON files in `app/Libraries/Data/valuesets/`: ```php use App\Libraries\Lookups; // Get formatted for frontend dropdowns [{value: 'X', label: 'Y'}, ...] $gender = Lookups::get('gender'); $priorities = Lookups::get('order_priority'); // Get raw data [{key: 'X', value: 'Y'}, ...] $raw = Lookups::getRaw('gender'); // Get single label by key $label = Lookups::getLabel('gender', '1'); // Returns 'Female' // Get options with key/value pairs $options = Lookups::getOptions('gender'); // Returns: [['key' => '1', 'value' => 'Female'], ...] // Transform data with lookup labels $patients = Lookups::transformLabels($patients, [ 'Sex' => 'gender', 'Priority' => 'order_priority' ]); // Clear cache after data changes Lookups::clearCache(); ``` ### Important Notes - **Soft deletes:** Use `DelDate` field instead of hard delete - **UTC timezone:** All dates normalized to UTC automatically - **JWT auth:** API endpoints require Bearer token in `Authorization` header - **No comments:** Do not add comments unless explicitly requested