From cd65e91db182183868e2fbfb20c972ba0693b090 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Mon, 5 Jan 2026 16:55:34 +0700 Subject: [PATCH] refactor: Rename controllers to follow CodeIgniter 4 naming convention - Rename all controllers from X.php to XController.php format - Add new RefTxtModel for text-based reference ranges - Rename group_dialog.php to grp_dialog.php and remove title_dialog.php - Add comprehensive test suite for v2/master/TestDef module - Update Routes.php to reflect controller renames - Remove obsolete data files (clqms_v2.sql, lab.dbml) --- README.md | 161 ++ app/Config/Routes.php | 244 +-- .../{AreaGeo.php => AreaGeoController.php} | 4 +- .../{Auth.php => AuthController.php} | 111 +- .../{AuthV2.php => AuthV2Controller.php} | 2 +- .../{Contact.php => ContactController.php} | 4 +- ...lty.php => MedicalSpecialtyController.php} | 4 +- ...ccupation.php => OccupationController.php} | 4 +- .../{Counter.php => CounterController.php} | 4 +- .../{Sample.php => DashboardController.php} | 16 +- .../{Edge.php => EdgeController.php} | 18 +- .../{Home.php => HomeController.php} | 2 +- .../{Location.php => LocationController.php} | 4 +- ...{OrderTest.php => OrderTestController.php} | 4 +- .../{Account.php => AccountController.php} | 4 +- ...epartment.php => DepartmentController.php} | 4 +- ...iscipline.php => DisciplineController.php} | 4 +- .../{Site.php => SiteController.php} | 4 +- ...kstation.php => WorkstationController.php} | 4 +- .../{PatVisit.php => PatVisitController.php} | 4 +- .../{Patient.php => PatientController.php} | 4 +- .../{Dashboard.php => ResultController.php} | 2 +- .../{Result.php => SampleController.php} | 2 +- ...inerDef.php => ContainerDefController.php} | 4 +- ...n.php => SpecimenCollectionController.php} | 4 +- .../{Specimen.php => SpecimenController.php} | 4 +- ...menPrep.php => SpecimenPrepController.php} | 4 +- ...tatus.php => SpecimenStatusController.php} | 2 +- .../{TestMap.php => TestMapController.php} | 4 +- app/Controllers/Tests.php | 523 ------- app/Controllers/TestsController.php | 741 +++++++++ .../{ValueSet.php => ValueSetController.php} | 4 +- ...ueSetDef.php => ValueSetDefController.php} | 4 +- .../{Zones.php => ZonesController.php} | 4 +- app/Models/RefRange/RefNumModel.php | 33 +- app/Models/RefRange/RefTxtModel.php | 33 + app/Views/v2/master/tests/calc_dialog.php | 564 ++++--- app/Views/v2/master/tests/group_dialog.php | 211 --- app/Views/v2/master/tests/grp_dialog.php | 305 ++++ app/Views/v2/master/tests/param_dialog.php | 558 ++++--- app/Views/v2/master/tests/test_dialog.php | 573 ++++--- app/Views/v2/master/tests/tests_index.php | 1390 ++++++++--------- app/Views/v2/master/tests/title_dialog.php | 120 -- data/clqms_v2.sql | 272 ---- data/lab.dbml | 234 --- plans/ref_range_multiple_support_plan.md | 689 -------- tests/_support/v2/MasterTestCase.php | 325 ++++ tests/feature/TestDef/TestDefSiteTest.php | 493 +++--- .../v2/master/TestDef/TestDefCalcTest.php | 328 ++++ .../v2/master/TestDef/TestDefGroupTest.php | 291 ++++ .../v2/master/TestDef/TestDefParamTest.php | 288 ++++ .../v2/master/TestDef/TestDefSiteTest.php | 375 +++++ .../v2/master/TestDef/TestDefCalModelTest.php | 145 ++ .../v2/master/TestDef/TestDefGrpModelTest.php | 132 ++ .../TestDef/TestDefSiteModelMasterTest.php | 220 +++ .../master/TestDef/TestDefSiteModelTest.php | 137 ++ .../master/TestDef/TestDefTechModelTest.php | 160 ++ .../v2/master/TestDef/TestMapModelTest.php | 155 ++ 58 files changed, 6057 insertions(+), 3886 deletions(-) rename app/Controllers/{AreaGeo.php => AreaGeoController.php} (97%) rename app/Controllers/{Auth.php => AuthController.php} (82%) rename app/Controllers/{AuthV2.php => AuthV2Controller.php} (99%) rename app/Controllers/Contact/{Contact.php => ContactController.php} (98%) rename app/Controllers/Contact/{MedicalSpecialty.php => MedicalSpecialtyController.php} (97%) rename app/Controllers/Contact/{Occupation.php => OccupationController.php} (97%) rename app/Controllers/{Counter.php => CounterController.php} (97%) rename app/Controllers/{Sample.php => DashboardController.php} (73%) rename app/Controllers/{Edge.php => EdgeController.php} (95%) rename app/Controllers/{Home.php => HomeController.php} (95%) rename app/Controllers/{Location.php => LocationController.php} (98%) rename app/Controllers/{OrderTest.php => OrderTestController.php} (99%) rename app/Controllers/Organization/{Account.php => AccountController.php} (98%) rename app/Controllers/Organization/{Department.php => DepartmentController.php} (97%) rename app/Controllers/Organization/{Discipline.php => DisciplineController.php} (98%) rename app/Controllers/Organization/{Site.php => SiteController.php} (98%) rename app/Controllers/Organization/{Workstation.php => WorkstationController.php} (97%) rename app/Controllers/{PatVisit.php => PatVisitController.php} (98%) rename app/Controllers/Patient/{Patient.php => PatientController.php} (99%) rename app/Controllers/{Dashboard.php => ResultController.php} (94%) rename app/Controllers/{Result.php => SampleController.php} (94%) rename app/Controllers/Specimen/{ContainerDef.php => ContainerDefController.php} (97%) rename app/Controllers/Specimen/{SpecimenCollection.php => SpecimenCollectionController.php} (97%) rename app/Controllers/Specimen/{Specimen.php => SpecimenController.php} (97%) rename app/Controllers/Specimen/{SpecimenPrep.php => SpecimenPrepController.php} (97%) rename app/Controllers/Specimen/{SpecimenStatus.php => SpecimenStatusController.php} (99%) rename app/Controllers/Test/{TestMap.php => TestMapController.php} (97%) delete mode 100644 app/Controllers/Tests.php create mode 100644 app/Controllers/TestsController.php rename app/Controllers/ValueSet/{ValueSet.php => ValueSetController.php} (98%) rename app/Controllers/ValueSet/{ValueSetDef.php => ValueSetDefController.php} (98%) rename app/Controllers/{Zones.php => ZonesController.php} (98%) create mode 100644 app/Models/RefRange/RefTxtModel.php delete mode 100644 app/Views/v2/master/tests/group_dialog.php create mode 100644 app/Views/v2/master/tests/grp_dialog.php delete mode 100644 app/Views/v2/master/tests/title_dialog.php delete mode 100644 data/clqms_v2.sql delete mode 100644 data/lab.dbml delete mode 100644 plans/ref_range_multiple_support_plan.md create mode 100644 tests/_support/v2/MasterTestCase.php create mode 100644 tests/feature/v2/master/TestDef/TestDefCalcTest.php create mode 100644 tests/feature/v2/master/TestDef/TestDefGroupTest.php create mode 100644 tests/feature/v2/master/TestDef/TestDefParamTest.php create mode 100644 tests/feature/v2/master/TestDef/TestDefSiteTest.php create mode 100644 tests/unit/v2/master/TestDef/TestDefCalModelTest.php create mode 100644 tests/unit/v2/master/TestDef/TestDefGrpModelTest.php create mode 100644 tests/unit/v2/master/TestDef/TestDefSiteModelMasterTest.php create mode 100644 tests/unit/v2/master/TestDef/TestDefSiteModelTest.php create mode 100644 tests/unit/v2/master/TestDef/TestDefTechModelTest.php create mode 100644 tests/unit/v2/master/TestDef/TestMapModelTest.php diff --git a/README.md b/README.md index 3173da2..7a9b8c3 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,167 @@ When working on UI components or dropdowns, **always check for existing ValueSet --- +## ๐ Master Data Management + +CLQMS provides comprehensive master data management for laboratory operations. All master data is accessible via the V2 UI at `/v2/master/*` endpoints. + +### ๐งช Laboratory Tests (`/v2/master/tests`) + +The Test Definitions module manages all laboratory test configurations including parameters, calculated tests, and test panels. + +#### Test Types + +| Type Code | Description | Table | +|-----------|-------------|-------| +| `TEST` | Individual laboratory test with technical specs | `testdefsite` + `testdeftech` | +| `PARAM` | Parameter value (non-lab measurement) | `testdefsite` + `testdeftech` | +| `CALC` | Calculated test with formula | `testdefsite` + `testdefcal` | +| `GROUP` | Panel/profile containing multiple tests | `testdefsite` + `testdefgrp` | +| `TITLE` | Section title for report organization | `testdefsite` | + +#### API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/tests` | List all tests with optional filtering | +| `GET` | `/api/tests/{id}` | Get test details with type-specific data | +| `POST` | `/api/tests` | Create new test definition | +| `PATCH` | `/api/tests` | Update existing test | +| `DELETE` | `/api/tests` | Soft delete test (sets EndDate) | + +#### Filtering Parameters + +- `TestSiteName` - Search by test name (partial match) +- `TestType` - Filter by test type VID (1-5) +- `VisibleScr` - Filter by screen visibility (0/1) +- `VisibleRpt` - Filter by report visibility (0/1) + +#### Test Response Structure + +```json +{ + "status": "success", + "message": "Data fetched successfully", + "data": [ + { + "TestSiteID": 1, + "TestSiteCode": "CBC", + "TestSiteName": "Complete Blood Count", + "TestType": 4, + "TypeCode": "GROUP", + "TypeName": "Group Test", + "SeqScr": 50, + "VisibleScr": 1, + "VisibleRpt": 1 + } + ] +} +``` + +### ๐ Reference Ranges (`/v2/master/refrange`) + +Reference Ranges define normal and critical values for test results. The system supports multiple reference range types based on patient demographics. + +#### Reference Range Types + +| Type | Table | Description | +|------|-------|-------------| +| Numeric | `refnum` | Numeric ranges with age/sex criteria | +| Threshold | `refthold` | Critical threshold values | +| Text | `reftxt` | Text-based reference values | +| Value Set | `refvset` | Coded reference values | + +#### Numeric Reference Range Structure + +| Field | Description | +|-------|-------------| +| `NumRefType` | Type: REF (Reference), CRTC (Critical), VAL (Validation), RERUN | +| `RangeType` | RANGE or THOLD | +| `Sex` | Gender filter (0=All, 1=Female, 2=Male) | +| `AgeStart` | Minimum age (years) | +| `AgeEnd` | Maximum age (years) | +| `LowSign` | Low boundary sign (=, <, <=) | +| `Low` | Low boundary value | +| `HighSign` | High boundary sign (=, >, >=) | +| `High` | High boundary value | +| `Flag` | Result flag (H, L, A, etc.) | + +#### API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/refnum` | List numeric reference ranges | +| `GET` | `/api/refnum/{id}` | Get reference range details | +| `POST` | `/api/refnum` | Create reference range | +| `PATCH` | `/api/refnum` | Update reference range | +| `DELETE` | `/api/refnum` | Soft delete reference range | + +### ๐ Value Sets (`/v2/master/valuesets`) + +Value Sets are configurable dropdown options used throughout the system. Each Value Set Definition (VSetDef) contains multiple Value Set Values (ValueSet). + +#### Value Set Hierarchy + +``` +valuesetdef (VSetDefID, VSName, VSDesc) + โโโ valueset (VID, VSetID, VValue, VDesc, VOrder, VCategory) +``` + +#### Common Value Sets + +| VSetDefID | Name | Example Values | +|-----------|------|----------------| +| 1 | Priority | STAT (S), ASAP (A), Routine (R), Preop (P) | +| 2 | Enable/Disable | Disabled (0), Enabled (1) | +| 3 | Gender | Female (1), Male (2), Unknown (3) | +| 10 | Order Status | STC, SCtd, SArrv, SRcvd, SAna, etc. | +| 15 | Specimen Type | BLD, SER, PLAS, UR, CSF, etc. | +| 16 | Unit | L, mL, g/dL, mg/dL, etc. | +| 27 | Test Type | TEST, PARAM, CALC, GROUP, TITLE | +| 28 | Result Unit | g/dL, g/L, mg/dL, x10^6/mL, etc. | +| 35 | Test Activity | Order, Analyse, VER, REV, REP | + +#### API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/valuesetdef` | List all value set definitions | +| `GET` | `/api/valuesetdef/{id}` | Get valueset with all values | +| `GET` | `/api/valuesetdef/{id}/values` | Get values for specific valueset | +| `POST` | `/api/valuesetdef` | Create new valueset definition | +| `PATCH` | `/api/valuesetdef` | Update valueset definition | +| `DELETE` | `/api/valuesetdef` | Delete valueset definition | + +#### Value Set Response Structure + +```json +{ + "status": "success", + "data": { + "VSetDefID": 27, + "VSName": "Test Type", + "VSDesc": "testdefsite.TestType", + "values": [ + { "VID": 1, "VValue": "TEST", "VDesc": "Test", "VOrder": 1 }, + { "VID": 2, "VValue": "PARAM", "VDesc": "Parameter", "VOrder": 2 }, + { "VID": 3, "VValue": "CALC", "VDesc": "Calculated Test", "VOrder": 3 }, + { "VID": 4, "VValue": "GROUP", "VDesc": "Group Test", "VOrder": 4 }, + { "VID": 5, "VValue": "TITLE", "VDesc": "Title", "VOrder": 5 } + ] + } +} +``` + +### ๐ Database Tables Summary + +| Category | Tables | Purpose | +|----------|--------|---------| +| Tests | `testdefsite`, `testdeftech`, `testdefcal`, `testdefgrp`, `testmap` | Test definitions | +| Reference Ranges | `refnum`, `refthold`, `reftxt`, `refvset` | Result validation | +| Value Sets | `valuesetdef`, `valueset` | Configurable options | + +--- + ## ๐ Edge API - Instrument Integration The **Edge API** provides endpoints for integrating laboratory instruments via the `tiny-edge` middleware. Results from instruments are staged in the `edgeres` table before processing into the main patient results (`patres`). diff --git a/app/Config/Routes.php b/app/Config/Routes.php index e6ad184..5fe5c9b 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -5,7 +5,7 @@ use CodeIgniter\Router\RouteCollection; /** * @var RouteCollection $routes */ -$routes->get('/', function() { +$routes->get('/', function () { return redirect()->to('/v2'); }); @@ -13,10 +13,10 @@ $routes->options('(:any)', function () { return ''; }); -$routes->group('api', ['filter' => 'auth'], function($routes) { - $routes->get('dashboard', 'Dashboard::index'); - $routes->get('result', 'Result::index'); - $routes->get('sample', 'Sample::index'); +$routes->group('api', ['filter' => 'auth'], function ($routes) { + $routes->get('dashboard', 'DashboardController::index'); + $routes->get('result', 'ResultController::index'); + $routes->get('sample', 'SampleController::index'); }); // Public Routes (no auth required) @@ -24,10 +24,10 @@ $routes->get('/v2/login', 'PagesController::login'); // V2 Auth API Routes (public - no auth required) $routes->group('v2/auth', function ($routes) { - $routes->post('login', 'AuthV2::login'); - $routes->post('register', 'AuthV2::register'); - $routes->get('check', 'AuthV2::checkAuth'); - $routes->post('logout', 'AuthV2::logout'); + $routes->post('login', 'AuthV2Controller::login'); + $routes->post('register', 'AuthV2Controller::register'); + $routes->get('check', 'AuthV2Controller::checkAuth'); + $routes->post('logout', 'AuthV2Controller::logout'); }); // Protected Page Routes - V2 (requires auth) @@ -37,18 +37,18 @@ $routes->group('v2', ['filter' => 'auth'], function ($routes) { $routes->get('patients', 'PagesController::patients'); $routes->get('requests', 'PagesController::requests'); $routes->get('settings', 'PagesController::settings'); - + // Master Data - Organization $routes->get('master/organization/accounts', 'PagesController::masterOrgAccounts'); $routes->get('master/organization/sites', 'PagesController::masterOrgSites'); $routes->get('master/organization/disciplines', 'PagesController::masterOrgDisciplines'); $routes->get('master/organization/departments', 'PagesController::masterOrgDepartments'); $routes->get('master/organization/workstations', 'PagesController::masterOrgWorkstations'); - + // Master Data - Specimen $routes->get('master/specimen/containers', 'PagesController::masterSpecimenContainers'); $routes->get('master/specimen/preparations', 'PagesController::masterSpecimenPreparations'); - + // Master Data - Tests & ValueSets $routes->get('master/tests', 'PagesController::masterTests'); $routes->get('master/valuesets', 'PagesController::masterValueSets'); @@ -60,36 +60,36 @@ $routes->get('faker/faker-patient/(:num)', 'faker\FakerPatient::sendMany/$1'); $routes->group('api', function ($routes) { // Auth $routes->group('auth', function ($routes) { - $routes->post('login', 'Auth::login'); - $routes->post('change_pass', 'Auth::change_pass'); - $routes->post('register', 'Auth::register'); - $routes->get('check', 'Auth::checkAuth'); - $routes->post('logout', 'Auth::logout'); + $routes->post('login', 'AuthController::login'); + $routes->post('change_pass', 'AuthController::change_pass'); + $routes->post('register', 'AuthController::register'); + $routes->get('check', 'AuthController::checkAuth'); + $routes->post('logout', 'AuthController::logout'); }); // Patient $routes->group('patient', function ($routes) { - $routes->get('/', 'Patient\Patient::index'); - $routes->post('/', 'Patient\Patient::create'); - $routes->get('(:num)', 'Patient\Patient::show/$1'); - $routes->delete('/', 'Patient\Patient::delete'); - $routes->patch('/', 'Patient\Patient::update'); - $routes->get('check', 'Patient\Patient::patientCheck'); + $routes->get('/', 'Patient\PatientController::index'); + $routes->post('/', 'Patient\PatientController::create'); + $routes->get('(:num)', 'Patient\PatientController::show/$1'); + $routes->delete('/', 'Patient\PatientController::delete'); + $routes->patch('/', 'Patient\PatientController::update'); + $routes->get('check', 'Patient\PatientController::patientCheck'); }); // PatVisit $routes->group('patvisit', function ($routes) { - $routes->get('/', 'PatVisit::index'); - $routes->post('/', 'PatVisit::create'); - $routes->get('patient/(:num)', 'PatVisit::showByPatient/$1'); - $routes->get('(:any)', 'PatVisit::show/$1'); - $routes->delete('/', 'PatVisit::delete'); - $routes->patch('/', 'PatVisit::update'); + $routes->get('/', 'PatVisitController::index'); + $routes->post('/', 'PatVisitController::create'); + $routes->get('patient/(:num)', 'PatVisitController::showByPatient/$1'); + $routes->get('(:any)', 'PatVisitController::show/$1'); + $routes->delete('/', 'PatVisitController::delete'); + $routes->patch('/', 'PatVisitController::update'); }); $routes->group('patvisitadt', function ($routes) { - $routes->post('/', 'PatVisit::createADT'); - $routes->patch('/', 'PatVisit::updateADT'); + $routes->post('/', 'PatVisitController::createADT'); + $routes->patch('/', 'PatVisitController::updateADT'); }); // Master Data @@ -115,169 +115,169 @@ $routes->group('api', function ($routes) { // Location $routes->group('location', function ($routes) { - $routes->get('/', 'Location::index'); - $routes->get('(:num)', 'Location::show/$1'); - $routes->post('/', 'Location::create'); - $routes->patch('/', 'Location::update'); - $routes->delete('/', 'Location::delete'); + $routes->get('/', 'LocationController::index'); + $routes->get('(:num)', 'LocationController::show/$1'); + $routes->post('/', 'LocationController::create'); + $routes->patch('/', 'LocationController::update'); + $routes->delete('/', 'LocationController::delete'); }); // Contact $routes->group('contact', function ($routes) { - $routes->get('/', 'Contact\Contact::index'); - $routes->get('(:num)', 'Contact\Contact::show/$1'); - $routes->post('/', 'Contact\Contact::create'); - $routes->patch('/', 'Contact\Contact::update'); - $routes->delete('/', 'Contact\Contact::delete'); + $routes->get('/', 'Contact\ContactController::index'); + $routes->get('(:num)', 'Contact\ContactController::show/$1'); + $routes->post('/', 'Contact\ContactController::create'); + $routes->patch('/', 'Contact\ContactController::update'); + $routes->delete('/', 'Contact\ContactController::delete'); }); $routes->group('occupation', function ($routes) { - $routes->get('/', 'Contact\Occupation::index'); - $routes->get('(:num)', 'Contact\Occupation::show/$1'); - $routes->post('/', 'Contact\Occupation::create'); - $routes->patch('/', 'Contact\Occupation::update'); - //$routes->delete('/', 'Contact\Occupation::delete'); + $routes->get('/', 'Contact\OccupationController::index'); + $routes->get('(:num)', 'Contact\OccupationController::show/$1'); + $routes->post('/', 'Contact\OccupationController::create'); + $routes->patch('/', 'Contact\OccupationController::update'); + //$routes->delete('/', 'Contact\OccupationController::delete'); }); $routes->group('medicalspecialty', function ($routes) { - $routes->get('/', 'Contact\MedicalSpecialty::index'); - $routes->get('(:num)', 'Contact\MedicalSpecialty::show/$1'); - $routes->post('/', 'Contact\MedicalSpecialty::create'); - $routes->patch('/', 'Contact\MedicalSpecialty::update'); + $routes->get('/', 'Contact\MedicalSpecialtyController::index'); + $routes->get('(:num)', 'Contact\MedicalSpecialtyController::show/$1'); + $routes->post('/', 'Contact\MedicalSpecialtyController::create'); + $routes->patch('/', 'Contact\MedicalSpecialtyController::update'); }); // ValueSet $routes->group('valueset', function ($routes) { - $routes->get('/', 'ValueSet\ValueSet::index'); - $routes->get('(:num)', 'ValueSet\ValueSet::show/$1'); - $routes->get('valuesetdef/(:num)', 'ValueSet\ValueSet::showByValueSetDef/$1'); - $routes->post('/', 'ValueSet\ValueSet::create'); - $routes->patch('/', 'ValueSet\ValueSet::update'); - $routes->delete('/', 'ValueSet\ValueSet::delete'); + $routes->get('/', 'ValueSet\ValueSetController::index'); + $routes->get('(:num)', 'ValueSet\ValueSetController::show/$1'); + $routes->get('valuesetdef/(:num)', 'ValueSet\ValueSetController::showByValueSetDef/$1'); + $routes->post('/', 'ValueSet\ValueSetController::create'); + $routes->patch('/', 'ValueSet\ValueSetController::update'); + $routes->delete('/', 'ValueSet\ValueSetController::delete'); }); $routes->group('valuesetdef', function ($routes) { - $routes->get('/', 'ValueSet\ValueSetDef::index'); - $routes->get('(:segment)', 'ValueSet\ValueSetDef::show/$1'); - $routes->post('/', 'ValueSet\ValueSetDef::create'); - $routes->patch('/', 'ValueSet\ValueSetDef::update'); - $routes->delete('/', 'ValueSet\ValueSetDef::delete'); + $routes->get('/', 'ValueSet\ValueSetDefController::index'); + $routes->get('(:segment)', 'ValueSet\ValueSetDefController::show/$1'); + $routes->post('/', 'ValueSet\ValueSetDefController::create'); + $routes->patch('/', 'ValueSet\ValueSetDefController::update'); + $routes->delete('/', 'ValueSet\ValueSetDefController::delete'); }); // Counter $routes->group('counter', function ($routes) { - $routes->get('/', 'Counter::index'); - $routes->get('(:num)', 'Counter::show/$1'); - $routes->post('/', 'Counter::create'); - $routes->patch('/', 'Counter::update'); - $routes->delete('/', 'Counter::delete'); + $routes->get('/', 'CounterController::index'); + $routes->get('(:num)', 'CounterController::show/$1'); + $routes->post('/', 'CounterController::create'); + $routes->patch('/', 'CounterController::update'); + $routes->delete('/', 'CounterController::delete'); }); // AreaGeo $routes->group('areageo', function ($routes) { - $routes->get('/', 'AreaGeo::index'); - $routes->get('provinces', 'AreaGeo::getProvinces'); - $routes->get('cities', 'AreaGeo::getCities'); + $routes->get('/', 'AreaGeoController::index'); + $routes->get('provinces', 'AreaGeoController::getProvinces'); + $routes->get('cities', 'AreaGeoController::getCities'); }); // Organization $routes->group('organization', function ($routes) { // Account $routes->group('account', function ($routes) { - $routes->get('/', 'Organization\Account::index'); - $routes->get('(:num)', 'Organization\Account::show/$1'); - $routes->post('/', 'Organization\Account::create'); - $routes->patch('/', 'Organization\Account::update'); - $routes->delete('/', 'Organization\Account::delete'); + $routes->get('/', 'Organization\AccountController::index'); + $routes->get('(:num)', 'Organization\AccountController::show/$1'); + $routes->post('/', 'Organization\AccountController::create'); + $routes->patch('/', 'Organization\AccountController::update'); + $routes->delete('/', 'Organization\AccountController::delete'); }); // Site $routes->group('site', function ($routes) { - $routes->get('/', 'Organization\Site::index'); - $routes->get('(:num)', 'Organization\Site::show/$1'); - $routes->post('/', 'Organization\Site::create'); - $routes->patch('/', 'Organization\Site::update'); - $routes->delete('/', 'Organization\Site::delete'); + $routes->get('/', 'Organization\SiteController::index'); + $routes->get('(:num)', 'Organization\SiteController::show/$1'); + $routes->post('/', 'Organization\SiteController::create'); + $routes->patch('/', 'Organization\SiteController::update'); + $routes->delete('/', 'Organization\SiteController::delete'); }); // Discipline $routes->group('discipline', function ($routes) { - $routes->get('/', 'Organization\Discipline::index'); - $routes->get('(:num)', 'Organization\Discipline::show/$1'); - $routes->post('/', 'Organization\Discipline::create'); - $routes->patch('/', 'Organization\Discipline::update'); - $routes->delete('/', 'Organization\Discipline::delete'); + $routes->get('/', 'Organization\DisciplineController::index'); + $routes->get('(:num)', 'Organization\DisciplineController::show/$1'); + $routes->post('/', 'Organization\DisciplineController::create'); + $routes->patch('/', 'Organization\DisciplineController::update'); + $routes->delete('/', 'Organization\DisciplineController::delete'); }); // Department $routes->group('department', function ($routes) { - $routes->get('/', 'Organization\Department::index'); - $routes->get('(:num)', 'Organization\Department::show/$1'); - $routes->post('/', 'Organization\Department::create'); - $routes->patch('/', 'Organization\Department::update'); - $routes->delete('/', 'Organization\Department::delete'); + $routes->get('/', 'Organization\DepartmentController::index'); + $routes->get('(:num)', 'Organization\DepartmentController::show/$1'); + $routes->post('/', 'Organization\DepartmentController::create'); + $routes->patch('/', 'Organization\DepartmentController::update'); + $routes->delete('/', 'Organization\DepartmentController::delete'); }); // Workstation $routes->group('workstation', function ($routes) { - $routes->get('/', 'Organization\Workstation::index'); - $routes->get('(:num)', 'Organization\Workstation::show/$1'); - $routes->post('/', 'Organization\Workstation::create'); - $routes->patch('/', 'Organization\Workstation::update'); - $routes->delete('/', 'Organization\Workstation::delete'); + $routes->get('/', 'Organization\WorkstationController::index'); + $routes->get('(:num)', 'Organization\WorkstationController::show/$1'); + $routes->post('/', 'Organization\WorkstationController::create'); + $routes->patch('/', 'Organization\WorkstationController::update'); + $routes->delete('/', 'Organization\WorkstationController::delete'); }); }); // Specimen $routes->group('specimen', function ($routes) { $routes->group('containerdef', function ($routes) { - $routes->get('/', 'Specimen\ContainerDef::index'); - $routes->get('(:num)', 'Specimen\ContainerDef::show/$1'); - $routes->post('/', 'Specimen\ContainerDef::create'); - $routes->patch('/', 'Specimen\ContainerDef::update'); + $routes->get('/', 'Specimen\ContainerDefController::index'); + $routes->get('(:num)', 'Specimen\ContainerDefController::show/$1'); + $routes->post('/', 'Specimen\ContainerDefController::create'); + $routes->patch('/', 'Specimen\ContainerDefController::update'); }); $routes->group('prep', function ($routes) { - $routes->get('/', 'Specimen\Prep::index'); - $routes->get('(:num)', 'Specimen\Prep::show/$1'); - $routes->post('/', 'Specimen\Prep::create'); - $routes->patch('/', 'Specimen\Prep::update'); + $routes->get('/', 'Specimen\SpecimenPrepController::index'); + $routes->get('(:num)', 'Specimen\SpecimenPrepController::show/$1'); + $routes->post('/', 'Specimen\SpecimenPrepController::create'); + $routes->patch('/', 'Specimen\SpecimenPrepController::update'); }); $routes->group('status', function ($routes) { - $routes->get('/', 'Specimen\Status::index'); - $routes->get('(:num)', 'Specimen\Status::show/$1'); - $routes->post('/', 'Specimen\Status::create'); - $routes->patch('/', 'Specimen\Status::update'); + $routes->get('/', 'Specimen\SpecimenStatusController::index'); + $routes->get('(:num)', 'Specimen\SpecimenStatusController::show/$1'); + $routes->post('/', 'Specimen\SpecimenStatusController::create'); + $routes->patch('/', 'Specimen\SpecimenStatusController::update'); }); $routes->group('collection', function ($routes) { - $routes->get('/', 'Specimen\Collection::index'); - $routes->get('(:num)', 'Specimen\Collection::show/$1'); - $routes->post('/', 'Specimen\Collection::create'); - $routes->patch('/', 'Specimen\Collection::update'); + $routes->get('/', 'Specimen\SpecimenCollectionController::index'); + $routes->get('(:num)', 'Specimen\SpecimenCollectionController::show/$1'); + $routes->post('/', 'Specimen\SpecimenCollectionController::create'); + $routes->patch('/', 'Specimen\SpecimenCollectionController::update'); }); - $routes->get('/', 'Specimen\Specimen::index'); - $routes->get('(:num)', 'Specimen\Specimen::show/$1'); - $routes->post('/', 'Specimen\Specimen::create'); - $routes->patch('/', 'Specimen\Specimen::update'); + $routes->get('/', 'Specimen\SpecimenController::index'); + $routes->get('(:num)', 'Specimen\SpecimenController::show/$1'); + $routes->post('/', 'Specimen\SpecimenController::create'); + $routes->patch('/', 'Specimen\SpecimenController::update'); }); // Tests $routes->group('tests', function ($routes) { - $routes->get('/', 'Tests::index'); - $routes->get('(:any)', 'Tests::show/$1'); - $routes->post('/', 'Tests::create'); - $routes->patch('/', 'Tests::update'); + $routes->get('/', 'TestsController::index'); + $routes->get('(:num)', 'TestsController::show/$1'); + $routes->post('/', 'TestsController::create'); + $routes->patch('/', 'TestsController::update'); }); // Edge API - Integration with tiny-edge $routes->group('edge', function ($routes) { - $routes->post('results', 'Edge::results'); - $routes->get('orders', 'Edge::orders'); - $routes->post('orders/(:num)/ack', 'Edge::ack/$1'); - $routes->post('status', 'Edge::status'); + $routes->post('results', 'EdgeController::results'); + $routes->get('orders', 'EdgeController::orders'); + $routes->post('orders/(:num)/ack', 'EdgeController::ack/$1'); + $routes->post('status', 'EdgeController::status'); }); }); diff --git a/app/Controllers/AreaGeo.php b/app/Controllers/AreaGeoController.php similarity index 97% rename from app/Controllers/AreaGeo.php rename to app/Controllers/AreaGeoController.php index 498e040..2775469 100644 --- a/app/Controllers/AreaGeo.php +++ b/app/Controllers/AreaGeoController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\AreaGeoModel; -class AreaGeo extends BaseController { +class AreaGeoController extends BaseController { use ResponseTrait; protected $model; @@ -44,4 +44,4 @@ class AreaGeo extends BaseController { return $this->respond([ 'status' => 'success', 'message'=> "Data fetched successfully", 'data' => $rows ], 200); } -} \ No newline at end of file +} diff --git a/app/Controllers/Auth.php b/app/Controllers/AuthController.php similarity index 82% rename from app/Controllers/Auth.php rename to app/Controllers/AuthController.php index 0ada4e5..25e0dba 100644 --- a/app/Controllers/Auth.php +++ b/app/Controllers/AuthController.php @@ -12,23 +12,26 @@ use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use CodeIgniter\Cookie\Cookie; -class Auth extends Controller { +class AuthController extends Controller +{ use ResponseTrait; // ok - public function __construct() { + public function __construct() + { $this->db = \Config\Database::connect(); } // ok - public function checkAuth() { + public function checkAuth() + { $token = $this->request->getCookie('token'); - $key = getenv('JWT_SECRET'); + $key = getenv('JWT_SECRET'); // Jika token FE tidak ada langsung kabarkan failed if (!$token) { return $this->respond([ - 'status' => 'failed', + 'status' => 'failed', 'message' => 'No token found' ], 401); } @@ -38,37 +41,37 @@ class Auth extends Controller { $decodedPayload = JWT::decode($token, new Key($key, 'HS256')); return $this->respond([ - 'status' => 'success', + 'status' => 'success', 'message' => 'Authenticated', - 'data' => $decodedPayload + 'data' => $decodedPayload ], 200); } catch (ExpiredException $e) { return $this->respond([ - 'status' => 'failed', + 'status' => 'failed', 'message' => 'Token expired', - 'data' => [] + 'data' => [] ], 401); } catch (SignatureInvalidException $e) { return $this->respond([ - 'status' => 'failed', + 'status' => 'failed', 'message' => 'Invalid token signature', - 'data' => [] + 'data' => [] ], 401); } catch (BeforeValidException $e) { return $this->respond([ - 'status' => 'failed', + 'status' => 'failed', 'message' => 'Token not valid yet', - 'data' => [] + 'data' => [] ], 401); } catch (\Exception $e) { return $this->respond([ - 'status' => 'failed', + 'status' => 'failed', 'message' => 'Invalid token: ' . $e->getMessage(), - 'data' => [] + 'data' => [] ], 401); } } @@ -122,7 +125,7 @@ class Auth extends Controller { // // 'httponly' => true, // dipakai agar cookie berikut tidak dapat diakses oleh javascript // // 'samesite' => $isSecure ? Cookie::SAMESITE_NONE : Cookie::SAMESITE_LAX // ]); - + // // Response tanpa token di body // return $this->respond([ @@ -131,7 +134,8 @@ class Auth extends Controller { // 'message' => 'Login successful' // ], 200); // } - public function login() { + public function login() + { // Ambil dari JSON Form dan Key .env $username = $this->request->getVar('username'); @@ -146,7 +150,9 @@ class Auth extends Controller { $query = $this->db->query($sql); $row = $query->getResultArray(); - if (!$row) { return $this->fail('User not found.', 401); } + if (!$row) { + return $this->fail('User not found.', 401); + } $row = $row[0]; if (!password_verify($password, $row['password'])) { return $this->fail('Invalid password.', 401); @@ -155,10 +161,10 @@ class Auth extends Controller { // Buat JWT payload $exp = time() + 864000; $payload = [ - 'userid' => $row['id'], + 'userid' => $row['id'], 'roleid' => $row['role_id'], 'username' => $row['username'], - 'exp' => $exp + 'exp' => $exp ]; try { @@ -170,18 +176,18 @@ class Auth extends Controller { // Kirim Respon ke HttpOnly yg akan disimpan di browser dan tidak akan dapat diakses oleh siapapun $this->response->setCookie([ - 'name' => 'token', // nama token - 'value' => $jwt, // value dari jwt yg sudah di hash - 'expire' => 864000, // 10 hari - 'path' => '/', // valid untuk semua path - 'secure' => true, // set true kalau sudah HTTPS + 'name' => 'token', // nama token + 'value' => $jwt, // value dari jwt yg sudah di hash + 'expire' => 864000, // 10 hari + 'path' => '/', // valid untuk semua path + 'secure' => true, // set true kalau sudah HTTPS 'httponly' => true, // dipakai agar cookie berikut tidak dapat diakses oleh javascript - 'samesite' => Cookie::SAMESITE_NONE + 'samesite' => Cookie::SAMESITE_NONE ]); // Response tanpa token di body return $this->respond([ - 'status' => 'success', + 'status' => 'success', 'code' => 200, 'message' => 'Login successful' ], 200); @@ -199,33 +205,35 @@ class Auth extends Controller { // 'secure' => $isSecure, // 'httponly' => true, // 'samesite' => $isSecure ? Cookie::SAMESITE_NONE : Cookie::SAMESITE_LAX - + // ])->setJSON([ // 'status' => 'success', // 'code' => 200, // 'message' => 'Logout successful' // ], 200); // } - public function logout() { + public function logout() + { // Definisikan ini pada cookies browser, harus sama dengan cookies login return $this->response->setCookie([ - 'name' => 'token', - 'value' => '', - 'expire' => time() - 3600, - 'path' => '/', - 'secure' => true, + 'name' => 'token', + 'value' => '', + 'expire' => time() - 3600, + 'path' => '/', + 'secure' => true, 'httponly' => true, 'samesite' => Cookie::SAMESITE_NONE - + ])->setJSON([ - 'status' => 'success', - 'code' => 200, - 'message' => 'Logout successful' - ], 200); + 'status' => 'success', + 'code' => 200, + 'message' => 'Logout successful' + ], 200); } // ok - public function register() { + public function register() + { $username = strtolower($this->request->getJsonVar('username')); $password = $this->request->getJsonVar('password'); @@ -233,7 +241,7 @@ class Auth extends Controller { // Validasi Awal Dari BE if (empty($username) || empty($password)) { return $this->respond([ - 'status' => 'failed', + 'status' => 'failed', 'code' => 400, 'message' => 'Username and password are required' ], 400); // Gunakan 400 Bad Request @@ -242,11 +250,11 @@ class Auth extends Controller { // Cek Duplikasi Username $exists = $this->db->query("SELECT id FROM users WHERE username = ?", [$username])->getRow(); if ($exists) { - return $this->respond(['status' => 'failed', 'code'=>409,'message' => 'Username already exists'], 409); + return $this->respond(['status' => 'failed', 'code' => 409, 'message' => 'Username already exists'], 409); } $hashedPassword = password_hash($password, PASSWORD_DEFAULT); - + // Mulai transaksi Insert $this->db->transStart(); $this->db->query( @@ -258,8 +266,8 @@ class Auth extends Controller { // Cek status transaksi if ($this->db->transStatus() === false) { return $this->respond([ - 'status' => 'error', - 'code' => 500, + 'status' => 'error', + 'code' => 500, 'message' => 'Failed to create user. Please try again later.' ], 500); } @@ -269,7 +277,7 @@ class Auth extends Controller { 'status' => 'success', 'code' => 201, 'message' => 'User ' . $username . ' successfully created.' - ], 201); + ], 201); } @@ -294,19 +302,20 @@ class Auth extends Controller { // return $this->respond($response); // } - public function coba() { + public function coba() + { $token = $this->request->getCookie('token'); - $key = getenv('JWT_SECRET'); + $key = getenv('JWT_SECRET'); // Decode Token dengan Key yg ada di .env $decodedPayload = JWT::decode($token, new Key($key, 'HS256')); return $this->respond([ - 'status' => 'success', + 'status' => 'success', 'message' => 'Authenticated', - 'data' => $decodedPayload + 'data' => $decodedPayload ], 200); } - + } diff --git a/app/Controllers/AuthV2.php b/app/Controllers/AuthV2Controller.php similarity index 99% rename from app/Controllers/AuthV2.php rename to app/Controllers/AuthV2Controller.php index 9603bc3..c84303b 100644 --- a/app/Controllers/AuthV2.php +++ b/app/Controllers/AuthV2Controller.php @@ -18,7 +18,7 @@ use CodeIgniter\Cookie\Cookie; * Handles authentication for V2 UI * Separate from the main Auth controller to avoid conflicts */ -class AuthV2 extends Controller +class AuthV2Controller extends Controller { use ResponseTrait; diff --git a/app/Controllers/Contact/Contact.php b/app/Controllers/Contact/ContactController.php similarity index 98% rename from app/Controllers/Contact/Contact.php rename to app/Controllers/Contact/ContactController.php index 1771b74..6fb0706 100644 --- a/app/Controllers/Contact/Contact.php +++ b/app/Controllers/Contact/ContactController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\Contact\ContactModel; -class Contact extends BaseController { +class ContactController extends BaseController { use ResponseTrait; protected $db; @@ -76,4 +76,4 @@ class Contact extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Contact/MedicalSpecialty.php b/app/Controllers/Contact/MedicalSpecialtyController.php similarity index 97% rename from app/Controllers/Contact/MedicalSpecialty.php rename to app/Controllers/Contact/MedicalSpecialtyController.php index a8e8730..8e3e9ae 100644 --- a/app/Controllers/Contact/MedicalSpecialty.php +++ b/app/Controllers/Contact/MedicalSpecialtyController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Contact\MedicalSpecialtyModel; -class MedicalSpecialty extends BaseController { +class MedicalSpecialtyController extends BaseController { use ResponseTrait; protected $db; @@ -61,4 +61,4 @@ class MedicalSpecialty extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Contact/Occupation.php b/app/Controllers/Contact/OccupationController.php similarity index 97% rename from app/Controllers/Contact/Occupation.php rename to app/Controllers/Contact/OccupationController.php index 1bc9087..43d017a 100644 --- a/app/Controllers/Contact/Occupation.php +++ b/app/Controllers/Contact/OccupationController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Contact\OccupationModel; -class Occupation extends BaseController { +class OccupationController extends BaseController { use ResponseTrait; protected $db; @@ -61,4 +61,4 @@ class Occupation extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Counter.php b/app/Controllers/CounterController.php similarity index 97% rename from app/Controllers/Counter.php rename to app/Controllers/CounterController.php index 9ca0721..31e2039 100644 --- a/app/Controllers/Counter.php +++ b/app/Controllers/CounterController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\CounterModel; -class Counter extends BaseController { +class CounterController extends BaseController { use ResponseTrait; protected $model; @@ -62,4 +62,4 @@ class Counter extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Sample.php b/app/Controllers/DashboardController.php similarity index 73% rename from app/Controllers/Sample.php rename to app/Controllers/DashboardController.php index 0ba1b58..c6b69b7 100644 --- a/app/Controllers/Sample.php +++ b/app/Controllers/DashboardController.php @@ -12,23 +12,25 @@ use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use CodeIgniter\Cookie\Cookie; -class Sample extends Controller { +class DashboardController extends Controller +{ use ResponseTrait; - public function index() { + public function index() + { $token = $this->request->getCookie('token'); - $key = getenv('JWT_SECRET'); + $key = getenv('JWT_SECRET'); // Decode Token dengan Key yg ada di .env $decodedPayload = JWT::decode($token, new Key($key, 'HS256')); return $this->respond([ - 'status' => 'success', - 'code' => 200, + 'status' => 'success', + 'code' => 200, 'message' => 'Authenticated', - 'data' => $decodedPayload + 'data' => $decodedPayload ], 200); } - + } diff --git a/app/Controllers/Edge.php b/app/Controllers/EdgeController.php similarity index 95% rename from app/Controllers/Edge.php rename to app/Controllers/EdgeController.php index 0951a67..b256669 100644 --- a/app/Controllers/Edge.php +++ b/app/Controllers/EdgeController.php @@ -5,13 +5,15 @@ namespace App\Controllers; use CodeIgniter\API\ResponseTrait; use CodeIgniter\Controller; -class Edge extends Controller { +class EdgeController extends Controller +{ use ResponseTrait; protected $db; protected $edgeResModel; - public function __construct() { + public function __construct() + { $this->db = \Config\Database::connect(); $this->edgeResModel = new \App\Models\EdgeResModel(); } @@ -20,7 +22,8 @@ class Edge extends Controller { * POST /api/edge/results * Receive results from tiny-edge */ - public function results() { + public function results() + { try { $input = $this->request->getJSON(true); @@ -70,7 +73,8 @@ class Edge extends Controller { * GET /api/edge/orders * Return pending orders for an instrument */ - public function orders() { + public function orders() + { try { $instrumentId = $this->request->getGet('instrument'); @@ -95,7 +99,8 @@ class Edge extends Controller { * POST /api/edge/orders/:id/ack * Acknowledge order delivery */ - public function ack($orderId = null) { + public function ack($orderId = null) + { try { if (!$orderId) { return $this->failValidationErrors('Order ID is required'); @@ -129,7 +134,8 @@ class Edge extends Controller { * POST /api/edge/status * Log instrument status */ - public function status() { + public function status() + { try { $input = $this->request->getJSON(true); diff --git a/app/Controllers/Home.php b/app/Controllers/HomeController.php similarity index 95% rename from app/Controllers/Home.php rename to app/Controllers/HomeController.php index 89f165a..e76ca6a 100644 --- a/app/Controllers/Home.php +++ b/app/Controllers/HomeController.php @@ -12,7 +12,7 @@ use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use CodeIgniter\Cookie\Cookie; -class Home extends Controller { +class HomeController extends Controller { use ResponseTrait; public function index() { diff --git a/app/Controllers/Location.php b/app/Controllers/LocationController.php similarity index 98% rename from app/Controllers/Location.php rename to app/Controllers/LocationController.php index e922d5b..24cb70e 100644 --- a/app/Controllers/Location.php +++ b/app/Controllers/LocationController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Location\LocationModel; -class Location extends BaseController { +class LocationController extends BaseController { use ResponseTrait; protected $model; @@ -72,4 +72,4 @@ class Location extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/OrderTest.php b/app/Controllers/OrderTestController.php similarity index 99% rename from app/Controllers/OrderTest.php rename to app/Controllers/OrderTestController.php index 140b926..a7ed0da 100644 --- a/app/Controllers/OrderTest.php +++ b/app/Controllers/OrderTestController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use CodeIgniter\Controller; use CodeIgniter\Database\RawSql; -class OrderTest extends Controller { +class OrderTestController extends Controller { use ResponseTrait; public function __construct() { @@ -214,4 +214,4 @@ class OrderTest extends Controller { return $data; } -} \ No newline at end of file +} diff --git a/app/Controllers/Organization/Account.php b/app/Controllers/Organization/AccountController.php similarity index 98% rename from app/Controllers/Organization/Account.php rename to app/Controllers/Organization/AccountController.php index 68cc3f1..0cc7ec6 100644 --- a/app/Controllers/Organization/Account.php +++ b/app/Controllers/Organization/AccountController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\Organization\AccountModel; -class Account extends BaseController { +class AccountController extends BaseController { use ResponseTrait; protected $db; @@ -76,4 +76,4 @@ class Account extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Organization/Department.php b/app/Controllers/Organization/DepartmentController.php similarity index 97% rename from app/Controllers/Organization/Department.php rename to app/Controllers/Organization/DepartmentController.php index 3cbe5f2..db76c50 100644 --- a/app/Controllers/Organization/Department.php +++ b/app/Controllers/Organization/DepartmentController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\Organization\DepartmentModel; -class Department extends BaseController { +class DepartmentController extends BaseController { use ResponseTrait; protected $db; @@ -72,4 +72,4 @@ class Department extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Organization/Discipline.php b/app/Controllers/Organization/DisciplineController.php similarity index 98% rename from app/Controllers/Organization/Discipline.php rename to app/Controllers/Organization/DisciplineController.php index e2cbb7c..c36d9a5 100644 --- a/app/Controllers/Organization/Discipline.php +++ b/app/Controllers/Organization/DisciplineController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\Organization\DisciplineModel; -class Discipline extends BaseController { +class DisciplineController extends BaseController { use ResponseTrait; protected $db; @@ -79,4 +79,4 @@ class Discipline extends BaseController { } */ } -} \ No newline at end of file +} diff --git a/app/Controllers/Organization/Site.php b/app/Controllers/Organization/SiteController.php similarity index 98% rename from app/Controllers/Organization/Site.php rename to app/Controllers/Organization/SiteController.php index 37ecf0b..9c0c2d6 100644 --- a/app/Controllers/Organization/Site.php +++ b/app/Controllers/Organization/SiteController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\Organization\SiteModel; -class Site extends BaseController { +class SiteController extends BaseController { use ResponseTrait; protected $db; @@ -75,4 +75,4 @@ class Site extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Organization/Workstation.php b/app/Controllers/Organization/WorkstationController.php similarity index 97% rename from app/Controllers/Organization/Workstation.php rename to app/Controllers/Organization/WorkstationController.php index 078c0c5..2414783 100644 --- a/app/Controllers/Organization/Workstation.php +++ b/app/Controllers/Organization/WorkstationController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\Organization\WorkstationModel; -class Workstation extends BaseController { +class WorkstationController extends BaseController { use ResponseTrait; protected $db; @@ -73,4 +73,4 @@ class Workstation extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/PatVisit.php b/app/Controllers/PatVisitController.php similarity index 98% rename from app/Controllers/PatVisit.php rename to app/Controllers/PatVisitController.php index 201a4fa..bea238f 100644 --- a/app/Controllers/PatVisit.php +++ b/app/Controllers/PatVisitController.php @@ -6,7 +6,7 @@ use App\Controllers\BaseController; use App\Models\PatVisit\PatVisitModel; use App\Models\PatVisit\PatVisitADTModel; -class PatVisit extends BaseController { +class PatVisitController extends BaseController { use ResponseTrait; protected $model; @@ -82,4 +82,4 @@ class PatVisit extends BaseController { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Patient/Patient.php b/app/Controllers/Patient/PatientController.php similarity index 99% rename from app/Controllers/Patient/Patient.php rename to app/Controllers/Patient/PatientController.php index 4f56469..ae63990 100644 --- a/app/Controllers/Patient/Patient.php +++ b/app/Controllers/Patient/PatientController.php @@ -6,7 +6,7 @@ use CodeIgniter\Controller; use App\Models\Patient\PatientModel; -class Patient extends Controller { +class PatientController extends Controller { use ResponseTrait; protected $db; @@ -214,4 +214,4 @@ class Patient extends Controller { return $this->failServerError('Something went wrong.'.$e->getMessage()); } } -} \ No newline at end of file +} diff --git a/app/Controllers/Dashboard.php b/app/Controllers/ResultController.php similarity index 94% rename from app/Controllers/Dashboard.php rename to app/Controllers/ResultController.php index b9c1a4f..f6ee148 100644 --- a/app/Controllers/Dashboard.php +++ b/app/Controllers/ResultController.php @@ -12,7 +12,7 @@ use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use CodeIgniter\Cookie\Cookie; -class Dashboard extends Controller { +class ResultController extends Controller { use ResponseTrait; public function index() { diff --git a/app/Controllers/Result.php b/app/Controllers/SampleController.php similarity index 94% rename from app/Controllers/Result.php rename to app/Controllers/SampleController.php index a906d6a..c02e318 100644 --- a/app/Controllers/Result.php +++ b/app/Controllers/SampleController.php @@ -12,7 +12,7 @@ use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use CodeIgniter\Cookie\Cookie; -class Result extends Controller { +class SampleController extends Controller { use ResponseTrait; public function index() { diff --git a/app/Controllers/Specimen/ContainerDef.php b/app/Controllers/Specimen/ContainerDefController.php similarity index 97% rename from app/Controllers/Specimen/ContainerDef.php rename to app/Controllers/Specimen/ContainerDefController.php index 0bb8c7b..4f1cb48 100644 --- a/app/Controllers/Specimen/ContainerDef.php +++ b/app/Controllers/Specimen/ContainerDefController.php @@ -6,7 +6,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Specimen\ContainerDefModel; -class ContainerDef extends BaseController { +class ContainerDefController extends BaseController { use ResponseTrait; protected $db; @@ -69,4 +69,4 @@ class ContainerDef extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Specimen/SpecimenCollection.php b/app/Controllers/Specimen/SpecimenCollectionController.php similarity index 97% rename from app/Controllers/Specimen/SpecimenCollection.php rename to app/Controllers/Specimen/SpecimenCollectionController.php index 2ca2962..69d38ac 100644 --- a/app/Controllers/Specimen/SpecimenCollection.php +++ b/app/Controllers/Specimen/SpecimenCollectionController.php @@ -6,7 +6,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Specimen\SpecimenCollectionModel; -class SpecimenCollection extends BaseController { +class SpecimenCollectionController extends BaseController { use ResponseTrait; protected $db; @@ -62,4 +62,4 @@ class SpecimenCollection extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Specimen/Specimen.php b/app/Controllers/Specimen/SpecimenController.php similarity index 97% rename from app/Controllers/Specimen/Specimen.php rename to app/Controllers/Specimen/SpecimenController.php index 8fb08ee..6eba3d0 100644 --- a/app/Controllers/Specimen/Specimen.php +++ b/app/Controllers/Specimen/SpecimenController.php @@ -6,7 +6,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Specimen\SpecimenModel; -class Specimen extends BaseController { +class SpecimenController extends BaseController { use ResponseTrait; protected $db; @@ -62,4 +62,4 @@ class Specimen extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Specimen/SpecimenPrep.php b/app/Controllers/Specimen/SpecimenPrepController.php similarity index 97% rename from app/Controllers/Specimen/SpecimenPrep.php rename to app/Controllers/Specimen/SpecimenPrepController.php index f694a3d..2238fe9 100644 --- a/app/Controllers/Specimen/SpecimenPrep.php +++ b/app/Controllers/Specimen/SpecimenPrepController.php @@ -6,7 +6,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Specimen\SpecimenPrepModel; -class SpecimenPrep extends BaseController { +class SpecimenPrepController extends BaseController { use ResponseTrait; protected $db; @@ -62,4 +62,4 @@ class SpecimenPrep extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Specimen/SpecimenStatus.php b/app/Controllers/Specimen/SpecimenStatusController.php similarity index 99% rename from app/Controllers/Specimen/SpecimenStatus.php rename to app/Controllers/Specimen/SpecimenStatusController.php index 58bd570..2444135 100644 --- a/app/Controllers/Specimen/SpecimenStatus.php +++ b/app/Controllers/Specimen/SpecimenStatusController.php @@ -62,4 +62,4 @@ class ContainerDef extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Test/TestMap.php b/app/Controllers/Test/TestMapController.php similarity index 97% rename from app/Controllers/Test/TestMap.php rename to app/Controllers/Test/TestMapController.php index 3ad323a..8d9677c 100644 --- a/app/Controllers/Test/TestMap.php +++ b/app/Controllers/Test/TestMapController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\Test\TestMapModel; -class TestMap extends BaseController { +class TestMapController extends BaseController { use ResponseTrait; protected $db; @@ -53,4 +53,4 @@ class TestMap extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Tests.php b/app/Controllers/Tests.php deleted file mode 100644 index 755b377..0000000 --- a/app/Controllers/Tests.php +++ /dev/null @@ -1,523 +0,0 @@ -db = \Config\Database::connect(); - $this->model = new \App\Models\Test\TestDefSiteModel; - $this->modelCal = new \App\Models\Test\TestDefCalModel; - $this->modelTech = new \App\Models\Test\TestDefTechModel; - $this->modelGrp = new \App\Models\Test\TestDefGrpModel; - $this->modelMap = new \App\Models\Test\TestMapModel; - $this->modelValueSet = new \App\Models\ValueSet\ValueSetModel; - - // Validation rules for main test definition - $this->rules = [ - 'TestSiteCode' => 'required|min_length[3]|max_length[6]', - 'TestSiteName' => 'required', - 'TestType' => 'required', - 'SiteID' => 'required' - ]; - } - - /** - * GET /v1/tests - * GET /v1/tests/site - * List all tests with optional filtering - */ - public function index() { - $siteId = $this->request->getGet('SiteID'); - $testType = $this->request->getGet('TestType'); - $visibleScr = $this->request->getGet('VisibleScr'); - $visibleRpt = $this->request->getGet('VisibleRpt'); - $keyword = $this->request->getGet('TestSiteName'); - - $builder = $this->db->table('testdefsite') - ->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType, - testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt, - 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'); - - if ($siteId) { - $builder->where('testdefsite.SiteID', $siteId); - } - - if ($testType) { - $builder->where('testdefsite.TestType', $testType); - } - - if ($visibleScr !== null) { - $builder->where('testdefsite.VisibleScr', $visibleScr); - } - - if ($visibleRpt !== null) { - $builder->where('testdefsite.VisibleRpt', $visibleRpt); - } - - if ($keyword) { - $builder->like('testdefsite.TestSiteName', $keyword); - } - - $rows = $builder->orderBy('testdefsite.SeqScr', 'ASC')->get()->getResultArray(); - - if (empty($rows)) { - return $this->respond([ 'status' => 'success', 'message' => "No data.", 'data' => [] ], 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) { - if (!$id) return $this->failValidationErrors('TestSiteID is required'); - - $row = $this->model->select("testdefsite.*, valueset.VValue as TypeCode, valueset.VDesc as TypeName") - ->join("valueset", "valueset.VID=testdefsite.TestType", "left") - ->where("testdefsite.TestSiteID", $id) - ->find($id); - - if (!$row) { - return $this->respond([ 'status' => 'success', 'message' => "No data.", 'data' => null ], 200); - } - - // Load related details based on TestType - $typeCode = $row['TypeCode'] ?? ''; - - if ($typeCode === 'CALC') { - // Load calculation details - $row['testdefcal'] = $this->db->table('testdefcal') - ->select('testdefcal.*, d.DisciplineName, dept.DepartmentName') - ->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left') - ->join('department dept', 'dept.DepartmentID=testdefcal.DepartmentID', 'left') - ->where('testdefcal.TestSiteID', $id) - ->where('testdefcal.EndDate IS NULL') - ->get()->getResultArray(); - - // Load test mappings - $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); - - } elseif ($typeCode === 'GROUP') { - // Load group members with test details - $row['testdefgrp'] = $this->db->table('testdefgrp') - ->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType, vs.VValue as MemberTypeCode') - ->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left') - ->join('valueset vs', 'vs.VID=t.TestType', 'left') - ->where('testdefgrp.TestSiteID', $id) - ->where('testdefgrp.EndDate IS NULL') - ->orderBy('testdefgrp.TestGrpID', 'ASC') - ->get()->getResultArray(); - - // Load test mappings - $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); - - } elseif ($typeCode === 'TITLE') { - // Load test mappings only for TITLE type - $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); - - } else { - // TEST or PARAM - load technical details - $row['testdeftech'] = $this->db->table('testdeftech') - ->select('testdeftech.*, d.DisciplineName, dept.DepartmentName') - ->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left') - ->join('department dept', 'dept.DepartmentID=testdeftech.DepartmentID', 'left') - ->where('testdeftech.TestSiteID', $id) - ->where('testdeftech.EndDate IS NULL') - ->get()->getResultArray(); - - // Load test mappings - $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); - } - - 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() { - $input = $this->request->getJSON(true); - - if (!$this->validateData($input, $this->rules)) { - return $this->failValidationErrors($this->validator->getErrors()); - } - - $this->db->transStart(); - - try { - // 1. Insert into Main Table (testdefsite) - $testSiteData = [ - 'SiteID' => $input['SiteID'], - 'TestSiteCode' => $input['TestSiteCode'], - 'TestSiteName' => $input['TestSiteName'], - 'TestType' => $input['TestType'], - 'Description' => $input['Description'] ?? null, - 'SeqScr' => $input['SeqScr'] ?? 0, - 'SeqRpt' => $input['SeqRpt'] ?? 0, - 'IndentLeft' => $input['IndentLeft'] ?? 0, - 'FontStyle' => $input['FontStyle'] ?? null, - 'VisibleScr' => $input['VisibleScr'] ?? 1, - 'VisibleRpt' => $input['VisibleRpt'] ?? 1, - 'CountStat' => $input['CountStat'] ?? 1, - 'StartDate' => $input['StartDate'] ?? date('Y-m-d H:i:s') - ]; - - $id = $this->model->insert($testSiteData); - if (!$id) { - throw new \Exception("Failed to insert main test definition"); - } - - // 2. Handle Details based on TestType - $this->handleDetails($id, $input, 'insert'); - - $this->db->transComplete(); - - if ($this->db->transStatus() === false) { - return $this->failServerError('Transaction failed'); - } - - return $this->respondCreated([ - 'status' => 'created', - 'message' => "Test created successfully", - 'data' => ['TestSiteId' => $id] - ]); - } catch (\Exception $e) { - $this->db->transRollback(); - return $this->failServerError('Something went wrong: ' . $e->getMessage()); - } - } - - /** - * PUT/PATCH /v1/tests/{id} - * PUT/PATCH /v1/tests/site/{id} - * Update existing test definition - */ - public function update($id = null) { - $input = $this->request->getJSON(true); - - // Determine ID - if (!$id && isset($input["TestSiteID"])) { $id = $input["TestSiteID"]; } - if (!$id) { return $this->failValidationErrors('TestSiteID is required.'); } - - // Verify record exists - $existing = $this->model->find($id); - if (!$existing) { - return $this->failNotFound('Test not found'); - } - - $this->db->transStart(); - - try { - // 1. Update Main Table - $testSiteData = []; - $allowedUpdateFields = ['TestSiteCode', 'TestSiteName', 'TestType', 'Description', - 'SeqScr', 'SeqRpt', 'IndentLeft', 'FontStyle', - 'VisibleScr', 'VisibleRpt', 'CountStat', 'StartDate']; - - foreach ($allowedUpdateFields as $field) { - if (isset($input[$field])) { - $testSiteData[$field] = $input[$field]; - } - } - - if (!empty($testSiteData)) { - $this->model->update($id, $testSiteData); - } - - // 2. Handle Details - $this->handleDetails($id, $input, 'update'); - - $this->db->transComplete(); - - if ($this->db->transStatus() === false) { - return $this->failServerError('Transaction failed'); - } - - return $this->respond([ - 'status' => 'success', - 'message' => "Test updated successfully", - 'data' => ['TestSiteId' => $id] - ]); - } catch (\Exception $e) { - $this->db->transRollback(); - return $this->failServerError('Something went wrong: ' . $e->getMessage()); - } - } - - /** - * DELETE /v1/tests/{id} - * DELETE /v1/tests/site/{id} - * Soft delete test by setting EndDate - */ - public function delete($id = null) { - $input = $this->request->getJSON(true); - - // Determine ID - if (!$id && isset($input["TestSiteID"])) { $id = $input["TestSiteID"]; } - if (!$id) { return $this->failValidationErrors('TestSiteID is required.'); } - - // Verify record exists - $existing = $this->model->find($id); - if (!$existing) { - return $this->failNotFound('Test not found'); - } - - // Check if already disabled - if (!empty($existing['EndDate'])) { - return $this->failValidationErrors('Test is already disabled'); - } - - $this->db->transStart(); - - try { - $now = date('Y-m-d H:i:s'); - - // 1. Soft delete main record - $this->model->update($id, ['EndDate' => $now]); - - // 2. Get TestType to handle related records - $testType = $existing['TestType']; - $vs = $this->modelValueSet->find($testType); - $typeCode = $vs['VValue'] ?? ''; - - // 3. Soft delete related records based on TestType - if ($typeCode === 'CALC') { - $this->db->table('testdefcal') - ->where('TestSiteID', $id) - ->update(['EndDate' => $now]); - } elseif ($typeCode === 'GROUP') { - $this->db->table('testdefgrp') - ->where('TestSiteID', $id) - ->update(['EndDate' => $now]); - } elseif (in_array($typeCode, ['TEST', 'PARAM'])) { - $this->db->table('testdeftech') - ->where('TestSiteID', $id) - ->update(['EndDate' => $now]); - } - - // 4. Soft delete test mappings - $this->db->table('testmap') - ->where('TestSiteID', $id) - ->update(['EndDate' => $now]); - - $this->db->transComplete(); - - if ($this->db->transStatus() === false) { - return $this->failServerError('Transaction failed'); - } - - return $this->respond([ - 'status' => 'success', - 'message' => "Test disabled successfully", - 'data' => ['TestSiteId' => $id, 'EndDate' => $now] - ]); - } catch (\Exception $e) { - $this->db->transRollback(); - return $this->failServerError('Something went wrong: ' . $e->getMessage()); - } - } - - /** - * Helper to handle inserting/updating sub-tables based on TestType - */ - private function handleDetails($testSiteID, $input, $action) { - $testTypeID = $input['TestType'] ?? null; - - // If update and TestType not in payload, fetch from DB - if (!$testTypeID && $action === 'update') { - $existing = $this->model->find($testSiteID); - $testTypeID = $existing['TestType'] ?? null; - } - - if (!$testTypeID) return; - - // Get Type Code (TEST, PARAM, CALC, GROUP, TITLE) - $vs = $this->modelValueSet->find($testTypeID); - $typeCode = $vs['VValue'] ?? ''; - - // Get details data from input - $details = $input['details'] ?? $input; - $details['TestSiteID'] = $testSiteID; - $details['SiteID'] = $input['SiteID'] ?? 1; - - switch ($typeCode) { - case 'CALC': - $this->saveCalcDetails($testSiteID, $details, $action); - break; - - case 'GROUP': - $this->saveGroupDetails($testSiteID, $details, $input, $action); - break; - - 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'])) { - $this->saveTestMap($testSiteID, $input['testmap'], $action); - } - break; - - case 'TEST': - case 'PARAM': - default: - $this->saveTechDetails($testSiteID, $details, $action, $typeCode); - break; - } - - // Save test mappings for TEST and CALC types as well - if (in_array($typeCode, ['TEST', 'CALC']) && isset($input['testmap']) && is_array($input['testmap'])) { - $this->saveTestMap($testSiteID, $input['testmap'], $action); - } - } - - /** - * Save technical details for TEST and PARAM types - */ - private function saveTechDetails($testSiteID, $data, $action, $typeCode) { - $techData = [ - 'TestSiteID' => $testSiteID, - 'DisciplineID' => $data['DisciplineID'] ?? null, - 'DepartmentID' => $data['DepartmentID'] ?? null, - 'ResultType' => $data['ResultType'] ?? null, - 'RefType' => $data['RefType'] ?? null, - 'VSet' => $data['VSet'] ?? null, - 'ReqQty' => $data['ReqQty'] ?? null, - 'ReqQtyUnit' => $data['ReqQtyUnit'] ?? null, - 'Unit1' => $data['Unit1'] ?? null, - 'Factor' => $data['Factor'] ?? null, - 'Unit2' => $data['Unit2'] ?? null, - 'Decimal' => $data['Decimal'] ?? 2, - 'CollReq' => $data['CollReq'] ?? null, - 'Method' => $data['Method'] ?? null, - 'ExpectedTAT' => $data['ExpectedTAT'] ?? null - ]; - - if ($action === 'update') { - $exists = $this->db->table('testdeftech') - ->where('TestSiteID', $testSiteID) - ->where('EndDate IS NULL') - ->get()->getRowArray(); - - if ($exists) { - $this->modelTech->update($exists['TestTechID'], $techData); - } else { - $this->modelTech->insert($techData); - } - } else { - $this->modelTech->insert($techData); - } - } - - /** - * Save calculation details for CALC type - */ - private function saveCalcDetails($testSiteID, $data, $action) { - $calcData = [ - 'TestSiteID' => $testSiteID, - 'DisciplineID' => $data['DisciplineID'] ?? null, - 'DepartmentID' => $data['DepartmentID'] ?? null, - 'FormulaInput' => $data['FormulaInput'] ?? null, - 'FormulaCode' => $data['FormulaCode'] ?? $data['Formula'] ?? null, - 'RefType' => $data['RefType'] ?? 'NMRC', - 'Unit1' => $data['Unit1'] ?? $data['ResultUnit'] ?? null, - 'Factor' => $data['Factor'] ?? null, - 'Unit2' => $data['Unit2'] ?? null, - 'Decimal' => $data['Decimal'] ?? 2, - 'Method' => $data['Method'] ?? null - ]; - - if ($action === 'update') { - $exists = $this->db->table('testdefcal') - ->where('TestSiteID', $testSiteID) - ->where('EndDate IS NULL') - ->get()->getRowArray(); - - if ($exists) { - $this->modelCal->update($exists['TestCalID'], $calcData); - } else { - $this->modelCal->insert($calcData); - } - } else { - $this->modelCal->insert($calcData); - } - } - - /** - * Save group details for GROUP type - */ - private function saveGroupDetails($testSiteID, $data, $input, $action) { - if ($action === 'update') { - // Soft delete existing members - $this->db->table('testdefgrp') - ->where('TestSiteID', $testSiteID) - ->update(['EndDate' => date('Y-m-d H:i:s')]); - } - - // Get members from details or input - $members = $data['members'] ?? ($input['Members'] ?? []); - - if (is_array($members)) { - foreach ($members as $m) { - $memberID = is_array($m) ? ($m['Member'] ?? ($m['TestSiteID'] ?? null)) : $m; - if ($memberID) { - $this->modelGrp->insert([ - 'TestSiteID' => $testSiteID, - 'Member' => $memberID - ]); - } - } - } - } - - /** - * Save test mappings - */ - private function saveTestMap($testSiteID, $mappings, $action) { - if ($action === 'update') { - // Soft delete existing mappings - $this->db->table('testmap') - ->where('TestSiteID', $testSiteID) - ->update(['EndDate' => date('Y-m-d H:i:s')]); - } - - if (is_array($mappings)) { - foreach ($mappings as $map) { - $mapData = [ - 'TestSiteID' => $testSiteID, - 'HostType' => $map['HostType'] ?? null, - 'HostID' => $map['HostID'] ?? null, - 'HostDataSource' => $map['HostDataSource'] ?? null, - 'HostTestCode' => $map['HostTestCode'] ?? null, - 'HostTestName' => $map['HostTestName'] ?? null, - 'ClientType' => $map['ClientType'] ?? null, - 'ClientID' => $map['ClientID'] ?? null, - 'ClientDataSource' => $map['ClientDataSource'] ?? null, - 'ConDefID' => $map['ConDefID'] ?? null, - 'ClientTestCode' => $map['ClientTestCode'] ?? null, - 'ClientTestName' => $map['ClientTestName'] ?? null - ]; - $this->modelMap->insert($mapData); - } - } - } -} diff --git a/app/Controllers/TestsController.php b/app/Controllers/TestsController.php new file mode 100644 index 0000000..b43ba6c --- /dev/null +++ b/app/Controllers/TestsController.php @@ -0,0 +1,741 @@ +db = \Config\Database::connect(); + $this->model = new \App\Models\Test\TestDefSiteModel; + $this->modelCal = new \App\Models\Test\TestDefCalModel; + $this->modelTech = new \App\Models\Test\TestDefTechModel; + $this->modelGrp = new \App\Models\Test\TestDefGrpModel; + $this->modelMap = new \App\Models\Test\TestMapModel; + $this->modelValueSet = new \App\Models\ValueSet\ValueSetModel; + $this->modelRefNum = new \App\Models\RefRange\RefNumModel; + $this->modelRefTxt = new \App\Models\RefRange\RefTxtModel; + + // Validation rules for main test definition + $this->rules = [ + 'TestSiteCode' => 'required|min_length[3]|max_length[6]', + 'TestSiteName' => 'required', + 'TestType' => 'required', + 'SiteID' => 'required' + ]; + } + + /** + * GET /v1/tests + * GET /v1/tests/site + * List all tests with optional filtering + */ + public function index() + { + $siteId = $this->request->getGet('SiteID'); + $testType = $this->request->getGet('TestType'); + $visibleScr = $this->request->getGet('VisibleScr'); + $visibleRpt = $this->request->getGet('VisibleRpt'); + $keyword = $this->request->getGet('TestSiteName'); + + $builder = $this->db->table('testdefsite') + ->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType, + testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt, + 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'); + + if ($siteId) { + $builder->where('testdefsite.SiteID', $siteId); + } + + if ($testType) { + $builder->where('testdefsite.TestType', $testType); + } + + if ($visibleScr !== null) { + $builder->where('testdefsite.VisibleScr', $visibleScr); + } + + if ($visibleRpt !== null) { + $builder->where('testdefsite.VisibleRpt', $visibleRpt); + } + + if ($keyword) { + $builder->like('testdefsite.TestSiteName', $keyword); + } + + $rows = $builder->orderBy('testdefsite.SeqScr', 'ASC')->get()->getResultArray(); + + if (empty($rows)) { + return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => []], 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) + { + if (!$id) + return $this->failValidationErrors('TestSiteID is required'); + + $row = $this->model->select("testdefsite.*, valueset.VValue as TypeCode, valueset.VDesc as TypeName") + ->join("valueset", "valueset.VID=testdefsite.TestType", "left") + ->where("testdefsite.TestSiteID", $id) + ->find($id); + + if (!$row) { + return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => null], 200); + } + + // Load related details based on TestType + $typeCode = $row['TypeCode'] ?? ''; + + if ($typeCode === 'CALC') { + // Load calculation details + $row['testdefcal'] = $this->db->table('testdefcal') + ->select('testdefcal.*, d.DisciplineName, dept.DepartmentName') + ->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left') + ->join('department dept', 'dept.DepartmentID=testdefcal.DepartmentID', 'left') + ->where('testdefcal.TestSiteID', $id) + ->where('testdefcal.EndDate IS NULL') + ->get()->getResultArray(); + + // Load test mappings + $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); + + } elseif ($typeCode === 'GROUP') { + // Load group members with test details + $row['testdefgrp'] = $this->db->table('testdefgrp') + ->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType, vs.VValue as MemberTypeCode') + ->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left') + ->join('valueset vs', 'vs.VID=t.TestType', 'left') + ->where('testdefgrp.TestSiteID', $id) + ->where('testdefgrp.EndDate IS NULL') + ->orderBy('testdefgrp.TestGrpID', 'ASC') + ->get()->getResultArray(); + + // Load test mappings + $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); + + } elseif ($typeCode === 'TITLE') { + // Load test mappings only for TITLE type + $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); + + } else { + // TEST or PARAM - load technical details + $row['testdeftech'] = $this->db->table('testdeftech') + ->select('testdeftech.*, d.DisciplineName, dept.DepartmentName') + ->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left') + ->join('department dept', 'dept.DepartmentID=testdeftech.DepartmentID', 'left') + ->where('testdeftech.TestSiteID', $id) + ->where('testdeftech.EndDate IS NULL') + ->get()->getResultArray(); + + // Load test mappings + $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); + + // Load refnum/reftxt based on RefType + if (!empty($row['testdeftech'])) { + $techData = $row['testdeftech'][0]; + $refType = (int) $techData['RefType']; + + // Load refnum for NMRC type (RefType = 1) + if ($refType === 1) { + $refnumData = $this->modelRefNum + ->where('TestSiteID', $id) + ->where('EndDate IS NULL') + ->orderBy('Display', 'ASC') + ->findAll(); + + // Add VValue for display + $row['refnum'] = array_map(function ($r) { + return [ + 'RefNumID' => $r['RefNumID'], + 'NumRefType' => (int) $r['NumRefType'], + 'NumRefTypeVValue' => $this->getVValue(46, $r['NumRefType']), + 'RangeType' => (int) $r['RangeType'], + 'RangeTypeVValue' => $this->getVValue(45, $r['RangeType']), + 'Sex' => (int) $r['Sex'], + 'SexVValue' => $this->getVValue(3, $r['Sex']), + '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, + 'Flag' => $r['Flag'] + ]; + }, $refnumData ?? []); + + $row['numRefTypeOptions'] = $this->getValuesetOptions(46); + $row['rangeTypeOptions'] = $this->getValuesetOptions(45); + } + + // Load reftxt for TEXT type (RefType = 2) + if ($refType === 2) { + $reftxtData = $this->modelRefTxt + ->where('TestSiteID', $id) + ->where('EndDate IS NULL') + ->orderBy('RefTxtID', 'ASC') + ->findAll(); + + $row['reftxt'] = array_map(function ($r) { + return [ + 'RefTxtID' => $r['RefTxtID'], + 'TxtRefType' => (int) $r['TxtRefType'], + 'TxtRefTypeVValue' => $this->getVValue(47, $r['TxtRefType']), + 'Sex' => (int) $r['Sex'], + 'SexVValue' => $this->getVValue(3, $r['Sex']), + 'AgeStart' => (int) $r['AgeStart'], + 'AgeEnd' => (int) $r['AgeEnd'], + 'RefTxt' => $r['RefTxt'], + 'Flag' => $r['Flag'] + ]; + }, $reftxtData ?? []); + + $row['txtRefTypeOptions'] = $this->getValuesetOptions(47); + } + } + } + + // Include valueset options for dropdowns + $row['refTypeOptions'] = $this->getValuesetOptions(self::VALUESET_REF_TYPE); + $row['sexOptions'] = $this->getValuesetOptions(self::VALUESET_SEX); + $row['mathSignOptions'] = $this->getValuesetOptions(self::VALUESET_MATH_SIGN); + + 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() + { + $input = $this->request->getJSON(true); + + if (!$this->validateData($input, $this->rules)) { + return $this->failValidationErrors($this->validator->getErrors()); + } + + $this->db->transStart(); + + try { + // 1. Insert into Main Table (testdefsite) + $testSiteData = [ + 'SiteID' => $input['SiteID'], + 'TestSiteCode' => $input['TestSiteCode'], + 'TestSiteName' => $input['TestSiteName'], + 'TestType' => $input['TestType'], + 'Description' => $input['Description'] ?? null, + 'SeqScr' => $input['SeqScr'] ?? 0, + 'SeqRpt' => $input['SeqRpt'] ?? 0, + 'IndentLeft' => $input['IndentLeft'] ?? 0, + 'FontStyle' => $input['FontStyle'] ?? null, + 'VisibleScr' => $input['VisibleScr'] ?? 1, + 'VisibleRpt' => $input['VisibleRpt'] ?? 1, + 'CountStat' => $input['CountStat'] ?? 1, + 'StartDate' => $input['StartDate'] ?? date('Y-m-d H:i:s') + ]; + + $id = $this->model->insert($testSiteData); + if (!$id) { + throw new \Exception("Failed to insert main test definition"); + } + + // 2. Handle Details based on TestType + $this->handleDetails($id, $input, 'insert'); + + $this->db->transComplete(); + + if ($this->db->transStatus() === false) { + return $this->failServerError('Transaction failed'); + } + + return $this->respondCreated([ + 'status' => 'created', + 'message' => "Test created successfully", + 'data' => ['TestSiteId' => $id] + ]); + } catch (\Exception $e) { + $this->db->transRollback(); + return $this->failServerError('Something went wrong: ' . $e->getMessage()); + } + } + + /** + * PUT/PATCH /v1/tests/{id} + * PUT/PATCH /v1/tests/site/{id} + * Update existing test definition + */ + public function update($id = null) + { + $input = $this->request->getJSON(true); + + // Determine ID + if (!$id && isset($input["TestSiteID"])) { + $id = $input["TestSiteID"]; + } + if (!$id) { + return $this->failValidationErrors('TestSiteID is required.'); + } + + // Verify record exists + $existing = $this->model->find($id); + if (!$existing) { + return $this->failNotFound('Test not found'); + } + + $this->db->transStart(); + + try { + // 1. Update Main Table + $testSiteData = []; + $allowedUpdateFields = [ + 'TestSiteCode', + 'TestSiteName', + 'TestType', + 'Description', + 'SeqScr', + 'SeqRpt', + 'IndentLeft', + 'FontStyle', + 'VisibleScr', + 'VisibleRpt', + 'CountStat', + 'StartDate' + ]; + + foreach ($allowedUpdateFields as $field) { + if (isset($input[$field])) { + $testSiteData[$field] = $input[$field]; + } + } + + if (!empty($testSiteData)) { + $this->model->update($id, $testSiteData); + } + + // 2. Handle Details + $this->handleDetails($id, $input, 'update'); + + $this->db->transComplete(); + + if ($this->db->transStatus() === false) { + return $this->failServerError('Transaction failed'); + } + + return $this->respond([ + 'status' => 'success', + 'message' => "Test updated successfully", + 'data' => ['TestSiteId' => $id] + ]); + } catch (\Exception $e) { + $this->db->transRollback(); + return $this->failServerError('Something went wrong: ' . $e->getMessage()); + } + } + + /** + * DELETE /v1/tests/{id} + * DELETE /v1/tests/site/{id} + * Soft delete test by setting EndDate + */ + public function delete($id = null) + { + $input = $this->request->getJSON(true); + + // Determine ID + if (!$id && isset($input["TestSiteID"])) { + $id = $input["TestSiteID"]; + } + if (!$id) { + return $this->failValidationErrors('TestSiteID is required.'); + } + + // Verify record exists + $existing = $this->model->find($id); + if (!$existing) { + return $this->failNotFound('Test not found'); + } + + // Check if already disabled + if (!empty($existing['EndDate'])) { + return $this->failValidationErrors('Test is already disabled'); + } + + $this->db->transStart(); + + try { + $now = date('Y-m-d H:i:s'); + + // 1. Soft delete main record + $this->model->update($id, ['EndDate' => $now]); + + // 2. Get TestType to handle related records + $testType = $existing['TestType']; + $vs = $this->modelValueSet->find($testType); + $typeCode = $vs['VValue'] ?? ''; + + // 3. Soft delete related records based on TestType + if ($typeCode === 'CALC') { + $this->db->table('testdefcal') + ->where('TestSiteID', $id) + ->update(['EndDate' => $now]); + } elseif ($typeCode === 'GROUP') { + $this->db->table('testdefgrp') + ->where('TestSiteID', $id) + ->update(['EndDate' => $now]); + } elseif (in_array($typeCode, ['TEST', 'PARAM'])) { + $this->db->table('testdeftech') + ->where('TestSiteID', $id) + ->update(['EndDate' => $now]); + + // Soft delete refnum and reftxt records + $this->modelRefNum->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') + ->where('TestSiteID', $id) + ->update(['EndDate' => $now]); + + $this->db->transComplete(); + + if ($this->db->transStatus() === false) { + return $this->failServerError('Transaction failed'); + } + + return $this->respond([ + 'status' => 'success', + 'message' => "Test disabled successfully", + 'data' => ['TestSiteId' => $id, 'EndDate' => $now] + ]); + } catch (\Exception $e) { + $this->db->transRollback(); + return $this->failServerError('Something went wrong: ' . $e->getMessage()); + } + } + + /** + * 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) + { + $testTypeID = $input['TestType'] ?? null; + + // If update and TestType not in payload, fetch from DB + if (!$testTypeID && $action === 'update') { + $existing = $this->model->find($testSiteID); + $testTypeID = $existing['TestType'] ?? null; + } + + if (!$testTypeID) + return; + + // Get Type Code (TEST, PARAM, CALC, GROUP, TITLE) + $vs = $this->modelValueSet->find($testTypeID); + $typeCode = $vs['VValue'] ?? ''; + + // Get details data from input + $details = $input['details'] ?? $input; + $details['TestSiteID'] = $testSiteID; + $details['SiteID'] = $input['SiteID'] ?? 1; + + switch ($typeCode) { + case 'CALC': + $this->saveCalcDetails($testSiteID, $details, $action); + break; + + case 'GROUP': + $this->saveGroupDetails($testSiteID, $details, $input, $action); + break; + + 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'])) { + $this->saveTestMap($testSiteID, $input['testmap'], $action); + } + break; + + case 'TEST': + case 'PARAM': + default: + $this->saveTechDetails($testSiteID, $details, $action, $typeCode); + + // Save refnum/reftxt for TEST/PARAM types + if (in_array($typeCode, ['TEST', 'PARAM']) && isset($details['RefType'])) { + $refType = (int) $details['RefType']; + + // Save refnum for NMRC type (RefType = 1) + if ($refType === 1 && isset($input['refnum']) && is_array($input['refnum'])) { + $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'])) { + $this->saveRefTxtRanges($testSiteID, $input['reftxt'], $action, $input['SiteID'] ?? 1); + } + } + break; + } + + // Save test mappings for TEST and CALC types as well + if (in_array($typeCode, ['TEST', 'CALC']) && isset($input['testmap']) && is_array($input['testmap'])) { + $this->saveTestMap($testSiteID, $input['testmap'], $action); + } + } + + /** + * Save technical details for TEST and PARAM types + */ + private function saveTechDetails($testSiteID, $data, $action, $typeCode) + { + $techData = [ + 'TestSiteID' => $testSiteID, + 'DisciplineID' => $data['DisciplineID'] ?? null, + 'DepartmentID' => $data['DepartmentID'] ?? null, + 'ResultType' => $data['ResultType'] ?? null, + 'RefType' => $data['RefType'] ?? null, + 'VSet' => $data['VSet'] ?? null, + 'ReqQty' => $data['ReqQty'] ?? null, + 'ReqQtyUnit' => $data['ReqQtyUnit'] ?? null, + 'Unit1' => $data['Unit1'] ?? null, + 'Factor' => $data['Factor'] ?? null, + 'Unit2' => $data['Unit2'] ?? null, + 'Decimal' => $data['Decimal'] ?? 2, + 'CollReq' => $data['CollReq'] ?? null, + 'Method' => $data['Method'] ?? null, + 'ExpectedTAT' => $data['ExpectedTAT'] ?? null + ]; + + if ($action === 'update') { + $exists = $this->db->table('testdeftech') + ->where('TestSiteID', $testSiteID) + ->where('EndDate IS NULL') + ->get()->getRowArray(); + + if ($exists) { + $this->modelTech->update($exists['TestTechID'], $techData); + } else { + $this->modelTech->insert($techData); + } + } else { + $this->modelTech->insert($techData); + } + } + + /** + * Save refnum ranges for NMRC type + */ + private function saveRefNumRanges($testSiteID, $ranges, $action, $siteID) + { + if ($action === 'update') { + $this->modelRefNum->where('TestSiteID', $testSiteID) + ->set('EndDate', date('Y-m-d H:i:s')) + ->update(); + } + + foreach ($ranges as $index => $range) { + $this->modelRefNum->insert([ + 'TestSiteID' => $testSiteID, + 'SiteID' => $siteID, + 'NumRefType' => (int) $range['NumRefType'], + 'RangeType' => (int) $range['RangeType'], + 'Sex' => (int) $range['Sex'], + 'AgeStart' => (int) ($range['AgeStart'] ?? 0), + 'AgeEnd' => (int) ($range['AgeEnd'] ?? 150), + 'LowSign' => !empty($range['LowSign']) ? (int) $range['LowSign'] : null, + 'Low' => !empty($range['Low']) ? (int) $range['Low'] : null, + 'HighSign' => !empty($range['HighSign']) ? (int) $range['HighSign'] : null, + 'High' => !empty($range['High']) ? (int) $range['High'] : null, + 'Flag' => $range['Flag'] ?? null, + 'Display' => $index, + 'CreateDate' => date('Y-m-d H:i:s') + ]); + } + } + + /** + * Save reftxt ranges for TEXT type + */ + private function saveRefTxtRanges($testSiteID, $ranges, $action, $siteID) + { + if ($action === 'update') { + $this->modelRefTxt->where('TestSiteID', $testSiteID) + ->set('EndDate', date('Y-m-d H:i:s')) + ->update(); + } + + foreach ($ranges as $range) { + $this->modelRefTxt->insert([ + 'TestSiteID' => $testSiteID, + 'SiteID' => $siteID, + 'TxtRefType' => (int) $range['TxtRefType'], + 'Sex' => (int) $range['Sex'], + 'AgeStart' => (int) ($range['AgeStart'] ?? 0), + 'AgeEnd' => (int) ($range['AgeEnd'] ?? 150), + 'RefTxt' => $range['RefTxt'] ?? '', + 'Flag' => $range['Flag'] ?? null, + 'CreateDate' => date('Y-m-d H:i:s') + ]); + } + } + + /** + * Save calculation details for CALC type + */ + private function saveCalcDetails($testSiteID, $data, $action) + { + $calcData = [ + 'TestSiteID' => $testSiteID, + 'DisciplineID' => $data['DisciplineID'] ?? null, + 'DepartmentID' => $data['DepartmentID'] ?? null, + 'FormulaInput' => $data['FormulaInput'] ?? null, + 'FormulaCode' => $data['FormulaCode'] ?? $data['Formula'] ?? null, + 'RefType' => $data['RefType'] ?? 'NMRC', + 'Unit1' => $data['Unit1'] ?? $data['ResultUnit'] ?? null, + 'Factor' => $data['Factor'] ?? null, + 'Unit2' => $data['Unit2'] ?? null, + 'Decimal' => $data['Decimal'] ?? 2, + 'Method' => $data['Method'] ?? null + ]; + + if ($action === 'update') { + $exists = $this->db->table('testdefcal') + ->where('TestSiteID', $testSiteID) + ->where('EndDate IS NULL') + ->get()->getRowArray(); + + if ($exists) { + $this->modelCal->update($exists['TestCalID'], $calcData); + } else { + $this->modelCal->insert($calcData); + } + } else { + $this->modelCal->insert($calcData); + } + } + + /** + * Save group details for GROUP type + */ + private function saveGroupDetails($testSiteID, $data, $input, $action) + { + if ($action === 'update') { + // Soft delete existing members + $this->db->table('testdefgrp') + ->where('TestSiteID', $testSiteID) + ->update(['EndDate' => date('Y-m-d H:i:s')]); + } + + // Get members from details or input + $members = $data['members'] ?? ($input['Members'] ?? []); + + if (is_array($members)) { + foreach ($members as $m) { + $memberID = is_array($m) ? ($m['Member'] ?? ($m['TestSiteID'] ?? null)) : $m; + if ($memberID) { + $this->modelGrp->insert([ + 'TestSiteID' => $testSiteID, + 'Member' => $memberID + ]); + } + } + } + } + + /** + * Save test mappings + */ + private function saveTestMap($testSiteID, $mappings, $action) + { + if ($action === 'update') { + // Soft delete existing mappings + $this->db->table('testmap') + ->where('TestSiteID', $testSiteID) + ->update(['EndDate' => date('Y-m-d H:i:s')]); + } + + if (is_array($mappings)) { + foreach ($mappings as $map) { + $mapData = [ + 'TestSiteID' => $testSiteID, + 'HostType' => $map['HostType'] ?? null, + 'HostID' => $map['HostID'] ?? null, + 'HostDataSource' => $map['HostDataSource'] ?? null, + 'HostTestCode' => $map['HostTestCode'] ?? null, + 'HostTestName' => $map['HostTestName'] ?? null, + 'ClientType' => $map['ClientType'] ?? null, + 'ClientID' => $map['ClientID'] ?? null, + 'ClientDataSource' => $map['ClientDataSource'] ?? null, + 'ConDefID' => $map['ConDefID'] ?? null, + 'ClientTestCode' => $map['ClientTestCode'] ?? null, + 'ClientTestName' => $map['ClientTestName'] ?? null + ]; + $this->modelMap->insert($mapData); + } + } + } +} diff --git a/app/Controllers/ValueSet/ValueSet.php b/app/Controllers/ValueSet/ValueSetController.php similarity index 98% rename from app/Controllers/ValueSet/ValueSet.php rename to app/Controllers/ValueSet/ValueSetController.php index 1b375c2..6e80bcc 100644 --- a/app/Controllers/ValueSet/ValueSet.php +++ b/app/Controllers/ValueSet/ValueSetController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\ValueSet\ValueSetModel; -class ValueSet extends BaseController { +class ValueSetController extends BaseController { use ResponseTrait; protected $db; @@ -94,4 +94,4 @@ class ValueSet extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/ValueSet/ValueSetDef.php b/app/Controllers/ValueSet/ValueSetDefController.php similarity index 98% rename from app/Controllers/ValueSet/ValueSetDef.php rename to app/Controllers/ValueSet/ValueSetDefController.php index 48d00b2..a3e5145 100644 --- a/app/Controllers/ValueSet/ValueSetDef.php +++ b/app/Controllers/ValueSet/ValueSetDefController.php @@ -5,7 +5,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\ValueSet\ValueSetDefModel; -class ValueSetDef extends BaseController { +class ValueSetDefController extends BaseController { use ResponseTrait; protected $db; @@ -70,4 +70,4 @@ class ValueSetDef extends BaseController { } } -} \ No newline at end of file +} diff --git a/app/Controllers/Zones.php b/app/Controllers/ZonesController.php similarity index 98% rename from app/Controllers/Zones.php rename to app/Controllers/ZonesController.php index 84de855..86db321 100644 --- a/app/Controllers/Zones.php +++ b/app/Controllers/ZonesController.php @@ -6,7 +6,7 @@ use CodeIgniter\API\ResponseTrait; use App\Controllers\BaseController; use App\Models\SyncCRM\ZonesModel; -class Zones extends BaseController { +class ZonesController extends BaseController { use ResponseTrait; protected $model; @@ -92,4 +92,4 @@ class Zones extends BaseController { } } -*/ \ No newline at end of file +*/ diff --git a/app/Models/RefRange/RefNumModel.php b/app/Models/RefRange/RefNumModel.php index 6bc1e82..e33db9c 100644 --- a/app/Models/RefRange/RefNumModel.php +++ b/app/Models/RefRange/RefNumModel.php @@ -4,17 +4,36 @@ namespace App\Models\RefRange; use App\Models\BaseModel; -class RefNumModel extends BaseModel { +class RefNumModel extends BaseModel +{ protected $table = 'refnum'; protected $primaryKey = 'RefNumID'; - protected $allowedFields = ['SiteID', 'TestSiteID', 'SpcType', 'Sex', 'AgeStart', 'AgeEnd', - 'CriticalLow', 'Low', 'High', 'CriticalHigh', - 'CreateDate', 'EndDate']; - + protected $allowedFields = [ + 'SiteID', + 'TestSiteID', + 'SpcType', + 'Sex', + 'Criteria', + 'AgeStart', + 'AgeEnd', + 'NumRefType', + 'RangeType', + 'LowSign', + 'Low', + 'HighSign', + 'High', + 'Display', + 'Flag', + 'Interpretation', + 'Notes', + 'CreateDate', + 'StartDate', + 'EndDate' + ]; + protected $useTimestamps = true; protected $createdField = 'CreateDate'; protected $updatedField = ''; protected $useSoftDeletes = true; protected $deletedField = "EndDate"; - -} \ No newline at end of file +} diff --git a/app/Models/RefRange/RefTxtModel.php b/app/Models/RefRange/RefTxtModel.php new file mode 100644 index 0000000..054df49 --- /dev/null +++ b/app/Models/RefRange/RefTxtModel.php @@ -0,0 +1,33 @@ + -