Update site controller, organization & test models, migrations, and API docs

This commit is contained in:
mahdahar 2026-03-10 16:40:37 +07:00
parent 011a2456c2
commit ad8e1cc977
23 changed files with 698 additions and 774 deletions

View File

@ -0,0 +1,97 @@
# CLQMS Code Conventions
## PHP Standards
- **Version**: PHP 8.1+
- **Autoloading**: PSR-4
- **Coding Style**: PSR-12 (where applicable)
## Naming Conventions
| Element | Convention | Example |
|---------|-----------|---------|
| Classes | PascalCase | `PatientController` |
| Methods | camelCase | `createPatient()` |
| Properties | snake_case (legacy) / camelCase (new) | `$patient_id` / `$patientId` |
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
| Database Tables | snake_case | `patient_visits` |
| Database Columns | PascalCase (legacy) | `PatientID`, `NameFirst` |
| JSON Fields | PascalCase | `"PatientID": "123"` |
## Imports & Namespaces
- Fully qualified namespaces at top of file
- Group imports: Framework first, then App, then external
- Alphabetical order within groups
```php
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\ResponseInterface;
use App\Traits\ResponseTrait;
use Firebase\JWT\JWT;
```
## Controller Pattern
Controllers handle HTTP requests, delegate business logic to Models (no DB queries in controllers).
```php
class ExampleController extends Controller
{
use ResponseTrait;
protected $model;
public function __construct()
{
$this->model = new \App\Models\ExampleModel();
}
}
```
## Response Format
All API responses use standardized format:
```php
// Success
return $this->respond([
'status' => 'success',
'message' => 'Operation completed',
'data' => $data
], 200);
// Error
return $this->respond([
'status' => 'failed',
'message' => 'Error description',
'data' => []
], 400);
```
## Database Operations
- Use CodeIgniter Query Builder or Model methods
- Use `helper('utc')` for UTC date conversion
- Wrap multi-table operations in transactions
```php
$this->db->transStart();
// ... operations
$this->db->transComplete();
if ($this->db->transStatus() === false) {
return $this->respond(['status' => 'error', ...], 500);
}
```
## Test Naming Convention
Format: `test<Action><Scenario><ExpectedResult>`
Examples: `testCreatePatientValidationFail`, `testUpdatePatientSuccess`
## HTTP Status Codes
- 200: GET/PATCH success
- 201: POST success
- 400: Validation error
- 401: Unauthorized
- 404: Not found
- 500: Server error
## Legacy Field Naming
Database uses PascalCase: `PatientID`, `NameFirst`, `Birthdate`, `CreatedAt`

View File

