clqms-be/AGENTS.md
mahdahar 4aa9cefc3d refactor: consolidate migrations and reorganize valueset data structure
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.
2026-01-13 07:22:25 +07:00

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 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
namespace App\Controllers\Patient;

use CodeIgniter\Controller;
use CodeIgniter\API\ResponseTrait;
use App\Models\Patient\PatientModel;
  • Use fully qualified class names or use statements
  • 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 \Exception with 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 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

// 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 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/:

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