feat: enhance Test Management module with improved UI and tests

- Refactor Tests.php controller with updated logic and error handling
- Update Test migration with schema improvements
- Enhance TestDefCalModel, TestDefGrpModel, TestDefTechModel with CRUD operations
- Improve TestMapModel with better test mapping relationships
- Redesign test dialog views (calc, group, param) with improved UX
- Update tests_index view with better data presentation
- Add CSS styles for test management UI components
- Add TestDefSiteTest feature test for site-based test definitions
- Add TestDefModelsTest unit test for model validation
- Remove obsolete Test Management.docx documentation
This commit is contained in:
mahdahar 2026-01-02 08:33:22 +07:00
parent 97451496c3
commit 97edfe50a8
14 changed files with 892 additions and 198 deletions

Binary file not shown.

View File

@ -35,7 +35,8 @@ class Tests extends BaseController {
}
/**
* GET /api/tests
* GET /v1/tests
* GET /v1/tests/site
* List all tests with optional filtering
*/
public function index() {
@ -82,11 +83,12 @@ class Tests extends BaseController {
}
/**
* GET /api/tests/{id}
* 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('ID is required');
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")
@ -134,11 +136,9 @@ class Tests extends BaseController {
} else {
// TEST or PARAM - load technical details
$row['testdeftech'] = $this->db->table('testdeftech')
->select('testdeftech.*, d.DisciplineName, dept.DepartmentName, w.WorkstationName, e.EquipmentName')
->select('testdeftech.*, d.DisciplineName, dept.DepartmentName')
->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left')
->join('department dept', 'dept.DepartmentID=testdeftech.DepartmentID', 'left')
->join('workstation w', 'w.WorkstationID=testdeftech.WorkstationID', 'left')
->join('equipment e', 'e.EquipmentID=testdeftech.EquipmentID', 'left')
->where('testdeftech.TestSiteID', $id)
->where('testdeftech.EndDate IS NULL')
->get()->getResultArray();
@ -151,7 +151,8 @@ class Tests extends BaseController {
}
/**
* POST /api/tests
* POST /v1/tests
* POST /v1/tests/site
* Create new test definition
*/
public function create() {
@ -196,9 +197,9 @@ class Tests extends BaseController {
}
return $this->respondCreated([
'status' => 'success',
'status' => 'created',
'message' => "Test created successfully",
'data' => ['TestSiteID' => $id]
'data' => ['TestSiteId' => $id]
]);
} catch (\Exception $e) {
$this->db->transRollback();
@ -207,7 +208,8 @@ class Tests extends BaseController {
}
/**
* PUT/PATCH /api/tests/{id}
* PUT/PATCH /v1/tests/{id}
* PUT/PATCH /v1/tests/site/{id}
* Update existing test definition
*/
public function update($id = null) {
@ -254,7 +256,7 @@ class Tests extends BaseController {
return $this->respond([
'status' => 'success',
'message' => "Test updated successfully",
'data' => ['TestSiteID' => $id]
'data' => ['TestSiteId' => $id]
]);
} catch (\Exception $e) {
$this->db->transRollback();
@ -263,7 +265,8 @@ class Tests extends BaseController {
}
/**
* DELETE /api/tests/{id}
* DELETE /v1/tests/{id}
* DELETE /v1/tests/site/{id}
* Soft delete test by setting EndDate
*/
public function delete($id = null) {
@ -326,7 +329,7 @@ class Tests extends BaseController {
return $this->respond([
'status' => 'success',
'message' => "Test disabled successfully",
'data' => ['TestSiteID' => $id, 'EndDate' => $now]
'data' => ['TestSiteId' => $id, 'EndDate' => $now]
]);
} catch (\Exception $e) {
$this->db->transRollback();
@ -392,17 +395,12 @@ class Tests extends BaseController {
*/
private function saveTechDetails($testSiteID, $data, $action, $typeCode) {
$techData = [
'SiteID' => $data['SiteID'],
'TestSiteID' => $testSiteID,
'DisciplineID' => $data['DisciplineID'] ?? null,
'DepartmentID' => $data['DepartmentID'] ?? null,
'WorkstationID' => $data['WorkstationID'] ?? null,
'EquipmentID' => $data['EquipmentID'] ?? null,
'ResultType' => $data['ResultType'] ?? ($typeCode === 'PARAM' ? 'Numeric' : null),
'ResultType' => $data['ResultType'] ?? null,
'RefType' => $data['RefType'] ?? null,
'VSet' => $data['VSet'] ?? null,
'SpcType' => $data['SpcType'] ?? null,
'SpcDesc' => $data['SpcDesc'] ?? null,
'ReqQty' => $data['ReqQty'] ?? null,
'ReqQtyUnit' => $data['ReqQtyUnit'] ?? null,
'Unit1' => $data['Unit1'] ?? null,
@ -435,7 +433,6 @@ class Tests extends BaseController {
*/
private function saveCalcDetails($testSiteID, $data, $action) {
$calcData = [
'SiteID' => $data['SiteID'],
'TestSiteID' => $testSiteID,
'DisciplineID' => $data['DisciplineID'] ?? null,
'DepartmentID' => $data['DepartmentID'] ?? null,
@ -484,7 +481,6 @@ class Tests extends BaseController {
$memberID = is_array($m) ? ($m['Member'] ?? ($m['TestSiteID'] ?? null)) : $m;
if ($memberID) {
$this->modelGrp->insert([
'SiteID' => $data['SiteID'],
'TestSiteID' => $testSiteID,
'Member' => $memberID
]);

View File

@ -6,7 +6,7 @@ use CodeIgniter\Database\Migration;
class CreateTestsTable extends Migration {
public function up() {
// testdefsite - Main test definition table
// testdefsite - Main test definition table per site
$this->forge->addField([
'TestSiteID' => ['type' => 'INT', 'auto_increment' => true, 'unsigned' => true],
'SiteID' => ['type' => 'INT', 'null' => false],
@ -31,17 +31,12 @@ class CreateTestsTable extends Migration {
// testdeftech - Technical definition for TEST and PARAM types
$this->forge->addField([
'TestTechID' => ['type' => 'INT', 'auto_increment' => true, 'unsigned' => true],
'SiteID' => ['type' => 'INT', 'null' => false],
'TestSiteID' => ['type' => 'INT', 'null' => false],
'DisciplineID' => ['type' => 'int', 'null' => true],
'DepartmentID' => ['type' => 'int', 'null' => true],
'WorkstationID' => ['type' => 'INT', 'null' => true],
'EquipmentID' => ['type' => 'INT', 'null' => true],
'ResultType' => ['type' => 'varchar', 'constraint'=> 20, 'null' => true],
'RefType' => ['type' => 'varchar', 'constraint'=> 10, 'null' => true],
'VSet' => ['type' => 'int', 'null' => true],
'SpcType' => ['type' => 'varchar', 'constraint'=> 10, 'null' => true],
'SpcDesc' => ['type' => 'varchar', 'constraint'=> 100, 'null' => true],
'ReqQty' => ['type' => 'DECIMAL', 'constraint'=> '10,2', 'null' => true],
'ReqQtyUnit' => ['type' => 'varchar', 'constraint'=> 20, 'null' => true],
'Unit1' => ['type' => 'varchar', 'constraint'=> 20, 'null' => true],
@ -61,12 +56,11 @@ class CreateTestsTable extends Migration {
// testdefcal - Calculation definition for CALC type
$this->forge->addField([
'TestCalID' => ['type' => 'INT', 'auto_increment' => true, 'unsigned' => true],
'SiteID' => ['type' => 'INT', 'null' => false],
'TestSiteID' => ['type' => 'INT', 'null' => false],
'DisciplineID' => ['type' => 'INT', 'null' => true],
'DepartmentID' => ['type' => 'INT', 'null' => true],
'FormulaInput' => ['type' => 'varchar', 'constraint'=> 255, 'null' => true],
'FormulaCode' => ['type' => 'text', 'null' => true],
'FormulaInput' => ['type' => 'text', 'null' => true],
'FormulaCode' => ['type' => 'varchar', 'constraint'=> 255, 'null' => true],
'RefType' => ['type' => 'varchar', 'constraint'=> 10, 'null' => true, 'default' => 'NMRC'],
'Unit1' => ['type' => 'varchar', 'constraint'=> 20, 'null' => true],
'Factor' => ['type' => 'DECIMAL', 'constraint'=> '10,4', 'null' => true],
@ -83,7 +77,6 @@ class CreateTestsTable extends Migration {
// testdefgrp - Group definition for GROUP type
$this->forge->addField([
'TestGrpID' => ['type' => 'INT', 'auto_increment' => true, 'unsigned' => true],
'SiteID' => ['type' => 'INT', 'null' => false],
'TestSiteID' => ['type' => 'INT', 'null' => false],
'Member' => ['type' => 'INT', 'null' => true],
'CreateDate' => ['type' => 'Datetime', 'null' => true],

View File

@ -8,8 +8,7 @@ class TestDefCalModel extends BaseModel {
protected $table = 'testdefcal';
protected $primaryKey = 'TestCalID';
protected $allowedFields = [
'SiteID',
'TestSiteID',
'TestSiteID',
'DisciplineID',
'DepartmentID',
'FormulaInput',
@ -23,11 +22,57 @@ class TestDefCalModel extends BaseModel {
'CreateDate',
'EndDate'
];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = "EndDate";
}
/**
* Get calculation details for a test
*/
public function getCalcDetails($testSiteID) {
$db = \Config\Database::connect();
return $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', $testSiteID)
->where('testdefcal.EndDate IS NULL')
->get()->getResultArray();
}
/**
* Get calculated tests by discipline
*/
public function getCalcsByDiscipline($disciplineID, $siteID = null) {
$builder = $this->select('testdefcal.*, testdefsite.TestSiteCode, testdefsite.TestSiteName')
->join('testdefsite', 'testdefsite.TestSiteID=testdefcal.TestSiteID', 'left')
->where('testdefcal.DisciplineID', $disciplineID)
->where('testdefcal.EndDate IS NULL');
if ($siteID) {
$builder->where('testdefsite.SiteID', $siteID);
}
return $builder->findAll();
}
/**
* Get calculated tests by department
*/
public function getCalcsByDepartment($departmentID, $siteID = null) {
$builder = $this->select('testdefcal.*, testdefsite.TestSiteCode, testdefsite.TestSiteName')
->join('testdefsite', 'testdefsite.TestSiteID=testdefcal.TestSiteID', 'left')
->where('testdefcal.DepartmentID', $departmentID)
->where('testdefcal.EndDate IS NULL');
if ($siteID) {
$builder->where('testdefsite.SiteID', $siteID);
}
return $builder->findAll();
}
}

View File

@ -7,12 +7,43 @@ use App\Models\BaseModel;
class TestDefGrpModel extends BaseModel {
protected $table = 'testdefgrp';
protected $primaryKey = 'TestGrpID';
protected $allowedFields = ['SiteID', 'TestSiteID', 'Member', 'CreateDate', 'EndDate'];
protected $allowedFields = [
'TestSiteID',
'Member',
'CreateDate',
'EndDate'
];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = "EndDate";
}
/**
* Get group members for a test group
*/
public function getGroupMembers($testSiteID) {
$db = \Config\Database::connect();
return $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', $testSiteID)
->where('testdefgrp.EndDate IS NULL')
->orderBy('testdefgrp.TestGrpID', 'ASC')
->get()->getResultArray();
}
/**
* Get all groups that contain a specific test
*/
public function getGroupsContainingTest($memberTestSiteID) {
return $this->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName')
->join('testdefsite t', 't.TestSiteID=testdefgrp.TestSiteID', 'left')
->where('testdefgrp.Member', $memberTestSiteID)
->where('testdefgrp.EndDate IS NULL')
->findAll();
}
}

View File

@ -8,17 +8,12 @@ class TestDefTechModel extends BaseModel {
protected $table = 'testdeftech';
protected $primaryKey = 'TestTechID';
protected $allowedFields = [
'SiteID',
'TestSiteID',
'TestSiteID',
'DisciplineID',
'DepartmentID',
'WorkstationID',
'EquipmentID',
'ResultType',
'RefType',
'VSet',
'SpcType',
'SpcDesc',
'ReqQty',
'ReqQtyUnit',
'Unit1',
@ -31,11 +26,57 @@ class TestDefTechModel extends BaseModel {
'CreateDate',
'EndDate'
];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = "EndDate";
}
/**
* Get technical details for a test
*/
public function getTechDetails($testSiteID) {
$db = \Config\Database::connect();
return $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', $testSiteID)
->where('testdeftech.EndDate IS NULL')
->get()->getResultArray();
}
/**
* Get tests by discipline
*/
public function getTestsByDiscipline($disciplineID, $siteID = null) {
$builder = $this->select('testdeftech.*, testdefsite.TestSiteCode, testdefsite.TestSiteName')
->join('testdefsite', 'testdefsite.TestSiteID=testdeftech.TestSiteID', 'left')
->where('testdeftech.DisciplineID', $disciplineID)
->where('testdeftech.EndDate IS NULL');
if ($siteID) {
$builder->where('testdefsite.SiteID', $siteID);
}
return $builder->findAll();
}
/**
* Get tests by department
*/
public function getTestsByDepartment($departmentID, $siteID = null) {
$builder = $this->select('testdeftech.*, testdefsite.TestSiteCode, testdefsite.TestSiteName')
->join('testdefsite', 'testdefsite.TestSiteID=testdeftech.TestSiteID', 'left')
->where('testdeftech.DepartmentID', $departmentID)
->where('testdeftech.EndDate IS NULL');
if ($siteID) {
$builder->where('testdefsite.SiteID', $siteID);
}
return $builder->findAll();
}
}

View File

@ -30,4 +30,67 @@ class TestMapModel extends BaseModel {
protected $useSoftDeletes = true;
protected $deletedField = "EndDate";
}
/**
* Get test mappings by test site
*/
public function getMappingsByTestSite($testSiteID) {
return $this->where('TestSiteID', $testSiteID)
->where('EndDate IS NULL')
->findAll();
}
/**
* Get test mappings by client (equipment/workstation)
*/
public function getMappingsByClient($clientType, $clientID) {
return $this->where('ClientType', $clientType)
->where('ClientID', $clientID)
->where('EndDate IS NULL')
->findAll();
}
/**
* Get test mappings by host (site/HIS)
*/
public function getMappingsByHost($hostType, $hostID) {
return $this->where('HostType', $hostType)
->where('HostID', $hostID)
->where('EndDate IS NULL')
->findAll();
}
/**
* Get test mapping by client test code and container
*/
public function getMappingByClientCode($clientTestCode, $conDefID = null) {
$builder = $this->where('ClientTestCode', $clientTestCode)
->where('EndDate IS NULL');
if ($conDefID) {
$builder->where('ConDefID', $conDefID);
}
return $builder->findAll();
}
/**
* Get test mapping by host test code
*/
public function getMappingByHostCode($hostTestCode) {
return $this->where('HostTestCode', $hostTestCode)
->where('EndDate IS NULL')
->findAll();
}
/**
* Check if mapping exists for client and host
*/
public function mappingExists($testSiteID, $clientType, $clientID, $clientTestCode) {
return $this->where('TestSiteID', $testSiteID)
->where('ClientType', $clientType)
->where('ClientID', $clientID)
->where('ClientTestCode', $clientTestCode)
->where('EndDate IS NULL')
->countAllResults() > 0;
}
}

View File

@ -84,7 +84,7 @@
<input
type="text"
class="input"
x-model="form.ResultUnit"
x-model="form.Unit1"
placeholder="% or cells/µL"
/>
</div>
@ -105,31 +105,28 @@
</label>
<textarea
class="input font-mono h-20"
:class="errors.Formula && 'input-error'"
x-model="form.Formula"
:class="errors.FormulaCode && 'input-error'"
x-model="form.FormulaCode"
placeholder="({WBC} * {NEU%}) / 100"
></textarea>
<label class="label" x-show="errors.Formula">
<span class="label-text-alt" style="color: rgb(var(--color-error));" x-text="errors.Formula"></span>
<label class="label" x-show="errors.FormulaCode">
<span class="label-text-alt" style="color: rgb(var(--color-error));" x-text="errors.FormulaCode"></span>
</label>
</div>
<!-- Formula Variables/Tests -->
<div>
<label class="label">
<span class="label-text font-medium">Used Test Variables</span>
<span class="label-text font-medium">Input Parameters</span>
<span class="label-text-alt text-base-content/60">Tests referenced in formula</span>
</label>
<div class="flex flex-wrap gap-2">
<template x-if="!form.formulaVars || form.formulaVars.length === 0">
<span class="text-sm text-base-content/50 italic">No variables defined. Add tests below.</span>
<template x-if="!form.FormulaInput || form.FormulaInput.length === 0">
<span class="text-sm text-base-content/50 italic">No parameters defined.</span>
</template>
<template x-for="v in form.formulaVars" :key="v">
<template x-for="(v, idx) in (form.FormulaInput ? form.FormulaInput.split('^') : [])" :key="idx">
<span class="badge badge-primary gap-1">
<code x-text="'{'+v+'}'"></code>
<button class="btn btn-ghost btn-xs btn-square" @click="removeFormulaVar(v)">
<i class="fa-solid fa-times"></i>
</button>
<code x-text="v"></code>
</span>
</template>
</div>
@ -139,7 +136,7 @@
<div class="flex gap-2">
<select class="select select-bordered flex-1" x-model="form.newFormulaVar">
<option value="">Select test variable...</option>
<template x-for="t in availableTests" :key="t.TestSiteID">
<template x-for="(t, idx) in availableTests" :key="idx">
<option :value="t.TestSiteCode" x-text="t.TestSiteCode + ' - ' + t.TestSiteName"></option>
</template>
</select>
@ -151,7 +148,7 @@
</div>
<!-- Result Options -->
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-3 gap-4">
<div>
<label class="label">
<span class="label-text font-medium">Decimal Places</span>
@ -159,7 +156,7 @@
<input
type="number"
class="input text-center"
x-model="form.DecimalPlaces"
x-model="form.Decimal"
min="0"
max="10"
placeholder="2"
@ -167,14 +164,25 @@
</div>
<div>
<label class="label">
<span class="label-text font-medium">Rounding Method</span>
<span class="label-text font-medium">Conversion Factor</span>
</label>
<select class="select" x-model="form.RoundingMethod">
<option value="round">Standard Round</option>
<option value="floor">Floor (Down)</option>
<option value="ceil">Ceiling (Up)</option>
<option value="truncate">Truncate</option>
</select>
<input
type="text"
class="input"
x-model="form.Factor"
placeholder="Optional conversion"
/>
</div>
<div>
<label class="label">
<span class="label-text font-medium">Secondary Unit</span>
</label>
<input
type="text"
class="input"
x-model="form.Unit2"
placeholder="Optional secondary unit"
/>
</div>
</div>

View File

@ -12,7 +12,7 @@
x-transition:leave-end="opacity-0"
>
<div
class="modal-content p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto"
class="modal-content p-5 max-w-2xl w-full max-h-[90vh] overflow-y-auto"
@click.stop
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 transform scale-95"
@ -22,8 +22,8 @@
x-transition:leave-end="opacity-0 transform scale-95"
>
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<h3 class="font-bold text-xl flex items-center gap-2" style="color: rgb(var(--color-text));">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg flex items-center gap-2" style="color: rgb(var(--color-text));">
<i class="fa-solid fa-layer-group" style="color: rgb(var(--color-primary));"></i>
<span x-text="isEditing ? 'Edit Test Group' : 'New Test Group'"></span>
</h3>
@ -33,206 +33,176 @@
</div>
<!-- Form -->
<div class="space-y-4">
<div class="space-y-3">
<!-- Basic Info -->
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-2 gap-3">
<div>
<label class="label">
<span class="label-text font-medium">Group Name <span style="color: rgb(var(--color-error));">*</span></span>
<label class="label py-1">
<span class="label-text font-medium text-sm">Group Name <span style="color: rgb(var(--color-error));">*</span></span>
</label>
<input
type="text"
class="input"
class="input input-sm input-bordered"
:class="errors.TestSiteName && 'input-error'"
x-model="form.TestSiteName"
placeholder="CBC Panel"
/>
<label class="label" x-show="errors.TestSiteName">
<span class="label-text-alt" style="color: rgb(var(--color-error));" x-text="errors.TestSiteName"></span>
</label>
</div>
<div>
<label class="label">
<span class="label-text font-medium">Group Code <span style="color: rgb(var(--color-error));">*</span></span>
<label class="label py-1">
<span class="label-text font-medium text-sm">Group Code <span style="color: rgb(var(--color-error));">*</span></span>
</label>
<input
type="text"
class="input font-mono"
class="input input-sm input-bordered font-mono"
:class="errors.TestSiteCode && 'input-error'"
x-model="form.TestSiteCode"
placeholder="CBC"
/>
<label class="label" x-show="errors.TestSiteCode">
<span class="label-text-alt" style="color: rgb(var(--color-error));" x-text="errors.TestSiteCode"></span>
</label>
</div>
</div>
<!-- Test Type & Specimen -->
<div class="grid grid-cols-2 gap-4">
<!-- Test Type & Default Specimen -->
<div class="grid grid-cols-2 gap-3">
<div>
<label class="label">
<span class="label-text font-medium">Test Type</span>
<label class="label py-1">
<span class="label-text font-medium text-sm">Test Type</span>
</label>
<input type="text" class="input input-disabled bg-base-200" x-model="form.TestTypeName" readonly />
<input type="text" class="input input-sm input-bordered bg-base-200" x-model="form.TestTypeName" readonly />
</div>
<div>
<label class="label">
<span class="label-text font-medium">Default Specimen</span>
<label class="label py-1">
<span class="label-text font-medium text-sm">Default Specimen</span>
</label>
<select class="select" x-model="form.DefaultSpecimenID">
<option value="">Select Specimen</option>
<template x-for="s in specimenTypesList" :key="s.SpecimenTypeID">
<option :value="s.SpecimenTypeID" x-text="s.SpecimenTypeName"></option>
<select class="select select-sm select-bordered" x-model="form.SpcType">
<option value="">Select</option>
<template x-for="s in specimenTypesList" :key="s.VID || s.id">
<option :value="s.VValue" x-text="s.VDesc || s.VValue"></option>
</template>
</select>
</div>
</div>
<!-- Description -->
<div>
<label class="label">
<span class="label-text font-medium">Description</span>
</label>
<textarea
class="input h-16 pt-2"
x-model="form.Description"
placeholder="Group description..."
></textarea>
</div>
<!-- Test Members Selection -->
<div class="border rounded-xl p-4 bg-base-50">
<div class="flex items-center justify-between mb-3">
<h4 class="font-semibold flex items-center gap-2">
<i class="fa-solid fa-list-check"></i>
Group Members (Tests)
<div class="border rounded-lg p-3 bg-base-50">
<div class="flex items-center justify-between mb-2">
<h4 class="font-semibold text-sm flex items-center gap-1">
<i class="fa-solid fa-list-check text-sm"></i>
Group Members
</h4>
<button class="btn btn-primary btn-sm" @click="openTestSelector()">
<i class="fa-solid fa-plus mr-1"></i> Add Tests
<button class="btn btn-primary btn-xs" @click="openTestSelector()">
<i class="fa-solid fa-plus mr-1"></i> Add
</button>
</div>
<!-- Selected Members List -->
<div class="space-y-2 max-h-60 overflow-y-auto">
<div class="space-y-2 max-h-64 overflow-y-auto">
<template x-if="!form.members || form.members.length === 0">
<div class="text-center py-8 text-base-content/50">
<i class="fa-solid fa-inbox text-3xl mb-2"></i>
<p>No tests added yet</p>
<p class="text-sm">Click "Add Tests" to select tests for this group</p>
<div class="text-center py-4 text-base-content/50">
<i class="fa-solid fa-inbox text-xl mb-1"></i>
<p class="text-xs">No tests added</p>
</div>
</template>
<template x-for="(member, index) in form.members" :key="index">
<div class="flex items-center gap-3 p-3 bg-white rounded-lg border">
<span class="badge badge-sm font-mono" x-text="member.TestSiteCode"></span>
<span class="flex-1 font-medium" x-text="member.TestSiteName"></span>
<input
type="number"
class="input input-bordered input-sm w-16 text-center"
x-model="member.SeqScr"
placeholder="Seq"
title="Sequence"
/>
<button class="btn btn-ghost btn-sm btn-square text-error" @click="removeMember(index)">
<i class="fa-solid fa-times"></i>
</button>
<div class="grid grid-cols-[1fr_auto] gap-2 p-2 bg-white rounded border items-center">
<span class="text-xs font-medium truncate" x-text="member.TestSiteCode+' - '+member.TestSiteName"></span>
<div class="flex items-center gap-1">
<input type="number" class="input input-xs py-1 text-center w-8" x-model="member.SeqScr" placeholder="#" title="Order"/>
<button class="btn btn-ghost btn-xs btn-square text-error" @click="removeMember(index)">
<i class="fa-solid fa-times text-xs"></i>
</button>
</div>
</div>
</template>
</div>
</div>
<!-- Sequence & Site -->
<div class="grid grid-cols-3 gap-4">
<div class="grid grid-cols-2 gap-2">
<div>
<label class="label">
<span class="label-text font-medium">Seq (Screen)</span>
</label>
<input type="number" class="input text-center" x-model="form.SeqScr" />
</div>
<div>
<label class="label">
<span class="label-text font-medium">Seq (Report)</span>
</label>
<input type="number" class="input text-center" x-model="form.SeqRpt" />
</div>
<!-- Sequence & Site - Very Compact -->
<div class="flex flex-wrap items-center gap-3">
<div class="flex items-center gap-1">
<span class="text-xs text-base-content/60">Scr:</span>
<input type="number" class="input input-xs w-12 text-center" x-model="form.SeqScr" />
</div>
<div>
<label class="label">
<span class="label-text font-medium">Site</span>
</label>
<select class="select" x-model="form.SiteID">
<template x-for="s in sitesList" :key="s.SiteID">
<option :value="s.SiteID" x-text="s.SiteName"></option>
</template>
</select>
<div class="flex items-center gap-1">
<span class="text-xs text-base-content/60">Rpt:</span>
<input type="number" class="input input-xs w-12 text-center" x-model="form.SeqRpt" />
</div>
<select class="select select-xs flex-1" x-model="form.SiteID">
<template x-for="s in sitesList" :key="s.SiteID">
<option :value="s.SiteID" x-text="s.SiteName"></option>
</template>
</select>
</div>
<!-- Options -->
<div class="flex items-center gap-6 p-4 rounded-xl border border-slate-100 bg-slate-50/50">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" class="checkbox" x-model="form.VisibleScr" :true-value="1" :false-value="0" />
<span class="label-text">Visible in Screen</span>
<!-- Options - Compact -->
<div class="flex items-center gap-3 p-2 rounded border border-slate-100 bg-slate-50/50">
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" class="checkbox checkbox-xs" x-model="form.VisibleScr" :true-value="1" :false-value="0" />
<span class="label-text text-xs">Screen</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" class="checkbox" x-model="form.VisibleRpt" :true-value="1" :false-value="0" />
<span class="label-text">Visible in Report</span>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" class="checkbox checkbox-xs" x-model="form.VisibleRpt" :true-value="1" :false-value="0" />
<span class="label-text text-xs">Report</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" class="checkbox" x-model="form.CountStat" :true-value="1" :false-value="0" />
<span class="label-text">Count in Statistics</span>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" class="checkbox checkbox-xs" x-model="form.CountStat" :true-value="1" :false-value="0" />
<span class="label-text text-xs">Stats</span>
</label>
</div>
</div>
<!-- Actions -->
<div class="flex gap-3 mt-8 pt-6" style="border-top: 1px solid rgb(var(--color-border));">
<button class="btn btn-ghost flex-1" @click="closeModal()">Cancel</button>
<button class="btn btn-primary flex-1" @click="save()" :disabled="saving">
<span x-show="saving" class="spinner spinner-sm"></span>
<i x-show="!saving" class="fa-solid fa-save mr-2"></i>
<span x-text="saving ? 'Saving...' : 'Save Group'"></span>
<div class="flex gap-2 mt-4 pt-3" style="border-top: 1px solid rgb(var(--color-border));">
<button class="btn btn-sm btn-ghost flex-1" @click="closeModal()">Cancel</button>
<button class="btn btn-sm btn-primary flex-1" @click="save()" :disabled="saving">
<span x-show="saving" class="spinner spinner-xs"></span>
<i x-show="!saving" class="fa-solid fa-save mr-1"></i>
<span x-text="saving ? 'Saving...' : 'Save'"></span>
</button>
</div>
</div>
<!-- Test Selector Modal -->
<div x-show="showTestSelector" x-cloak class="modal-overlay" style="z-index: 100;">
<div class="modal-content p-6 max-w-3xl" @click.stop>
<h4 class="font-bold text-lg mb-4">Select Tests to Add</h4>
<div class="modal-content p-4 max-w-lg" @click.stop>
<div class="flex items-center justify-between mb-3">
<h4 class="font-bold">Select Tests</h4>
<button class="btn btn-ghost btn-sm btn-square" @click="showTestSelector = false">
<i class="fa-solid fa-times"></i>
</button>
</div>
<input
type="text"
class="input mb-4"
class="input input-sm mb-2"
placeholder="Search tests..."
x-model="testSearch"
/>
<div class="max-h-96 overflow-y-auto space-y-2">
<div class="max-h-48 overflow-y-auto space-y-1">
<template x-for="test in availableTests" :key="test.TestSiteID">
<label class="flex items-center gap-3 p-3 hover:bg-base-200 rounded-lg cursor-pointer">
<label class="flex items-center gap-2 p-2 hover:bg-base-200 rounded-lg cursor-pointer">
<input
type="checkbox"
class="checkbox checkbox-primary"
class="checkbox checkbox-sm checkbox-primary"
:checked="isTestSelected(test.TestSiteID)"
@change="toggleTestSelection(test)"
/>
<div class="flex-1">
<div class="font-medium" x-text="test.TestSiteName"></div>
<div class="text-sm text-base-content/60 font-mono" x-text="test.TestSiteCode"></div>
<div class="font-medium text-sm" x-text="test.TestSiteName"></div>
<div class="text-xs text-base-content/60 font-mono" x-text="test.TestSiteCode"></div>
</div>
</label>
</template>
</div>
<div class="flex gap-3 mt-6">
<button class="btn btn-ghost flex-1" @click="showTestSelector = false">Cancel</button>
<button class="btn btn-primary flex-1" @click="confirmTestSelection()">
<i class="fa-solid fa-check mr-2"></i> Confirm Selection
<div class="flex gap-2 mt-3 pt-2" style="border-top: 1px solid rgb(var(--color-border));">
<button class="btn btn-sm btn-ghost flex-1" @click="showTestSelector = false">Cancel</button>
<button class="btn btn-sm btn-primary flex-1" @click="confirmTestSelection()">
<i class="fa-solid fa-check mr-1"></i> Confirm
</button>
</div>
</div>

View File

@ -81,10 +81,10 @@
<label class="label">
<span class="label-text font-medium">Method</span>
</label>
<select class="select" x-model="form.MethodID">
<select class="select" x-model="form.Method">
<option value="">Select Method</option>
<template x-for="m in methodsList" :key="m.MethodID">
<option :value="m.MethodID" x-text="m.MethodName"></option>
<template x-for="m in methodsList" :key="m.VID">
<option :value="m.VValue" x-text="m.VDesc || m.VValue"></option>
</template>
</select>
</div>
@ -96,10 +96,10 @@
<label class="label">
<span class="label-text font-medium">Specimen Type</span>
</label>
<select class="select" x-model="form.SpecimenTypeID">
<select class="select" x-model="form.SpcType">
<option value="">Select Specimen</option>
<template x-for="s in specimenTypesList" :key="s.SpecimenTypeID">
<option :value="s.SpecimenTypeID" x-text="s.SpecimenTypeName"></option>
<template x-for="s in specimenTypesList" :key="s.VID || s.id">
<option :value="s.VValue" x-text="s.VDesc || s.VValue"></option>
</template>
</select>
</div>
@ -107,10 +107,10 @@
<label class="label">
<span class="label-text font-medium">Container</span>
</label>
<select class="select" x-model="form.ContainerID">
<select class="select" x-model="form.ConDefID">
<option value="">Select Container</option>
<template x-for="c in containersList" :key="c.ContainerID">
<option :value="c.ContainerID" x-text="c.ContainerName"></option>
<template x-for="c in containersList" :key="c.ConDefID || c.id">
<option :value="c.ConDefID" x-text="c.ConCode || c.name"></option>
</template>
</select>
</div>

View File

@ -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 = [];
}
},

View File

@ -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;

View File

@ -0,0 +1,259 @@
<?php
namespace Tests\Feature\TestDef;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
class TestDefSiteTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/tests';
protected $token;
protected function setUp(): void
{
parent::setUp();
// Generate Token
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => '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);
}
}

View File

@ -0,0 +1,237 @@
<?php
namespace Tests\Unit\TestDef;
use CodeIgniter\Test\CIUnitTestCase;
use App\Models\Test\TestDefSiteModel;
use App\Models\Test\TestDefTechModel;
use App\Models\Test\TestDefCalModel;
use App\Models\Test\TestDefGrpModel;
use App\Models\Test\TestMapModel;
class TestDefModelsTest extends CIUnitTestCase
{
protected $testDefSiteModel;
protected $testDefTechModel;
protected $testDefCalModel;
protected $testDefGrpModel;
protected $testMapModel;
protected function setUp(): void
{
parent::setUp();
$this->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);
}
}