devsofttech_hms_ci4/app/Libraries/AbdmAuditService.php
DevSoftTech Server a56e8a5776 feat: SNOMED background coding pipeline + ABDM Coding Panel
- Add opd_coding_queue + opd_snomed_suggestions migrations
- Add ProcessSnomedCoding spark command (cron: snomed:process-coding)
  Extracts clinical phrases, matches local SNOMED FULLTEXT, saves suggestions
- Add AbdmCodingPanel controller: index/review/confirm/reject/correct/mark_fhir_ready/tip_check
- Add coding_panel + coding_panel_review views (Bootstrap 5, AJAX actions)
- Inject queue trigger in opd_prescription_save (fail-open, ON DUPLICATE KEY)
- Add consult form tip badge: polls tip_check 4s after save, shows review link
- Add AbdmCodingPanel routes behind abdm permission filter
- Various ABDM gateway, FHIR, ABHA, SNOMED library updates
2026-05-19 20:40:27 +05:30

103 lines
4.0 KiB
PHP

<?php
namespace App\Libraries;
use CodeIgniter\I18n\Time;
/**
* ABDM Audit Trail Service
*
* Records every ABDM operation (push record, link record, consent lifecycle,
* ABHA validation, callbacks) to the abdm_audit_logs table.
*
* The service is FAIL-OPEN: if the DB insert fails for any reason, the
* exception is swallowed and the calling code continues unaffected.
*
* Logged fields per entry:
* actor_user_id, actor_name — from session or caller-supplied
* action — push_record | link_record | consent_request |
* consent_grant | consent_revoke | record_linked |
* consent_callback | abha_validate | push_failure
* entity_type, entity_id — source module + record PK
* abha_id, patient_id — subject identifiers
* request_json — sanitised outbound payload
* response_json — ABDM gateway response
* ip_address, user_agent — requester context
* outcome — success | failure
* error_message — populated on failure
*/
class AbdmAuditService
{
private \CodeIgniter\Database\ConnectionInterface $db;
public function __construct()
{
$this->db = \Config\Database::connect();
}
/**
* Write one audit entry.
*
* @param array{
* action: string,
* entity_type?: string,
* entity_id?: string|int,
* abha_id?: string,
* patient_id?: int,
* request?: array<mixed>,
* response?: array<mixed>,
* outcome?: string,
* error_message?: string,
* actor_user_id?: int,
* actor_name?: string,
* } $data
*/
public function log(array $data): void
{
try {
if (! $this->db->tableExists('abdm_audit_logs')) {
return;
}
$httpRequest = \Config\Services::request();
$session = \Config\Services::session();
$actorUserId = (int) ($data['actor_user_id'] ?? $session->get('user_id') ?? 0);
$actorName = trim((string) ($data['actor_name']
?? $session->get('full_name')
?? $session->get('name')
?? ''));
// Serialise request / response to JSON; never store raw binary data
$requestJson = isset($data['request'])
? json_encode($data['request'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
: null;
$responseJson = isset($data['response'])
? json_encode($data['response'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
: null;
$entityId = (string) ($data['entity_id'] ?? '');
$patientId = (int) ($data['patient_id'] ?? 0);
$this->db->table('abdm_audit_logs')->insert([
'actor_user_id' => $actorUserId > 0 ? $actorUserId : null,
'actor_name' => $actorName !== '' ? $actorName : null,
'action' => (string) ($data['action'] ?? 'unknown'),
'entity_type' => (string) ($data['entity_type'] ?? ''),
'entity_id' => $entityId !== '' ? $entityId : null,
'abha_id' => trim((string) ($data['abha_id'] ?? '')) ?: null,
'patient_id' => $patientId > 0 ? $patientId : null,
'request_json' => $requestJson,
'response_json' => $responseJson,
'ip_address' => $httpRequest->getIPAddress(),
'user_agent' => mb_substr((string) $httpRequest->getUserAgent(), 0, 255),
'outcome' => (string) ($data['outcome'] ?? 'success'),
'error_message' => trim((string) ($data['error_message'] ?? '')) ?: null,
'created_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
]);
} catch (\Throwable) {
// Fail-open: audit failure must never break the main application flow
}
}
}