diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 5fe5c9b..d933534 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -230,6 +230,13 @@ $routes->group('api', function ($routes) { // Specimen $routes->group('specimen', function ($routes) { + // Container aliases - 'container' and 'containerdef' both point to ContainerDefController + $routes->group('container', function ($routes) { + $routes->get('/', 'Specimen\ContainerDefController::index'); + $routes->get('(:num)', 'Specimen\ContainerDefController::show/$1'); + $routes->post('/', 'Specimen\ContainerDefController::create'); + $routes->patch('/', 'Specimen\ContainerDefController::update'); + }); $routes->group('containerdef', function ($routes) { $routes->get('/', 'Specimen\ContainerDefController::index'); $routes->get('(:num)', 'Specimen\ContainerDefController::show/$1'); diff --git a/app/Database/Seeds/AreaGeoSeeder.php b/app/Database/Seeds/AreaGeoSeeder.php index ba5d47c..c7cdf38 100644 --- a/app/Database/Seeds/AreaGeoSeeder.php +++ b/app/Database/Seeds/AreaGeoSeeder.php @@ -3,23 +3,36 @@ namespace App\Database\Seeds; use CodeIgniter\Database\Seeder; +use CodeIgniter\HTTP\CURLRequest; class AreaGeoSeeder extends Seeder { /** - * External database configuration (same server) + * API configuration for fetching zones data */ - protected string $externalDbName = 'crm_v2'; + protected string $apiUrl = 'https://your-api-domain.com/api/zones'; public function run() { - // Query external database directly using database.table syntax - $externalData = $this->db->query( - "SELECT zonecode, zoneclass, zonename, parentzoneid FROM {$this->externalDbName}.zones ORDER BY zoneid ASC" - )->getResultArray(); + // Fetch data from external API + $options = [ + 'baseURI' => $this->apiUrl, + 'timeout' => 30, + ]; + + $client = new CURLRequest($options); + + $response = $client->get(''); + + if ($response->getStatusCode() !== 200) { + echo "Failed to fetch data from API. Status: " . $response->getStatusCode() . "\n"; + return; + } + + $externalData = $response->getJSON(true); if (empty($externalData)) { - echo "No data found in external database.\n"; + echo "No data found from API.\n"; return; } @@ -27,10 +40,10 @@ class AreaGeoSeeder extends Seeder $data = []; foreach ($externalData as $row) { $data[] = [ - 'AreaCode' => $row['zonecode'], - 'Class' => $row['zoneclass'], - 'AreaName' => $row['zonename'], - 'Parent' => $row['parentzoneid'], + 'AreaCode' => $row['zonecode'] ?? null, + 'Class' => $row['zoneclass'] ?? null, + 'AreaName' => str_replace('_', ' ', $row['zonename'] ?? ''), + 'Parent' => $row['parentzoneid'] ?? null, ]; } diff --git a/app/Database/Seeds/DummySeeder.php b/app/Database/Seeds/DummySeeder.php index 9755ceb..6bb22bf 100644 --- a/app/Database/Seeds/DummySeeder.php +++ b/app/Database/Seeds/DummySeeder.php @@ -4,17 +4,18 @@ namespace App\Database\Seeds; use CodeIgniter\Database\Seeder; -class DummySeeder extends Seeder { - public function run() { +class DummySeeder extends Seeder +{ + public function run() + { $now = date('Y-m-d H:i:s'); // users // Password: 'password' for all users (bcrypt hash) - $passwordHash = password_hash('password', PASSWORD_BCRYPT); + $passwordHash = password_hash('123', PASSWORD_BCRYPT); $data = [ - ['id' => 1, 'role_id' => 1, 'username' => 'zaka', 'password' => $passwordHash], - ['id' => 2, 'role_id' => 1, 'username' => 'tes' , 'password' => $passwordHash], - ['id' => 3, 'role_id' => 1, 'username' => 'tes2', 'password' => $passwordHash], + ['id' => 1, 'role_id' => 1, 'username' => 'lisfse', 'password' => $passwordHash], + ['id' => 2, 'role_id' => 1, 'username' => 'tes', 'password' => $passwordHash] ]; $this->db->table('users')->insertBatch($data); diff --git a/app/Database/Seeds/PatientSeeder.php b/app/Database/Seeds/PatientSeeder.php index 9d91938..0a97504 100644 --- a/app/Database/Seeds/PatientSeeder.php +++ b/app/Database/Seeds/PatientSeeder.php @@ -13,12 +13,12 @@ use CodeIgniter\Database\Seeder; * Run with: php spark db:seed PatientTestSeeder * Or for test DB: php spark db:seed PatientTestSeeder --all --n tests */ -class PatientSeeder extends Seeder +class PatientSeeder extends Seeder { - public function run() + public function run() { $now = date('Y-m-d H:i:s'); - + // ======================================== // 1. VALUESET - Required for joins // ======================================== @@ -27,38 +27,38 @@ class PatientSeeder extends Seeder ['VID' => 5, 'SiteID' => 1, 'VSetID' => 1, 'VOrder' => 1, 'VValue' => 'M', 'VDesc' => 'Male', 'CreateDate' => $now], ['VID' => 6, 'SiteID' => 1, 'VSetID' => 1, 'VOrder' => 2, 'VValue' => 'F', 'VDesc' => 'Female', 'CreateDate' => $now], ['VID' => 7, 'SiteID' => 1, 'VSetID' => 1, 'VOrder' => 3, 'VValue' => 'O', 'VDesc' => 'Other', 'CreateDate' => $now], - + // Marital Status (VSetID = 3) ['VID' => 8, 'SiteID' => 1, 'VSetID' => 3, 'VOrder' => 1, 'VValue' => 'S', 'VDesc' => 'Single', 'CreateDate' => $now], ['VID' => 9, 'SiteID' => 1, 'VSetID' => 3, 'VOrder' => 2, 'VValue' => 'M', 'VDesc' => 'Married', 'CreateDate' => $now], ['VID' => 10, 'SiteID' => 1, 'VSetID' => 3, 'VOrder' => 3, 'VValue' => 'D', 'VDesc' => 'Divorced', 'CreateDate' => $now], ['VID' => 11, 'SiteID' => 1, 'VSetID' => 3, 'VOrder' => 4, 'VValue' => 'W', 'VDesc' => 'Widowed', 'CreateDate' => $now], - + // Death Indicator (VSetID = 4) ['VID' => 16, 'SiteID' => 1, 'VSetID' => 4, 'VOrder' => 1, 'VValue' => 'Y', 'VDesc' => 'Deceased', 'CreateDate' => $now], ['VID' => 17, 'SiteID' => 1, 'VSetID' => 4, 'VOrder' => 2, 'VValue' => 'N', 'VDesc' => 'Alive', 'CreateDate' => $now], - + // Race (VSetID = 5) ['VID' => 175, 'SiteID' => 1, 'VSetID' => 5, 'VOrder' => 1, 'VValue' => 'AS', 'VDesc' => 'Asian', 'CreateDate' => $now], ['VID' => 176, 'SiteID' => 1, 'VSetID' => 5, 'VOrder' => 2, 'VValue' => 'WH', 'VDesc' => 'White', 'CreateDate' => $now], - + // Religion (VSetID = 6) ['VID' => 206, 'SiteID' => 1, 'VSetID' => 6, 'VOrder' => 1, 'VValue' => 'IS', 'VDesc' => 'Islam', 'CreateDate' => $now], ['VID' => 207, 'SiteID' => 1, 'VSetID' => 6, 'VOrder' => 2, 'VValue' => 'CH', 'VDesc' => 'Christian', 'CreateDate' => $now], ['VID' => 208, 'SiteID' => 1, 'VSetID' => 6, 'VOrder' => 3, 'VValue' => 'CA', 'VDesc' => 'Catholic', 'CreateDate' => $now], ['VID' => 209, 'SiteID' => 1, 'VSetID' => 6, 'VOrder' => 4, 'VValue' => 'HI', 'VDesc' => 'Hindu', 'CreateDate' => $now], ['VID' => 210, 'SiteID' => 1, 'VSetID' => 6, 'VOrder' => 5, 'VValue' => 'BU', 'VDesc' => 'Buddha', 'CreateDate' => $now], - + // Ethnic (VSetID = 7) ['VID' => 213, 'SiteID' => 1, 'VSetID' => 7, 'VOrder' => 1, 'VValue' => 'JV', 'VDesc' => 'Javanese', 'CreateDate' => $now], ['VID' => 214, 'SiteID' => 1, 'VSetID' => 7, 'VOrder' => 2, 'VValue' => 'SD', 'VDesc' => 'Sundanese', 'CreateDate' => $now], ['VID' => 215, 'SiteID' => 1, 'VSetID' => 7, 'VOrder' => 3, 'VValue' => 'BT', 'VDesc' => 'Batak', 'CreateDate' => $now], - + // Country (VSetID = 8) ['VID' => 221, 'SiteID' => 1, 'VSetID' => 8, 'VOrder' => 1, 'VValue' => 'ID', 'VDesc' => 'Indonesia', 'CreateDate' => $now], ['VID' => 222, 'SiteID' => 1, 'VSetID' => 8, 'VOrder' => 2, 'VValue' => 'MY', 'VDesc' => 'Malaysia', 'CreateDate' => $now], ['VID' => 223, 'SiteID' => 1, 'VSetID' => 8, 'VOrder' => 3, 'VValue' => 'SG', 'VDesc' => 'Singapore', 'CreateDate' => $now], - + // Link Type (VSetID = 9) ['VID' => 2, 'SiteID' => 1, 'VSetID' => 9, 'VOrder' => 1, 'VValue' => 'F', 'VDesc' => 'Family', 'CreateDate' => $now], ['VID' => 3, 'SiteID' => 1, 'VSetID' => 9, 'VOrder' => 2, 'VValue' => 'S', 'VDesc' => 'Spouse', 'CreateDate' => $now], @@ -74,30 +74,7 @@ class PatientSeeder extends Seeder echo "Valueset data seeded.\n"; // ======================================== - // 2. AREAGEO - Province & City - // ======================================== - $areageos = [ - ['AreaGeoID' => 1, 'AreaCode' => '31', 'Class' => 1, 'AreaName' => 'DKI Jakarta', 'Parent' => null], - ['AreaGeoID' => 2, 'AreaCode' => '3101', 'Class' => 2, 'AreaName' => 'Jakarta Pusat', 'Parent' => 1], - ['AreaGeoID' => 3, 'AreaCode' => '3102', 'Class' => 2, 'AreaName' => 'Jakarta Utara', 'Parent' => 1], - ['AreaGeoID' => 4, 'AreaCode' => '3103', 'Class' => 2, 'AreaName' => 'Jakarta Barat', 'Parent' => 1], - ['AreaGeoID' => 5, 'AreaCode' => '3104', 'Class' => 2, 'AreaName' => 'Jakarta Selatan', 'Parent' => 1], - ['AreaGeoID' => 6, 'AreaCode' => '3105', 'Class' => 2, 'AreaName' => 'Jakarta Timur', 'Parent' => 1], - ['AreaGeoID' => 7, 'AreaCode' => '32', 'Class' => 1, 'AreaName' => 'Jawa Barat', 'Parent' => null], - ['AreaGeoID' => 8, 'AreaCode' => '3201', 'Class' => 2, 'AreaName' => 'Bandung', 'Parent' => 7], - ['AreaGeoID' => 9, 'AreaCode' => '3202', 'Class' => 2, 'AreaName' => 'Bogor', 'Parent' => 7], - ]; - - foreach ($areageos as $area) { - $exists = $this->db->table('areageo')->where('AreaGeoID', $area['AreaGeoID'])->get()->getRow(); - if (!$exists) { - $this->db->table('areageo')->insert($area); - } - } - echo "AreaGeo data seeded.\n"; - - // ======================================== - // 3. PATIENT - Main patient data + // 2. PATIENT - Main patient data // ======================================== $patients = [ [ @@ -111,14 +88,14 @@ class PatientSeeder extends Seeder 'NameLast' => 'Patient', 'Suffix' => 'S.Kom', 'NameAlias' => 'DummyTest', - 'Gender' => 5, // Male + 'Gender' => 5, 'PlaceOfBirth' => 'Jakarta', 'Birthdate' => '1990-05-15', 'Street_1' => 'Jl. Sudirman No. 123', 'Street_2' => 'RT 01 RW 02', 'Street_3' => 'Kelurahan Menteng', - 'City' => '2', // Jakarta Pusat - 'Province' => '1', // DKI Jakarta + 'City' => '2', + 'Province' => '1', 'ZIP' => '10110', 'EmailAddress1' => 'dummy1@test.com', 'EmailAddress2' => 'dummy1alt@test.com', @@ -126,13 +103,13 @@ class PatientSeeder extends Seeder 'MobilePhone' => '081234567890', 'Custodian' => null, 'AccountNumber' => null, - 'Country' => 221, // Indonesia - 'Race' => 175, // Asian - 'MaritalStatus' => 9, // Married - 'Religion' => 206, // Islam - 'Ethnic' => 213, // Javanese + 'Country' => 221, + 'Race' => 175, + 'MaritalStatus' => 9, + 'Religion' => 206, + 'Ethnic' => 213, 'Citizenship' => 'WNI', - 'DeathIndicator' => 17, // Alive + 'DeathIndicator' => 17, 'TimeOfDeath' => null, 'LinkTo' => null, 'CreateDate' => $now, diff --git a/docs/prj_clinical laboratory quality management system_3a.docx b/docs/prj_clinical laboratory quality management system_3a.docx new file mode 100644 index 0000000..af41ddc Binary files /dev/null and b/docs/prj_clinical laboratory quality management system_3a.docx differ diff --git a/tests/feature/TestDef/TestDefSiteTest.php b/tests/feature/TestDef/TestDefSiteTest.php deleted file mode 100644 index 98441f1..0000000 --- a/tests/feature/TestDef/TestDefSiteTest.php +++ /dev/null @@ -1,374 +0,0 @@ -withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests'); - - $result->assertStatus(200); - $result->assertJSONExact([ - 'status' => 'success', - 'message' => 'Data fetched successfully', - 'data' => $result->getJSON(true)['data'] - ]); - } - - /** - * Test listing all tests returns array - */ - public function testIndexReturnsArray(): void - { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests'); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertIsArray($response['data']); - } - - /** - * Test index contains test type information - */ - public function testIndexContainsTypeInformation(): void - { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests'); - - $result->assertStatus(200); - $response = $result->getJSON(true); - - if (!empty($response['data'])) { - $test = $response['data'][0]; - $this->assertArrayHasKey('TypeCode', $test); - $this->assertArrayHasKey('TypeName', $test); - } - } - - /** - * Test filtering by test type - */ - public function testIndexFiltersByTestType(): void - { - // Test filtering by TEST type (VID = 1) - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests?TestType=1'); - - $result->assertStatus(200); - $response = $result->getJSON(true); - - foreach ($response['data'] as $test) { - $this->assertEquals('TEST', $test['TypeCode']); - } - } - - /** - * Test filtering by keyword - */ - public function testIndexFiltersByKeyword(): void - { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests?TestSiteName=HB'); - - $result->assertStatus(200); - $response = $result->getJSON(true); - - if (!empty($response['data'])) { - foreach ($response['data'] as $test) { - $this->assertStringContainsString('HB', $test['TestSiteName']); - } - } - } - - /** - * Test showing single test returns success - */ - public function testShowReturnsSuccess(): void - { - // Get a test ID from the seeder data - $model = new TestDefSiteModel(); - $test = $model->first(); - - if ($test) { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get("api/tests/{$test['TestSiteID']}"); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertArrayHasKey('data', $response); - } else { - $this->markTestSkipped('No test data available'); - } - } - - /** - * Test showing single test includes type-specific details for TEST type - */ - public function testShowIncludesTechDetailsForTestType(): void - { - $model = new TestDefSiteModel(); - $test = $model->first(); - - if ($test && $test['TypeCode'] === 'TEST') { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get("api/tests/{$test['TestSiteID']}"); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertArrayHasKey('testdeftech', $response['data']); - } else { - $this->markTestSkipped('No TEST type data available'); - } - } - - /** - * Test showing single test includes type-specific details for CALC type - */ - public function testShowIncludesCalcDetailsForCalcType(): void - { - $model = new TestDefSiteModel(); - $test = $model->first(); - - if ($test && $test['TypeCode'] === 'CALC') { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get("api/tests/{$test['TestSiteID']}"); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertArrayHasKey('testdefcal', $response['data']); - } else { - $this->markTestSkipped('No CALC type data available'); - } - } - - /** - * Test showing single test includes type-specific details for GROUP type - */ - public function testShowIncludesGrpDetailsForGroupType(): void - { - $model = new TestDefSiteModel(); - $test = $model->first(); - - if ($test && $test['TypeCode'] === 'GROUP') { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get("api/tests/{$test['TestSiteID']}"); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertArrayHasKey('testdefgrp', $response['data']); - } else { - $this->markTestSkipped('No GROUP type data available'); - } - } - - /** - * Test creating a new test - */ - public function testCreateTest(): void - { - $testData = [ - 'SiteID' => 1, - 'TestSiteCode' => 'NEWTEST', - 'TestSiteName' => 'New Test', - 'TestType' => 1, // TEST type - 'Description' => 'Test description', - 'SeqScr' => 100, - 'SeqRpt' => 100, - 'VisibleScr' => 1, - 'VisibleRpt' => 1, - 'CountStat' => 1 - ]; - - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->post('api/tests', $testData); - - $result->assertStatus(201); - $response = $result->getJSON(true); - $this->assertArrayHasKey('data', $response); - $this->assertArrayHasKey('TestSiteId', $response['data']); - } - - /** - * Test creating test with validation error (missing required fields) - */ - public function testCreateTestValidationError(): void - { - $testData = [ - 'SiteID' => 1 - // Missing required fields - ]; - - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->post('api/tests', $testData); - - $result->assertStatus(400); - } - - /** - * Test updating a test - */ - public function testUpdateTest(): void - { - $model = new TestDefSiteModel(); - $test = $model->first(); - - if ($test) { - $updateData = [ - 'TestSiteID' => $test['TestSiteID'], - 'TestSiteName' => 'Updated Test Name', - 'Description' => 'Updated description' - ]; - - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->patch('api/tests', $updateData); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertEquals('success', $response['status']); - } else { - $this->markTestSkipped('No test data available'); - } - } - - /** - * Test deleting a test (soft delete) - */ - public function testDeleteTest(): void - { - $model = new TestDefSiteModel(); - $test = $model->first(); - - if ($test) { - $deleteData = [ - 'TestSiteID' => $test['TestSiteID'] - ]; - - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->delete('api/tests', $deleteData); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertEquals('success', $response['status']); - $this->assertArrayHasKey('EndDate', $response['data']); - } else { - $this->markTestSkipped('No test data available'); - } - } - - /** - * Test getting non-existent test returns empty data - */ - public function testShowNonExistentTest(): void - { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests/999999'); - - $result->assertStatus(200); - $response = $result->getJSON(true); - $this->assertNull($response['data']); - } - - /** - * Test test types are correctly mapped from valueset - */ - public function testTestTypesAreMapped(): void - { - $model = new TestDefSiteModel(); - $tests = $model->findAll(); - - $validTypes = ['TEST', 'PARAM', 'CALC', 'GROUP', 'TITLE']; - - foreach ($tests as $test) { - if (isset($test['TypeCode'])) { - $this->assertContains($test['TypeCode'], $validTypes); - } - } - } - - /** - * Test filtering by visible on screen - */ - public function testIndexFiltersByVisibleScr(): void - { - $result = $this->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - ])->get('api/tests?VisibleScr=1'); - - $result->assertStatus(200); - $response = $result->getJSON(true); - - foreach ($response['data'] as $test) { - $this->assertEquals(1, $test['VisibleScr']); - } - } - - /** - * Test all test types from seeder are present - */ - public function testAllTestTypesArePresent(): void - { - $model = new TestDefSiteModel(); - $tests = $model->findAll(); - - $typeCodes = array_column($tests, 'TypeCode'); - $uniqueTypes = array_unique($typeCodes); - - // Check that we have at least TEST and PARAM types from seeder - $this->assertContains('TEST', $uniqueTypes); - $this->assertContains('PARAM', $uniqueTypes); - $this->assertContains('CALC', $uniqueTypes); - $this->assertContains('GROUP', $uniqueTypes); - } -}