-
-
Select Tests to Add
+
+
+
Select Tests
+
+
-
+
-
-
-
diff --git a/app/Views/v2/master/tests/param_dialog.php b/app/Views/v2/master/tests/param_dialog.php
index d047548..69632cd 100644
--- a/app/Views/v2/master/tests/param_dialog.php
+++ b/app/Views/v2/master/tests/param_dialog.php
@@ -81,10 +81,10 @@
-
@@ -96,10 +96,10 @@
-
+
-
-
+
+
@@ -107,10 +107,10 @@
-
+
-
-
+
+
diff --git a/app/Views/v2/master/tests/tests_index.php b/app/Views/v2/master/tests/tests_index.php
index e3de78a..59d6886 100644
--- a/app/Views/v2/master/tests/tests_index.php
+++ b/app/Views/v2/master/tests/tests_index.php
@@ -232,6 +232,8 @@ function labTests() {
await this.fetchDisciplines();
await this.fetchDepartments();
await this.fetchMethods();
+ await this.fetchSpecimenTypes();
+ await this.fetchContainers();
// Additional data can be loaded on demand when specific dialogs are opened
// Watch for typesList changes to ensure dropdown is populated
@@ -290,7 +292,38 @@ function labTests() {
this.methodsList = data.data || [];
}
} catch (err) {
- // Silently fail
+ // Silently fail - use empty array
+ this.methodsList = [];
+ }
+ },
+
+ // Fetch specimen types from valueset
+ async fetchSpecimenTypes() {
+ try {
+ const res = await fetch(`${BASEURL}api/valueset/valuesetdef/29`, {
+ credentials: 'include'
+ });
+ if (res.ok) {
+ const data = await res.json();
+ this.specimenTypesList = data.data || [];
+ }
+ } catch (err) {
+ this.specimenTypesList = [];
+ }
+ },
+
+ // Fetch containers from endpoint
+ async fetchContainers() {
+ try {
+ const res = await fetch(`${BASEURL}api/specimen/containerdef`, {
+ credentials: 'include'
+ });
+ if (res.ok) {
+ const data = await res.json();
+ this.containersList = data.data || [];
+ }
+ } catch (err) {
+ this.containersList = [];
}
},
diff --git a/public/css/v2/styles.css b/public/css/v2/styles.css
index 0b71dca..427dac0 100644
--- a/public/css/v2/styles.css
+++ b/public/css/v2/styles.css
@@ -247,6 +247,11 @@ body {
font-size: 0.8125rem;
}
+.btn-xs {
+ padding: 0.25rem 0.625rem;
+ font-size: 0.75rem;
+}
+
.btn-lg {
padding: 0.875rem 1.75rem;
font-size: 1rem;
@@ -395,6 +400,19 @@ body {
box-shadow: 0 0 0 3px rgba(var(--color-error), 0.15);
}
+/* Input Sizes */
+.input-sm {
+ padding: 0.5rem 0.75rem;
+ font-size: 0.8125rem;
+ min-height: 34px;
+}
+
+.input-xs {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+ min-height: 26px;
+}
+
/* Checkbox */
.checkbox {
width: 1.25rem;
diff --git a/tests/feature/TestDef/TestDefSiteTest.php b/tests/feature/TestDef/TestDefSiteTest.php
new file mode 100644
index 0000000..28e9d5f
--- /dev/null
+++ b/tests/feature/TestDef/TestDefSiteTest.php
@@ -0,0 +1,259 @@
+ 'localhost',
+ 'aud' => 'localhost',
+ 'iat' => time(),
+ 'nbf' => time(),
+ 'exp' => time() + 3600,
+ 'uid' => 1,
+ 'email' => 'admin@admin.com'
+ ];
+ $this->token = \Firebase\JWT\JWT::encode($payload, $key, 'HS256');
+ }
+
+ public function get(string $path, array $options = []) {
+ $this->withHeaders(['Cookie' => 'token=' . $this->token]);
+ return $this->call('get', $path, $options);
+ }
+
+ public function post(string $path, array $options = []) {
+ $this->withHeaders(['Cookie' => 'token=' . $this->token]);
+ return $this->call('post', $path, $options);
+ }
+
+ public function put(string $path, array $options = []) {
+ $this->withHeaders(['Cookie' => 'token=' . $this->token]);
+ return $this->call('put', $path, $options);
+ }
+
+ public function delete(string $path, array $options = []) {
+ $this->withHeaders(['Cookie' => 'token=' . $this->token]);
+ return $this->call('delete', $path, $options);
+ }
+
+ /**
+ * Test index endpoint returns list of tests
+ */
+ public function testIndexReturnsTestList()
+ {
+ $result = $this->get($this->endpoint);
+ $result->assertStatus(200);
+
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertArrayHasKey('status', $body);
+ $this->assertArrayHasKey('data', $body);
+ $this->assertArrayHasKey('message', $body);
+ }
+
+ /**
+ * Test index with SiteID filter
+ */
+ public function testIndexWithSiteFilter()
+ {
+ $result = $this->get($this->endpoint . '?SiteID=1');
+ $result->assertStatus(200);
+
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertEquals('success', $body['status']);
+ }
+
+ /**
+ * Test index with TestType filter
+ */
+ public function testIndexWithTypeFilter()
+ {
+ $result = $this->get($this->endpoint . '?TestType=TEST');
+ $result->assertStatus(200);
+
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertEquals('success', $body['status']);
+ }
+
+ /**
+ * Test show endpoint returns single test
+ */
+ public function testShowReturnsSingleTest()
+ {
+ // First get the list to find a valid ID
+ $indexResult = $this->get($this->endpoint);
+ $indexBody = json_decode($indexResult->response()->getBody(), true);
+
+ if (isset($indexBody['data']) && is_array($indexBody['data']) && !empty($indexBody['data'])) {
+ $firstItem = $indexBody['data'][0];
+ $testSiteID = $firstItem['TestSiteID'] ?? 1;
+
+ $showResult = $this->get($this->endpoint . '/' . $testSiteID);
+ $showResult->assertStatus(200);
+
+ $body = json_decode($showResult->response()->getBody(), true);
+ $this->assertArrayHasKey('data', $body);
+ $this->assertEquals('success', $body['status']);
+
+ // Check that related details are loaded based on TestType
+ if ($body['data'] !== null) {
+ $typeCode = $body['data']['TypeCode'] ?? '';
+ if ($typeCode === 'CALC') {
+ $this->assertArrayHasKey('testdefcal', $body['data']);
+ } elseif ($typeCode === 'GROUP') {
+ $this->assertArrayHasKey('testdefgrp', $body['data']);
+ } elseif (in_array($typeCode, ['TEST', 'PARAM'])) {
+ $this->assertArrayHasKey('testdeftech', $body['data']);
+ }
+ // All types should have testmap
+ $this->assertArrayHasKey('testmap', $body['data']);
+ }
+ }
+ }
+
+ /**
+ * Test show with non-existent ID returns null data
+ */
+ public function testShowWithInvalidIDReturnsNull()
+ {
+ $result = $this->get($this->endpoint . '/9999999');
+ $result->assertStatus(200);
+
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertArrayHasKey('data', $body);
+ $this->assertNull($body['data']);
+ }
+
+ /**
+ * Test create new test definition
+ */
+ public function testCreateTest()
+ {
+ $testData = [
+ 'SiteID' => 1,
+ 'TestSiteCode' => 'HB',
+ 'TestSiteName' => 'Hemoglobin',
+ 'TestType' => 'TEST',
+ 'Description' => 'Hemoglobin concentration test',
+ 'SeqScr' => 3,
+ 'SeqRpt' => 3,
+ 'IndentLeft' => 0,
+ 'FontStyle' => 'Bold',
+ 'VisibleScr' => 1,
+ 'VisibleRpt' => 1,
+ 'CountStat' => 1,
+ 'StartDate' => date('Y-m-d H:i:s')
+ ];
+
+ $result = $this->post($this->endpoint, ['body' => json_encode($testData)]);
+
+ // If validation fails due to duplicate code, that's expected
+ $status = $result->response()->getStatusCode();
+ $this->assertTrue(in_array($status, [201, 400]), "Expected 201 or 400, got $status");
+
+ if ($status === 201) {
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertEquals('created', $body['status']);
+ $this->assertArrayHasKey('TestSiteId', $body['data']);
+ }
+ }
+
+ /**
+ * Test update existing test
+ */
+ public function testUpdateTest()
+ {
+ // Get a valid test ID first
+ $indexResult = $this->get($this->endpoint);
+ $indexBody = json_decode($indexResult->response()->getBody(), true);
+
+ if (isset($indexBody['data']) && is_array($indexBody['data']) && !empty($indexBody['data'])) {
+ $firstItem = $indexBody['data'][0];
+ $testSiteID = $firstItem['TestSiteID'] ?? null;
+
+ if ($testSiteID) {
+ $updateData = [
+ 'TestSiteName' => 'Updated Test Name',
+ 'Description' => 'Updated description'
+ ];
+
+ $result = $this->put($this->endpoint . '/' . $testSiteID, ['body' => json_encode($updateData)]);
+ $status = $result->response()->getStatusCode();
+ $this->assertTrue(in_array($status, [200, 404]), "Expected 200 or 404, got $status");
+
+ if ($status === 200) {
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertEquals('success', $body['status']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test soft delete (disable) test
+ */
+ public function testDeleteTest()
+ {
+ // Get a valid test ID first
+ $indexResult = $this->get($this->endpoint);
+ $indexBody = json_decode($indexResult->response()->getBody(), true);
+
+ if (isset($indexBody['data']) && is_array($indexBody['data']) && !empty($indexBody['data'])) {
+ $firstItem = $indexBody['data'][0];
+ $testSiteID = $firstItem['TestSiteID'] ?? null;
+
+ if ($testSiteID) {
+ $result = $this->delete($this->endpoint . '/' . $testSiteID);
+ $status = $result->response()->getStatusCode();
+ $this->assertTrue(in_array($status, [200, 404]), "Expected 200 or 404, got $status");
+
+ if ($status === 200) {
+ $body = json_decode($result->response()->getBody(), true);
+ $this->assertEquals('success', $body['status']);
+ $this->assertArrayHasKey('EndDate', $body['data']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test validation - missing required fields
+ */
+ public function testCreateValidation()
+ {
+ $invalidData = [
+ 'TestSiteName' => 'Test without code'
+ ];
+
+ $result = $this->post($this->endpoint, ['body' => json_encode($invalidData)]);
+ $result->assertStatus(400);
+ }
+
+ /**
+ * Test that TestSiteCode is max 6 characters
+ */
+ public function testTestSiteCodeLength()
+ {
+ $invalidData = [
+ 'SiteID' => 1,
+ 'TestSiteCode' => 'HB123456', // 8 characters - invalid
+ 'TestSiteName' => 'Test with too long code',
+ 'TestType' => 'TEST'
+ ];
+
+ $result = $this->post($this->endpoint, ['body' => json_encode($invalidData)]);
+ $result->assertStatus(400);
+ }
+}
diff --git a/tests/unit/TestDef/TestDefModelsTest.php b/tests/unit/TestDef/TestDefModelsTest.php
new file mode 100644
index 0000000..d070c67
--- /dev/null
+++ b/tests/unit/TestDef/TestDefModelsTest.php
@@ -0,0 +1,237 @@
+testDefSiteModel = new TestDefSiteModel();
+ $this->testDefTechModel = new TestDefTechModel();
+ $this->testDefCalModel = new TestDefCalModel();
+ $this->testDefGrpModel = new TestDefGrpModel();
+ $this->testMapModel = new TestMapModel();
+ }
+
+ /**
+ * Test TestDefSiteModel has correct table name
+ */
+ public function testTestDefSiteModelTable()
+ {
+ $this->assertEquals('testdefsite', $this->testDefSiteModel->table);
+ }
+
+ /**
+ * Test TestDefSiteModel has correct primary key
+ */
+ public function testTestDefSiteModelPrimaryKey()
+ {
+ $this->assertEquals('TestSiteID', $this->testDefSiteModel->primaryKey);
+ }
+
+ /**
+ * Test TestDefSiteModel has correct allowed fields
+ */
+ public function testTestDefSiteModelAllowedFields()
+ {
+ $allowedFields = $this->testDefSiteModel->allowedFields;
+
+ $this->assertContains('SiteID', $allowedFields);
+ $this->assertContains('TestSiteCode', $allowedFields);
+ $this->assertContains('TestSiteName', $allowedFields);
+ $this->assertContains('TestType', $allowedFields);
+ $this->assertContains('Description', $allowedFields);
+ $this->assertContains('SeqScr', $allowedFields);
+ $this->assertContains('SeqRpt', $allowedFields);
+ $this->assertContains('IndentLeft', $allowedFields);
+ $this->assertContains('FontStyle', $allowedFields);
+ $this->assertContains('VisibleScr', $allowedFields);
+ $this->assertContains('VisibleRpt', $allowedFields);
+ $this->assertContains('CountStat', $allowedFields);
+ $this->assertContains('CreateDate', $allowedFields);
+ $this->assertContains('StartDate', $allowedFields);
+ $this->assertContains('EndDate', $allowedFields);
+ }
+
+ /**
+ * Test TestDefSiteModel uses soft deletes
+ */
+ public function testTestDefSiteModelSoftDeletes()
+ {
+ $this->assertTrue($this->testDefSiteModel->useSoftDeletes);
+ $this->assertEquals('EndDate', $this->testDefSiteModel->deletedField);
+ }
+
+ /**
+ * Test TestDefTechModel has correct table name
+ */
+ public function testTestDefTechModelTable()
+ {
+ $this->assertEquals('testdeftech', $this->testDefTechModel->table);
+ }
+
+ /**
+ * Test TestDefTechModel has correct primary key
+ */
+ public function testTestDefTechModelPrimaryKey()
+ {
+ $this->assertEquals('TestTechID', $this->testDefTechModel->primaryKey);
+ }
+
+ /**
+ * Test TestDefTechModel has correct allowed fields
+ */
+ public function testTestDefTechModelAllowedFields()
+ {
+ $allowedFields = $this->testDefTechModel->allowedFields;
+
+ $this->assertContains('TestSiteID', $allowedFields);
+ $this->assertContains('DisciplineID', $allowedFields);
+ $this->assertContains('DepartmentID', $allowedFields);
+ $this->assertContains('ResultType', $allowedFields);
+ $this->assertContains('RefType', $allowedFields);
+ $this->assertContains('VSet', $allowedFields);
+ $this->assertContains('Unit1', $allowedFields);
+ $this->assertContains('Factor', $allowedFields);
+ $this->assertContains('Unit2', $allowedFields);
+ $this->assertContains('Decimal', $allowedFields);
+ $this->assertContains('Method', $allowedFields);
+ $this->assertContains('ExpectedTAT', $allowedFields);
+ }
+
+ /**
+ * Test TestDefCalModel has correct table name
+ */
+ public function testTestDefCalModelTable()
+ {
+ $this->assertEquals('testdefcal', $this->testDefCalModel->table);
+ }
+
+ /**
+ * Test TestDefCalModel has correct primary key
+ */
+ public function testTestDefCalModelPrimaryKey()
+ {
+ $this->assertEquals('TestCalID', $this->testDefCalModel->primaryKey);
+ }
+
+ /**
+ * Test TestDefCalModel has correct allowed fields
+ */
+ public function testTestDefCalModelAllowedFields()
+ {
+ $allowedFields = $this->testDefCalModel->allowedFields;
+
+ $this->assertContains('TestSiteID', $allowedFields);
+ $this->assertContains('DisciplineID', $allowedFields);
+ $this->assertContains('DepartmentID', $allowedFields);
+ $this->assertContains('FormulaInput', $allowedFields);
+ $this->assertContains('FormulaCode', $allowedFields);
+ $this->assertContains('RefType', $allowedFields);
+ $this->assertContains('Unit1', $allowedFields);
+ $this->assertContains('Factor', $allowedFields);
+ $this->assertContains('Unit2', $allowedFields);
+ $this->assertContains('Decimal', $allowedFields);
+ $this->assertContains('Method', $allowedFields);
+ }
+
+ /**
+ * Test TestDefGrpModel has correct table name
+ */
+ public function testTestDefGrpModelTable()
+ {
+ $this->assertEquals('testdefgrp', $this->testDefGrpModel->table);
+ }
+
+ /**
+ * Test TestDefGrpModel has correct primary key
+ */
+ public function testTestDefGrpModelPrimaryKey()
+ {
+ $this->assertEquals('TestGrpID', $this->testDefGrpModel->primaryKey);
+ }
+
+ /**
+ * Test TestDefGrpModel has correct allowed fields
+ */
+ public function testTestDefGrpModelAllowedFields()
+ {
+ $allowedFields = $this->testDefGrpModel->allowedFields;
+
+ $this->assertContains('TestSiteID', $allowedFields);
+ $this->assertContains('Member', $allowedFields);
+ }
+
+ /**
+ * Test TestMapModel has correct table name
+ */
+ public function testTestMapModelTable()
+ {
+ $this->assertEquals('testmap', $this->testMapModel->table);
+ }
+
+ /**
+ * Test TestMapModel has correct primary key
+ */
+ public function testTestMapModelPrimaryKey()
+ {
+ $this->assertEquals('TestMapID', $this->testMapModel->primaryKey);
+ }
+
+ /**
+ * Test TestMapModel has correct allowed fields
+ */
+ public function testTestMapModelAllowedFields()
+ {
+ $allowedFields = $this->testMapModel->allowedFields;
+
+ $this->assertContains('TestSiteID', $allowedFields);
+ $this->assertContains('HostType', $allowedFields);
+ $this->assertContains('HostID', $allowedFields);
+ $this->assertContains('HostDataSource', $allowedFields);
+ $this->assertContains('HostTestCode', $allowedFields);
+ $this->assertContains('HostTestName', $allowedFields);
+ $this->assertContains('ClientType', $allowedFields);
+ $this->assertContains('ClientID', $allowedFields);
+ $this->assertContains('ClientDataSource', $allowedFields);
+ $this->assertContains('ConDefID', $allowedFields);
+ $this->assertContains('ClientTestCode', $allowedFields);
+ $this->assertContains('ClientTestName', $allowedFields);
+ }
+
+ /**
+ * Test all models use soft deletes
+ */
+ public function testAllModelsUseSoftDeletes()
+ {
+ $this->assertTrue($this->testDefTechModel->useSoftDeletes);
+ $this->assertTrue($this->testDefCalModel->useSoftDeletes);
+ $this->assertTrue($this->testDefGrpModel->useSoftDeletes);
+ $this->assertTrue($this->testMapModel->useSoftDeletes);
+ }
+
+ /**
+ * Test all models have EndDate as deleted field
+ */
+ public function testAllModelsUseEndDateAsDeletedField()
+ {
+ $this->assertEquals('EndDate', $this->testDefTechModel->deletedField);
+ $this->assertEquals('EndDate', $this->testDefCalModel->deletedField);
+ $this->assertEquals('EndDate', $this->testDefGrpModel->deletedField);
+ $this->assertEquals('EndDate', $this->testMapModel->deletedField);
+ }
+}