2026-01-20 16:47:11 +07:00
|
|
|
<?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\ResultCommentsModel;
|
|
|
|
|
|
|
|
|
|
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 ResultCommentsModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/entry/controls
|
|
|
|
|
* Get controls by dept (optional dept param)
|
|
|
|
|
* Optionally filter by date: only non-expired controls
|
|
|
|
|
*/
|
|
|
|
|
public function getControls()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$deptId = $this->request->getGet('dept_id');
|
|
|
|
|
$date = $this->request->getGet('date');
|
|
|
|
|
|
|
|
|
|
if ($deptId) {
|
|
|
|
|
$controls = $this->controlModel->where('dept_id', $deptId)->where('deleted_at', null)->findAll();
|
|
|
|
|
} else {
|
|
|
|
|
$controls = $this->controlModel->where('deleted_at', null)->findAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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']
|
|
|
|
|
];
|
|
|
|
|
}, $controls);
|
|
|
|
|
|
|
|
|
|
return $this->respond([
|
|
|
|
|
'status' => 'success',
|
|
|
|
|
'message' => 'fetch success',
|
|
|
|
|
'data' => $data
|
|
|
|
|
], 200);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
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,
|
2026-01-21 13:41:37 +07:00
|
|
|
'res_value' => $r['value'] !== '' ? (float) $r['value'] : null
|
2026-01-20 16:47:11 +07:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if ($data['res_value'] === null) {
|
|
|
|
|
continue; // Skip empty values
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
$resultId = $this->resultModel->upsertResult($data);
|
|
|
|
|
$savedIds[] = [
|
|
|
|
|
'testId' => $r['testId'],
|
|
|
|
|
'resultId' => $resultId
|
|
|
|
|
];
|
2026-01-20 16:47:11 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
// Get controls for this test with QC parameters (filter out expired)
|
|
|
|
|
$controls = $this->controlTestModel->getByTest((int) $testId, $month);
|
2026-01-20 16:47:11 +07:00
|
|
|
|
|
|
|
|
// Get existing results for this month
|
|
|
|
|
$results = $this->resultModel->getByMonth((int) $testId, $month);
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
// Get comments for this test (via results)
|
|
|
|
|
$comments = $this->commentModel->getByTest((int) $testId);
|
2026-01-20 16:47:11 +07:00
|
|
|
|
|
|
|
|
// 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'],
|
2026-01-21 13:41:37 +07:00
|
|
|
'resValue' => $r['resValue']
|
2026-01-20 16:47:11 +07:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
// Map comments by result_id
|
|
|
|
|
$commentsByResultId = [];
|
2026-01-20 16:47:11 +07:00
|
|
|
foreach ($comments as $c) {
|
2026-01-21 13:41:37 +07:00
|
|
|
$commentsByResultId[$c['resultId']] = [
|
2026-01-20 16:47:11 +07:00
|
|
|
'commentId' => $c['resultCommentId'],
|
2026-01-21 13:41:37 +07:00
|
|
|
'commentText' => $c['commentText']
|
2026-01-20 16:47:11 +07:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2026-01-21 13:41:37 +07:00
|
|
|
$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;
|
2026-01-20 16:47:11 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$controlsWithData[] = [
|
|
|
|
|
'controlTestId' => $c['id'],
|
|
|
|
|
'controlId' => $c['controlId'],
|
|
|
|
|
'controlName' => $c['controlName'],
|
|
|
|
|
'lot' => $c['lot'],
|
|
|
|
|
'producer' => $c['producer'],
|
|
|
|
|
'mean' => $c['mean'],
|
|
|
|
|
'sd' => $c['sd'],
|
2026-01-21 13:41:37 +07:00
|
|
|
'results' => $resultsArray
|
2026-01-20 16:47:11 +07:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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;
|
2026-01-21 13:41:37 +07:00
|
|
|
$resultIdMap = []; // Map controlId + day -> resultId
|
2026-01-20 16:47:11 +07:00
|
|
|
|
|
|
|
|
// Start transaction
|
|
|
|
|
$this->resultModel->db->transBegin();
|
|
|
|
|
|
|
|
|
|
foreach ($controls as $c) {
|
|
|
|
|
$controlId = $c['controlId'];
|
|
|
|
|
$results = $c['results'] ?? [];
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 16:47:11 +07:00
|
|
|
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);
|
2026-01-21 13:41:37 +07:00
|
|
|
$resultData = [
|
2026-01-20 16:47:11 +07:00
|
|
|
'control_id' => $controlId,
|
|
|
|
|
'test_id' => $testId,
|
|
|
|
|
'res_date' => $date,
|
|
|
|
|
'res_value' => (float) $value
|
|
|
|
|
];
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
$resultId = $this->resultModel->upsertResult($resultData);
|
|
|
|
|
$resultIdMap["{$controlId}_{$day}"] = $resultId;
|
2026-01-20 16:47:11 +07:00
|
|
|
$savedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Commit transaction
|
|
|
|
|
$this->resultModel->db->transCommit();
|
|
|
|
|
|
|
|
|
|
return $this->respond([
|
|
|
|
|
'status' => 'success',
|
2026-01-21 13:41:37 +07:00
|
|
|
'message' => "Saved {$savedCount} results",
|
|
|
|
|
'data' => [
|
|
|
|
|
'savedCount' => $savedCount,
|
|
|
|
|
'resultIdMap' => $resultIdMap
|
|
|
|
|
]
|
2026-01-20 16:47:11 +07:00
|
|
|
], 200);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Rollback transaction on error
|
|
|
|
|
$this->resultModel->db->transRollback();
|
|
|
|
|
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/entry/comment
|
2026-01-21 13:41:37 +07:00
|
|
|
* Save daily comment (single)
|
2026-01-20 16:47:11 +07:00
|
|
|
*/
|
|
|
|
|
public function saveComment()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$input = $this->request->getJSON(true);
|
|
|
|
|
|
2026-01-21 13:41:37 +07:00
|
|
|
$required = ['resultId', 'comment'];
|
2026-01-20 16:47:11 +07:00
|
|
|
foreach ($required as $field) {
|
|
|
|
|
if (!isset($input[$field])) {
|
|
|
|
|
return $this->failValidationErrors([$field => 'Required']);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$commentData = [
|
2026-01-21 13:41:37 +07:00
|
|
|
'result_id' => $input['resultId'],
|
|
|
|
|
'comment_text' => trim($input['comment'])
|
2026-01-20 16:47:11 +07:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|