Major refactoring to clean up database migrations and reorganize static lookup data: - Consolidated 13 old migrations (2025) into 10 new numbered migrations (2026-01-01) - Deleted redundant migrations: Location, Users, Contact, ValueSet, Counter, RefRange, CRMOrganizations, Organization, AreaGeo, DeviceLogin, EdgeRes - New consolidated migrations: - 2026-01-01-000001_CreateLookups: valueset, counter, containerdef, occupation, specialty - 2026-01-01-000002_CreateOrganization: account, site, location, discipline, department - 2026-01-01-000003_CreatePatientCore: patient, patidentifier, pataddress, patcontact - 2026-01-01-000004_CreateSecurity: contact, contactdetail, userdevices, loginattempts - 2026-01-01-000005_CreatePatientVisits: patvisit, patinsurance - 2026-01-01-000006_CreateOrders: porder, orderitem - 2026-01-01-000007_CreateSpecimens: specimen, specmenactivity, containerdef - 2026-01-01-000008_CreateTestDefinitions: testdefinition, testactivity, refnum, reftxt - 2026-01-01-000009_CreateResults: patresult, patresultdetail, patresultcomment - 2026-01-01-000010_CreateLabInfrastructure: edgeres, edgestatus, edgeack, workstation - Moved 44 JSON files from valuesets/ subdirectory to app/Libraries/Data/ root - Added new country.json lookup - Added _meta.json for valueset metadata - Deleted old valuesets/_meta.json - Renamed gender.json to sex.json for consistency with patient.Sex column - Removed duplicate country.json from valuesets/ - AGENTS.md: Updated Lookups library documentation with new methods - README.md: Complete rewrite of lookup/valueset documentation - Renamed MVP_TODO.md to TODO.md - Added VUE_SPA_IMPLEMENTATION_PLAN.md - Removed deprecated prj_clinical laboratory quality management system_3a.docx - ValueSet.php: Enhanced with caching and new lookup methods - Lookups.php: Removed (functionality merged into ValueSet) Impact: Prepares codebase for 2026 with cleaner migration history and improved lookup data organization for the name-based valueset system.
6.7 KiB
6.7 KiB
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
# 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 withFeatureTestTrait - 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
namespace App\Controllers\Patient;
use CodeIgniter\Controller;
use CodeIgniter\API\ResponseTrait;
use App\Models\Patient\PatientModel;
- Use fully qualified class names or
usestatements - Group imports logically
- Avoid unnecessary aliases
Code Formatting
- Indentation: 4 spaces (not tabs)
- Braces: Allman style for classes/functions, K&R for control structures
- Line length: Soft limit 120 characters
- Empty lines: Single blank line between method definitions and logical groups
Type Hints and Return Types
// Required for new code
public function getPatient(int $internalPID): ?array
protected function createPatient(array $input): int
private function checkDbError(object $db, string $context): void
// Use nullable types for optional returns
public function findById(?int $id): ?array
Controller Patterns
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 = [...]; // Validation rules
}
public function index() {
try {
$data = $this->model->findAll();
return $this->respond([...], 200);
} catch (\Exception $e) {
return $this->failServerError($e->getMessage());
}
}
}
Model Patterns
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
\Exceptionwith descriptive messages - Database errors: Check
$db->error()after operations - Always validate input before DB operations
Validation Rules
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
BaseModelcallbacks - Use
utchelper 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
// 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
$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
BASEURLglobal variable - Include
credentials: 'include'for authenticated requests - Modals use
x-showwith@click.selfbackdrop 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/:
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
DelDatefield instead of hard delete - UTC timezone: All dates normalized to UTC automatically
- JWT auth: API endpoints require Bearer token in
Authorizationheader - No comments: Do not add comments unless explicitly requested