@ -1,55 +1,54 @@
# CLQMS Project Overview
## Project Purpose
CLQMS (Clinical Laboratory Quality Management System) is a headless REST API backend for clinical laboratory workflows. It provides comprehensive JSON endpoints for:
- Patient management
- Order/test management
- Specimen tracking
- Result management and verification
- Reference ranges
- Laboratory instrument integration (Edge API)
CLQMS (Clinical Laboratory Quality Management System) is a **headless REST API backend** designed for modern clinical laboratory workflows. This API-only system provides comprehensive JSON endpoints for laboratory operations without any view layer. Frontend applications (web, mobile, desktop) consume these REST endpoints to build laboratory information systems.
## Tech Stack
- **Language**: PHP 8.1+
- **Framework**: CodeIgniter 4 (API-only mode)
- **Database**: MySQL with MySQLi driver
- **Authentication**: JWT (JSON Web Tokens)
- **Testing**: PHPUnit 10.5+
- **Documentation**: OpenAPI/Swagger YAML
### Core Features
- Patient registration and management
- Laboratory test ordering and tracking
- Specimen lifecycle management (collection → transport → reception → prep → analysis)
- Result entry with reference range validation
- Multi-level result verification (Technical → Clinical → Reporting)
- Instrument integration via Edge API (tiny-edge middleware)
- Master data management (value sets, test definitions, reference ranges)
- Quality control and calibration tracking
## Architecture
- **API-First**: No view layer, headless REST API only
- **Stateless**: JWT-based authentication per request
- **UTC Dates**: All dates stored in UTC, converted for display
- **PSR-4 Autoloading**: `App\``app/`, `Config\``app/Config/`
### Product Vision
To provide a headless, API-first laboratory information system serving as the backend for any frontend client while streamlining laboratory operations, ensuring regulatory compliance, and integrating seamlessly with laboratory instruments.
## Key Directories
```
app/
Controllers/ # API endpoint handlers
Models/ # Database models
Libraries/ # Helper classes (Lookups, ValueSet)
Database/
Migrations/ # Schema migrations
Seeds/ # Test data seeders
Helpers/ # json_helper.php, utc_helper.php
Traits/ # ResponseTrait
Config/ # Configuration files
Filters/ # AuthFilter, CORS
## Technology Stack
public/ # Web root
paths/ # OpenAPI path definitions
components/schemas/ # OpenAPI schemas
| Component | Specification |
|-----------|---------------|
| **Language** | PHP 8.1+ (PSR-compliant) |
| **Framework** | CodeIgniter 4 (API-only mode) |
| **Database** | MySQL 8.0+ |
| **Security** | JWT (JSON Web Tokens) via firebase/php-jwt |
| **Testing** | PHPUnit 10.5+ |
| **API Format** | RESTful JSON |
| **Architecture** | Clean architecture, API-first design |
tests/
feature/ # Feature/integration tests
unit/ # Unit tests
_support/ # Test support files
```
### Key Dependencies
- `codeigniter4/framework` - Main framework
## Key Dependencies
- `codeigniter4/framework` - Core framework
- `firebase/php-jwt` - JWT authentication
- `fakerphp/faker` - Test data generation
- `phpunit/phpunit` - Unit/feature testing
## Strategic Pillars
- **Precision & Accuracy**: Strict validation for all laboratory parameters and reference ranges
- **Scalability**: Optimized for high-volume diagnostic environments
- **Compliance**: Built-in audit trails and status history for full traceability
- **Interoperability**: Modular architecture designed for LIS, HIS, and analyzer integrations
## Key Characteristics
- **API-Only**: No view layer, no server-side rendering
- **Frontend Agnostic**: Any client can consume these APIs
- **JSON-First**: All requests/responses use JSON format
- **Stateless**: Each API request is independent with JWT authentication
- **UTC Normalization**: Automatic UTC timestamp handling via BaseModel
## Current Architecture Status
The system is undergoing an **Architectural Redesign** to consolidate legacy structures into a high-performance, maintainable schema focusing on:
- Unified Test Definitions (consolidating technical, calculated, and site-specific test data)
- Reference Range Centralization (unified engine for numeric, threshold, text, and coded results)
- Ordered Workflow Management (precise tracking of orders from collection to verification)
- `fakerphp/faker` - Test data generation (dev)
- `phpunit/phpunit` - Testing (dev)

View File

@ -1,393 +1,100 @@
# CLQMS Suggested Commands
## Database Commands
### Migrations
```bash
# Run all pending migrations
php spark migrate
# Run specific migration
php spark migrate [migration_file]
# Rollback last batch of migrations
php spark migrate:rollback
# Rollback and re-run migrations
php spark migrate:refresh
# Refresh and seed database
php spark migrate:refresh --seed
# Check migration status
php spark migrate:status
```
### Database Seeding
```bash
# Run all seeders
php spark db:seed
# Run specific seeder
php spark db:seed DBSeeder
# Create new database
php spark db:create
```
### Database Query
```bash
# Get table information
php spark db:table [table_name]
```
## Testing Commands
### PHPUnit Tests
## Testing
```bash
# Run all tests
vendor/bin/phpunit
./vendor/bin/phpunit
# Run specific test file (IMPORTANT for debugging)
vendor/bin/phpunit tests/feature/UniformShowTest.php
vendor/bin/phpunit tests/feature/Patient/PatientCreateTest.php
# Run tests in specific directory
vendor/bin/phpunit tests/feature/Patient/
# Run tests with coverage (text output)
vendor/bin/phpunit --coverage-text=tests/coverage.txt -d memory_limit=1024m
# Run tests with coverage (HTML report)
vendor/bin/phpunit --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m
# Run with verbose output
vendor/bin/phpunit --verbose
# Run specific test file
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run specific test method
vendor/bin/phpunit --filter testCanCreatePatient
```
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
## Code Generation Commands
# Run tests with coverage
./vendor/bin/phpunit --coverage-html build/logs/html
### Generate Code Files
```bash
# Generate new model
php spark make:model ModelName
# Run tests by suite
./vendor/bin/phpunit --testsuite App
# Generate new controller
php spark make:controller ControllerName
# Generate new migration
php spark make:migration MigrationName
# Generate new seeder
php spark make:seeder SeederName
# Generate new test
php spark make:test TestName
# Generate scaffold (complete set)
php spark make:scaffold ModelName
# Run via composer
composer test
```
## Development Server
### Start Development Server
```bash
# Start CodeIgniter dev server (default port 8080)
# Start PHP development server
php spark serve
# Start on specific port
php spark serve --port=8080
# Start with specific host
php spark serve --host=0.0.0.0
# Or specify port
php spark serve --port 8080
```
## Cache Management
### Clear Caches
## Database
```bash
# Clear application cache
php spark cache:clear
# Run migrations
php spark migrate
# Show cache information
php spark cache:info
# Rollback migrations
php spark migrate:rollback
# Clear debug bar JSON files
php spark debugbar:clear
# Create new migration
php spark make:migration CreateUsersTable
# Clear log files
php spark logs:clear
# Run database seeds
php spark db:seed DBSeeder
php spark db:seed PatientSeeder
```
## System Utilities
### Configuration & Environment
## Code Generation (Scaffolding)
```bash
# Get current environment
php spark env
# Create controller
php spark make:controller Users
# Set environment
php spark env development
# Create model
php spark make:model UserModel
# Check configuration values
php spark config:check
# Create migration
php spark make:migration CreateUsersTable
# Check php.ini values (for production)
php spark phpini:check
# Verify namespaces
php spark namespaces
# Create seeder
php spark make:seeder UserSeeder
```
### Routes
## API Documentation
```bash
# Display all routes
php spark routes
# After updating YAML files, regenerate bundled docs
node public/bundle-api-docs.js
# Check filters for a route
php spark filter:check
# Produces: public/api-docs.bundled.yaml
```
### Optimization
## Utilities (Windows)
```bash
# Optimize for production
php spark optimize
```
## Utility Commands
### Encryption
```bash
# Generate new encryption key (writes to .env)
php spark key:generate
```
### Publish
```bash
# Publish predefined resources
php spark publish
```
## General Utility Commands (Windows)
### File & Directory Operations
```bash
# List files in current directory
# List files
dir
# List files with details
dir /a
# Search in files (PowerShell)
Select-String -Path "app\*.php" -Pattern "PatientModel"
# Change directory
cd path\to\directory
# Or using git bash (if available)
grep -r "PatientModel" app/
# Go to parent directory
cd ..
# Create directory
mkdir directory_name
# Remove directory (empty)
rmdir directory_name
# Remove directory with contents
rmdir /s /q directory_name
# Copy file
copy source_file destination
# Move/Rename file
move source destination
# Delete file
del filename
# Search for file
dir /s filename
# Clear writable cache
del /q writable\cache\*
```
### File Content Operations
## Git Commands
```bash
# Display file content
type filename
# Display file content page by page
more filename
# Search for text in file
findstr "pattern" filename
# Search recursively
findstr /s /i "pattern" *.php
```
### Git Commands
```bash
# Check git status
# Check status
git status
# View changes
git diff
# View staged changes
git diff --staged
# Add files to staging
# Add files
git add .
# Add specific file
git add path/to/file
# Commit (only when explicitly asked)
git commit -m "message"
# Commit changes
git commit -m "commit message"
# Push to remote
git push
# Pull from remote
git pull
# View commit history
git log
# View commit history with graph
git log --graph --oneline
# Create new branch
git checkout -b branch_name
# Switch branch
git checkout branch_name
# Merge branch
git merge branch_name
# View branches
git branch
# View recent commits
git log --oneline -10
```
### Process Management
```bash
# List running processes
tasklist
# Kill process by PID
taskkill /PID pid_number
# Kill process by name
taskkill /IM process_name.exe
# Kill process forcefully
taskkill /F /PID pid_number
```
### Network Operations
```bash
# Check port usage
netstat -an | findstr :port
# Test network connectivity
ping hostname
# View active connections
netstat -an
```
## After Task Completion Checklist
When completing a task, run the following commands to ensure code quality:
### 1. Run Tests
```bash
# Run all tests
vendor/bin/phpunit
# If tests fail, run specific test file for debugging
vendor/bin/phpunit tests/feature/[SpecificTestFile].php
```
### 2. Check for Linting Issues (if configured)
```bash
# Check for PHP syntax errors
php -l app/Controllers/YourController.php
php -l app/Models/YourModel.php
# Run any custom linting tools if configured in composer.json
composer test # if 'test' script includes linting
```
### 3. Type Checking (if configured)
```bash
# Run static analysis tools if configured
vendor/bin/phpstan analyse # if phpstan is installed
vendor/bin/psalm # if psalm is installed
```
### 4. Database Verification
```bash
# Check migration status
php spark migrate:status
# If you created a migration, run it
php spark migrate
```
### 5. API Documentation Update (CRITICAL)
```bash
# After modifying ANY controller, MUST update api-docs.yaml
# This is a manual process - edit public/api-docs.yaml
# Verify YAML syntax (optional, if yamllint is available)
yamllint public/api-docs.yaml
```
### 6. Code Review Checklist
- [ ] All tests passing
- [ ] No PHP syntax errors
- [ ] Database migrations applied (if any)
- [ ] API documentation updated (`public/api-docs.yaml`)
- [ ] Code follows style conventions (see `code_style_conventions.md`)
- [ ] Proper error handling in place
- [ ] Input validation implemented
- [ ] JWT authentication required where needed
- [ ] UTC date handling via BaseModel
- [ ] Soft delete using `DelDate` where applicable
- [ ] ValueSet lookups properly used and cached cleared if modified
### 7. Verify API Endpoints (if applicable)
```bash
# If you have curl or a REST client, test the endpoints
# Example using curl:
curl -X GET http://localhost:8080/api/patient -H "Cookie: token=your_jwt_token"
```
## Common Workflows
### Creating a New API Endpoint
1. Create controller: `php spark make:controller ControllerName`
2. Create model: `php spark make:model ModelName`
3. Create migration (if new table): `php spark make:migration MigrationName`
4. Run migration: `php spark migrate`
5. Add routes in `app/Config/Routes.php`
6. Implement controller methods following pattern
7. Create tests: `php spark make:test Feature/EndpointNameTest`
8. Run tests: `vendor/bin/phpunit tests/feature/EndpointNameTest.php`
9. Update `public/api-docs.yaml`
10. Verify with test client
### Debugging a Failing Test
1. Run specific test file: `vendor/bin/phpunit tests/feature/SpecificTest.php`
2. Add debug output: `var_dump($result); die();` temporarily
3. Check database state: View with database client
4. Check logs: `writable/logs/` directory
5. Run with verbose: `vendor/bin/phpunit --verbose`
6. Isolate specific test: `vendor/bin/phpunit --filter testMethodName`
### Working with ValueSets
1. Create/edit JSON file: `app/Libraries/Data/valuesets/{name}.json`
2. Clear cache: `ValueSet::clearCache();` (in code) or via code
3. Verify via API: GET `/api/valueset/{name}`
4. Test lookup: `ValueSet::get('name')` in controller/model

View File

@ -1,129 +1,67 @@
# CLQMS Task Completion Guidelines
# CLQMS Task Completion Checklist
## When a Task is Completed
### 1. Run Tests
Always run relevant tests after making changes:
When completing a task, ensure:
## 1. Tests Pass
```bash
# Run all tests
./vendor/bin/phpunit
# Run specific test file
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run specific test method
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
```
- All existing tests must pass
- Add new tests for new features
- Test naming: `test<Action><Scenario><Result>`
### 2. Verify Code Quality
## 2. API Documentation Updated (CRITICAL)
When updating ANY controller, update corresponding OpenAPI YAML:
#### Check for Syntax Errors
| Controller | YAML Path File | YAML Schema File |
|-----------|----------------|------------------|
| `PatientController` | `paths/patients.yaml` | `components/schemas/patient.yaml` |
| `PatVisitController` | `paths/patient-visits.yaml` | `components/schemas/patient-visit.yaml` |
| `OrderTestController` | `paths/orders.yaml` | `components/schemas/orders.yaml` |
| `SpecimenController` | `paths/specimen.yaml` | `components/schemas/specimen.yaml` |
| `TestsController` | `paths/tests.yaml` | `components/schemas/tests.yaml` |
| `AuthController` | `paths/authentication.yaml` | `components/schemas/authentication.yaml` |
| `ResultController` | `paths/results.yaml` | `components/schemas/*.yaml` |
| `EdgeController` | `paths/edge-api.yaml` | `components/schemas/edge-api.yaml` |
| `LocationController` | `paths/locations.yaml` | `components/schemas/master-data.yaml` |
| `ValueSetController` | `paths/valuesets.yaml` | `components/schemas/valuesets.yaml` |
| `ContactController` | `paths/contact.yaml` | (inline schemas) |
After updating YAML files:
```bash
php -l app/Controllers/Patient/PatientController.php
node public/bundle-api-docs.js
```
#### Check Test Results
Ensure:
- All existing tests still pass
- New tests (if any) pass
- No unexpected warnings or errors
### 3. Code Review Checklist
#### Controller Changes
- [ ] Validation rules are properly defined
- [ ] Error handling uses try-catch blocks
- [ ] Responses use standardized format with `ResponseTrait`
- [ ] Authentication filter applied where needed
- [ ] Input sanitization via validation rules
- [ ] Database operations wrapped in transactions (if multi-table)
#### Model Changes
- [ ] Extends `BaseModel` for UTC handling
- [ ] Table name, primary key, allowed fields defined
- [ ] Uses `checkDbError()` for error detection
- [ ] Audit logging via `AuditService::logData()` for data changes
- [ ] Soft delete fields configured if using soft deletes
- [ ] Nested data properly handled (extracted before filtering)
#### New Routes
- [ ] Added to `app/Config/Routes.php`
- [ ] Follows REST conventions (GET, POST, PATCH, DELETE)
- [ ] Grouped appropriately under `/api/`
- [ ] Auth filter applied if needed
#### New Tests
- [ ] Test name follows `test<Action><Scenario><ExpectedResult>` pattern
- [ ] Uses `FeatureTestTrait`
- [ ] Uses `Factory::create('id_ID')` for Indonesian test data
- [ ] Asserts correct status codes (200, 201, 400, 401, 404, 500)
- [ ] Tests both success and failure scenarios
### 4. Common Issues to Check
#### Database Operations
- Multi-table operations should use transactions
- Use parameterized queries (Query Builder handles this)
- Check for empty/null arrays before processing
#### Nested Data
- Extract nested arrays before filtering/processing
- Handle empty/null nested data appropriately
## 3. Code Quality Checks
- PSR-12 compliance where applicable
- No database queries in controllers
- Use transactions for multi-table operations
- Proper error handling with try-catch for JWT/external calls
- Log errors: `log_message('error', $message)`
#### Date Handling
- All dates stored in UTC
- Use `helper('utc')` for conversions
- BaseModel extends with automatic UTC conversion
#### Security
- Input validation rules defined
- SQL injection prevention via parameterized queries
- JWT validation on protected endpoints
- No sensitive data logged or exposed
### 5. What NOT to Do
- **Do NOT commit** unless explicitly asked by the user
- **Do NOT push** to remote repository unless asked
- **Do NOT** skip running tests
- **Do NOT** add comments unless specifically requested
- **Do NOT** modify `.env` (database credentials, secrets)
- **Do NOT** include hardcoded secrets in code
### 6. Common Test Status Codes Reference
| Code | Usage | Example |
|------|-------|---------|
| 200 | GET/PATCH success | `->assertStatus(200)` |
| 201 | POST success (created) | `->assertStatus(201)` |
| 400 | Validation error | `->assertStatus(400)` |
| 401 | Unauthorized | `->assertStatus(401)` |
| 404 | Not found | `->assertStatus(404)` |
| 500 | Server error | `->assertStatus(500)` |
### 7. After Completing Tasks
Simply inform the user the task is complete. For example:
```
Task completed. The patient create endpoint now validates the identifier type dynamically.
## 4. Response Format Verification
Ensure all responses follow the standard format:
```php
return $this->respond([
'status' => 'success|failed',
'message' => 'Description',
'data' => $data
], $httpStatus);
```
Or if tests were run:
```
Task completed. All tests passing:
- testCreatePatientSuccess ✓
- testCreatePatientValidationFail ✓
```
## 5. Security Checklist
- Use `auth` filter for protected routes
- Sanitize user inputs
- Use parameterized queries
- No secrets committed to repo (use .env)
### 8. When Something Goes Wrong
## 6. Naming Conventions
- Classes: PascalCase
- Methods: camelCase
- Properties: snake_case (legacy) / camelCase (new)
- Database columns: PascalCase (legacy convention)
If tests fail or errors occur:
1. Check the error message carefully
2. Review the code against the patterns in this guide
3. Check database connection and configuration
4. Verify all required dependencies are installed
5. Review log files in `writable/logs/`
If unable to resolve, inform the user with details of the issue.
## 7. Do NOT Commit Unless Explicitly Asked
- Check status: `git status`
- Never commit .env files
- Never commit secrets

View File

@ -125,3 +125,8 @@ language_backend:
# list of regex patterns which, when matched, mark a memory entry as readonly.
# Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: []
# line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending:

View File

@ -56,6 +56,17 @@ class SiteController extends BaseController {
public function create() {
$input = $this->request->getJSON(true);
$validation = service('validation');
$validation->setRules([
'SiteCode' => 'required|regex_match[/^[A-Z0-9]{2}$/]',
'SiteName' => 'required',
]);
if (!$validation->run($input)) {
return $this->failValidationErrors($validation->getErrors());
}
try {
$id = $this->model->insert($input,true);
return $this->respondCreated([ 'status' => 'success', 'message' => 'data created successfully', 'data' => $id ], 201);
@ -66,9 +77,22 @@ class SiteController extends BaseController {
public function update() {
$input = $this->request->getJSON(true);
try {
$id = $input['SiteID'];
if (!$id) { return $this->failValidationErrors('ID is required.'); }
if (!empty($input['SiteCode'])) {
$validation = service('validation');
$validation->setRules([
'SiteCode' => 'regex_match[/^[A-Z0-9]{2}$/]',
]);
if (!$validation->run($input)) {
return $this->failValidationErrors($validation->getErrors());
}
}
try {
$this->model->update($id, $input);
return $this->respondCreated([ 'status' => 'success', 'message' => 'data updated successfully', 'data' => $id ], 201);
} catch (\Throwable $e) {

View File

@ -11,7 +11,6 @@ class TestsController extends BaseController
{
use ResponseTrait;
protected $db;
protected $model;
protected $modelCal;
protected $modelGrp;
@ -23,7 +22,6 @@ class TestsController extends BaseController
public function __construct()
{
$this->db = \Config\Database::connect();
$this->model = new \App\Models\Test\TestDefSiteModel;
$this->modelCal = new \App\Models\Test\TestDefCalModel;
$this->modelGrp = new \App\Models\Test\TestDefGrpModel;
@ -81,9 +79,7 @@ class TestsController extends BaseController
return $this->failValidationErrors('TestSiteID is required');
}
$row = $this->model->select('testdefsite.*')
->where('testdefsite.TestSiteID', $id)
->find($id);
$row = $this->model->getTestById($id);
if (!$row) {
return $this->respond([
@ -93,25 +89,14 @@ class TestsController extends BaseController
], 200);
}
$row = ValueSet::transformLabels([$row], [
'TestType' => 'test_type',
])[0];
$typeCode = $row['TestType'] ?? '';
if ($typeCode === 'CALC') {
$row['testdefcal'] = $this->modelCal->getByTestSiteID($id);
} elseif ($typeCode === 'GROUP') {
$row['testdefgrp'] = $this->modelGrp->getGroupMembers($id);
} elseif ($typeCode === 'TITLE') {
} else {
$row['testdeftech'] = $this->db->table('testdefsite')
->select('testdefsite.*, d.DisciplineName, dept.DepartmentName')
->join('discipline d', 'd.DisciplineID=testdefsite.DisciplineID', 'left')
->join('department dept', 'dept.DepartmentID=testdefsite.DepartmentID', 'left')
->where('testdefsite.TestSiteID', $id)
->where('testdefsite.EndDate IS NULL')
->get()->getResultArray();
} elseif ($typeCode !== 'TITLE') {
$row['testdeftech'] = $this->model->getTestTechWithRelations($id);
if (!empty($row['testdeftech'])) {
$techData = $row['testdeftech'][0];
@ -119,47 +104,11 @@ class TestsController extends BaseController
$resultType = $techData['ResultType'] ?? '';
if (TestValidationService::usesRefNum($resultType, $refType)) {
$refnumData = $this->modelRefNum->getActiveByTestSiteID($id);
$row['refnum'] = array_map(function ($r) {
return [
'RefNumID' => $r['RefNumID'],
'NumRefType' => $r['NumRefType'],
'NumRefTypeLabel' => $r['NumRefType'] ? ValueSet::getLabel('numeric_ref_type', $r['NumRefType']) : '',
'RangeType' => $r['RangeType'],
'RangeTypeLabel' => $r['RangeType'] ? ValueSet::getLabel('range_type', $r['RangeType']) : '',
'Sex' => $r['Sex'],
'SexLabel' => $r['Sex'] ? ValueSet::getLabel('gender', $r['Sex']) : '',
'LowSign' => $r['LowSign'],
'LowSignLabel' => $r['LowSign'] ? ValueSet::getLabel('math_sign', $r['LowSign']) : '',
'HighSign' => $r['HighSign'],
'HighSignLabel' => $r['HighSign'] ? ValueSet::getLabel('math_sign', $r['HighSign']) : '',
'High' => $r['High'] !== null ? (float) $r['High'] : null,
'Low' => $r['Low'] !== null ? (float) $r['Low'] : null,
'AgeStart' => (int) $r['AgeStart'],
'AgeEnd' => (int) $r['AgeEnd'],
'Flag' => $r['Flag'],
'Interpretation' => $r['Interpretation'],
];
}, $refnumData ?? []);
$row['refnum'] = $this->modelRefNum->getFormattedByTestSiteID($id);
}
if (TestValidationService::usesRefTxt($resultType, $refType)) {
$reftxtData = $this->modelRefTxt->getActiveByTestSiteID($id);
$row['reftxt'] = array_map(function ($r) {
return [
'RefTxtID' => $r['RefTxtID'],
'TxtRefType' => $r['TxtRefType'],
'TxtRefTypeLabel'=> $r['TxtRefType'] ? ValueSet::getLabel('text_ref_type', $r['TxtRefType']) : '',
'Sex' => $r['Sex'],
'SexLabel' => $r['Sex'] ? ValueSet::getLabel('gender', $r['Sex']) : '',
'AgeStart' => (int) $r['AgeStart'],
'AgeEnd' => (int) $r['AgeEnd'],
'RefTxt' => $r['RefTxt'],
'Flag' => $r['Flag'],
];
}, $reftxtData ?? []);
$row['reftxt'] = $this->modelRefTxt->getFormattedByTestSiteID($id);
}
}
}
@ -199,7 +148,8 @@ class TestsController extends BaseController
}
}
$this->db->transStart();
$db = \Config\Database::connect();
$db->transStart();
try {
$testSiteData = [
@ -225,9 +175,9 @@ class TestsController extends BaseController
$this->handleDetails($id, $input, 'insert');
$this->db->transComplete();
$db->transComplete();
if ($this->db->transStatus() === false) {
if ($db->transStatus() === false) {
return $this->failServerError('Transaction failed');
}
@ -237,7 +187,7 @@ class TestsController extends BaseController
'data' => ['TestSiteId' => $id],
]);
} catch (\Exception $e) {
$this->db->transRollback();
$db->transRollback();
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
@ -279,7 +229,8 @@ class TestsController extends BaseController
}
}
$this->db->transStart();
$db = \Config\Database::connect();
$db->transStart();
try {
$testSiteData = [];
@ -310,9 +261,9 @@ class TestsController extends BaseController
$this->handleDetails($id, $input, 'update');
$this->db->transComplete();
$db->transComplete();
if ($this->db->transStatus() === false) {
if ($db->transStatus() === false) {
return $this->failServerError('Transaction failed');
}
@ -322,7 +273,7 @@ class TestsController extends BaseController
'data' => ['TestSiteId' => $id],
]);
} catch (\Exception $e) {
$this->db->transRollback();
$db->transRollback();
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
@ -348,7 +299,8 @@ class TestsController extends BaseController
return $this->failValidationErrors('Test is already disabled');
}
$this->db->transStart();
$db = \Config\Database::connect();
$db->transStart();
try {
$now = date('Y-m-d H:i:s');
@ -363,8 +315,8 @@ class TestsController extends BaseController
} elseif (TestValidationService::isGroup($typeCode)) {
$this->modelGrp->disableByTestSiteID($id);
} elseif (TestValidationService::isTechnicalTest($typeCode)) {
$this->modelRefNum->where('TestSiteID', $id)->set('EndDate', $now)->update();
$this->modelRefTxt->where('TestSiteID', $id)->set('EndDate', $now)->update();
$this->modelRefNum->disableByTestSiteID($id);
$this->modelRefTxt->disableByTestSiteID($id);
}
// Disable testmap by test code
@ -372,17 +324,14 @@ class TestsController extends BaseController
if ($testSiteCode) {
$existingMaps = $this->modelMap->getMappingsByTestCode($testSiteCode);
foreach ($existingMaps as $existingMap) {
$this->modelMapDetail->where('TestMapID', $existingMap['TestMapID'])
->where('EndDate', null)
->set('EndDate', $now)
->update();
$this->modelMapDetail->disableByTestMapID($existingMap['TestMapID']);
$this->modelMap->update($existingMap['TestMapID'], ['EndDate' => $now]);
}
}
$this->db->transComplete();
$db->transComplete();
if ($this->db->transStatus() === false) {
if ($db->transStatus() === false) {
return $this->failServerError('Transaction failed');
}
@ -392,7 +341,7 @@ class TestsController extends BaseController
'data' => ['TestSiteId' => $id, 'EndDate' => $now],
]);
} catch (\Exception $e) {
$this->db->transRollback();
$db->transRollback();
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
@ -488,53 +437,19 @@ class TestsController extends BaseController
private function saveRefNumRanges($testSiteID, $ranges, $action, $siteID)
{
if ($action === 'update') {
$this->modelRefNum->where('TestSiteID', $testSiteID)
->set('EndDate', date('Y-m-d H:i:s'))
->update();
$this->modelRefNum->disableByTestSiteID($testSiteID);
}
foreach ($ranges as $index => $range) {
$this->modelRefNum->insert([
'TestSiteID' => $testSiteID,
'SiteID' => $siteID,
'NumRefType' => $range['NumRefType'],
'RangeType' => $range['RangeType'],
'Sex' => $range['Sex'],
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
'LowSign' => !empty($range['LowSign']) ? $range['LowSign'] : null,
'Low' => !empty($range['Low']) ? (float) $range['Low'] : null,
'HighSign' => !empty($range['HighSign']) ? $range['HighSign'] : null,
'High' => !empty($range['High']) ? (float) $range['High'] : null,
'Flag' => $range['Flag'] ?? null,
'Interpretation'=> $range['Interpretation'] ?? null,
'Display' => $index,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
$this->modelRefNum->batchInsert($testSiteID, $siteID, $ranges);
}
private function saveRefTxtRanges($testSiteID, $ranges, $action, $siteID)
{
if ($action === 'update') {
$this->modelRefTxt->where('TestSiteID', $testSiteID)
->set('EndDate', date('Y-m-d H:i:s'))
->update();
$this->modelRefTxt->disableByTestSiteID($testSiteID);
}
foreach ($ranges as $range) {
$this->modelRefTxt->insert([
'TestSiteID' => $testSiteID,
'SiteID' => $siteID,
'TxtRefType' => $range['TxtRefType'],
'Sex' => $range['Sex'],
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
'RefTxt' => $range['RefTxt'] ?? '',
'Flag' => $range['Flag'] ?? null,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
$this->modelRefTxt->batchInsert($testSiteID, $siteID, $ranges);
}
private function saveCalcDetails($testSiteID, $data, $action)
@ -595,10 +510,7 @@ class TestsController extends BaseController
$existingMaps = $this->modelMap->getMappingsByTestCode($testSiteCode);
foreach ($existingMaps as $existingMap) {
$this->modelMapDetail->where('TestMapID', $existingMap['TestMapID'])
->where('EndDate', null)
->set('EndDate', date('Y-m-d H:i:s'))
->update();
$this->modelMapDetail->disableByTestMapID($existingMap['TestMapID']);
}
// Soft delete the testmap headers

View File

@ -36,6 +36,7 @@ class CreateLookups extends Migration {
'CounterValue' => ['type' => 'INT', 'null' => false],
'CounterStart' => ['type' => 'INT', 'null' => false],
'CounterEnd' => ['type' => 'INT', 'null' => false],
'CounterName' => [ 'type' => 'VARCHAR', 'constraint' => 50, 'null' => true, 'after' => 'CounterID' ],
'CounterDesc' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
'CounterReset' => ['type' => 'varchar', 'constraint' => 1, 'null' => true],
'CreateDate' => ['type' => 'Datetime', 'null' => true],

View File

@ -31,12 +31,13 @@ class CreateOrganization extends Migration {
$this->forge->addField([
'SiteID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'SiteCode' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => false],
'ExtSiteID' => ['type' => 'int', 'null' => true],
'SiteCode' => ['type' => 'VARCHAR', 'constraint' => 2, 'null' => false],
'SiteName' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => false],
'AccountID' => ['type' => 'int', 'null' => true],
'SiteTypeID' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
'SiteType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
'Parent' => ['type' => 'int', 'null' => true],
'SiteClassID' => ['type' => 'VARCHAR', 'constraint' => 50, 'null' => true],
'SiteClass' => ['type' => 'VARCHAR', 'constraint' => 50, 'null' => true],
'ME' => ['type' => 'VARCHAR', 'constraint' => 50, 'null' => true],
'CreateDate' => ['type' => 'datetime', 'null'=> true],
'EndDate' => ['type' => 'datetime', 'null'=> true]
@ -81,6 +82,8 @@ class CreateOrganization extends Migration {
'DisciplineCode' => ['type' => 'varchar', 'constraint'=> 10, 'null'=> false],
'DisciplineName' => ['type' => 'varchar', 'constraint'=> 150, 'null'=> true],
'Parent' => ['type' => 'int', 'null'=> true],
'SeqScr' => ['type' => 'int', 'null' => true],
'SeqRpt' => ['type' => 'int', 'null' => true],
'CreateDate' => ['type'=>'DATETIME', 'null' => true],
'EndDate' => ['type'=>'DATETIME', 'null' => true]
]);

View File

@ -20,25 +20,25 @@ class OrganizationSeeder extends Seeder
$this->db->table('account')->insertBatch($data);
$data = [
[ 'SiteID' => 1, 'SiteCode' => 'QSIT', 'SiteName' => 'Dummy Site', 'AccountID' => 1, 'Parent' => null, 'CreateDate' => "$now" ],
[ 'SiteID' => 2, 'SiteCode' => 'XSIT', 'SiteName' => 'Dummy Child Site', 'AccountID' => 1, 'Parent' => 1, 'CreateDate' => "$now" ],
[ 'SiteID' => 1, 'SiteCode' => 'Q1', 'SiteName' => 'Dummy Site', 'AccountID' => 1, 'Parent' => null, 'CreateDate' => "$now" ],
[ 'SiteID' => 2, 'SiteCode' => 'X1', 'SiteName' => 'Dummy Child Site', 'AccountID' => 1, 'Parent' => 1, 'CreateDate' => "$now" ],
];
$this->db->table('site')->insertBatch($data);
$data = [
['DisciplineID' => '1', 'DisciplineCode' => 'HEMA', 'DisciplineName' => 'Hematology', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '2', 'DisciplineCode' => 'CHEM', 'DisciplineName' => 'Clinical Chemistry', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '3', 'DisciplineCode' => 'IMSR', 'DisciplineName' => 'Immunology/Serology', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '4', 'DisciplineCode' => 'URIN', 'DisciplineName' => 'Urinalysis', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '5', 'DisciplineCode' => 'FECAL', 'DisciplineName' => 'Fecal Analysis', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '6', 'DisciplineCode' => 'HC', 'DisciplineName' => 'Pathology/Cytology', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '7', 'DisciplineCode' => 'MICRO', 'DisciplineName' => 'Microbiology', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '8', 'DisciplineCode' => 'TXC', 'DisciplineName' => 'Toxicology', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '9', 'DisciplineCode' => 'LF', 'DisciplineName' => 'Life Sciences', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '10', 'DisciplineCode' => 'ND', 'DisciplineName' => 'Non-discipline', 'Parent' => null, 'CreateDate' => "$now"],
['DisciplineID' => '11', 'DisciplineCode' => 'HEMO', 'DisciplineName' => 'Hemostasis', 'Parent' => '1', 'CreateDate' => "$now"],
['DisciplineID' => '12', 'DisciplineCode' => 'BLGLU', 'DisciplineName' => 'Blood Glucose', 'Parent' => '2', 'CreateDate' => "$now"],
['DisciplineID' => '13', 'DisciplineCode' => 'KIDF', 'DisciplineName' => 'Kidney Function', 'Parent' => '2', 'CreateDate' => "$now"],
['DisciplineID' => '1', 'DisciplineCode' => 'HEMA', 'DisciplineName' => 'Hematology', 'Parent' => null, 'SeqScr' => 10, 'SeqRpt' => 10, 'CreateDate' => "$now"],
['DisciplineID' => '2', 'DisciplineCode' => 'CHEM', 'DisciplineName' => 'Clinical Chemistry', 'Parent' => null, 'SeqScr' => 20, 'SeqRpt' => 20, 'CreateDate' => "$now"],
['DisciplineID' => '3', 'DisciplineCode' => 'IMSR', 'DisciplineName' => 'Immunology/Serology', 'Parent' => null, 'SeqScr' => 30, 'SeqRpt' => 30, 'CreateDate' => "$now"],
['DisciplineID' => '4', 'DisciplineCode' => 'URIN', 'DisciplineName' => 'Urinalysis', 'Parent' => null, 'SeqScr' => 40, 'SeqRpt' => 40, 'CreateDate' => "$now"],
['DisciplineID' => '5', 'DisciplineCode' => 'FECAL', 'DisciplineName' => 'Fecal Analysis', 'Parent' => null, 'SeqScr' => 50, 'SeqRpt' => 50, 'CreateDate' => "$now"],
['DisciplineID' => '6', 'DisciplineCode' => 'HC', 'DisciplineName' => 'Pathology/Cytology', 'Parent' => null, 'SeqScr' => 60, 'SeqRpt' => 60, 'CreateDate' => "$now"],
['DisciplineID' => '7', 'DisciplineCode' => 'MICRO', 'DisciplineName' => 'Microbiology', 'Parent' => null, 'SeqScr' => 70, 'SeqRpt' => 70, 'CreateDate' => "$now"],
['DisciplineID' => '8', 'DisciplineCode' => 'TXC', 'DisciplineName' => 'Toxicology', 'Parent' => null, 'SeqScr' => 80, 'SeqRpt' => 80, 'CreateDate' => "$now"],
['DisciplineID' => '9', 'DisciplineCode' => 'LF', 'DisciplineName' => 'Life Sciences', 'Parent' => null, 'SeqScr' => 90, 'SeqRpt' => 90, 'CreateDate' => "$now"],
['DisciplineID' => '10', 'DisciplineCode' => 'ND', 'DisciplineName' => 'Non-discipline', 'Parent' => null, 'SeqScr' => 100, 'SeqRpt' => 100, 'CreateDate' => "$now"],
['DisciplineID' => '11', 'DisciplineCode' => 'HEMO', 'DisciplineName' => 'Hemostasis', 'Parent' => '1', 'SeqScr' => 15, 'SeqRpt' => 15, 'CreateDate' => "$now"],
['DisciplineID' => '12', 'DisciplineCode' => 'BLGLU', 'DisciplineName' => 'Blood Glucose', 'Parent' => '2', 'SeqScr' => 25, 'SeqRpt' => 25, 'CreateDate' => "$now"],
['DisciplineID' => '13', 'DisciplineCode' => 'KIDF', 'DisciplineName' => 'Kidney Function', 'Parent' => '2', 'SeqScr' => 26, 'SeqRpt' => 26, 'CreateDate' => "$now"],
];
$this->db->table('discipline')->insertBatch($data);

View File

@ -65,128 +65,51 @@ class TestSeeder extends Seeder
};
// ========================================
// TEST TYPE - Actual Laboratory Tests
// DEPARTMENT 1 - HEMATOLOGY (Discipline 1)
// Order: GROUP (<100) → PARAM (<100) → TEST (100+) → CALC (100+)
// ========================================
// Hematology Tests - Technical details merged into testdefsite
$data = ['SiteID' => '1', 'TestSiteCode' => 'HB', 'TestSiteName' => 'Hemoglobin', 'TestType' => 'TEST', 'Description' => '', 'SeqScr' => '2', 'SeqRpt' => '2', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'g/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
// GROUP: CBC (Complete Blood Count)
$data = ['SiteID' => '1', 'TestSiteCode' => 'CBC', 'TestSiteName' => 'Complete Blood Count', 'TestType' => 'GROUP', 'Description' => 'Darah Lengkap', 'SeqScr' => '10', 'SeqRpt' => '10', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['CBC'] = $this->db->insertID();
// PARAM: (none for Hematology)
// TEST: Hematology Tests
$data = ['SiteID' => '1', 'TestSiteCode' => 'HB', 'TestSiteName' => 'Hemoglobin', 'TestType' => 'TEST', 'Description' => '', 'SeqScr' => '100', 'SeqRpt' => '100', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'g/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['HB'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'HCT', 'TestSiteName' => 'Hematocrit', 'TestType' => 'TEST', 'Description' => '', 'SeqScr' => '3', 'SeqRpt' => '3', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => '%', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'HCT', 'TestSiteName' => 'Hematocrit', 'TestType' => 'TEST', 'Description' => '', 'SeqScr' => '110', 'SeqRpt' => '110', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => '%', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['HCT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'RBC', 'TestSiteName' => 'Red Blood Cell', 'TestType' => 'TEST', 'Description' => 'Eritrosit', 'SeqScr' => '4', 'SeqRpt' => '4', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'x10^6/uL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '2', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'RBC', 'TestSiteName' => 'Red Blood Cell', 'TestType' => 'TEST', 'Description' => 'Eritrosit', 'SeqScr' => '120', 'SeqRpt' => '120', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'x10^6/uL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '2', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['RBC'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'WBC', 'TestSiteName' => 'White Blood Cell', 'TestType' => 'TEST', 'Description' => 'Leukosit', 'SeqScr' => '5', 'SeqRpt' => '5', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'x10^3/uL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '2', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'WBC', 'TestSiteName' => 'White Blood Cell', 'TestType' => 'TEST', 'Description' => 'Leukosit', 'SeqScr' => '130', 'SeqRpt' => '130', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'x10^3/uL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '2', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['WBC'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'PLT', 'TestSiteName' => 'Platelet', 'TestType' => 'TEST', 'Description' => 'Trombosit', 'SeqScr' => '6', 'SeqRpt' => '6', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'x10^3/uL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'PLT', 'TestSiteName' => 'Platelet', 'TestType' => 'TEST', 'Description' => 'Trombosit', 'SeqScr' => '140', 'SeqRpt' => '140', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'x10^3/uL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['PLT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'MCV', 'TestSiteName' => 'MCV', 'TestType' => 'TEST', 'Description' => 'Mean Corpuscular Volume', 'SeqScr' => '7', 'SeqRpt' => '7', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'fL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'MCV', 'TestSiteName' => 'MCV', 'TestType' => 'TEST', 'Description' => 'Mean Corpuscular Volume', 'SeqScr' => '150', 'SeqRpt' => '150', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'fL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['MCV'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'MCH', 'TestSiteName' => 'MCH', 'TestType' => 'TEST', 'Description' => 'Mean Corpuscular Hemoglobin', 'SeqScr' => '8', 'SeqRpt' => '8', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'pg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'MCH', 'TestSiteName' => 'MCH', 'TestType' => 'TEST', 'Description' => 'Mean Corpuscular Hemoglobin', 'SeqScr' => '160', 'SeqRpt' => '160', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'pg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['MCH'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'MCHC', 'TestSiteName' => 'MCHC', 'TestType' => 'TEST', 'Description' => 'Mean Corpuscular Hemoglobin Concentration', 'SeqScr' => '9', 'SeqRpt' => '9', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'g/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'MCHC', 'TestSiteName' => 'MCHC', 'TestType' => 'TEST', 'Description' => 'Mean Corpuscular Hemoglobin Concentration', 'SeqScr' => '170', 'SeqRpt' => '170', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '1', 'DepartmentID' => '1', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '500', 'ReqQtyUnit' => 'uL', 'Unit1' => 'g/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'CBC Analyzer', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['MCHC'] = $this->db->insertID();
// Chemistry Tests
$data = ['SiteID' => '1', 'TestSiteCode' => 'GLU', 'TestSiteName' => 'Glucose', 'TestType' => 'TEST', 'Description' => 'Glukosa Sewaktu', 'SeqScr' => '11', 'SeqRpt' => '11', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '0.0555', 'Unit2' => 'mmol/L', 'Decimal' => '0', 'Method' => 'Hexokinase', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['GLU'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'CREA', 'TestSiteName' => 'Creatinine', 'TestType' => 'TEST', 'Description' => 'Kreatinin', 'SeqScr' => '12', 'SeqRpt' => '12', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '88.4', 'Unit2' => 'umol/L', 'Decimal' => '2', 'Method' => 'Enzymatic', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['CREA'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'UREA', 'TestSiteName' => 'Blood Urea Nitrogen', 'TestType' => 'TEST', 'Description' => 'BUN', 'SeqScr' => '13', 'SeqRpt' => '13', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'Urease-GLDH', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['UREA'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'SGOT', 'TestSiteName' => 'AST (SGOT)', 'TestType' => 'TEST', 'Description' => 'Aspartate Aminotransferase', 'SeqScr' => '14', 'SeqRpt' => '14', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'U/L', 'Factor' => '0.017', 'Unit2' => 'ukat/L', 'Decimal' => '0', 'Method' => 'IFCC', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['SGOT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'SGPT', 'TestSiteName' => 'ALT (SGPT)', 'TestType' => 'TEST', 'Description' => 'Alanine Aminotransferase', 'SeqScr' => '15', 'SeqRpt' => '15', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'U/L', 'Factor' => '0.017', 'Unit2' => 'ukat/L', 'Decimal' => '0', 'Method' => 'IFCC', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['SGPT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'CHOL', 'TestSiteName' => 'Total Cholesterol', 'TestType' => 'TEST', 'Description' => 'Kolesterol Total', 'SeqScr' => '16', 'SeqRpt' => '16', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'Enzymatic', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['CHOL'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'TG', 'TestSiteName' => 'Triglycerides', 'TestType' => 'TEST', 'Description' => 'Trigliserida', 'SeqScr' => '17', 'SeqRpt' => '17', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'GPO-PAP', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['TG'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'HDL', 'TestSiteName' => 'HDL Cholesterol', 'TestType' => 'TEST', 'Description' => 'Kolesterol HDL', 'SeqScr' => '18', 'SeqRpt' => '18', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'Direct', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['HDL'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'LDL', 'TestSiteName' => 'LDL Cholesterol', 'TestType' => 'TEST', 'Description' => 'Kolesterol LDL', 'SeqScr' => '19', 'SeqRpt' => '19', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'Direct', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LDL'] = $this->db->insertID();
// ========================================
// PARAM TYPE - Parameters (non-lab values)
// ========================================
$data = ['SiteID' => '1', 'TestSiteCode' => 'HEIGHT', 'TestSiteName' => 'Height', 'TestType' => 'PARAM', 'Description' => 'Tinggi Badan', 'SeqScr' => '40', 'SeqRpt' => '40', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'cm', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['HEIGHT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'WEIGHT', 'TestSiteName' => 'Weight', 'TestType' => 'PARAM', 'Description' => 'Berat Badan', 'SeqScr' => '41', 'SeqRpt' => '41', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'kg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['WEIGHT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'AGE', 'TestSiteName' => 'Age', 'TestType' => 'PARAM', 'Description' => 'Usia', 'SeqScr' => '42', 'SeqRpt' => '42', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'years', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['AGE'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'SYSTL', 'TestSiteName' => 'Systolic BP', 'TestType' => 'PARAM', 'Description' => 'Tekanan Darah Sistolik', 'SeqScr' => '43', 'SeqRpt' => '43', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'mmHg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['SYSTL'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'DIASTL', 'TestSiteName' => 'Diastolic BP', 'TestType' => 'PARAM', 'Description' => 'Tekanan Darah Diastolik', 'SeqScr' => '44', 'SeqRpt' => '44', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'mmHg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['DIASTL'] = $this->db->insertID();
// ========================================
// CALC TYPE - Calculated Tests
// ========================================
$data = ['SiteID' => '1', 'TestSiteCode' => 'BMI', 'TestSiteName' => 'Body Mass Index', 'TestType' => 'CALC', 'Description' => 'Indeks Massa Tubuh - weight/(height^2)', 'SeqScr' => '45', 'SeqRpt' => '45', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['BMI'] = $this->db->insertID();
$data = ['TestSiteID' => $tIDs['BMI'], 'DisciplineID' => '10', 'DepartmentID' => '', 'FormulaInput' => 'WEIGHT,HEIGHT', 'FormulaCode' => 'WEIGHT / ((HEIGHT/100) * (HEIGHT/100))', 'RefType' => 'RANGE', 'Unit1' => 'kg/m2', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'CreateDate' => "$now"];
$this->db->table('testdefcal')->insert($data);
$data = ['SiteID' => '1', 'TestSiteCode' => 'EGFR', 'TestSiteName' => 'eGFR (CKD-EPI)', 'TestType' => 'CALC', 'Description' => 'Estimated Glomerular Filtration Rate', 'SeqScr' => '20', 'SeqRpt' => '20', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['EGFR'] = $this->db->insertID();
$data = ['TestSiteID' => $tIDs['EGFR'], 'DisciplineID' => '2', 'DepartmentID' => '2', 'FormulaInput' => 'CREA,AGE,GENDER', 'FormulaCode' => 'CKD_EPI(CREA,AGE,GENDER)', 'RefType' => 'RANGE', 'Unit1' => 'mL/min/1.73m2', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefcal')->insert($data);
$data = ['SiteID' => '1', 'TestSiteCode' => 'LDLCALC', 'TestSiteName' => 'LDL Cholesterol (Calculated)', 'TestType' => 'CALC', 'Description' => 'Friedewald formula: TC - HDL - (TG/5)', 'SeqScr' => '21', 'SeqRpt' => '21', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LDLCALC'] = $this->db->insertID();
$data = ['TestSiteID' => $tIDs['LDLCALC'], 'DisciplineID' => '2', 'DepartmentID' => '2', 'FormulaInput' => 'CHOL,HDL,TG', 'FormulaCode' => 'CHOL - HDL - (TG/5)', 'RefType' => 'RANGE', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefcal')->insert($data);
// ========================================
// GROUP TYPE - Panel/Profile Tests
// ========================================
$data = ['SiteID' => '1', 'TestSiteCode' => 'CBC', 'TestSiteName' => 'Complete Blood Count', 'TestType' => 'GROUP', 'Description' => 'Darah Lengkap', 'SeqScr' => '50', 'SeqRpt' => '50', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['CBC'] = $this->db->insertID();
// Add CBC members now that tests are defined
$this->db->table('testdefgrp')->insertBatch([
['TestSiteID' => $tIDs['CBC'], 'Member' => $tIDs['HB'], 'CreateDate' => "$now"],
['TestSiteID' => $tIDs['CBC'], 'Member' => $tIDs['HCT'], 'CreateDate' => "$now"],
@ -198,9 +121,79 @@ class TestSeeder extends Seeder
['TestSiteID' => $tIDs['CBC'], 'Member' => $tIDs['MCHC'], 'CreateDate' => "$now"]
]);
$data = ['SiteID' => '1', 'TestSiteCode' => 'LIPID', 'TestSiteName' => 'Lipid Profile', 'TestType' => 'GROUP', 'Description' => 'Profil Lipid', 'SeqScr' => '51', 'SeqRpt' => '51', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
// CALC: (none for Hematology)
// ========================================
// DEPARTMENT 2 - CHEMISTRY (Discipline 2)
// Order: GROUP (<100) → PARAM (<100) → TEST (100+) → CALC (100+)
// ========================================
// GROUP: LIPID, LFT, RFT
$data = ['SiteID' => '1', 'TestSiteCode' => 'LIPID', 'TestSiteName' => 'Lipid Profile', 'TestType' => 'GROUP', 'Description' => 'Profil Lipid', 'SeqScr' => '10', 'SeqRpt' => '10', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LIPID'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'LFT', 'TestSiteName' => 'Liver Function Test', 'TestType' => 'GROUP', 'Description' => 'Fungsi Hati', 'SeqScr' => '20', 'SeqRpt' => '20', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LFT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'RFT', 'TestSiteName' => 'Renal Function Test', 'TestType' => 'GROUP', 'Description' => 'Fungsi Ginjal', 'SeqScr' => '30', 'SeqRpt' => '30', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['RFT'] = $this->db->insertID();
// PARAM: (none for Chemistry)
// TEST: Chemistry Tests
$data = ['SiteID' => '1', 'TestSiteCode' => 'GLU', 'TestSiteName' => 'Glucose', 'TestType' => 'TEST', 'Description' => 'Glukosa Sewaktu', 'SeqScr' => '100', 'SeqRpt' => '100', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '0.0555', 'Unit2' => 'mmol/L', 'Decimal' => '0', 'Method' => 'Hexokinase', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['GLU'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'CREA', 'TestSiteName' => 'Creatinine', 'TestType' => 'TEST', 'Description' => 'Kreatinin', 'SeqScr' => '110', 'SeqRpt' => '110', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '88.4', 'Unit2' => 'umol/L', 'Decimal' => '2', 'Method' => 'Enzymatic', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['CREA'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'UREA', 'TestSiteName' => 'Blood Urea Nitrogen', 'TestType' => 'TEST', 'Description' => 'BUN', 'SeqScr' => '120', 'SeqRpt' => '120', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'Urease-GLDH', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['UREA'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'SGOT', 'TestSiteName' => 'AST (SGOT)', 'TestType' => 'TEST', 'Description' => 'Aspartate Aminotransferase', 'SeqScr' => '130', 'SeqRpt' => '130', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'U/L', 'Factor' => '0.017', 'Unit2' => 'ukat/L', 'Decimal' => '0', 'Method' => 'IFCC', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['SGOT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'SGPT', 'TestSiteName' => 'ALT (SGPT)', 'TestType' => 'TEST', 'Description' => 'Alanine Aminotransferase', 'SeqScr' => '140', 'SeqRpt' => '140', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'U/L', 'Factor' => '0.017', 'Unit2' => 'ukat/L', 'Decimal' => '0', 'Method' => 'IFCC', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['SGPT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'CHOL', 'TestSiteName' => 'Total Cholesterol', 'TestType' => 'TEST', 'Description' => 'Kolesterol Total', 'SeqScr' => '150', 'SeqRpt' => '150', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'Enzymatic', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['CHOL'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'TG', 'TestSiteName' => 'Triglycerides', 'TestType' => 'TEST', 'Description' => 'Trigliserida', 'SeqScr' => '160', 'SeqRpt' => '160', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'GPO-PAP', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['TG'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'HDL', 'TestSiteName' => 'HDL Cholesterol', 'TestType' => 'TEST', 'Description' => 'Kolesterol HDL', 'SeqScr' => '170', 'SeqRpt' => '170', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'Direct', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['HDL'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'LDL', 'TestSiteName' => 'LDL Cholesterol', 'TestType' => 'TEST', 'Description' => 'Kolesterol LDL', 'SeqScr' => '180', 'SeqRpt' => '180', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '2', 'DepartmentID' => '2', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '300', 'ReqQtyUnit' => 'uL', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => 'Direct', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LDL'] = $this->db->insertID();
// CALC: Chemistry Calculated Tests
$data = ['SiteID' => '1', 'TestSiteCode' => 'EGFR', 'TestSiteName' => 'eGFR (CKD-EPI)', 'TestType' => 'CALC', 'Description' => 'Estimated Glomerular Filtration Rate', 'SeqScr' => '190', 'SeqRpt' => '190', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['EGFR'] = $this->db->insertID();
$data = ['TestSiteID' => $tIDs['EGFR'], 'DisciplineID' => '2', 'DepartmentID' => '2', 'FormulaInput' => 'CREA,AGE,GENDER', 'FormulaCode' => 'CKD_EPI(CREA,AGE,GENDER)', 'RefType' => 'RANGE', 'Unit1' => 'mL/min/1.73m2', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefcal')->insert($data);
$data = ['SiteID' => '1', 'TestSiteCode' => 'LDLCALC', 'TestSiteName' => 'LDL Cholesterol (Calculated)', 'TestType' => 'CALC', 'Description' => 'Friedewald formula: TC - HDL - (TG/5)', 'SeqScr' => '200', 'SeqRpt' => '200', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LDLCALC'] = $this->db->insertID();
$data = ['TestSiteID' => $tIDs['LDLCALC'], 'DisciplineID' => '2', 'DepartmentID' => '2', 'FormulaInput' => 'CHOL,HDL,TG', 'FormulaCode' => 'CHOL - HDL - (TG/5)', 'RefType' => 'RANGE', 'Unit1' => 'mg/dL', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefcal')->insert($data);
// Add Chemistry Group members now that tests are defined
$this->db->table('testdefgrp')->insertBatch([
['TestSiteID' => $tIDs['LIPID'], 'Member' => $tIDs['CHOL'], 'CreateDate' => "$now"],
['TestSiteID' => $tIDs['LIPID'], 'Member' => $tIDs['TG'], 'CreateDate' => "$now"],
@ -209,48 +202,87 @@ class TestSeeder extends Seeder
['TestSiteID' => $tIDs['LIPID'], 'Member' => $tIDs['LDLCALC'], 'CreateDate' => "$now"]
]);
$data = ['SiteID' => '1', 'TestSiteCode' => 'LFT', 'TestSiteName' => 'Liver Function Test', 'TestType' => 'GROUP', 'Description' => 'Fungsi Hati', 'SeqScr' => '52', 'SeqRpt' => '52', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['LFT'] = $this->db->insertID();
$this->db->table('testdefgrp')->insertBatch([
['TestSiteID' => $tIDs['LFT'], 'Member' => $tIDs['SGOT'], 'CreateDate' => "$now"],
['TestSiteID' => $tIDs['LFT'], 'Member' => $tIDs['SGPT'], 'CreateDate' => "$now"]
]);
$data = ['SiteID' => '1', 'TestSiteCode' => 'RFT', 'TestSiteName' => 'Renal Function Test', 'TestType' => 'GROUP', 'Description' => 'Fungsi Ginjal', 'SeqScr' => '53', 'SeqRpt' => '53', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'ResultType' => 'NORES', 'RefType' => 'NOREF', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['RFT'] = $this->db->insertID();
$this->db->table('testdefgrp')->insertBatch([
['TestSiteID' => $tIDs['RFT'], 'Member' => $tIDs['UREA'], 'CreateDate' => "$now"],
['TestSiteID' => $tIDs['RFT'], 'Member' => $tIDs['CREA'], 'CreateDate' => "$now"],
['TestSiteID' => $tIDs['RFT'], 'Member' => $tIDs['EGFR'], 'CreateDate' => "$now"]
]);
// Urinalysis Tests (with valueset result type)
$data = ['SiteID' => '1', 'TestSiteCode' => 'UCOLOR', 'TestSiteName' => 'Urine Color', 'TestType' => 'TEST', 'Description' => 'Warna Urine', 'SeqScr' => '31', 'SeqRpt' => '31', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'VSET', 'RefType' => 'VSET', 'VSet' => '1001', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '', 'Method' => 'Visual', 'CreateDate' => "$now"];
// ========================================
// DEPARTMENT 4 - URINALYSIS (Discipline 4)
// Order: GROUP (<100) → PARAM (<100) → TEST (100+) → CALC (100+)
// ========================================
// GROUP: (none for Urinalysis in current setup)
// PARAM: (none for Urinalysis)
// TEST: Urinalysis Tests
$data = ['SiteID' => '1', 'TestSiteCode' => 'UCOLOR', 'TestSiteName' => 'Urine Color', 'TestType' => 'TEST', 'Description' => 'Warna Urine', 'SeqScr' => '100', 'SeqRpt' => '100', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'VSET', 'RefType' => 'VSET', 'VSet' => '1001', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '', 'Method' => 'Visual', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['UCOLOR'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'UGLUC', 'TestSiteName' => 'Urine Glucose', 'TestType' => 'TEST', 'Description' => 'Glukosa Urine', 'SeqScr' => '32', 'SeqRpt' => '32', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'VSET', 'RefType' => 'VSET', 'VSet' => '1002', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '', 'Method' => 'Dipstick', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'UGLUC', 'TestSiteName' => 'Urine Glucose', 'TestType' => 'TEST', 'Description' => 'Glukosa Urine', 'SeqScr' => '110', 'SeqRpt' => '110', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'VSET', 'RefType' => 'VSET', 'VSet' => '1002', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '', 'Method' => 'Dipstick', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['UGLUC'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'UPROT', 'TestSiteName' => 'Urine Protein', 'TestType' => 'TEST', 'Description' => 'Protein Urine', 'SeqScr' => '33', 'SeqRpt' => '33', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'VSET', 'RefType' => 'VSET', 'VSet' => '1003', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '', 'Method' => 'Dipstick', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'UPROT', 'TestSiteName' => 'Urine Protein', 'TestType' => 'TEST', 'Description' => 'Protein Urine', 'SeqScr' => '120', 'SeqRpt' => '120', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'VSET', 'RefType' => 'VSET', 'VSet' => '1003', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '', 'Method' => 'Dipstick', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['UPROT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'PH', 'TestSiteName' => 'Urine pH', 'TestType' => 'TEST', 'Description' => 'pH Urine', 'SeqScr' => '34', 'SeqRpt' => '34', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'Dipstick', 'CreateDate' => "$now"];
$data = ['SiteID' => '1', 'TestSiteCode' => 'PH', 'TestSiteName' => 'Urine pH', 'TestType' => 'TEST', 'Description' => 'pH Urine', 'SeqScr' => '130', 'SeqRpt' => '130', 'IndentLeft' => '1', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '1', 'DisciplineID' => '4', 'DepartmentID' => '4', 'ResultType' => 'NMRIC', 'RefType' => 'RANGE', 'VSet' => '', 'ReqQty' => '10', 'ReqQtyUnit' => 'mL', 'Unit1' => '', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => 'Dipstick', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['PH'] = $this->db->insertID();
// CALC: (none for Urinalysis)
// ========================================
// DEPARTMENT 10 - GENERAL (Discipline 10)
// Order: GROUP (<100) → PARAM (<100) → TEST (100+) → CALC (100+)
// ========================================
// GROUP: (none for General)
// PARAM: General Parameters (< 100)
$data = ['SiteID' => '1', 'TestSiteCode' => 'HEIGHT', 'TestSiteName' => 'Height', 'TestType' => 'PARAM', 'Description' => 'Tinggi Badan', 'SeqScr' => '10', 'SeqRpt' => '10', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'cm', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['HEIGHT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'WEIGHT', 'TestSiteName' => 'Weight', 'TestType' => 'PARAM', 'Description' => 'Berat Badan', 'SeqScr' => '20', 'SeqRpt' => '20', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'kg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['WEIGHT'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'AGE', 'TestSiteName' => 'Age', 'TestType' => 'PARAM', 'Description' => 'Usia', 'SeqScr' => '30', 'SeqRpt' => '30', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'years', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['AGE'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'SYSTL', 'TestSiteName' => 'Systolic BP', 'TestType' => 'PARAM', 'Description' => 'Tekanan Darah Sistolik', 'SeqScr' => '40', 'SeqRpt' => '40', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'mmHg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['SYSTL'] = $this->db->insertID();
$data = ['SiteID' => '1', 'TestSiteCode' => 'DIASTL', 'TestSiteName' => 'Diastolic BP', 'TestType' => 'PARAM', 'Description' => 'Tekanan Darah Diastolik', 'SeqScr' => '50', 'SeqRpt' => '50', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '0', 'CountStat' => '0', 'DisciplineID' => '10', 'DepartmentID' => '', 'ResultType' => 'NMRIC', 'RefType' => '', 'VSet' => '', 'ReqQty' => '', 'ReqQtyUnit' => '', 'Unit1' => 'mmHg', 'Factor' => '', 'Unit2' => '', 'Decimal' => '0', 'Method' => '', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['DIASTL'] = $this->db->insertID();
// TEST: (none for General)
// CALC: BMI (>= 100)
$data = ['SiteID' => '1', 'TestSiteCode' => 'BMI', 'TestSiteName' => 'Body Mass Index', 'TestType' => 'CALC', 'Description' => 'Indeks Massa Tubuh - weight/(height^2)', 'SeqScr' => '100', 'SeqRpt' => '100', 'IndentLeft' => '0', 'VisibleScr' => '1', 'VisibleRpt' => '1', 'CountStat' => '0', 'CreateDate' => "$now"];
$this->db->table('testdefsite')->insert($data);
$tIDs['BMI'] = $this->db->insertID();
$data = ['TestSiteID' => $tIDs['BMI'], 'DisciplineID' => '10', 'DepartmentID' => '', 'FormulaInput' => 'WEIGHT,HEIGHT', 'FormulaCode' => 'WEIGHT / ((HEIGHT/100) * (HEIGHT/100))', 'RefType' => 'RANGE', 'Unit1' => 'kg/m2', 'Factor' => '', 'Unit2' => '', 'Decimal' => '1', 'CreateDate' => "$now"];
$this->db->table('testdefcal')->insert($data);
// ========================================
// TEST MAP - Specimen Mapping
// Maps tests to workstations and containers
// New structure: testmap (header) + testmapdetail (details)
// ========================================
// Get container IDs (inserted by SpecimenSeeder in order)
// ConCode order: 1, 11, 12, 13, 14, 15, 16, 20, 101, 150, 200, 290, 295, 900
$conSST = 1; // SST (Serum Separator Tube)
$conEDTA = 9; // EDTA - Hematology
$conCitrate = 10; // Citrate - Koagulasi
@ -299,10 +331,7 @@ class TestSeeder extends Seeder
];
};
// ============================================
// TEST MAP CONFIGURATION
// Grouped by discipline for maintainability
// ============================================
// Test mappings configuration
$testMappings = [
// Hematology: Site → HAUTO → Sysmex (EDTA)
[
@ -347,7 +376,7 @@ class TestSeeder extends Seeder
$entityInst = $this->getEntityType('INST') ?? 'INST';
foreach ($testMappings as $mapping) {
// Site → Workstation mapping (one header per workstation)
// Site → Workstation mapping
$testMapSiteWsID = $getTestMapID($entitySite, '1', $entityWst, (string)$mapping['siteToWs']['ws']);
foreach ($mapping['tests'] as $testCode) {
$addDetail($testMapSiteWsID, $testCode, $testCode, $mapping['siteToWs']['con'], $testCode, $testCode);
@ -356,7 +385,7 @@ class TestSeeder extends Seeder
$addDetail($testMapSiteWsID, $panelCode, $panelCode, $mapping['siteToWs']['con'], $panelCode, $panelCode);
}
// Workstation → Instrument mapping (one header per instrument)
// Workstation → Instrument mapping
if ($mapping['wsToInst'] !== null) {
$testMapWsInstID = $getTestMapID($entityWst, (string)$mapping['wsToInst']['ws'], $entityInst, (string)$mapping['wsToInst']['inst']);
foreach ($mapping['tests'] as $testCode) {

View File

@ -5,7 +5,7 @@ use App\Models\BaseModel;
class CounterModel extends BaseModel {
protected $table = 'counter';
protected $primaryKey = 'CounterID';
protected $allowedFields = ['CounterValue', 'CounterStart', 'CounterEnd', 'CounterReset', 'CreateDate', 'EndDate'];
protected $allowedFields = ['CounterValue', 'CounterStart', 'CounterEnd', 'CounterReset', 'CounterName', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = "CreateDate";

View File

@ -60,7 +60,7 @@ class OrderTestModel extends BaseModel {
$this->db->transStart();
try {
$orderID = $data['OrderID'] ?? $this->generateOrderID($data['SiteCode'] ?? '00');
$orderID = !empty($data['OrderID']) ? $data['OrderID'] : $this->generateOrderID($data['SiteCode'] ?? '00');
$orderData = [
'OrderID' => $orderID,

View File

@ -5,7 +5,7 @@ use App\Models\BaseModel;
class DisciplineModel extends BaseModel {
protected $table = 'discipline';
protected $primaryKey = 'DisciplineID';
protected $allowedFields = ['DisciplineCode', 'DisciplineName', 'SiteID', 'Parent', 'CreateDate', 'EndDate'];
protected $allowedFields = ['DisciplineCode', 'DisciplineName', 'SiteID', 'Parent', 'SeqScr', 'SeqRpt', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
@ -15,7 +15,7 @@ class DisciplineModel extends BaseModel {
public function getDisciplines($filter) {
$builder = $this->select('discipline.DisciplineID, discipline.DisciplineCode, discipline.DisciplineName, discipline.SiteID,
discipline.Parent, d.DisciplineCode as ParentCode,d.DisciplineName as ParentName')
discipline.Parent, discipline.SeqScr, discipline.SeqRpt, d.DisciplineCode as ParentCode,d.DisciplineName as ParentName')
->join('discipline as d', 'd.DisciplineID = discipline.Parent', 'left');
if (!empty($filter['DisciplineCode'])) {

View File

@ -6,7 +6,7 @@ use App\Libraries\ValueSet;
class SiteModel extends BaseModel {
protected $table = 'site';
protected $primaryKey = 'SiteID';
protected $allowedFields = ['SiteCode', 'SiteName', 'AccountID', 'SiteTypeID', 'Parent', 'SiteClassID', 'ME',
protected $allowedFields = ['SiteCode', 'ExtSiteID', 'SiteName', 'AccountID', 'SiteType', 'Parent', 'SiteClass', 'ME',
'CreateDate', 'EndDate'];
protected $useTimestamps = true;

View File

@ -46,4 +46,82 @@ class RefNumModel extends BaseModel
->orderBy('Display', 'ASC')
->findAll();
}
/**
* Get formatted numeric reference ranges with labels
*
* @param int $testSiteID
* @return array
*/
public function getFormattedByTestSiteID($testSiteID)
{
$rows = $this->getActiveByTestSiteID($testSiteID);
return array_map(function ($r) {
return [
'RefNumID' => $r['RefNumID'],
'NumRefType' => $r['NumRefType'],
'NumRefTypeLabel' => $r['NumRefType'] ? \App\Libraries\ValueSet::getLabel('numeric_ref_type', $r['NumRefType']) : '',
'RangeType' => $r['RangeType'],
'RangeTypeLabel' => $r['RangeType'] ? \App\Libraries\ValueSet::getLabel('range_type', $r['RangeType']) : '',
'Sex' => $r['Sex'],
'SexLabel' => $r['Sex'] ? \App\Libraries\ValueSet::getLabel('gender', $r['Sex']) : '',
'LowSign' => $r['LowSign'],
'LowSignLabel' => $r['LowSign'] ? \App\Libraries\ValueSet::getLabel('math_sign', $r['LowSign']) : '',
'HighSign' => $r['HighSign'],
'HighSignLabel' => $r['HighSign'] ? \App\Libraries\ValueSet::getLabel('math_sign', $r['HighSign']) : '',
'High' => $r['High'] !== null ? (float) $r['High'] : null,
'Low' => $r['Low'] !== null ? (float) $r['Low'] : null,
'AgeStart' => (int) $r['AgeStart'],
'AgeEnd' => (int) $r['AgeEnd'],
'Flag' => $r['Flag'],
'Interpretation' => $r['Interpretation'],
];
}, $rows ?? []);
}
/**
* Disable all numeric reference ranges for a test
*
* @param int $testSiteID
* @return void
*/
public function disableByTestSiteID($testSiteID)
{
$this->where('TestSiteID', $testSiteID)
->set('EndDate', date('Y-m-d H:i:s'))
->update();
}
/**
* Batch insert numeric reference ranges
*
* @param int $testSiteID
* @param int $siteID
* @param array $ranges
* @return void
*/
public function batchInsert($testSiteID, $siteID, $ranges)
{
foreach ($ranges as $index => $range) {
$this->insert([
'TestSiteID' => $testSiteID,
'SiteID' => $siteID,
'NumRefType' => $range['NumRefType'],
'RangeType' => $range['RangeType'],
'Sex' => $range['Sex'],
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
'LowSign' => !empty($range['LowSign']) ? $range['LowSign'] : null,
'Low' => !empty($range['Low']) ? (float) $range['Low'] : null,
'HighSign' => !empty($range['HighSign']) ? $range['HighSign'] : null,
'High' => !empty($range['High']) ? (float) $range['High'] : null,
'Flag' => $range['Flag'] ?? null,
'Interpretation'=> $range['Interpretation'] ?? null,
'Display' => $index,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
}
}

View File

@ -40,4 +40,68 @@ class RefTxtModel extends BaseModel
->orderBy('RefTxtID', 'ASC')
->findAll();
}
/**
* Get formatted text reference ranges with labels
*
* @param int $testSiteID
* @return array
*/
public function getFormattedByTestSiteID($testSiteID)
{
$rows = $this->getActiveByTestSiteID($testSiteID);
return array_map(function ($r) {
return [
'RefTxtID' => $r['RefTxtID'],
'TxtRefType' => $r['TxtRefType'],
'TxtRefTypeLabel'=> $r['TxtRefType'] ? \App\Libraries\ValueSet::getLabel('text_ref_type', $r['TxtRefType']) : '',
'Sex' => $r['Sex'],
'SexLabel' => $r['Sex'] ? \App\Libraries\ValueSet::getLabel('gender', $r['Sex']) : '',
'AgeStart' => (int) $r['AgeStart'],
'AgeEnd' => (int) $r['AgeEnd'],
'RefTxt' => $r['RefTxt'],
'Flag' => $r['Flag'],
];
}, $rows ?? []);
}
/**
* Disable all text reference ranges for a test
*
* @param int $testSiteID
* @return void
*/
public function disableByTestSiteID($testSiteID)
{
$this->where('TestSiteID', $testSiteID)
->set('EndDate', date('Y-m-d H:i:s'))
->update();
}
/**
* Batch insert text reference ranges
*
* @param int $testSiteID
* @param int $siteID
* @param array $ranges
* @return void
*/
public function batchInsert($testSiteID, $siteID, $ranges)
{
foreach ($ranges as $range) {
$this->insert([
'TestSiteID' => $testSiteID,
'SiteID' => $siteID,
'TxtRefType' => $range['TxtRefType'],
'Sex' => $range['Sex'],
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
'RefTxt' => $range['RefTxt'] ?? '',
'Flag' => $range['Flag'] ?? null,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
}
}

View File

@ -11,6 +11,8 @@ class TestDefGrpModel extends BaseModel {
protected $allowedFields = [
'TestSiteID',
'Member',
'SeqScr',
'SeqRpt',
'CreateDate',
'EndDate'
];
@ -25,10 +27,12 @@ class TestDefGrpModel extends BaseModel {
$db = \Config\Database::connect();
$rows = $db->table('testdefgrp')
->select('testdefgrp.*, t.TestSiteID as MemberTestSiteID, t.TestSiteCode, t.TestSiteName, t.TestType')
->select('t.TestSiteID as TestSiteID, t.TestSiteCode, t.TestSiteName, t.TestType, t.SeqScr, t. SeqRpt')
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
->where('testdefgrp.TestSiteID', $testSiteID)
->where('testdefgrp.EndDate IS NULL')
->orderBy('t.SeqScr', 'ASC')
->orderBy('t.SeqRpt', 'ASC')
->orderBy('testdefgrp.TestGrpID', 'ASC')
->get()->getResultArray();

View File

@ -158,17 +158,8 @@ class TestDefSiteModel extends BaseModel {
$row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
} elseif (TestValidationService::isGroup($typeCode)) {
$row['testdefgrp'] = $db->table('testdefgrp')
->select('testdefgrp.*, t.TestSiteID as MemberTestSiteID, t.TestSiteCode, t.TestSiteName, t.TestType')
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
->where('testdefgrp.TestSiteID', $TestSiteID)
->where('testdefgrp.EndDate IS NULL')
->orderBy('testdefgrp.TestGrpID', 'ASC')
->get()->getResultArray();
$row['testdefgrp'] = ValueSet::transformLabels($row['testdefgrp'], [
'TestType' => 'test_type',
]);
$testDefGrpModel = new \App\Models\Test\TestDefGrpModel();
$row['testdefgrp'] = $testDefGrpModel->getGroupMembers($TestSiteID);
$testMapModel = new \App\Models\Test\TestMapModel();
$row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
@ -230,4 +221,45 @@ class TestDefSiteModel extends BaseModel {
{
return TestValidationService::getReferenceTable($resultType, $refType);
}
/**
* Get test by ID with ValueSet transformation
*
* @param int $id
* @return array|null
*/
public function getTestById($id)
{
$row = $this->select('testdefsite.*')
->where('testdefsite.TestSiteID', $id)
->find($id);
if (!$row) {
return null;
}
$rows = ValueSet::transformLabels([$row], [
'TestType' => 'test_type',
]);
return $rows[0];
}
/**
* Get technical test with discipline and department relations
*
* @param int $testSiteID
* @return array
*/
public function getTestTechWithRelations($testSiteID)
{
return $this->db->table('testdefsite')
->select('testdefsite.*, d.DisciplineName, dept.DepartmentName')
->join('discipline d', 'd.DisciplineID=testdefsite.DisciplineID', 'left')
->join('department dept', 'dept.DepartmentID=testdefsite.DepartmentID', 'left')
->where('testdefsite.TestSiteID', $testSiteID)
->where('testdefsite.EndDate IS NULL')
->get()
->getResultArray();
}
}

View File

@ -50,4 +50,19 @@ class TestMapDetailModel extends BaseModel {
->where('EndDate IS NULL')
->findAll();
}
/**
* Disable all details for a test map
*
* @param int $testMapID
* @return void
*/
public function disableByTestMapID($testMapID)
{
$this->where('TestMapID', $testMapID)
->where('EndDate', null)
->set('EndDate', date('Y-m-d H:i:s'))
->update();
}
}

View File

@ -5519,6 +5519,8 @@ components:
type: string
SiteCode:
type: string
maxLength: 2
pattern: ^[A-Z0-9]{2}$
AccountID:
type: integer
Discipline:

View File

@ -19,6 +19,8 @@ Site:
type: string
SiteCode:
type: string
maxLength: 2
pattern: '^[A-Z0-9]{2}$'
AccountID:
type: integer
@ -31,6 +33,12 @@ Discipline:
type: string
DisciplineCode:
type: string
SeqScr:
type: integer
description: Display order on screen
SeqRpt:
type: integer
description: Display order in reports
Department:
type: object

View File

@ -150,6 +150,12 @@
type: string
DisciplineCode:
type: string
SeqScr:
type: integer
description: Display order on screen
SeqRpt:
type: integer
description: Display order in reports
responses:
'200':
description: Discipline updated