db = \Config\Database::connect(); $this->model = new \App\Models\Test\TestDefSiteModel; $this->modelCal = new \App\Models\Test\TestDefCalModel; $this->modelTech = new \App\Models\Test\TestDefTechModel; $this->modelGrp = new \App\Models\Test\TestDefGrpModel; $this->modelMap = new \App\Models\Test\TestMapModel; $this->modelValueSet = new \App\Models\ValueSet\ValueSetModel; // Validation rules for main test definition $this->rules = [ 'TestSiteCode' => 'required|min_length[3]|max_length[6]', 'TestSiteName' => 'required', 'TestType' => 'required', 'SiteID' => 'required' ]; } /** * GET /v1/tests * GET /v1/tests/site * List all tests with optional filtering */ public function index() { $siteId = $this->request->getGet('SiteID'); $testType = $this->request->getGet('TestType'); $visibleScr = $this->request->getGet('VisibleScr'); $visibleRpt = $this->request->getGet('VisibleRpt'); $keyword = $this->request->getGet('TestSiteName'); $builder = $this->db->table('testdefsite') ->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType, testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt, testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate, valueset.VValue as TypeCode, valueset.VDesc as TypeName") ->join("valueset", "valueset.VID=testdefsite.TestType", "left") ->where('testdefsite.EndDate IS NULL'); if ($siteId) { $builder->where('testdefsite.SiteID', $siteId); } if ($testType) { $builder->where('testdefsite.TestType', $testType); } if ($visibleScr !== null) { $builder->where('testdefsite.VisibleScr', $visibleScr); } if ($visibleRpt !== null) { $builder->where('testdefsite.VisibleRpt', $visibleRpt); } if ($keyword) { $builder->like('testdefsite.TestSiteName', $keyword); } $rows = $builder->orderBy('testdefsite.SeqScr', 'ASC')->get()->getResultArray(); if (empty($rows)) { return $this->respond([ 'status' => 'success', 'message' => "No data.", 'data' => [] ], 200); } return $this->respond([ 'status' => 'success', 'message'=> "Data fetched successfully", 'data' => $rows ], 200); } /** * GET /v1/tests/{id} * GET /v1/tests/site/{id} * Get single test by ID with all related details */ public function show($id = null) { if (!$id) return $this->failValidationErrors('TestSiteID is required'); $row = $this->model->select("testdefsite.*, valueset.VValue as TypeCode, valueset.VDesc as TypeName") ->join("valueset", "valueset.VID=testdefsite.TestType", "left") ->where("testdefsite.TestSiteID", $id) ->find($id); if (!$row) { return $this->respond([ 'status' => 'success', 'message' => "No data.", 'data' => null ], 200); } // Load related details based on TestType $typeCode = $row['TypeCode'] ?? ''; if ($typeCode === 'CALC') { // Load calculation details $row['testdefcal'] = $this->db->table('testdefcal') ->select('testdefcal.*, d.DisciplineName, dept.DepartmentName') ->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left') ->join('department dept', 'dept.DepartmentID=testdefcal.DepartmentID', 'left') ->where('testdefcal.TestSiteID', $id) ->where('testdefcal.EndDate IS NULL') ->get()->getResultArray(); // Load test mappings $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } elseif ($typeCode === 'GROUP') { // Load group members with test details $row['testdefgrp'] = $this->db->table('testdefgrp') ->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName, t.TestType, vs.VValue as MemberTypeCode') ->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left') ->join('valueset vs', 'vs.VID=t.TestType', 'left') ->where('testdefgrp.TestSiteID', $id) ->where('testdefgrp.EndDate IS NULL') ->orderBy('testdefgrp.TestGrpID', 'ASC') ->get()->getResultArray(); // Load test mappings $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } elseif ($typeCode === 'TITLE') { // Load test mappings only for TITLE type $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } else { // TEST or PARAM - load technical details $row['testdeftech'] = $this->db->table('testdeftech') ->select('testdeftech.*, d.DisciplineName, dept.DepartmentName') ->join('discipline d', 'd.DisciplineID=testdeftech.DisciplineID', 'left') ->join('department dept', 'dept.DepartmentID=testdeftech.DepartmentID', 'left') ->where('testdeftech.TestSiteID', $id) ->where('testdeftech.EndDate IS NULL') ->get()->getResultArray(); // Load test mappings $row['testmap'] = $this->modelMap->where('TestSiteID', $id)->where('EndDate IS NULL')->findAll(); } return $this->respond([ 'status' => 'success', 'message'=> "Data fetched successfully", 'data' => $row ], 200); } /** * POST /v1/tests * POST /v1/tests/site * Create new test definition */ public function create() { $input = $this->request->getJSON(true); if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); } $this->db->transStart(); try { // 1. Insert into Main Table (testdefsite) $testSiteData = [ 'SiteID' => $input['SiteID'], 'TestSiteCode' => $input['TestSiteCode'], 'TestSiteName' => $input['TestSiteName'], 'TestType' => $input['TestType'], 'Description' => $input['Description'] ?? null, 'SeqScr' => $input['SeqScr'] ?? 0, 'SeqRpt' => $input['SeqRpt'] ?? 0, 'IndentLeft' => $input['IndentLeft'] ?? 0, 'FontStyle' => $input['FontStyle'] ?? null, 'VisibleScr' => $input['VisibleScr'] ?? 1, 'VisibleRpt' => $input['VisibleRpt'] ?? 1, 'CountStat' => $input['CountStat'] ?? 1, 'StartDate' => $input['StartDate'] ?? date('Y-m-d H:i:s') ]; $id = $this->model->insert($testSiteData); if (!$id) { throw new \Exception("Failed to insert main test definition"); } // 2. Handle Details based on TestType $this->handleDetails($id, $input, 'insert'); $this->db->transComplete(); if ($this->db->transStatus() === false) { return $this->failServerError('Transaction failed'); } return $this->respondCreated([ 'status' => 'created', 'message' => "Test created successfully", 'data' => ['TestSiteId' => $id] ]); } catch (\Exception $e) { $this->db->transRollback(); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * PUT/PATCH /v1/tests/{id} * PUT/PATCH /v1/tests/site/{id} * Update existing test definition */ public function update($id = null) { $input = $this->request->getJSON(true); // Determine ID if (!$id && isset($input["TestSiteID"])) { $id = $input["TestSiteID"]; } if (!$id) { return $this->failValidationErrors('TestSiteID is required.'); } // Verify record exists $existing = $this->model->find($id); if (!$existing) { return $this->failNotFound('Test not found'); } $this->db->transStart(); try { // 1. Update Main Table $testSiteData = []; $allowedUpdateFields = ['TestSiteCode', 'TestSiteName', 'TestType', 'Description', 'SeqScr', 'SeqRpt', 'IndentLeft', 'FontStyle', 'VisibleScr', 'VisibleRpt', 'CountStat', 'StartDate']; foreach ($allowedUpdateFields as $field) { if (isset($input[$field])) { $testSiteData[$field] = $input[$field]; } } if (!empty($testSiteData)) { $this->model->update($id, $testSiteData); } // 2. Handle Details $this->handleDetails($id, $input, 'update'); $this->db->transComplete(); if ($this->db->transStatus() === false) { return $this->failServerError('Transaction failed'); } return $this->respond([ 'status' => 'success', 'message' => "Test updated successfully", 'data' => ['TestSiteId' => $id] ]); } catch (\Exception $e) { $this->db->transRollback(); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * DELETE /v1/tests/{id} * DELETE /v1/tests/site/{id} * Soft delete test by setting EndDate */ public function delete($id = null) { $input = $this->request->getJSON(true); // Determine ID if (!$id && isset($input["TestSiteID"])) { $id = $input["TestSiteID"]; } if (!$id) { return $this->failValidationErrors('TestSiteID is required.'); } // Verify record exists $existing = $this->model->find($id); if (!$existing) { return $this->failNotFound('Test not found'); } // Check if already disabled if (!empty($existing['EndDate'])) { return $this->failValidationErrors('Test is already disabled'); } $this->db->transStart(); try { $now = date('Y-m-d H:i:s'); // 1. Soft delete main record $this->model->update($id, ['EndDate' => $now]); // 2. Get TestType to handle related records $testType = $existing['TestType']; $vs = $this->modelValueSet->find($testType); $typeCode = $vs['VValue'] ?? ''; // 3. Soft delete related records based on TestType if ($typeCode === 'CALC') { $this->db->table('testdefcal') ->where('TestSiteID', $id) ->update(['EndDate' => $now]); } elseif ($typeCode === 'GROUP') { $this->db->table('testdefgrp') ->where('TestSiteID', $id) ->update(['EndDate' => $now]); } elseif (in_array($typeCode, ['TEST', 'PARAM'])) { $this->db->table('testdeftech') ->where('TestSiteID', $id) ->update(['EndDate' => $now]); } // 4. Soft delete test mappings $this->db->table('testmap') ->where('TestSiteID', $id) ->update(['EndDate' => $now]); $this->db->transComplete(); if ($this->db->transStatus() === false) { return $this->failServerError('Transaction failed'); } return $this->respond([ 'status' => 'success', 'message' => "Test disabled successfully", 'data' => ['TestSiteId' => $id, 'EndDate' => $now] ]); } catch (\Exception $e) { $this->db->transRollback(); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * Helper to handle inserting/updating sub-tables based on TestType */ private function handleDetails($testSiteID, $input, $action) { $testTypeID = $input['TestType'] ?? null; // If update and TestType not in payload, fetch from DB if (!$testTypeID && $action === 'update') { $existing = $this->model->find($testSiteID); $testTypeID = $existing['TestType'] ?? null; } if (!$testTypeID) return; // Get Type Code (TEST, PARAM, CALC, GROUP, TITLE) $vs = $this->modelValueSet->find($testTypeID); $typeCode = $vs['VValue'] ?? ''; // Get details data from input $details = $input['details'] ?? $input; $details['TestSiteID'] = $testSiteID; $details['SiteID'] = $input['SiteID'] ?? 1; switch ($typeCode) { case 'CALC': $this->saveCalcDetails($testSiteID, $details, $action); break; case 'GROUP': $this->saveGroupDetails($testSiteID, $details, $input, $action); break; case 'TITLE': // TITLE type only has testdefsite, no additional details needed // But we should save test mappings if provided if (isset($input['testmap']) && is_array($input['testmap'])) { $this->saveTestMap($testSiteID, $input['testmap'], $action); } break; case 'TEST': case 'PARAM': default: $this->saveTechDetails($testSiteID, $details, $action, $typeCode); break; } // Save test mappings for TEST and CALC types as well if (in_array($typeCode, ['TEST', 'CALC']) && isset($input['testmap']) && is_array($input['testmap'])) { $this->saveTestMap($testSiteID, $input['testmap'], $action); } } /** * Save technical details for TEST and PARAM types */ private function saveTechDetails($testSiteID, $data, $action, $typeCode) { $techData = [ 'TestSiteID' => $testSiteID, 'DisciplineID' => $data['DisciplineID'] ?? null, 'DepartmentID' => $data['DepartmentID'] ?? null, 'ResultType' => $data['ResultType'] ?? null, 'RefType' => $data['RefType'] ?? null, 'VSet' => $data['VSet'] ?? null, 'ReqQty' => $data['ReqQty'] ?? null, 'ReqQtyUnit' => $data['ReqQtyUnit'] ?? null, 'Unit1' => $data['Unit1'] ?? null, 'Factor' => $data['Factor'] ?? null, 'Unit2' => $data['Unit2'] ?? null, 'Decimal' => $data['Decimal'] ?? 2, 'CollReq' => $data['CollReq'] ?? null, 'Method' => $data['Method'] ?? null, 'ExpectedTAT' => $data['ExpectedTAT'] ?? null ]; if ($action === 'update') { $exists = $this->db->table('testdeftech') ->where('TestSiteID', $testSiteID) ->where('EndDate IS NULL') ->get()->getRowArray(); if ($exists) { $this->modelTech->update($exists['TestTechID'], $techData); } else { $this->modelTech->insert($techData); } } else { $this->modelTech->insert($techData); } } /** * Save calculation details for CALC type */ private function saveCalcDetails($testSiteID, $data, $action) { $calcData = [ 'TestSiteID' => $testSiteID, 'DisciplineID' => $data['DisciplineID'] ?? null, 'DepartmentID' => $data['DepartmentID'] ?? null, 'FormulaInput' => $data['FormulaInput'] ?? null, 'FormulaCode' => $data['FormulaCode'] ?? $data['Formula'] ?? null, 'RefType' => $data['RefType'] ?? 'NMRC', 'Unit1' => $data['Unit1'] ?? $data['ResultUnit'] ?? null, 'Factor' => $data['Factor'] ?? null, 'Unit2' => $data['Unit2'] ?? null, 'Decimal' => $data['Decimal'] ?? 2, 'Method' => $data['Method'] ?? null ]; if ($action === 'update') { $exists = $this->db->table('testdefcal') ->where('TestSiteID', $testSiteID) ->where('EndDate IS NULL') ->get()->getRowArray(); if ($exists) { $this->modelCal->update($exists['TestCalID'], $calcData); } else { $this->modelCal->insert($calcData); } } else { $this->modelCal->insert($calcData); } } /** * Save group details for GROUP type */ private function saveGroupDetails($testSiteID, $data, $input, $action) { if ($action === 'update') { // Soft delete existing members $this->db->table('testdefgrp') ->where('TestSiteID', $testSiteID) ->update(['EndDate' => date('Y-m-d H:i:s')]); } // Get members from details or input $members = $data['members'] ?? ($input['Members'] ?? []); if (is_array($members)) { foreach ($members as $m) { $memberID = is_array($m) ? ($m['Member'] ?? ($m['TestSiteID'] ?? null)) : $m; if ($memberID) { $this->modelGrp->insert([ 'TestSiteID' => $testSiteID, 'Member' => $memberID ]); } } } } /** * Save test mappings */ private function saveTestMap($testSiteID, $mappings, $action) { if ($action === 'update') { // Soft delete existing mappings $this->db->table('testmap') ->where('TestSiteID', $testSiteID) ->update(['EndDate' => date('Y-m-d H:i:s')]); } if (is_array($mappings)) { foreach ($mappings as $map) { $mapData = [ 'TestSiteID' => $testSiteID, 'HostType' => $map['HostType'] ?? null, 'HostID' => $map['HostID'] ?? null, 'HostDataSource' => $map['HostDataSource'] ?? null, 'HostTestCode' => $map['HostTestCode'] ?? null, 'HostTestName' => $map['HostTestName'] ?? null, 'ClientType' => $map['ClientType'] ?? null, 'ClientID' => $map['ClientID'] ?? null, 'ClientDataSource' => $map['ClientDataSource'] ?? null, 'ConDefID' => $map['ConDefID'] ?? null, 'ClientTestCode' => $map['ClientTestCode'] ?? null, 'ClientTestName' => $map['ClientTestName'] ?? null ]; $this->modelMap->insert($mapData); } } } }