374 lines
13 KiB
PHP
374 lines
13 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace api\modules\v1\controllers;
|
||
|
|
|
||
|
|
use api\components\Controller;
|
||
|
|
use yii\web\NotFoundHttpException;
|
||
|
|
use Yii;
|
||
|
|
use common\models\HisRequest;
|
||
|
|
use api\models\search\HisRequestSearch;
|
||
|
|
use common\models\HisObr;
|
||
|
|
use common\models\HisObx;
|
||
|
|
use OpenApi\Attributes as OA;
|
||
|
|
|
||
|
|
#[OA\Tag(name: "HisRequest", description: "Hospital import endpoints")]
|
||
|
|
class HisRequestController extends Controller
|
||
|
|
{
|
||
|
|
/*
|
||
|
|
#[OA\Get(
|
||
|
|
path: "/his-request",
|
||
|
|
summary: "Get list hospital import",
|
||
|
|
operationId: "hisRequestIndex",
|
||
|
|
tags: ["HisRequest"],
|
||
|
|
responses: [
|
||
|
|
new OA\Response(
|
||
|
|
response: 200,
|
||
|
|
description: "OK",
|
||
|
|
content: new OA\JsonContent(
|
||
|
|
type: "object",
|
||
|
|
properties: [
|
||
|
|
new OA\Property(property: "count", type: "integer", example: 10),
|
||
|
|
new OA\Property(
|
||
|
|
property: "dataModels",
|
||
|
|
type: "array",
|
||
|
|
items: new OA\Items(ref: "#/components/schemas/HisRequest")
|
||
|
|
),
|
||
|
|
new OA\Property(property: "total", type: "integer", example: 100),
|
||
|
|
]
|
||
|
|
)
|
||
|
|
),
|
||
|
|
new OA\Response(
|
||
|
|
response: 401,
|
||
|
|
description: "Unauthorized",
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/Unauthorized")
|
||
|
|
),
|
||
|
|
]
|
||
|
|
)]
|
||
|
|
public function actionIndex()
|
||
|
|
{
|
||
|
|
$search['HisRequestSearch'] = Yii::$app->request->queryParams;
|
||
|
|
$searchModel = new HisRequestSearch();
|
||
|
|
$dataProvider = $searchModel->search($search);
|
||
|
|
|
||
|
|
return $this->apiCollection([
|
||
|
|
'count' => $dataProvider->count,
|
||
|
|
'dataModels' => $dataProvider->models,
|
||
|
|
], $dataProvider->totalCount);
|
||
|
|
}*/
|
||
|
|
|
||
|
|
#[OA\Post(
|
||
|
|
path: "/his-request",
|
||
|
|
summary: "Create data hospital request API",
|
||
|
|
operationId: "hisRequestCreate",
|
||
|
|
tags: ["HisRequest"],
|
||
|
|
security: [["bearerAuth" => []]],
|
||
|
|
requestBody: new OA\RequestBody(
|
||
|
|
required: true,
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/CreateHisRequest")
|
||
|
|
),
|
||
|
|
responses: [
|
||
|
|
new OA\Response(
|
||
|
|
response: 201,
|
||
|
|
description: "Created",
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/HisRequest")
|
||
|
|
),
|
||
|
|
new OA\Response(
|
||
|
|
response: 422,
|
||
|
|
description: "Validation error",
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/ErrorValidate")
|
||
|
|
),
|
||
|
|
]
|
||
|
|
)]
|
||
|
|
public function actionCreate()
|
||
|
|
{
|
||
|
|
|
||
|
|
|
||
|
|
// ---------- Helper: HL7 DTM converters ----------
|
||
|
|
$hl7Date = function (?string $s): ?string {
|
||
|
|
$s = trim((string)$s);
|
||
|
|
if ($s === '') return null;
|
||
|
|
$y = substr($s, 0, 4);
|
||
|
|
$m = substr($s, 4, 2);
|
||
|
|
$d = substr($s, 6, 2);
|
||
|
|
if (!checkdate((int)$m, (int)$d, (int)$y)) return null;
|
||
|
|
return sprintf('%04d-%02d-%02d', $y, $m, $d);
|
||
|
|
};
|
||
|
|
$hl7DateTime = function (?string $s): ?string {
|
||
|
|
$s = trim((string)$s);
|
||
|
|
if ($s === '') return null;
|
||
|
|
$y = substr($s, 0, 4);
|
||
|
|
$m = substr($s, 4, 2);
|
||
|
|
$d = substr($s, 6, 2);
|
||
|
|
$H = strlen($s) >= 10 ? substr($s, 8, 2) : '00';
|
||
|
|
$i = strlen($s) >= 12 ? substr($s, 10, 2) : '00';
|
||
|
|
$S = strlen($s) >= 14 ? substr($s, 12, 2) : '00';
|
||
|
|
if (!checkdate((int)$m, (int)$d, (int)$y)) return null;
|
||
|
|
return sprintf('%04d-%02d-%02d %02d:%02d:%02d', $y, $m, $d, $H, $i, $S);
|
||
|
|
};
|
||
|
|
|
||
|
|
// ---------- Read & normalize body ----------
|
||
|
|
$root = Yii::$app->request->getBodyParams();
|
||
|
|
// payload อาจมาเป็น {'HisRequest':{...}} หรือเป็น {...} ตรง ๆ
|
||
|
|
$reqData = $root['HisRequest'] ?? $root;
|
||
|
|
|
||
|
|
//error_log('[HisRequest] INCOMING: ' . print_r($reqData, true));
|
||
|
|
if (HisRequest::find()->where(['message_control_id' => $reqData['message_control_id'] ?? null])->exists()) {
|
||
|
|
return $this->apiConflict(
|
||
|
|
'DUPLICATE_MESSAGE',
|
||
|
|
'Duplicate message_control_id: ' . ($reqData['message_control_id'] ?? 'unknown'),
|
||
|
|
['message_control_id' => $reqData['message_control_id'] ?? null]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
$tx = Yii::$app->db->beginTransaction();
|
||
|
|
try {
|
||
|
|
// =======================
|
||
|
|
// 1) SAVE his_request
|
||
|
|
// =======================
|
||
|
|
$req = new HisRequest();
|
||
|
|
//$req->import_at = date('Y-m-d H:i:s');
|
||
|
|
|
||
|
|
// แปลง DTM ก่อนเซฟ (ถ้า model ใช้ DATE/DATETIME)
|
||
|
|
$reqData['request_datetime'] = $hl7DateTime($reqData['request_datetime'] ?? null);
|
||
|
|
$reqData['date_time_of_message'] = $hl7DateTime($reqData['date_time_of_message'] ?? null);
|
||
|
|
$reqData['date_time_of_birth'] = $hl7Date($reqData['date_time_of_birth'] ?? null);
|
||
|
|
$reqData['admit_date_time'] = $hl7DateTime($reqData['admit_date_time'] ?? null);
|
||
|
|
$reqData['discharge_date_time'] = $hl7DateTime($reqData['discharge_date_time'] ?? null);
|
||
|
|
$reqData['date_time_of_transaction'] = $hl7DateTime($reqData['date_time_of_transaction'] ?? null);
|
||
|
|
|
||
|
|
// เผื่อ message_type แยกแล้ว ใส่ทั้งสาม field
|
||
|
|
if (!empty($reqData['message_type']) && empty($reqData['message_type_code'])) {
|
||
|
|
$parts = explode('^', $reqData['message_type']);
|
||
|
|
$reqData['message_type_code'] = $parts[0] ?? null;
|
||
|
|
$reqData['message_type_name'] = $parts[1] ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// map ลง model (ให้แน่ใจว่า HisRequest::rules() รับฟิลด์ทั้งหมดเป็น safe หรือกำหนดตามเหมาะสม)
|
||
|
|
$req->attributes = $reqData;
|
||
|
|
|
||
|
|
if (!$req->save()) {
|
||
|
|
error_log('[HisRequest] ERROR: ' . json_encode($req->errors, JSON_UNESCAPED_UNICODE));
|
||
|
|
throw new \Exception('Save his_request failed');
|
||
|
|
}
|
||
|
|
|
||
|
|
// =======================
|
||
|
|
// 2) SAVE his_obr (+ his_obx)
|
||
|
|
// =======================
|
||
|
|
$orders = $reqData['orders'] ?? [];
|
||
|
|
foreach ($orders as $oIdx => $o) {
|
||
|
|
if (empty($o['obr'])) continue; // ไม่มี OBR ข้าม
|
||
|
|
|
||
|
|
$obrIn = $o['obr'];
|
||
|
|
|
||
|
|
$obr = new HisObr();
|
||
|
|
$obr->his_request_id = $req->id;
|
||
|
|
|
||
|
|
// แปลง DTM OBR
|
||
|
|
$obrIn['requested_date_time'] = $hl7DateTime($obrIn['requested_date_time'] ?? null);
|
||
|
|
|
||
|
|
// map fields (ตาม schema ล่าสุด)
|
||
|
|
$mapFields = [
|
||
|
|
'obr_set_id',
|
||
|
|
'obr_placer_order_number',
|
||
|
|
'obr_filler_order_number',
|
||
|
|
// raw และ flatten ของ universal_service
|
||
|
|
'universal_service',
|
||
|
|
'universal_service_id',
|
||
|
|
'universal_service_text',
|
||
|
|
'universal_service_name_of_coding_system',
|
||
|
|
'universal_service_alternate_id',
|
||
|
|
'universal_service_alternate_text',
|
||
|
|
'name_of_alternate_coding_system',
|
||
|
|
'priority',
|
||
|
|
'requested_date_time',
|
||
|
|
'relevant_clinical_info',
|
||
|
|
// specimen
|
||
|
|
'specimen_source',
|
||
|
|
'specimen_source_code',
|
||
|
|
'specimen_source_name',
|
||
|
|
// ordering provider
|
||
|
|
'obr_ordering_provider_id',
|
||
|
|
'obr_ordering_provider_last_name',
|
||
|
|
'obr_ordering_provider_first_name',
|
||
|
|
'obr_ordering_provider_middle_name',
|
||
|
|
'obr_ordering_provider_prefix_name',
|
||
|
|
// etc.
|
||
|
|
'placer_field_1',
|
||
|
|
'ward_code_name',
|
||
|
|
'charge_to_practice',
|
||
|
|
];
|
||
|
|
foreach ($mapFields as $f) {
|
||
|
|
if (array_key_exists($f, $obrIn)) {
|
||
|
|
$obr->$f = $obrIn[$f];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ถ้ามี ORC ระดับก้อน (ออปชั่น)
|
||
|
|
if (!empty($o['orc'])) {
|
||
|
|
$orc = $o['orc'];
|
||
|
|
$obr->orc_order_control = $orc['order_control'] ?? null;
|
||
|
|
$obr->orc_placer_no = $orc['orc_placer_order_number'] ?? null;
|
||
|
|
$obr->orc_filler_no = $orc['orc_filler_order_number'] ?? null;
|
||
|
|
$obr->orc_status = $orc['order_status'] ?? null;
|
||
|
|
$obr->orc_datetime_tx = $hl7DateTime($orc['date_time_of_transaction'] ?? null);
|
||
|
|
$obr->orc_ordering_provider_raw = $orc['orc_ordering_provider'] ?? null;
|
||
|
|
$obr->orc_ordering_provider_id = $orc['orc_ordering_provider_id'] ?? null;
|
||
|
|
$obr->orc_ordering_provider_lastname = $orc['orc_ordering_provider_last_name'] ?? null;
|
||
|
|
$obr->orc_ordering_provider_firstname = $orc['orc_ordering_provider_first_name'] ?? null;
|
||
|
|
$obr->orc_entering_org = $orc['entering_organization'] ?? null;
|
||
|
|
$obr->orc_entering_device = $orc['entering_device'] ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!$obr->save()) {
|
||
|
|
error_log('[HisObr] ERROR: ' . json_encode($obr->errors, JSON_UNESCAPED_UNICODE));
|
||
|
|
throw new \Exception('Save his_obr failed at index ' . $oIdx);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------- OBX ----------
|
||
|
|
$obxArr = $o['obx'] ?? [];
|
||
|
|
foreach ($obxArr as $xIdx => $x) {
|
||
|
|
$obx = new HisObx();
|
||
|
|
$obx->his_obr_id = $obr->id;
|
||
|
|
|
||
|
|
// map ชื่อฟิลด์ให้ตรงกับ schema (เราใช้ obx_text, obx_values)
|
||
|
|
$obx->set_id = $x['set_id'] ?? null;
|
||
|
|
$obx->value_type = $x['value_type'] ?? null;
|
||
|
|
$obx->identifier = $x['id'] ?? null;
|
||
|
|
$obx->obx_text = $x['text'] ?? null;
|
||
|
|
$obx->coding_system = $x['system'] ?? null;
|
||
|
|
|
||
|
|
// values: อาจมาเป็น array หรือ string; ถ้า array ให้ join ด้วย "~"
|
||
|
|
$values = $x['values'] ?? null;
|
||
|
|
if (is_array($values)) {
|
||
|
|
$values = implode('~', $values);
|
||
|
|
}
|
||
|
|
$obx->obx_values = $values;
|
||
|
|
|
||
|
|
$obx->units = $x['units'] ?? null;
|
||
|
|
$obx->reference_range = $x['reference_range'] ?? null;
|
||
|
|
$obx->result_status = $x['result_status'] ?? null;
|
||
|
|
|
||
|
|
if (!$obx->save()) {
|
||
|
|
error_log('[HisObx] ERROR: ' . json_encode($obx->errors, JSON_UNESCAPED_UNICODE));
|
||
|
|
throw new \Exception('Save his_obx failed at req=' . $req->id . ' obr=' . $obr->id . ' idx=' . $xIdx);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$tx->commit();
|
||
|
|
|
||
|
|
// ตอบกลับข้อมูลที่สร้าง (จะส่งเฉพาะ his_request ตามเดิม)
|
||
|
|
return $this->apiCreated($req, 'CREATE_COMPLETED');
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
$tx->rollBack();
|
||
|
|
error_log('[HisRequest] ROLLBACK: ' . $e->getMessage());
|
||
|
|
return $this->apiValidate(['error' => [$e->getMessage()]]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// ---------- End of actionCreate ----------
|
||
|
|
/*
|
||
|
|
#[OA\Put(
|
||
|
|
path: "/his-request/{id}",
|
||
|
|
summary: "Update data hospital import",
|
||
|
|
operationId: "hisRequestUpdate",
|
||
|
|
tags: ["HisRequest"],
|
||
|
|
parameters: [
|
||
|
|
new OA\Parameter(
|
||
|
|
name: "id",
|
||
|
|
in: "path",
|
||
|
|
required: true,
|
||
|
|
description: "HisRequest ID",
|
||
|
|
schema: new OA\Schema(type: "integer", example: 123)
|
||
|
|
),
|
||
|
|
],
|
||
|
|
requestBody: new OA\RequestBody(
|
||
|
|
required: true,
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/UpdateHisRequest")
|
||
|
|
),
|
||
|
|
responses: [
|
||
|
|
new OA\Response(
|
||
|
|
response: 202,
|
||
|
|
description: "Accepted",
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/HisRequest")
|
||
|
|
),
|
||
|
|
new OA\Response(
|
||
|
|
response: 422,
|
||
|
|
description: "Validation error",
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/ErrorValidate")
|
||
|
|
),
|
||
|
|
new OA\Response(response: 404, description: "Not found"),
|
||
|
|
]
|
||
|
|
)]
|
||
|
|
public function actionUpdate($id)
|
||
|
|
{
|
||
|
|
$dataRequest['HisRequest'] = Yii::$app->request->getBodyParams();
|
||
|
|
$model = $this->findModel($id);
|
||
|
|
if ($model->load($dataRequest) && $model->save()) {
|
||
|
|
return $this->apiUpdated($model);
|
||
|
|
}
|
||
|
|
return $this->apiValidate($model->errors);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[OA\Get(
|
||
|
|
path: "/his-request/{id}",
|
||
|
|
summary: "Get data hospital import",
|
||
|
|
operationId: "hisRequestView",
|
||
|
|
tags: ["HisRequest"],
|
||
|
|
parameters: [
|
||
|
|
new OA\Parameter(
|
||
|
|
name: "id",
|
||
|
|
in: "path",
|
||
|
|
required: true,
|
||
|
|
schema: new OA\Schema(type: "integer", example: 123)
|
||
|
|
),
|
||
|
|
],
|
||
|
|
responses: [
|
||
|
|
new OA\Response(
|
||
|
|
response: 200,
|
||
|
|
description: "OK",
|
||
|
|
content: new OA\JsonContent(ref: "#/components/schemas/HisRequest")
|
||
|
|
),
|
||
|
|
new OA\Response(response: 404, description: "Not found"),
|
||
|
|
]
|
||
|
|
)]
|
||
|
|
public function actionView($id)
|
||
|
|
{
|
||
|
|
return $this->apiItem($this->findModel($id));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[OA\Delete(
|
||
|
|
path: "/his-request/{id}",
|
||
|
|
summary: "Delete data hospital import",
|
||
|
|
operationId: "hisRequestDelete",
|
||
|
|
tags: ["HisRequest"],
|
||
|
|
parameters: [
|
||
|
|
new OA\Parameter(
|
||
|
|
name: "id",
|
||
|
|
in: "path",
|
||
|
|
required: true,
|
||
|
|
schema: new OA\Schema(type: "integer", example: 123)
|
||
|
|
),
|
||
|
|
],
|
||
|
|
responses: [
|
||
|
|
new OA\Response(response: 202, description: "Accepted"),
|
||
|
|
new OA\Response(response: 404, description: "Not found"),
|
||
|
|
]
|
||
|
|
)]
|
||
|
|
public function actionDelete($id)
|
||
|
|
{
|
||
|
|
if ($this->findModel($id)->delete()) {
|
||
|
|
return $this->apiDeleted(true);
|
||
|
|
}
|
||
|
|
return $this->apiDeleted(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected function findModel($id)
|
||
|
|
{
|
||
|
|
if (($model = HisRequest::findOne($id)) !== null) {
|
||
|
|
return $model;
|
||
|
|
}
|
||
|
|
throw new NotFoundHttpException('Resource not found');
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
}
|