controlModel = new MasterControlsModel(); $this->testModel = new MasterTestsModel(); $this->resultModel = new ResultsModel(); $this->controlTestModel = new ControlTestsModel(); $this->commentModel = new ResultCommentsModel(); } /** * GET /api/entry/controls * Get controls by dept (optional dept param) * Optionally filter by date: only non-expired controls */ public function getControls() { try { $keyword = $this->request->getGet('keyword'); $deptId = $this->request->getGet('dept_id'); $date = $this->request->getGet('date'); $controls = $this->controlModel->search($keyword, $deptId); // Debug logging log_message('debug', 'getControls: keyword=' . var_export($keyword, true) . ', deptId=' . var_export($deptId, true) . ', date=' . var_export($date, true) . ', found=' . count($controls)); // Filter expired controls if date provided if ($date) { $controls = array_filter($controls, function ($c) use ($date) { return $c['expDate'] === null || $c['expDate'] >= $date; }); } // Convert to camelCase (BaseModel already returns camelCase) $data = array_map(function ($c) { return [ 'id' => $c['controlId'], 'controlId' => $c['controlId'], 'controlName' => $c['controlName'], 'lot' => $c['lot'], 'producer' => $c['producer'], 'expDate' => $c['expDate'], 'deptName' => $c['deptName'] ?? null ]; }, $controls); log_message('debug', 'getControls: returning ' . count($data) . ' controls'); return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $data ], 200); } catch (\Exception $e) { log_message('error', 'getControls error: ' . $e->getMessage()); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * GET /api/entry/tests * Get tests for a control (by control_id) */ public function getTests() { try { $controlId = $this->request->getGet('control_id'); if (!$controlId) { return $this->failValidationErrors(['control_id' => 'Required']); } $tests = $this->controlTestModel->getByControl((int) $controlId); return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $tests ], 200); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * GET /api/entry/daily * Get existing results for date+control */ public function getDailyData() { try { $date = $this->request->getGet('date'); $controlId = $this->request->getGet('control_id'); if (!$date || !$controlId) { return $this->failValidationErrors(['date' => 'Required', 'control_id' => 'Required']); } // Get tests for this control $tests = $this->controlTestModel->getByControl((int) $controlId); // Get existing results for this date $existingResults = $this->resultModel->getByDateAndControl($date, (int) $controlId); // Map existing results by test_id $resultsByTest = []; foreach ($existingResults as $r) { $resultsByTest[$r['testId']] = [ 'resultId' => $r['id'], 'resValue' => $r['resValue'], 'resComment' => $r['resComment'] ]; } // Merge tests with existing values $data = []; foreach ($tests as $t) { $existing = $resultsByTest[$t['testId']] ?? null; $data[] = [ 'controlTestId' => $t['id'], 'controlId' => $t['controlId'], 'testId' => $t['testId'], 'testName' => $t['testName'], 'testUnit' => $t['testUnit'], 'mean' => $t['mean'], 'sd' => $t['sd'], 'existingResult' => $existing ]; } return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $data ], 200); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * POST /api/entry/daily * Save/update daily results (batch) */ public function saveDaily() { try { $input = $this->request->getJSON(true); if (!isset($input['date']) || !isset($input['results']) || !is_array($input['results'])) { return $this->failValidationErrors(['Invalid input']); } $date = $input['date']; $results = $input['results']; $savedIds = []; // Start transaction $this->resultModel->db->transBegin(); foreach ($results as $r) { if (!isset($r['controlId']) || !isset($r['testId']) || !isset($r['value'])) { continue; } $data = [ 'control_id' => $r['controlId'], 'test_id' => $r['testId'], 'res_date' => $date, 'res_value' => $r['value'] !== '' ? (float) $r['value'] : null ]; if ($data['res_value'] === null) { continue; // Skip empty values } $resultId = $this->resultModel->upsertResult($data); $savedIds[] = [ 'testId' => $r['testId'], 'resultId' => $resultId ]; } // Commit transaction $this->resultModel->db->transCommit(); return $this->respond([ 'status' => 'success', 'message' => 'Saved ' . count($savedIds) . ' results', 'data' => ['savedIds' => $savedIds] ], 200); } catch (\Exception $e) { // Rollback transaction on error $this->resultModel->db->transRollback(); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * GET /api/entry/monthly * Get monthly data by test */ public function getMonthlyData() { try { $testId = $this->request->getGet('test_id'); $month = $this->request->getGet('month'); // YYYY-MM if (!$testId || !$month) { return $this->failValidationErrors(['test_id' => 'Required', 'month' => 'Required']); } // Get test details $test = $this->testModel->find($testId); if (!$test) { return $this->failNotFound('Test not found'); } // Get controls for this test with QC parameters (filter out expired) $controls = $this->controlTestModel->getByTest((int) $testId, $month); // Get existing results for this month $results = $this->resultModel->getByMonth((int) $testId, $month); // Get comments for this test (via results) $comments = $this->commentModel->getByTest((int) $testId); // Map results by control_id and day $resultsByControl = []; foreach ($results as $r) { $day = (int) date('j', strtotime($r['resDate'])); $resultsByControl[$r['controlId']][$day] = [ 'resultId' => $r['id'], 'resValue' => $r['resValue'] ]; } // Map comments by result_id $commentsByResultId = []; foreach ($comments as $c) { $commentsByResultId[$c['resultId']] = [ 'commentId' => $c['resultCommentId'], 'commentText' => $c['commentText'] ]; } // Build controls with results array[31] $controlsWithData = []; foreach ($controls as $c) { $resultsByDay = $resultsByControl[$c['controlId']] ?? []; $resultsArray = array_fill(1, 31, null); foreach ($resultsByDay as $day => $val) { $resultWithComment = $val; // Add comment if exists for this result if (isset($commentsByResultId[$val['resultId']])) { $resultWithComment['resComment'] = $commentsByResultId[$val['resultId']]['commentText']; } else { $resultWithComment['resComment'] = null; } $resultsArray[$day] = $resultWithComment; } $controlsWithData[] = [ 'controlTestId' => $c['id'], 'controlId' => $c['controlId'], 'controlName' => $c['controlName'], 'lot' => $c['lot'], 'producer' => $c['producer'], 'mean' => $c['mean'], 'sd' => $c['sd'], 'results' => $resultsArray ]; } $data = [ 'test' => [ 'testId' => $test['testId'], 'testName' => $test['testName'], 'testUnit' => $test['testUnit'] ], 'month' => $month, 'controls' => $controlsWithData ]; return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $data ], 200); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * POST /api/entry/monthly * Save monthly batch (results + comments) */ public function saveMonthly() { try { $input = $this->request->getJSON(true); if (!isset($input['testId']) || !isset($input['month']) || !isset($input['controls'])) { return $this->failValidationErrors(['Invalid input']); } $testId = $input['testId']; $month = $input['month']; $controls = $input['controls']; // Validate month has valid days $daysInMonth = (int) date('t', strtotime($month . '-01')); $savedCount = 0; $resultIdMap = []; // Map controlId + day -> resultId // Start transaction $this->resultModel->db->transBegin(); foreach ($controls as $c) { $controlId = $c['controlId']; $results = $c['results'] ?? []; // Save results with optional comments foreach ($results as $day => $data) { // Handle both old format (value only) and new format (value + comment) if (is_array($data)) { $value = $data['value']; $commentText = $data['comment'] ?? null; } else { $value = $data; $commentText = null; } if ($value === null || $value === '') { continue; } // Validate day exists in month if ($day < 1 || $day > $daysInMonth) { continue; } $date = $month . '-' . str_pad($day, 2, '0', STR_PAD_LEFT); $resultData = [ 'control_id' => $controlId, 'test_id' => $testId, 'res_date' => $date, 'res_value' => (float) $value ]; $resultId = $this->resultModel->upsertResult($resultData); $resultIdMap["{$controlId}_{$day}"] = $resultId; $savedCount++; } } // Commit transaction $this->resultModel->db->transCommit(); return $this->respond([ 'status' => 'success', 'message' => "Saved {$savedCount} results", 'data' => [ 'savedCount' => $savedCount, 'resultIdMap' => $resultIdMap ] ], 200); } catch (\Exception $e) { // Rollback transaction on error $this->resultModel->db->transRollback(); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } /** * POST /api/entry/comment * Save daily comment (single) */ public function saveComment() { try { $input = $this->request->getJSON(true); $required = ['resultId', 'comment']; foreach ($required as $field) { if (!isset($input[$field])) { return $this->failValidationErrors([$field => 'Required']); } } $commentData = [ 'result_id' => $input['resultId'], 'comment_text' => trim($input['comment']) ]; $id = $this->commentModel->upsertComment($commentData); return $this->respond([ 'status' => 'success', 'message' => 'Comment saved', 'data' => ['id' => $id] ], 200); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } }