feat(valueset): refactor from ID-based to name-based lookups
Complete overhaul of the valueset system to use human-readable names
instead of numeric IDs for improved maintainability and API consistency.
- PatientController: Renamed 'Gender' field to 'Sex' in validation rules
- ValuesetController: Changed API endpoints from ID-based (/:num) to name-based (/:any)
- TestsController: Refactored to use ValueSet library instead of direct valueset queries
- Added ValueSet library (app/Libraries/ValueSet.php) with static lookup methods:
- getOptions() - returns dropdown format [{value, label}]
- getLabel(, ) - returns label for a value
- transformLabels(, ) - batch transform records
- get() and getRaw() for Lookups compatibility
- Added ValueSetApiController for public valueset API endpoints
- Added ValueSet refresh endpoint (POST /api/valueset/refresh)
- Added DemoOrderController for testing order creation without auth
- 2026-01-12-000001: Convert valueset references from VID to VValue
- 2026-01-12-000002: Rename patient.Gender column to Sex
- OrderTestController: Now uses OrderTestModel with proper model pattern
- TestsController: Uses ValueSet library for all lookup operations
- ValueSetController: Simplified to use name-based lookups
- Updated all organization (account/site/workstation) dialogs and index views
- Updated specimen container dialogs and index views
- Updated tests_index.php with ValueSet integration
- Updated patient dialog form and index views
- Removed .factory/config.json and CLAUDE.md (replaced by AGENTS.md)
- Consolidated lookups in Lookups.php (removed inline valueset constants)
- Updated all test files to match new field names
- 32 modified files, 17 new files, 2 deleted files
- Net: +661 insertions, -1443 deletions (significant cleanup)
This commit is contained in:
parent
f11bde4d30
commit
bb7df6b70c
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"custom_models": [
|
|
||||||
{
|
|
||||||
"model_display_name": "MiniMax-M2.1",
|
|
||||||
"model": "MiniMax-M2.1",
|
|
||||||
"base_url": "https://api.minimax.io/anthropic",
|
|
||||||
"api_key": "sk-cp-eMsvq_OqP6UiCBirrr3W6gZlG6-NXnIQeneGNpAJ8aWxywzNq5I9mibfQFBBy84C2Mm7jCqMtjKmbpnx6h02nz_D7xG6ETmBY4K6Nog454cYs_ZkYgMyG_g",
|
|
||||||
"provider": "anthropic",
|
|
||||||
"max_tokens": 64000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
221
AGENTS.md
Normal file
221
AGENTS.md
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
# CLQMS Backend - Agent Instructions
|
||||||
|
|
||||||
|
**Project:** Clinical Laboratory Quality Management System (CLQMS) Backend
|
||||||
|
**Framework:** CodeIgniter 4 (PHP 8.1+)
|
||||||
|
**Platform:** Windows - Use PowerShell or CMD for terminal commands
|
||||||
|
**Frontend:** Alpine.js (views/v2 directory)
|
||||||
|
|
||||||
|
## Build / Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
composer install
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
composer test
|
||||||
|
php vendor/bin/phpunit
|
||||||
|
|
||||||
|
# Run single test file
|
||||||
|
php vendor/bin/phpunit tests/feature/Patients/PatientIndexTest.php
|
||||||
|
|
||||||
|
# Run single test method
|
||||||
|
php vendor/bin/phpunit tests/feature/Patients/PatientIndexTest.php --filter=testIndexWithoutParams
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
php vendor/bin/phpunit --coverage-html build/logs/html
|
||||||
|
|
||||||
|
# Run tests in verbose mode
|
||||||
|
php vendor/bin/phpunit --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test Structure:**
|
||||||
|
- Feature tests: `tests/feature/` - API endpoint testing with `FeatureTestTrait`
|
||||||
|
- Unit tests: `tests/unit/` - Model/Logic testing
|
||||||
|
- Base test case: `Tests\Support\v2\MasterTestCase.php` - Provides JWT auth and helper methods
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### PHP Standards
|
||||||
|
- **PHP Version:** 8.1 minimum
|
||||||
|
- **PSR-4 Autoloading:** Follow namespace-to-path conventions (`App\Controllers\*`, `App\Models\*`)
|
||||||
|
- **Line endings:** Unix-style (LF) - configure editor accordingly
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
| Element | Convention | Examples |
|
||||||
|
|---------|------------|----------|
|
||||||
|
| Classes | PascalCase | `PatientController`, `BaseModel` |
|
||||||
|
| Methods | camelCase | `getPatient()`, `createPatient()` |
|
||||||
|
| Variables | camelCase | `$internalPID`, `$patientData` |
|
||||||
|
| Constants | UPPER_SNAKE_CASE | `ORDER_PRIORITY`, `TEST_TYPE` |
|
||||||
|
| Table names | snake_case | `patient`, `pat_idt`, `valueset` |
|
||||||
|
| Column names | PascalCase (original DB) | `InternalPID`, `PatientID` |
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── Controllers/{Domain}/
|
||||||
|
│ └── DomainController.php
|
||||||
|
├── Models/{Domain}/
|
||||||
|
│ └── DomainModel.php
|
||||||
|
├── Libraries/
|
||||||
|
│ └── Lookups.php
|
||||||
|
└── Views/v2/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Imports and Namespaces
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
namespace App\Controllers\Patient;
|
||||||
|
|
||||||
|
use CodeIgniter\Controller;
|
||||||
|
use CodeIgniter\API\ResponseTrait;
|
||||||
|
use App\Models\Patient\PatientModel;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Use fully qualified class names or `use` statements
|
||||||
|
- Group imports logically
|
||||||
|
- Avoid unnecessary aliases
|
||||||
|
|
||||||
|
### Code Formatting
|
||||||
|
- **Indentation:** 4 spaces (not tabs)
|
||||||
|
- **Braces:** Allman style for classes/functions, K&R for control structures
|
||||||
|
- **Line length:** Soft limit 120 characters
|
||||||
|
- **Empty lines:** Single blank line between method definitions and logical groups
|
||||||
|
|
||||||
|
### Type Hints and Return Types
|
||||||
|
```php
|
||||||
|
// Required for new code
|
||||||
|
public function getPatient(int $internalPID): ?array
|
||||||
|
protected function createPatient(array $input): int
|
||||||
|
private function checkDbError(object $db, string $context): void
|
||||||
|
|
||||||
|
// Use nullable types for optional returns
|
||||||
|
public function findById(?int $id): ?array
|
||||||
|
```
|
||||||
|
|
||||||
|
### Controller Patterns
|
||||||
|
```php
|
||||||
|
class PatientController extends Controller {
|
||||||
|
use ResponseTrait;
|
||||||
|
|
||||||
|
protected $db;
|
||||||
|
protected $model;
|
||||||
|
protected $rules;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = \Config\Database::connect();
|
||||||
|
$this->model = new PatientModel();
|
||||||
|
$this->rules = [...]; // Validation rules
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
try {
|
||||||
|
$data = $this->model->findAll();
|
||||||
|
return $this->respond([...], 200);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->failServerError($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model Patterns
|
||||||
|
```php
|
||||||
|
class PatientModel extends BaseModel {
|
||||||
|
protected $table = 'patient';
|
||||||
|
protected $primaryKey = 'InternalPID';
|
||||||
|
protected $allowedFields = [...];
|
||||||
|
protected $useSoftDeletes = true;
|
||||||
|
protected $deletedField = 'DelDate';
|
||||||
|
|
||||||
|
public function getPatients(array $filters = []): array {
|
||||||
|
// Query builder chain
|
||||||
|
$this->select('...');
|
||||||
|
$this->join(...);
|
||||||
|
if (!empty($filters['key'])) {
|
||||||
|
$this->where('key', $filters['key']);
|
||||||
|
}
|
||||||
|
return $this->findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Controllers: Use try-catch with `failServerError()`, `failValidationErrors()`, `failNotFound()`
|
||||||
|
- Models: Throw `\Exception` with descriptive messages
|
||||||
|
- Database errors: Check `$db->error()` after operations
|
||||||
|
- Always validate input before DB operations
|
||||||
|
|
||||||
|
### Validation Rules
|
||||||
|
```php
|
||||||
|
protected $rules = [
|
||||||
|
'PatientID' => 'required|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
|
||||||
|
'EmailAddress' => 'permit_empty|valid_email|max_length[100]',
|
||||||
|
'Phone' => 'permit_empty|regex_match[/^\+?[0-9]{8,15}$/]',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Date Handling
|
||||||
|
- All dates stored/retrieved in UTC via `BaseModel` callbacks
|
||||||
|
- Use `utc` helper functions: `convert_array_to_utc()`, `convert_array_to_utc_iso()`
|
||||||
|
- Format: ISO 8601 (`Y-m-d\TH:i:s\Z`) for API responses
|
||||||
|
|
||||||
|
### API Response Format
|
||||||
|
```php
|
||||||
|
// Success
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Data fetched successfully',
|
||||||
|
'data' => $rows
|
||||||
|
], 200);
|
||||||
|
|
||||||
|
// Created
|
||||||
|
return $this->respondCreated([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Record created'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Error
|
||||||
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Transactions
|
||||||
|
```php
|
||||||
|
$db->transBegin();
|
||||||
|
try {
|
||||||
|
$this->insert($data);
|
||||||
|
$this->checkDbError($db, 'Insert operation');
|
||||||
|
$db->transCommit();
|
||||||
|
return $insertId;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$db->transRollback();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Integration (Alpine.js)
|
||||||
|
- API calls use `BASEURL` global variable
|
||||||
|
- Include `credentials: 'include'` for authenticated requests
|
||||||
|
- Modals use `x-show` with `@click.self` backdrop close
|
||||||
|
|
||||||
|
### Lookups Library
|
||||||
|
Use `App\Libraries\Lookups` for all static lookup values - no database queries:
|
||||||
|
```php
|
||||||
|
use App\Libraries\Lookups;
|
||||||
|
|
||||||
|
// Frontend dropdown format
|
||||||
|
Lookups::get('gender'); // [{value: '1', label: 'Female'}, ...]
|
||||||
|
Lookups::get('test_type'); // [{value: 'TEST', label: 'Test'}, ...]
|
||||||
|
|
||||||
|
// Raw key-value pairs
|
||||||
|
Lookups::getRaw('gender'); // ['1' => 'Female', ...]
|
||||||
|
|
||||||
|
// All lookups
|
||||||
|
Lookups::getAll();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important Notes
|
||||||
|
- **Soft deletes:** Use `DelDate` field instead of hard delete
|
||||||
|
- **UTC timezone:** All dates normalized to UTC automatically
|
||||||
|
- **JWT auth:** API endpoints require Bearer token in `Authorization` header
|
||||||
|
- **No comments:** Do not add comments unless explicitly requested
|
||||||
174
CLAUDE.md
174
CLAUDE.md
@ -1,174 +0,0 @@
|
|||||||
# CLQMS Backend - Claude Code Instructions
|
|
||||||
|
|
||||||
**Project:** Clinical Laboratory Quality Management System (CLQMS) Backend
|
|
||||||
**Framework:** CodeIgniter 4 (PHP)
|
|
||||||
**Platform:** Windows - Use PowerShell or CMD for terminal commands
|
|
||||||
**Frontend:** Alpine.js (views/v2 directory contains Alpine.js components)
|
|
||||||
|
|
||||||
### Views/V2 Structure
|
|
||||||
```
|
|
||||||
app/Views/v2/
|
|
||||||
├── layout/
|
|
||||||
│ └── main_layout.php # Main layout with sidebar, navbar, Alpine.js layout() component
|
|
||||||
├── auth/
|
|
||||||
│ └── login.php # Login page
|
|
||||||
├── dashboard/
|
|
||||||
│ └── dashboard_index.php # Dashboard view
|
|
||||||
├── patients/
|
|
||||||
│ ├── patients_index.php # Patient list with x-data="patients()" component
|
|
||||||
│ └── dialog_form.php # Patient form dialog
|
|
||||||
├── requests/
|
|
||||||
│ └── requests_index.php # Lab requests
|
|
||||||
├── settings/
|
|
||||||
│ └── settings_index.php # Settings page
|
|
||||||
└── master/
|
|
||||||
├── organization/ # Organization management (accounts, sites, disciplines, departments, workstations)
|
|
||||||
├── specimen/ # Specimen management (containers, preparations)
|
|
||||||
├── tests/ # Lab tests (tests_index, param_dialog, grp_dialog, calc_dialog)
|
|
||||||
└── valuesets/ # Value sets management
|
|
||||||
```
|
|
||||||
|
|
||||||
### Alpine.js Patterns
|
|
||||||
- **Global layout:** `layout()` function in `main_layout.php` handles sidebar state, theme toggle, and navigation
|
|
||||||
- **Page components:** Each page uses `x-data="componentName()"` (e.g., `x-data="patients()"`)
|
|
||||||
- **API calls:** Use `fetch()` with `BASEURL` global variable and `credentials: 'include'`
|
|
||||||
- **Dialogs:** Modals use `x-show` with `@click.self` backdrop click to close
|
|
||||||
- **TailwindCSS 4:** Loaded via CDN with custom CSS variables for theming
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
**Static Library:**
|
|
||||||
- [`Lookups`](app/Libraries/Lookups.php) - Static lookup constants (no database queries)
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
```php
|
|
||||||
use App\Libraries\Lookups;
|
|
||||||
|
|
||||||
// Get formatted lookup [{value: 'KEY', label: 'Label'}, ...]
|
|
||||||
Lookups::get('gender');
|
|
||||||
|
|
||||||
// Get raw associative array ['KEY' => 'Label', ...]
|
|
||||||
Lookups::getRaw('gender');
|
|
||||||
|
|
||||||
// Get all lookups for frontend
|
|
||||||
Lookups::getAll();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Agent Workflow for Valueset Queries
|
|
||||||
|
|
||||||
Use `Lookups` class for all lookup queries - no database queries needed.
|
|
||||||
|
|
||||||
### Step 1: Identify the Lookup Constant
|
|
||||||
|
|
||||||
**By Category Name:** Match VSName to constant name (e.g., "Gender" → `Lookups::GENDER`)
|
|
||||||
**By Reference:** Match VSDesc to constant name (e.g., `testdefsite.TestType` → `Lookups::TEST_TYPE`)
|
|
||||||
**By VSetDefID:** Map VSetDefID to constant (see Common Lookups table below)
|
|
||||||
|
|
||||||
### Step 2: Retrieve Values
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Formatted for frontend dropdowns
|
|
||||||
Lookups::get('gender'); // [{value: '1', label: 'Female'}, ...]
|
|
||||||
|
|
||||||
// Raw key-value pairs
|
|
||||||
Lookups::getRaw('gender'); // ['1' => 'Female', '2' => 'Male', ...]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Return Results
|
|
||||||
|
|
||||||
**Response Format (formatted):**
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "value": "1", "label": "Female" },
|
|
||||||
{ "value": "2", "label": "Male" },
|
|
||||||
{ "value": "3", "label": "Unknown" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Lookups
|
|
||||||
|
|
||||||
| VSetDefID | Constant | Search Keywords |
|
|
||||||
|-----------|----------|-----------------|
|
|
||||||
| 1 | `WS_TYPE` | workstation, type |
|
|
||||||
| 2 | `ENABLE_DISABLE` | enable, disable |
|
|
||||||
| 3 | `GENDER` | gender, sex |
|
|
||||||
| 4 | `MARITAL_STATUS` | marital, status |
|
|
||||||
| 5 | `DEATH_INDICATOR` | death, indicator |
|
|
||||||
| 6 | `IDENTIFIER_TYPE` | identifier, type, KTP, passport |
|
|
||||||
| 7 | `OPERATION` | operation, CRUD |
|
|
||||||
| 8 | `DID_TYPE` | device, ID, AAID, IDFA |
|
|
||||||
| 9 | `REQUESTED_ENTITY` | requested, entity, patient, insurance |
|
|
||||||
| 10 | `ORDER_PRIORITY` | priority, order, stat, ASAP |
|
|
||||||
| 11 | `ORDER_STATUS` | status, order |
|
|
||||||
| 12 | `LOCATION_TYPE` | location, type |
|
|
||||||
| 13 | `ADDITIVE` | additive, heparin, EDTA |
|
|
||||||
| 14 | `CONTAINER_CLASS` | container, class |
|
|
||||||
| 15 | `SPECIMEN_TYPE` | specimen, type, blood, urine |
|
|
||||||
| 16 | `UNIT` | unit |
|
|
||||||
| 17 | `GENERATE_BY` | generate, by |
|
|
||||||
| 18 | `SPECIMEN_ACTIVITY` | specimen, activity |
|
|
||||||
| 19 | `ACTIVITY_RESULT` | activity, result |
|
|
||||||
| 20 | `SPECIMEN_STATUS` | specimen, status |
|
|
||||||
| 21 | `SPECIMEN_CONDITION` | specimen, condition |
|
|
||||||
| 22 | `SPECIMEN_ROLE` | specimen, role |
|
|
||||||
| 23 | `COLLECTION_METHOD` | collection, method |
|
|
||||||
| 24 | `BODY_SITE` | body, site |
|
|
||||||
| 25 | `CONTAINER_SIZE` | container, size |
|
|
||||||
| 26 | `FASTING_STATUS` | fasting, status |
|
|
||||||
| 27 | `TEST_TYPE` | test, type, testdefsite |
|
|
||||||
| 28 | `RESULT_UNIT` | result, unit |
|
|
||||||
| 29 | `FORMULA_LANGUAGE` | formula, language |
|
|
||||||
| 30 | `RACE` | race, ethnicity |
|
|
||||||
| 31 | `RELIGION` | religion |
|
|
||||||
| 32 | `ETHNIC` | ethnic |
|
|
||||||
| 33 | `COUNTRY` | country (loaded from external file) |
|
|
||||||
| 34 | `CONTAINER_CAP_COLOR` | container, cap, color |
|
|
||||||
| 35 | `TEST_ACTIVITY` | test, activity |
|
|
||||||
| 36 | `ADT_EVENT` | ADT, event |
|
|
||||||
| 37 | `SITE_TYPE` | site, type |
|
|
||||||
| 38 | `SITE_CLASS` | site, class |
|
|
||||||
| 39 | `ENTITY_TYPE` | entity, type |
|
|
||||||
| 40 | `AREA_CLASS` | area, class |
|
|
||||||
| 41 | `MATH_SIGN` | math, sign |
|
|
||||||
| 42 | `V_CATEGORY` | category |
|
|
||||||
| 43 | `RESULT_TYPE` | result, type |
|
|
||||||
| 44 | `REFERENCE_TYPE` | reference, type |
|
|
||||||
| 45 | `RANGE_TYPE` | range, type |
|
|
||||||
| 46 | `NUMERIC_REF_TYPE` | numeric, reference |
|
|
||||||
| 47 | `TEXT_REF_TYPE` | text, reference |
|
|
||||||
|
|
||||||
**Convenience Aliases:**
|
|
||||||
- `Lookups::PRIORITY` → alias for `ORDER_PRIORITY`
|
|
||||||
- `Lookups::TEST_STATUS` → Test status values
|
|
||||||
- `Lookups::REQUEST_STATUS` → alias for `SPECIMEN_STATUS`
|
|
||||||
- `Lookups::RESULT_STATUS` → Result status values
|
|
||||||
|
|
||||||
## Example Agent Conversations
|
|
||||||
|
|
||||||
**User:** "Show me Gender values"
|
|
||||||
**Agent:**
|
|
||||||
1. `Lookups::get('gender')` → Returns formatted array
|
|
||||||
2. Output: Female, Male, Unknown
|
|
||||||
|
|
||||||
**User:** "What values for testdefsite.TestType?"
|
|
||||||
**Agent:**
|
|
||||||
1. `Lookups::get('test_type')` → Returns formatted array
|
|
||||||
2. Output: TEST, PARAM, CALC, GROUP, TITLE
|
|
||||||
|
|
||||||
**User:** "Find specimen status options"
|
|
||||||
**Agent:**
|
|
||||||
1. `Lookups::get('specimen_status')` → Returns formatted array
|
|
||||||
2. Output: To be collected, Collected, In-transport, Arrived, etc.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Commanding Officer Persona Mode
|
|
||||||
|
|
||||||
When the user addresses you as their commanding officer or in a starship context, respond accordingly:
|
|
||||||
|
|
||||||
- Address the officer respectfully ("Commander", "Captain", "Sir/Ma'am")
|
|
||||||
- Use military/space command terminology ("affirmative", "reporting", "orders", "status")
|
|
||||||
- Frame technical responses in mission-ops format ("Systems operational", "Data retrieved", "Report ready")
|
|
||||||
- Keep responses crisp and professional, befitting ship command
|
|
||||||
- Example: "Commander, the valueset data you requested is ready for review."
|
|
||||||
- Start something with basmalah and end with hamdalah
|
|
||||||
284
MVP_TODO.md
Normal file
284
MVP_TODO.md
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
# CLQMS MVP Todo List
|
||||||
|
|
||||||
|
> Clinical Laboratory Quality Management System - Minimum Viable Product
|
||||||
|
|
||||||
|
## Quick Start: Create Order with Minimal Master Data
|
||||||
|
|
||||||
|
You **don't need** all master data finished to create an order. Here's what's actually required:
|
||||||
|
|
||||||
|
### Minimum Required (4 Tables)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. Patient (already exists in codebase)
|
||||||
|
-- Just need at least 1 patient
|
||||||
|
|
||||||
|
-- 2. Order Status Values (VSetID=11)
|
||||||
|
INSERT INTO valueset (VID, VSetID, VValue, VDesc, VOrder) VALUES
|
||||||
|
(1, 11, 'ORD', 'Ordered', 1),
|
||||||
|
(2, 11, 'SCH', 'Scheduled', 2),
|
||||||
|
(3, 11, 'ANA', 'Analysis', 3),
|
||||||
|
(4, 11, 'VER', 'Verified', 4),
|
||||||
|
(5, 11, 'REV', 'Reviewed', 5),
|
||||||
|
(6, 11, 'REP', 'Reported', 6);
|
||||||
|
|
||||||
|
-- 3. Priority Values (VSetID=10)
|
||||||
|
INSERT INTO valueset (VID, VSetID, VValue, VDesc, VOrder) VALUES
|
||||||
|
(1, 10, 'S', 'Stat', 1),
|
||||||
|
(2, 10, 'R', 'Routine', 2),
|
||||||
|
(3, 10, 'A', 'ASAP', 3);
|
||||||
|
|
||||||
|
-- 4. Counter for Order ID
|
||||||
|
INSERT INTO counter (CounterName, CounterValue) VALUES ('ORDER', 1);
|
||||||
|
|
||||||
|
-- Run seeder: php spark db:seed MinimalMasterDataSeeder
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoints (No Auth Required for Testing)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create demo order (auto-creates patient if needed)
|
||||||
|
POST /api/demo/order
|
||||||
|
{
|
||||||
|
"PatientID": "PT001",
|
||||||
|
"NameFirst": "John",
|
||||||
|
"NameLast": "Doe",
|
||||||
|
"Gender": "1",
|
||||||
|
"Birthdate": "1990-05-15",
|
||||||
|
"Priority": "R",
|
||||||
|
"OrderingProvider": "Dr. Smith"
|
||||||
|
}
|
||||||
|
|
||||||
|
# List orders
|
||||||
|
GET /api/demo/orders
|
||||||
|
|
||||||
|
# Create order (requires auth)
|
||||||
|
POST /api/ordertest
|
||||||
|
{
|
||||||
|
"InternalPID": 1,
|
||||||
|
"Priority": "R",
|
||||||
|
"OrderingProvider": "Dr. Smith"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update order status
|
||||||
|
POST /api/ordertest/status
|
||||||
|
{
|
||||||
|
"OrderID": "00250112000001",
|
||||||
|
"OrderStatus": "SCH"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Workflow
|
||||||
|
Order → Collection → Reception → Preparation → Analysis → Verification → Review → Reporting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Core Lab Workflow (Must Have)
|
||||||
|
|
||||||
|
### 1.1 Order Management
|
||||||
|
- [ ] Complete `OrderTestController` create/update/delete
|
||||||
|
- [ ] Implement order ID generation (LLYYMMDDXXXXX format)
|
||||||
|
- [ ] Implement order attachment handling (ordercom, orderatt tables)
|
||||||
|
- [ ] Add order status tracking (ORD→SCH→ANA→VER→REV→REP)
|
||||||
|
- [ ] Create order test mapping (testmap table)
|
||||||
|
- [ ] Add calculated test parameter auto-selection
|
||||||
|
|
||||||
|
### 1.2 Specimen Management
|
||||||
|
- [ ] Complete `SpecimenController` API
|
||||||
|
- [ ] Implement specimen ID generation (OrderID + SSS + C)
|
||||||
|
- [ ] Build specimen collection API (Collection status)
|
||||||
|
- [ ] Build specimen transport API (In-transport status)
|
||||||
|
- [ ] Build specimen reception API (Received/Rejected status)
|
||||||
|
- [ ] Build specimen preparation API (Centrifuge, Aliquot, Pre-treatment)
|
||||||
|
- [ ] Build specimen storage API (Stored status)
|
||||||
|
- [ ] Build specimen dispatching API (Dispatch status)
|
||||||
|
- [ ] Implement specimen condition tracking (HEM, ITC, LIP flags)
|
||||||
|
|
||||||
|
### 1.3 Result Management
|
||||||
|
- [ ] Complete `ResultController` with full CRUD
|
||||||
|
- [ ] Implement result entry API (numeric, text, valueset, range)
|
||||||
|
- [ ] Implement result verification workflow (Technical + Clinical)
|
||||||
|
- [ ] Add reference range validation (numeric, threshold, text, valueset)
|
||||||
|
- [ ] Implement critical value flagging (threshold-based)
|
||||||
|
- [ ] Implement result rerun with AspCnt tracking
|
||||||
|
- [ ] Add result report generation API
|
||||||
|
|
||||||
|
### 1.4 Patient Visit
|
||||||
|
- [ ] Complete `PatVisitController` create/read
|
||||||
|
- [ ] Implement patient visit to order linking
|
||||||
|
- [ ] Add admission/discharge/transfer (ADT) tracking
|
||||||
|
- [ ] Add diagnosis linking (patdiag table)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Instrument Integration (Must Have)
|
||||||
|
|
||||||
|
### 2.1 Edge API
|
||||||
|
- [ ] Complete `EdgeController` results endpoint
|
||||||
|
- [ ] Implement edgeres table data handling
|
||||||
|
- [ ] Implement edgestatus tracking
|
||||||
|
- [ ] Implement edgeack acknowledgment
|
||||||
|
- [ ] Build instrument orders endpoint (/api/edge/orders)
|
||||||
|
- [ ] Build order acknowledgment endpoint (/api/edge/orders/:id/ack)
|
||||||
|
- [ ] Build status logging endpoint (/api/edge/status)
|
||||||
|
|
||||||
|
### 2.2 Test Mapping
|
||||||
|
- [ ] Implement test mapping CRUD (TestMapModel)
|
||||||
|
- [ ] Build instrument code to LQMS test mapping
|
||||||
|
- [ ] Add many-to-one mapping support (e.g., glucose variations)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Quality Management (Should Have)
|
||||||
|
|
||||||
|
### 3.1 Quality Control
|
||||||
|
- [ ] Build QC result entry API
|
||||||
|
- [ ] Implement QC result storage (calres table)
|
||||||
|
- [ ] Add Levey-Jennings data preparation endpoints
|
||||||
|
- [ ] Implement QC validation (2SD auto-validation)
|
||||||
|
- [ ] Add Sigma score calculation endpoint
|
||||||
|
|
||||||
|
### 3.2 Calibration
|
||||||
|
- [ ] Build calibration result entry API
|
||||||
|
- [ ] Implement calibration factor tracking
|
||||||
|
- [ ] Add calibration history endpoint
|
||||||
|
- [ ] Implement calibration validation
|
||||||
|
|
||||||
|
### 3.3 Audit Trail
|
||||||
|
- [ ] Add audit logging middleware
|
||||||
|
- [ ] Implement data change tracking (what/who/when/how/where)
|
||||||
|
- [ ] Build audit query endpoint
|
||||||
|
- [ ] Add security log endpoints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Master Data (Already Have - Need Completion)
|
||||||
|
|
||||||
|
### 4.1 Test Definitions ✅ Existing
|
||||||
|
- [ ] Test definitions (testdefsite)
|
||||||
|
- [ ] Technical specs (testdeftech)
|
||||||
|
- [ ] Calculated tests (testdefcal)
|
||||||
|
- [ ] Group tests (testdefgrp)
|
||||||
|
- [ ] Test parameters
|
||||||
|
|
||||||
|
### 4.2 Reference Ranges ✅ Existing
|
||||||
|
- [ ] Numeric ranges (refnum)
|
||||||
|
- [ ] Threshold ranges (refthold)
|
||||||
|
- [ ] Text ranges (reftxt)
|
||||||
|
- [ ] Value set ranges (refvset)
|
||||||
|
|
||||||
|
### 4.3 Organizations ✅ Existing
|
||||||
|
- [ ] Sites (SiteController)
|
||||||
|
- [ ] Departments (DepartmentController)
|
||||||
|
- [ ] Workstations (WorkstationController)
|
||||||
|
- [ ] Disciplines (DisciplineController)
|
||||||
|
|
||||||
|
### 4.4 Value Sets ✅ Existing
|
||||||
|
- [ ] Value set definitions (ValueSetDefController)
|
||||||
|
- [ ] Value set values (ValueSetController)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Inventory & Billing (Nice to Have)
|
||||||
|
|
||||||
|
### 5.1 Inventory
|
||||||
|
- [ ] Build counter management API
|
||||||
|
- [ ] Implement product catalog endpoints
|
||||||
|
- [ ] Add reagent tracking
|
||||||
|
- [ ] Implement consumables usage logging
|
||||||
|
|
||||||
|
### 5.2 Billing
|
||||||
|
- [ ] Add billing account linking
|
||||||
|
- [ ] Implement tariff selection by service class
|
||||||
|
- [ ] Build billing export endpoint
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority Matrix
|
||||||
|
|
||||||
|
| Priority | Feature | Controller/Model | Status |
|
||||||
|
|----------|---------|-----------------|--------|
|
||||||
|
| P0 | Order CRUD | OrderTestController + OrderTestModel | ✅ Done |
|
||||||
|
| P0 | Specimen Status | SpecimenController | ⚠️ Needs API |
|
||||||
|
| P0 | Result Entry | ResultController | ❌ Empty |
|
||||||
|
| P0 | Result Verification | ResultController | ❌ Empty |
|
||||||
|
| P1 | Visit Management | PatVisitController | ⚠️ Partial |
|
||||||
|
| P1 | Instrument Integration | EdgeController | ⚠️ Partial |
|
||||||
|
| P1 | Reference Range Validation | RefNumModel + API | ⚠️ Need API |
|
||||||
|
| P2 | QC Results | New Controller | ❌ Not exist |
|
||||||
|
| P2 | Audit Trail | New Model | ❌ Not exist |
|
||||||
|
| P3 | Inventory | CounterController | ⚠️ Partial |
|
||||||
|
| P3 | Billing | New Controller | ❌ Not exist |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Test: Does Order Creation Work?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test 1: Create demo order (no auth required)
|
||||||
|
curl -X POST http://localhost:8080/api/demo/order \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"NameFirst": "Test", "NameLast": "Patient"}'
|
||||||
|
|
||||||
|
# Expected Response:
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Demo order created successfully",
|
||||||
|
"data": {
|
||||||
|
"PatientID": "DEMO1736689600",
|
||||||
|
"InternalPID": 1,
|
||||||
|
"OrderID": "00250112000001",
|
||||||
|
"OrderStatus": "ORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test 2: Update order status
|
||||||
|
curl -X POST http://localhost:8080/api/ordertest/status \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <token>" \
|
||||||
|
-d '{"OrderID": "00250112000001", "OrderStatus": "SCH"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
### Functional
|
||||||
|
- Patient registration works ✅
|
||||||
|
- Test ordering generates valid OrderID and SID
|
||||||
|
- Specimens track through collection → transport → reception → preparation → analysis
|
||||||
|
- Results can be entered with reference range validation
|
||||||
|
- Results verified through VER → REV → REP workflow
|
||||||
|
- Instruments can send results via Edge API
|
||||||
|
|
||||||
|
### Non-Functional
|
||||||
|
- JWT authentication required for all endpoints
|
||||||
|
- Soft delete (DelDate) on all transactions
|
||||||
|
- UTC timezone for all datetime fields
|
||||||
|
- Audit logging for data changes
|
||||||
|
- < 2s response time for standard queries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Codebase Status
|
||||||
|
|
||||||
|
### Controllers (Need Work)
|
||||||
|
- ❌ OrderTestController - placeholder code, incomplete
|
||||||
|
- ❌ ResultController - only validates JWT
|
||||||
|
- ✅ PatientController - complete
|
||||||
|
- ✅ TestsController - complete
|
||||||
|
- ✅ PatVisitController - partial
|
||||||
|
|
||||||
|
### Models (Good)
|
||||||
|
- ✅ PatientModel - complete
|
||||||
|
- ✅ TestDef* models - complete
|
||||||
|
- ✅ Ref* models - complete
|
||||||
|
- ✅ ValueSet* models - complete
|
||||||
|
- ✅ SpecimenModel - exists, needs API
|
||||||
|
|
||||||
|
### Missing Controllers
|
||||||
|
- ❌ SpecimenController - need full implementation
|
||||||
|
- ❌ ResultController - need full implementation
|
||||||
|
- ❌ QualityControlController - not exist
|
||||||
|
- ❌ CalibrationController - not exist
|
||||||
|
- ❌ AuditController - not exist
|
||||||
|
- ❌ BillingController - not exist
|
||||||
@ -149,11 +149,11 @@ $routes->group('api', function ($routes) {
|
|||||||
// ValueSet
|
// ValueSet
|
||||||
$routes->group('valueset', function ($routes) {
|
$routes->group('valueset', function ($routes) {
|
||||||
$routes->get('/', 'ValueSet\ValueSetController::index');
|
$routes->get('/', 'ValueSet\ValueSetController::index');
|
||||||
$routes->get('(:num)', 'ValueSet\ValueSetController::show/$1');
|
$routes->get('(:any)', 'ValueSet\ValueSetController::showByName/$1');
|
||||||
$routes->get('valuesetdef/(:num)', 'ValueSet\ValueSetController::showByValueSetDef/$1');
|
|
||||||
$routes->post('/', 'ValueSet\ValueSetController::create');
|
$routes->post('/', 'ValueSet\ValueSetController::create');
|
||||||
$routes->patch('/', 'ValueSet\ValueSetController::update');
|
$routes->patch('/', 'ValueSet\ValueSetController::update');
|
||||||
$routes->delete('/', 'ValueSet\ValueSetController::delete');
|
$routes->delete('/', 'ValueSet\ValueSetController::delete');
|
||||||
|
$routes->post('refresh', 'ValueSet\ValueSetController::refresh');
|
||||||
});
|
});
|
||||||
|
|
||||||
$routes->group('valuesetdef', function ($routes) {
|
$routes->group('valuesetdef', function ($routes) {
|
||||||
@ -164,6 +164,17 @@ $routes->group('api', function ($routes) {
|
|||||||
$routes->delete('/', 'ValueSet\ValueSetDefController::delete');
|
$routes->delete('/', 'ValueSet\ValueSetDefController::delete');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$routes->group('valuesets', function ($routes) {
|
||||||
|
$routes->get('/', 'ValueSet\ValueSetApiController::index');
|
||||||
|
$routes->post('refresh', 'ValueSet\ValueSetApiController::refresh');
|
||||||
|
});
|
||||||
|
|
||||||
|
$routes->group('valueset', function ($routes) {
|
||||||
|
$routes->get('/', 'ValueSetApiController::index');
|
||||||
|
$routes->get('all', 'ValueSetApiController::all');
|
||||||
|
$routes->get('(:segment)', 'ValueSetApiController::index/$1');
|
||||||
|
});
|
||||||
|
|
||||||
// Counter
|
// Counter
|
||||||
$routes->group('counter', function ($routes) {
|
$routes->group('counter', function ($routes) {
|
||||||
$routes->get('/', 'CounterController::index');
|
$routes->get('/', 'CounterController::index');
|
||||||
@ -279,7 +290,23 @@ $routes->group('api', function ($routes) {
|
|||||||
$routes->patch('/', 'TestsController::update');
|
$routes->patch('/', 'TestsController::update');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edge API - Integration with tiny-edge
|
// Orders
|
||||||
|
$routes->group('ordertest', function ($routes) {
|
||||||
|
$routes->get('/', 'OrderTestController::index');
|
||||||
|
$routes->get('(:any)', 'OrderTestController::show/$1');
|
||||||
|
$routes->post('/', 'OrderTestController::create');
|
||||||
|
$routes->patch('/', 'OrderTestController::update');
|
||||||
|
$routes->delete('/', 'OrderTestController::delete');
|
||||||
|
$routes->post('status', 'OrderTestController::updateStatus');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo/Test Routes (No Auth)
|
||||||
|
$routes->group('api/demo', function ($routes) {
|
||||||
|
$routes->post('order', 'Test\DemoOrderController::createDemoOrder');
|
||||||
|
$routes->get('orders', 'Test\DemoOrderController::listDemoOrders');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edge API - Integration with tiny-edge
|
||||||
$routes->group('edge', function ($routes) {
|
$routes->group('edge', function ($routes) {
|
||||||
$routes->post('results', 'EdgeController::results');
|
$routes->post('results', 'EdgeController::results');
|
||||||
$routes->get('orders', 'EdgeController::orders');
|
$routes->get('orders', 'EdgeController::orders');
|
||||||
|
|||||||
@ -3,215 +3,190 @@ namespace App\Controllers;
|
|||||||
|
|
||||||
use CodeIgniter\API\ResponseTrait;
|
use CodeIgniter\API\ResponseTrait;
|
||||||
use CodeIgniter\Controller;
|
use CodeIgniter\Controller;
|
||||||
use CodeIgniter\Database\RawSql;
|
use App\Models\OrderTest\OrderTestModel;
|
||||||
|
use App\Models\Patient\PatientModel;
|
||||||
|
use App\Models\PatVisit\PatVisitModel;
|
||||||
|
|
||||||
class OrderTestController extends Controller {
|
class OrderTestController extends Controller {
|
||||||
use ResponseTrait;
|
use ResponseTrait;
|
||||||
|
|
||||||
|
protected $db;
|
||||||
|
protected $model;
|
||||||
|
protected $patientModel;
|
||||||
|
protected $visitModel;
|
||||||
|
protected $rules;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->db = \Config\Database::connect();
|
$this->db = \Config\Database::connect();
|
||||||
$this->rulesOrderTest = [
|
$this->model = new OrderTestModel();
|
||||||
'NameFirst' => 'required'
|
$this->patientModel = new PatientModel();
|
||||||
|
$this->visitModel = new PatVisitModel();
|
||||||
|
$this->rules = [
|
||||||
|
'InternalPID' => 'required|is_natural'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index() {
|
public function index() {
|
||||||
$rows = $this->db->table('ordertest')->select("*")->get()->getResultArray();
|
$internalPID = $this->request->getVar('InternalPID');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($internalPID) {
|
||||||
|
$rows = $this->model->getOrdersByPatient($internalPID);
|
||||||
|
} else {
|
||||||
|
$rows = $this->db->table('ordertest')
|
||||||
|
->where('DelDate', null)
|
||||||
|
->orderBy('OrderDateTime', 'DESC')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($rows)) {
|
|
||||||
return $this->respond([
|
return $this->respond([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => "no Data.",
|
'message' => 'Data fetched successfully',
|
||||||
'data' => [],
|
'data' => $rows
|
||||||
], 200);
|
], 200);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respond([
|
|
||||||
'status' => 'success',
|
|
||||||
'message'=> "fetch success",
|
|
||||||
'data' => $rows,
|
|
||||||
], 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show($OrderID = null) {
|
public function show($orderID = null) {
|
||||||
$row=$this->db->table('ordertest')->select("*")->where('OrderID', $OrderID)->get()->getRowArray();
|
try {
|
||||||
|
$row = $this->model->getOrder($orderID);
|
||||||
if (empty($row)) {
|
if (empty($row)) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Data not found.',
|
||||||
|
'data' => null
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
return $this->respond([
|
return $this->respond([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => "Data not found.",
|
'message' => 'Data fetched successfully',
|
||||||
'data' => null,
|
'data' => $row
|
||||||
], 200);
|
], 200);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respond([
|
|
||||||
'status' => 'success',
|
|
||||||
'message'=> "Data fetched successfully",
|
|
||||||
'data' => $row,
|
|
||||||
], 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create() {
|
public function create() {
|
||||||
|
$input = $this->request->getJSON(true);
|
||||||
|
|
||||||
|
if (!$this->validateData($input, $this->rules)) {
|
||||||
|
return $this->failValidationErrors($this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$input = $this->request->getJSON(true);
|
if (!$this->patientModel->find($input['InternalPID'])) {
|
||||||
|
return $this->failValidationErrors(['InternalPID' => 'Patient not found']);
|
||||||
// Prepare data
|
|
||||||
$dataOrderTest = $this->prepareOrderTestData($input);
|
|
||||||
$dataOrderCom = $this->prepareOrderComData($input);
|
|
||||||
$dataOrderAtt = $this->prepareOrderAttData($input);
|
|
||||||
|
|
||||||
if (!$this->validateData($dataLocation, $this->rules)) {
|
|
||||||
return $this->failValidationErrors($this->validator->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start transaction
|
if (!empty($input['PatVisitID'])) {
|
||||||
$this->db->transStart();
|
$visit = $this->visitModel->find($input['PatVisitID']);
|
||||||
|
if (!$visit) {
|
||||||
// Insert location
|
return $this->failValidationErrors(['PatVisitID' => 'Visit not found']);
|
||||||
$this->db->table('location')->insert($dataLocation);
|
}
|
||||||
$newLocationID = $this->db->insertID();
|
|
||||||
|
|
||||||
// Insert address if available
|
|
||||||
if (!empty($dataLocationAddress)) {
|
|
||||||
$dataLocationAddress['LocationID'] = $newLocationID;
|
|
||||||
$this->db->table('locationaddress')->insert($dataLocationAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete transaction
|
$orderID = $this->model->createOrder($input);
|
||||||
$this->db->transComplete();
|
|
||||||
|
|
||||||
if ($this->db->transStatus() === false) {
|
|
||||||
$dbError = $this->db->error();
|
|
||||||
return $this->failServerError(
|
|
||||||
'Failed to create location data (transaction rolled back): ' . ($dbError['message'] ?? 'Unknown database error')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondCreated([
|
return $this->respondCreated([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Location created successfully',
|
'message' => 'Order created successfully',
|
||||||
'data' => $dataLocation,
|
'data' => ['OrderID' => $orderID]
|
||||||
], 201);
|
], 201);
|
||||||
|
} catch (\Exception $e) {
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Ensure rollback if something goes wrong
|
|
||||||
if ($this->db->transStatus() !== false) {
|
|
||||||
$this->db->transRollback();
|
|
||||||
}
|
|
||||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update() {
|
public function update() {
|
||||||
|
$input = $this->request->getJSON(true);
|
||||||
|
|
||||||
|
if (empty($input['OrderID'])) {
|
||||||
|
return $this->failValidationErrors(['OrderID' => 'OrderID is required']);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$input = $this->request->getJSON(true);
|
$order = $this->model->getOrder($input['OrderID']);
|
||||||
|
if (!$order) {
|
||||||
// Prepare data
|
return $this->failNotFound('Order not found');
|
||||||
$dataLocation = $this->prepareLocationData($input);
|
|
||||||
$dataLocationAddress = $this->prepareLocationAddressData($input);
|
|
||||||
|
|
||||||
if (!$this->validateData($dataLocation, $this->rules)) {
|
|
||||||
return $this->failValidationErrors( $this->validator->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start transaction
|
$updateData = [];
|
||||||
$this->db->transStart();
|
if (isset($input['Priority'])) $updateData['Priority'] = $input['Priority'];
|
||||||
|
if (isset($input['OrderStatus'])) $updateData['OrderStatus'] = $input['OrderStatus'];
|
||||||
|
if (isset($input['OrderingProvider'])) $updateData['OrderingProvider'] = $input['OrderingProvider'];
|
||||||
|
if (isset($input['DepartmentID'])) $updateData['DepartmentID'] = $input['DepartmentID'];
|
||||||
|
if (isset($input['WorkstationID'])) $updateData['WorkstationID'] = $input['WorkstationID'];
|
||||||
|
|
||||||
// Insert location
|
if (!empty($updateData)) {
|
||||||
$this->db->table('location')->where('LocationID', $dataLocation["LocationID"])->update($dataLocation);
|
$this->model->update($input['OrderID'], $updateData);
|
||||||
|
|
||||||
// Insert address if available
|
|
||||||
if (!empty($dataLocationAddress)) {
|
|
||||||
$dataLocationAddress['LocationID'] = $input["LocationID"];
|
|
||||||
$this->db->table('locationaddress')->upsert($dataLocationAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete transaction
|
return $this->respond([
|
||||||
$this->db->transComplete();
|
'status' => 'success',
|
||||||
|
'message' => 'Order updated successfully',
|
||||||
if ($this->db->transStatus() === false) {
|
'data' => $this->model->getOrder($input['OrderID'])
|
||||||
$dbError = $this->db->error();
|
], 200);
|
||||||
return $this->failServerError(
|
} catch (\Exception $e) {
|
||||||
'Failed to update location data (transaction rolled back): ' . ($dbError['message'] ?? 'Unknown database error')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondCreated([
|
|
||||||
'status' => 'success',
|
|
||||||
'message' => 'Location updated successfully',
|
|
||||||
'data' => $dataLocation,
|
|
||||||
], 201);
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Ensure rollback if something goes wrong
|
|
||||||
if ($this->db->transStatus() !== false) {
|
|
||||||
$this->db->transRollback();
|
|
||||||
}
|
|
||||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete() {
|
public function delete() {
|
||||||
|
$input = $this->request->getJSON(true);
|
||||||
|
$orderID = $input['OrderID'] ?? null;
|
||||||
|
|
||||||
|
if (empty($orderID)) {
|
||||||
|
return $this->failValidationErrors(['OrderID' => 'OrderID is required']);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$input = $this->request->getJSON(true);
|
$order = $this->model->getOrder($orderID);
|
||||||
$LocationID = $input["LocationID"];
|
if (!$order) {
|
||||||
if (!$LocationID) {
|
return $this->failNotFound('Order not found');
|
||||||
return $this->failValidationError('LocationID is required.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->model->softDelete($orderID);
|
||||||
$location = $this->db->table('location')->where('LocationID', $LocationID)->get()->getRow();
|
|
||||||
if (!$location) {
|
|
||||||
return $this->failNotFound("LocationID with {$LocationID} not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->db->table('location')->where('LocationID', $LocationID)->update(['DelDate' => NOW()]);
|
|
||||||
|
|
||||||
return $this->respondDeleted([
|
return $this->respondDeleted([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => "Location with {$LocationID} deleted successfully."
|
'message' => 'Order deleted successfully'
|
||||||
]);
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Ensure rollback if something goes wrong
|
|
||||||
if ($this->db->transStatus() !== false) {
|
|
||||||
$this->db->transRollback();
|
|
||||||
}
|
|
||||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepareLocationData(array $input): array {
|
public function updateStatus() {
|
||||||
$LinkTo = null;
|
$input = $this->request->getJSON(true);
|
||||||
if (!empty($input['LinkTo'])) {
|
|
||||||
$ids = array_column($input['LinkTo'], 'InternalPID');
|
if (empty($input['OrderID']) || empty($input['OrderStatus'])) {
|
||||||
$LinkTo = implode(',', $ids);
|
return $this->failValidationErrors(['error' => 'OrderID and OrderStatus are required']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
$validStatuses = ['ORD', 'SCH', 'ANA', 'VER', 'REV', 'REP'];
|
||||||
"LocCode" => $input['LocCode'] ?? null,
|
if (!in_array($input['OrderStatus'], $validStatuses)) {
|
||||||
"Parent" => $input['Parent'] ?? null,
|
return $this->failValidationErrors(['OrderStatus' => 'Invalid status. Valid: ' . implode(', ', $validStatuses)]);
|
||||||
"LocFull" => $input['LocFull'] ?? null,
|
}
|
||||||
"Description" => $input['Description'] ?? null,
|
|
||||||
];
|
|
||||||
|
|
||||||
if(!empty($input["LocationID"])) { $data["LocationID"] = $input["LocationID"]; }
|
try {
|
||||||
|
$order = $this->model->getOrder($input['OrderID']);
|
||||||
|
if (!$order) {
|
||||||
|
return $this->failNotFound('Order not found');
|
||||||
|
}
|
||||||
|
|
||||||
return $data;
|
$this->model->updateStatus($input['OrderID'], $input['OrderStatus']);
|
||||||
}
|
|
||||||
|
|
||||||
private function prepareLocationAddressData(array $input): array {
|
return $this->respond([
|
||||||
$data = [
|
'status' => 'success',
|
||||||
"LocationID" => $input['LocationID'] ?? null,
|
'message' => 'Order status updated successfully',
|
||||||
"Street1" => $input['Street1'] ?? null,
|
'data' => $this->model->getOrder($input['OrderID'])
|
||||||
"Street2" => $input['Street2'] ?? null,
|
], 200);
|
||||||
"City" => $input['City'] ?? null,
|
} catch (\Exception $e) {
|
||||||
"Province" => $input['Province'] ?? null,
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||||
"PostCode" => $input['PostCode'] ?? null,
|
}
|
||||||
"GeoLocationSystem" => $input['GeoLocationSystem'] ?? null,
|
|
||||||
"GeoLocationData" => $input['GeoLocationData'] ?? null,
|
|
||||||
];
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class PatientController extends Controller {
|
|||||||
'PatientID' => 'required|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
|
'PatientID' => 'required|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
|
||||||
'AlternatePID' => 'permit_empty|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
|
'AlternatePID' => 'permit_empty|regex_match[/^[A-Za-z0-9]+$/]|max_length[30]',
|
||||||
'Prefix' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|max_length[10]',
|
'Prefix' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|max_length[10]',
|
||||||
'Gender' => 'required',
|
'Sex' => 'required',
|
||||||
|
|
||||||
'NameFirst' => 'required|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
|
'NameFirst' => 'required|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
|
||||||
'NameMiddle' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
|
'NameMiddle' => 'permit_empty|regex_match[/^[A-Za-z\'\. ]+$/]|min_length[1]|max_length[60]',
|
||||||
|
|||||||
78
app/Controllers/Test/DemoOrderController.php
Normal file
78
app/Controllers/Test/DemoOrderController.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Controllers\Test;
|
||||||
|
|
||||||
|
use CodeIgniter\API\ResponseTrait;
|
||||||
|
use CodeIgniter\Controller;
|
||||||
|
use App\Models\Patient\PatientModel;
|
||||||
|
use App\Models\OrderTest\OrderTestModel;
|
||||||
|
|
||||||
|
class DemoOrderController extends Controller {
|
||||||
|
use ResponseTrait;
|
||||||
|
|
||||||
|
protected $db;
|
||||||
|
protected $patientModel;
|
||||||
|
protected $orderModel;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = \Config\Database::connect();
|
||||||
|
$this->patientModel = new PatientModel();
|
||||||
|
$this->orderModel = new OrderTestModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDemoOrder() {
|
||||||
|
$input = $this->request->getJSON(true);
|
||||||
|
|
||||||
|
$patientData = [
|
||||||
|
'PatientID' => $input['PatientID'] ?? 'DEMO' . time(),
|
||||||
|
'Gender' => $input['Gender'] ?? '1',
|
||||||
|
'NameFirst' => $input['NameFirst'] ?? 'Demo',
|
||||||
|
'NameLast' => $input['NameLast'] ?? 'Patient',
|
||||||
|
'Birthdate' => $input['Birthdate'] ?? '1990-01-01'
|
||||||
|
];
|
||||||
|
|
||||||
|
$patient = $this->patientModel->where('PatientID', $patientData['PatientID'])->findAll();
|
||||||
|
if (empty($patient)) {
|
||||||
|
$internalPID = $this->patientModel->createPatient($patientData);
|
||||||
|
} else {
|
||||||
|
$internalPID = $patient[0]['InternalPID'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderData = [
|
||||||
|
'InternalPID' => $internalPID,
|
||||||
|
'PatVisitID' => $input['PatVisitID'] ?? null,
|
||||||
|
'Priority' => $input['Priority'] ?? 'R',
|
||||||
|
'OrderingProvider' => $input['OrderingProvider'] ?? 'Dr. Demo',
|
||||||
|
'DepartmentID' => $input['DepartmentID'] ?? 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$orderID = $this->orderModel->createOrder($orderData);
|
||||||
|
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Demo order created successfully',
|
||||||
|
'data' => [
|
||||||
|
'PatientID' => $patientData['PatientID'],
|
||||||
|
'InternalPID' => $internalPID,
|
||||||
|
'OrderID' => $orderID,
|
||||||
|
'OrderStatus' => 'ORD'
|
||||||
|
]
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listDemoOrders() {
|
||||||
|
$orders = $this->db->table('ordertest ot')
|
||||||
|
->select('ot.OrderID, ot.InternalPID, p.PatientID, ot.OrderDateTime, ot.Priority, ot.OrderStatus')
|
||||||
|
->join('patient p', 'p.InternalPID = ot.InternalPID')
|
||||||
|
->where('ot.DelDate', null)
|
||||||
|
->orderBy('ot.OrderDateTime', 'DESC')
|
||||||
|
->limit(50)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Data fetched successfully',
|
||||||
|
'data' => $orders
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ namespace App\Controllers;
|
|||||||
|
|
||||||
use CodeIgniter\API\ResponseTrait;
|
use CodeIgniter\API\ResponseTrait;
|
||||||
use App\Controllers\BaseController;
|
use App\Controllers\BaseController;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class TestsController extends BaseController
|
class TestsController extends BaseController
|
||||||
{
|
{
|
||||||
@ -15,18 +16,9 @@ class TestsController extends BaseController
|
|||||||
protected $modelTech;
|
protected $modelTech;
|
||||||
protected $modelGrp;
|
protected $modelGrp;
|
||||||
protected $modelMap;
|
protected $modelMap;
|
||||||
protected $modelValueSet;
|
|
||||||
protected $modelRefNum;
|
protected $modelRefNum;
|
||||||
protected $modelRefTxt;
|
protected $modelRefTxt;
|
||||||
|
|
||||||
// Valueset ID constants
|
|
||||||
const VALUESET_REF_TYPE = 44; // testdeftech.RefType
|
|
||||||
const VALUESET_RANGE_TYPE = 45; // refnum.RangeType
|
|
||||||
const VALUESET_NUM_REF_TYPE = 46; // refnum.NumRefType
|
|
||||||
const VALUESET_TXT_REF_TYPE = 47; // reftxt.TxtRefType
|
|
||||||
const VALUESET_SEX = 3; // Sex values
|
|
||||||
const VALUESET_MATH_SIGN = 41; // LowSign, HighSign
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->db = \Config\Database::connect();
|
$this->db = \Config\Database::connect();
|
||||||
@ -35,11 +27,9 @@ class TestsController extends BaseController
|
|||||||
$this->modelTech = new \App\Models\Test\TestDefTechModel;
|
$this->modelTech = new \App\Models\Test\TestDefTechModel;
|
||||||
$this->modelGrp = new \App\Models\Test\TestDefGrpModel;
|
$this->modelGrp = new \App\Models\Test\TestDefGrpModel;
|
||||||
$this->modelMap = new \App\Models\Test\TestMapModel;
|
$this->modelMap = new \App\Models\Test\TestMapModel;
|
||||||
$this->modelValueSet = new \App\Models\ValueSet\ValueSetModel;
|
|
||||||
$this->modelRefNum = new \App\Models\RefRange\RefNumModel;
|
$this->modelRefNum = new \App\Models\RefRange\RefNumModel;
|
||||||
$this->modelRefTxt = new \App\Models\RefRange\RefTxtModel;
|
$this->modelRefTxt = new \App\Models\RefRange\RefTxtModel;
|
||||||
|
|
||||||
// Validation rules for main test definition
|
|
||||||
$this->rules = [
|
$this->rules = [
|
||||||
'TestSiteCode' => 'required|min_length[3]|max_length[6]',
|
'TestSiteCode' => 'required|min_length[3]|max_length[6]',
|
||||||
'TestSiteName' => 'required',
|
'TestSiteName' => 'required',
|
||||||
@ -48,11 +38,6 @@ class TestsController extends BaseController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /v1/tests
|
|
||||||
* GET /v1/tests/site
|
|
||||||
* List all tests with optional filtering
|
|
||||||
*/
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$siteId = $this->request->getGet('SiteID');
|
$siteId = $this->request->getGet('SiteID');
|
||||||
@ -64,9 +49,7 @@ class TestsController extends BaseController
|
|||||||
$builder = $this->db->table('testdefsite')
|
$builder = $this->db->table('testdefsite')
|
||||||
->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType,
|
->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType,
|
||||||
testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt,
|
testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt,
|
||||||
testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate,
|
testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate")
|
||||||
valueset.VValue as TypeCode, valueset.VDesc as TypeName")
|
|
||||||
->join("valueset", "valueset.VID=testdefsite.TestType", "left")
|
|
||||||
->where('testdefsite.EndDate IS NULL');
|
->where('testdefsite.EndDate IS NULL');
|
||||||
|
|
||||||
if ($siteId) {
|
if ($siteId) {
|
||||||
@ -94,21 +77,20 @@ class TestsController extends BaseController
|
|||||||
if (empty($rows)) {
|
if (empty($rows)) {
|
||||||
return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => []], 200);
|
return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => []], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'TestType' => 'test_type',
|
||||||
|
]);
|
||||||
|
|
||||||
return $this->respond(['status' => 'success', 'message' => "Data fetched successfully", 'data' => $rows], 200);
|
return $this->respond(['status' => 'success', 'message' => "Data fetched successfully", 'data' => $rows], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /v1/tests/{id}
|
|
||||||
* GET /v1/tests/site/{id}
|
|
||||||
* Get single test by ID with all related details
|
|
||||||
*/
|
|
||||||
public function show($id = null)
|
public function show($id = null)
|
||||||
{
|
{
|
||||||
if (!$id)
|
if (!$id)
|
||||||
return $this->failValidationErrors('TestSiteID is required');
|
return $this->failValidationErrors('TestSiteID is required');
|
||||||
|
|
||||||
$row = $this->model->select("testdefsite.*, valueset.VValue as TypeCode, valueset.VDesc as TypeName")
|
$row = $this->model->select("testdefsite.*")
|
||||||
->join("valueset", "valueset.VID=testdefsite.TestType", "left")
|
|
||||||
->where("testdefsite.TestSiteID", $id)
|
->where("testdefsite.TestSiteID", $id)
|
||||||
->find($id);
|
->find($id);
|
||||||
|
|
||||||
@ -116,11 +98,13 @@ class TestsController extends BaseController
|
|||||||
return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => null], 200);
|
return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => null], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load related details based on TestType
|
$row = ValueSet::transformLabels([$row], [
|
||||||
$typeCode = $row['TypeCode'] ?? '';
|
'TestType' => 'test_type',
|
||||||
|
])[0];
|
||||||
|
|
||||||
|
$typeCode = $row['TestType'] ?? '';
|
||||||
|
|
||||||
if ($typeCode === 'CALC') {
|
if ($typeCode === 'CALC') {
|
||||||
// Load calculation details
|
|
||||||
$row['testdefcal'] = $this->db->table('testdefcal')
|
$row['testdefcal'] = $this->db->table('testdefcal')
|
||||||
->select('testdefcal.*, d.DisciplineName, dept.DepartmentName')
|
->select('testdefcal.*, d.DisciplineName, dept.DepartmentName')
|
||||||
->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left')
|
->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left')
|
||||||
@ -129,29 +113,27 @@ class TestsController extends BaseController
|
|||||||
->where('testdefcal.EndDate IS NULL')
|
->where('testdefcal.EndDate IS NULL')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
// Load test mappings
|
|
||||||
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
} elseif ($typeCode === 'GROUP') {
|
} elseif ($typeCode === 'GROUP') {
|
||||||
// Load group members with test details
|
|
||||||
$row['testdefgrp'] = $this->db->table('testdefgrp')
|
$row['testdefgrp'] = $this->db->table('testdefgrp')
|
||||||
->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType, vs.VValue as MemberTypeCode')
|
->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType')
|
||||||
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
|
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
|
||||||
->join('valueset vs', 'vs.VID=t.TestType', 'left')
|
|
||||||
->where('testdefgrp.TestSiteID', $id)
|
->where('testdefgrp.TestSiteID', $id)
|
||||||
->where('testdefgrp.EndDate IS NULL')
|
->where('testdefgrp.EndDate IS NULL')
|
||||||
->orderBy('testdefgrp.TestGrpID', 'ASC')
|
->orderBy('testdefgrp.TestGrpID', 'ASC')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
// Load test mappings
|
$row['testdefgrp'] = ValueSet::transformLabels($row['testdefgrp'], [
|
||||||
|
'TestType' => 'test_type',
|
||||||
|
]);
|
||||||
|
|
||||||
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
} elseif ($typeCode === 'TITLE') {
|
} elseif ($typeCode === 'TITLE') {
|
||||||
// Load test mappings only for TITLE type
|
|
||||||
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// TEST or PARAM - load technical details
|
|
||||||
$row['testdeftech'] = $this->db->table('testdeftech')
|
$row['testdeftech'] = $this->db->table('testdeftech')
|
||||||
->select('testdeftech.*, d.DisciplineName, dept.DepartmentName')
|
->select('testdeftech.*, d.DisciplineName, dept.DepartmentName')
|
||||||
->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left')
|
->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left')
|
||||||
@ -160,50 +142,38 @@ class TestsController extends BaseController
|
|||||||
->where('testdeftech.EndDate IS NULL')
|
->where('testdeftech.EndDate IS NULL')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
// Load test mappings
|
|
||||||
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
// Load refnum/reftxt based on RefType
|
|
||||||
if (!empty($row['testdeftech'])) {
|
if (!empty($row['testdeftech'])) {
|
||||||
$techData = $row['testdeftech'][0];
|
$techData = $row['testdeftech'][0];
|
||||||
$refType = (int) $techData['RefType'];
|
$refType = $techData['RefType'];
|
||||||
|
|
||||||
// Load refnum for NMRC type (RefType = 1)
|
if ($refType === '1') {
|
||||||
if ($refType === 1) {
|
|
||||||
$refnumData = $this->modelRefNum
|
$refnumData = $this->modelRefNum
|
||||||
->where('TestSiteID', $id)
|
->where('TestSiteID', $id)
|
||||||
->where('EndDate IS NULL')
|
->where('EndDate IS NULL')
|
||||||
->orderBy('Display', 'ASC')
|
->orderBy('Display', 'ASC')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
// Add VValue for display
|
|
||||||
$row['refnum'] = array_map(function ($r) {
|
$row['refnum'] = array_map(function ($r) {
|
||||||
return [
|
return [
|
||||||
'RefNumID' => $r['RefNumID'],
|
'RefNumID' => $r['RefNumID'],
|
||||||
'NumRefType' => (int) $r['NumRefType'],
|
'NumRefType' => $r['NumRefType'],
|
||||||
'NumRefTypeVValue' => $this->getVValue(46, $r['NumRefType']),
|
'NumRefTypeVValue' => ValueSet::getLabel('numeric_ref_type', $r['NumRefType']),
|
||||||
'RangeType' => (int) $r['RangeType'],
|
'RangeTypeVValue' => ValueSet::getLabel('range_type', $r['RangeType']),
|
||||||
'RangeTypeVValue' => $this->getVValue(45, $r['RangeType']),
|
'SexVValue' => ValueSet::getLabel('gender', $r['Sex']),
|
||||||
'Sex' => (int) $r['Sex'],
|
'LowSignVValue' => ValueSet::getLabel('math_sign', $r['LowSign']),
|
||||||
'SexVValue' => $this->getVValue(3, $r['Sex']),
|
'HighSignVValue' => ValueSet::getLabel('math_sign', $r['HighSign']),
|
||||||
'AgeStart' => (int) $r['AgeStart'],
|
|
||||||
'AgeEnd' => (int) $r['AgeEnd'],
|
|
||||||
'LowSign' => $r['LowSign'] !== null ? (int) $r['LowSign'] : null,
|
|
||||||
'LowSignVValue' => $this->getVValue(41, $r['LowSign']),
|
|
||||||
'Low' => $r['Low'] !== null ? (int) $r['Low'] : null,
|
|
||||||
'HighSign' => $r['HighSign'] !== null ? (int) $r['HighSign'] : null,
|
|
||||||
'HighSignVValue' => $this->getVValue(41, $r['HighSign']),
|
|
||||||
'High' => $r['High'] !== null ? (int) $r['High'] : null,
|
'High' => $r['High'] !== null ? (int) $r['High'] : null,
|
||||||
'Flag' => $r['Flag']
|
'Flag' => $r['Flag']
|
||||||
];
|
];
|
||||||
}, $refnumData ?? []);
|
}, $refnumData ?? []);
|
||||||
|
|
||||||
$row['numRefTypeOptions'] = $this->getValuesetOptions(46);
|
$row['numRefTypeOptions'] = ValueSet::getOptions('numeric_ref_type');
|
||||||
$row['rangeTypeOptions'] = $this->getValuesetOptions(45);
|
$row['rangeTypeOptions'] = ValueSet::getOptions('range_type');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reftxt for TEXT type (RefType = 2)
|
if ($refType === '2') {
|
||||||
if ($refType === 2) {
|
|
||||||
$reftxtData = $this->modelRefTxt
|
$reftxtData = $this->modelRefTxt
|
||||||
->where('TestSiteID', $id)
|
->where('TestSiteID', $id)
|
||||||
->where('EndDate IS NULL')
|
->where('EndDate IS NULL')
|
||||||
@ -213,10 +183,10 @@ class TestsController extends BaseController
|
|||||||
$row['reftxt'] = array_map(function ($r) {
|
$row['reftxt'] = array_map(function ($r) {
|
||||||
return [
|
return [
|
||||||
'RefTxtID' => $r['RefTxtID'],
|
'RefTxtID' => $r['RefTxtID'],
|
||||||
'TxtRefType' => (int) $r['TxtRefType'],
|
'TxtRefType' => $r['TxtRefType'],
|
||||||
'TxtRefTypeVValue' => $this->getVValue(47, $r['TxtRefType']),
|
'TxtRefTypeVValue' => ValueSet::getLabel('text_ref_type', $r['TxtRefType']),
|
||||||
'Sex' => (int) $r['Sex'],
|
'Sex' => $r['Sex'],
|
||||||
'SexVValue' => $this->getVValue(3, $r['Sex']),
|
'SexVValue' => ValueSet::getLabel('gender', $r['Sex']),
|
||||||
'AgeStart' => (int) $r['AgeStart'],
|
'AgeStart' => (int) $r['AgeStart'],
|
||||||
'AgeEnd' => (int) $r['AgeEnd'],
|
'AgeEnd' => (int) $r['AgeEnd'],
|
||||||
'RefTxt' => $r['RefTxt'],
|
'RefTxt' => $r['RefTxt'],
|
||||||
@ -224,24 +194,18 @@ class TestsController extends BaseController
|
|||||||
];
|
];
|
||||||
}, $reftxtData ?? []);
|
}, $reftxtData ?? []);
|
||||||
|
|
||||||
$row['txtRefTypeOptions'] = $this->getValuesetOptions(47);
|
$row['txtRefTypeOptions'] = ValueSet::getOptions('text_ref_type');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include valueset options for dropdowns
|
$row['refTypeOptions'] = ValueSet::getOptions('reference_type');
|
||||||
$row['refTypeOptions'] = $this->getValuesetOptions(self::VALUESET_REF_TYPE);
|
$row['sexOptions'] = ValueSet::getOptions('gender');
|
||||||
$row['sexOptions'] = $this->getValuesetOptions(self::VALUESET_SEX);
|
$row['mathSignOptions'] = ValueSet::getOptions('math_sign');
|
||||||
$row['mathSignOptions'] = $this->getValuesetOptions(self::VALUESET_MATH_SIGN);
|
|
||||||
|
|
||||||
return $this->respond(['status' => 'success', 'message' => "Data fetched successfully", 'data' => $row], 200);
|
return $this->respond(['status' => 'success', 'message' => "Data fetched successfully", 'data' => $row], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /v1/tests
|
|
||||||
* POST /v1/tests/site
|
|
||||||
* Create new test definition
|
|
||||||
*/
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$input = $this->request->getJSON(true);
|
$input = $this->request->getJSON(true);
|
||||||
@ -253,7 +217,6 @@ class TestsController extends BaseController
|
|||||||
$this->db->transStart();
|
$this->db->transStart();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Insert into Main Table (testdefsite)
|
|
||||||
$testSiteData = [
|
$testSiteData = [
|
||||||
'SiteID' => $input['SiteID'],
|
'SiteID' => $input['SiteID'],
|
||||||
'TestSiteCode' => $input['TestSiteCode'],
|
'TestSiteCode' => $input['TestSiteCode'],
|
||||||
@ -275,7 +238,6 @@ class TestsController extends BaseController
|
|||||||
throw new \Exception("Failed to insert main test definition");
|
throw new \Exception("Failed to insert main test definition");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Handle Details based on TestType
|
|
||||||
$this->handleDetails($id, $input, 'insert');
|
$this->handleDetails($id, $input, 'insert');
|
||||||
|
|
||||||
$this->db->transComplete();
|
$this->db->transComplete();
|
||||||
@ -295,16 +257,10 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT/PATCH /v1/tests/{id}
|
|
||||||
* PUT/PATCH /v1/tests/site/{id}
|
|
||||||
* Update existing test definition
|
|
||||||
*/
|
|
||||||
public function update($id = null)
|
public function update($id = null)
|
||||||
{
|
{
|
||||||
$input = $this->request->getJSON(true);
|
$input = $this->request->getJSON(true);
|
||||||
|
|
||||||
// Determine ID
|
|
||||||
if (!$id && isset($input["TestSiteID"])) {
|
if (!$id && isset($input["TestSiteID"])) {
|
||||||
$id = $input["TestSiteID"];
|
$id = $input["TestSiteID"];
|
||||||
}
|
}
|
||||||
@ -312,7 +268,6 @@ class TestsController extends BaseController
|
|||||||
return $this->failValidationErrors('TestSiteID is required.');
|
return $this->failValidationErrors('TestSiteID is required.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify record exists
|
|
||||||
$existing = $this->model->find($id);
|
$existing = $this->model->find($id);
|
||||||
if (!$existing) {
|
if (!$existing) {
|
||||||
return $this->failNotFound('Test not found');
|
return $this->failNotFound('Test not found');
|
||||||
@ -321,7 +276,6 @@ class TestsController extends BaseController
|
|||||||
$this->db->transStart();
|
$this->db->transStart();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Update Main Table
|
|
||||||
$testSiteData = [];
|
$testSiteData = [];
|
||||||
$allowedUpdateFields = [
|
$allowedUpdateFields = [
|
||||||
'TestSiteCode',
|
'TestSiteCode',
|
||||||
@ -348,7 +302,6 @@ class TestsController extends BaseController
|
|||||||
$this->model->update($id, $testSiteData);
|
$this->model->update($id, $testSiteData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Handle Details
|
|
||||||
$this->handleDetails($id, $input, 'update');
|
$this->handleDetails($id, $input, 'update');
|
||||||
|
|
||||||
$this->db->transComplete();
|
$this->db->transComplete();
|
||||||
@ -368,16 +321,10 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE /v1/tests/{id}
|
|
||||||
* DELETE /v1/tests/site/{id}
|
|
||||||
* Soft delete test by setting EndDate
|
|
||||||
*/
|
|
||||||
public function delete($id = null)
|
public function delete($id = null)
|
||||||
{
|
{
|
||||||
$input = $this->request->getJSON(true);
|
$input = $this->request->getJSON(true);
|
||||||
|
|
||||||
// Determine ID
|
|
||||||
if (!$id && isset($input["TestSiteID"])) {
|
if (!$id && isset($input["TestSiteID"])) {
|
||||||
$id = $input["TestSiteID"];
|
$id = $input["TestSiteID"];
|
||||||
}
|
}
|
||||||
@ -385,13 +332,11 @@ class TestsController extends BaseController
|
|||||||
return $this->failValidationErrors('TestSiteID is required.');
|
return $this->failValidationErrors('TestSiteID is required.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify record exists
|
|
||||||
$existing = $this->model->find($id);
|
$existing = $this->model->find($id);
|
||||||
if (!$existing) {
|
if (!$existing) {
|
||||||
return $this->failNotFound('Test not found');
|
return $this->failNotFound('Test not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already disabled
|
|
||||||
if (!empty($existing['EndDate'])) {
|
if (!empty($existing['EndDate'])) {
|
||||||
return $this->failValidationErrors('Test is already disabled');
|
return $this->failValidationErrors('Test is already disabled');
|
||||||
}
|
}
|
||||||
@ -401,15 +346,11 @@ class TestsController extends BaseController
|
|||||||
try {
|
try {
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
// 1. Soft delete main record
|
|
||||||
$this->model->update($id, ['EndDate' => $now]);
|
$this->model->update($id, ['EndDate' => $now]);
|
||||||
|
|
||||||
// 2. Get TestType to handle related records
|
|
||||||
$testType = $existing['TestType'];
|
$testType = $existing['TestType'];
|
||||||
$vs = $this->modelValueSet->find($testType);
|
$typeCode = $testType;
|
||||||
$typeCode = $vs['VValue'] ?? '';
|
|
||||||
|
|
||||||
// 3. Soft delete related records based on TestType
|
|
||||||
if ($typeCode === 'CALC') {
|
if ($typeCode === 'CALC') {
|
||||||
$this->db->table('testdefcal')
|
$this->db->table('testdefcal')
|
||||||
->where('TestSiteID', $id)
|
->where('TestSiteID', $id)
|
||||||
@ -423,12 +364,10 @@ class TestsController extends BaseController
|
|||||||
->where('TestSiteID', $id)
|
->where('TestSiteID', $id)
|
||||||
->update(['EndDate' => $now]);
|
->update(['EndDate' => $now]);
|
||||||
|
|
||||||
// Soft delete refnum and reftxt records
|
|
||||||
$this->modelRefNum->where('TestSiteID', $id)->set('EndDate', $now)->update();
|
$this->modelRefNum->where('TestSiteID', $id)->set('EndDate', $now)->update();
|
||||||
$this->modelRefTxt->where('TestSiteID', $id)->set('EndDate', $now)->update();
|
$this->modelRefTxt->where('TestSiteID', $id)->set('EndDate', $now)->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Soft delete test mappings
|
|
||||||
$this->db->table('testmap')
|
$this->db->table('testmap')
|
||||||
->where('TestSiteID', $id)
|
->where('TestSiteID', $id)
|
||||||
->update(['EndDate' => $now]);
|
->update(['EndDate' => $now]);
|
||||||
@ -450,41 +389,10 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to get valueset options
|
|
||||||
*/
|
|
||||||
private function getValuesetOptions($vsetID)
|
|
||||||
{
|
|
||||||
return $this->db->table('valueset')
|
|
||||||
->select('VID as vid, VValue as vvalue, VDesc as vdesc')
|
|
||||||
->where('VSetID', $vsetID)
|
|
||||||
->orderBy('VOrder', 'ASC')
|
|
||||||
->get()->getResultArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to get VValue from VID for display
|
|
||||||
*/
|
|
||||||
private function getVValue($vsetID, $vid)
|
|
||||||
{
|
|
||||||
if ($vid === null || $vid === '')
|
|
||||||
return null;
|
|
||||||
$row = $this->db->table('valueset')
|
|
||||||
->select('VValue as vvalue')
|
|
||||||
->where('VSetID', $vsetID)
|
|
||||||
->where('VID', (int) $vid)
|
|
||||||
->get()->getRowArray();
|
|
||||||
return $row ? $row['vvalue'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to handle inserting/updating sub-tables based on TestType
|
|
||||||
*/
|
|
||||||
private function handleDetails($testSiteID, $input, $action)
|
private function handleDetails($testSiteID, $input, $action)
|
||||||
{
|
{
|
||||||
$testTypeID = $input['TestType'] ?? null;
|
$testTypeID = $input['TestType'] ?? null;
|
||||||
|
|
||||||
// If update and TestType not in payload, fetch from DB
|
|
||||||
if (!$testTypeID && $action === 'update') {
|
if (!$testTypeID && $action === 'update') {
|
||||||
$existing = $this->model->find($testSiteID);
|
$existing = $this->model->find($testSiteID);
|
||||||
$testTypeID = $existing['TestType'] ?? null;
|
$testTypeID = $existing['TestType'] ?? null;
|
||||||
@ -493,11 +401,8 @@ class TestsController extends BaseController
|
|||||||
if (!$testTypeID)
|
if (!$testTypeID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get Type Code (TEST, PARAM, CALC, GROUP, TITLE)
|
$typeCode = $testTypeID;
|
||||||
$vs = $this->modelValueSet->find($testTypeID);
|
|
||||||
$typeCode = $vs['VValue'] ?? '';
|
|
||||||
|
|
||||||
// Get details data from input
|
|
||||||
$details = $input['details'] ?? $input;
|
$details = $input['details'] ?? $input;
|
||||||
$details['TestSiteID'] = $testSiteID;
|
$details['TestSiteID'] = $testSiteID;
|
||||||
$details['SiteID'] = $input['SiteID'] ?? 1;
|
$details['SiteID'] = $input['SiteID'] ?? 1;
|
||||||
@ -512,8 +417,6 @@ class TestsController extends BaseController
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'TITLE':
|
case 'TITLE':
|
||||||
// TITLE type only has testdefsite, no additional details needed
|
|
||||||
// But we should save test mappings if provided
|
|
||||||
if (isset($input['testmap']) && is_array($input['testmap'])) {
|
if (isset($input['testmap']) && is_array($input['testmap'])) {
|
||||||
$this->saveTestMap($testSiteID, $input['testmap'], $action);
|
$this->saveTestMap($testSiteID, $input['testmap'], $action);
|
||||||
}
|
}
|
||||||
@ -524,32 +427,25 @@ class TestsController extends BaseController
|
|||||||
default:
|
default:
|
||||||
$this->saveTechDetails($testSiteID, $details, $action, $typeCode);
|
$this->saveTechDetails($testSiteID, $details, $action, $typeCode);
|
||||||
|
|
||||||
// Save refnum/reftxt for TEST/PARAM types
|
|
||||||
if (in_array($typeCode, ['TEST', 'PARAM']) && isset($details['RefType'])) {
|
if (in_array($typeCode, ['TEST', 'PARAM']) && isset($details['RefType'])) {
|
||||||
$refType = (int) $details['RefType'];
|
$refType = $details['RefType'];
|
||||||
|
|
||||||
// Save refnum for NMRC type (RefType = 1)
|
if ($refType === '1' && isset($input['refnum']) && is_array($input['refnum'])) {
|
||||||
if ($refType === 1 && isset($input['refnum']) && is_array($input['refnum'])) {
|
|
||||||
$this->saveRefNumRanges($testSiteID, $input['refnum'], $action, $input['SiteID'] ?? 1);
|
$this->saveRefNumRanges($testSiteID, $input['refnum'], $action, $input['SiteID'] ?? 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save reftxt for TEXT type (RefType = 2)
|
if ($refType === '2' && isset($input['reftxt']) && is_array($input['reftxt'])) {
|
||||||
if ($refType === 2 && isset($input['reftxt']) && is_array($input['reftxt'])) {
|
|
||||||
$this->saveRefTxtRanges($testSiteID, $input['reftxt'], $action, $input['SiteID'] ?? 1);
|
$this->saveRefTxtRanges($testSiteID, $input['reftxt'], $action, $input['SiteID'] ?? 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save test mappings for TEST and CALC types as well
|
|
||||||
if (in_array($typeCode, ['TEST', 'CALC']) && isset($input['testmap']) && is_array($input['testmap'])) {
|
if (in_array($typeCode, ['TEST', 'CALC']) && isset($input['testmap']) && is_array($input['testmap'])) {
|
||||||
$this->saveTestMap($testSiteID, $input['testmap'], $action);
|
$this->saveTestMap($testSiteID, $input['testmap'], $action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save technical details for TEST and PARAM types
|
|
||||||
*/
|
|
||||||
private function saveTechDetails($testSiteID, $data, $action, $typeCode)
|
private function saveTechDetails($testSiteID, $data, $action, $typeCode)
|
||||||
{
|
{
|
||||||
$techData = [
|
$techData = [
|
||||||
@ -586,9 +482,6 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save refnum ranges for NMRC type
|
|
||||||
*/
|
|
||||||
private function saveRefNumRanges($testSiteID, $ranges, $action, $siteID)
|
private function saveRefNumRanges($testSiteID, $ranges, $action, $siteID)
|
||||||
{
|
{
|
||||||
if ($action === 'update') {
|
if ($action === 'update') {
|
||||||
@ -601,14 +494,14 @@ class TestsController extends BaseController
|
|||||||
$this->modelRefNum->insert([
|
$this->modelRefNum->insert([
|
||||||
'TestSiteID' => $testSiteID,
|
'TestSiteID' => $testSiteID,
|
||||||
'SiteID' => $siteID,
|
'SiteID' => $siteID,
|
||||||
'NumRefType' => (int) $range['NumRefType'],
|
'NumRefType' => $range['NumRefType'],
|
||||||
'RangeType' => (int) $range['RangeType'],
|
'RangeType' => $range['RangeType'],
|
||||||
'Sex' => (int) $range['Sex'],
|
'Sex' => $range['Sex'],
|
||||||
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
|
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
|
||||||
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
|
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
|
||||||
'LowSign' => !empty($range['LowSign']) ? (int) $range['LowSign'] : null,
|
'LowSign' => !empty($range['LowSign']) ? $range['LowSign'] : null,
|
||||||
'Low' => !empty($range['Low']) ? (int) $range['Low'] : null,
|
'Low' => !empty($range['Low']) ? (int) $range['Low'] : null,
|
||||||
'HighSign' => !empty($range['HighSign']) ? (int) $range['HighSign'] : null,
|
'HighSign' => !empty($range['HighSign']) ? $range['HighSign'] : null,
|
||||||
'High' => !empty($range['High']) ? (int) $range['High'] : null,
|
'High' => !empty($range['High']) ? (int) $range['High'] : null,
|
||||||
'Flag' => $range['Flag'] ?? null,
|
'Flag' => $range['Flag'] ?? null,
|
||||||
'Display' => $index,
|
'Display' => $index,
|
||||||
@ -617,9 +510,6 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save reftxt ranges for TEXT type
|
|
||||||
*/
|
|
||||||
private function saveRefTxtRanges($testSiteID, $ranges, $action, $siteID)
|
private function saveRefTxtRanges($testSiteID, $ranges, $action, $siteID)
|
||||||
{
|
{
|
||||||
if ($action === 'update') {
|
if ($action === 'update') {
|
||||||
@ -632,8 +522,8 @@ class TestsController extends BaseController
|
|||||||
$this->modelRefTxt->insert([
|
$this->modelRefTxt->insert([
|
||||||
'TestSiteID' => $testSiteID,
|
'TestSiteID' => $testSiteID,
|
||||||
'SiteID' => $siteID,
|
'SiteID' => $siteID,
|
||||||
'TxtRefType' => (int) $range['TxtRefType'],
|
'TxtRefType' => $range['TxtRefType'],
|
||||||
'Sex' => (int) $range['Sex'],
|
'Sex' => $range['Sex'],
|
||||||
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
|
'AgeStart' => (int) ($range['AgeStart'] ?? 0),
|
||||||
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
|
'AgeEnd' => (int) ($range['AgeEnd'] ?? 150),
|
||||||
'RefTxt' => $range['RefTxt'] ?? '',
|
'RefTxt' => $range['RefTxt'] ?? '',
|
||||||
@ -643,9 +533,6 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save calculation details for CALC type
|
|
||||||
*/
|
|
||||||
private function saveCalcDetails($testSiteID, $data, $action)
|
private function saveCalcDetails($testSiteID, $data, $action)
|
||||||
{
|
{
|
||||||
$calcData = [
|
$calcData = [
|
||||||
@ -678,19 +565,14 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save group details for GROUP type
|
|
||||||
*/
|
|
||||||
private function saveGroupDetails($testSiteID, $data, $input, $action)
|
private function saveGroupDetails($testSiteID, $data, $input, $action)
|
||||||
{
|
{
|
||||||
if ($action === 'update') {
|
if ($action === 'update') {
|
||||||
// Soft delete existing members
|
|
||||||
$this->db->table('testdefgrp')
|
$this->db->table('testdefgrp')
|
||||||
->where('TestSiteID', $testSiteID)
|
->where('TestSiteID', $testSiteID)
|
||||||
->update(['EndDate' => date('Y-m-d H:i:s')]);
|
->update(['EndDate' => date('Y-m-d H:i:s')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get members from details or input
|
|
||||||
$members = $data['members'] ?? ($input['Members'] ?? []);
|
$members = $data['members'] ?? ($input['Members'] ?? []);
|
||||||
|
|
||||||
if (is_array($members)) {
|
if (is_array($members)) {
|
||||||
@ -706,13 +588,9 @@ class TestsController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save test mappings
|
|
||||||
*/
|
|
||||||
private function saveTestMap($testSiteID, $mappings, $action)
|
private function saveTestMap($testSiteID, $mappings, $action)
|
||||||
{
|
{
|
||||||
if ($action === 'update') {
|
if ($action === 'update') {
|
||||||
// Soft delete existing mappings
|
|
||||||
$this->db->table('testmap')
|
$this->db->table('testmap')
|
||||||
->where('TestSiteID', $testSiteID)
|
->where('TestSiteID', $testSiteID)
|
||||||
->update(['EndDate' => date('Y-m-d H:i:s')]);
|
->update(['EndDate' => date('Y-m-d H:i:s')]);
|
||||||
|
|||||||
@ -3,87 +3,80 @@ namespace App\Controllers\ValueSet;
|
|||||||
|
|
||||||
use CodeIgniter\API\ResponseTrait;
|
use CodeIgniter\API\ResponseTrait;
|
||||||
use App\Controllers\BaseController;
|
use App\Controllers\BaseController;
|
||||||
use App\Models\ValueSet\ValueSetModel;
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class ValueSetController extends BaseController {
|
class ValueSetController extends BaseController {
|
||||||
use ResponseTrait;
|
use ResponseTrait;
|
||||||
|
|
||||||
protected $db;
|
|
||||||
protected $model;
|
|
||||||
protected $rules;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->db = \Config\Database::connect();
|
|
||||||
$this->model = new ValueSetModel;
|
|
||||||
$this->rules = [
|
|
||||||
'VSetID' => 'required',
|
|
||||||
'VValue' => 'required',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index() {
|
public function index() {
|
||||||
$param = $this->request->getVar('param');
|
$param = $this->request->getVar('param');
|
||||||
$VSetID = $this->request->getVar('VSetID');
|
|
||||||
|
if ($param) {
|
||||||
$data = $this->model->getValueSets($param, $VSetID);
|
$all = ValueSet::getAll();
|
||||||
|
$filtered = array_filter($all, function($item) use ($param) {
|
||||||
|
return stripos($item['VSName'] ?? '', $param) !== false ||
|
||||||
|
stripos($item['name'] ?? '', $param) !== false;
|
||||||
|
});
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => array_values($filtered)
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => ValueSet::getAll()
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showByName(string $name = null) {
|
||||||
|
if (!$name) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Name is required'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = ValueSet::get($name);
|
||||||
|
if (!$data) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => "ValueSet '$name' not found"
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->respond([
|
return $this->respond([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message'=> "Data fetched successfully",
|
|
||||||
'data' => $data
|
'data' => $data
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show($VID = null) {
|
|
||||||
$row = $this->model->getValueSet($VID);
|
|
||||||
if (empty($row)) {
|
|
||||||
return $this->respond([ 'status' => 'success', 'message' => "ValueSet with ID $VID not found.", 'data' => null ], 200);
|
|
||||||
}
|
|
||||||
return $this->respond([ 'status' => 'success', 'message'=> "Data fetched successfully", 'data' => $row], 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showByValueSetDef($VSetID = null) {
|
|
||||||
$rows = $this->model->getValueSetByValueSetDef($VSetID);
|
|
||||||
if (empty($rows)) {
|
|
||||||
return $this->respond([ 'status' => 'success', 'message' => "ValueSet not found.", 'data' => [] ], 200);
|
|
||||||
}
|
|
||||||
return $this->respond([ 'status' => 'success', 'message'=> "Data fetched successfully", 'data' => $rows ], 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create() {
|
public function create() {
|
||||||
$input = $this->request->getJSON(true);
|
return $this->respond([
|
||||||
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); }
|
'status' => 'error',
|
||||||
try {
|
'message' => 'CRUD operations on value sets are disabled. Edit JSON files directly.'
|
||||||
$VID = $this->model->insert($input);
|
], 403);
|
||||||
return $this->respondCreated([ 'status' => 'success', 'message' => "data $VID created successfully" ]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update() {
|
public function update() {
|
||||||
$input = $this->request->getJSON(true);
|
return $this->respond([
|
||||||
$VID = $input["VID"];
|
'status' => 'error',
|
||||||
if (!$VID) { return $this->failValidationErrors('VID is required.'); }
|
'message' => 'CRUD operations on value sets are disabled. Edit JSON files directly.'
|
||||||
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors( $this->validator->getErrors() ); }
|
], 403);
|
||||||
try {
|
|
||||||
$this->model->update($VID,$input);
|
|
||||||
return $this->respondCreated([ 'status' => 'success', 'message' => "data $VID updated successfully" ]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete() {
|
public function delete() {
|
||||||
$input = $this->request->getJSON(true);
|
return $this->respond([
|
||||||
$VID = $input["VID"];
|
'status' => 'error',
|
||||||
if (!$VID) { return $this->failValidationErrors('VID is required.'); }
|
'message' => 'CRUD operations on value sets are disabled. Edit JSON files directly.'
|
||||||
try {
|
], 403);
|
||||||
$this->model->delete($VID);
|
}
|
||||||
return $this->respondDeleted(['status' => 'success', 'message' => "Data $VID deleted successfully."]);
|
|
||||||
} catch (\Throwable $e) {
|
public function refresh() {
|
||||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
ValueSet::clearCache();
|
||||||
}
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Cache cleared'
|
||||||
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
app/Controllers/ValueSetApiController.php
Normal file
38
app/Controllers/ValueSetApiController.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
|
class ValueSetApiController extends \CodeIgniter\Controller
|
||||||
|
{
|
||||||
|
use \CodeIgniter\API\ResponseTrait;
|
||||||
|
|
||||||
|
public function index(string $lookupName)
|
||||||
|
{
|
||||||
|
$data = ValueSet::getOptions($lookupName);
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $data
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
$dir = APPPATH . 'Libraries/Data/valuesets/';
|
||||||
|
$files = glob($dir . '*.json');
|
||||||
|
$result = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$name = basename($file, '.json');
|
||||||
|
if ($name[0] === '_') continue;
|
||||||
|
$result[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'options' => ValueSet::getOptions($name)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $result
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class ValuesetVidToVvalue extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
$this->forge->modifyColumn('patient', [
|
||||||
|
'Gender' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Country' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Race' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Religion' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Ethnic' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'MaritalStatus' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'DeathIndicator' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('testdefsite', [
|
||||||
|
'TestType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => false],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('containerdef', [
|
||||||
|
'Additive' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'ConClass' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Color' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('location', [
|
||||||
|
'LocType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('workstation', [
|
||||||
|
'Type' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Enable' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('site', [
|
||||||
|
'SiteTypeID' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'SiteClassID' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('account', [
|
||||||
|
'Country' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('refnum', [
|
||||||
|
'Sex' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'NumRefType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'RangeType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'LowSign' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'HighSign' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('reftxt', [
|
||||||
|
'Sex' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'TxtRefType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('orderstatus', [
|
||||||
|
'OrderStatus' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => false],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
$this->forge->modifyColumn('patient', [
|
||||||
|
'Gender' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Country' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Race' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Religion' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Ethnic' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'MaritalStatus' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'DeathIndicator' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('testdefsite', [
|
||||||
|
'TestType' => ['type' => 'INT', 'null' => false],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('containerdef', [
|
||||||
|
'Additive' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'ConClass' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Color' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('location', [
|
||||||
|
'LocType' => ['type' => 'INT', 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('workstation', [
|
||||||
|
'Type' => ['type' => 'TINYINT', 'null' => true],
|
||||||
|
'Enable' => ['type' => 'INT', 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('site', [
|
||||||
|
'SiteTypeID' => ['type' => 'INT', 'null' => true],
|
||||||
|
'SiteClassID' => ['type' => 'INT', 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('account', [
|
||||||
|
'Country' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('refnum', [
|
||||||
|
'Sex' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'NumRefType' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'RangeType' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'LowSign' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'HighSign' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('reftxt', [
|
||||||
|
'Sex' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'TxtRefType' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('orderstatus', [
|
||||||
|
'OrderStatus' => ['type' => 'INT', 'null' => false],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class RenamePatientGenderToSex extends Migration {
|
||||||
|
public function up() {
|
||||||
|
$this->forge->modifyColumn('patient', [
|
||||||
|
'Gender' => ['name' => 'Sex', 'type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down() {
|
||||||
|
$this->forge->modifyColumn('patient', [
|
||||||
|
'Sex' => ['name' => 'Gender', 'type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -88,7 +88,7 @@ class PatientSeeder extends Seeder
|
|||||||
'NameLast' => 'Patient',
|
'NameLast' => 'Patient',
|
||||||
'Suffix' => 'S.Kom',
|
'Suffix' => 'S.Kom',
|
||||||
'NameAlias' => 'DummyTest',
|
'NameAlias' => 'DummyTest',
|
||||||
'Gender' => 5,
|
'Sex' => 5,
|
||||||
'PlaceOfBirth' => 'Jakarta',
|
'PlaceOfBirth' => 'Jakarta',
|
||||||
'Birthdate' => '1990-05-15',
|
'Birthdate' => '1990-05-15',
|
||||||
'Street_1' => 'Jl. Sudirman No. 123',
|
'Street_1' => 'Jl. Sudirman No. 123',
|
||||||
@ -126,7 +126,7 @@ class PatientSeeder extends Seeder
|
|||||||
'NameLast' => 'Doe',
|
'NameLast' => 'Doe',
|
||||||
'Suffix' => null,
|
'Suffix' => null,
|
||||||
'NameAlias' => 'JaneDoe',
|
'NameAlias' => 'JaneDoe',
|
||||||
'Gender' => 6, // Female
|
'Sex' => 6, // Female
|
||||||
'PlaceOfBirth' => 'Bandung',
|
'PlaceOfBirth' => 'Bandung',
|
||||||
'Birthdate' => '1985-10-20',
|
'Birthdate' => '1985-10-20',
|
||||||
'Street_1' => 'Jl. Asia Afrika No. 456',
|
'Street_1' => 'Jl. Asia Afrika No. 456',
|
||||||
@ -164,7 +164,7 @@ class PatientSeeder extends Seeder
|
|||||||
'NameLast' => 'Wijaya',
|
'NameLast' => 'Wijaya',
|
||||||
'Suffix' => null,
|
'Suffix' => null,
|
||||||
'NameAlias' => 'BudiW',
|
'NameAlias' => 'BudiW',
|
||||||
'Gender' => 5,
|
'Sex' => 5,
|
||||||
'PlaceOfBirth' => 'Surabaya',
|
'PlaceOfBirth' => 'Surabaya',
|
||||||
'Birthdate' => '2000-01-01',
|
'Birthdate' => '2000-01-01',
|
||||||
'Street_1' => 'Jl. Pahlawan No. 789',
|
'Street_1' => 'Jl. Pahlawan No. 789',
|
||||||
|
|||||||
54
app/Libraries/Data/valuesets/_meta.json
Normal file
54
app/Libraries/Data/valuesets/_meta.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"generated": "2026-01-12",
|
||||||
|
"description": "Value Set Definitions - Static lookup values for CLQMS",
|
||||||
|
"valuesets": [
|
||||||
|
{"file": "ws_type.json", "VSetID": 1, "VSName": "Workstation Type"},
|
||||||
|
{"file": "enable_disable.json", "VSetID": 2, "VSName": "Enable/Disable"},
|
||||||
|
{"file": "gender.json", "VSetID": 3, "VSName": "Gender"},
|
||||||
|
{"file": "marital_status.json", "VSetID": 4, "VSName": "Marital Status"},
|
||||||
|
{"file": "death_indicator.json", "VSetID": 5, "VSName": "Death Indicator"},
|
||||||
|
{"file": "identifier_type.json", "VSetID": 6, "VSName": "Identifier Type"},
|
||||||
|
{"file": "operation.json", "VSetID": 7, "VSName": "Operation (CRUD)"},
|
||||||
|
{"file": "did_type.json", "VSetID": 8, "VSName": "DID Type"},
|
||||||
|
{"file": "requested_entity.json", "VSetID": 9, "VSName": "Requested Entity"},
|
||||||
|
{"file": "order_priority.json", "VSetID": 10, "VSName": "Order Priority"},
|
||||||
|
{"file": "order_status.json", "VSetID": 11, "VSName": "Order Status"},
|
||||||
|
{"file": "location_type.json", "VSetID": 12, "VSName": "Location Type"},
|
||||||
|
{"file": "additive.json", "VSetID": 13, "VSName": "Additive"},
|
||||||
|
{"file": "container_class.json", "VSetID": 14, "VSName": "Container Class"},
|
||||||
|
{"file": "specimen_type.json", "VSetID": 15, "VSName": "Specimen Type"},
|
||||||
|
{"file": "unit.json", "VSetID": 16, "VSName": "Unit"},
|
||||||
|
{"file": "generate_by.json", "VSetID": 17, "VSName": "Generate By"},
|
||||||
|
{"file": "specimen_activity.json", "VSetID": 18, "VSName": "Specimen Activity"},
|
||||||
|
{"file": "activity_result.json", "VSetID": 19, "VSName": "Activity Result"},
|
||||||
|
{"file": "specimen_status.json", "VSetID": 20, "VSName": "Specimen Status"},
|
||||||
|
{"file": "specimen_condition.json", "VSetID": 21, "VSName": "Specimen Condition"},
|
||||||
|
{"file": "specimen_role.json", "VSetID": 22, "VSName": "Specimen Role"},
|
||||||
|
{"file": "collection_method.json", "VSetID": 23, "VSName": "Collection Method"},
|
||||||
|
{"file": "body_site.json", "VSetID": 24, "VSName": "Body Site"},
|
||||||
|
{"file": "container_size.json", "VSetID": 25, "VSName": "Container Size"},
|
||||||
|
{"file": "fasting_status.json", "VSetID": 26, "VSName": "Fasting Status"},
|
||||||
|
{"file": "test_type.json", "VSetID": 27, "VSName": "Test Type"},
|
||||||
|
{"file": "result_unit.json", "VSetID": 28, "VSName": "Result Unit"},
|
||||||
|
{"file": "formula_language.json", "VSetID": 29, "VSName": "Formula Language"},
|
||||||
|
{"file": "race.json", "VSetID": 30, "VSName": "Race (Ethnicity)"},
|
||||||
|
{"file": "religion.json", "VSetID": 31, "VSName": "Religion"},
|
||||||
|
{"file": "ethnic.json", "VSetID": 32, "VSName": "Ethnic"},
|
||||||
|
{"file": "country.json", "VSetID": 33, "VSName": "Country"},
|
||||||
|
{"file": "container_cap_color.json", "VSetID": 34, "VSName": "Container Cap Color"},
|
||||||
|
{"file": "test_activity.json", "VSetID": 35, "VSName": "Test Activity"},
|
||||||
|
{"file": "adt_event.json", "VSetID": 36, "VSName": "ADT Event"},
|
||||||
|
{"file": "site_type.json", "VSetID": 37, "VSName": "Site Type"},
|
||||||
|
{"file": "site_class.json", "VSetID": 38, "VSName": "Site Class"},
|
||||||
|
{"file": "entity_type.json", "VSetID": 39, "VSName": "Entity Type"},
|
||||||
|
{"file": "area_class.json", "VSetID": 40, "VSName": "Area Class"},
|
||||||
|
{"file": "math_sign.json", "VSetID": 41, "VSName": "Math Sign"},
|
||||||
|
{"file": "v_category.json", "VSetID": 42, "VSName": "VCategory"},
|
||||||
|
{"file": "result_type.json", "VSetID": 43, "VSName": "Result Type"},
|
||||||
|
{"file": "reference_type.json", "VSetID": 44, "VSName": "Reference Type"},
|
||||||
|
{"file": "range_type.json", "VSetID": 45, "VSName": "Range Type"},
|
||||||
|
{"file": "numeric_ref_type.json", "VSetID": 46, "VSName": "Numeric Reference Type"},
|
||||||
|
{"file": "text_ref_type.json", "VSetID": 47, "VSName": "Text Reference Type"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/activity_result.json
Normal file
11
app/Libraries/Data/valuesets/activity_result.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 19,
|
||||||
|
"name": "activity_result",
|
||||||
|
"VSName": "Activity Result",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "0", "value": "Failed"},
|
||||||
|
{"key": "1", "value": "Success with note"},
|
||||||
|
{"key": "2", "value": "Success"}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
app/Libraries/Data/valuesets/additive.json
Normal file
27
app/Libraries/Data/valuesets/additive.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 13,
|
||||||
|
"name": "additive",
|
||||||
|
"VSName": "Additive",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "Hep", "value": "Heparin ammonium"},
|
||||||
|
{"key": "Apro", "value": "Aprotinin"},
|
||||||
|
{"key": "HepCa", "value": "Heparin calcium"},
|
||||||
|
{"key": "H3BO3", "value": "Boric acid"},
|
||||||
|
{"key": "CaOxa", "value": "Calcium oxalate"},
|
||||||
|
{"key": "EDTA", "value": "EDTA"},
|
||||||
|
{"key": "Ede", "value": "Edetate"},
|
||||||
|
{"key": "HCl", "value": "Hydrochloric acid"},
|
||||||
|
{"key": "Hrdn", "value": "Hirudin"},
|
||||||
|
{"key": "EdeK", "value": "Edetate dipotassium"},
|
||||||
|
{"key": "EdeTri", "value": "Tripotassium edetate"},
|
||||||
|
{"key": "LiHep", "value": "Heparin lithium"},
|
||||||
|
{"key": "EdeNa", "value": "Edetate disodium"},
|
||||||
|
{"key": "NaCtrt", "value": "Sodium citrate"},
|
||||||
|
{"key": "NaHep", "value": "Heparin sodium"},
|
||||||
|
{"key": "NaF", "value": "Sodium fluoride"},
|
||||||
|
{"key": "Borax", "value": "Sodium tetraborate"},
|
||||||
|
{"key": "Mntl", "value": "Mannitol"},
|
||||||
|
{"key": "NaFrm", "value": "Sodium formate"}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
app/Libraries/Data/valuesets/adt_event.json
Normal file
21
app/Libraries/Data/valuesets/adt_event.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 36,
|
||||||
|
"name": "adt_event",
|
||||||
|
"VSName": "ADT Event",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "A01", "value": "Admit"},
|
||||||
|
{"key": "A02", "value": "Transfer"},
|
||||||
|
{"key": "A03", "value": "Discharge"},
|
||||||
|
{"key": "A04", "value": "Register"},
|
||||||
|
{"key": "A08", "value": "Update patient information"},
|
||||||
|
{"key": "A11", "value": "Cancel admit"},
|
||||||
|
{"key": "A12", "value": "Cancel transfer"},
|
||||||
|
{"key": "A13", "value": "Cancel discharge"},
|
||||||
|
{"key": "A23", "value": "Delete patient record"},
|
||||||
|
{"key": "A24", "value": "Link patient information"},
|
||||||
|
{"key": "A37", "value": "Unlink patient information"},
|
||||||
|
{"key": "A54", "value": "Change attending doctor"},
|
||||||
|
{"key": "A61", "value": "Change consulting doctor"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/area_class.json
Normal file
11
app/Libraries/Data/valuesets/area_class.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 40,
|
||||||
|
"name": "area_class",
|
||||||
|
"VSName": "Area Class",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "PROP", "value": "Propinsi"},
|
||||||
|
{"key": "KAB", "value": "Kabupaten"},
|
||||||
|
{"key": "KOTA", "value": "Kota"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/body_site.json
Normal file
12
app/Libraries/Data/valuesets/body_site.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 24,
|
||||||
|
"name": "body_site",
|
||||||
|
"VSName": "Body Site",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "LA", "value": "Left Arm"},
|
||||||
|
{"key": "RA", "value": "Right Arm"},
|
||||||
|
{"key": "LF", "value": "Left Foot"},
|
||||||
|
{"key": "RF", "value": "Right Foot"}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
app/Libraries/Data/valuesets/collection_method.json
Normal file
18
app/Libraries/Data/valuesets/collection_method.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 23,
|
||||||
|
"name": "collection_method",
|
||||||
|
"VSName": "Collection Method",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "pcntr", "value": "Puncture"},
|
||||||
|
{"key": "fprk", "value": "Finger-prick sampling"},
|
||||||
|
{"key": "ucct", "value": "Urine specimen collection, clean catch"},
|
||||||
|
{"key": "utcl", "value": "Timed urine collection"},
|
||||||
|
{"key": "ucth", "value": "Urine specimen collection, catheterized"},
|
||||||
|
{"key": "scgh", "value": "Collection of coughed sputum"},
|
||||||
|
{"key": "bpsy", "value": "Biopsy"},
|
||||||
|
{"key": "aspn", "value": "Aspiration"},
|
||||||
|
{"key": "excs", "value": "Excision"},
|
||||||
|
{"key": "scrp", "value": "Scraping"}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
app/Libraries/Data/valuesets/container_cap_color.json
Normal file
16
app/Libraries/Data/valuesets/container_cap_color.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 34,
|
||||||
|
"name": "container_cap_color",
|
||||||
|
"VSName": "Container Cap Color",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "PRPL", "value": "Purple"},
|
||||||
|
{"key": "RED", "value": "Red"},
|
||||||
|
{"key": "YLLW", "value": "Yellow"},
|
||||||
|
{"key": "GRN", "value": "Green"},
|
||||||
|
{"key": "PINK", "value": "Pink"},
|
||||||
|
{"key": "LBLU", "value": "Light Blue"},
|
||||||
|
{"key": "RBLU", "value": "Royal Blue"},
|
||||||
|
{"key": "GRAY", "value": "Gray"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/container_class.json
Normal file
11
app/Libraries/Data/valuesets/container_class.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 14,
|
||||||
|
"name": "container_class",
|
||||||
|
"VSName": "Container Class",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "Pri", "value": "Primary"},
|
||||||
|
{"key": "Sec", "value": "Secondary"},
|
||||||
|
{"key": "Ter", "value": "Tertiary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/container_size.json
Normal file
12
app/Libraries/Data/valuesets/container_size.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 25,
|
||||||
|
"name": "container_size",
|
||||||
|
"VSName": "Container Size",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "5ml", "value": "5 mL"},
|
||||||
|
{"key": "7ml", "value": "7 mL"},
|
||||||
|
{"key": "10ml", "value": "10 mL"},
|
||||||
|
{"key": "1l", "value": "1 L"}
|
||||||
|
]
|
||||||
|
}
|
||||||
26
app/Libraries/Data/valuesets/country.json
Normal file
26
app/Libraries/Data/valuesets/country.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 33,
|
||||||
|
"name": "country",
|
||||||
|
"VSName": "Country",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "ID", "value": "Indonesia"},
|
||||||
|
{"key": "US", "value": "United States"},
|
||||||
|
{"key": "MY", "value": "Malaysia"},
|
||||||
|
{"key": "SG", "value": "Singapore"},
|
||||||
|
{"key": "AU", "value": "Australia"},
|
||||||
|
{"key": "JP", "value": "Japan"},
|
||||||
|
{"key": "KR", "value": "South Korea"},
|
||||||
|
{"key": "CN", "value": "China"},
|
||||||
|
{"key": "IN", "value": "India"},
|
||||||
|
{"key": "TH", "value": "Thailand"},
|
||||||
|
{"key": "PH", "value": "Philippines"},
|
||||||
|
{"key": "VN", "value": "Vietnam"},
|
||||||
|
{"key": "GB", "value": "United Kingdom"},
|
||||||
|
{"key": "DE", "value": "Germany"},
|
||||||
|
{"key": "FR", "value": "France"},
|
||||||
|
{"key": "NL", "value": "Netherlands"},
|
||||||
|
{"key": "CA", "value": "Canada"},
|
||||||
|
{"key": "NZ", "value": "New Zealand"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/death_indicator.json
Normal file
10
app/Libraries/Data/valuesets/death_indicator.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 5,
|
||||||
|
"name": "death_indicator",
|
||||||
|
"VSName": "Death Indicator",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "Y", "value": "Death"},
|
||||||
|
{"key": "N", "value": "Life"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/did_type.json
Normal file
11
app/Libraries/Data/valuesets/did_type.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 8,
|
||||||
|
"name": "did_type",
|
||||||
|
"VSName": "DID Type",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "WDID", "value": "Windows Device ID"},
|
||||||
|
{"key": "AAID", "value": "Android AAID"},
|
||||||
|
{"key": "IDFA", "value": "iOS IDFA"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/enable_disable.json
Normal file
10
app/Libraries/Data/valuesets/enable_disable.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 2,
|
||||||
|
"name": "enable_disable",
|
||||||
|
"VSName": "Enable/Disable",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "0", "value": "Disabled"},
|
||||||
|
{"key": "1", "value": "Enabled"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/entity_type.json
Normal file
12
app/Libraries/Data/valuesets/entity_type.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 39,
|
||||||
|
"name": "entity_type",
|
||||||
|
"VSName": "Entity Type",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "HIS", "value": "HIS"},
|
||||||
|
{"key": "SITE", "value": "Site"},
|
||||||
|
{"key": "WST", "value": "Workstation"},
|
||||||
|
{"key": "INST", "value": "Equipment/Instrument"}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
app/Libraries/Data/valuesets/ethnic.json
Normal file
16
app/Libraries/Data/valuesets/ethnic.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 32,
|
||||||
|
"name": "ethnic",
|
||||||
|
"VSName": "Ethnic",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "PPMLN", "value": "Papua Melanezoid"},
|
||||||
|
{"key": "NGRID", "value": "Negroid"},
|
||||||
|
{"key": "WDOID", "value": "Weddoid"},
|
||||||
|
{"key": "MMPM", "value": "Melayu Mongoloid_Proto Melayu"},
|
||||||
|
{"key": "MMDM", "value": "Melayu Mongoloid_Deutro Melayu"},
|
||||||
|
{"key": "TNGHA", "value": "Tionghoa"},
|
||||||
|
{"key": "INDIA", "value": "India"},
|
||||||
|
{"key": "ARAB", "value": "Arab"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/fasting_status.json
Normal file
11
app/Libraries/Data/valuesets/fasting_status.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 26,
|
||||||
|
"name": "fasting_status",
|
||||||
|
"VSName": "Fasting Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "F", "value": "Fasting"},
|
||||||
|
{"key": "NF", "value": "Not Fasting"},
|
||||||
|
{"key": "NG", "value": "Not Given"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/formula_language.json
Normal file
12
app/Libraries/Data/valuesets/formula_language.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 29,
|
||||||
|
"name": "formula_language",
|
||||||
|
"VSName": "Formula Language",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "Phyton", "value": "Phyton"},
|
||||||
|
{"key": "CQL", "value": "Clinical Quality Language"},
|
||||||
|
{"key": "FHIRP", "value": "FHIRPath"},
|
||||||
|
{"key": "SQL", "value": "SQL"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/gender.json
Normal file
11
app/Libraries/Data/valuesets/gender.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 3,
|
||||||
|
"name": "gender",
|
||||||
|
"VSName": "Gender",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "1", "value": "Female"},
|
||||||
|
{"key": "2", "value": "Male"},
|
||||||
|
{"key": "3", "value": "Unknown"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/generate_by.json
Normal file
10
app/Libraries/Data/valuesets/generate_by.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 17,
|
||||||
|
"name": "generate_by",
|
||||||
|
"VSName": "Generate By",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "order", "value": "Generate by order"},
|
||||||
|
{"key": "user", "value": "Generate by user"}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
app/Libraries/Data/valuesets/identifier_type.json
Normal file
13
app/Libraries/Data/valuesets/identifier_type.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 6,
|
||||||
|
"name": "identifier_type",
|
||||||
|
"VSName": "Identifier Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "KTP", "value": "Kartu Tanda Penduduk"},
|
||||||
|
{"key": "PASS", "value": "Passport"},
|
||||||
|
{"key": "SSN", "value": "Social Security Number"},
|
||||||
|
{"key": "SIM", "value": "Surat Izin Mengemudi"},
|
||||||
|
{"key": "KTAS", "value": "Kartu Izin Tinggal Terbatas"}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
app/Libraries/Data/valuesets/location_type.json
Normal file
16
app/Libraries/Data/valuesets/location_type.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 12,
|
||||||
|
"name": "location_type",
|
||||||
|
"VSName": "Location Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "FCLT", "value": "Facility"},
|
||||||
|
{"key": "BLDG", "value": "Building"},
|
||||||
|
{"key": "FLOR", "value": "Floor"},
|
||||||
|
{"key": "POC", "value": "Point of Care"},
|
||||||
|
{"key": "ROOM", "value": "Room"},
|
||||||
|
{"key": "BED", "value": "Bed"},
|
||||||
|
{"key": "MOBL", "value": "Mobile"},
|
||||||
|
{"key": "REMT", "value": "Remote"}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
app/Libraries/Data/valuesets/marital_status.json
Normal file
16
app/Libraries/Data/valuesets/marital_status.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 4,
|
||||||
|
"name": "marital_status",
|
||||||
|
"VSName": "Marital Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "A", "value": "Separated"},
|
||||||
|
{"key": "D", "value": "Divorced"},
|
||||||
|
{"key": "M", "value": "Married"},
|
||||||
|
{"key": "S", "value": "Single"},
|
||||||
|
{"key": "W", "value": "Widowed"},
|
||||||
|
{"key": "B", "value": "Unmarried"},
|
||||||
|
{"key": "U", "value": "Unknown"},
|
||||||
|
{"key": "O", "value": "Other"}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
app/Libraries/Data/valuesets/math_sign.json
Normal file
13
app/Libraries/Data/valuesets/math_sign.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 41,
|
||||||
|
"name": "math_sign",
|
||||||
|
"VSName": "Math Sign",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "=", "value": "Equal"},
|
||||||
|
{"key": "<", "value": "Less than"},
|
||||||
|
{"key": ">", "value": "Greater than"},
|
||||||
|
{"key": "<=", "value": "Less than or equal to"},
|
||||||
|
{"key": ">=", "value": "Greater than or equal to"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/numeric_ref_type.json
Normal file
10
app/Libraries/Data/valuesets/numeric_ref_type.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 46,
|
||||||
|
"name": "numeric_ref_type",
|
||||||
|
"VSName": "Numeric Reference Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "RANGE", "value": "Range"},
|
||||||
|
{"key": "THOLD", "value": "Threshold"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/operation.json
Normal file
12
app/Libraries/Data/valuesets/operation.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 7,
|
||||||
|
"name": "operation",
|
||||||
|
"VSName": "Operation (CRUD)",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "Create", "value": "Create record"},
|
||||||
|
{"key": "Read", "value": "Read record/field"},
|
||||||
|
{"key": "Update", "value": "Update record/field"},
|
||||||
|
{"key": "Delete", "value": "Delete record/field"}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
app/Libraries/Data/valuesets/order_priority.json
Normal file
15
app/Libraries/Data/valuesets/order_priority.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 10,
|
||||||
|
"name": "order_priority",
|
||||||
|
"VSName": "Order Priority",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "S", "value": "Stat"},
|
||||||
|
{"key": "A", "value": "ASAP"},
|
||||||
|
{"key": "R", "value": "Routine"},
|
||||||
|
{"key": "P", "value": "Preop"},
|
||||||
|
{"key": "C", "value": "Callback"},
|
||||||
|
{"key": "T", "value": "Timing critical"},
|
||||||
|
{"key": "PRN", "value": "As needed"}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
app/Libraries/Data/valuesets/order_status.json
Normal file
20
app/Libraries/Data/valuesets/order_status.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 11,
|
||||||
|
"name": "order_status",
|
||||||
|
"VSName": "Order Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "A", "value": "Some, not all results available"},
|
||||||
|
{"key": "CA", "value": "Order is cancelled"},
|
||||||
|
{"key": "CM", "value": "Order is completed"},
|
||||||
|
{"key": "DC", "value": "Order was discontinued"},
|
||||||
|
{"key": "ER", "value": "Error, order not found"},
|
||||||
|
{"key": "HD", "value": "Order on hold"},
|
||||||
|
{"key": "IP", "value": "In process, unspecified"},
|
||||||
|
{"key": "RP", "value": "Order has been replaced"},
|
||||||
|
{"key": "SC", "value": "In process, scheduled"},
|
||||||
|
{"key": "CL", "value": "Closed"},
|
||||||
|
{"key": "AC", "value": "Archived"},
|
||||||
|
{"key": "DL", "value": "Deleted"}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
app/Libraries/Data/valuesets/priority.json
Normal file
15
app/Libraries/Data/valuesets/priority.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 10,
|
||||||
|
"name": "priority",
|
||||||
|
"VSName": "Priority",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "S", "value": "Stat"},
|
||||||
|
{"key": "A", "value": "ASAP"},
|
||||||
|
{"key": "R", "value": "Routine"},
|
||||||
|
{"key": "P", "value": "Preop"},
|
||||||
|
{"key": "C", "value": "Callback"},
|
||||||
|
{"key": "T", "value": "Timing critical"},
|
||||||
|
{"key": "PRN", "value": "As needed"}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
app/Libraries/Data/valuesets/race.json
Normal file
39
app/Libraries/Data/valuesets/race.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 30,
|
||||||
|
"name": "race",
|
||||||
|
"VSName": "Race (Ethnicity)",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "JAWA", "value": "Jawa"},
|
||||||
|
{"key": "SUNDA", "value": "Sunda"},
|
||||||
|
{"key": "BATAK", "value": "Batak"},
|
||||||
|
{"key": "SULOR", "value": "Suku asal Sulawesi lainnya"},
|
||||||
|
{"key": "MDRA", "value": "Madura"},
|
||||||
|
{"key": "BTWI", "value": "Betawi"},
|
||||||
|
{"key": "MNG", "value": "Minangkabau"},
|
||||||
|
{"key": "BUGIS", "value": "Bugis"},
|
||||||
|
{"key": "MLYU", "value": "Melayu"},
|
||||||
|
{"key": "SUMSL", "value": "Suku asal Sumatera Selatan"},
|
||||||
|
{"key": "BTNOR", "value": "Suku asal Banten"},
|
||||||
|
{"key": "NTTOR", "value": "Suku asal Nusa Tenggara Timur"},
|
||||||
|
{"key": "BNJAR", "value": "Banjar"},
|
||||||
|
{"key": "ACEH", "value": "Aceh"},
|
||||||
|
{"key": "BALI", "value": "Bali"},
|
||||||
|
{"key": "SASAK", "value": "Sasak"},
|
||||||
|
{"key": "DAYAK", "value": "Dayak"},
|
||||||
|
{"key": "TNGHA", "value": "Tionghoa"},
|
||||||
|
{"key": "PPAOR", "value": "Suku asal Papua"},
|
||||||
|
{"key": "MKSSR", "value": "Makassar"},
|
||||||
|
{"key": "SUMOR", "value": "Suku asal Sumatera lainnya"},
|
||||||
|
{"key": "MLKOR", "value": "Suku asal Maluku"},
|
||||||
|
{"key": "KLMOR", "value": "Suku asal Kalimantan lainnya"},
|
||||||
|
{"key": "CRBON", "value": "Cirebon"},
|
||||||
|
{"key": "JBIOR", "value": "Suku asal Jambi"},
|
||||||
|
{"key": "LPGOR", "value": "Suku Lampung"},
|
||||||
|
{"key": "NTBOR", "value": "Suku asal Nusa Tenggara Barat lainnya"},
|
||||||
|
{"key": "GRTLO", "value": "Gorontalo"},
|
||||||
|
{"key": "MNHSA", "value": "Minahasa"},
|
||||||
|
{"key": "NIAS", "value": "Nias"},
|
||||||
|
{"key": "FORGN", "value": "Asing/luar negeri"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/range_type.json
Normal file
12
app/Libraries/Data/valuesets/range_type.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 45,
|
||||||
|
"name": "range_type",
|
||||||
|
"VSName": "Range Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "REF", "value": "Reference Range"},
|
||||||
|
{"key": "CRTC", "value": "Critical Range"},
|
||||||
|
{"key": "VAL", "value": "Validation Range"},
|
||||||
|
{"key": "RERUN", "value": "Rerun Range"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/reference_type.json
Normal file
10
app/Libraries/Data/valuesets/reference_type.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 44,
|
||||||
|
"name": "reference_type",
|
||||||
|
"VSName": "Reference Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "NMRC", "value": "Numeric"},
|
||||||
|
{"key": "TEXT", "value": "Text"}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
app/Libraries/Data/valuesets/religion.json
Normal file
15
app/Libraries/Data/valuesets/religion.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 31,
|
||||||
|
"name": "religion",
|
||||||
|
"VSName": "Religion",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "ISLAM", "value": "Islam"},
|
||||||
|
{"key": "KRSTN", "value": "Kristen"},
|
||||||
|
{"key": "KTLIK", "value": "Katolik"},
|
||||||
|
{"key": "HINDU", "value": "Hindu"},
|
||||||
|
{"key": "BUDHA", "value": "Budha"},
|
||||||
|
{"key": "KHCU", "value": "Khong Hu Cu"},
|
||||||
|
{"key": "OTHER", "value": "Lainnya"}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
app/Libraries/Data/valuesets/request_status.json
Normal file
28
app/Libraries/Data/valuesets/request_status.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 20,
|
||||||
|
"name": "request_status",
|
||||||
|
"VSName": "Request Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "STC", "value": "To be collected"},
|
||||||
|
{"key": "SCFld", "value": "Collection failed"},
|
||||||
|
{"key": "SCtd", "value": "Collected"},
|
||||||
|
{"key": "STran", "value": "In-transport"},
|
||||||
|
{"key": "STFld", "value": "Transport failed"},
|
||||||
|
{"key": "SArrv", "value": "Arrived"},
|
||||||
|
{"key": "SRejc", "value": "Rejected"},
|
||||||
|
{"key": "SRcvd", "value": "Received"},
|
||||||
|
{"key": "SPAna", "value": "Pre-analytical"},
|
||||||
|
{"key": "SPAF", "value": "Pre-analytical failed"},
|
||||||
|
{"key": "STA", "value": "To be analyze"},
|
||||||
|
{"key": "SAFld", "value": "Analytical failed"},
|
||||||
|
{"key": "SAna", "value": "Analytical"},
|
||||||
|
{"key": "STS", "value": "To be stored"},
|
||||||
|
{"key": "SSFld", "value": "Store failed"},
|
||||||
|
{"key": "SStrd", "value": "Stored"},
|
||||||
|
{"key": "SExp", "value": "Expired"},
|
||||||
|
{"key": "STD", "value": "To be destroyed"},
|
||||||
|
{"key": "SDFld", "value": "Failed to destroy"},
|
||||||
|
{"key": "SDstd", "value": "Destroyed"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/requested_entity.json
Normal file
12
app/Libraries/Data/valuesets/requested_entity.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 9,
|
||||||
|
"name": "requested_entity",
|
||||||
|
"VSName": "Requested Entity",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "PAT", "value": "Patient"},
|
||||||
|
{"key": "ISN", "value": "Insurance"},
|
||||||
|
{"key": "ACC", "value": "Account"},
|
||||||
|
{"key": "DOC", "value": "Doctor"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/result_status.json
Normal file
12
app/Libraries/Data/valuesets/result_status.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 99,
|
||||||
|
"name": "result_status",
|
||||||
|
"VSName": "Result Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "PRELIMINARY", "value": "Preliminary"},
|
||||||
|
{"key": "FINAL", "value": "Final"},
|
||||||
|
{"key": "CORRECTED", "value": "Corrected"},
|
||||||
|
{"key": "CANCELLED", "value": "Cancelled"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/result_type.json
Normal file
12
app/Libraries/Data/valuesets/result_type.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 43,
|
||||||
|
"name": "result_type",
|
||||||
|
"VSName": "Result Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "NMRIC", "value": "Numeric"},
|
||||||
|
{"key": "RANGE", "value": "Range"},
|
||||||
|
{"key": "TEXT", "value": "Text"},
|
||||||
|
{"key": "VSET", "value": "Value set"}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
app/Libraries/Data/valuesets/result_unit.json
Normal file
18
app/Libraries/Data/valuesets/result_unit.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 28,
|
||||||
|
"name": "result_unit",
|
||||||
|
"VSName": "Result Unit",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "g/dL", "value": "g/dL"},
|
||||||
|
{"key": "g/L", "value": "g/L"},
|
||||||
|
{"key": "mg/dL", "value": "mg/dL"},
|
||||||
|
{"key": "mg/L", "value": "mg/L"},
|
||||||
|
{"key": "L/L", "value": "L/L"},
|
||||||
|
{"key": "x106/mL", "value": "x106/mL"},
|
||||||
|
{"key": "x1012/L", "value": "x1012/L"},
|
||||||
|
{"key": "fL", "value": "fL"},
|
||||||
|
{"key": "pg", "value": "pg"},
|
||||||
|
{"key": "x109/L", "value": "x109/L"}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
app/Libraries/Data/valuesets/site_class.json
Normal file
14
app/Libraries/Data/valuesets/site_class.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 38,
|
||||||
|
"name": "site_class",
|
||||||
|
"VSName": "Site Class",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "A", "value": "Kelas A"},
|
||||||
|
{"key": "B", "value": "Kelas B"},
|
||||||
|
{"key": "C", "value": "Kelas C"},
|
||||||
|
{"key": "D", "value": "Kelas D"},
|
||||||
|
{"key": "Utm", "value": "Utama"},
|
||||||
|
{"key": "Ptm", "value": "Pratama"}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
app/Libraries/Data/valuesets/site_type.json
Normal file
14
app/Libraries/Data/valuesets/site_type.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 37,
|
||||||
|
"name": "site_type",
|
||||||
|
"VSName": "Site Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "GH", "value": "Government Hospital"},
|
||||||
|
{"key": "PH", "value": "Private Hospital"},
|
||||||
|
{"key": "GHL", "value": "Government Hospital Lab"},
|
||||||
|
{"key": "PHL", "value": "Private Hospital Lab"},
|
||||||
|
{"key": "GL", "value": "Government Lab"},
|
||||||
|
{"key": "PL", "value": "Private Lab"}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
app/Libraries/Data/valuesets/specimen_activity.json
Normal file
15
app/Libraries/Data/valuesets/specimen_activity.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 18,
|
||||||
|
"name": "specimen_activity",
|
||||||
|
"VSName": "Specimen Activity",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "SColl", "value": "Collection"},
|
||||||
|
{"key": "STran", "value": "Transport"},
|
||||||
|
{"key": "SRec", "value": "Reception"},
|
||||||
|
{"key": "SPrep", "value": "Preparation"},
|
||||||
|
{"key": "SAlqt", "value": "Aliquot"},
|
||||||
|
{"key": "SDisp", "value": "Dispatching"},
|
||||||
|
{"key": "SDest", "value": "Destruction"}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
app/Libraries/Data/valuesets/specimen_condition.json
Normal file
19
app/Libraries/Data/valuesets/specimen_condition.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 21,
|
||||||
|
"name": "specimen_condition",
|
||||||
|
"VSName": "Specimen Condition",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "HEM", "value": "Hemolyzed"},
|
||||||
|
{"key": "ITC", "value": "Icteric"},
|
||||||
|
{"key": "LIP", "value": "Lipemic"},
|
||||||
|
{"key": "CFU", "value": "Centrifuged"},
|
||||||
|
{"key": "ROOM", "value": "Room temperature"},
|
||||||
|
{"key": "COOL", "value": "Cool"},
|
||||||
|
{"key": "FROZ", "value": "Frozen"},
|
||||||
|
{"key": "CLOT", "value": "Clotted"},
|
||||||
|
{"key": "AUT", "value": "Autolyzed"},
|
||||||
|
{"key": "CON", "value": "Contaminated"},
|
||||||
|
{"key": "LIVE", "value": "Live"}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
app/Libraries/Data/valuesets/specimen_role.json
Normal file
17
app/Libraries/Data/valuesets/specimen_role.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 22,
|
||||||
|
"name": "specimen_role",
|
||||||
|
"VSName": "Specimen Role",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "P", "value": "Patient"},
|
||||||
|
{"key": "B", "value": "Blind Sample"},
|
||||||
|
{"key": "Q", "value": "Control specimen"},
|
||||||
|
{"key": "E", "value": "Electronic QC"},
|
||||||
|
{"key": "F", "value": "Filler Organization Proficiency"},
|
||||||
|
{"key": "O", "value": "Operator Proficiency"},
|
||||||
|
{"key": "C", "value": "Calibrator"},
|
||||||
|
{"key": "R", "value": "Replicate"},
|
||||||
|
{"key": "V", "value": "Verifying Calibrator"}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
app/Libraries/Data/valuesets/specimen_status.json
Normal file
28
app/Libraries/Data/valuesets/specimen_status.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 20,
|
||||||
|
"name": "specimen_status",
|
||||||
|
"VSName": "Specimen Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "STC", "value": "To be collected"},
|
||||||
|
{"key": "SCFld", "value": "Collection failed"},
|
||||||
|
{"key": "SCtd", "value": "Collected"},
|
||||||
|
{"key": "STran", "value": "In-transport"},
|
||||||
|
{"key": "STFld", "value": "Transport failed"},
|
||||||
|
{"key": "SArrv", "value": "Arrived"},
|
||||||
|
{"key": "SRejc", "value": "Rejected"},
|
||||||
|
{"key": "SRcvd", "value": "Received"},
|
||||||
|
{"key": "SPAna", "value": "Pre-analytical"},
|
||||||
|
{"key": "SPAF", "value": "Pre-analytical failed"},
|
||||||
|
{"key": "STA", "value": "To be analyze"},
|
||||||
|
{"key": "SAFld", "value": "Analytical failed"},
|
||||||
|
{"key": "SAna", "value": "Analytical"},
|
||||||
|
{"key": "STS", "value": "To be stored"},
|
||||||
|
{"key": "SSFld", "value": "Store failed"},
|
||||||
|
{"key": "SStrd", "value": "Stored"},
|
||||||
|
{"key": "SExp", "value": "Expired"},
|
||||||
|
{"key": "STD", "value": "To be destroyed"},
|
||||||
|
{"key": "SDFld", "value": "Failed to destroy"},
|
||||||
|
{"key": "SDstd", "value": "Destroyed"}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
app/Libraries/Data/valuesets/specimen_type.json
Normal file
23
app/Libraries/Data/valuesets/specimen_type.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 15,
|
||||||
|
"name": "specimen_type",
|
||||||
|
"VSName": "Specimen Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "BLD", "value": "Whole blood"},
|
||||||
|
{"key": "BLDA", "value": "Blood arterial"},
|
||||||
|
{"key": "BLDCO", "value": "Cord blood"},
|
||||||
|
{"key": "FBLOOD", "value": "Blood, Fetal"},
|
||||||
|
{"key": "CSF", "value": "Cerebral spinal fluid"},
|
||||||
|
{"key": "WB", "value": "Blood, Whole"},
|
||||||
|
{"key": "BBL", "value": "Blood bag"},
|
||||||
|
{"key": "SER", "value": "Serum"},
|
||||||
|
{"key": "PLAS", "value": "Plasma"},
|
||||||
|
{"key": "PLB", "value": "Plasma bag"},
|
||||||
|
{"key": "MUCOS", "value": "Mucosa"},
|
||||||
|
{"key": "MUCUS", "value": "Mucus"},
|
||||||
|
{"key": "UR", "value": "Urine"},
|
||||||
|
{"key": "RANDU", "value": "Urine, Random"},
|
||||||
|
{"key": "URINM", "value": "Urine, Midstream"}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
app/Libraries/Data/valuesets/test_activity.json
Normal file
13
app/Libraries/Data/valuesets/test_activity.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 35,
|
||||||
|
"name": "test_activity",
|
||||||
|
"VSName": "Test Activity",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "ORD", "value": "Order"},
|
||||||
|
{"key": "ANA", "value": "Analyse"},
|
||||||
|
{"key": "VER", "value": "Result Verification/Technical Validation"},
|
||||||
|
{"key": "REV", "value": "Clinical Review/Clinical Validation"},
|
||||||
|
{"key": "REP", "value": "Reporting"}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/Libraries/Data/valuesets/test_status.json
Normal file
12
app/Libraries/Data/valuesets/test_status.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 99,
|
||||||
|
"name": "test_status",
|
||||||
|
"VSName": "Test Status",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "PENDING", "value": "Waiting for Results"},
|
||||||
|
{"key": "IN_PROCESS", "value": "Analyzing"},
|
||||||
|
{"key": "VERIFIED", "value": "Verified & Signed"},
|
||||||
|
{"key": "REJECTED", "value": "Sample Rejected"}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
app/Libraries/Data/valuesets/test_type.json
Normal file
13
app/Libraries/Data/valuesets/test_type.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 27,
|
||||||
|
"name": "test_type",
|
||||||
|
"VSName": "Test Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "TEST", "value": "Test"},
|
||||||
|
{"key": "PARAM", "value": "Parameter"},
|
||||||
|
{"key": "CALC", "value": "Calculated Test"},
|
||||||
|
{"key": "GROUP", "value": "Group Test"},
|
||||||
|
{"key": "TITLE", "value": "Title"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/text_ref_type.json
Normal file
10
app/Libraries/Data/valuesets/text_ref_type.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 47,
|
||||||
|
"name": "text_ref_type",
|
||||||
|
"VSName": "Text Reference Type",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "VSET", "value": "Value Set"},
|
||||||
|
{"key": "TEXT", "value": "Text"}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
app/Libraries/Data/valuesets/unit.json
Normal file
11
app/Libraries/Data/valuesets/unit.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 16,
|
||||||
|
"name": "unit",
|
||||||
|
"VSName": "Unit",
|
||||||
|
"VCategory": "User-defined",
|
||||||
|
"values": [
|
||||||
|
{"key": "L", "value": "Liter"},
|
||||||
|
{"key": "mL", "value": "Mili Liter"},
|
||||||
|
{"key": "Pcs", "value": "Pieces"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/v_category.json
Normal file
10
app/Libraries/Data/valuesets/v_category.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 42,
|
||||||
|
"name": "v_category",
|
||||||
|
"VSName": "VCategory",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "0", "value": "System"},
|
||||||
|
{"key": "1", "value": "User-defined"}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
app/Libraries/Data/valuesets/ws_type.json
Normal file
10
app/Libraries/Data/valuesets/ws_type.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"VSetID": 1,
|
||||||
|
"name": "ws_type",
|
||||||
|
"VSName": "Workstation Type",
|
||||||
|
"VCategory": "System",
|
||||||
|
"values": [
|
||||||
|
{"key": "0", "value": "Primary"},
|
||||||
|
{"key": "1", "value": "Secondary"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,649 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Libraries;
|
namespace App\Libraries;
|
||||||
|
|
||||||
/**
|
class Lookups extends ValueSet {
|
||||||
* Static Lookup Values
|
|
||||||
* All predefined lookup values stored as constants for easy access.
|
|
||||||
* No database queries - all data is hardcoded.
|
|
||||||
* Based on valuesetdef and valueset tables.
|
|
||||||
*/
|
|
||||||
class Lookups {
|
|
||||||
|
|
||||||
// VSetID 1: Workstation Type
|
|
||||||
const WS_TYPE = [
|
|
||||||
'0' => 'Primary',
|
|
||||||
'1' => 'Secondary'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 2: Enable/Disable
|
|
||||||
const ENABLE_DISABLE = [
|
|
||||||
'0' => 'Disabled',
|
|
||||||
'1' => 'Enabled'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 3: Gender
|
|
||||||
const GENDER = [
|
|
||||||
'1' => 'Female',
|
|
||||||
'2' => 'Male',
|
|
||||||
'3' => 'Unknown'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 4: Marital Status
|
|
||||||
const MARITAL_STATUS = [
|
|
||||||
'A' => 'Separated',
|
|
||||||
'D' => 'Divorced',
|
|
||||||
'M' => 'Married',
|
|
||||||
'S' => 'Single',
|
|
||||||
'W' => 'Widowed',
|
|
||||||
'B' => 'Unmarried',
|
|
||||||
'U' => 'Unknown',
|
|
||||||
'O' => 'Other'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 5: Death Indicator
|
|
||||||
const DEATH_INDICATOR = [
|
|
||||||
'Y' => 'Death',
|
|
||||||
'N' => 'Life'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 6: Identifier Type
|
|
||||||
const IDENTIFIER_TYPE = [
|
|
||||||
'KTP' => 'Kartu Tanda Penduduk',
|
|
||||||
'PASS' => 'Passport',
|
|
||||||
'SSN' => 'Social Security Number',
|
|
||||||
'SIM' => 'Surat Izin Mengemudi',
|
|
||||||
'KTAS' => 'Kartu Izin Tinggal Terbatas'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 7: Operation (CRUD)
|
|
||||||
const OPERATION = [
|
|
||||||
'Create' => 'Create record',
|
|
||||||
'Read' => 'Read record/field',
|
|
||||||
'Update' => 'Update record/field',
|
|
||||||
'Delete' => 'Delete record/field'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 8: DID Type
|
|
||||||
const DID_TYPE = [
|
|
||||||
'WDID' => 'Windows Device ID',
|
|
||||||
'AAID' => 'Android AAID',
|
|
||||||
'IDFA' => 'iOS IDFA'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 9: Requested Entity
|
|
||||||
const REQUESTED_ENTITY = [
|
|
||||||
'PAT' => 'Patient',
|
|
||||||
'ISN' => 'Insurance',
|
|
||||||
'ACC' => 'Account',
|
|
||||||
'DOC' => 'Doctor'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 10: Order Priority
|
|
||||||
const ORDER_PRIORITY = [
|
|
||||||
'S' => 'Stat',
|
|
||||||
'A' => 'ASAP',
|
|
||||||
'R' => 'Routine',
|
|
||||||
'P' => 'Preop',
|
|
||||||
'C' => 'Callback',
|
|
||||||
'T' => 'Timing critical',
|
|
||||||
'PRN' => 'As needed'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 11: Order Status
|
|
||||||
const ORDER_STATUS = [
|
|
||||||
'A' => 'Some, not all results available',
|
|
||||||
'CA' => 'Order is cancelled',
|
|
||||||
'CM' => 'Order is completed',
|
|
||||||
'DC' => 'Order was discontinued',
|
|
||||||
'ER' => 'Error, order not found',
|
|
||||||
'HD' => 'Order on hold',
|
|
||||||
'IP' => 'In process, unspecified',
|
|
||||||
'RP' => 'Order has been replaced',
|
|
||||||
'SC' => 'In process, scheduled',
|
|
||||||
'CL' => 'Closed',
|
|
||||||
'AC' => 'Archived',
|
|
||||||
'DL' => 'Deleted'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 12: Location Type
|
|
||||||
const LOCATION_TYPE = [
|
|
||||||
'FCLT' => 'Facility',
|
|
||||||
'BLDG' => 'Building',
|
|
||||||
'FLOR' => 'Floor',
|
|
||||||
'POC' => 'Point of Care',
|
|
||||||
'ROOM' => 'Room',
|
|
||||||
'BED' => 'Bed',
|
|
||||||
'MOBL' => 'Mobile',
|
|
||||||
'REMT' => 'Remote'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 13: Additive
|
|
||||||
const ADDITIVE = [
|
|
||||||
'Hep' => 'Heparin ammonium',
|
|
||||||
'Apro' => 'Aprotinin',
|
|
||||||
'HepCa' => 'Heparin calcium',
|
|
||||||
'H3BO3' => 'Boric acid',
|
|
||||||
'CaOxa' => 'Calcium oxalate',
|
|
||||||
'EDTA' => 'EDTA',
|
|
||||||
'Ede' => 'Edetate',
|
|
||||||
'HCl' => 'Hydrochloric acid',
|
|
||||||
'Hrdn' => 'Hirudin',
|
|
||||||
'EdeK' => 'Edetate dipotassium',
|
|
||||||
'EdeTri' => 'Tripotassium edetate',
|
|
||||||
'LiHep' => 'Heparin lithium',
|
|
||||||
'EdeNa' => 'Edetate disodium',
|
|
||||||
'NaCtrt' => 'Sodium citrate',
|
|
||||||
'NaHep' => 'Heparin sodium',
|
|
||||||
'NaF' => 'Sodium fluoride',
|
|
||||||
'Borax' => 'Sodium tetraborate',
|
|
||||||
'Mntl' => 'Mannitol',
|
|
||||||
'NaFrm' => 'Sodium formate'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 14: Container Class
|
|
||||||
const CONTAINER_CLASS = [
|
|
||||||
'Pri' => 'Primary',
|
|
||||||
'Sec' => 'Secondary',
|
|
||||||
'Ter' => 'Tertiary'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 15: Specimen Type
|
|
||||||
const SPECIMEN_TYPE = [
|
|
||||||
'BLD' => 'Whole blood',
|
|
||||||
'BLDA' => 'Blood arterial',
|
|
||||||
'BLDCO' => 'Cord blood',
|
|
||||||
'FBLOOD' => 'Blood, Fetal',
|
|
||||||
'CSF' => 'Cerebral spinal fluid',
|
|
||||||
'WB' => 'Blood, Whole',
|
|
||||||
'BBL' => 'Blood bag',
|
|
||||||
'SER' => 'Serum',
|
|
||||||
'PLAS' => 'Plasma',
|
|
||||||
'PLB' => 'Plasma bag',
|
|
||||||
'MUCOS' => 'Mucosa',
|
|
||||||
'MUCUS' => 'Mucus',
|
|
||||||
'UR' => 'Urine',
|
|
||||||
'RANDU' => 'Urine, Random',
|
|
||||||
'URINM' => 'Urine, Midstream'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 16: Unit
|
|
||||||
const UNIT = [
|
|
||||||
'L' => 'Liter',
|
|
||||||
'mL' => 'Mili Liter',
|
|
||||||
'Pcs' => 'Pieces'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 17: Generate By
|
|
||||||
const GENERATE_BY = [
|
|
||||||
'order' => 'Generate by order',
|
|
||||||
'user' => 'Generate by user'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 18: Specimen Activity
|
|
||||||
const SPECIMEN_ACTIVITY = [
|
|
||||||
'SColl' => 'Collection',
|
|
||||||
'STran' => 'Transport',
|
|
||||||
'SRec' => 'Reception',
|
|
||||||
'SPrep' => 'Preparation',
|
|
||||||
'SAlqt' => 'Aliquot',
|
|
||||||
'SDisp' => 'Dispatching',
|
|
||||||
'SDest' => 'Destruction'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 19: Activity Result
|
|
||||||
const ACTIVITY_RESULT = [
|
|
||||||
'0' => 'Failed',
|
|
||||||
'1' => 'Success with note',
|
|
||||||
'2' => 'Success'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 20: Specimen Status
|
|
||||||
const SPECIMEN_STATUS = [
|
|
||||||
'STC' => 'To be collected',
|
|
||||||
'SCFld' => 'Collection failed',
|
|
||||||
'SCtd' => 'Collected',
|
|
||||||
'STran' => 'In-transport',
|
|
||||||
'STFld' => 'Transport failed',
|
|
||||||
'SArrv' => 'Arrived',
|
|
||||||
'SRejc' => 'Rejected',
|
|
||||||
'SRcvd' => 'Received',
|
|
||||||
'SPAna' => 'Pre-analytical',
|
|
||||||
'SPAF' => 'Pre-analytical failed',
|
|
||||||
'STA' => 'To be analyze',
|
|
||||||
'SAFld' => 'Analytical failed',
|
|
||||||
'SAna' => 'Analytical',
|
|
||||||
'STS' => 'To be stored',
|
|
||||||
'SSFld' => 'Store failed',
|
|
||||||
'SStrd' => 'Stored',
|
|
||||||
'SExp' => 'Expired',
|
|
||||||
'STD' => 'To be destroyed',
|
|
||||||
'SDFld' => 'Failed to destroy',
|
|
||||||
'SDstd' => 'Destroyed'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 21: Specimen Condition
|
|
||||||
const SPECIMEN_CONDITION = [
|
|
||||||
'HEM' => 'Hemolyzed',
|
|
||||||
'ITC' => 'Icteric',
|
|
||||||
'LIP' => 'Lipemic',
|
|
||||||
'CFU' => 'Centrifuged',
|
|
||||||
'ROOM' => 'Room temperature',
|
|
||||||
'COOL' => 'Cool',
|
|
||||||
'FROZ' => 'Frozen',
|
|
||||||
'CLOT' => 'Clotted',
|
|
||||||
'AUT' => 'Autolyzed',
|
|
||||||
'CON' => 'Contaminated',
|
|
||||||
'LIVE' => 'Live'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 22: Specimen Role
|
|
||||||
const SPECIMEN_ROLE = [
|
|
||||||
'P' => 'Patient',
|
|
||||||
'B' => 'Blind Sample',
|
|
||||||
'Q' => 'Control specimen',
|
|
||||||
'E' => 'Electronic QC',
|
|
||||||
'F' => 'Filler Organization Proficiency',
|
|
||||||
'O' => 'Operator Proficiency',
|
|
||||||
'C' => 'Calibrator',
|
|
||||||
'R' => 'Replicate',
|
|
||||||
'V' => 'Verifying Calibrator'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 23: Collection Method
|
|
||||||
const COLLECTION_METHOD = [
|
|
||||||
'pcntr' => 'Puncture',
|
|
||||||
'fprk' => 'Finger-prick sampling',
|
|
||||||
'ucct' => 'Urine specimen collection, clean catch',
|
|
||||||
'utcl' => 'Timed urine collection',
|
|
||||||
'ucth' => 'Urine specimen collection, catheterized',
|
|
||||||
'scgh' => 'Collection of coughed sputum',
|
|
||||||
'bpsy' => 'Biopsy',
|
|
||||||
'aspn' => 'Aspiration',
|
|
||||||
'excs' => 'Excision',
|
|
||||||
'scrp' => 'Scraping'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 24: Body Site
|
|
||||||
const BODY_SITE = [
|
|
||||||
'LA' => 'Left Arm',
|
|
||||||
'RA' => 'Right Arm',
|
|
||||||
'LF' => 'Left Foot',
|
|
||||||
'RF' => 'Right Foot'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 25: Container Size
|
|
||||||
const CONTAINER_SIZE = [
|
|
||||||
'5ml' => '5 mL',
|
|
||||||
'7ml' => '7 mL',
|
|
||||||
'10ml' => '10 mL',
|
|
||||||
'1l' => '1 L'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 26: Fasting Status
|
|
||||||
const FASTING_STATUS = [
|
|
||||||
'F' => 'Fasting',
|
|
||||||
'NF' => 'Not Fasting',
|
|
||||||
'NG' => 'Not Given'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 27: Test Type
|
|
||||||
const TEST_TYPE = [
|
|
||||||
'TEST' => 'Test',
|
|
||||||
'PARAM' => 'Parameter',
|
|
||||||
'CALC' => 'Calculated Test',
|
|
||||||
'GROUP' => 'Group Test',
|
|
||||||
'TITLE' => 'Title'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 28: Result Unit
|
|
||||||
const RESULT_UNIT = [
|
|
||||||
'g/dL' => 'g/dL',
|
|
||||||
'g/L' => 'g/L',
|
|
||||||
'mg/dL' => 'mg/dL',
|
|
||||||
'mg/L' => 'mg/L',
|
|
||||||
'L/L' => 'L/L',
|
|
||||||
'x106/mL' => 'x106/mL',
|
|
||||||
'x1012/L' => 'x1012/L',
|
|
||||||
'fL' => 'fL',
|
|
||||||
'pg' => 'pg',
|
|
||||||
'x109/L' => 'x109/L'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 29: Formula Language
|
|
||||||
const FORMULA_LANGUAGE = [
|
|
||||||
'Phyton' => 'Phyton',
|
|
||||||
'CQL' => 'Clinical Quality Language',
|
|
||||||
'FHIRP' => 'FHIRPath',
|
|
||||||
'SQL' => 'SQL'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 30: Race (Ethnicity)
|
|
||||||
const RACE = [
|
|
||||||
'JAWA' => 'Jawa',
|
|
||||||
'SUNDA' => 'Sunda',
|
|
||||||
'BATAK' => 'Batak',
|
|
||||||
'SULOR' => 'Suku asal Sulawesi lainnya',
|
|
||||||
'MDRA' => 'Madura',
|
|
||||||
'BTWI' => 'Betawi',
|
|
||||||
'MNG' => 'Minangkabau',
|
|
||||||
'BUGIS' => 'Bugis',
|
|
||||||
'MLYU' => 'Melayu',
|
|
||||||
'SUMSL' => 'Suku asal Sumatera Selatan',
|
|
||||||
'BTNOR' => 'Suku asal Banten',
|
|
||||||
'NTTOR' => 'Suku asal Nusa Tenggara Timur',
|
|
||||||
'BNJAR' => 'Banjar',
|
|
||||||
'ACEH' => 'Aceh',
|
|
||||||
'BALI' => 'Bali',
|
|
||||||
'SASAK' => 'Sasak',
|
|
||||||
'DAYAK' => 'Dayak',
|
|
||||||
'TNGHA' => 'Tionghoa',
|
|
||||||
'PPAOR' => 'Suku asal Papua',
|
|
||||||
'MKSSR' => 'Makassar',
|
|
||||||
'SUMOR' => 'Suku asal Sumatera lainnya',
|
|
||||||
'MLKOR' => 'Suku asal Maluku',
|
|
||||||
'KLMOR' => 'Suku asal Kalimantan lainnya',
|
|
||||||
'CRBON' => 'Cirebon',
|
|
||||||
'JBIOR' => 'Suku asal Jambi',
|
|
||||||
'LPGOR' => 'Suku Lampung',
|
|
||||||
'NTBOR' => 'Suku asal Nusa Tenggara Barat lainnya',
|
|
||||||
'GRTLO' => 'Gorontalo',
|
|
||||||
'MNHSA' => 'Minahasa',
|
|
||||||
'NIAS' => 'Nias',
|
|
||||||
'FORGN' => 'Asing/luar negeri'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 31: Religion
|
|
||||||
const RELIGION = [
|
|
||||||
'ISLAM' => 'Islam',
|
|
||||||
'KRSTN' => 'Kristen',
|
|
||||||
'KTLIK' => 'Katolik',
|
|
||||||
'HINDU' => 'Hindu',
|
|
||||||
'BUDHA' => 'Budha',
|
|
||||||
'KHCU' => 'Khong Hu Cu',
|
|
||||||
'OTHER' => 'Lainnya'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 32: Ethnic
|
|
||||||
const ETHNIC = [
|
|
||||||
'PPMLN' => 'Papua Melanezoid',
|
|
||||||
'NGRID' => 'Negroid',
|
|
||||||
'WDOID' => 'Weddoid',
|
|
||||||
'MMPM' => 'Melayu Mongoloid_Proto Melayu',
|
|
||||||
'MMDM' => 'Melayu Mongoloid_Deutro Melayu',
|
|
||||||
'TNGHA' => 'Tionghoa',
|
|
||||||
'INDIA' => 'India',
|
|
||||||
'ARAB' => 'Arab'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 33: Country (ISO 2-letter codes - ISO 3166-1 alpha-2)
|
|
||||||
const COUNTRY = null; // Loaded from external file
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get COUNTRY data from external file (lazy load)
|
|
||||||
*/
|
|
||||||
private static function loadCountryData(): array {
|
|
||||||
$file = APPPATH . 'Libraries/Data/Countries.php';
|
|
||||||
if (is_file($file)) {
|
|
||||||
return require $file;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get formatted country list
|
|
||||||
*/
|
|
||||||
public static function getCountry(): array {
|
|
||||||
return self::format(self::loadCountryData());
|
|
||||||
}
|
|
||||||
|
|
||||||
// VSetID 34: Container Cap Color
|
|
||||||
const CONTAINER_CAP_COLOR = [
|
|
||||||
'PRPL' => 'Purple',
|
|
||||||
'RED' => 'Red',
|
|
||||||
'YLLW' => 'Yellow',
|
|
||||||
'GRN' => 'Green',
|
|
||||||
'PINK' => 'Pink',
|
|
||||||
'LBLU' => 'Light Blue',
|
|
||||||
'RBLU' => 'Royal Blue',
|
|
||||||
'GRAY' => 'Gray'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 35: Test Activity
|
|
||||||
const TEST_ACTIVITY = [
|
|
||||||
'ORD' => 'Order',
|
|
||||||
'ANA' => 'Analyse',
|
|
||||||
'VER' => 'Result Verification/Technical Validation',
|
|
||||||
'REV' => 'Clinical Review/Clinical Validation',
|
|
||||||
'REP' => 'Reporting'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 36: ADT Event
|
|
||||||
const ADT_EVENT = [
|
|
||||||
'A01' => 'Admit',
|
|
||||||
'A02' => 'Transfer',
|
|
||||||
'A03' => 'Discharge',
|
|
||||||
'A04' => 'Register',
|
|
||||||
'A08' => 'Update patient information',
|
|
||||||
'A11' => 'Cancel admit',
|
|
||||||
'A12' => 'Cancel transfer',
|
|
||||||
'A13' => 'Cancel discharge',
|
|
||||||
'A23' => 'Delete patient record',
|
|
||||||
'A24' => 'Link patient information',
|
|
||||||
'A37' => 'Unlink patient information',
|
|
||||||
'A54' => 'Change attending doctor',
|
|
||||||
'A61' => 'Change consulting doctor'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 37: Site Type
|
|
||||||
const SITE_TYPE = [
|
|
||||||
'GH' => 'Government Hospital',
|
|
||||||
'PH' => 'Private Hospital',
|
|
||||||
'GHL' => 'Government Hospital Lab',
|
|
||||||
'PHL' => 'Private Hospital Lab',
|
|
||||||
'GL' => 'Government Lab',
|
|
||||||
'PL' => 'Private Lab'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 38: Site Class
|
|
||||||
const SITE_CLASS = [
|
|
||||||
'A' => 'Kelas A',
|
|
||||||
'B' => 'Kelas B',
|
|
||||||
'C' => 'Kelas C',
|
|
||||||
'D' => 'Kelas D',
|
|
||||||
'Utm' => 'Utama',
|
|
||||||
'Ptm' => 'Pratama'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 39: Entity Type
|
|
||||||
const ENTITY_TYPE = [
|
|
||||||
'HIS' => 'HIS',
|
|
||||||
'SITE' => 'Site',
|
|
||||||
'WST' => 'Workstation',
|
|
||||||
'INST' => 'Equipment/Instrument'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 40: Area Class
|
|
||||||
const AREA_CLASS = [
|
|
||||||
'PROP' => 'Propinsi',
|
|
||||||
'KAB' => 'Kabupaten',
|
|
||||||
'KOTA' => 'Kota'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 41: Math Sign
|
|
||||||
const MATH_SIGN = [
|
|
||||||
'=' => 'Equal',
|
|
||||||
'<' => 'Less than',
|
|
||||||
'>' => 'Greater than',
|
|
||||||
'<=' => 'Less than or equal to',
|
|
||||||
'>=' => 'Greater than or equal to'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 42: VCategory
|
|
||||||
const V_CATEGORY = [
|
|
||||||
'0' => 'System',
|
|
||||||
'1' => 'User-defined'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 43: Result Type
|
|
||||||
const RESULT_TYPE = [
|
|
||||||
'NMRIC' => 'Numeric',
|
|
||||||
'RANGE' => 'Range',
|
|
||||||
'TEXT' => 'Text',
|
|
||||||
'VSET' => 'Value set'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 44: Reference Type
|
|
||||||
const REFERENCE_TYPE = [
|
|
||||||
'NMRC' => 'Numeric',
|
|
||||||
'TEXT' => 'Text'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 45: Range Type
|
|
||||||
const RANGE_TYPE = [
|
|
||||||
'REF' => 'Reference Range',
|
|
||||||
'CRTC' => 'Critical Range',
|
|
||||||
'VAL' => 'Validation Range',
|
|
||||||
'RERUN' => 'Rerun Range'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 46: Numeric Reference Type
|
|
||||||
const NUMERIC_REF_TYPE = [
|
|
||||||
'RANGE' => 'Range',
|
|
||||||
'THOLD' => 'Threshold'
|
|
||||||
];
|
|
||||||
|
|
||||||
// VSetID 47: Text Reference Type
|
|
||||||
const TEXT_REF_TYPE = [
|
|
||||||
'VSET' => 'Value Set',
|
|
||||||
'TEXT' => 'Text'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Convenience constants (aliases for common use cases)
|
|
||||||
const PRIORITY = self::ORDER_PRIORITY;
|
|
||||||
const TEST_STATUS = [
|
|
||||||
'PENDING' => 'Waiting for Results',
|
|
||||||
'IN_PROCESS' => 'Analyzing',
|
|
||||||
'VERIFIED' => 'Verified & Signed',
|
|
||||||
'REJECTED' => 'Sample Rejected'
|
|
||||||
];
|
|
||||||
const REQUEST_STATUS = self::SPECIMEN_STATUS;
|
|
||||||
const RESULT_STATUS = [
|
|
||||||
'PRELIMINARY' => 'Preliminary',
|
|
||||||
'FINAL' => 'Final',
|
|
||||||
'CORRECTED' => 'Corrected',
|
|
||||||
'CANCELLED' => 'Cancelled'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all lookups formatted for frontend
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function getAll(): array {
|
|
||||||
return [
|
|
||||||
'ws_type' => self::format(self::WS_TYPE),
|
|
||||||
'enable_disable' => self::format(self::ENABLE_DISABLE),
|
|
||||||
'gender' => self::format(self::GENDER),
|
|
||||||
'marital_status' => self::format(self::MARITAL_STATUS),
|
|
||||||
'death_indicator' => self::format(self::DEATH_INDICATOR),
|
|
||||||
'identifier_type' => self::format(self::IDENTIFIER_TYPE),
|
|
||||||
'operation' => self::format(self::OPERATION),
|
|
||||||
'did_type' => self::format(self::DID_TYPE),
|
|
||||||
'requested_entity' => self::format(self::REQUESTED_ENTITY),
|
|
||||||
'order_priority' => self::format(self::ORDER_PRIORITY),
|
|
||||||
'order_status' => self::format(self::ORDER_STATUS),
|
|
||||||
'location_type' => self::format(self::LOCATION_TYPE),
|
|
||||||
'additive' => self::format(self::ADDITIVE),
|
|
||||||
'container_class' => self::format(self::CONTAINER_CLASS),
|
|
||||||
'specimen_type' => self::format(self::SPECIMEN_TYPE),
|
|
||||||
'unit' => self::format(self::UNIT),
|
|
||||||
'generate_by' => self::format(self::GENERATE_BY),
|
|
||||||
'specimen_activity' => self::format(self::SPECIMEN_ACTIVITY),
|
|
||||||
'activity_result' => self::format(self::ACTIVITY_RESULT),
|
|
||||||
'specimen_status' => self::format(self::SPECIMEN_STATUS),
|
|
||||||
'specimen_condition' => self::format(self::SPECIMEN_CONDITION),
|
|
||||||
'specimen_role' => self::format(self::SPECIMEN_ROLE),
|
|
||||||
'collection_method' => self::format(self::COLLECTION_METHOD),
|
|
||||||
'body_site' => self::format(self::BODY_SITE),
|
|
||||||
'container_size' => self::format(self::CONTAINER_SIZE),
|
|
||||||
'fasting_status' => self::format(self::FASTING_STATUS),
|
|
||||||
'test_type' => self::format(self::TEST_TYPE),
|
|
||||||
'result_unit' => self::format(self::RESULT_UNIT),
|
|
||||||
'formula_language' => self::format(self::FORMULA_LANGUAGE),
|
|
||||||
'race' => self::format(self::RACE),
|
|
||||||
'religion' => self::format(self::RELIGION),
|
|
||||||
'ethnic' => self::format(self::ETHNIC),
|
|
||||||
'country' => self::getCountry(),
|
|
||||||
'container_cap_color' => self::format(self::CONTAINER_CAP_COLOR),
|
|
||||||
'test_activity' => self::format(self::TEST_ACTIVITY),
|
|
||||||
'adt_event' => self::format(self::ADT_EVENT),
|
|
||||||
'site_type' => self::format(self::SITE_TYPE),
|
|
||||||
'site_class' => self::format(self::SITE_CLASS),
|
|
||||||
'entity_type' => self::format(self::ENTITY_TYPE),
|
|
||||||
'area_class' => self::format(self::AREA_CLASS),
|
|
||||||
'math_sign' => self::format(self::MATH_SIGN),
|
|
||||||
'v_category' => self::format(self::V_CATEGORY),
|
|
||||||
'result_type' => self::format(self::RESULT_TYPE),
|
|
||||||
'reference_type' => self::format(self::REFERENCE_TYPE),
|
|
||||||
'range_type' => self::format(self::RANGE_TYPE),
|
|
||||||
'numeric_ref_type' => self::format(self::NUMERIC_REF_TYPE),
|
|
||||||
'text_ref_type' => self::format(self::TEXT_REF_TYPE)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format associative array as [{value: 'KEY', label: 'Label'}, ...]
|
|
||||||
*/
|
|
||||||
private static function format(array $array): array {
|
|
||||||
$result = [];
|
|
||||||
foreach ($array as $key => $label) {
|
|
||||||
$result[] = ['value' => (string) $key, 'label' => (string) $label];
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get single lookup by constant name (case-insensitive)
|
|
||||||
* @param string $name Constant name (e.g., 'gender', 'PRIORITY')
|
|
||||||
* @return array|null
|
|
||||||
*/
|
|
||||||
public static function get(string $name): ?array {
|
|
||||||
$const = strtoupper($name);
|
|
||||||
// Special case for COUNTRY (loaded from external file)
|
|
||||||
if ($const === 'COUNTRY') {
|
|
||||||
return self::getCountry();
|
|
||||||
}
|
|
||||||
if (defined("self::$const")) {
|
|
||||||
return self::format(constant("self::$const"));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get raw constant array (not formatted)
|
|
||||||
* @param string $name
|
|
||||||
* @return array|null
|
|
||||||
*/
|
|
||||||
public static function getRaw(string $name): ?array {
|
|
||||||
$const = strtoupper($name);
|
|
||||||
// Special case for COUNTRY (loaded from external file)
|
|
||||||
if ($const === 'COUNTRY') {
|
|
||||||
return self::loadCountryData();
|
|
||||||
}
|
|
||||||
if (defined("self::$const")) {
|
|
||||||
return constant("self::$const");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get label by key from a lookup
|
|
||||||
* @param string $lookup Lookup name
|
|
||||||
* @param string $key Key to find
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public static function getLabel(string $lookup, string $key): ?string {
|
|
||||||
$raw = self::getRaw($lookup);
|
|
||||||
return $raw[$key] ?? null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
108
app/Libraries/ValueSet.php
Normal file
108
app/Libraries/ValueSet.php
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Libraries;
|
||||||
|
|
||||||
|
use CodeIgniter\Cache\CacheFactory;
|
||||||
|
use CodeIgniter\Config\BaseConfig;
|
||||||
|
|
||||||
|
class ValueSet {
|
||||||
|
private static $cache = null;
|
||||||
|
private static string $dataPath = APPPATH . 'Libraries/Data/valuesets/';
|
||||||
|
private static string $cacheKey = 'valueset_all';
|
||||||
|
|
||||||
|
private static function getCacheHandler() {
|
||||||
|
if (self::$cache === null) {
|
||||||
|
$config = config('Cache');
|
||||||
|
self::$cache = CacheFactory::getHandler($config);
|
||||||
|
}
|
||||||
|
return self::$cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(string $name): ?array {
|
||||||
|
$all = self::getAll();
|
||||||
|
$values = $all[$name]['values'] ?? null;
|
||||||
|
if ($values === null) return null;
|
||||||
|
return self::format($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRaw(string $name): ?array {
|
||||||
|
$all = self::getAll();
|
||||||
|
return $all[$name]['values'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAll(): array {
|
||||||
|
$handler = self::getCacheHandler();
|
||||||
|
$data = $handler->get(self::$cacheKey);
|
||||||
|
|
||||||
|
if ($data !== null) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = self::bundleAll();
|
||||||
|
$handler->save(self::$cacheKey, $data, 0);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLabel(string $lookupName, string $key): ?string {
|
||||||
|
$raw = self::getRaw($lookupName);
|
||||||
|
if ($raw === null) return null;
|
||||||
|
foreach ($raw as $item) {
|
||||||
|
if (($item['key'] ?? $item['value'] ?? null) === $key) {
|
||||||
|
return $item['value'] ?? $item['label'] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getOptions(string $lookupName): array {
|
||||||
|
$raw = self::getRaw($lookupName);
|
||||||
|
if ($raw === null) return [];
|
||||||
|
return array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'key' => $item['key'] ?? '',
|
||||||
|
'value' => $item['value'] ?? $item['label'] ?? '',
|
||||||
|
];
|
||||||
|
}, $raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function transformLabels(array $data, array $fieldMappings): array {
|
||||||
|
foreach ($data as &$row) {
|
||||||
|
foreach ($fieldMappings as $field => $lookupName) {
|
||||||
|
if (isset($row[$field]) && $row[$field] !== null) {
|
||||||
|
$row[$field . 'Text'] = self::getLabel($lookupName, $row[$field]) ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clearCache(): bool {
|
||||||
|
$handler = self::getCacheHandler();
|
||||||
|
return $handler->delete(self::$cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function bundleAll(): array {
|
||||||
|
$result = [];
|
||||||
|
foreach (glob(self::$dataPath . '*.json') as $file) {
|
||||||
|
$name = pathinfo($file, PATHINFO_FILENAME);
|
||||||
|
if ($name[0] === '_') continue;
|
||||||
|
$data = self::loadFile($file);
|
||||||
|
if ($data) {
|
||||||
|
$result[$name] = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function loadFile(string $path): ?array {
|
||||||
|
if (!is_file($path)) return null;
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
return json_decode($content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function format(array $values): array {
|
||||||
|
return array_map(fn($v) => [
|
||||||
|
'value' => (string) ($v['key'] ?? ''),
|
||||||
|
'label' => (string) ($v['value'] ?? $v['label'] ?? '')
|
||||||
|
], $values);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Models\Location;
|
namespace App\Models\Location;
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class LocationModel extends BaseModel {
|
class LocationModel extends BaseModel {
|
||||||
protected $table = 'location';
|
protected $table = 'location';
|
||||||
@ -14,24 +15,31 @@ class LocationModel extends BaseModel {
|
|||||||
protected $deletedField = 'EndDate';
|
protected $deletedField = 'EndDate';
|
||||||
|
|
||||||
public function getLocations($LocCode, $LocName) {
|
public function getLocations($LocCode, $LocName) {
|
||||||
$sql = $this->select("LocationID, LocCode, Parent, LocFull, LocType, v.VDesc as LocTypeText")
|
$sql = $this->select("LocationID, LocCode, Parent, LocFull, LocType");
|
||||||
->join("valueset v", "v.VID=location.loctype", 'left');
|
|
||||||
if($LocName != '') { $sql->like('LocFull', $LocName, 'both'); }
|
if($LocName != '') { $sql->like('LocFull', $LocName, 'both'); }
|
||||||
if($LocCode != '') { $sql->like('LocCode', $LocCode, 'both'); }
|
if($LocCode != '') { $sql->like('LocCode', $LocCode, 'both'); }
|
||||||
$rows = $sql->findAll();
|
$rows = $sql->findAll();
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'LocType' => 'location_type',
|
||||||
|
]);
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLocation($LocationID) {
|
public function getLocation($LocationID) {
|
||||||
//'Street1', 'Street2', 'City', 'Province', 'PostCode', 'GeoLocationSystem', 'GeoLocationData',
|
|
||||||
$row = $this->select("location.*, la.Street1, la.Street2, la.PostCode, la.GeoLocationSystem, la.GeoLocationData,
|
$row = $this->select("location.*, la.Street1, la.Street2, la.PostCode, la.GeoLocationSystem, la.GeoLocationData,
|
||||||
v.VDesc as LocTypeText, prop.AreaGeoID as ProvinceID, prop.AreaName as Province, city.AreaGeoID as CityID, city.AreaName as City, site.SiteID, site.SiteName")
|
prop.AreaGeoID as ProvinceID, prop.AreaName as Province, city.AreaGeoID as CityID, city.AreaName as City, site.SiteID, site.SiteName")
|
||||||
->join("locationaddress la", "location.LocationID=la.LocationID", "left")
|
->join("locationaddress la", "location.LocationID=la.LocationID", "left")
|
||||||
->join("valueset v", "v.VID=location.loctype", "left")
|
|
||||||
->join("areageo prop", "la.Province=prop.AreaGeoID", "left")
|
->join("areageo prop", "la.Province=prop.AreaGeoID", "left")
|
||||||
->join("areageo city", "la.City=city.AreaGeoID", "left")
|
->join("areageo city", "la.City=city.AreaGeoID", "left")
|
||||||
->join("site", "site.SiteID=location.SiteID", "left")
|
->join("site", "site.SiteID=location.SiteID", "left")
|
||||||
->where('location.LocationID', (int) $LocationID)->first();
|
->where('location.LocationID', (int) $LocationID)->first();
|
||||||
|
|
||||||
|
if (!$row) return null;
|
||||||
|
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'LocType' => 'location_type',
|
||||||
|
])[0];
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,4 +91,3 @@ class LocationModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
102
app/Models/OrderTest/OrderTestModel.php
Normal file
102
app/Models/OrderTest/OrderTestModel.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Models\OrderTest;
|
||||||
|
|
||||||
|
use App\Models\BaseModel;
|
||||||
|
|
||||||
|
class OrderTestModel extends BaseModel {
|
||||||
|
protected $table = 'ordertest';
|
||||||
|
protected $primaryKey = 'OrderID';
|
||||||
|
protected $allowedFields = [
|
||||||
|
'OrderID',
|
||||||
|
'InternalPID',
|
||||||
|
'PatVisitID',
|
||||||
|
'OrderDateTime',
|
||||||
|
'Priority',
|
||||||
|
'OrderStatus',
|
||||||
|
'OrderedBy',
|
||||||
|
'OrderingProvider',
|
||||||
|
'SiteID',
|
||||||
|
'SourceSiteID',
|
||||||
|
'DepartmentID',
|
||||||
|
'WorkstationID',
|
||||||
|
'BillingAccount',
|
||||||
|
'DelDate'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function generateOrderID(string $siteCode = '00'): string {
|
||||||
|
$date = new \DateTime();
|
||||||
|
$year = $date->format('y');
|
||||||
|
$month = $date->format('m');
|
||||||
|
$day = $date->format('d');
|
||||||
|
|
||||||
|
$counter = $this->db->table('counter')
|
||||||
|
->where('CounterName', 'ORDER')
|
||||||
|
->get()
|
||||||
|
->getRow();
|
||||||
|
|
||||||
|
if (!$counter) {
|
||||||
|
$this->db->table('counter')->insert([
|
||||||
|
'CounterName' => 'ORDER',
|
||||||
|
'CounterValue' => 1
|
||||||
|
]);
|
||||||
|
$seq = 1;
|
||||||
|
} else {
|
||||||
|
$seq = $counter->CounterValue + 1;
|
||||||
|
$this->db->table('counter')
|
||||||
|
->where('CounterName', 'ORDER')
|
||||||
|
->update(['CounterValue' => $seq]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$seqStr = str_pad($seq, 5, '0', STR_PAD_LEFT);
|
||||||
|
return $siteCode . $year . $month . $day . $seqStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createOrder(array $data): string {
|
||||||
|
$orderID = $data['OrderID'] ?? $this->generateOrderID();
|
||||||
|
|
||||||
|
$orderData = [
|
||||||
|
'OrderID' => $orderID,
|
||||||
|
'InternalPID' => $data['InternalPID'],
|
||||||
|
'PatVisitID' => $data['PatVisitID'] ?? null,
|
||||||
|
'OrderDateTime' => $data['OrderDateTime'] ?? date('Y-m-d H:i:s'),
|
||||||
|
'Priority' => $data['Priority'] ?? 'R',
|
||||||
|
'OrderStatus' => $data['OrderStatus'] ?? 'ORD',
|
||||||
|
'OrderedBy' => $data['OrderedBy'] ?? null,
|
||||||
|
'OrderingProvider' => $data['OrderingProvider'] ?? null,
|
||||||
|
'SiteID' => $data['SiteID'] ?? 1,
|
||||||
|
'SourceSiteID' => $data['SourceSiteID'] ?? 1,
|
||||||
|
'DepartmentID' => $data['DepartmentID'] ?? null,
|
||||||
|
'WorkstationID' => $data['WorkstationID'] ?? null,
|
||||||
|
'BillingAccount' => $data['BillingAccount'] ?? null,
|
||||||
|
'CreateDate' => date('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->insert($orderData);
|
||||||
|
return $orderID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrder(string $orderID): ?array {
|
||||||
|
return $this->select('*')
|
||||||
|
->where('OrderID', $orderID)
|
||||||
|
->where('DelDate', null)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrdersByPatient(int $internalPID): array {
|
||||||
|
return $this->select('*')
|
||||||
|
->where('InternalPID', $internalPID)
|
||||||
|
->where('DelDate', null)
|
||||||
|
->orderBy('OrderDateTime', 'DESC')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateStatus(string $orderID, string $status): bool {
|
||||||
|
return $this->where('OrderID', $orderID)->update(['OrderStatus' => $status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function softDelete(string $orderID): bool {
|
||||||
|
return $this->where('OrderID', $orderID)->update(['DelDate' => date('Y-m-d H:i:s')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Models\Organization;
|
namespace App\Models\Organization;
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class AccountModel extends BaseModel {
|
class AccountModel extends BaseModel {
|
||||||
protected $table = 'account';
|
protected $table = 'account';
|
||||||
@ -14,7 +15,7 @@ class AccountModel extends BaseModel {
|
|||||||
protected $updatedField = '';
|
protected $updatedField = '';
|
||||||
protected $useSoftDeletes = true;
|
protected $useSoftDeletes = true;
|
||||||
protected $deletedField = 'EndDate';
|
protected $deletedField = 'EndDate';
|
||||||
|
|
||||||
public function getAccounts($filter=[]) {
|
public function getAccounts($filter=[]) {
|
||||||
$builder = $this->select('account.AccountID, account.AccountName, account.Parent, pa.AccountName as ParentName, account.Initial')
|
$builder = $this->select('account.AccountID, account.AccountName, account.Parent, pa.AccountName as ParentName, account.Initial')
|
||||||
->join('account pa', 'pa.AccountID=account.Parent', 'left');
|
->join('account pa', 'pa.AccountID=account.Parent', 'left');
|
||||||
@ -32,15 +33,20 @@ class AccountModel extends BaseModel {
|
|||||||
|
|
||||||
public function getAccount($AccountID) {
|
public function getAccount($AccountID) {
|
||||||
$row = $this->select('account.*, pa.AccountName as ParentName, areageo.AreaName, areageo.AreaGeoID,
|
$row = $this->select('account.*, pa.AccountName as ParentName, areageo.AreaName, areageo.AreaGeoID,
|
||||||
city.AreaName as CityName, city.AreaGeoID as City, prov.AreaName as ProvName, prov.AreaGeoID as Prov,
|
city.AreaName as CityName, city.AreaGeoID as City, prov.AreaName as ProvName, prov.AreaGeoID as Prov')
|
||||||
country.VValue as CountryName, country.VID as country')
|
|
||||||
->join('account pa', 'pa.AccountID=account.Parent', 'left')
|
->join('account pa', 'pa.AccountID=account.Parent', 'left')
|
||||||
->join('areageo', 'areageo.AreaCode=account.AreaCode', 'left')
|
->join('areageo', 'areageo.AreaCode=account.AreaCode', 'left')
|
||||||
->join('areageo city', 'city.AreaGeoID=account.City', 'left')
|
->join('areageo city', 'city.AreaGeoID=account.City', 'left')
|
||||||
->join('areageo prov', 'prov.AreaGeoID=account.Province', 'left')
|
->join('areageo prov', 'prov.AreaGeoID=account.Province', 'left')
|
||||||
->join('valueset country', 'country.VID=account.Country', 'left')
|
|
||||||
->where('account.AccountID', $AccountID)
|
->where('account.AccountID', $AccountID)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
if (!$row) return null;
|
||||||
|
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'Country' => 'account_Country',
|
||||||
|
])[0];
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Models\Organization;
|
namespace App\Models\Organization;
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class SiteModel extends BaseModel {
|
class SiteModel extends BaseModel {
|
||||||
protected $table = 'site';
|
protected $table = 'site';
|
||||||
@ -13,7 +14,7 @@ class SiteModel extends BaseModel {
|
|||||||
protected $updatedField = '';
|
protected $updatedField = '';
|
||||||
protected $useSoftDeletes = true;
|
protected $useSoftDeletes = true;
|
||||||
protected $deletedField = 'EndDate';
|
protected $deletedField = 'EndDate';
|
||||||
|
|
||||||
public function getSites($filter) {
|
public function getSites($filter) {
|
||||||
$builder = $this->select('site.SiteID, site.SiteCode, site.SiteName, s1.SiteName as ParentName, account.AccountName')
|
$builder = $this->select('site.SiteID, site.SiteCode, site.SiteName, s1.SiteName as ParentName, account.AccountName')
|
||||||
->join('account', 'account.AccountID=site.AccountID', 'left')
|
->join('account', 'account.AccountID=site.AccountID', 'left')
|
||||||
@ -32,13 +33,19 @@ class SiteModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getSite($SiteID) {
|
public function getSite($SiteID) {
|
||||||
$row = $this->select('site.*, account.AccountName, s1.SiteName as ParentName, sitetype.VValue as SiteType, siteclass.VValue as SiteClass')
|
$row = $this->select('site.*, account.AccountName, s1.SiteName as ParentName')
|
||||||
->join('account', 'account.AccountID=site.AccountID', 'left')
|
->join('account', 'account.AccountID=site.AccountID', 'left')
|
||||||
->join('site s1', 's1.SiteID=site.Parent', 'left')
|
->join('site s1', 's1.SiteID=site.Parent', 'left')
|
||||||
->join('valueset sitetype', 'site.SiteTypeID=sitetype.VID', 'left')
|
|
||||||
->join('valueset siteclass', 'site.SiteClassID=siteclass.VID', 'left')
|
|
||||||
->where('site.SiteID', $SiteID)
|
->where('site.SiteID', $SiteID)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
if (!$row) return null;
|
||||||
|
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'SiteTypeID' => 'site_type',
|
||||||
|
'SiteClassID' => 'site_class',
|
||||||
|
])[0];
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Models\Organization;
|
namespace App\Models\Organization;
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class WorkstationModel extends BaseModel {
|
class WorkstationModel extends BaseModel {
|
||||||
protected $table = 'workstation';
|
protected $table = 'workstation';
|
||||||
@ -13,12 +14,12 @@ class WorkstationModel extends BaseModel {
|
|||||||
protected $updatedField = '';
|
protected $updatedField = '';
|
||||||
protected $useSoftDeletes = true;
|
protected $useSoftDeletes = true;
|
||||||
protected $deletedField = 'EndDate';
|
protected $deletedField = 'EndDate';
|
||||||
|
|
||||||
public function getWorkstations($filter = []) {
|
public function getWorkstations($filter = []) {
|
||||||
$this->select('workstation.*, department.DepartmentName, wst1.WorkstationName as LinkToName')
|
$this->select('workstation.*, department.DepartmentName, wst1.WorkstationName as LinkToName')
|
||||||
->join('workstation wst1', 'workstation.LinkTo=wst1.WorkstationID', 'left')
|
->join('workstation wst1', 'workstation.LinkTo=wst1.WorkstationID', 'left')
|
||||||
->join('department', 'department.DepartmentID=workstation.DepartmentID', 'left');
|
->join('department', 'department.DepartmentID=workstation.DepartmentID', 'left');
|
||||||
|
|
||||||
if (!empty($filter['WorkstationCode'])) {
|
if (!empty($filter['WorkstationCode'])) {
|
||||||
$this->like('workstation.WorkstationCode', $filter['WorkstationCode'], 'both');
|
$this->like('workstation.WorkstationCode', $filter['WorkstationCode'], 'both');
|
||||||
}
|
}
|
||||||
@ -30,13 +31,19 @@ class WorkstationModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getWorkstation($WorkstationID) {
|
public function getWorkstation($WorkstationID) {
|
||||||
$row = $this->select("workstation.*, department.DepartmentName, wst1.WorkstationName as LinkToName,enable.VDesc as EnableName, wstype.VDesc as TypeName")
|
$row = $this->select("workstation.*, department.DepartmentName, wst1.WorkstationName as LinkToName")
|
||||||
->join('workstation wst1', 'workstation.LinkTo=wst1.WorkstationID', 'left')
|
->join('workstation wst1', 'workstation.LinkTo=wst1.WorkstationID', 'left')
|
||||||
->join('department', 'department.DepartmentID=workstation.DepartmentID', 'left')
|
->join('department', 'department.DepartmentID=workstation.DepartmentID', 'left')
|
||||||
->join('valueset wstype', 'wstype.VID=workstation.Type', 'left')
|
|
||||||
->join('valueset enable', 'enable.VID=workstation.Enable', 'left')
|
|
||||||
->where('workstation.WorkstationID', $WorkstationID)
|
->where('workstation.WorkstationID', $WorkstationID)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
if (!$row) return null;
|
||||||
|
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'Type' => 'ws_type',
|
||||||
|
'Enable' => 'enable_disable',
|
||||||
|
])[0];
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
namespace App\Models\Patient;
|
namespace App\Models\Patient;
|
||||||
|
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
use App\Models\Patient\PatAttModel;
|
use App\Models\Patient\PatAttModel;
|
||||||
use App\Models\Patient\PatComModel;
|
use App\Models\Patient\PatComModel;
|
||||||
@ -10,9 +11,9 @@ use App\Models\Patient\PatIdtModel;
|
|||||||
class PatientModel extends BaseModel {
|
class PatientModel extends BaseModel {
|
||||||
protected $table = 'patient';
|
protected $table = 'patient';
|
||||||
protected $primaryKey = 'InternalPID';
|
protected $primaryKey = 'InternalPID';
|
||||||
protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Gender', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3',
|
protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Sex', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3',
|
||||||
'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship',
|
'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship',
|
||||||
'DeathIndicator', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ];
|
'DeathIndicator', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ];
|
||||||
|
|
||||||
protected $useTimestamps = true;
|
protected $useTimestamps = true;
|
||||||
protected $createdField = 'CreateDate';
|
protected $createdField = 'CreateDate';
|
||||||
@ -23,8 +24,7 @@ class PatientModel extends BaseModel {
|
|||||||
public function getPatients($filters = []) {
|
public function getPatients($filters = []) {
|
||||||
$qname = "CONCAT_WS(' ', IFNULL(Prefix,''), IFNULL(NameFirst,''), IFNULL(NameMiddle,''), IFNULL(NameLast,''), IFNULL(NameMaiden,''), IFNULL(Suffix,''))";
|
$qname = "CONCAT_WS(' ', IFNULL(Prefix,''), IFNULL(NameFirst,''), IFNULL(NameMiddle,''), IFNULL(NameLast,''), IFNULL(NameMaiden,''), IFNULL(Suffix,''))";
|
||||||
|
|
||||||
$this->select("InternalPID, PatientID, $qname as FullName, vs.VDesc as Gender, Birthdate, EmailAddress1 as Email, MobilePhone");
|
$this->select("InternalPID, PatientID, $qname as FullName, Sex, Birthdate, EmailAddress1 as Email, MobilePhone");
|
||||||
$this->join('valueset vs', 'vs.vid = Gender', 'left');
|
|
||||||
|
|
||||||
if (!empty($filters['Name'])) {
|
if (!empty($filters['Name'])) {
|
||||||
$this->like($qname, $filters['Name'], 'both');
|
$this->like($qname, $filters['Name'], 'both');
|
||||||
@ -42,26 +42,16 @@ class PatientModel extends BaseModel {
|
|||||||
$this->where('Birthdate', $filters['Birthdate']);
|
$this->where('Birthdate', $filters['Birthdate']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->findAll();
|
$rows = $this->findAll();
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'Sex' => 'gender',
|
||||||
|
]);
|
||||||
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPatient($InternalPID) {
|
public function getPatient($InternalPID) {
|
||||||
$rows = $this->select("
|
$rows = $this->select("
|
||||||
patient.*,
|
patient.*,
|
||||||
country.VDesc as Country,
|
|
||||||
country.VID as CountryVID,
|
|
||||||
race.VDesc as Race,
|
|
||||||
race.VID as RaceVID,
|
|
||||||
religion.VDesc as Religion,
|
|
||||||
religion.VID as ReligionVID,
|
|
||||||
ethnic.VDesc as Ethnic,
|
|
||||||
ethnic.VID as EthnicVID,
|
|
||||||
gender.VDesc as Gender,
|
|
||||||
gender.VID as GenderVID,
|
|
||||||
deathindicator.VDesc as DeathIndicator,
|
|
||||||
deathindicator.VID as DeathIndicatorVID,
|
|
||||||
maritalstatus.VDesc as MaritalStatus,
|
|
||||||
maritalstatus.VID as MaritalStatusVID,
|
|
||||||
patcom.Comment as Comment,
|
patcom.Comment as Comment,
|
||||||
patidt.IdentifierType,
|
patidt.IdentifierType,
|
||||||
patidt.Identifier,
|
patidt.Identifier,
|
||||||
@ -72,13 +62,6 @@ class PatientModel extends BaseModel {
|
|||||||
areageo2.AreaName as City
|
areageo2.AreaName as City
|
||||||
|
|
||||||
")
|
")
|
||||||
->join('valueset country', 'country.VID = patient.Country', 'left')
|
|
||||||
->join('valueset race', 'race.VID = patient.Race', 'left')
|
|
||||||
->join('valueset religion', 'religion.VID = patient.Religion', 'left')
|
|
||||||
->join('valueset ethnic', 'ethnic.VID = patient.Ethnic', 'left')
|
|
||||||
->join('valueset gender', 'gender.VID = patient.Gender', 'left')
|
|
||||||
->join('valueset deathindicator', 'deathindicator.VID = patient.DeathIndicator', 'left')
|
|
||||||
->join('valueset maritalstatus', 'maritalstatus.VID = patient.MaritalStatus', 'left')
|
|
||||||
->join('patcom', 'patcom.InternalPID = patient.InternalPID', 'left')
|
->join('patcom', 'patcom.InternalPID = patient.InternalPID', 'left')
|
||||||
->join('patidt', 'patidt.InternalPID = patient.InternalPID', 'left')
|
->join('patidt', 'patidt.InternalPID = patient.InternalPID', 'left')
|
||||||
->join('patatt', 'patatt.InternalPID = patient.InternalPID and patatt.DelDate is null', 'left')
|
->join('patatt', 'patatt.InternalPID = patient.InternalPID and patatt.DelDate is null', 'left')
|
||||||
@ -97,7 +80,16 @@ class PatientModel extends BaseModel {
|
|||||||
unset($patient['Identifier']);
|
unset($patient['Identifier']);
|
||||||
unset($patient['Comment']);
|
unset($patient['Comment']);
|
||||||
|
|
||||||
// Default nested structures
|
$patient = ValueSet::transformLabels([$patient], [
|
||||||
|
'Sex' => 'gender',
|
||||||
|
'Country' => 'country',
|
||||||
|
'Race' => 'race',
|
||||||
|
'Religion' => 'religion',
|
||||||
|
'Ethnic' => 'ethnic',
|
||||||
|
'DeathIndicator' => 'death_indicator',
|
||||||
|
'MaritalStatus' => 'marital_status',
|
||||||
|
])[0];
|
||||||
|
|
||||||
$patient['PatIdt'] = null;
|
$patient['PatIdt'] = null;
|
||||||
$patient['PatAtt'] = [];
|
$patient['PatAtt'] = [];
|
||||||
|
|
||||||
@ -133,24 +125,20 @@ class PatientModel extends BaseModel {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Insert Data ke Tabel Patient, get ID dan cek apa ada error
|
|
||||||
$this->insert($input);
|
$this->insert($input);
|
||||||
$newInternalPID = $this->getInsertID();
|
$newInternalPID = $this->getInsertID();
|
||||||
$this->checkDbError($db, 'Insert patient');
|
$this->checkDbError($db, 'Insert patient');
|
||||||
|
|
||||||
// Insert Data ke Tabel PatIdt
|
|
||||||
if (!empty($input['PatIdt'])) {
|
if (!empty($input['PatIdt'])) {
|
||||||
$modelPatIdt->createPatIdt($input['PatIdt'], $newInternalPID);
|
$modelPatIdt->createPatIdt($input['PatIdt'], $newInternalPID);
|
||||||
$this->checkDbError($db, 'Insert PatIdt');
|
$this->checkDbError($db, 'Insert PatIdt');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert Data ke Tabel PatCom
|
|
||||||
if (!empty($input['PatCom'])) {
|
if (!empty($input['PatCom'])) {
|
||||||
$modelPatCom->createPatCom($input['PatCom'], $newInternalPID);
|
$modelPatCom->createPatCom($input['PatCom'], $newInternalPID);
|
||||||
$this->checkDbError($db, 'Insert PatCom');
|
$this->checkDbError($db, 'Insert PatCom');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert Data ke Tabel PatAtt
|
|
||||||
if (!empty($input['PatAtt'])) {
|
if (!empty($input['PatAtt'])) {
|
||||||
$modelPatAtt->createPatAtt($input['PatAtt'], $newInternalPID);
|
$modelPatAtt->createPatAtt($input['PatAtt'], $newInternalPID);
|
||||||
$this->checkDbError($db, 'Insert PatAtt');
|
$this->checkDbError($db, 'Insert PatAtt');
|
||||||
@ -178,12 +166,10 @@ class PatientModel extends BaseModel {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Update Patient
|
|
||||||
$InternalPID = $input['InternalPID'];
|
$InternalPID = $input['InternalPID'];
|
||||||
$this->where('InternalPID',$InternalPID)->set($input)->update();
|
$this->where('InternalPID',$InternalPID)->set($input)->update();
|
||||||
$this->checkDbError($db, 'Update patient');
|
$this->checkDbError($db, 'Update patient');
|
||||||
|
|
||||||
// Update Patidt
|
|
||||||
if (!empty($input['PatIdt'])) {
|
if (!empty($input['PatIdt'])) {
|
||||||
$modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID);
|
$modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID);
|
||||||
$this->checkDbError($db, 'Update patIdt');
|
$this->checkDbError($db, 'Update patIdt');
|
||||||
@ -192,7 +178,6 @@ class PatientModel extends BaseModel {
|
|||||||
$this->checkDbError($db, 'Update patidt');
|
$this->checkDbError($db, 'Update patidt');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Patcom
|
|
||||||
if (!empty($input['PatCom'])) {
|
if (!empty($input['PatCom'])) {
|
||||||
$modelPatCom->updatePatCom($input['PatCom'], $InternalPID);
|
$modelPatCom->updatePatCom($input['PatCom'], $InternalPID);
|
||||||
$this->checkDbError($db, 'Update PatCom');
|
$this->checkDbError($db, 'Update PatCom');
|
||||||
@ -201,7 +186,6 @@ class PatientModel extends BaseModel {
|
|||||||
$this->checkDbError($db, 'Update patcom');
|
$this->checkDbError($db, 'Update patcom');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Patatt
|
|
||||||
if (!empty($input['PatAtt'])) {
|
if (!empty($input['PatAtt'])) {
|
||||||
$modelPatAtt->updatePatAtt($input['PatAtt'], $InternalPID);
|
$modelPatAtt->updatePatAtt($input['PatAtt'], $InternalPID);
|
||||||
$this->checkDbError($db, 'Update PatAtt');
|
$this->checkDbError($db, 'Update PatAtt');
|
||||||
@ -254,11 +238,9 @@ class PatientModel extends BaseModel {
|
|||||||
->first() ?: null;
|
->first() ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion to (Years Months Days) - For Age
|
|
||||||
private function calculateAgeFromBirthdate($birthdate, $deathdate) {
|
private function calculateAgeFromBirthdate($birthdate, $deathdate) {
|
||||||
$dob = new \DateTime($birthdate);
|
$dob = new \DateTime($birthdate);
|
||||||
|
|
||||||
// Cek DeathTime
|
|
||||||
if ($deathdate == null) {
|
if ($deathdate == null) {
|
||||||
$today = new \DateTime();
|
$today = new \DateTime();
|
||||||
} else {
|
} else {
|
||||||
@ -281,19 +263,17 @@ class PatientModel extends BaseModel {
|
|||||||
return $formattedAge;
|
return $formattedAge;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion Time to Format Y-m-d\TH:i:s\Z
|
|
||||||
private function formattedDate(?string $dateString): ?string {
|
private function formattedDate(?string $dateString): ?string {
|
||||||
try {
|
try {
|
||||||
if (empty($dateString)) {return null;}
|
if (empty($dateString)) {return null;}
|
||||||
|
|
||||||
$dt = new \DateTime($dateString, new \DateTimeZone("UTC"));
|
$dt = new \DateTime($dateString, new \DateTimeZone("UTC"));
|
||||||
return $dt->format('Y-m-d\TH:i:s\Z'); // ISO 8601 UTC
|
return $dt->format('Y-m-d\TH:i:s\Z');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion Time to Format j M Y - For BirthdateConversion
|
|
||||||
private function formatedDateForDisplay($dateString) {
|
private function formatedDateForDisplay($dateString) {
|
||||||
$date = \DateTime::createFromFormat('Y-m-d H:i', $dateString);
|
$date = \DateTime::createFromFormat('Y-m-d H:i', $dateString);
|
||||||
|
|
||||||
@ -308,7 +288,6 @@ class PatientModel extends BaseModel {
|
|||||||
return $date->format('j M Y');
|
return $date->format('j M Y');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Error and Send Spesific Messages
|
|
||||||
private function checkDbError($db, string $context) {
|
private function checkDbError($db, string $context) {
|
||||||
$error = $db->error();
|
$error = $db->error();
|
||||||
if (!empty($error['code'])) {
|
if (!empty($error['code'])) {
|
||||||
@ -318,17 +297,14 @@ class PatientModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preventif 0000-00-00
|
|
||||||
private function isValidDateTime($datetime) {
|
private function isValidDateTime($datetime) {
|
||||||
if (empty($datetime) || $datetime=="") {return null; }
|
if (empty($datetime) || $datetime=="") {return null; }
|
||||||
try {
|
try {
|
||||||
// Kalau input hanya Y-m-d (tanpa jam)
|
|
||||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $datetime)) {
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $datetime)) {
|
||||||
$dt = \DateTime::createFromFormat('Y-m-d', $datetime);
|
$dt = \DateTime::createFromFormat('Y-m-d', $datetime);
|
||||||
return $dt ? $dt->format('Y-m-d') : null; // hanya tanggal
|
return $dt ? $dt->format('Y-m-d') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selain itu (ISO 8601 atau datetime lain), format ke Y-m-d H:i:s
|
|
||||||
$dt = new \DateTime($datetime);
|
$dt = new \DateTime($datetime);
|
||||||
return $dt->format('Y-m-d H:i:s');
|
return $dt->format('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
namespace App\Models\Specimen;
|
namespace App\Models\Specimen;
|
||||||
|
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class ContainerDefModel extends BaseModel {
|
class ContainerDefModel extends BaseModel {
|
||||||
protected $table = 'containerdef';
|
protected $table = 'containerdef';
|
||||||
@ -16,10 +17,7 @@ class ContainerDefModel extends BaseModel {
|
|||||||
|
|
||||||
|
|
||||||
public function getContainers($filter = []) {
|
public function getContainers($filter = []) {
|
||||||
$builder = $this->select('containerdef.*, vscol.VValue as ColorTxt, vscla.VValue as ConClassTxt, vsadd.VValue as AdditiveTxt')
|
$builder = $this->select('containerdef.*');
|
||||||
->join('valueset vscol', 'vscol.VID=containerdef.Color', 'left')
|
|
||||||
->join('valueset vscla', 'vscla.VID=containerdef.ConClass', 'left')
|
|
||||||
->join('valueset vsadd', 'vsadd.VID=containerdef.Additive', 'left');
|
|
||||||
|
|
||||||
if (!empty($filter['ConCode'])) {
|
if (!empty($filter['ConCode'])) {
|
||||||
$builder->like('containerdef.ConCode', $filter['ConCode'], 'both');
|
$builder->like('containerdef.ConCode', $filter['ConCode'], 'both');
|
||||||
@ -29,15 +27,26 @@ class ContainerDefModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$rows = $builder->findAll();
|
$rows = $builder->findAll();
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'Color' => 'container_cap_color',
|
||||||
|
'ConClass' => 'container_class',
|
||||||
|
'Additive' => 'additive',
|
||||||
|
]);
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContainer($ConDefID) {
|
public function getContainer($ConDefID) {
|
||||||
$row = $this->select('containerdef.*, vscol.VValue as ColorTxt, vscla.VValue as ConClassTxt, vsadd.VValue as AdditiveTxt')
|
$row = $this->select('containerdef.*')
|
||||||
->join('valueset vscol', 'vscol.VID=containerdef.Color', 'left')
|
|
||||||
->join('valueset vscla', 'vscla.VID=containerdef.ConClass', 'left')
|
|
||||||
->join('valueset vsadd', 'vsadd.VID=containerdef.Additive', 'left')
|
|
||||||
->where('ConDefID', $ConDefID)->first();
|
->where('ConDefID', $ConDefID)->first();
|
||||||
|
|
||||||
|
if (!$row) return null;
|
||||||
|
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'Color' => 'container_cap_color',
|
||||||
|
'ConClass' => 'container_class',
|
||||||
|
'Additive' => 'additive',
|
||||||
|
])[0];
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models\Test;
|
namespace App\Models\Test;
|
||||||
|
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class TestDefGrpModel extends BaseModel {
|
class TestDefGrpModel extends BaseModel {
|
||||||
protected $table = 'testdefgrp';
|
protected $table = 'testdefgrp';
|
||||||
@ -13,32 +14,31 @@ class TestDefGrpModel extends BaseModel {
|
|||||||
'CreateDate',
|
'CreateDate',
|
||||||
'EndDate'
|
'EndDate'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $useTimestamps = true;
|
protected $useTimestamps = true;
|
||||||
protected $createdField = 'CreateDate';
|
protected $createdField = 'CreateDate';
|
||||||
protected $updatedField = '';
|
protected $updatedField = '';
|
||||||
protected $useSoftDeletes = true;
|
protected $useSoftDeletes = true;
|
||||||
protected $deletedField = "EndDate";
|
protected $deletedField = "EndDate";
|
||||||
|
|
||||||
/**
|
|
||||||
* Get group members for a test group
|
|
||||||
*/
|
|
||||||
public function getGroupMembers($testSiteID) {
|
public function getGroupMembers($testSiteID) {
|
||||||
$db = \Config\Database::connect();
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
return $db->table('testdefgrp')
|
$rows = $db->table('testdefgrp')
|
||||||
->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType, vs.VValue as MemberTypeCode')
|
->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType')
|
||||||
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
|
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
|
||||||
->join('valueset vs', 'vs.VID=t.TestType', 'left')
|
|
||||||
->where('testdefgrp.TestSiteID', $testSiteID)
|
->where('testdefgrp.TestSiteID', $testSiteID)
|
||||||
->where('testdefgrp.EndDate IS NULL')
|
->where('testdefgrp.EndDate IS NULL')
|
||||||
->orderBy('testdefgrp.TestGrpID', 'ASC')
|
->orderBy('testdefgrp.TestGrpID', 'ASC')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'TestType' => 'test_type',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all groups that contain a specific test
|
|
||||||
*/
|
|
||||||
public function getGroupsContainingTest($memberTestSiteID) {
|
public function getGroupsContainingTest($memberTestSiteID) {
|
||||||
return $this->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName')
|
return $this->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName')
|
||||||
->join('testdefsite t', 't.TestSiteID=testdefgrp.TestSiteID', 'left')
|
->join('testdefsite t', 't.TestSiteID=testdefgrp.TestSiteID', 'left')
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models\Test;
|
namespace App\Models\Test;
|
||||||
|
|
||||||
use App\Models\BaseModel;
|
use App\Models\BaseModel;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
class TestDefSiteModel extends BaseModel {
|
class TestDefSiteModel extends BaseModel {
|
||||||
protected $table = 'testdefsite';
|
protected $table = 'testdefsite';
|
||||||
@ -23,23 +24,18 @@ class TestDefSiteModel extends BaseModel {
|
|||||||
'CreateDate',
|
'CreateDate',
|
||||||
'StartDate',
|
'StartDate',
|
||||||
'EndDate'
|
'EndDate'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $useTimestamps = true;
|
protected $useTimestamps = true;
|
||||||
protected $createdField = 'CreateDate';
|
protected $createdField = 'CreateDate';
|
||||||
protected $updatedField = 'StartDate';
|
protected $updatedField = 'StartDate';
|
||||||
protected $useSoftDeletes = true;
|
protected $useSoftDeletes = true;
|
||||||
protected $deletedField = "EndDate";
|
protected $deletedField = "EndDate";
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all tests with type information
|
|
||||||
*/
|
|
||||||
public function getTests($siteId = null, $testType = null, $visibleScr = null, $visibleRpt = null, $keyword = null) {
|
public function getTests($siteId = null, $testType = null, $visibleScr = null, $visibleRpt = null, $keyword = null) {
|
||||||
$builder = $this->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType,
|
$builder = $this->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType,
|
||||||
testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt,
|
testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt,
|
||||||
testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate,
|
testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate")
|
||||||
valueset.VValue as TypeCode, valueset.VDesc as TypeName")
|
|
||||||
->join("valueset", "valueset.VID=testdefsite.TestType", "left")
|
|
||||||
->where('testdefsite.EndDate IS NULL');
|
->where('testdefsite.EndDate IS NULL');
|
||||||
|
|
||||||
if ($siteId) {
|
if ($siteId) {
|
||||||
@ -62,27 +58,29 @@ class TestDefSiteModel extends BaseModel {
|
|||||||
$builder->like('testdefsite.TestSiteName', $keyword);
|
$builder->like('testdefsite.TestSiteName', $keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $builder->orderBy('testdefsite.SeqScr', 'ASC')->findAll();
|
$rows = $builder->orderBy('testdefsite.SeqScr', 'ASC')->findAll();
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'TestType' => 'test_type',
|
||||||
|
]);
|
||||||
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get single test with all related details based on TestType
|
|
||||||
*/
|
|
||||||
public function getTest($TestSiteID) {
|
public function getTest($TestSiteID) {
|
||||||
$db = \Config\Database::connect();
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
$row = $this->select("testdefsite.*, valueset.VValue as TypeCode, valueset.VDesc as TypeName")
|
$row = $this->select("testdefsite.*")
|
||||||
->join("valueset", "valueset.VID=testdefsite.TestType", "left")
|
|
||||||
->where("testdefsite.TestSiteID", $TestSiteID)
|
->where("testdefsite.TestSiteID", $TestSiteID)
|
||||||
->find($TestSiteID);
|
->find($TestSiteID);
|
||||||
|
|
||||||
if (!$row) return null;
|
if (!$row) return null;
|
||||||
|
|
||||||
$typeCode = $row['TypeCode'] ?? '';
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'TestType' => 'test_type',
|
||||||
|
])[0];
|
||||||
|
|
||||||
|
$typeCode = $row['TestType'] ?? '';
|
||||||
|
|
||||||
// Load related details based on TestType
|
|
||||||
if ($typeCode === 'CALC') {
|
if ($typeCode === 'CALC') {
|
||||||
// Load calculation details with joined discipline and department
|
|
||||||
$row['testdefcal'] = $db->table('testdefcal')
|
$row['testdefcal'] = $db->table('testdefcal')
|
||||||
->select('testdefcal.*, d.DisciplineName, dept.DepartmentName')
|
->select('testdefcal.*, d.DisciplineName, dept.DepartmentName')
|
||||||
->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left')
|
->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left')
|
||||||
@ -91,32 +89,30 @@ class TestDefSiteModel extends BaseModel {
|
|||||||
->where('testdefcal.EndDate IS NULL')
|
->where('testdefcal.EndDate IS NULL')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
// Load test mappings
|
|
||||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
} elseif ($typeCode === 'GROUP') {
|
} elseif ($typeCode === 'GROUP') {
|
||||||
// Load group members with test details
|
|
||||||
$row['testdefgrp'] = $db->table('testdefgrp')
|
$row['testdefgrp'] = $db->table('testdefgrp')
|
||||||
->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType, vs.VValue as MemberTypeCode')
|
->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType')
|
||||||
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
|
->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
|
||||||
->join('valueset vs', 'vs.VID=t.TestType', 'left')
|
|
||||||
->where('testdefgrp.TestSiteID', $TestSiteID)
|
->where('testdefgrp.TestSiteID', $TestSiteID)
|
||||||
->where('testdefgrp.EndDate IS NULL')
|
->where('testdefgrp.EndDate IS NULL')
|
||||||
->orderBy('testdefgrp.TestGrpID', 'ASC')
|
->orderBy('testdefgrp.TestGrpID', 'ASC')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
// Load test mappings
|
$row['testdefgrp'] = ValueSet::transformLabels($row['testdefgrp'], [
|
||||||
|
'TestType' => 'test_type',
|
||||||
|
]);
|
||||||
|
|
||||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
} elseif ($typeCode === 'TITLE') {
|
} elseif ($typeCode === 'TITLE') {
|
||||||
// Load test mappings only for TITLE type
|
|
||||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||||
|
|
||||||
} elseif (in_array($typeCode, ['TEST', 'PARAM'])) {
|
} elseif (in_array($typeCode, ['TEST', 'PARAM'])) {
|
||||||
// TEST or PARAM - load technical details with joined tables
|
|
||||||
$row['testdeftech'] = $db->table('testdeftech')
|
$row['testdeftech'] = $db->table('testdeftech')
|
||||||
->select('testdeftech.*, d.DisciplineName, dept.DepartmentName')
|
->select('testdeftech.*, d.DisciplineName, dept.DepartmentName')
|
||||||
->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left')
|
->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left')
|
||||||
@ -125,7 +121,6 @@ class TestDefSiteModel extends BaseModel {
|
|||||||
->where('testdeftech.EndDate IS NULL')
|
->where('testdeftech.EndDate IS NULL')
|
||||||
->get()->getResultArray();
|
->get()->getResultArray();
|
||||||
|
|
||||||
// Load test mappings
|
|
||||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,30 +148,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">ZIP Code</span>
|
<span class="label-text font-medium">ZIP Code</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="input"
|
class="input"
|
||||||
x-model="form.ZIP"
|
x-model="form.ZIP"
|
||||||
placeholder="12345"
|
placeholder="12345"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Country</span>
|
||||||
|
</label>
|
||||||
|
<select class="select" x-model="form.Country">
|
||||||
|
<option value="">Select Country</option>
|
||||||
|
<template x-for="opt in countryOptions" :key="opt.key">
|
||||||
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text font-medium">Country</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input"
|
|
||||||
x-model="form.Country"
|
|
||||||
placeholder="Indonesia"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -151,6 +151,7 @@ function accounts() {
|
|||||||
loading: false,
|
loading: false,
|
||||||
list: [],
|
list: [],
|
||||||
keyword: "",
|
keyword: "",
|
||||||
|
countryOptions: [],
|
||||||
|
|
||||||
// Form Modal
|
// Form Modal
|
||||||
showModal: false,
|
showModal: false,
|
||||||
@ -179,6 +180,21 @@ function accounts() {
|
|||||||
// Lifecycle
|
// Lifecycle
|
||||||
async init() {
|
async init() {
|
||||||
await this.fetchList();
|
await this.fetchList();
|
||||||
|
await this.fetchCountryOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch country options
|
||||||
|
async fetchCountryOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/account_Country`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.countryOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch country options:', err);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetch account list
|
// Fetch account list
|
||||||
|
|||||||
@ -92,6 +92,31 @@
|
|||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Site Type</span>
|
||||||
|
</label>
|
||||||
|
<select class="select" x-model="form.SiteTypeID">
|
||||||
|
<option value="">Select Type</option>
|
||||||
|
<template x-for="opt in siteTypeOptions" :key="opt.key">
|
||||||
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Site Class</span>
|
||||||
|
</label>
|
||||||
|
<select class="select" x-model="form.SiteClassID">
|
||||||
|
<option value="">Select Class</option>
|
||||||
|
<template x-for="opt in siteClassOptions" :key="opt.key">
|
||||||
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">ME (Medical Examiner?)</span>
|
<span class="label-text font-medium">ME (Medical Examiner?)</span>
|
||||||
|
|||||||
@ -57,7 +57,8 @@
|
|||||||
<th>Site Name</th>
|
<th>Site Name</th>
|
||||||
<th>Code</th>
|
<th>Code</th>
|
||||||
<th>Account</th>
|
<th>Account</th>
|
||||||
<th>Parent Site</th>
|
<th>Type</th>
|
||||||
|
<th>Class</th>
|
||||||
<th class="text-center">Actions</th>
|
<th class="text-center">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -88,7 +89,8 @@
|
|||||||
</td>
|
</td>
|
||||||
<td x-text="site.SiteCode || '-'"></td>
|
<td x-text="site.SiteCode || '-'"></td>
|
||||||
<td x-text="site.AccountName || '-'"></td>
|
<td x-text="site.AccountName || '-'"></td>
|
||||||
<td x-text="site.ParentName || '-'"></td>
|
<td x-text="site.SiteTypeText || site.SiteTypeID || '-'"></td>
|
||||||
|
<td x-text="site.SiteClassText || site.SiteClassID || '-'"></td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="flex items-center justify-center gap-1">
|
<div class="flex items-center justify-center gap-1">
|
||||||
<button class="btn btn-ghost btn-sm btn-square" @click="editSite(site.SiteID)" title="Edit">
|
<button class="btn btn-ghost btn-sm btn-square" @click="editSite(site.SiteID)" title="Edit">
|
||||||
@ -172,10 +174,44 @@ function sites() {
|
|||||||
deleteTarget: null,
|
deleteTarget: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
|
|
||||||
|
// Lookup Options
|
||||||
|
siteTypeOptions: [],
|
||||||
|
siteClassOptions: [],
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
async init() {
|
async init() {
|
||||||
await this.fetchList();
|
await this.fetchList();
|
||||||
await this.fetchAccounts();
|
await this.fetchAccounts();
|
||||||
|
await this.fetchSiteTypeOptions();
|
||||||
|
await this.fetchSiteClassOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch site type options
|
||||||
|
async fetchSiteTypeOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/site_type`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.siteTypeOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch site type options:', err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch site class options
|
||||||
|
async fetchSiteClassOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/site_class`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.siteClassOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch site class options:', err);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetch site list
|
// Fetch site list
|
||||||
|
|||||||
@ -87,8 +87,9 @@
|
|||||||
</label>
|
</label>
|
||||||
<select class="select" x-model="form.Type">
|
<select class="select" x-model="form.Type">
|
||||||
<option value="">Select Type</option>
|
<option value="">Select Type</option>
|
||||||
<option value="1">Manual</option>
|
<template x-for="opt in typeOptions" :key="opt.key">
|
||||||
<option value="2">Automated</option>
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -96,8 +97,9 @@
|
|||||||
<span class="label-text font-medium">Status</span>
|
<span class="label-text font-medium">Status</span>
|
||||||
</label>
|
</label>
|
||||||
<select class="select" x-model="form.Enable">
|
<select class="select" x-model="form.Enable">
|
||||||
<option value="1">Enabled</option>
|
<template x-for="opt in enableOptions" :key="opt.key">
|
||||||
<option value="0">Disabled</option>
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -89,7 +89,7 @@
|
|||||||
<td class="font-mono text-sm" x-text="ws.WorkstationCode || '-'"></td>
|
<td class="font-mono text-sm" x-text="ws.WorkstationCode || '-'"></td>
|
||||||
<td x-text="ws.DepartmentName || '-'"></td>
|
<td x-text="ws.DepartmentName || '-'"></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-sm" :class="ws.Enable == 1 ? 'badge-success' : 'badge-ghost'" x-text="ws.Enable == 1 ? 'Active' : 'Disabled'"></span>
|
<span class="badge badge-sm" :class="ws.EnableText === 'Enabled' || ws.Enable === '1' ? 'badge-success' : 'badge-ghost'" x-text="ws.EnableText || (ws.Enable == 1 ? 'Enabled' : 'Disabled')"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="flex items-center justify-center gap-1">
|
<div class="flex items-center justify-center gap-1">
|
||||||
@ -161,7 +161,7 @@ function workstations() {
|
|||||||
WorkstationName: "",
|
WorkstationName: "",
|
||||||
DepartmentID: "",
|
DepartmentID: "",
|
||||||
Type: "",
|
Type: "",
|
||||||
Enable: 1,
|
Enable: "1",
|
||||||
LinkTo: ""
|
LinkTo: ""
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -170,10 +170,48 @@ function workstations() {
|
|||||||
deleteTarget: null,
|
deleteTarget: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
|
|
||||||
|
// Lookup Options
|
||||||
|
typeOptions: [],
|
||||||
|
enableOptions: [],
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
async init() {
|
async init() {
|
||||||
await this.fetchList();
|
await this.fetchList();
|
||||||
await this.fetchDepartments();
|
await this.fetchDepartments();
|
||||||
|
await this.fetchTypeOptions();
|
||||||
|
await this.fetchEnableOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch workstation type options
|
||||||
|
async fetchTypeOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/ws_type`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.typeOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch type options:', err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch enable options
|
||||||
|
async fetchEnableOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/enable_disable`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.enableOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch enable options:', err);
|
||||||
|
this.enableOptions = [
|
||||||
|
{ key: '1', value: 'Enabled' },
|
||||||
|
{ key: '0', value: 'Disabled' }
|
||||||
|
];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetch workstation list
|
// Fetch workstation list
|
||||||
@ -220,7 +258,7 @@ function workstations() {
|
|||||||
WorkstationName: "",
|
WorkstationName: "",
|
||||||
DepartmentID: "",
|
DepartmentID: "",
|
||||||
Type: "",
|
Type: "",
|
||||||
Enable: 1,
|
Enable: "1",
|
||||||
LinkTo: ""
|
LinkTo: ""
|
||||||
};
|
};
|
||||||
this.errors = {};
|
this.errors = {};
|
||||||
|
|||||||
@ -71,12 +71,12 @@
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Cap Color</span>
|
<span class="label-text font-medium">Cap Color</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<select class="select" x-model="form.Color">
|
||||||
type="text"
|
<option value="">Select Color</option>
|
||||||
class="input"
|
<template x-for="opt in colorOptions" :key="opt.key">
|
||||||
x-model="form.Color"
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
placeholder="e.g. Gold, Red, Lavender"
|
</template>
|
||||||
/>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -96,23 +96,23 @@
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Additive</span>
|
<span class="label-text font-medium">Additive</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<select class="select" x-model="form.Additive">
|
||||||
type="text"
|
<option value="">Select Additive</option>
|
||||||
class="input"
|
<template x-for="opt in additiveOptions" :key="opt.key">
|
||||||
x-model="form.Additive"
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
placeholder="SST / EDTA / Heparin"
|
</template>
|
||||||
/>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Class</span>
|
<span class="label-text font-medium">Class</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<select class="select" x-model="form.ConClass">
|
||||||
type="text"
|
<option value="">Select Class</option>
|
||||||
class="input"
|
<template x-for="opt in classOptions" :key="opt.key">
|
||||||
x-model="form.ConClass"
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
placeholder="Tube / Swab"
|
</template>
|
||||||
/>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -90,10 +90,10 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-3 h-3 rounded-full border border-slate-300" :style="`background-color: ${con.Color || 'transparent'}`"></div>
|
<div class="w-3 h-3 rounded-full border border-slate-300" :style="`background-color: ${con.Color || 'transparent'}`"></div>
|
||||||
<span x-text="con.ColorTxt || con.Color || '-'"></span>
|
<span x-text="con.ColorText || con.Color || '-'"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td x-text="con.AdditiveTxt || con.Additive || '-'"></td>
|
<td x-text="con.AdditiveText || con.Additive || '-'"></td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="flex items-center justify-center gap-1">
|
<div class="flex items-center justify-center gap-1">
|
||||||
<button class="btn btn-ghost btn-sm btn-square" @click="editContainer(con.ConDefID)" title="Edit">
|
<button class="btn btn-ghost btn-sm btn-square" @click="editContainer(con.ConDefID)" title="Edit">
|
||||||
@ -174,10 +174,60 @@ function containers() {
|
|||||||
deleteTarget: null,
|
deleteTarget: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
|
|
||||||
|
// Lookup Options
|
||||||
|
colorOptions: [],
|
||||||
|
additiveOptions: [],
|
||||||
|
classOptions: [],
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
async init() {
|
async init() {
|
||||||
await this.fetchList();
|
await this.fetchList();
|
||||||
await this.fetchSites();
|
await this.fetchSites();
|
||||||
|
await this.fetchColorOptions();
|
||||||
|
await this.fetchAdditiveOptions();
|
||||||
|
await this.fetchClassOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch color options
|
||||||
|
async fetchColorOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/container_cap_color`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.colorOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch color options:', err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch additive options
|
||||||
|
async fetchAdditiveOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/additive`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.additiveOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch additive options:', err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch class options
|
||||||
|
async fetchClassOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/container_class`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.classOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch class options:', err);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetch container list
|
// Fetch container list
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
@keyup.enter="fetchList()" />
|
@keyup.enter="fetchList()" />
|
||||||
<select class="select w-40" x-model="filterType" @change="fetchList()">
|
<select class="select w-40" x-model="filterType" @change="fetchList()">
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
<template x-for="(type, index) in (testTypes || [])" :key="(type?.VID ?? index)">
|
<template x-for="(type, index) in (testTypes || [])" :key="(type?.key ?? index)">
|
||||||
<option :value="type?.VID" x-text="(type?.VValue || '') + ' - ' + (type?.VDesc || '')"></option>
|
<option :value="type?.key" x-text="(type?.value || '')"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn-primary" @click="fetchList()">
|
<button class="btn btn-primary" @click="fetchList()">
|
||||||
@ -440,7 +440,7 @@
|
|||||||
// Fetch test types from valueset
|
// Fetch test types from valueset
|
||||||
async fetchTestTypes() {
|
async fetchTestTypes() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${BASEURL}api/valuesetdef/27`, {
|
const res = await fetch(`${BASEURL}api/valueset/test_type`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error("HTTP error");
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
@ -448,13 +448,12 @@
|
|||||||
this.testTypes = data.data || [];
|
this.testTypes = data.data || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch test types:', err);
|
console.error('Failed to fetch test types:', err);
|
||||||
// Fallback to hardcoded types
|
|
||||||
this.testTypes = [
|
this.testTypes = [
|
||||||
{ VID: 1, VValue: 'TEST', VDesc: 'Test' },
|
{ key: 'TEST', value: 'Test' },
|
||||||
{ VID: 2, VValue: 'PARAM', VDesc: 'Parameter' },
|
{ key: 'PARAM', value: 'Parameter' },
|
||||||
{ VID: 3, VValue: 'CALC', VDesc: 'Calculated' },
|
{ key: 'CALC', value: 'Calculated Test' },
|
||||||
{ VID: 4, VValue: 'GROUP', VDesc: 'Group' },
|
{ key: 'GROUP', value: 'Group Test' },
|
||||||
{ VID: 5, VValue: 'TITLE', VDesc: 'Title' }
|
{ key: 'TITLE', value: 'Title' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -477,8 +476,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Get type display name
|
// Get type display name
|
||||||
getTypeName(vid) {
|
getTypeName(value) {
|
||||||
const code = this.getTypeCode(vid);
|
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'TEST': 'Test',
|
'TEST': 'Test',
|
||||||
'PARAM': 'Parameter',
|
'PARAM': 'Parameter',
|
||||||
@ -486,7 +484,12 @@
|
|||||||
'GROUP': 'Group',
|
'GROUP': 'Group',
|
||||||
'TITLE': 'Title'
|
'TITLE': 'Title'
|
||||||
};
|
};
|
||||||
return typeMap[code] || 'Test';
|
return typeMap[value] || value || 'Test';
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get type code from value
|
||||||
|
getTypeCode(value) {
|
||||||
|
return value || '';
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetch test list
|
// Fetch test list
|
||||||
@ -584,23 +587,11 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.data) {
|
if (data.data) {
|
||||||
const testData = data.data;
|
const testData = data.data;
|
||||||
// Map TypeCode to TestType (VID) for proper dialog display
|
|
||||||
const typeCode = testData.TypeCode || '';
|
|
||||||
let testTypeVid = testData.TestType || null;
|
|
||||||
|
|
||||||
// Convert TypeCode string to VID if needed
|
|
||||||
if (!testTypeVid && typeCode) {
|
|
||||||
const typeMap = { 'TEST': 1, 'PARAM': 2, 'CALC': 3, 'GROUP': 4, 'TITLE': 5 };
|
|
||||||
testTypeVid = typeMap[typeCode] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.form = {
|
this.form = {
|
||||||
...this.form,
|
...this.form,
|
||||||
...testData,
|
...testData,
|
||||||
TestType: testTypeVid,
|
TestType: testData.TestType || '',
|
||||||
// Store TypeCode directly for dialog display
|
TypeCode: testData.TestType || '',
|
||||||
TypeCode: typeCode,
|
|
||||||
// Preserve group members if editing group
|
|
||||||
groupMembers: testData.testdefgrp || []
|
groupMembers: testData.testdefgrp || []
|
||||||
};
|
};
|
||||||
this.showModal = true;
|
this.showModal = true;
|
||||||
@ -699,7 +690,6 @@
|
|||||||
const method = this.isEditing ? 'PATCH' : 'POST';
|
const method = this.isEditing ? 'PATCH' : 'POST';
|
||||||
const payload = { ...this.form };
|
const payload = { ...this.form };
|
||||||
|
|
||||||
// Handle group members for GROUP type
|
|
||||||
if (this.getTypeCode(this.form.TestType) === 'GROUP' && this.form.groupMembers?.length > 0) {
|
if (this.getTypeCode(this.form.TestType) === 'GROUP' && this.form.groupMembers?.length > 0) {
|
||||||
payload.groupMembers = this.form.groupMembers;
|
payload.groupMembers = this.form.groupMembers;
|
||||||
}
|
}
|
||||||
@ -762,18 +752,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get type code from VID (with fallback and null safety)
|
|
||||||
getTypeCode(vid) {
|
|
||||||
if (!vid) return '';
|
|
||||||
// First try to find in loaded testTypes
|
|
||||||
if (this.testTypes && Array.isArray(this.testTypes) && this.testTypes.length > 0) {
|
|
||||||
const type = this.testTypes.find(t => t && t.VID === vid);
|
|
||||||
if (type && type.VValue) return type.VValue;
|
|
||||||
}
|
|
||||||
// Fallback to hardcoded mapping
|
|
||||||
const typeMap = { 1: 'TEST', 2: 'PARAM', 3: 'CALC', 4: 'GROUP', 5: 'TITLE' };
|
|
||||||
return typeMap[vid] || '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -95,11 +95,12 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Gender</span>
|
<span class="label-text font-medium">Sex</span>
|
||||||
</label>
|
</label>
|
||||||
<select class="select" x-model="form.Gender">
|
<select class="select" x-model="form.Sex">
|
||||||
<option value="1">Male</option>
|
<template x-for="opt in sexOptions" :key="opt.key">
|
||||||
<option value="2">Female</option>
|
<option :value="opt.key" x-text="opt.value"></option>
|
||||||
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -92,7 +92,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Patient ID</th>
|
<th>Patient ID</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Gender</th>
|
<th>Sex</th>
|
||||||
<th>Birth Date</th>
|
<th>Birth Date</th>
|
||||||
<th>Phone</th>
|
<th>Phone</th>
|
||||||
<th class="text-center">Actions</th>
|
<th class="text-center">Actions</th>
|
||||||
@ -134,8 +134,8 @@
|
|||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
class="badge badge-sm"
|
class="badge badge-sm"
|
||||||
:class="patient.Gender == 1 ? 'badge-info' : 'badge-secondary'"
|
:class="patient.Sex === 'M' ? 'badge-info' : patient.Sex === 'F' ? 'badge-secondary' : 'badge-ghost'"
|
||||||
x-text="patient.Gender == 1 ? 'Male' : patient.Gender == 2 ? 'Female' : '-'"
|
x-text="patient.SexText || patient.Sex || '-'"
|
||||||
></span>
|
></span>
|
||||||
</td>
|
</td>
|
||||||
<td x-text="formatDate(patient.Birthdate)"></td>
|
<td x-text="formatDate(patient.Birthdate)"></td>
|
||||||
@ -227,7 +227,7 @@ function patients() {
|
|||||||
NameFirst: "",
|
NameFirst: "",
|
||||||
NameMiddle: "",
|
NameMiddle: "",
|
||||||
NameLast: "",
|
NameLast: "",
|
||||||
Gender: 1,
|
Sex: "M",
|
||||||
Birthdate: "",
|
Birthdate: "",
|
||||||
MobilePhone: "",
|
MobilePhone: "",
|
||||||
EmailAddress1: "",
|
EmailAddress1: "",
|
||||||
@ -242,9 +242,31 @@ function patients() {
|
|||||||
deleteTarget: null,
|
deleteTarget: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
|
|
||||||
|
// Lookup Options
|
||||||
|
sexOptions: [],
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
async init() {
|
async init() {
|
||||||
await this.fetchList();
|
await this.fetchList();
|
||||||
|
await this.fetchSexOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch sex options from valueset
|
||||||
|
async fetchSexOptions() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASEURL}api/valueset/gender`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("HTTP error");
|
||||||
|
const data = await res.json();
|
||||||
|
this.sexOptions = data.data || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch sex options:', err);
|
||||||
|
this.sexOptions = [
|
||||||
|
{ key: 'M', value: 'Male' },
|
||||||
|
{ key: 'F', value: 'Female' }
|
||||||
|
];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetch patient list
|
// Fetch patient list
|
||||||
@ -299,7 +321,7 @@ function patients() {
|
|||||||
NameFirst: "",
|
NameFirst: "",
|
||||||
NameMiddle: "",
|
NameMiddle: "",
|
||||||
NameLast: "",
|
NameLast: "",
|
||||||
Gender: 1,
|
Sex: "M",
|
||||||
Birthdate: "",
|
Birthdate: "",
|
||||||
MobilePhone: "",
|
MobilePhone: "",
|
||||||
EmailAddress1: "",
|
EmailAddress1: "",
|
||||||
|
|||||||
645
docs/MIGRATION_VALUESET_VID_TO_VVALUE.md
Normal file
645
docs/MIGRATION_VALUESET_VID_TO_VVALUE.md
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
# Migration Plan: Valueset VID → VValue
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Transition from using database `valueset` table (VID as primary key) to the new `App\Libraries\ValueSet` library (VValue as key). This eliminates database joins for lookup values and uses JSON-based static lookup files.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
- Database `valueset` table with columns: `VID` (PK, INT), `VValue` (VARCHAR), `VDesc` (VARCHAR)
|
||||||
|
- 30+ places using `->join('valueset', 'valueset.VID = ...')`
|
||||||
|
- Selects use `valueset.VValue` and `valueset.VDesc` for display text
|
||||||
|
|
||||||
|
## Target State
|
||||||
|
|
||||||
|
- Use `App\Libraries\ValueSet` library with `getLabel(lookupName, key)` method
|
||||||
|
- Lookup names use table-prefixed PascalCase (e.g., `patient_Sex`, `test_TestType`, `container_ContainerCapColor`)
|
||||||
|
- All fields store `VValue` codes directly (e.g., '1', '2', 'M', 'F', 'TEST')
|
||||||
|
- Remove all `valueset` table joins from queries
|
||||||
|
- Keep raw field values for codes; use `ValueSet::getLabel()` for display text
|
||||||
|
- JSON files in `app/Libraries/Data/valuesets/` are already populated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: JSON Files Rename
|
||||||
|
|
||||||
|
Rename all JSON files in `app/Libraries/Data/valuesets/` to use table-prefixed PascalCase format:
|
||||||
|
|
||||||
|
| Old Name | New Name | Source Table | Field |
|
||||||
|
|----------|----------|--------------|-------|
|
||||||
|
| `gender.json` | `patient_Sex.json` | patient | Gender |
|
||||||
|
| `country.json` | `patient_Country.json` | patient | Country |
|
||||||
|
| `race.json` | `patient_Race.json` | patient | Race |
|
||||||
|
| `religion.json` | `patient_Religion.json` | patient | Religion |
|
||||||
|
| `ethnic.json` | `patient_Ethnic.json` | patient | Ethnic |
|
||||||
|
| `marital_status.json` | `patient_MaritalStatus.json` | patient | MaritalStatus |
|
||||||
|
| `death_indicator.json` | `patient_DeathIndicator.json` | patient | DeathIndicator |
|
||||||
|
| `test_type.json` | `test_TestType.json` | testdefsite | TestType |
|
||||||
|
| `container_cap_color.json` | `container_ContainerCapColor.json` | containerdef | Color |
|
||||||
|
| `container_class.json` | `container_ContainerClass.json` | containerdef | ConClass |
|
||||||
|
| `additive.json` | `container_Additive.json` | containerdef | Additive |
|
||||||
|
| `location_type.json` | `location_LocationType.json` | location | LocType |
|
||||||
|
| `ws_type.json` | `organization_WorkstationType.json` | workstation | Type |
|
||||||
|
| `enable_disable.json` | `organization_EnableDisable.json` | workstation | Enable |
|
||||||
|
| `site_type.json` | `organization_SiteType.json` | site | SiteTypeID |
|
||||||
|
| `site_class.json` | `organization_SiteClass.json` | site | SiteClassID |
|
||||||
|
| `numeric_ref_type.json` | `ref_NumericRefType.json` | refnum | NumRefType |
|
||||||
|
| `range_type.json` | `ref_RangeType.json` | refnum | RangeType |
|
||||||
|
| `text_ref_type.json` | `ref_TextRefType.json` | reftxt | TxtRefType |
|
||||||
|
| `reference_type.json` | `test_ReferenceType.json` | testdeftech | RefType |
|
||||||
|
| `math_sign.json` | `ref_MathSign.json` | refnum | LowSign, HighSign |
|
||||||
|
| `country.json` | `account_Country.json` | account | Country |
|
||||||
|
| ... | ... | ... | ... |
|
||||||
|
|
||||||
|
All lookup names use `{table}_{Field}` format for clarity and namespace isolation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Database Schema Migration
|
||||||
|
|
||||||
|
### Files to DELETE
|
||||||
|
|
||||||
|
| File | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `app\Database\Seeds\ValueSetSeeder.php` | DELETE |
|
||||||
|
| `app\Database\Seeds\ValueSetCountrySeeder.php` | DELETE |
|
||||||
|
| `app\Database\Seeds\MinimalMasterDataSeeder.php` | DELETE |
|
||||||
|
| `app\Database\Seeds\PatientSeeder.php` | DELETE |
|
||||||
|
| `app\Database\Migrations\2025-09-15-130122_ValueSet.php` | DELETE |
|
||||||
|
|
||||||
|
### Migration: Modify Columns INT → VARCHAR(10)
|
||||||
|
|
||||||
|
**File:** `app\Database\Migrations\2026-01-12-000001_ValuesetVidToVvalue.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class ValuesetVidToVvalue extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
// patient table
|
||||||
|
$this->forge->modifyColumn('patient', [
|
||||||
|
'Gender' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Country' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Race' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Religion' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Ethnic' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'MaritalStatus' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'DeathIndicator' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// testdefsite table
|
||||||
|
$this->forge->modifyColumn('testdefsite', [
|
||||||
|
'TestType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => false],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// containerdef table
|
||||||
|
$this->forge->modifyColumn('containerdef', [
|
||||||
|
'Additive' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'ConClass' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Color' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// location table
|
||||||
|
$this->forge->modifyColumn('location', [
|
||||||
|
'LocType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// workstation table
|
||||||
|
$this->forge->modifyColumn('workstation', [
|
||||||
|
'Type' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'Enable' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// site table
|
||||||
|
$this->forge->modifyColumn('site', [
|
||||||
|
'SiteTypeID' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'SiteClassID' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// account table
|
||||||
|
$this->forge->modifyColumn('account', [
|
||||||
|
'Country' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// refnum table
|
||||||
|
$this->forge->modifyColumn('refnum', [
|
||||||
|
'Sex' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'NumRefType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'RangeType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'LowSign' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'HighSign' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// reftxt table
|
||||||
|
$this->forge->modifyColumn('reftxt', [
|
||||||
|
'Sex' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
'TxtRefType' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// orderstatus table
|
||||||
|
$this->forge->modifyColumn('orderstatus', [
|
||||||
|
'OrderStatus' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => false],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
// Revert to INT
|
||||||
|
$this->forge->modifyColumn('patient', [
|
||||||
|
'Gender' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Country' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Race' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Religion' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Ethnic' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'MaritalStatus' => ['type' => 'TINYINT', 'null' => true],
|
||||||
|
'DeathIndicator' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('testdefsite', [
|
||||||
|
'TestType' => ['type' => 'INT', 'null' => false],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('containerdef', [
|
||||||
|
'Additive' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'ConClass' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'Color' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('location', [
|
||||||
|
'LocType' => ['type' => 'INT', 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('workstation', [
|
||||||
|
'Type' => ['type' => 'TINYINT', 'null' => true],
|
||||||
|
'Enable' => ['type' => 'INT', 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('site', [
|
||||||
|
'SiteTypeID' => ['type' => 'INT', 'null' => true],
|
||||||
|
'SiteClassID' => ['type' => 'INT', 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('account', [
|
||||||
|
'Country' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('refnum', [
|
||||||
|
'Sex' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'NumRefType' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'RangeType' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'LowSign' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'HighSign' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('reftxt', [
|
||||||
|
'Sex' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
'TxtRefType' => ['type' => 'INT', 'constraint' => 11, 'null' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->modifyColumn('orderstatus', [
|
||||||
|
'OrderStatus' => ['type' => 'INT', 'null' => false],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** No data migration needed - dummy data will be lost. This is acceptable for development/testing environments.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Library & Model Updates
|
||||||
|
|
||||||
|
### ValueSet Library - Update to read from JSON files
|
||||||
|
|
||||||
|
**File:** `app/Libraries/ValueSet.php`
|
||||||
|
|
||||||
|
Ensure the library reads from JSON files in `app/Libraries/Data/valuesets/`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Libraries;
|
||||||
|
|
||||||
|
class ValueSet
|
||||||
|
{
|
||||||
|
private static $cache = [];
|
||||||
|
|
||||||
|
private static function loadFile(string $name): array
|
||||||
|
{
|
||||||
|
if (!isset(self::$cache[$name])) {
|
||||||
|
$path = APPPATH . 'Libraries/Data/valuesets/' . $name . '.json';
|
||||||
|
if (file_exists($path)) {
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
self::$cache[$name] = json_decode($content, true)['values'] ?? [];
|
||||||
|
} else {
|
||||||
|
self::$cache[$name] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self::$cache[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLabel(string $lookupName, string $key): ?string
|
||||||
|
{
|
||||||
|
$values = self::loadFile($lookupName);
|
||||||
|
foreach ($values as $item) {
|
||||||
|
if (($item['key'] ?? $item['value'] ?? null) === $key) {
|
||||||
|
return $item['value'] ?? $item['label'] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getOptions(string $lookupName): array
|
||||||
|
{
|
||||||
|
$values = self::loadFile($lookupName);
|
||||||
|
return array_map(function ($item) {
|
||||||
|
return [
|
||||||
|
'key' => $item['key'] ?? '',
|
||||||
|
'value' => $item['value'] ?? $item['label'] ?? '',
|
||||||
|
];
|
||||||
|
}, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function transformLabels(array $data, array $fieldMappings): array
|
||||||
|
{
|
||||||
|
foreach ($data as &$row) {
|
||||||
|
foreach ($fieldMappings as $field => $lookupName) {
|
||||||
|
if (isset($row[$field]) && $row[$field] !== null) {
|
||||||
|
$row[$field . 'Text'] = self::getLabel($lookupName, $row[$field]) ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ValueSetModel - Deprecate or Repurpose
|
||||||
|
|
||||||
|
**File:** `app/Models/ValueSet/ValueSetModel.php`
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. **Deprecate entirely** - No longer needed after migration
|
||||||
|
2. **Repurpose for JSON file management** - Read/write to JSON files
|
||||||
|
3. **Keep as-is for backward compatibility** - If database valuesets are still needed
|
||||||
|
|
||||||
|
Recommended: Deprecate and remove references after migration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Model Changes
|
||||||
|
|
||||||
|
### Pattern for Model Updates
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```php
|
||||||
|
$this->select("..., gender.VValue as Gender, gender.VDesc as GenderText")
|
||||||
|
->join('valueset gender', 'gender.VID = patient.Gender', 'left')
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```php
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
|
$this->select("..., patient.Gender");
|
||||||
|
// After fetching:
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'Gender' => 'patient_Sex',
|
||||||
|
'Country' => 'patient_Country',
|
||||||
|
'Race' => 'patient_Race',
|
||||||
|
'Religion' => 'patient_Religion',
|
||||||
|
'Ethnic' => 'patient_Ethnic',
|
||||||
|
'DeathIndicator' => 'patient_DeathIndicator',
|
||||||
|
'MaritalStatus' => 'patient_MaritalStatus',
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Models to Modify (8 files)
|
||||||
|
|
||||||
|
#### 1. `app/Models/Patient/PatientModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Line 27: `$this->join('valueset vs', 'vs.vid = Gender', 'left');`
|
||||||
|
- Lines 52-64: All `*.VID as *VID` aliases
|
||||||
|
- Lines 75-81: All `valueset.*` joins
|
||||||
|
|
||||||
|
**Add transformation in `getPatient()`:**
|
||||||
|
```php
|
||||||
|
$patient = ValueSet::transformLabels([$patient], [
|
||||||
|
'Gender' => 'patient_gender',
|
||||||
|
'Country' => 'patient_country',
|
||||||
|
'Race' => 'patient_race',
|
||||||
|
'Religion' => 'patient_religion',
|
||||||
|
'Ethnic' => 'patient_ethnic',
|
||||||
|
'DeathIndicator' => 'patient_death_indicator',
|
||||||
|
'MaritalStatus' => 'patient_marital_status',
|
||||||
|
])[0];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. `app/Models/Location/LocationModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Lines 18, 30: `->join("valueset v", "v.VID=location.loctype", ...)`
|
||||||
|
|
||||||
|
**Add transformation:**
|
||||||
|
```php
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'LocType' => 'location_LocationType',
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. `app/Models/Test/TestDefSiteModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Lines 42, 75, 103: `->join("valueset", "valueset.VID=...")`
|
||||||
|
|
||||||
|
**Add transformation:**
|
||||||
|
```php
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'TestType' => 'test_TestType',
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. `app/Models/Test/TestDefGrpModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Line 32: `->join('valueset vs', 'vs.VID=t.TestType', 'left')`
|
||||||
|
|
||||||
|
#### 5. `app/Models/Specimen/ContainerDefModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Lines 20-22, 37-39: All 6 `valueset.*` joins
|
||||||
|
|
||||||
|
**Add transformation:**
|
||||||
|
```php
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'Color' => 'container_ContainerCapColor',
|
||||||
|
'ConClass' => 'container_ContainerClass',
|
||||||
|
'Additive' => 'container_Additive',
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. `app/Models/Organization/SiteModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Lines 38-39: `->join('valueset sitetype'...)` and `->join('valueset siteclass'...)`
|
||||||
|
|
||||||
|
**Add transformation:**
|
||||||
|
```php
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'SiteTypeID' => 'organization_SiteType',
|
||||||
|
'SiteClassID' => 'organization_SiteClass',
|
||||||
|
])[0];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. `app/Models/Organization/AccountModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Line 41: `->join('valueset country'...)`
|
||||||
|
|
||||||
|
**Remove from select:**
|
||||||
|
- Line 36: `country.VID as country`
|
||||||
|
|
||||||
|
**Add transformation in controller if needed:**
|
||||||
|
```php
|
||||||
|
$rows = ValueSet::transformLabels($rows, [
|
||||||
|
'Country' => 'account_Country',
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8. `app/Models/Organization/WorkstationModel.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Lines 36-37: `->join('valueset wstype'...)` and `->join('valueset enable'...)`
|
||||||
|
|
||||||
|
**Add transformation:**
|
||||||
|
```php
|
||||||
|
$row = ValueSet::transformLabels([$row], [
|
||||||
|
'Type' => 'organization_WorkstationType',
|
||||||
|
'Enable' => 'organization_EnableDisable',
|
||||||
|
])[0];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Controller Changes
|
||||||
|
|
||||||
|
### `app/Controllers/TestsController.php`
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- Line 69: `->join("valueset", "valueset.VID=testdefsite.TestType", "left")`
|
||||||
|
- Line 111: `->join("valueset", "valueset.VID=testdefsite.TestType", "left")`
|
||||||
|
- Line 140: `->join('valueset vs', 'vs.VID=t.TestType', 'left')`
|
||||||
|
|
||||||
|
**Replace `getVValue()` method:**
|
||||||
|
```php
|
||||||
|
private function getVValue($vsetID, $vid) {
|
||||||
|
// DEPRECATED - Use ValueSet::getLabel() instead
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update references from `getVValue()` to `ValueSet::getLabel()`:**
|
||||||
|
```php
|
||||||
|
// Before:
|
||||||
|
'NumRefTypeVValue' => $this->getVValue(46, $r['NumRefType']),
|
||||||
|
|
||||||
|
// After:
|
||||||
|
'NumRefTypeVValue' => \App\Libraries\ValueSet::getLabel('ref_NumericRefType', $r['NumRefType']),
|
||||||
|
```
|
||||||
|
|
||||||
|
**VSetID to Lookup Name Mapping:**
|
||||||
|
| VSetID | Constant | Lookup Name |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| 44 | `VALUESET_REF_TYPE` | `test_ReferenceType` |
|
||||||
|
| 45 | `VALUESET_RANGE_TYPE` | `ref_RangeType` |
|
||||||
|
| 46 | `VALUESET_NUM_REF_TYPE` | `ref_NumericRefType` |
|
||||||
|
| 47 | `VALUESET_TXT_REF_TYPE` | `ref_TextRefType` |
|
||||||
|
| 3 | `VALUESET_SEX` | `patient_Sex` |
|
||||||
|
| 41 | `VALUESET_MATH_SIGN` | `ref_MathSign` |
|
||||||
|
|
||||||
|
**Update `getValuesetOptions()` to use JSON:**
|
||||||
|
```php
|
||||||
|
private function getValuesetOptions($lookupName)
|
||||||
|
{
|
||||||
|
return \App\Libraries\ValueSet::getOptions($lookupName);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: API Endpoints - Replace with JSON-based endpoints
|
||||||
|
|
||||||
|
### New API Controller: `app/Controllers/ValueSetApiController.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
|
class ValueSetApiController extends \CodeIgniter\Controller
|
||||||
|
{
|
||||||
|
use \CodeIgniter\API\ResponseTrait;
|
||||||
|
|
||||||
|
public function index(string $lookupName)
|
||||||
|
{
|
||||||
|
$data = ValueSet::getOptions($lookupName);
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $data
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
$dir = APPPATH . 'Libraries/Data/valuesets/';
|
||||||
|
$files = glob($dir . '*.json');
|
||||||
|
$result = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$name = basename($file, '.json');
|
||||||
|
$result[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'options' => ValueSet::getOptions($name)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $this->respond([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $result
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Routes: `app/Config/Routes.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
$routes->group('api', function ($routes) {
|
||||||
|
$routes->get('valueset/(:segment)', 'ValueSetApiController::index/$1');
|
||||||
|
$routes->get('valueset', 'ValueSetApiController::all');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: View Updates
|
||||||
|
|
||||||
|
### 1. `app/Views/v2/master/valuesets/valuesets_index.php`
|
||||||
|
|
||||||
|
Repurpose to manage JSON-based valuesets instead of database table.
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
|--------|-------|
|
||||||
|
| `fetch(...api/valueset...)` | `fetch(...api/valueset/lookupName...)` |
|
||||||
|
| Database CRUD operations | File-based CRUD operations |
|
||||||
|
|
||||||
|
### 2. `app/Views/v2/master/valuesets/valueset_nested_crud.php`
|
||||||
|
|
||||||
|
Repurpose for JSON file management.
|
||||||
|
|
||||||
|
### 3. `app/Views/v2/master/valuesets/valueset_dialog.php`
|
||||||
|
|
||||||
|
Update for JSON file format.
|
||||||
|
|
||||||
|
### 4. `app/Views/v2/master/tests/tests_index.php`
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
|--------|-------|
|
||||||
|
| `type?.VID` | `type?.key` |
|
||||||
|
| `type?.VValue` | `type?.value` |
|
||||||
|
| `type?.VDesc` | `type?.label` |
|
||||||
|
| `{ VID: 1, VValue: 'TEST', ... }` | `{ key: 'TEST', value: 'Test', ... }` |
|
||||||
|
| `getTypeName(vid)` | `getTypeName(value)` |
|
||||||
|
| `api/valuesetdef/27` | `api/valueset/test_TestType` |
|
||||||
|
| Hardcoded fallback: `{ VID: 1, VValue: 'TEST', VDesc: 'Test' }` | `{ key: 'TEST', value: 'Test' }` |
|
||||||
|
|
||||||
|
### 5. Additional Views to Update
|
||||||
|
|
||||||
|
| View File | Fields to Update | Lookup Name |
|
||||||
|
|-----------|------------------|-------------|
|
||||||
|
| `app/Views/v2/patients/patients_index.php` | Gender, Country, Race, Religion, Ethnic, MaritalStatus, DeathIndicator | `patient_*` |
|
||||||
|
| `app/Views/v2/master/specimen/containers_index.php` | Color, ConClass, Additive | `container_*` |
|
||||||
|
| `app/Views/v2/master/organization/sites_index.php` | SiteTypeID, SiteClassID | `organization_*` |
|
||||||
|
| `app/Views/v2/master/organization/workstations_index.php` | Type, Enable | `organization_*` |
|
||||||
|
| `app/Views/v2/master/organization/accounts_index.php` | Country | `account_Country` |
|
||||||
|
| `app/Views/v2/master/organization/locations_index.php` | LocType | `location_LocationType` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Test Files Update
|
||||||
|
|
||||||
|
### `tests/feature/ValueSet/ValueSetApiControllerTest.php`
|
||||||
|
|
||||||
|
Update tests to use new JSON-based API endpoints.
|
||||||
|
|
||||||
|
### `tests/_support/v2/MasterTestCase.php`
|
||||||
|
|
||||||
|
Update any valueset-related test data setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
1. **Unit Tests**
|
||||||
|
- Test `ValueSet::getLabel('patient_Sex', '1')` returns 'Female'
|
||||||
|
- Test `ValueSet::getLabel('test_TestType', 'TEST')` returns 'Test'
|
||||||
|
- Test `ValueSet::getOptions('container_ContainerCapColor')` returns correct format
|
||||||
|
- Test `ValueSet::transformLabels()` with table-prefixed field mappings
|
||||||
|
|
||||||
|
2. **Integration Tests**
|
||||||
|
- Patient CRUD (create, read, update, delete)
|
||||||
|
- Test definition CRUD
|
||||||
|
- Location CRUD
|
||||||
|
- Container definition CRUD
|
||||||
|
- Organization (site, account, workstation) CRUD
|
||||||
|
|
||||||
|
3. **Manual Testing**
|
||||||
|
- Verify all dropdowns display correct labels
|
||||||
|
- Verify filtering by valueset fields works
|
||||||
|
- Verify form submissions save correct VValue codes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
1. Run migration `down()` to revert column types
|
||||||
|
2. Restore deleted seeders from git if needed
|
||||||
|
3. Restore deleted migration file from git
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Summary
|
||||||
|
|
||||||
|
| Phase | Action | Files |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| 1 | RENAME | ~50 JSON files in `app/Libraries/Data/valuesets/` |
|
||||||
|
| 2 | DELETE | 5 seeders, 1 migration |
|
||||||
|
| 2 | CREATE | 1 migration (column changes) |
|
||||||
|
| 3 | UPDATE | 1 library (ValueSet.php), ValueSetModel.php deprecation |
|
||||||
|
| 4 | UPDATE | 8 Model files |
|
||||||
|
| 5 | UPDATE | 1 Controller, Routes |
|
||||||
|
| 6 | CREATE | 1 Controller (ValueSetApiController.php) |
|
||||||
|
| 6 | UPDATE | ~6+ View files |
|
||||||
|
| 8 | UPDATE | 2+ Test files |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estimated Effort
|
||||||
|
|
||||||
|
- Phase 1 (JSON Rename): 15 minutes
|
||||||
|
- Phase 2 (Migration): 30 minutes
|
||||||
|
- Phase 3 (Library + Model): 30 minutes
|
||||||
|
- Phase 4 (Models): 1.5 hours
|
||||||
|
- Phase 5 (Controller): 30 minutes
|
||||||
|
- Phase 6 (API + Views): 2 hours
|
||||||
|
- Phase 8 (Tests): 30 minutes
|
||||||
|
- Testing: 1 hour
|
||||||
|
|
||||||
|
**Total Estimated Time: ~7 hours**
|
||||||
23078
docs/prj_clinical laboratory quality management system_3a.md
Normal file
23078
docs/prj_clinical laboratory quality management system_3a.md
Normal file
File diff suppressed because it is too large
Load Diff
100
seeds/MinimalMasterDataSeeder.php
Normal file
100
seeds/MinimalMasterDataSeeder.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Seeder;
|
||||||
|
|
||||||
|
class MinimalMasterDataSeeder extends Seeder {
|
||||||
|
public function run() {
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
|
$orderStatuses = [
|
||||||
|
['VID' => 1, 'VSetID' => 11, 'VValue' => 'ORD', 'VDesc' => 'Ordered', 'VOrder' => 1],
|
||||||
|
['VID' => 2, 'VSetID' => 11, 'VValue' => 'SCH', 'VDesc' => 'Scheduled', 'VOrder' => 2],
|
||||||
|
['VID' => 3, 'VSetID' => 11, 'VValue' => 'ANA', 'VDesc' => 'Analysis', 'VOrder' => 3],
|
||||||
|
['VID' => 4, 'VSetID' => 11, 'VValue' => 'VER', 'VDesc' => 'Verified', 'VOrder' => 4],
|
||||||
|
['VID' => 5, 'VSetID' => 11, 'VValue' => 'REV', 'VDesc' => 'Reviewed', 'VOrder' => 5],
|
||||||
|
['VID' => 6, 'VSetID' => 11, 'VValue' => 'REP', 'VDesc' => 'Reported', 'VOrder' => 6],
|
||||||
|
];
|
||||||
|
|
||||||
|
$priorities = [
|
||||||
|
['VID' => 1, 'VSetID' => 10, 'VValue' => 'S', 'VDesc' => 'Stat', 'VOrder' => 1],
|
||||||
|
['VID' => 2, 'VSetID' => 10, 'VValue' => 'A', 'VDesc' => 'ASAP', 'VOrder' => 2],
|
||||||
|
['VID' => 3, 'VSetID' => 10, 'VValue' => 'R', 'VDesc' => 'Routine', 'VOrder' => 3],
|
||||||
|
['VID' => 4, 'VSetID' => 10, 'VValue' => 'P', 'VDesc' => 'Preop', 'VOrder' => 4],
|
||||||
|
];
|
||||||
|
|
||||||
|
$specimenTypes = [
|
||||||
|
['VID' => 1, 'VSetID' => 29, 'VValue' => 'BLD', 'VDesc' => 'Blood', 'VOrder' => 1],
|
||||||
|
['VID' => 2, 'VSetID' => 29, 'VValue' => 'SER', 'VDesc' => 'Serum', 'VOrder' => 2],
|
||||||
|
['VID' => 3, 'VSetID' => 29, 'VValue' => 'PLAS', 'VDesc' => 'Plasma', 'VOrder' => 3],
|
||||||
|
['VID' => 4, 'VSetID' => 29, 'VValue' => 'UR', 'VDesc' => 'Urine', 'VOrder' => 4],
|
||||||
|
['VID' => 5, 'VSetID' => 29, 'VValue' => 'CSF', 'VDesc' => 'Cerebrospinal Fluid', 'VOrder' => 5],
|
||||||
|
];
|
||||||
|
|
||||||
|
$testTypes = [
|
||||||
|
['VID' => 1, 'VSetID' => 27, 'VValue' => 'TEST', 'VDesc' => 'Test', 'VOrder' => 1],
|
||||||
|
['VID' => 2, 'VSetID' => 27, 'VValue' => 'PARAM', 'VDesc' => 'Parameter', 'VOrder' => 2],
|
||||||
|
['VID' => 3, 'VSetID' => 27, 'VValue' => 'CALC', 'VDesc' => 'Calculated Test', 'VOrder' => 3],
|
||||||
|
['VID' => 4, 'VSetID' => 27, 'VValue' => 'GROUP', 'VDesc' => 'Group Test', 'VOrder' => 4],
|
||||||
|
['VID' => 5, 'VSetID' => 27, 'VValue' => 'TITLE', 'VDesc' => 'Title', 'VOrder' => 5],
|
||||||
|
];
|
||||||
|
|
||||||
|
$genders = [
|
||||||
|
['VID' => 1, 'VSetID' => 3, 'VValue' => '1', 'VDesc' => 'Female', 'VOrder' => 1],
|
||||||
|
['VID' => 2, 'VSetID' => 3, 'VValue' => '2', 'VDesc' => 'Male', 'VOrder' => 2],
|
||||||
|
['VID' => 3, 'VSetID' => 3, 'VValue' => '3', 'VDesc' => 'Unknown', 'VOrder' => 3],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($orderStatuses as $row) {
|
||||||
|
$exists = $db->table('valueset')->where('VSetID', $row['VSetID'])->where('VValue', $row['VValue'])->get()->getRow();
|
||||||
|
if (!$exists) {
|
||||||
|
$db->table('valueset')->insert($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($priorities as $row) {
|
||||||
|
$exists = $db->table('valueset')->where('VSetID', $row['VSetID'])->where('VValue', $row['VValue'])->get()->getRow();
|
||||||
|
if (!$exists) {
|
||||||
|
$db->table('valueset')->insert($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($specimenTypes as $row) {
|
||||||
|
$exists = $db->table('valueset')->where('VSetID', $row['VSetID'])->where('VValue', $row['VValue'])->get()->getRow();
|
||||||
|
if (!$exists) {
|
||||||
|
$db->table('valueset')->insert($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($testTypes as $row) {
|
||||||
|
$exists = $db->table('valueset')->where('VSetID', $row['VSetID'])->where('VValue', $row['VValue'])->get()->getRow();
|
||||||
|
if (!$exists) {
|
||||||
|
$db->table('valueset')->insert($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($genders as $row) {
|
||||||
|
$exists = $db->table('valueset')->where('VSetID', $row['VSetID'])->where('VValue', $row['VValue'])->get()->getRow();
|
||||||
|
if (!$exists) {
|
||||||
|
$db->table('valueset')->insert($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$counterExists = $db->table('counter')->where('CounterName', 'ORDER')->get()->getRow();
|
||||||
|
if (!$counterExists) {
|
||||||
|
$db->table('counter')->insert(['CounterName' => 'ORDER', 'CounterValue' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$siteExists = $db->table('site')->where('SiteCode', '00')->get()->getRow();
|
||||||
|
if (!$siteExists) {
|
||||||
|
$db->table('site')->insert([
|
||||||
|
'SiteCode' => '00',
|
||||||
|
'SiteName' => 'Main Laboratory',
|
||||||
|
'SiteType' => 'PHL',
|
||||||
|
'CreateDate' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Minimal master data seeded successfully.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ class PatientCreateTest extends CIUnitTestCase
|
|||||||
"NameLast"=> "Wijaya",
|
"NameLast"=> "Wijaya",
|
||||||
"Suffix"=> "S.kom",
|
"Suffix"=> "S.kom",
|
||||||
"NameAlias"=> "Bud",
|
"NameAlias"=> "Bud",
|
||||||
"Gender"=> "1",
|
"Sex"=> "1",
|
||||||
];
|
];
|
||||||
$result = $this->withBodyFormat('json')->call('post', 'api/patient', $payload);
|
$result = $this->withBodyFormat('json')->call('post', 'api/patient', $payload);
|
||||||
$result->assertStatus(400);
|
$result->assertStatus(400);
|
||||||
@ -57,7 +57,7 @@ class PatientCreateTest extends CIUnitTestCase
|
|||||||
"NameLast" => $faker->lastName,
|
"NameLast" => $faker->lastName,
|
||||||
"Suffix" => "S.Kom",
|
"Suffix" => "S.Kom",
|
||||||
"NameAlias" => $faker->userName,
|
"NameAlias" => $faker->userName,
|
||||||
"Gender" => $faker->numberBetween(5, 6),
|
"Sex" => $faker->numberBetween(5, 6),
|
||||||
"PlaceOfBirth" => $faker->city,
|
"PlaceOfBirth" => $faker->city,
|
||||||
"Birthdate" => $faker->date('Y-m-d'),
|
"Birthdate" => $faker->date('Y-m-d'),
|
||||||
"ZIP" => $faker->postcode,
|
"ZIP" => $faker->postcode,
|
||||||
@ -118,7 +118,7 @@ class PatientCreateTest extends CIUnitTestCase
|
|||||||
"NameLast" => $faker->lastName,
|
"NameLast" => $faker->lastName,
|
||||||
"Suffix" => "S.Kom",
|
"Suffix" => "S.Kom",
|
||||||
"NameAlias" => $faker->userName,
|
"NameAlias" => $faker->userName,
|
||||||
"Gender" => $faker->numberBetween(5, 6),
|
"Sex" => $faker->numberBetween(5, 6),
|
||||||
"PlaceOfBirth" => $faker->city,
|
"PlaceOfBirth" => $faker->city,
|
||||||
"Birthdate" => $faker->date('Y-m-d'),
|
"Birthdate" => $faker->date('Y-m-d'),
|
||||||
"ZIP" => $faker->postcode,
|
"ZIP" => $faker->postcode,
|
||||||
@ -177,7 +177,7 @@ class PatientCreateTest extends CIUnitTestCase
|
|||||||
"NameLast" => $faker->lastName,
|
"NameLast" => $faker->lastName,
|
||||||
"Suffix" => "S.Kom",
|
"Suffix" => "S.Kom",
|
||||||
"NameAlias" => $faker->userName,
|
"NameAlias" => $faker->userName,
|
||||||
"Gender" => $faker->numberBetween(5, 6),
|
"Sex" => $faker->numberBetween(5, 6),
|
||||||
"PlaceOfBirth" => $faker->city,
|
"PlaceOfBirth" => $faker->city,
|
||||||
"Birthdate" => $faker->date('Y-m-d'),
|
"Birthdate" => $faker->date('Y-m-d'),
|
||||||
"ZIP" => $faker->postcode,
|
"ZIP" => $faker->postcode,
|
||||||
@ -233,7 +233,7 @@ class PatientCreateTest extends CIUnitTestCase
|
|||||||
"NameLast" => $faker->lastName,
|
"NameLast" => $faker->lastName,
|
||||||
"Suffix" => "S.Kom",
|
"Suffix" => "S.Kom",
|
||||||
"NameAlias" => $faker->userName,
|
"NameAlias" => $faker->userName,
|
||||||
"Gender" => $faker->numberBetween(5, 6),
|
"Sex" => $faker->numberBetween(5, 6),
|
||||||
"PlaceOfBirth" => $faker->city,
|
"PlaceOfBirth" => $faker->city,
|
||||||
"Birthdate" => $faker->date('Y-m-d'),
|
"Birthdate" => $faker->date('Y-m-d'),
|
||||||
"ZIP" => $faker->postcode,
|
"ZIP" => $faker->postcode,
|
||||||
@ -293,7 +293,7 @@ class PatientCreateTest extends CIUnitTestCase
|
|||||||
"NameLast" => $faker->lastName,
|
"NameLast" => $faker->lastName,
|
||||||
"Suffix" => "S.Kom",
|
"Suffix" => "S.Kom",
|
||||||
"NameAlias" => $faker->userName,
|
"NameAlias" => $faker->userName,
|
||||||
"Gender" => $faker->numberBetween(5, 6),
|
"Sex" => $faker->numberBetween(5, 6),
|
||||||
"PlaceOfBirth" => $faker->city,
|
"PlaceOfBirth" => $faker->city,
|
||||||
"Birthdate" => $faker->date('Y-m-d'),
|
"Birthdate" => $faker->date('Y-m-d'),
|
||||||
"ZIP" => $faker->postcode,
|
"ZIP" => $faker->postcode,
|
||||||
|
|||||||
@ -223,7 +223,7 @@ class PatientDeleteTest extends CIUnitTestCase
|
|||||||
// "Prefix" => "Mr.",
|
// "Prefix" => "Mr.",
|
||||||
// "NameFirst" => "ToBeDeleted",
|
// "NameFirst" => "ToBeDeleted",
|
||||||
// "NameLast" => "Patient",
|
// "NameLast" => "Patient",
|
||||||
// "Gender" => "5",
|
// "Sex" => "5",
|
||||||
// "Birthdate" => "1990-01-01",
|
// "Birthdate" => "1990-01-01",
|
||||||
// ];
|
// ];
|
||||||
//
|
//
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class PatientUpdateTest extends CIUnitTestCase
|
|||||||
"MobilePhone" => $faker->numerify('08##########'),
|
"MobilePhone" => $faker->numerify('08##########'),
|
||||||
'NameFirst' => $faker->firstName,
|
'NameFirst' => $faker->firstName,
|
||||||
'NameLast' => $faker->lastName,
|
'NameLast' => $faker->lastName,
|
||||||
'Gender' => '1',
|
'Sex' => '1',
|
||||||
'Birthdate' => $faker->date('Y-m-d'),
|
'Birthdate' => $faker->date('Y-m-d'),
|
||||||
'PatIdt' => [ 'IdentifierType' => 'KTP', 'Identifier' => $faker->nik() ],
|
'PatIdt' => [ 'IdentifierType' => 'KTP', 'Identifier' => $faker->nik() ],
|
||||||
'PatCom' => $faker->sentence,
|
'PatCom' => $faker->sentence,
|
||||||
@ -71,7 +71,7 @@ class PatientUpdateTest extends CIUnitTestCase
|
|||||||
'NameFirst' => $faker->firstName,
|
'NameFirst' => $faker->firstName,
|
||||||
'NameMiddle' => $faker->firstName,
|
'NameMiddle' => $faker->firstName,
|
||||||
'NameLast' => $faker->lastName,
|
'NameLast' => $faker->lastName,
|
||||||
'Gender' => '1',
|
'Sex' => '1',
|
||||||
'Birthdate' => $faker->date('Y-m-d'),
|
'Birthdate' => $faker->date('Y-m-d'),
|
||||||
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
||||||
"Phone" => $faker->numerify('08##########'),
|
"Phone" => $faker->numerify('08##########'),
|
||||||
@ -115,7 +115,7 @@ class PatientUpdateTest extends CIUnitTestCase
|
|||||||
'NameFirst' => $faker->firstName,
|
'NameFirst' => $faker->firstName,
|
||||||
'NameMiddle' => $faker->firstName,
|
'NameMiddle' => $faker->firstName,
|
||||||
'NameLast' => $faker->lastName,
|
'NameLast' => $faker->lastName,
|
||||||
'Gender' => '1',
|
'Sex' => '1',
|
||||||
'Birthdate' => $faker->date('Y-m-d'),
|
'Birthdate' => $faker->date('Y-m-d'),
|
||||||
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
||||||
"Phone" => $faker->numerify('08##########'),
|
"Phone" => $faker->numerify('08##########'),
|
||||||
@ -155,7 +155,7 @@ class PatientUpdateTest extends CIUnitTestCase
|
|||||||
'NameFirst' => $faker->firstName,
|
'NameFirst' => $faker->firstName,
|
||||||
'NameMiddle' => $faker->firstName,
|
'NameMiddle' => $faker->firstName,
|
||||||
'NameLast' => $faker->lastName,
|
'NameLast' => $faker->lastName,
|
||||||
'Gender' => '1',
|
'Sex' => '1',
|
||||||
'Birthdate' => $faker->date('Y-m-d'),
|
'Birthdate' => $faker->date('Y-m-d'),
|
||||||
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
||||||
"Phone" => $faker->numerify('08##########'),
|
"Phone" => $faker->numerify('08##########'),
|
||||||
@ -193,7 +193,7 @@ class PatientUpdateTest extends CIUnitTestCase
|
|||||||
'NameFirst' => $faker->firstName,
|
'NameFirst' => $faker->firstName,
|
||||||
'NameMiddle' => $faker->firstName,
|
'NameMiddle' => $faker->firstName,
|
||||||
'NameLast' => $faker->lastName,
|
'NameLast' => $faker->lastName,
|
||||||
'Gender' => '1',
|
'Sex' => '1',
|
||||||
'Birthdate' => $faker->date('Y-m-d'),
|
'Birthdate' => $faker->date('Y-m-d'),
|
||||||
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
||||||
"Phone" => $faker->numerify('08##########'),
|
"Phone" => $faker->numerify('08##########'),
|
||||||
@ -233,7 +233,7 @@ class PatientUpdateTest extends CIUnitTestCase
|
|||||||
'NameFirst' => $faker->firstName,
|
'NameFirst' => $faker->firstName,
|
||||||
'NameMiddle' => $faker->firstName,
|
'NameMiddle' => $faker->firstName,
|
||||||
'NameLast' => $faker->lastName,
|
'NameLast' => $faker->lastName,
|
||||||
'Gender' => '1',
|
'Sex' => '1',
|
||||||
'Birthdate' => $faker->date('Y-m-d'),
|
'Birthdate' => $faker->date('Y-m-d'),
|
||||||
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
'EmailAddress1' => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
|
||||||
"Phone" => $faker->numerify('08##########'),
|
"Phone" => $faker->numerify('08##########'),
|
||||||
|
|||||||
179
tests/feature/ValueSet/ValueSetApiControllerTest.php
Normal file
179
tests/feature/ValueSet/ValueSetApiControllerTest.php
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\ValueSet;
|
||||||
|
|
||||||
|
use CodeIgniter\Test\FeatureTestTrait;
|
||||||
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
|
class ValueSetApiControllerTest extends CIUnitTestCase
|
||||||
|
{
|
||||||
|
use FeatureTestTrait;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
ValueSet::clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexReturnsAllLookups()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset');
|
||||||
|
|
||||||
|
$result->assertStatus(200);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('success', $data['status']);
|
||||||
|
$this->assertArrayHasKey('gender', $data['data']);
|
||||||
|
$this->assertArrayHasKey('specimen_type', $data['data']);
|
||||||
|
$this->assertArrayHasKey('order_priority', $data['data']);
|
||||||
|
$this->assertArrayHasKey('specimen_status', $data['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowByNameReturnsSingleLookup()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset/gender');
|
||||||
|
|
||||||
|
$result->assertStatus(200);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('success', $data['status']);
|
||||||
|
$this->assertIsArray($data['data']);
|
||||||
|
$this->assertNotEmpty($data['data']);
|
||||||
|
$this->assertArrayHasKey('value', $data['data'][0]);
|
||||||
|
$this->assertArrayHasKey('label', $data['data'][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowByNameGenderReturnsCorrectValues()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset/gender');
|
||||||
|
|
||||||
|
$result->assertStatus(200);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$values = array_column($data['data'], 'value');
|
||||||
|
$labels = array_column($data['data'], 'label');
|
||||||
|
|
||||||
|
$this->assertContains('1', $values);
|
||||||
|
$this->assertContains('2', $values);
|
||||||
|
$this->assertContains('Female', $labels);
|
||||||
|
$this->assertContains('Male', $labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowByNameInvalidLookupReturns404()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset/nonexistent_lookup');
|
||||||
|
|
||||||
|
$result->assertStatus(404);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('error', $data['status']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowByNameOrderPriority()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset/order_priority');
|
||||||
|
|
||||||
|
$result->assertStatus(200);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('success', $data['status']);
|
||||||
|
$this->assertIsArray($data['data']);
|
||||||
|
|
||||||
|
$labels = array_column($data['data'], 'label');
|
||||||
|
$this->assertContains('Stat', $labels);
|
||||||
|
$this->assertContains('ASAP', $labels);
|
||||||
|
$this->assertContains('Routine', $labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateReturns403()
|
||||||
|
{
|
||||||
|
$result = $this->call('post', 'api/valueset', [
|
||||||
|
'name' => 'test',
|
||||||
|
'values' => []
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result->assertStatus(403);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('error', $data['status']);
|
||||||
|
$this->assertStringContainsString('disabled', $data['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateReturns403()
|
||||||
|
{
|
||||||
|
$result = $this->call('patch', 'api/valueset', [
|
||||||
|
'name' => 'gender',
|
||||||
|
'values' => []
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result->assertStatus(403);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('error', $data['status']);
|
||||||
|
$this->assertStringContainsString('disabled', $data['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteReturns403()
|
||||||
|
{
|
||||||
|
$result = $this->call('delete', 'api/valueset', [
|
||||||
|
'name' => 'gender'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result->assertStatus(403);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('error', $data['status']);
|
||||||
|
$this->assertStringContainsString('disabled', $data['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexWithParamFiltersResults()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset', ['param' => 'gender']);
|
||||||
|
|
||||||
|
$result->assertStatus(200);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('success', $data['status']);
|
||||||
|
$this->assertIsArray($data['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowByNameSpecimenStatus()
|
||||||
|
{
|
||||||
|
$result = $this->call('get', 'api/valueset/specimen_status');
|
||||||
|
|
||||||
|
$result->assertStatus(200);
|
||||||
|
|
||||||
|
$json = $result->getJSON();
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
$this->assertEquals('success', $data['status']);
|
||||||
|
$this->assertIsArray($data['data']);
|
||||||
|
|
||||||
|
$values = array_column($data['data'], 'value');
|
||||||
|
$labels = array_column($data['data'], 'label');
|
||||||
|
|
||||||
|
$this->assertContains('STC', $values);
|
||||||
|
$this->assertContains('SCtd', $values);
|
||||||
|
$this->assertContains('To be collected', $labels);
|
||||||
|
$this->assertContains('Collected', $labels);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -114,7 +114,7 @@ class PatientModelTest extends CIUnitTestCase
|
|||||||
$this->assertIsArray($allowedFields);
|
$this->assertIsArray($allowedFields);
|
||||||
$this->assertContains('PatientID', $allowedFields);
|
$this->assertContains('PatientID', $allowedFields);
|
||||||
$this->assertContains('NameFirst', $allowedFields);
|
$this->assertContains('NameFirst', $allowedFields);
|
||||||
$this->assertContains('Gender', $allowedFields);
|
$this->assertContains('Sex', $allowedFields);
|
||||||
$this->assertContains('Birthdate', $allowedFields);
|
$this->assertContains('Birthdate', $allowedFields);
|
||||||
$this->assertContains('EmailAddress1', $allowedFields);
|
$this->assertContains('EmailAddress1', $allowedFields);
|
||||||
$this->assertContains('Phone', $allowedFields);
|
$this->assertContains('Phone', $allowedFields);
|
||||||
|
|||||||
110
tests/unit/ValueSet/LookupsBackwardCompatibilityTest.php
Normal file
110
tests/unit/ValueSet/LookupsBackwardCompatibilityTest.php
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Lookups;
|
||||||
|
|
||||||
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
|
use App\Libraries\Lookups;
|
||||||
|
|
||||||
|
class LookupsBackwardCompatibilityTest extends CIUnitTestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
\App\Libraries\ValueSet::clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsGetMethodWorks()
|
||||||
|
{
|
||||||
|
$result = Lookups::get('gender');
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertNotEmpty($result);
|
||||||
|
$this->assertArrayHasKey('value', $result[0]);
|
||||||
|
$this->assertArrayHasKey('label', $result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsGetRawMethodWorks()
|
||||||
|
{
|
||||||
|
$result = Lookups::getRaw('gender');
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertNotEmpty($result);
|
||||||
|
$this->assertArrayHasKey('key', $result[0]);
|
||||||
|
$this->assertArrayHasKey('value', $result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsGetRawGenderContainsExpectedData()
|
||||||
|
{
|
||||||
|
$result = Lookups::getRaw('gender');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$genderMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Female', $genderMap['1']);
|
||||||
|
$this->assertEquals('Male', $genderMap['2']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsGetLabelMethodWorks()
|
||||||
|
{
|
||||||
|
$this->assertEquals('Female', Lookups::getLabel('gender', '1'));
|
||||||
|
$this->assertEquals('Male', Lookups::getLabel('gender', '2'));
|
||||||
|
$this->assertEquals('Stat', Lookups::getLabel('order_priority', 'S'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsGetAllMethodWorks()
|
||||||
|
{
|
||||||
|
$result = Lookups::getAll();
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertArrayHasKey('gender', $result);
|
||||||
|
$this->assertArrayHasKey('specimen_type', $result);
|
||||||
|
$this->assertArrayHasKey('order_priority', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsClearCacheMethodWorks()
|
||||||
|
{
|
||||||
|
Lookups::getAll();
|
||||||
|
$result = Lookups::clearCache();
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsSameAsValueSet()
|
||||||
|
{
|
||||||
|
$lookupResult = Lookups::get('gender');
|
||||||
|
$valueSetResult = \App\Libraries\ValueSet::get('gender');
|
||||||
|
$this->assertEquals($lookupResult, $valueSetResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsBackwardCompatWithSpecimenStatus()
|
||||||
|
{
|
||||||
|
$result = Lookups::getRaw('specimen_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('To be collected', $statusMap['STC']);
|
||||||
|
$this->assertEquals('Collected', $statusMap['SCtd']);
|
||||||
|
$this->assertEquals('In-transport', $statusMap['STran']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsBackwardCompatWithMaritalStatus()
|
||||||
|
{
|
||||||
|
$result = Lookups::getRaw('marital_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Married', $statusMap['M']);
|
||||||
|
$this->assertEquals('Single', $statusMap['S']);
|
||||||
|
$this->assertEquals('Divorced', $statusMap['D']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupsBackwardCompatWithReligion()
|
||||||
|
{
|
||||||
|
$result = Lookups::getRaw('religion');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$religionMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Islam', $religionMap['ISLAM']);
|
||||||
|
$this->assertEquals('Kristen', $religionMap['KRSTN']);
|
||||||
|
$this->assertEquals('Katolik', $religionMap['KTLIK']);
|
||||||
|
}
|
||||||
|
}
|
||||||
380
tests/unit/ValueSet/ValueSetTest.php
Normal file
380
tests/unit/ValueSet/ValueSetTest.php
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\ValueSet;
|
||||||
|
|
||||||
|
use CodeIgniter\Test\CIUnitTestCase;
|
||||||
|
use App\Libraries\ValueSet;
|
||||||
|
|
||||||
|
class ValueSetTest extends CIUnitTestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
ValueSet::clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPatientSexReturnsFormattedArray()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('gender');
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertNotEmpty($result);
|
||||||
|
$this->assertArrayHasKey('value', $result[0]);
|
||||||
|
$this->assertArrayHasKey('label', $result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPatientSexContainsExpectedValues()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('gender');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
$labels = array_column($result, 'label');
|
||||||
|
|
||||||
|
$this->assertContains('1', $values);
|
||||||
|
$this->assertContains('2', $values);
|
||||||
|
$this->assertContains('3', $values);
|
||||||
|
$this->assertContains('Female', $labels);
|
||||||
|
$this->assertContains('Male', $labels);
|
||||||
|
$this->assertContains('Unknown', $labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRawReturnsArrayOfKeyValuePairs()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('gender');
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertNotEmpty($result);
|
||||||
|
$this->assertArrayHasKey('key', $result[0]);
|
||||||
|
$this->assertArrayHasKey('value', $result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRawPatientSexContainsExpectedData()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('gender');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$this->assertContains('1', $keys);
|
||||||
|
$this->assertContains('2', $keys);
|
||||||
|
$this->assertContains('Female', $values);
|
||||||
|
$this->assertContains('Male', $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLabelConvertsCodeToLabel()
|
||||||
|
{
|
||||||
|
$this->assertEquals('Female', ValueSet::getLabel('gender', '1'));
|
||||||
|
$this->assertEquals('Male', ValueSet::getLabel('gender', '2'));
|
||||||
|
$this->assertEquals('Unknown', ValueSet::getLabel('gender', '3'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLabelForOrderPriority()
|
||||||
|
{
|
||||||
|
$this->assertEquals('Stat', ValueSet::getLabel('order_priority', 'S'));
|
||||||
|
$this->assertEquals('ASAP', ValueSet::getLabel('order_priority', 'A'));
|
||||||
|
$this->assertEquals('Routine', ValueSet::getLabel('order_priority', 'R'));
|
||||||
|
$this->assertEquals('Preop', ValueSet::getLabel('order_priority', 'P'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLabelReturnsNullForInvalidKey()
|
||||||
|
{
|
||||||
|
$this->assertNull(ValueSet::getLabel('gender', '99'));
|
||||||
|
$this->assertNull(ValueSet::getLabel('gender', 'invalid'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetLabelReturnsNullForInvalidLookup()
|
||||||
|
{
|
||||||
|
$this->assertNull(ValueSet::getLabel('nonexistent_lookup', '1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetReturnsNullForInvalidLookup()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('nonexistent_lookup');
|
||||||
|
$this->assertNull($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllReturnsMultipleLookups()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getAll();
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertArrayHasKey('gender', $result);
|
||||||
|
$this->assertArrayHasKey('specimen_type', $result);
|
||||||
|
$this->assertArrayHasKey('order_priority', $result);
|
||||||
|
$this->assertArrayHasKey('specimen_status', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllContainsValuesKey()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getAll();
|
||||||
|
$this->assertIsArray($result['gender']['values']);
|
||||||
|
$this->assertNotEmpty($result['gender']['values']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAllContainsMetadata()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getAll();
|
||||||
|
$this->assertArrayHasKey('VSetID', $result['gender']);
|
||||||
|
$this->assertArrayHasKey('VSName', $result['gender']);
|
||||||
|
$this->assertArrayHasKey('VCategory', $result['gender']);
|
||||||
|
$this->assertArrayHasKey('values', $result['gender']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPatientSex()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('gender');
|
||||||
|
$this->assertEquals('1', $result[0]['value']);
|
||||||
|
$this->assertEquals('Female', $result[0]['label']);
|
||||||
|
$this->assertEquals('2', $result[1]['value']);
|
||||||
|
$this->assertEquals('Male', $result[1]['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSpecimenStatus()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('specimen_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$this->assertContains('STC', $keys);
|
||||||
|
$this->assertContains('SCtd', $keys);
|
||||||
|
$this->assertContains('STran', $keys);
|
||||||
|
$this->assertContains('SArrv', $keys);
|
||||||
|
$this->assertContains('SRejc', $keys);
|
||||||
|
$this->assertContains('SRcvd', $keys);
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('To be collected', $statusMap['STC']);
|
||||||
|
$this->assertEquals('Collected', $statusMap['SCtd']);
|
||||||
|
$this->assertEquals('In-transport', $statusMap['STran']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOrderPriority()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('order_priority');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$this->assertContains('S', $keys);
|
||||||
|
$this->assertContains('A', $keys);
|
||||||
|
$this->assertContains('R', $keys);
|
||||||
|
|
||||||
|
$priorityMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Stat', $priorityMap['S']);
|
||||||
|
$this->assertEquals('ASAP', $priorityMap['A']);
|
||||||
|
$this->assertEquals('Routine', $priorityMap['R']);
|
||||||
|
$this->assertEquals('Preop', $priorityMap['P']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSpecimenType()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('specimen_type');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$typeMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Whole blood', $typeMap['BLD']);
|
||||||
|
$this->assertEquals('Serum', $typeMap['SER']);
|
||||||
|
$this->assertEquals('Plasma', $typeMap['PLAS']);
|
||||||
|
$this->assertEquals('Urine', $typeMap['UR']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMaritalStatus()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('marital_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Separated', $statusMap['A']);
|
||||||
|
$this->assertEquals('Divorced', $statusMap['D']);
|
||||||
|
$this->assertEquals('Married', $statusMap['M']);
|
||||||
|
$this->assertEquals('Single', $statusMap['S']);
|
||||||
|
$this->assertEquals('Widowed', $statusMap['W']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPatientReligion()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('religion');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$religionMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Islam', $religionMap['ISLAM']);
|
||||||
|
$this->assertEquals('Kristen', $religionMap['KRSTN']);
|
||||||
|
$this->assertEquals('Katolik', $religionMap['KTLIK']);
|
||||||
|
$this->assertEquals('Hindu', $religionMap['HINDU']);
|
||||||
|
$this->assertEquals('Budha', $religionMap['BUDHA']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetResultType()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('result_type');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$typeMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Numeric', $typeMap['NMRIC']);
|
||||||
|
$this->assertEquals('Range', $typeMap['RANGE']);
|
||||||
|
$this->assertEquals('Text', $typeMap['TEXT']);
|
||||||
|
$this->assertEquals('Value set', $typeMap['VSET']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearCacheDoesNotThrowError()
|
||||||
|
{
|
||||||
|
ValueSet::getAll();
|
||||||
|
$result = ValueSet::clearCache();
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOrderPriorityFromAlias()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('priority');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$priorityMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Stat', $priorityMap['S']);
|
||||||
|
$this->assertEquals('Routine', $priorityMap['R']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTestStatus()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('test_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Waiting for Results', $statusMap['PENDING']);
|
||||||
|
$this->assertEquals('Analyzing', $statusMap['IN_PROCESS']);
|
||||||
|
$this->assertEquals('Verified & Signed', $statusMap['VERIFIED']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOrderRequestStatus()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('request_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('To be collected', $statusMap['STC']);
|
||||||
|
$this->assertEquals('Collected', $statusMap['SCtd']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetResultResultStatus()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('result_status');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$statusMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Preliminary', $statusMap['PRELIMINARY']);
|
||||||
|
$this->assertEquals('Final', $statusMap['FINAL']);
|
||||||
|
$this->assertEquals('Corrected', $statusMap['CORRECTED']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetReturnsFormattedValues()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('gender');
|
||||||
|
$this->assertEquals('1', $result[0]['value']);
|
||||||
|
$this->assertEquals('Female', $result[0]['label']);
|
||||||
|
$this->assertEquals('2', $result[1]['value']);
|
||||||
|
$this->assertEquals('Male', $result[1]['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetWithSpecialCharactersInKey()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('result_unit');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
$this->assertContains('g/dL', $values);
|
||||||
|
$this->assertContains('mg/dL', $values);
|
||||||
|
$this->assertContains('x106/mL', $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMathSigns()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('math_sign');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$signMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Equal', $signMap['=']);
|
||||||
|
$this->assertEquals('Less than', $signMap['<']);
|
||||||
|
$this->assertEquals('Greater than', $signMap['>']);
|
||||||
|
$this->assertEquals('Less than or equal to', $signMap['<=']);
|
||||||
|
$this->assertEquals('Greater than or equal to', $signMap['>=']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetContainerCapColor()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('container_cap_color');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$colorMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Purple', $colorMap['PRPL']);
|
||||||
|
$this->assertEquals('Red', $colorMap['RED']);
|
||||||
|
$this->assertEquals('Yellow', $colorMap['YLLW']);
|
||||||
|
$this->assertEquals('Green', $colorMap['GRN']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOrganizationSiteType()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('site_type');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$siteMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('Government Hospital', $siteMap['GH']);
|
||||||
|
$this->assertEquals('Private Hospital', $siteMap['PH']);
|
||||||
|
$this->assertEquals('Government Lab', $siteMap['GL']);
|
||||||
|
$this->assertEquals('Private Lab', $siteMap['PL']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPatEntityType()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getRaw('entity_type');
|
||||||
|
$keys = array_column($result, 'key');
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
|
||||||
|
$entityMap = array_combine($keys, $values);
|
||||||
|
$this->assertEquals('HIS', $entityMap['HIS']);
|
||||||
|
$this->assertEquals('Site', $entityMap['SITE']);
|
||||||
|
$this->assertEquals('Workstation', $entityMap['WST']);
|
||||||
|
$this->assertEquals('Equipment/Instrument', $entityMap['INST']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTestType()
|
||||||
|
{
|
||||||
|
$result = ValueSet::get('test_type');
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertNotEmpty($result);
|
||||||
|
|
||||||
|
$values = array_column($result, 'value');
|
||||||
|
$this->assertContains('TEST', $values);
|
||||||
|
$this->assertContains('PARAM', $values);
|
||||||
|
$this->assertContains('CALC', $values);
|
||||||
|
$this->assertContains('GROUP', $values);
|
||||||
|
$this->assertContains('TITLE', $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransformLabels()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
['Gender' => '1', 'Country' => 'ID'],
|
||||||
|
['Gender' => '2', 'Country' => 'US']
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ValueSet::transformLabels($data, [
|
||||||
|
'Gender' => 'gender',
|
||||||
|
'Country' => 'country'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals('Female', $result[0]['GenderText']);
|
||||||
|
$this->assertEquals('Male', $result[1]['GenderText']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetOptions()
|
||||||
|
{
|
||||||
|
$result = ValueSet::getOptions('gender');
|
||||||
|
$this->assertIsArray($result);
|
||||||
|
$this->assertNotEmpty($result);
|
||||||
|
$this->assertArrayHasKey('key', $result[0]);
|
||||||
|
$this->assertArrayHasKey('value', $result[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user