Confirmed empirically: the SAME clinical document (identical FHIR Bundle.id) gets re-delivered by the bridge under a DIFFERENT transaction_id/consent_ref every time it is re-fetched (e.g. via the by-ABHA-address historical fetch, which returns every past consent session for the patient). The old dedup hash included transaction_id+consent_ref, so every re-fetch created a brand-new duplicate row instead of updating the existing one -- observed as literal duplicate rows in the OPD ABDM Fetched Data list (same date/title appearing 2-3x). bundle_id is set once by the HIP and is stable across re-fetches/consents, so pair it with care_context_reference to uniquely identify a document instead.
- M3HiuWorkflowService: data_fetch now supports fetch_by_request_id=1, sending request_id (not consent_id) per bridge Method 1 -- targets the current/self consent request's own session, distinct from Method 2 (consent_id, which can resolve to a stale session sharing the same artifact).
- Patient::abdm_content_fetch_only(): tries Method 2 (consent_id) -> Method 1 (request_id, self data) -> Method 3 (abha_address, historical fallback) in order, stopping as soon as real documents are found.
- Fixed a real pre-existing bug this surfaced: getLatestAbdmSyncSnapshot() used the same flawed 'status=SUCCESS implies COMPLETED' check (without requiring workflow_state=DATA_RECEIVED) already fixed elsewhere in computeConsentSessionDetail(). This let broad/historical data_fetch rows (status=success, but decrypted_data=[]) hijack priority-500 'best' selection and silently drift the resolved 'latest granted consent' to an unrelated historical consent_id after Method 3 fallback ran. Now requires workflow_state=DATA_RECEIVED, matching the earlier fix.
Per bridge gateway team guidance, GET /v1/hiu/data/fetch?abha_address=... returns ALL historical decrypted records across every past consent for a patient, unlike the consent/request-scoped Methods 1/2 previously used exclusively. Added M3HiuWorkflowService support for a fetch_by_abha_address flag on the data_fetch operation, and wired abdm_content_fetch_only() to automatically fall back to this broader lookup when the consent-scoped fetch finds nothing new -- this surfaced 14 previously-invisible historical documents (consultation reports, pathology/cytology) for the test patient that were never being fetched before.
abdm_content_fetch_only() always returned phase=COMPLETED and 'Records fetched successfully' whenever the data_fetch API call itself succeeded (fetchResult.ok===1), even when documents_persisted+documents_updated was 0 (bridge/HIP hasn't actually delivered any records yet -- decrypted_data still empty, session status 'requested'). Now check documents_persisted+documents_updated: if 0, report phase=GRANTED with a clear 'not sent any records yet' message instead of falsely claiming success.
computeConsentSessionDetail() treated any data_fetch/hi_data_push_callback row with status=success as proof the session reached COMPLETED. But the DB 'status' column only reflects whether the underlying API call/poll succeeded (no HTTP/transport error) -- NOT whether data was actually received; that's tracked separately via workflow_state (DATA_PENDING vs DATA_RECEIVED). A data_fetch poll that succeeds at the HTTP level but returns decrypted_data=[] (bridge still processing) was incorrectly marked COMPLETED instead of staying at GRANTED. Now also require workflow_state === DATA_RECEIVED for the COMPLETED phase.
When a consent session's overall phase resolves to GRANTED/COMPLETED via a data_fetch or hi_data_push_callback row (proof data was actually fetched), but no consent_reconcile/consent_status row in that session ever recorded which hi_types were granted (e.g. data arrives via async push before any reconcile poll runs), grantedHiTypes stayed empty and every requested HI type was incorrectly marked DENIED. Fall back to treating the full requested set as granted when we have positive proof of GRANTED/COMPLETED but no negative evidence of per-type denial.
- New Patient::abdm_content_request_custom(int \) endpoint builds a custom
consent{} block (purpose, hiTypes, permission.dateRange, dataEraseAt) from
user-submitted hi_types[]/date_from/date_to/purpose form fields, instead of
always using M3HiuWorkflowService::buildDefaultConsent()'s hardcoded
OPConsultation+DiagnosticReport / 365-day defaults. Reuses the existing
requester/hiu-id resolution and hiTypes normalization already built into
preparePayloadForOperation() for the consent_request operation, so no
M3HiuWorkflowService changes were needed.
- New route (POST) patient/abdm_content_request_custom/(:num).
- New 'Custom Request' button + '#abdmCustomConsentModal' form
(Patient_Profile_Opd_V.php) with HI type checkboxes (OPConsultation,
DiagnosticReport, Prescription, DischargeSummary, HealthDocument,
ImmunizationRecord, Wellness, Invoice), Valid From/To date inputs
(defaulted to today-365d..today), and a Purpose text field. On submit,
reuses the existing auto-flow polling loop (runAutoFlowStep) with the
returned request_id to track status through to data fetch.
- Live-verified end-to-end on patient 11 (Devender Singh): submitted a
request for OPConsultation + DiagnosticReport + Prescription; sandbox
granted only OPConsultation/DiagnosticReport, and the Phase 1 consent
detail modal correctly showed Prescription as DENIED alongside the two
GRANTED types, confirming the partial-grant per-HI-type breakdown works
correctly end-to-end.
- New Patient::getAbdmConsentArtifactDetail() helper builds a requested-vs-granted
HI type breakdown for a patient's current ABDM consent artifact, using the
bridge-team-confirmed 'Option A' model: overall status is single-valued
(GRANTED/REVOKED/EXPIRED/DENIED), per-type status is derived by comparing the
requested hiTypes (from the saved consent_request request_json) against the
granted hi_types array (already flattened onto responses by
M3HiuGatewayClient). No bridge changes or DB migrations required.
- New Patient::abdm_consent_detail(int \) JSON endpoint + route
patient/abdm_consent_detail/(:num).
- New 'View Consent' link + Bootstrap modal on the OPD ABDM tab
(Patient_Profile_Opd_V.php) rendering a lifecycle stepper, consent metadata
(Consent ID, Purpose, Valid From/To, Erase Date), and a per-HI-type table
(Document Type / Permission / Status / Timestamp).
- Fixed a bug found during live testing: once a consent reaches COMPLETED
(after a successful data_fetch), the highest-priority workflow row has no
hi_types field (it's a FHIR data bundle), which was causing all granted
types to show as DENIED. Now searches workflow history for the most recent
row that actually carries the granted hi_types payload.
- Live-verified against patient 15 (Meera Bisht): both requested HI types
(OPConsultation, DiagnosticReport) correctly show GRANTED with timestamps.
Previously, clicking 'Fetch ABDM Records' when a prior consent request was
stuck in REQUESTED/PENDING (no GRANTED/DENIED callback ever received from
the ABDM bridge/PHR - e.g. patient never opened/approved it in their PHR
app) would just resume/re-poll that same dead request indefinitely, since
getLatestAbdmSyncSnapshot() always prioritized the existing REQUESTED
request over starting a new one.
Now:
- getLatestAbdmSyncSnapshot() computes the age of a REQUESTED/PENDING
snapshot's updated_at and marks restart_required = true once it has
been pending for more than ABDM_PENDING_STALE_SECONDS (1 hour).
- abdm_content_auto_flow() honors this flag: when the last sync is stale,
it no longer resumes the old request_id and instead falls through to
create a brand-new consent_request against the bridge.
Verified locally: a 6h-old stale REQUESTED request for a test ABHA was
correctly abandoned and a fresh consent_request was created and granted
on the next 'Fetch ABDM Records' click, instead of re-polling the dead
request.
- M3HiuDocumentRepository::persistFromDataFetch no longer includes the
per-call request_id in the document dedup hash. request_id is unique
every time data_fetch runs, so it was causing a brand-new duplicate
row to be inserted on every re-fetch of the exact same ABDM document.
Now dedup key is transaction_id + consent_ref + care_context_reference
+ bundle_id, which stays stable across repeated fetches -> re-fetches
UPDATE the existing row instead of duplicating it.
- Added Patient::abdm_content_fetch_only() + route
billing/patient/abdm_content_fetch_only/(id): a lightweight action for
when consent is already GRANTED/COMPLETED. It calls data_fetch directly
using the saved consent reference, skipping consent_request + reconcile
polling entirely, avoiding unnecessary bridge calls/noise.
- Patient_Profile_Opd_V.php: added a 'Fetch Records' button that only
appears once consent phase is GRANTED or COMPLETED, wired to the new
endpoint; on success it refreshes the document list in place. The
original 'Fetch ABDM Records' button still owns the full consent
request -> reconcile -> fetch flow and already stops/re-enables
correctly on DENIED/FAILED (verified, no change needed there).
- One-time cleanup: deduped existing abdm_hiu_documents rows in dev DB
(13 -> 3) and recomputed doc_hash with the new stable key.
Adds a 'Verify SSL Certificate' switch on Admin -> ABDM Gateway page,
persisted as hospital_setting.ABDM_BRIDGE_SSL_VERIFY (default ON/secure).
Wired into M3HiuGatewayClient, EAtriaBridgeConnector, and the settings
page's own Test Connection check. Per-machine ABDM_BRIDGE_SSL_VERIFY env
var still overrides this DB setting for deployments needing a quick
local-only workaround. Verification stays on by default everywhere;
disabling it is an explicit, visible opt-in with an on-page warning.
Some local WAMP/XAMPP/Laragon PHP installs ship an outdated cacert.pem,
causing outbound HTTPS calls to the bridge to fail with cURL error 60
(SSL certificate problem). Rather than globally disabling certificate
verification (as bridge support suggested), verification stays ON by
default and can only be disabled per-deployment by explicitly setting
ABDM_BRIDGE_SSL_VERIFY=false in that machine's .env. The real fix on
affected machines is updating curl.cainfo in php.ini.
- add facility QR support and OPD queue bridge fixes
- support care-context push warnings for lab, radiology, IPD and remaining HI types
- improve ABHA patient matching and diagnosis autocomplete performance
- clean up webcam streams on page navigation
Serialize the full repaired body instead of only the temporary wrapper contents so DOMDocument recovery does not drop later discharge sections before mPDF rendering.
- EAtriaBridgeConnector: fix OPConsultRecord hi_type normalization (was wrongly
mapping to PrescriptionRecord); allow push with either abha_address or abha_id;
add careContextId alias for care_context_reference in pushRecord payload
- AbdmGateway: migrate OPD share to records/push semantics with unique
care_context_reference, ABHA id/address resolution, bridge_record_id capture,
and 409 DUPLICATE_RECORD non-fatal handling
- AbdmGateway: add non-ABHA local-only storage path for OPD/IPD/Lab share
(push_status=local_only, no bridge call, audit logged)
- AbdmGateway: add POST /records/discover endpoint with ABHA-first + fallback
matching by mobile, hospital ID, patient UHID+birthYear (PHR-compatible)
- AbdmGateway: add GET /records/fetch/{careContextId} endpoint returning
decrypted FHIR payload from health_records
- AbdmGateway: dual-write consent artefacts to abdm_consent_records AND
consents table when both present; getActiveConsentRecord falls back to
consents table when abdm_consent_records is absent
- AbdmAuditService: write to audit_logs (generic schema) when present,
fallback to abdm_audit_logs; field-aware insert supports both schemas
- Routes: register /records/discover and /records/fetch/(:any)
- Webhook signature validator: check EKA_WEBHOOK_SECRET then HMS_WEBHOOK_SECRET
for gateway compatibility