devsofttech_hms_ci4/BRIDGE_SYNC_SERVICE_IMPLEMENTATION.md
DevSoftTech Server 0628f8f813 feat: ABDM OPD Queue + ABHA Register flow + patient ABHA search fix
ABDM OPD Queue (today):
- New EAtriaBridgeConnector: opdQueueFetch/opdTokenCreate/opdTokenUpdateStatus + patch() helper
- New AbdmOpdQueue controller: index/list/fetchQueue/createToken/updateTokenStatus/processScannedToken
  - fetchQueue syncs gateway tokens into abdm_opd_tokens (local DB) and enriches with HMS patient data
  - processScannedToken auto-finds/creates patient_master record from ABHA scan data, marks token CALLED
- New migration: abdm_opd_tokens table (gateway_token_id, source, status, patient_id, opd_id, processed_at)
- New view: abdm/opd_queue.php - reception screen with auto-refresh 30s, HMS Patient column (green row when OPD registered)
- Sidebar: ABDM OPD Queue nav link added
- Routes: AbdmOpdQueue with abdmPermFilter

ABHA Register tab (earlier this session):
- New Abha controller: initiate/verifyOtp/communication/verifyCommOtp/validateAbha/card + autoCreateOrFindPatient
- New views: abha/card.php, partials/abha_create_panel.php, partials/abha_otp_modal.php
- Patient_V.php: ABHA Register tab with 4-method wizard (ABHA number, QR scan, facility QR, mobile OTP)

Patient ABHA search fix:
- Patient::search() now detects ABHA column (abha_id/abha_no/abha/abha_address) and searches by it

ABDM Bridge infrastructure:
- EAtriaBridgeConnector: new connector for e-atria.in ABDM bridge gateway
- AbdmConnectorFactory/Interface updated
- AbdmGatewaySettings controller + view
- SNOMED/CSNOTK terminology service skeleton
- Finance payout OPD consult views
- Migration: AddAbdmPushFieldsToOpdAndPatient, CreateSnomedCoreTables
- Docs: abdm/ API specs, architecture, gateway guides
2026-05-16 04:21:31 +05:30

11 KiB

BridgeSyncService Routing Implementation

Current Issue

The BridgeSyncService::buildBridgeDispatchContext() method treats all events the same, sending them to a single BRIDGE_SYNC_URL.

Current behavior (WRONG):

All events → BRIDGE_SYNC_URL (only one URL)

Required behavior (CORRECT):

snomed.* events → BRIDGE_SYNC_URL (CSNOtk)
abdm.* events → ABDM_BRIDGE_URL (ABDM Bridge)

Implementation Steps

Step 1: Update readSetting() to Support New Keys

File: app/Libraries/BridgeSyncService.php

Current: readSetting() already handles priority order (env → constant → config → DB), so no changes needed. Just ensure both config keys are recognized.

Verify in app/Config/AbdmConnector.php:

// Add if not present:
public string $dreamsoftBridgeUrl = 'https://csnotk.e-atria.in/api/bridge';
public string $abdmBridgeUrl = 'https://abdm-bridge.e-atria.in/api/v1/bridge';
public string $dreamsoftBridgeToken = '';
public string $abdmBridgeToken = '';

Step 2: Create New Method buildDispatchContextByType()

Replace or update the current buildDispatchContext() method in app/Libraries/BridgeSyncService.php

Current Code (Lines ~335-365)

private function buildDispatchContext(array $row, array $payload): array
{
    $eventType = (string) ($row['event_type'] ?? '');
    if ($eventType === '') {
        return ['ok' => false, 'error' => 'Missing event_type'];
    }

    $isAbdmOrNhcx = str_starts_with($eventType, 'abdm.') || str_starts_with($eventType, 'nhcx.');
    $provider = strtolower($this->readSetting('ABDM_SYNC_PROVIDER'));
    if ($provider === '' || ! $isAbdmOrNhcx) {
        $provider = strtolower($this->readSetting('BRIDGE_SYNC_PROVIDER'));
    }
    if ($provider === '') {
        $provider = 'bridge';
    }

    if ($provider === 'eka' && $isAbdmOrNhcx) {
        return $this->buildEkaDispatchContext($row, $payload);
    }

    return $this->buildBridgeDispatchContext($row, $payload);
}

Updated Code (ADD THIS)

