tinyqc/app/Controllers/Api/EntryApiController.php
2026-02-12 09:01:59 +07:00

456 lines
16 KiB
PHP
Executable File

<?php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use CodeIgniter\API\ResponseTrait;
use App\Models\Master\MasterControlsModel;
use App\Models\Master\MasterTestsModel;
use App\Models\Qc\ResultsModel;
use App\Models\Qc\ControlTestsModel;
use App\Models\Qc\TestCommentsModel;
class EntryApiController extends BaseController
{
use ResponseTrait;
protected $controlModel;
protected $testModel;
protected $resultModel;
protected $controlTestModel;
protected $commentModel;
public function __construct()
{
$this->controlModel = new MasterControlsModel();
$this->testModel = new MasterTestsModel();
$this->resultModel = new ResultsModel();
$this->controlTestModel = new ControlTestsModel();
$this->commentModel = new TestCommentsModel();
}
/**
* 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']
];
}
// Get all test IDs for this control
$testIds = array_column($tests, 'testId');
// Fetch comments separately from test_comments table (comments are per test + date)
$commentsData = [];
if (!empty($testIds)) {
$comments = $this->commentModel->getByTestAndDateRange($testIds, $date, $date);
foreach ($comments as $c) {
$commentsData[$c['testId']] = $c['commentText'];
}
}
// Merge tests with existing values and comments
$data = [];
foreach ($tests as $t) {
$existing = $resultsByTest[$t['testId']] ?? null;
// Get comment from either the test_comments table or existing result
$comment = $commentsData[$t['testId']] ?? null;
if ($existing && isset($existing['resComment']) && $existing['resComment']) {
$comment = $existing['resComment']; // Existing result's comment takes precedence
}
$data[] = [
'controlTestId' => $t['id'],
'controlId' => $t['controlId'],
'testId' => $t['testId'],
'testName' => $t['testName'],
'testUnit' => $t['testUnit'],
'mean' => $t['mean'],
'sd' => $t['sd'],
'existingResult' => $existing,
'comment' => $comment
];
}
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 (test_id + date based)
$comments = $this->commentModel->getByTestAndMonth((int) $testId, $month);
// 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'],
'resDate' => $r['resDate']
];
}
// Map comments by date (comments are now per test + date)
$commentsByDate = [];
foreach ($comments as $c) {
$commentsByDate[$c['commentDate']] = [
'commentId' => $c['testCommentId'],
'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 date (comments are per test + date)
$resultDate = date('Y-m-d', strtotime($val['resDate']));
if (isset($commentsByDate[$resultDate])) {
$resultWithComment['resComment'] = $commentsByDate[$resultDate]['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 = ['testId', 'date', 'comment'];
foreach ($required as $field) {
if (!isset($input[$field])) {
return $this->failValidationErrors([$field => 'Required']);
}
}
$commentData = [
'test_id' => $input['testId'],
'comment_date' => $input['date'],
'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());
}
}
}