db = \Config\Database::connect(); $this->model = new \App\Models\Test\TestDefSiteModel; $this->modelCal = new \App\Models\Test\TestDefCalModel; $this->modelGrp = new \App\Models\Test\TestDefGrpModel; $this->modelMap = new \App\Models\Test\TestMapModel; $this->modelRefNum = new \App\Models\RefRange\RefNumModel; $this->modelRefTxt = new \App\Models\RefRange\RefTxtModel; $this->rules = [ 'TestSiteCode' => 'required', 'TestSiteName' => 'required', 'TestType' => 'required', 'SiteID' => 'required' ]; } 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, COALESCE(testdefsite.DisciplineID, cal.DisciplineID) as DisciplineID, COALESCE(testdefsite.DepartmentID, cal.DepartmentID) as DepartmentID, d.DisciplineName, dept.DepartmentName") ->join('testdefcal cal', 'cal.TestSiteID = testdefsite.TestSiteID AND cal.EndDate IS NULL', 'left') ->join('discipline d', 'd.DisciplineID = COALESCE(testdefsite.DisciplineID, cal.DisciplineID)', 'left') ->join('department dept', 'dept.DepartmentID = COALESCE(testdefsite.DepartmentID, cal.DepartmentID)', '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); } $rows = ValueSet::transformLabels($rows, [ 'TestType' => 'test_type', ]); return $this->respond(['status' => 'success', 'message' => "Data fetched successfully", 'data' => $rows], 200); } public function show($id = null) { if (!$id) return $this->failValidationErrors('TestSiteID is required'); $row = $this->model->select("testdefsite.*") ->where("testdefsite.TestSiteID", $id) ->find($id); if (!$row) { return $this->respond(['status' => 'success', 'message' => "No data.", 'data' => null], 200); } $row = ValueSet::transformLabels([$row], [ 'TestType' => 'test_type', ])[0]; $typeCode = $row['TestType'] ?? ''; if ($typeCode === 'CALC') { $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(); $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } elseif ($typeCode === 'GROUP') { $row['testdefgrp'] = $this->db->table('testdefgrp') ->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType') ->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left') ->where('testdefgrp.TestSiteID', $id) ->where('testdefgrp.EndDate IS NULL') ->orderBy('testdefgrp.TestGrpID', 'ASC') ->get()->getResultArray(); $row['testdefgrp'] = ValueSet::transformLabels($row['testdefgrp'], [ 'TestType' => 'test_type', ]); $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } elseif ($typeCode === 'TITLE') { $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } else { // Technical details are now stored directly in testdefsite $row['testdeftech'] = $this->db->table('testdefsite') ->select('testdefsite.*, d.DisciplineName, dept.DepartmentName') ->join('discipline d', 'd.DisciplineID=testdefsite.DisciplineID', 'left') ->join('department dept', 'dept.DepartmentID=testdefsite.DepartmentID', 'left') ->where('testdefsite.TestSiteID', $id) ->where('testdefsite.EndDate IS NULL') ->get()->getResultArray(); $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); if (!empty($row['testdeftech'])) { $techData = $row['testdeftech'][0]; $refType = $techData['RefType']; $resultType = $techData['ResultType'] ?? ''; // Use TestValidationService to determine reference table if (TestValidationService::usesRefNum($resultType, $refType)) { $refnumData = $this->modelRefNum ->where('TestSiteID', $id) ->where('EndDate IS NULL') ->orderBy('Display', 'ASC') ->findAll(); $row['refnum'] = array_map(function ($r) { return [ 'RefNumID' => $r['RefNumID'], 'NumRefType' => $r['NumRefType'], 'NumRefTypeLabel' => $r['NumRefType'] ? ValueSet::getLabel('numeric_ref_type', $r['NumRefType']) : '', 'RangeType' => $r['RangeType'], 'RangeTypeLabel' => $r['RangeType'] ? ValueSet::getLabel('range_type', $r['RangeType']) : '', 'Sex' => $r['Sex'], 'SexLabel' => $r['Sex'] ? ValueSet::getLabel('gender', $r['Sex']) : '', 'LowSign' => $r['LowSign'], 'LowSignLabel' => $r['LowSign'] ? ValueSet::getLabel('math_sign', $r['LowSign']) : '', 'HighSign' => $r['HighSign'], 'HighSignLabel' => $r['HighSign'] ? ValueSet::getLabel('math_sign', $r['HighSign']) : '', 'High' => $r['High'] !== null ? (float) $r['High'] : null, 'Low' => $r['Low'] !== null ? (float) $r['Low'] : null, 'AgeStart' => (int) $r['AgeStart'], 'AgeEnd' => (int) $r['AgeEnd'], 'Flag' => $r['Flag'], 'Interpretation' => $r['Interpretation'] ]; }, $refnumData ?? []); } if (TestValidationService::usesRefTxt($resultType, $refType)) { $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' => $r['TxtRefType'], 'TxtRefTypeLabel' => $r['TxtRefType'] ? ValueSet::getLabel('text_ref_type', $r['TxtRefType']) : '', 'Sex' => $r['Sex'], 'SexLabel' => $r['Sex'] ? ValueSet::getLabel('gender', $r['Sex']) : '', 'AgeStart' => (int) $r['AgeStart'], 'AgeEnd' => (int) $r['AgeEnd'], 'RefTxt' => $r['RefTxt'], 'Flag' => $r['Flag'] ]; }, $reftxtData ?? []); } } } // $row['refTypeOptions'] = ValueSet::getOptions('reference_type'); // $row['sexOptions'] = ValueSet::getOptions('gender'); // $row['mathSignOptions'] = ValueSet::getOptions('math_sign'); return $this->respond(['status' => 'success', 'message' => "Data fetched successfully", 'data' => $row], 200); } public function create() { $input = $this->request->getJSON(true); if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); } // Validate TestType, ResultType, and RefType combinations $testType = $input['TestType'] ?? ''; $details = $input['details'] ?? $input; $resultType = $details['ResultType'] ?? ''; $refType = $details['RefType'] ?? ''; // Set defaults for CALC, GROUP, TITLE types if (TestValidationService::isCalc($testType)) { $resultType = 'NMRIC'; $refType = $refType ?: 'RANGE'; } elseif (TestValidationService::isGroup($testType) || TestValidationService::isTitle($testType)) { $resultType = 'NORES'; $refType = 'NOREF'; } if ($resultType && $refType) { $validation = TestValidationService::validate($testType, $resultType, $refType); if (!$validation['valid']) { return $this->failValidationErrors(['type_validation' => $validation['error']]); } } $this->db->transStart(); try { $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"); } $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()); } } public function update($id = null) { $input = $this->request->getJSON(true); if (!$id && isset($input["TestSiteID"])) { $id = $input["TestSiteID"]; } if (!$id) { return $this->failValidationErrors('TestSiteID is required.'); } $existing = $this->model->find($id); if (!$existing) { return $this->failNotFound('Test not found'); } // Validate TestType, ResultType, and RefType combinations if provided $testType = $input['TestType'] ?? $existing['TestType'] ?? ''; $details = $input['details'] ?? $input; $resultType = $details['ResultType'] ?? $existing['ResultType'] ?? ''; $refType = $details['RefType'] ?? $existing['RefType'] ?? ''; // Set defaults for CALC, GROUP, TITLE types if (TestValidationService::isCalc($testType)) { $resultType = 'NMRIC'; $refType = $refType ?: 'RANGE'; } elseif (TestValidationService::isGroup($testType) || TestValidationService::isTitle($testType)) { $resultType = 'NORES'; $refType = 'NOREF'; } if ($resultType && $refType) { $validation = TestValidationService::validate($testType, $resultType, $refType); if (!$validation['valid']) { return $this->failValidationErrors(['type_validation' => $validation['error']]); } } $this->db->transStart(); try { $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); } $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()); } } public function delete($id = null) { $input = $this->request->getJSON(true); if (!$id && isset($input["TestSiteID"])) { $id = $input["TestSiteID"]; } if (!$id) { return $this->failValidationErrors('TestSiteID is required.'); } $existing = $this->model->find($id); if (!$existing) { return $this->failNotFound('Test not found'); } if (!empty($existing['EndDate'])) { return $this->failValidationErrors('Test is already disabled'); } $this->db->transStart(); try { $now = date('Y-m-d H:i:s'); $this->model->update($id, ['EndDate' => $now]); $testType = $existing['TestType']; $typeCode = $testType; if (TestValidationService::isCalc($typeCode)) { $this->db->table('testdefcal') ->where('TestSiteID', $id) ->update(['EndDate' => $now]); } elseif (TestValidationService::isGroup($typeCode)) { $this->db->table('testdefgrp') ->where('TestSiteID', $id) ->update(['EndDate' => $now]); } elseif (TestValidationService::isTechnicalTest($typeCode)) { $this->modelRefNum->where('TestSiteID', $id)->set('EndDate', $now)->update(); $this->modelRefTxt->where('TestSiteID', $id)->set('EndDate', $now)->update(); } $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()); } } private function handleDetails($testSiteID, $input, $action) { $testTypeID = $input['TestType'] ?? null; if (!$testTypeID && $action === 'update') { $existing = $this->model->find($testSiteID); $testTypeID = $existing['TestType'] ?? null; } if (!$testTypeID) return; $typeCode = $testTypeID; $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': 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); if (in_array($typeCode, ['TEST', 'PARAM']) && isset($details['RefType'])) { $refType = (string) $details['RefType']; $resultType = $details['ResultType'] ?? ''; // Use TestValidationService to determine which reference table to use if (TestValidationService::usesRefNum($resultType, $refType) && isset($input['refnum']) && is_array($input['refnum'])) { $this->saveRefNumRanges($testSiteID, $input['refnum'], $action, $input['SiteID'] ?? 1); } if (TestValidationService::usesRefTxt($resultType, $refType) && isset($input['reftxt']) && is_array($input['reftxt'])) { $this->saveRefTxtRanges($testSiteID, $input['reftxt'], $action, $input['SiteID'] ?? 1); } } break; } if ((TestValidationService::isTechnicalTest($typeCode) || TestValidationService::isCalc($typeCode)) && isset($input['testmap']) && is_array($input['testmap'])) { $this->saveTestMap($testSiteID, $input['testmap'], $action); } } private function saveTechDetails($testSiteID, $data, $action, $typeCode) { // Technical details are now stored directly in testdefsite table $techData = [ '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 ]; // Update the testdefsite record directly $this->model->update($testSiteID, $techData); } 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' => $range['NumRefType'], 'RangeType' => $range['RangeType'], 'Sex' => $range['Sex'], 'AgeStart' => (int) ($range['AgeStart'] ?? 0), 'AgeEnd' => (int) ($range['AgeEnd'] ?? 150), 'LowSign' => !empty($range['LowSign']) ? $range['LowSign'] : null, 'Low' => !empty($range['Low']) ? (float) $range['Low'] : null, 'HighSign' => !empty($range['HighSign']) ? $range['HighSign'] : null, 'High' => !empty($range['High']) ? (float) $range['High'] : null, 'Flag' => $range['Flag'] ?? null, 'Interpretation' => $range['Interpretation'] ?? null, 'Display' => $index, 'CreateDate' => date('Y-m-d H:i:s') ]); } } 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' => $range['TxtRefType'], 'Sex' => $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') ]); } } 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, 'ResultType' => 'NMRIC', // CALC always has NMRIC result type 'RefType' => $data['RefType'] ?? 'RANGE', '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); } } private function saveGroupDetails($testSiteID, $data, $input, $action) { if ($action === 'update') { $this->db->table('testdefgrp') ->where('TestSiteID', $testSiteID) ->update(['EndDate' => date('Y-m-d H:i:s')]); } $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 ]); } } } } private function saveTestMap($testSiteID, $mappings, $action) { if ($action === 'update') { $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); } } } }