private function buildDispatchContext(array $row, array $payload): array
{
    $eventType = (string) ($row['event_type'] ?? '');
    if ($eventType === '') {
        return ['ok' => false, 'error' => 'Missing event_type'];
    }

    // NEW ROUTING LOGIC: Route by event type prefix
    if (str_starts_with($eventType, 'snomed.')) {
        return $this->buildCsnotkDispatchContext($row, $payload);
    }

    if (str_starts_with($eventType, 'abdm.') || str_starts_with($eventType, 'nhcx.')) {
        $provider = strtolower($this->readSetting('ABDM_SYNC_PROVIDER'));
        if ($provider === 'eka') {
            return $this->buildEkaDispatchContext($row, $payload);
        }
        return $this->buildAbdmDispatchContext($row, $payload);
    }

    // Fallback for other event types
    $provider = strtolower($this->readSetting('BRIDGE_SYNC_PROVIDER'));
    if ($provider === '') {
        $provider = 'bridge';
    }

    return $this->buildBridgeDispatchContext($row, $payload);
}

Step 3: Rename & Update buildBridgeDispatchContext() → buildCsnotkDispatchContext()

File: app/Libraries/BridgeSyncService.php (Lines ~365-400)

Current Method

private function buildBridgeDispatchContext(array $row, array $payload): array
{
    $endpoint = $this->readSetting('BRIDGE_SYNC_URL');
    if ($endpoint === '') {
        return ['ok' => false, 'error' => 'BRIDGE_SYNC_URL is not configured'];
    }

    $token = $this->readSetting('BRIDGE_SYNC_TOKEN');
    $source = $this->readSetting('BRIDGE_SOURCE_CODE');
    if ($source === '') {
        $source = (string) ($this->readSetting('HOSPITAL_CODE') ?: 'hms-local');
    }

    $headers = ['Content-Type' => 'application/json'];
    if ($token !== '') {
        $headers['Authorization'] = 'Bearer ' . $token;
    }

    return [
        'ok' => true,
        'channel' => 'bridge',
        'endpoint' => $endpoint,
        'method' => 'POST',
        'headers' => $headers,
        'body' => [
            'queue_id' => (int) ($row['id'] ?? 0),
            'source' => $source,
            'event_type' => (string) ($row['event_type'] ?? ''),
            'entity_type' => (string) ($row['entity_type'] ?? ''),
            'entity_id' => (string) ($row['entity_id'] ?? ''),
            'payload' => $payload,
            'occurred_at' => (string) ($row['created_at'] ?? Time::now('Asia/Kolkata')->toDateTimeString()),
        ],
    ];
}

Rename to buildCsnotkDispatchContext() (Keep as is)

private function buildCsnotkDispatchContext(array $row, array $payload): array
{
    $endpoint = $this->readSetting('BRIDGE_SYNC_URL');
    if ($endpoint === '') {
        return ['ok' => false, 'error' => 'BRIDGE_SYNC_URL (CSNOtk) is not configured'];
    }

    $token = $this->readSetting('BRIDGE_SYNC_TOKEN');
    $source = $this->readSetting('BRIDGE_SOURCE_CODE');
    if ($source === '') {
        $source = (string) ($this->readSetting('HOSPITAL_CODE') ?: 'hms-local');
    }

    $headers = ['Content-Type' => 'application/json'];
    if ($token !== '') {
        $headers['Authorization'] = 'Bearer ' . $token;
    }

    return [
        'ok' => true,
        'channel' => 'csnotk',  // Changed from 'bridge' to 'csnotk'
        'endpoint' => $endpoint,
        'method' => 'POST',
        'headers' => $headers,
        'body' => [
            'queue_id' => (int) ($row['id'] ?? 0),
            'source' => $source,
            'event_type' => (string) ($row['event_type'] ?? ''),
            'entity_type' => (string) ($row['entity_type'] ?? ''),
            'entity_id' => (string) ($row['entity_id'] ?? ''),
            'payload' => $payload,
            'occurred_at' => (string) ($row['created_at'] ?? Time::now('Asia/Kolkata')->toDateTimeString()),
        ],
    ];
}

Step 4: Create New Method buildAbdmDispatchContext()

File: app/Libraries/BridgeSyncService.php (Add after buildCsnotkDispatchContext())

/**
 * Build dispatch context for ABDM Bridge (routes to https://abdm-bridge.e-atria.in/api/v1/bridge)
 *
 * @param array<string, mixed> $row
 * @param array<string, mixed> $payload
 * @return array<string, mixed>
 */
