clqms-be/AGENTS.md
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

222 lines
6.1 KiB
Markdown

# 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/
│ └── Lookups.php
└── Views/v2/
```
### Imports and Namespaces
```php
<?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
```php
// 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
```php
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
```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` for all static lookup values - no database queries:
```php
use App\Libraries\Lookups;
// Frontend dropdown format
Lookups::get('gender'); // [{value: '1', label: 'Female'}, ...]
Lookups::get('test_type'); // [{value: 'TEST', label: 'Test'}, ...]
// Raw key-value pairs
Lookups::getRaw('gender'); // ['1' => 'Female', ...]
// All lookups
Lookups::getAll();
```
### 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