feat(abdm): add task status filtering, search, and completed task visibility on task board
- Add getTasks() method with status filtering (all, open, completed, failed) to AbdmWorkTaskService - Enhance AbdmTaskBoard to enrich all task types with health_records, invoice_code, and care contexts - Add status toggle filter buttons (All, Open, Completed) with dynamic count badges to Task Board UI - Add real-time fuzzy/keyword search input for instant filtering by invoice ID, patient name, ABHA, or task code - Render completed/linked tasks with green LINKED badge, gateway reference, and FHIR Preview button - Add unit tests in AbdmTaskBoardBackfillTest
This commit is contained in:
parent
2569f694c1
commit
e43bfbad71
|
|
@ -13,6 +13,7 @@ class AbdmTaskBoard extends BaseController
|
|||
public function __construct()
|
||||
{
|
||||
$this->taskService = new AbdmWorkTaskService();
|
||||
$this->db = db_connect();
|
||||
}
|
||||
|
||||
public function index()
|
||||
|
|
@ -22,7 +23,14 @@ class AbdmTaskBoard extends BaseController
|
|||
$this->backfillImmunizationTasks();
|
||||
$this->backfillHealthDocumentTasks();
|
||||
$this->backfillWellnessTasks();
|
||||
$tasks = $this->enrichTasksWithCoolingState($this->enrichImmunizationPushState($this->taskService->getOpenTasks(300)));
|
||||
|
||||
$taskStatus = strtolower(trim((string) ($this->request->getGet('task_status') ?? 'all')));
|
||||
if (! in_array($taskStatus, ['all', 'open', 'completed', 'failed'], true)) {
|
||||
$taskStatus = 'all';
|
||||
}
|
||||
|
||||
$rawTasks = $this->taskService->getTasks($taskStatus, 600);
|
||||
$tasks = $this->enrichTasksWithCoolingState($this->enrichTasksWithHealthRecordState($rawTasks));
|
||||
|
||||
$dateFrom = trim((string) ($this->request->getGet('date_from') ?? date('Y-m-d')));
|
||||
$dateTo = trim((string) ($this->request->getGet('date_to') ?? date('Y-m-d')));
|
||||
|
|
@ -38,6 +46,7 @@ class AbdmTaskBoard extends BaseController
|
|||
|
||||
return view('abdm/task_board', [
|
||||
'tasks' => $tasks,
|
||||
'task_status' => $taskStatus,
|
||||
'dashboard_metrics' => $this->getDashboardMetrics($dateFrom, $dateTo),
|
||||
'dashboard_date_from' => $dateFrom,
|
||||
'dashboard_date_to' => $dateTo,
|
||||
|
|
@ -55,9 +64,17 @@ class AbdmTaskBoard extends BaseController
|
|||
$this->backfillImmunizationTasks();
|
||||
$this->backfillHealthDocumentTasks();
|
||||
$this->backfillWellnessTasks();
|
||||
|
||||
$taskStatus = strtolower(trim((string) ($this->request->getGet('task_status') ?? 'all')));
|
||||
if (! in_array($taskStatus, ['all', 'open', 'completed', 'failed'], true)) {
|
||||
$taskStatus = 'all';
|
||||
}
|
||||
|
||||
$rawTasks = $this->taskService->getTasks($taskStatus, 600);
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'tasks' => $this->enrichTasksWithCoolingState($this->enrichImmunizationPushState($this->taskService->getOpenTasks(300))),
|
||||
'task_status' => $taskStatus,
|
||||
'tasks' => $this->enrichTasksWithCoolingState($this->enrichTasksWithHealthRecordState($rawTasks)),
|
||||
'csrfName' => csrf_token(),
|
||||
'csrfHash' => csrf_hash(),
|
||||
]);
|
||||
|
|
@ -89,48 +106,91 @@ class AbdmTaskBoard extends BaseController
|
|||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $tasks */
|
||||
private function enrichImmunizationPushState(array $tasks): array
|
||||
private function enrichTasksWithHealthRecordState(array $tasks): array
|
||||
{
|
||||
if ($tasks === [] || ! $this->db->tableExists('health_records')) {
|
||||
return $tasks;
|
||||
if ($tasks === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$entityIds = [];
|
||||
foreach ($tasks as $task) {
|
||||
if (($task['task_type'] ?? '') === 'immunization_record_publish') {
|
||||
$entityIds[] = (string) ($task['entity_id'] ?? '');
|
||||
// 1. Extract invoice metadata and resolve invoice_code
|
||||
$invoiceIds = [];
|
||||
foreach ($tasks as &$task) {
|
||||
$payload = json_decode((string) ($task['payload_json'] ?? ''), true);
|
||||
$meta = (is_array($payload) && isset($payload['meta']) && is_array($payload['meta'])) ? $payload['meta'] : [];
|
||||
if (! empty($meta['invoice_id'])) {
|
||||
$task['invoice_id'] = (int) $meta['invoice_id'];
|
||||
$invoiceIds[] = (int) $meta['invoice_id'];
|
||||
}
|
||||
if (! empty($meta['invoice_code'])) {
|
||||
$task['invoice_code'] = (string) $meta['invoice_code'];
|
||||
}
|
||||
}
|
||||
$entityIds = array_values(array_filter(array_unique($entityIds), static fn (string $id): bool => $id !== ''));
|
||||
if ($entityIds === []) {
|
||||
return $tasks;
|
||||
unset($task);
|
||||
|
||||
$db = $this->db ?? db_connect();
|
||||
|
||||
if (! empty($invoiceIds) && $db->tableExists('invoice_master')) {
|
||||
$invRows = $db->table('invoice_master')
|
||||
->select('id, invoice_code')
|
||||
->whereIn('id', array_unique($invoiceIds))
|
||||
->get()
|
||||
->getResultArray();
|
||||
$codeMap = [];
|
||||
foreach ($invRows as $ir) {
|
||||
$codeMap[(int) $ir['id']] = (string) ($ir['invoice_code'] ?? '');
|
||||
}
|
||||
foreach ($tasks as &$task) {
|
||||
if (! empty($task['invoice_id']) && empty($task['invoice_code']) && isset($codeMap[(int) $task['invoice_id']])) {
|
||||
$task['invoice_code'] = $codeMap[(int) $task['invoice_id']];
|
||||
}
|
||||
}
|
||||
unset($task);
|
||||
}
|
||||
|
||||
$latest = [];
|
||||
$rows = $this->db->table('health_records')
|
||||
->select('id, entity_id, push_status, care_context_reference, linked_at')
|
||||
->where('hi_type', 'ImmunizationRecord')
|
||||
->where('entity_type', 'immunization')
|
||||
->whereIn('entity_id', $entityIds)
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
foreach ($rows as $row) {
|
||||
$entityId = (string) ($row['entity_id'] ?? '');
|
||||
if ($entityId !== '' && ! isset($latest[$entityId])) {
|
||||
$latest[$entityId] = $row;
|
||||
// 2. Lookup health_records by entity_id
|
||||
$latestHr = [];
|
||||
if ($db->tableExists('health_records')) {
|
||||
$entityIds = [];
|
||||
foreach ($tasks as $task) {
|
||||
$entityId = trim((string) ($task['entity_id'] ?? ''));
|
||||
if ($entityId !== '') {
|
||||
$entityIds[] = $entityId;
|
||||
}
|
||||
}
|
||||
$entityIds = array_values(array_filter(array_unique($entityIds)));
|
||||
|
||||
if ($entityIds !== []) {
|
||||
$rows = $db->table('health_records')
|
||||
->select('id, entity_type, entity_id, push_status, care_context_reference, linked_at, abdm_txn_id')
|
||||
->whereIn('entity_id', $entityIds)
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
foreach ($rows as $row) {
|
||||
$eid = (string) ($row['entity_id'] ?? '');
|
||||
if ($eid !== '' && ! isset($latestHr[$eid])) {
|
||||
$latestHr[$eid] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tasks as &$task) {
|
||||
if (($task['task_type'] ?? '') !== 'immunization_record_publish') {
|
||||
continue;
|
||||
}
|
||||
$healthRecord = $latest[(string) ($task['entity_id'] ?? '')] ?? null;
|
||||
$pushStatus = strtolower(trim((string) ($healthRecord['push_status'] ?? '')));
|
||||
$task['bridge_health_record_id'] = (int) ($healthRecord['id'] ?? 0);
|
||||
$eid = (string) ($task['entity_id'] ?? '');
|
||||
$hr = $latestHr[$eid] ?? null;
|
||||
$pushStatus = strtolower(trim((string) ($hr['push_status'] ?? '')));
|
||||
$task['bridge_health_record_id'] = (int) ($hr['id'] ?? 0);
|
||||
$task['bridge_push_status'] = $pushStatus;
|
||||
$task['bridge_care_context_reference'] = trim((string) ($healthRecord['care_context_reference'] ?? ''));
|
||||
|
||||
$careContext = trim((string) ($hr['care_context_reference'] ?? ''));
|
||||
if ($careContext === '') {
|
||||
$resultText = (string) ($task['last_action_result'] ?? '');
|
||||
if (preg_match('/(RAD-\d+-\d+|LAB-\d+-\d+|OPD-\d+-\d+|INVOICE-\w+-\d+-\d+|IMM-\d+-\d+|DIS-\d+-\d+)/i', $resultText, $ccm)) {
|
||||
$careContext = $ccm[1];
|
||||
}
|
||||
}
|
||||
|
||||
$task['bridge_care_context_reference'] = $careContext;
|
||||
$task['bridge_submitted'] = in_array($pushStatus, ['queued', 'pushed', 'linked'], true) ? 1 : 0;
|
||||
}
|
||||
unset($task);
|
||||
|
|
@ -138,6 +198,12 @@ class AbdmTaskBoard extends BaseController
|
|||
return $tasks;
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $tasks */
|
||||
private function enrichImmunizationPushState(array $tasks): array
|
||||
{
|
||||
return $this->enrichTasksWithHealthRecordState($tasks);
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $tasks */
|
||||
private function enrichTasksWithCoolingState(array $tasks): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,24 +115,51 @@ class AbdmWorkTaskService
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $status 'all' (default), 'open', 'completed', or 'failed'
|
||||
* @param int $limit Max rows to fetch
|
||||
* @param string|null $taskType Optional task type filter
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getOpenTasks(int $limit = 200): array
|
||||
public function getTasks(string $status = 'all', int $limit = 500, ?string $taskType = null): array
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_work_tasks')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->syncPatientAbhaBacklog(max(200, $limit));
|
||||
$status = strtolower(trim($status));
|
||||
if (in_array($status, ['open', 'all'], true)) {
|
||||
$this->syncPatientAbhaBacklog(max(200, min(500, $limit)));
|
||||
}
|
||||
|
||||
return $this->db->table('abdm_work_tasks')
|
||||
->whereIn('status', ['pending', 'in_progress'])
|
||||
$builder = $this->db->table('abdm_work_tasks');
|
||||
|
||||
if ($status === 'open') {
|
||||
$builder->whereIn('status', ['pending', 'in_progress']);
|
||||
} elseif ($status === 'completed') {
|
||||
$builder->whereIn('status', ['completed', 'linked']);
|
||||
} elseif ($status === 'failed') {
|
||||
$builder->whereIn('status', ['failed', 'cancelled']);
|
||||
}
|
||||
|
||||
if (! empty($taskType)) {
|
||||
$builder->where('task_type', trim($taskType));
|
||||
}
|
||||
|
||||
return $builder
|
||||
->orderBy('id', 'DESC')
|
||||
->limit(max(1, $limit))
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getOpenTasks(int $limit = 200): array
|
||||
{
|
||||
return $this->getTasks('open', $limit);
|
||||
}
|
||||
|
||||
public function markTaskStatus(int $taskId, string $status, string $resultText = ''): bool
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_work_tasks') || $taskId <= 0) {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,20 @@
|
|||
</div>
|
||||
|
||||
<div class="card shadow-sm" id="taskTableCard">
|
||||
<div class="card-header py-2 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<strong id="taskTableTitle">Work Queue</strong>
|
||||
<span class="badge bg-primary" id="taskTableCount"><?= count($tasks ?? []) ?></span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<input type="search" class="form-control form-control-sm" id="taskTableSearch" placeholder="Search invoice, patient, code..." style="width: 230px;">
|
||||
<div class="btn-group btn-group-sm" role="group" id="statusFilterGroup">
|
||||
<button type="button" class="btn btn-sm btn-primary status-filter-btn active" data-status="all">All <span class="badge bg-light text-dark ms-1" id="countAll">0</span></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary status-filter-btn" data-status="open">Open <span class="badge bg-light text-dark ms-1" id="countOpen">0</span></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary status-filter-btn" data-status="completed">Completed <span class="badge bg-light text-dark ms-1" id="countCompleted">0</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped mb-0" id="taskTable">
|
||||
|
|
@ -132,15 +146,37 @@
|
|||
$meta = (isset($payload['meta']) && is_array($payload['meta'])) ? $payload['meta'] : [];
|
||||
$opdId = (int) ($meta['opd_id'] ?? 0);
|
||||
$opdSessionId = (int) ($meta['opd_session_id'] ?? 0);
|
||||
$invoiceId = (int) ($t['invoice_id'] ?? ($meta['invoice_id'] ?? 0));
|
||||
$invoiceCode = trim((string) ($t['invoice_code'] ?? ($meta['invoice_code'] ?? '')));
|
||||
$invoiceDisplay = $invoiceCode !== '' ? $invoiceCode : ($invoiceId > 0 ? ('#' . $invoiceId) : '');
|
||||
|
||||
$showSandbox = ($type === 'opd_prescription_publish' && $opdId > 0);
|
||||
$showPreview = ($type === 'opd_prescription_publish' && $opdId > 0)
|
||||
|| in_array($type, ['lab_report_publish', 'radiology_report_publish', 'ipd_discharge_publish', 'immunization_record_publish', 'health_document_publish', 'wellness_record_publish'], true);
|
||||
$bridgeSubmitted = (int) ($t['bridge_submitted'] ?? 0) === 1;
|
||||
$bridgeCareContext = trim((string) ($t['bridge_care_context_reference'] ?? ''));
|
||||
|
||||
$rawTaskStatus = strtolower(trim((string) ($t['status'] ?? 'pending')));
|
||||
$isTaskLinked = in_array($rawTaskStatus, ['completed', 'linked'], true) || ($bridgeSubmitted && strtolower((string) ($t['bridge_push_status'] ?? '')) === 'linked');
|
||||
|
||||
$searchText = strtolower(implode(' ', array_filter([
|
||||
(string) ($t['task_code'] ?? ''),
|
||||
(string) ($t['task_type'] ?? ''),
|
||||
(string) ($t['patient_name'] ?? ''),
|
||||
(string) ($t['patient_id'] ?? ''),
|
||||
(string) ($t['abha_id'] ?? ''),
|
||||
(string) ($t['entity_id'] ?? ''),
|
||||
$invoiceCode,
|
||||
$invoiceId > 0 ? (string) $invoiceId : '',
|
||||
$bridgeCareContext,
|
||||
(string) ($t['last_action_result'] ?? ''),
|
||||
])));
|
||||
?>
|
||||
<tr
|
||||
data-task-id="<?= (int) ($t['id'] ?? 0) ?>"
|
||||
data-task-type="<?= esc((string) ($t['task_type'] ?? '')) ?>"
|
||||
data-task-status="<?= esc($rawTaskStatus) ?>"
|
||||
data-is-linked="<?= $isTaskLinked ? '1' : '0' ?>"
|
||||
data-entity-id="<?= esc((string) ($t['entity_id'] ?? '')) ?>"
|
||||
data-patient-id="<?= (int) ($t['patient_id'] ?? 0) ?>"
|
||||
data-abha-id="<?= esc($abhaId) ?>"
|
||||
|
|
@ -148,32 +184,44 @@
|
|||
data-opd-session-id="<?= $opdSessionId ?>"
|
||||
data-sandbox-eligible="<?= $showSandbox ? '1' : '0' ?>"
|
||||
data-bridge-submitted="<?= $bridgeSubmitted ? '1' : '0' ?>"
|
||||
data-search="<?= esc($searchText) ?>"
|
||||
>
|
||||
<td><?= (int) ($t['id'] ?? 0) ?></td>
|
||||
<td>
|
||||
<div><strong><?= esc((string) ($t['task_code'] ?? '')) ?></strong></div>
|
||||
<div class="text-muted small"><?= esc((string) ($t['task_type'] ?? '')) ?></div>
|
||||
<?php if ($invoiceDisplay !== ''): ?>
|
||||
<div class="small text-primary fw-semibold"><i class="bi bi-receipt me-1"></i>Inv: <?= esc($invoiceDisplay) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div><?= esc((string) ($t['patient_name'] ?? '')) ?></div>
|
||||
<div class="text-muted small">#<?= (int) ($t['patient_id'] ?? 0) ?></div>
|
||||
</td>
|
||||
<td><input type="text" class="form-control form-control-sm abha-input" value="<?= esc((string) ($t['abha_id'] ?? '')) ?>" placeholder="14-digit ABHA"></td>
|
||||
<td>
|
||||
<?php if ($isTaskLinked): ?>
|
||||
<div class="small"><span class="badge bg-light text-dark border"><i class="bi bi-person-check text-success me-1"></i><?= esc((string) ($t['abha_id'] ?? '')) ?></span></div>
|
||||
<?php else: ?>
|
||||
<input type="text" class="form-control form-control-sm abha-input" value="<?= esc((string) ($t['abha_id'] ?? '')) ?>" placeholder="14-digit ABHA">
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div><?= esc((string) ($t['entity_type'] ?? '')) ?></div>
|
||||
<div class="text-muted small"><?= esc((string) ($t['entity_id'] ?? '')) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$rawTaskStatus = strtolower(trim((string) ($t['status'] ?? 'pending')));
|
||||
$isTaskLinked = in_array($rawTaskStatus, ['completed', 'linked'], true) || ($bridgeSubmitted && strtolower((string) ($t['bridge_push_status'] ?? '')) === 'linked');
|
||||
?>
|
||||
<?php if ($isTaskLinked): ?>
|
||||
<span class="badge bg-success status-pill">LINKED</span>
|
||||
<?php if ($bridgeCareContext !== ''): ?><div class="small"><code><?= esc($bridgeCareContext) ?></code></div><?php endif; ?>
|
||||
<span class="badge bg-success status-pill"><i class="bi bi-check-circle me-1"></i>LINKED</span>
|
||||
<?php if ($bridgeCareContext !== ''): ?>
|
||||
<div class="small mt-1"><code><?= esc($bridgeCareContext) ?></code></div>
|
||||
<?php elseif (! empty($t['last_action_result'])): ?>
|
||||
<div class="small text-muted mt-1" style="font-size:10px; max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="<?= esc((string) $t['last_action_result']) ?>">
|
||||
<?= esc((string) $t['last_action_result']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($bridgeSubmitted): ?>
|
||||
<span class="badge bg-info status-pill">BRIDGE SUBMITTED</span>
|
||||
<?php if ($bridgeCareContext !== ''): ?><div class="small"><code><?= esc($bridgeCareContext) ?></code></div><?php endif; ?>
|
||||
<?php if ($bridgeCareContext !== ''): ?><div class="small mt-1"><code><?= esc($bridgeCareContext) ?></code></div><?php endif; ?>
|
||||
<?php elseif (! empty($t['cooling_active'])): ?>
|
||||
<span class="badge bg-warning text-dark status-pill" title="<?= esc((string) ($t['cooling_tooltip'] ?? '')) ?>"><i class="bi bi-clock-history"></i> <?= esc((string) ($t['cooling_label'] ?? 'COOLING')) ?></span>
|
||||
<?php if (! empty($t['auto_link_at']) && $t['auto_link_at'] !== 'disabled'): ?>
|
||||
|
|
@ -186,24 +234,37 @@
|
|||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1 flex-wrap">
|
||||
<?php if ($type === 'patient_abha_create' || $type === 'patient_abha_link'): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-success action-btn" data-action="create_abha">Create ABHA</button>
|
||||
<?php elseif ($type === 'patient_abha_update'): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary action-btn" data-action="update_abha">Update ABHA</button>
|
||||
<div class="d-flex gap-1 flex-wrap align-items-center">
|
||||
<?php if ($isTaskLinked): ?>
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle py-1 px-2"><i class="bi bi-check2-all me-1"></i>Done</span>
|
||||
<?php if ($showPreview): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary preview-fhir-btn"><i class="bi bi-eye me-1"></i>Preview FHIR</button>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<?php if ($type === 'patient_abha_create' || $type === 'patient_abha_link'): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-success action-btn" data-action="create_abha">Create ABHA</button>
|
||||
<?php elseif ($type === 'patient_abha_update'): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary action-btn" data-action="update_abha">Update ABHA</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-success btn-row-hip-link" title="HIP-Initiated Care Context Linking"><i class="bi bi-link-45deg"></i> Link Care Context</button>
|
||||
<?php if ($showPreview): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary preview-fhir-btn">Preview FHIR</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($showSandbox): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-info sandbox-btn">Sandbox</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-dark close-btn">Close</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-success btn-row-hip-link" title="HIP-Initiated Care Context Linking"><i class="bi bi-link-45deg"></i> Link Care Context</button>
|
||||
<?php if ($showPreview): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary preview-fhir-btn">Preview FHIR</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($showSandbox): ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-info sandbox-btn">Sandbox</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-dark close-btn">Close</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr id="taskTableEmptyRow" class="d-none">
|
||||
<td colspan="7" class="text-center py-4 text-muted">
|
||||
<i class="bi bi-inbox fs-4 d-block mb-1"></i>
|
||||
<span>No tasks match the selected filter.</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -665,7 +726,82 @@
|
|||
var taskTableCard = document.getElementById('taskTableCard');
|
||||
var dashboardCard = document.getElementById('dashboardCard');
|
||||
|
||||
var currentCategoryFilter = 'dashboard';
|
||||
var currentStatusFilter = 'all';
|
||||
var currentSearchQuery = '';
|
||||
|
||||
function updateTaskTableDisplay() {
|
||||
var rows = document.querySelectorAll('#taskTable tbody tr:not(#taskTableEmptyRow)');
|
||||
var emptyRow = document.getElementById('taskTableEmptyRow');
|
||||
var q = currentSearchQuery.trim().toLowerCase();
|
||||
|
||||
var visibleCount = 0;
|
||||
var countAll = 0;
|
||||
var countOpen = 0;
|
||||
var countCompleted = 0;
|
||||
|
||||
rows.forEach(function (row) {
|
||||
var type = (row.getAttribute('data-task-type') || '').toLowerCase();
|
||||
var status = (row.getAttribute('data-task-status') || '').toLowerCase();
|
||||
var isLinked = row.getAttribute('data-is-linked') === '1' || status === 'completed' || status === 'linked';
|
||||
var searchData = (row.getAttribute('data-search') || '').toLowerCase();
|
||||
|
||||
// Check if matches current category
|
||||
var categoryMatch = (currentCategoryFilter === 'all' || type === currentCategoryFilter.toLowerCase());
|
||||
if (!categoryMatch) {
|
||||
row.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Tally status counts for this category
|
||||
countAll++;
|
||||
if (isLinked) {
|
||||
countCompleted++;
|
||||
} else {
|
||||
countOpen++;
|
||||
}
|
||||
|
||||
// Check status match
|
||||
var statusMatch = false;
|
||||
if (currentStatusFilter === 'all') {
|
||||
statusMatch = true;
|
||||
} else if (currentStatusFilter === 'completed') {
|
||||
statusMatch = isLinked;
|
||||
} else if (currentStatusFilter === 'open') {
|
||||
statusMatch = !isLinked;
|
||||
} else if (currentStatusFilter === status) {
|
||||
statusMatch = true;
|
||||
}
|
||||
|
||||
// Check search match
|
||||
var searchMatch = !q || searchData.indexOf(q) !== -1;
|
||||
|
||||
if (statusMatch && searchMatch) {
|
||||
row.style.display = '';
|
||||
visibleCount++;
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Update counts in UI
|
||||
var elCountAll = document.getElementById('countAll');
|
||||
var elCountOpen = document.getElementById('countOpen');
|
||||
var elCountCompleted = document.getElementById('countCompleted');
|
||||
var elTableCount = document.getElementById('taskTableCount');
|
||||
if (elCountAll) elCountAll.textContent = countAll;
|
||||
if (elCountOpen) elCountOpen.textContent = countOpen;
|
||||
if (elCountCompleted) elCountCompleted.textContent = countCompleted;
|
||||
if (elTableCount) elTableCount.textContent = visibleCount;
|
||||
|
||||
if (emptyRow) {
|
||||
emptyRow.classList.toggle('d-none', visibleCount > 0);
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilter(filter) {
|
||||
currentCategoryFilter = filter;
|
||||
|
||||
// Hide dedicated section cards
|
||||
if (opdBookCard) opdBookCard.classList.add('d-none');
|
||||
if (opdConsultCard) opdConsultCard.classList.add('d-none');
|
||||
|
|
@ -686,11 +822,12 @@
|
|||
if (invoiceCard) invoiceCard.classList.remove('d-none');
|
||||
} else {
|
||||
if (taskTableCard) taskTableCard.classList.remove('d-none');
|
||||
var rows = document.querySelectorAll('#taskTable tbody tr');
|
||||
rows.forEach(function (row) {
|
||||
var type = (row.getAttribute('data-task-type') || '').toLowerCase();
|
||||
row.style.display = (type === filter.toLowerCase()) ? '' : 'none';
|
||||
});
|
||||
var titleEl = document.getElementById('taskTableTitle');
|
||||
if (titleEl) {
|
||||
var btn = document.querySelector('.filter-btn[data-filter="' + filter + '"]');
|
||||
titleEl.textContent = btn ? btn.textContent.trim() : 'Work Queue';
|
||||
}
|
||||
updateTaskTableDisplay();
|
||||
}
|
||||
|
||||
document.querySelectorAll('.filter-btn').forEach(function (btn) {
|
||||
|
|
@ -1270,6 +1407,27 @@
|
|||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.status-filter-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
document.querySelectorAll('.status-filter-btn').forEach(function (b) {
|
||||
b.classList.remove('btn-primary', 'active');
|
||||
b.classList.add('btn-outline-primary');
|
||||
});
|
||||
btn.classList.remove('btn-outline-primary');
|
||||
btn.classList.add('btn-primary', 'active');
|
||||
currentStatusFilter = (btn.getAttribute('data-status') || 'all').toLowerCase();
|
||||
updateTaskTableDisplay();
|
||||
});
|
||||
});
|
||||
|
||||
var searchInput = document.getElementById('taskTableSearch');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function () {
|
||||
currentSearchQuery = searchInput.value || '';
|
||||
updateTaskTableDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.sandbox-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
openSandboxForRow(btn.closest('tr'));
|
||||
|
|
|
|||
|
|
@ -25,4 +25,63 @@ final class AbdmTaskBoardBackfillTest extends CIUnitTestCase
|
|||
$this->assertFalse($method->invoke($controller, '12345'));
|
||||
$this->assertFalse($method->invoke($controller, 'invalid_address'));
|
||||
}
|
||||
|
||||
public function testEnrichTasksWithHealthRecordStateExtractsInvoiceAndCareContext(): void
|
||||
{
|
||||
$controller = (new ReflectionClass(AbdmTaskBoard::class))->newInstanceWithoutConstructor();
|
||||
$method = new ReflectionMethod(AbdmTaskBoard::class, 'enrichTasksWithHealthRecordState');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$mockTasks = [
|
||||
[
|
||||
'id' => 686,
|
||||
'task_code' => 'ABDM-20261001151127-C8C055',
|
||||
'task_type' => 'radiology_report_publish',
|
||||
'entity_id' => '51450',
|
||||
'status' => 'completed',
|
||||
'payload_json' => json_encode([
|
||||
'meta' => [
|
||||
'lab_type' => 3,
|
||||
'invoice_id' => 40291,
|
||||
'invoice_code' => 'N26100040291',
|
||||
],
|
||||
]),
|
||||
'last_action_result' => 'Linked by cron: REQ-20261001151202-7447b1c4',
|
||||
],
|
||||
[
|
||||
'id' => 679,
|
||||
'task_code' => 'ABDM-20261001142918-25422B',
|
||||
'task_type' => 'radiology_report_publish',
|
||||
'entity_id' => '51450',
|
||||
'status' => 'completed',
|
||||
'payload_json' => json_encode([
|
||||
'meta' => [
|
||||
'invoice_id' => 40291,
|
||||
],
|
||||
]),
|
||||
'last_action_result' => 'Care context already linked in ABDM: RAD-51450-20261001',
|
||||
],
|
||||
];
|
||||
|
||||
$enriched = $method->invoke($controller, $mockTasks);
|
||||
|
||||
$this->assertCount(2, $enriched);
|
||||
$this->assertSame(40291, $enriched[0]['invoice_id']);
|
||||
$this->assertSame('N26100040291', $enriched[0]['invoice_code']);
|
||||
$this->assertSame(40291, $enriched[1]['invoice_id']);
|
||||
// From last_action_result regex fallback
|
||||
$this->assertSame('RAD-51450-20261001', $enriched[1]['bridge_care_context_reference']);
|
||||
}
|
||||
|
||||
public function testWorkTaskServiceHasGetTasksMethod(): void
|
||||
{
|
||||
$serviceClass = new ReflectionClass(\App\Libraries\AbdmWorkTaskService::class);
|
||||
$this->assertTrue($serviceClass->hasMethod('getTasks'));
|
||||
$this->assertTrue($serviceClass->hasMethod('getOpenTasks'));
|
||||
|
||||
$getTasksMethod = $serviceClass->getMethod('getTasks');
|
||||
$params = $getTasksMethod->getParameters();
|
||||
$this->assertSame('status', $params[0]->getName());
|
||||
$this->assertSame('all', $params[0]->getDefaultValue());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user