private function buildAbdmDispatchContext(array $row, array $payload): array
{
    $endpoint = $this->readSetting('ABDM_BRIDGE_URL');
    if ($endpoint === '') {
        return ['ok' => false, 'error' => 'ABDM_BRIDGE_URL is not configured'];
    }

    $token = $this->readSetting('ABDM_BRIDGE_TOKEN');
    $source = $this->readSetting('BRIDGE_SOURCE_CODE');
    if ($source === '') {
        $source = (string) ($this->readSetting('HOSPITAL_CODE') ?: 'hms-local');
    }

    $headers = ['Content-Type' => 'application/json'];
    if ($token !== '') {
        $headers['Authorization'] = 'Bearer ' . $token;
    }

    return [
        'ok' => true,
        'channel' => 'abdm',  // Distinct from 'csnotk'
        'endpoint' => $endpoint,
        'method' => 'POST',
        'headers' => $headers,
        'body' => [
            'queue_id' => (int) ($row['id'] ?? 0),
            'source' => $source,
            'event_type' => (string) ($row['event_type'] ?? ''),
            'entity_type' => (string) ($row['entity_type'] ?? ''),
            'entity_id' => (string) ($row['entity_id'] ?? ''),
            'payload' => $payload,
            'occurred_at' => (string) ($row['created_at'] ?? Time::now('Asia/Kolkata')->toDateTimeString()),
        ],
    ];
}

Step 5: Rename OLD buildBridgeDispatchContext() to buildLegacyDispatchContext()

For backward compatibility, keep the old method but rename it:

/**
 * Legacy bridge context (fallback for unknown event types)
 * @deprecated Use buildCsnotkDispatchContext() or buildAbdmDispatchContext()
 *
 * @param array<string, mixed> $row
 * @param array<string, mixed> $payload
 * @return array<string, mixed>
 */
private function buildLegacyDispatchContext(array $row, array $payload): array
{
    // ... original buildBridgeDispatchContext() code ...
}

Testing Checklist

Test 1: Queue SNOMED Event

# In HMS database, insert test record:
INSERT INTO bridge_sync_queue (
    channel, event_type, entity_type, entity_id,
    payload_json, payload_hash, status, attempts, max_attempts
) VALUES (
    'bridge',
    'snomed.diagnosis.validate',
    'diagnosis',
    '0',
    '{"diagnosis_text":"fever","return_limit":10}',
    SHA2('snomed.diagnosis.validate|diagnosis|0|...', 256),
    'pending',
    0,
    10
);

# Run queue processor
php spark bridge:sync --limit 5

# Check logs
SELECT * FROM abdm_api_logs WHERE channel='csnotk' AND event_type='snomed.diagnosis.validate' ORDER BY created_at DESC LIMIT 1;

Expected:

  • Queue record status changed to 'sent' (or 'retry' if CSNOtk unavailable)
  • abdm_api_logs.endpoint = https://csnotk.e-atria.in/api/bridge
  • abdm_api_logs.channel = 'csnotk'

Test 2: Queue ABDM Event

# In HMS database, insert test record:
INSERT INTO bridge_sync_queue (
    channel, event_type, entity_type, entity_id,
    payload_json, payload_hash, status, attempts, max_attempts
) VALUES (
    'bridge',
    'abdm.abha.validate',
    'patient',
    '12345',
    '{"abha_id":"00-0000-0000-0000"}',
    SHA2('abdm.abha.validate|patient|12345|...', 256),
    'pending',
    0,
    10
);

# Run queue processor
php spark bridge:sync --limit 5

# Check logs
SELECT * FROM abdm_api_logs WHERE channel='abdm' AND event_type='abdm.abha.validate' ORDER BY created_at DESC LIMIT 1;

Expected:

  • Queue record status changed to 'sent' (or 'retry' if gateway unavailable)
  • abdm_api_logs.endpoint = https://abdm-bridge.e-atria.in/api/v1/bridge
  • abdm_api_logs.channel = 'abdm'

Configuration Before Testing

Update HMS .env:

BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge
BRIDGE_SYNC_TOKEN = <32-char-hex-token-1>
ABDM_BRIDGE_URL = https://abdm-bridge.e-atria.in/api/v1/bridge
ABDM_BRIDGE_TOKEN = <32-char-hex-token-2>
BRIDGE_SOURCE_CODE = SBXID_033661

Generate tokens:

openssl rand -hex 32  # Token 1
openssl rand -hex 32  # Token 2

Code Review Checklist

  • buildDispatchContext() correctly identifies event prefix (snomed.* vs abdm.*)
  • buildCsnotkDispatchContext() uses correct URL and token keys
  • buildAbdmDispatchContext() uses correct URL and token keys
  • Channel is set correctly ('csnotk' or 'abdm') for logging
  • Both methods return same structure for processPending() to consume
  • Error handling for missing config keys
  • Backward compatibility maintained for legacy event types

References

  • Dual-Bridge Architecture: DUAL_BRIDGE_ARCHITECTURE.md
  • BridgeSyncService: app/Libraries/BridgeSyncService.php
  • AbdmConnector Config: app/Config/AbdmConnector.php
  • API Logs Table: bridge_sync_queue, abdm_api_logs