fix(abdm): capture patient-modified consent dates, fix premature expiry, and update consent UI labels
- Extract and contrast both requested and patient-granted date ranges and eraseAt timestamps from ABDM consent artefacts - Fix premature session expiration when patient grants longer consent validity than initially requested by HIU - Update UI labels from Valid From / Valid To to Request From / Request To and Consent Valid Upto - Show original requested dates struck-through and granted dates highlighted with Updated badges in consent detail modals - Add visual indicators for patient-modified dates in consent requests tables
This commit is contained in:
parent
43d52163ab
commit
6cc34057b5
|
|
@ -6327,9 +6327,9 @@ class Patient extends BaseController
|
|||
$requestedHiTypes = [];
|
||||
$requestedOn = '';
|
||||
$purpose = '';
|
||||
$validFrom = '';
|
||||
$validTo = '';
|
||||
$eraseAt = '';
|
||||
$requestedFrom = '';
|
||||
$requestedTo = '';
|
||||
$requestedEraseAt = '';
|
||||
$requestedBy = '';
|
||||
$hfrId = trim((string) ($best['hfr_id'] ?? ''));
|
||||
|
||||
|
|
@ -6342,40 +6342,31 @@ class Patient extends BaseController
|
|||
$requestedHiTypes = $this->normalizeHiTypesList($consentBlock['hiTypes'] ?? $consentBlock['hi_types'] ?? []);
|
||||
$requestedOn = trim((string) ($consentRequestRow['created_at'] ?? ''));
|
||||
$purpose = trim((string) ($consentBlock['purpose']['text'] ?? $consentBlock['purpose']['code'] ?? ''));
|
||||
$validFrom = trim((string) ($consentBlock['permission']['dateRange']['from'] ?? $consentBlock['permission']['date_range']['from'] ?? ''));
|
||||
$validTo = trim((string) ($consentBlock['permission']['dateRange']['to'] ?? $consentBlock['permission']['date_range']['to'] ?? ''));
|
||||
$eraseAt = trim((string) ($consentBlock['permission']['dataEraseAt'] ?? $consentBlock['permission']['data_erase_at'] ?? ''));
|
||||
$requestedFrom = trim((string) ($consentBlock['permission']['dateRange']['from'] ?? $consentBlock['permission']['date_range']['from'] ?? ''));
|
||||
$requestedTo = trim((string) ($consentBlock['permission']['dateRange']['to'] ?? $consentBlock['permission']['date_range']['to'] ?? ''));
|
||||
$requestedEraseAt = trim((string) ($consentBlock['permission']['dataEraseAt'] ?? $consentBlock['permission']['data_erase_at'] ?? ''));
|
||||
$requestedBy = trim((string) ($consentBlock['requester']['name'] ?? ''));
|
||||
if ($hfrId === '') {
|
||||
$hfrId = trim((string) ($consentRequestRow['hfr_id'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback scanning across all session rows for missing metadata (dates, purpose, requestedBy)
|
||||
// Fallback scanning across session rows for missing requested metadata
|
||||
foreach ($rows as $row) {
|
||||
$rowReq = json_decode((string) ($row['request_json'] ?? ''), true);
|
||||
$rowResp = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
$containers = [
|
||||
is_array($rowReq) ? ($rowReq['consent'] ?? null) : null,
|
||||
is_array($rowReq) ? ($rowReq['consentDetail'] ?? null) : null,
|
||||
is_array($rowReq) ? ($rowReq['data']['consent'] ?? null) : null,
|
||||
is_array($rowReq) ? ($rowReq['data']['consentDetail'] ?? null) : null,
|
||||
is_array($rowReq) ? ($rowReq['data'] ?? null) : null,
|
||||
is_array($rowReq) ? $rowReq : null,
|
||||
is_array($rowResp) ? ($rowResp['consent'] ?? null) : null,
|
||||
is_array($rowResp) ? ($rowResp['consentDetail'] ?? null) : null,
|
||||
is_array($rowResp) ? ($rowResp['data']['consent'] ?? null) : null,
|
||||
is_array($rowResp) ? ($rowResp['data']['consentDetail'] ?? null) : null,
|
||||
is_array($rowResp) ? ($rowResp['data'] ?? null) : null,
|
||||
is_array($rowResp) ? $rowResp : null,
|
||||
];
|
||||
|
||||
foreach ($containers as $c) {
|
||||
if (! is_array($c)) {
|
||||
continue;
|
||||
}
|
||||
if ($validFrom === '') {
|
||||
$validFrom = trim((string) (
|
||||
if ($requestedFrom === '') {
|
||||
$requestedFrom = trim((string) (
|
||||
$c['permission']['dateRange']['from']
|
||||
?? $c['permission']['date_range']['from']
|
||||
?? $c['date_range']['from']
|
||||
|
|
@ -6387,8 +6378,8 @@ class Patient extends BaseController
|
|||
?? ''
|
||||
));
|
||||
}
|
||||
if ($validTo === '') {
|
||||
$validTo = trim((string) (
|
||||
if ($requestedTo === '') {
|
||||
$requestedTo = trim((string) (
|
||||
$c['permission']['dateRange']['to']
|
||||
?? $c['permission']['date_range']['to']
|
||||
?? $c['date_range']['to']
|
||||
|
|
@ -6400,8 +6391,8 @@ class Patient extends BaseController
|
|||
?? ''
|
||||
));
|
||||
}
|
||||
if ($eraseAt === '') {
|
||||
$eraseAt = trim((string) (
|
||||
if ($requestedEraseAt === '') {
|
||||
$requestedEraseAt = trim((string) (
|
||||
$c['permission']['dataEraseAt']
|
||||
?? $c['permission']['data_erase_at']
|
||||
?? $c['expiry']
|
||||
|
|
@ -6416,7 +6407,6 @@ class Patient extends BaseController
|
|||
$purpose = trim((string) (
|
||||
$c['purpose']['text']
|
||||
?? $c['purpose']['code']
|
||||
?? $c['purpose']
|
||||
?? ''
|
||||
));
|
||||
}
|
||||
|
|
@ -6427,66 +6417,167 @@ class Patient extends BaseController
|
|||
?? ''
|
||||
));
|
||||
}
|
||||
if ($requestedOn === '') {
|
||||
$requestedOn = trim((string) ($row['created_at'] ?? ''));
|
||||
}
|
||||
}
|
||||
if ($requestedOn === '') {
|
||||
$requestedOn = trim((string) ($row['created_at'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge flattens consent.hi_types -> top-level hi_types on consent_status /
|
||||
// consent_reconcile / consent_callback responses (see M3HiuGatewayClient
|
||||
// flatten logic). The "best" row for overall phase can be a data_fetch /
|
||||
// hi_data_push_callback row (once records are COMPLETED) whose response is
|
||||
// a FHIR data bundle with no hi_types field at all — so look separately for
|
||||
// the most recent row (within this session) that actually carries the
|
||||
// granted hi_types payload.
|
||||
// Scan for GRANTED / UPDATED metadata from ABDM callback & response payloads
|
||||
$grantedHiTypes = [];
|
||||
$grantedOn = '';
|
||||
$grantedFrom = '';
|
||||
$grantedTo = '';
|
||||
$grantedEraseAt = '';
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
$rowHiTypes = $this->normalizeHiTypesList($decoded['hi_types'] ?? $decoded['consent']['hi_types'] ?? []);
|
||||
if ($rowHiTypes !== []) {
|
||||
$grantedHiTypes = $rowHiTypes;
|
||||
$grantedOn = trim((string) ($decoded['granted_at'] ?? $row['updated_at'] ?? ''));
|
||||
break;
|
||||
|
||||
$containers = [
|
||||
$decoded['consent'] ?? null,
|
||||
$decoded['consentDetail'] ?? null,
|
||||
$decoded['data']['consent'] ?? null,
|
||||
$decoded['data']['consentDetail'] ?? null,
|
||||
$decoded['data'] ?? null,
|
||||
$decoded,
|
||||
];
|
||||
|
||||
foreach ($containers as $c) {
|
||||
if (! is_array($c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowHiTypes = $this->normalizeHiTypesList($c['hi_types'] ?? $c['hiTypes'] ?? []);
|
||||
if ($rowHiTypes !== []) {
|
||||
$grantedHiTypes = $rowHiTypes;
|
||||
}
|
||||
|
||||
$candGrantedOn = trim((string) (
|
||||
$c['granted_at']
|
||||
?? $c['grantedAt']
|
||||
?? $c['permission']['granted_at']
|
||||
?? $c['permission']['grantedAt']
|
||||
?? ''
|
||||
));
|
||||
if ($candGrantedOn !== '') {
|
||||
$grantedOn = $candGrantedOn;
|
||||
}
|
||||
|
||||
$candFrom = trim((string) (
|
||||
$c['date_range']['from']
|
||||
?? $c['dateRange']['from']
|
||||
?? $c['permission']['dateRange']['from']
|
||||
?? $c['permission']['date_range']['from']
|
||||
?? $c['valid_from']
|
||||
?? $c['validFrom']
|
||||
?? ''
|
||||
));
|
||||
if ($candFrom !== '') {
|
||||
$grantedFrom = $candFrom;
|
||||
}
|
||||
|
||||
$candTo = trim((string) (
|
||||
$c['date_range']['to']
|
||||
?? $c['dateRange']['to']
|
||||
?? $c['permission']['dateRange']['to']
|
||||
?? $c['permission']['date_range']['to']
|
||||
?? $c['valid_to']
|
||||
?? $c['validTo']
|
||||
?? ''
|
||||
));
|
||||
if ($candTo !== '') {
|
||||
$grantedTo = $candTo;
|
||||
}
|
||||
|
||||
$candErase = trim((string) (
|
||||
(is_array($c['expiry'] ?? null) ? ($c['expiry']['date'] ?? null) : ($c['expiry'] ?? null))
|
||||
?? $c['permission']['dataEraseAt']
|
||||
?? $c['permission']['data_erase_at']
|
||||
?? $c['dataEraseAt']
|
||||
?? $c['data_erase_at']
|
||||
?? $c['erase_at']
|
||||
?? $c['eraseAt']
|
||||
?? ''
|
||||
));
|
||||
if ($candErase !== '') {
|
||||
$grantedEraseAt = $candErase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this session's overall phase is GRANTED/COMPLETED but no row ever
|
||||
// recorded WHICH hi_types were granted (e.g. data arrived via an async
|
||||
// hi_data_push_callback before any consent_reconcile/consent_status poll
|
||||
// ran and stored a hi_types-bearing response), do not mark every
|
||||
// requested type as DENIED for lack of evidence — a COMPLETED/GRANTED
|
||||
// phase is itself proof the consent covers at least the requested
|
||||
// types, so fall back to treating the full requested set as granted.
|
||||
// recorded WHICH hi_types were granted, fall back to requested types.
|
||||
if ($grantedHiTypes === [] && $requestedHiTypes !== [] && in_array($phase, ['GRANTED', 'COMPLETED'], true)) {
|
||||
$grantedHiTypes = $requestedHiTypes;
|
||||
}
|
||||
|
||||
// Effective values: if granted values exist from ABDM, they represent the official patient-approved values
|
||||
$validFrom = $grantedFrom !== '' ? $grantedFrom : $requestedFrom;
|
||||
$validTo = $grantedTo !== '' ? $grantedTo : $requestedTo;
|
||||
$eraseAt = $grantedEraseAt !== '' ? $grantedEraseAt : $requestedEraseAt;
|
||||
|
||||
// Detect if patient modified the date range or expiry in the PHR app
|
||||
$isDateFromModified = false;
|
||||
if ($requestedFrom !== '' && $grantedFrom !== '') {
|
||||
$tsReqFrom = strtotime($requestedFrom);
|
||||
$tsGntFrom = strtotime($grantedFrom);
|
||||
if ($tsReqFrom !== false && $tsGntFrom !== false && date('Y-m-d', $tsReqFrom) !== date('Y-m-d', $tsGntFrom)) {
|
||||
$isDateFromModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
$isDateToModified = false;
|
||||
if ($requestedTo !== '' && $grantedTo !== '') {
|
||||
$tsReqTo = strtotime($requestedTo);
|
||||
$tsGntTo = strtotime($grantedTo);
|
||||
if ($tsReqTo !== false && $tsGntTo !== false && date('Y-m-d', $tsReqTo) !== date('Y-m-d', $tsGntTo)) {
|
||||
$isDateToModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
$isEraseAtModified = false;
|
||||
if ($requestedEraseAt !== '' && $grantedEraseAt !== '') {
|
||||
$tsReqErase = strtotime($requestedEraseAt);
|
||||
$tsGntErase = strtotime($grantedEraseAt);
|
||||
if ($tsReqErase !== false && $tsGntErase !== false && abs($tsReqErase - $tsGntErase) > 60) {
|
||||
$isEraseAtModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
$revokedOn = trim((string) ($best['revoked_at'] ?? ''));
|
||||
$expiredOn = trim((string) ($best['expired_at'] ?? ''));
|
||||
if (in_array($phase, ['GRANTED', 'COMPLETED'], true) && $grantedOn === '') {
|
||||
$grantedOn = trim((string) ($bestDecoded['granted_at'] ?? $best['updated_at'] ?? ''));
|
||||
}
|
||||
|
||||
// ABDM M3 HIU compliance: evaluate consent expiration against erase_at (dataEraseAt)
|
||||
// ABDM M3 HIU compliance: evaluate consent expiration against effective erase_at (dataEraseAt)
|
||||
$isConsentExpired = false;
|
||||
if ($phase === 'EXPIRED') {
|
||||
$isConsentExpired = true;
|
||||
} elseif ($eraseAt !== '') {
|
||||
$eraseTimestamp = strtotime($eraseAt);
|
||||
if ($eraseTimestamp !== false && $eraseTimestamp <= time()) {
|
||||
$effectiveEraseTimestamp = ($eraseAt !== '') ? strtotime($eraseAt) : false;
|
||||
if ($effectiveEraseTimestamp !== false) {
|
||||
if ($effectiveEraseTimestamp <= time()) {
|
||||
$isConsentExpired = true;
|
||||
}
|
||||
}
|
||||
if (! $isConsentExpired && $expiredOn !== '') {
|
||||
} elseif ($expiredOn !== '') {
|
||||
$expTime = strtotime($expiredOn);
|
||||
if ($expTime !== false && $expTime <= time()) {
|
||||
$isConsentExpired = true;
|
||||
}
|
||||
} elseif ($phase === 'EXPIRED') {
|
||||
$isConsentExpired = true;
|
||||
}
|
||||
|
||||
// If effective eraseAt is in the future, the consent is NOT expired!
|
||||
// (Revive from any premature EXPIRED state stamped by legacy code)
|
||||
if (! $isConsentExpired && $phase === 'EXPIRED') {
|
||||
$expiredOn = '';
|
||||
if ($status === 'SUCCESS' && ($state === 'DATA_RECEIVED' || in_array($bestPriority, [480, 490, 500], true))) {
|
||||
$phase = 'COMPLETED';
|
||||
} elseif ($grantedOn !== '' || in_array($rawConsentStatus, ['GRANTED', 'APPROVED', 'ACTIVE'], true) || in_array($state, ['GRANTED', 'APPROVED', 'ACTIVE'], true)) {
|
||||
$phase = 'GRANTED';
|
||||
}
|
||||
}
|
||||
|
||||
if ($isConsentExpired) {
|
||||
|
|
@ -6575,6 +6666,16 @@ class Patient extends BaseController
|
|||
'valid_from' => $validFrom,
|
||||
'valid_to' => $validTo,
|
||||
'erase_at' => $eraseAt,
|
||||
'requested_from' => $requestedFrom,
|
||||
'requested_to' => $requestedTo,
|
||||
'requested_erase_at' => $requestedEraseAt,
|
||||
'granted_from' => $grantedFrom,
|
||||
'granted_to' => $grantedTo,
|
||||
'granted_erase_at' => $grantedEraseAt,
|
||||
'is_date_from_modified' => $isDateFromModified,
|
||||
'is_date_to_modified' => $isDateToModified,
|
||||
'is_date_range_modified' => ($isDateFromModified || $isDateToModified),
|
||||
'is_erase_at_modified' => $isEraseAtModified,
|
||||
'requested_on' => $requestedOn,
|
||||
'granted_on' => $grantedOn,
|
||||
'revoked_on' => $revokedOn,
|
||||
|
|
|
|||
|
|
@ -573,9 +573,9 @@ class ConsentSessionListService
|
|||
$requestedHiTypes = [];
|
||||
$requestedOn = '';
|
||||
$purpose = '';
|
||||
$validFrom = '';
|
||||
$validTo = '';
|
||||
$eraseAt = '';
|
||||
$requestedFrom = '';
|
||||
$requestedTo = '';
|
||||
$requestedEraseAt = '';
|
||||
$requestedBy = '';
|
||||
$hfrId = trim((string) ($best['hfr_id'] ?? ''));
|
||||
|
||||
|
|
@ -588,17 +588,17 @@ class ConsentSessionListService
|
|||
$requestedHiTypes = $this->normalizeHiTypesList($consentBlock['hiTypes'] ?? $consentBlock['hi_types'] ?? []);
|
||||
$requestedOn = trim((string) ($consentRequestRow['created_at'] ?? ''));
|
||||
$purpose = $this->extractScalarString($consentBlock['purpose']['text'] ?? $consentBlock['purpose']['code'] ?? null);
|
||||
$validFrom = $this->extractScalarString(
|
||||
$requestedFrom = $this->extractScalarString(
|
||||
$consentBlock['permission']['dateRange']['from']
|
||||
?? $consentBlock['permission']['date_range']['from']
|
||||
?? null
|
||||
);
|
||||
$validTo = $this->extractScalarString(
|
||||
$requestedTo = $this->extractScalarString(
|
||||
$consentBlock['permission']['dateRange']['to']
|
||||
?? $consentBlock['permission']['date_range']['to']
|
||||
?? null
|
||||
);
|
||||
$eraseAt = $this->extractScalarString(
|
||||
$requestedEraseAt = $this->extractScalarString(
|
||||
$consentBlock['permission']['dataEraseAt']
|
||||
?? $consentBlock['permission']['data_erase_at']
|
||||
?? null
|
||||
|
|
@ -609,48 +609,55 @@ class ConsentSessionListService
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback scanning across all session rows for missing metadata (dates, purpose, requestedBy)
|
||||
// Fallback scanning across session rows for missing requested metadata
|
||||
foreach ($rows as $row) {
|
||||
$rowReq = json_decode((string) ($row['request_json'] ?? ''), true);
|
||||
$rowResp = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
$containers = [
|
||||
is_array($rowReq) ? ($rowReq['consent'] ?? null) : null,
|
||||
is_array($rowReq) ? ($rowReq['consentDetail'] ?? null) : null,
|
||||
is_array($rowReq) ? ($rowReq['data']['consent'] ?? null) : null,
|
||||
is_array($rowReq) ? $rowReq : null,
|
||||
is_array($rowResp) ? ($rowResp['consent'] ?? null) : null,
|
||||
is_array($rowResp) ? ($rowResp['consentDetail'] ?? null) : null,
|
||||
is_array($rowResp) ? $rowResp : null,
|
||||
];
|
||||
|
||||
foreach ($containers as $c) {
|
||||
if (! is_array($c)) {
|
||||
continue;
|
||||
}
|
||||
if ($validFrom === '') {
|
||||
$validFrom = $this->extractScalarString(
|
||||
if ($requestedFrom === '') {
|
||||
$requestedFrom = $this->extractScalarString(
|
||||
$c['permission']['dateRange']['from']
|
||||
?? $c['permission']['date_range']['from']
|
||||
?? $c['date_range']['from']
|
||||
?? $c['dateRange']['from']
|
||||
?? $c['valid_from']
|
||||
?? $c['validFrom']
|
||||
?? $c['date_from']
|
||||
?? $c['dateFrom']
|
||||
?? null
|
||||
);
|
||||
}
|
||||
if ($validTo === '') {
|
||||
$validTo = $this->extractScalarString(
|
||||
if ($requestedTo === '') {
|
||||
$requestedTo = $this->extractScalarString(
|
||||
$c['permission']['dateRange']['to']
|
||||
?? $c['permission']['date_range']['to']
|
||||
?? $c['date_range']['to']
|
||||
?? $c['dateRange']['to']
|
||||
?? $c['valid_to']
|
||||
?? $c['validTo']
|
||||
?? $c['date_to']
|
||||
?? $c['dateTo']
|
||||
?? null
|
||||
);
|
||||
}
|
||||
if ($eraseAt === '') {
|
||||
$eraseAt = $this->extractScalarString(
|
||||
if ($requestedEraseAt === '') {
|
||||
$requestedEraseAt = $this->extractScalarString(
|
||||
$c['permission']['dataEraseAt']
|
||||
?? $c['permission']['data_erase_at']
|
||||
?? (is_array($c['expiry'] ?? null) ? ($c['expiry']['date'] ?? null) : ($c['expiry'] ?? null))
|
||||
?? $c['dataEraseAt']
|
||||
?? $c['data_erase_at']
|
||||
?? $c['erase_at']
|
||||
?? $c['eraseAt']
|
||||
?? null
|
||||
);
|
||||
}
|
||||
|
|
@ -658,7 +665,6 @@ class ConsentSessionListService
|
|||
$purpose = $this->extractScalarString(
|
||||
$c['purpose']['text']
|
||||
?? $c['purpose']['code']
|
||||
?? $c['purpose']
|
||||
?? null
|
||||
);
|
||||
}
|
||||
|
|
@ -669,27 +675,133 @@ class ConsentSessionListService
|
|||
?? null
|
||||
);
|
||||
}
|
||||
if ($requestedOn === '') {
|
||||
$requestedOn = trim((string) ($row['created_at'] ?? ''));
|
||||
}
|
||||
}
|
||||
if ($requestedOn === '') {
|
||||
$requestedOn = trim((string) ($row['created_at'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
// Scan for GRANTED / UPDATED metadata from ABDM callback & response payloads
|
||||
$grantedHiTypes = [];
|
||||
$grantedOn = '';
|
||||
$grantedFrom = '';
|
||||
$grantedTo = '';
|
||||
$grantedEraseAt = '';
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
$rowHiTypes = $this->normalizeHiTypesList($decoded['hi_types'] ?? $decoded['consent']['hi_types'] ?? []);
|
||||
if ($rowHiTypes !== []) {
|
||||
$grantedHiTypes = $rowHiTypes;
|
||||
$grantedOn = trim((string) (
|
||||
$this->extractScalarString($decoded['granted_at'] ?? null)
|
||||
?: ($row['updated_at'] ?? '')
|
||||
|
||||
$containers = [
|
||||
$decoded['consent'] ?? null,
|
||||
$decoded['consentDetail'] ?? null,
|
||||
$decoded['data']['consent'] ?? null,
|
||||
$decoded['data']['consentDetail'] ?? null,
|
||||
$decoded['data'] ?? null,
|
||||
$decoded,
|
||||
];
|
||||
|
||||
foreach ($containers as $c) {
|
||||
if (! is_array($c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowHiTypes = $this->normalizeHiTypesList($c['hi_types'] ?? $c['hiTypes'] ?? []);
|
||||
if ($rowHiTypes !== []) {
|
||||
$grantedHiTypes = $rowHiTypes;
|
||||
}
|
||||
|
||||
$candGrantedOn = trim((string) (
|
||||
$this->extractScalarString($c['granted_at'] ?? null)
|
||||
?: $this->extractScalarString($c['grantedAt'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['granted_at'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['grantedAt'] ?? null)
|
||||
?: ''
|
||||
));
|
||||
break;
|
||||
if ($candGrantedOn !== '') {
|
||||
$grantedOn = $candGrantedOn;
|
||||
}
|
||||
|
||||
$candFrom = trim((string) (
|
||||
$this->extractScalarString($c['date_range']['from'] ?? null)
|
||||
?: $this->extractScalarString($c['dateRange']['from'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['dateRange']['from'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['date_range']['from'] ?? null)
|
||||
?: $this->extractScalarString($c['valid_from'] ?? null)
|
||||
?: $this->extractScalarString($c['validFrom'] ?? null)
|
||||
?: ''
|
||||
));
|
||||
if ($candFrom !== '') {
|
||||
$grantedFrom = $candFrom;
|
||||
}
|
||||
|
||||
$candTo = trim((string) (
|
||||
$this->extractScalarString($c['date_range']['to'] ?? null)
|
||||
?: $this->extractScalarString($c['dateRange']['to'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['dateRange']['to'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['date_range']['to'] ?? null)
|
||||
?: $this->extractScalarString($c['valid_to'] ?? null)
|
||||
?: $this->extractScalarString($c['validTo'] ?? null)
|
||||
?: ''
|
||||
));
|
||||
if ($candTo !== '') {
|
||||
$grantedTo = $candTo;
|
||||
}
|
||||
|
||||
$candErase = trim((string) (
|
||||
$this->extractScalarString(is_array($c['expiry'] ?? null) ? ($c['expiry']['date'] ?? null) : ($c['expiry'] ?? null))
|
||||
?: $this->extractScalarString($c['permission']['dataEraseAt'] ?? null)
|
||||
?: $this->extractScalarString($c['permission']['data_erase_at'] ?? null)
|
||||
?: $this->extractScalarString($c['dataEraseAt'] ?? null)
|
||||
?: $this->extractScalarString($c['data_erase_at'] ?? null)
|
||||
?: $this->extractScalarString($c['erase_at'] ?? null)
|
||||
?: $this->extractScalarString($c['eraseAt'] ?? null)
|
||||
?: ''
|
||||
));
|
||||
if ($candErase !== '') {
|
||||
$grantedEraseAt = $candErase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this session's overall phase is GRANTED/COMPLETED but no row ever
|
||||
// recorded WHICH hi_types were granted, fall back to requested types.
|
||||
if ($grantedHiTypes === [] && $requestedHiTypes !== [] && in_array($phase, ['GRANTED', 'COMPLETED'], true)) {
|
||||
$grantedHiTypes = $requestedHiTypes;
|
||||
}
|
||||
|
||||
// Effective values: if granted values exist from ABDM, they represent the official patient-approved values
|
||||
$validFrom = $grantedFrom !== '' ? $grantedFrom : $requestedFrom;
|
||||
$validTo = $grantedTo !== '' ? $grantedTo : $requestedTo;
|
||||
$eraseAt = $grantedEraseAt !== '' ? $grantedEraseAt : $requestedEraseAt;
|
||||
|
||||
// Detect if patient modified the date range or expiry in the PHR app
|
||||
$isDateFromModified = false;
|
||||
if ($requestedFrom !== '' && $grantedFrom !== '') {
|
||||
$tsReqFrom = strtotime($requestedFrom);
|
||||
$tsGntFrom = strtotime($grantedFrom);
|
||||
if ($tsReqFrom !== false && $tsGntFrom !== false && date('Y-m-d', $tsReqFrom) !== date('Y-m-d', $tsGntFrom)) {
|
||||
$isDateFromModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
$isDateToModified = false;
|
||||
if ($requestedTo !== '' && $grantedTo !== '') {
|
||||
$tsReqTo = strtotime($requestedTo);
|
||||
$tsGntTo = strtotime($grantedTo);
|
||||
if ($tsReqTo !== false && $tsGntTo !== false && date('Y-m-d', $tsReqTo) !== date('Y-m-d', $tsGntTo)) {
|
||||
$isDateToModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
$isEraseAtModified = false;
|
||||
if ($requestedEraseAt !== '' && $grantedEraseAt !== '') {
|
||||
$tsReqErase = strtotime($requestedEraseAt);
|
||||
$tsGntErase = strtotime($grantedEraseAt);
|
||||
if ($tsReqErase !== false && $tsGntErase !== false && abs($tsReqErase - $tsGntErase) > 60) {
|
||||
$isEraseAtModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -702,21 +814,31 @@ class ConsentSessionListService
|
|||
));
|
||||
}
|
||||
|
||||
// ABDM M3 HIU compliance: evaluate consent expiration against erase_at (dataEraseAt)
|
||||
// ABDM M3 HIU compliance: evaluate consent expiration against effective erase_at (dataEraseAt)
|
||||
$isConsentExpired = false;
|
||||
if ($phase === 'EXPIRED') {
|
||||
$isConsentExpired = true;
|
||||
} elseif ($eraseAt !== '') {
|
||||
$eraseTimestamp = strtotime($eraseAt);
|
||||
if ($eraseTimestamp !== false && $eraseTimestamp <= time()) {
|
||||
$effectiveEraseTimestamp = ($eraseAt !== '') ? strtotime($eraseAt) : false;
|
||||
if ($effectiveEraseTimestamp !== false) {
|
||||
if ($effectiveEraseTimestamp <= time()) {
|
||||
$isConsentExpired = true;
|
||||
}
|
||||
}
|
||||
if (! $isConsentExpired && $expiredOn !== '') {
|
||||
} elseif ($expiredOn !== '') {
|
||||
$expTime = strtotime($expiredOn);
|
||||
if ($expTime !== false && $expTime <= time()) {
|
||||
$isConsentExpired = true;
|
||||
}
|
||||
} elseif ($phase === 'EXPIRED') {
|
||||
$isConsentExpired = true;
|
||||
}
|
||||
|
||||
// If effective eraseAt is in the future, the consent is NOT expired!
|
||||
// (Revive from any premature EXPIRED state stamped by legacy code)
|
||||
if (! $isConsentExpired && $phase === 'EXPIRED') {
|
||||
$expiredOn = '';
|
||||
if ($status === 'SUCCESS' && ($state === 'DATA_RECEIVED' || in_array($bestPriority, [480, 490, 500], true))) {
|
||||
$phase = 'COMPLETED';
|
||||
} elseif ($grantedOn !== '' || in_array($rawConsentStatus, ['GRANTED', 'APPROVED', 'ACTIVE'], true) || in_array($state, ['GRANTED', 'APPROVED', 'ACTIVE'], true)) {
|
||||
$phase = 'GRANTED';
|
||||
}
|
||||
}
|
||||
|
||||
if ($isConsentExpired) {
|
||||
|
|
@ -797,6 +919,16 @@ class ConsentSessionListService
|
|||
'valid_from' => $validFrom,
|
||||
'valid_to' => $validTo,
|
||||
'erase_at' => $eraseAt,
|
||||
'requested_from' => $requestedFrom,
|
||||
'requested_to' => $requestedTo,
|
||||
'requested_erase_at' => $requestedEraseAt,
|
||||
'granted_from' => $grantedFrom,
|
||||
'granted_to' => $grantedTo,
|
||||
'granted_erase_at' => $grantedEraseAt,
|
||||
'is_date_from_modified' => $isDateFromModified,
|
||||
'is_date_to_modified' => $isDateToModified,
|
||||
'is_date_range_modified' => ($isDateFromModified || $isDateToModified),
|
||||
'is_erase_at_modified' => $isEraseAtModified,
|
||||
'requested_on' => $requestedOn,
|
||||
'granted_on' => $grantedOn,
|
||||
'revoked_on' => $revokedOn,
|
||||
|
|
|
|||
|
|
@ -144,17 +144,34 @@
|
|||
if (!consent) {
|
||||
return '<div class="text-danger small">No consent details available.</div>';
|
||||
}
|
||||
var metaHtml = '<div class="row small text-muted mb-3">'
|
||||
function renderDateField(label, reqVal, gntVal, isMod, effectiveVal) {
|
||||
if (!reqVal && !gntVal && !effectiveVal) {
|
||||
return '<div class="col-md-6">' + label + ': <strong>-</strong></div>';
|
||||
}
|
||||
if (isMod && reqVal && gntVal) {
|
||||
return '<div class="col-md-6">' + label + ': '
|
||||
+ '<span class="text-decoration-line-through text-muted small" title="Original Requested">' + fmtTs(reqVal) + '</span> '
|
||||
+ '→ <strong class="text-primary" title="Patient Updated in PHR">' + fmtTs(gntVal) + '</strong> '
|
||||
+ '<span class="badge bg-warning-subtle text-warning border border-warning-subtle py-0 px-1 ms-1" style="font-size:0.75rem;" title="Updated by Patient in PHR App">Updated</span>'
|
||||
+ '</div>';
|
||||
}
|
||||
var displayVal = gntVal || effectiveVal || reqVal;
|
||||
return '<div class="col-md-6">' + label + ': <strong>' + fmtTs(displayVal) + '</strong></div>';
|
||||
}
|
||||
|
||||
var metaHtml = '<div class="row small text-muted mb-3 g-2">'
|
||||
+ '<div class="col-md-6">Patient: <strong>' + escHtml(consent.patient_name || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">UHID: <strong>' + escHtml(consent.patient_code || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Consent ID: <strong>' + escHtml(consent.consent_id || consent.abdm_consent_artifact_id || consent.consent_request_id || consent.request_id || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">ABHA: <strong>' + escHtml(consent.abha_address || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Purpose: <strong>' + escHtml(consent.purpose || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Status: <strong>' + escHtml(consent.status || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid From: <strong>' + fmtTs(consent.valid_from) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid To: <strong>' + fmtTs(consent.valid_to) + '</strong></div>'
|
||||
+ renderDateField('Request From', consent.requested_from, consent.granted_from, consent.is_date_from_modified, consent.valid_from)
|
||||
+ renderDateField('Request To', consent.requested_to, consent.granted_to, consent.is_date_to_modified, consent.valid_to)
|
||||
+ '<div class="col-md-6">Requested On: <strong>' + fmtTs(consent.requested_on) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Erase Date: <strong>' + fmtTs(consent.erase_at) + '</strong></div>'
|
||||
+ renderDateField('Consent Valid Upto', consent.requested_erase_at, consent.granted_erase_at, consent.is_erase_at_modified, consent.erase_at)
|
||||
+ (consent.granted_on ? '<div class="col-md-6">Granted On: <strong>' + fmtTs(consent.granted_on) + '</strong></div>' : '')
|
||||
+ (consent.requested_by ? '<div class="col-md-6">Requested By: <strong>' + escHtml(consent.requested_by) + '</strong></div>' : '')
|
||||
+ '</div>';
|
||||
|
||||
var rowsHtml = '';
|
||||
|
|
|
|||
|
|
@ -390,9 +390,9 @@ if ($patientPhotoPath === '') {
|
|||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Status</th>
|
||||
<th>Expiry Date</th>
|
||||
<th>Date From</th>
|
||||
<th>Date To</th>
|
||||
<th>Consent Valid Upto</th>
|
||||
<th>Request From</th>
|
||||
<th>Request To</th>
|
||||
<th>View</th>
|
||||
<th class="text-end">Show Data / Print</th>
|
||||
</tr>
|
||||
|
|
@ -1241,15 +1241,32 @@ $(function() {
|
|||
});
|
||||
stepperHtml += '</div>';
|
||||
|
||||
var metaHtml = '<div class="row small text-muted mb-3">'
|
||||
function renderDateField(label, reqVal, gntVal, isMod, effectiveVal) {
|
||||
if (!reqVal && !gntVal && !effectiveVal) {
|
||||
return '<div class="col-md-6">' + label + ': <strong>-</strong></div>';
|
||||
}
|
||||
if (isMod && reqVal && gntVal) {
|
||||
return '<div class="col-md-6">' + label + ': '
|
||||
+ '<span class="text-decoration-line-through text-muted small" title="Original Requested">' + fmtConsentTs(reqVal) + '</span> '
|
||||
+ '→ <strong class="text-primary" title="Patient Updated in PHR">' + fmtConsentTs(gntVal) + '</strong> '
|
||||
+ '<span class="badge bg-warning-subtle text-warning border border-warning-subtle py-0 px-1 ms-1" style="font-size:0.75rem;" title="Updated by Patient in PHR App">Updated</span>'
|
||||
+ '</div>';
|
||||
}
|
||||
var displayVal = gntVal || effectiveVal || reqVal;
|
||||
return '<div class="col-md-6">' + label + ': <strong>' + fmtConsentTs(displayVal) + '</strong></div>';
|
||||
}
|
||||
|
||||
var metaHtml = '<div class="row small text-muted mb-3 g-2">'
|
||||
+ '<div class="col-md-6">Consent ID: <strong>' + escHtml(consent.consent_id || consent.abdm_consent_artifact_id || consent.consent_request_id || consent.request_id || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">ABHA: <strong>' + escHtml(consent.abha_address || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Purpose: <strong>' + escHtml(consent.purpose || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Status: <strong>' + escHtml(statusUpper || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid From: <strong>' + fmtConsentTs(consent.valid_from) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid To: <strong>' + fmtConsentTs(consent.valid_to) + '</strong></div>'
|
||||
+ renderDateField('Request From', consent.requested_from, consent.granted_from, consent.is_date_from_modified, consent.valid_from)
|
||||
+ renderDateField('Request To', consent.requested_to, consent.granted_to, consent.is_date_to_modified, consent.valid_to)
|
||||
+ '<div class="col-md-6">Requested On: <strong>' + fmtConsentTs(consent.requested_on) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Erase Date: <strong>' + fmtConsentTs(consent.erase_at) + '</strong></div>'
|
||||
+ renderDateField('Consent Valid Upto', consent.requested_erase_at, consent.granted_erase_at, consent.is_erase_at_modified, consent.erase_at)
|
||||
+ (consent.granted_on ? '<div class="col-md-6">Granted On: <strong>' + fmtConsentTs(consent.granted_on) + '</strong></div>' : '')
|
||||
+ (consent.requested_by ? '<div class="col-md-6">Requested By: <strong>' + escHtml(consent.requested_by) + '</strong></div>' : '')
|
||||
+ '</div>';
|
||||
|
||||
var rowsHtml = '';
|
||||
|
|
@ -1328,9 +1345,9 @@ $(function() {
|
|||
html += '<tr>'
|
||||
+ '<td class="small">' + escHtml(rowId) + '</td>'
|
||||
+ '<td><span class="badge ' + consentStatusBadgeClass(status) + '">' + escHtml(status || 'UNKNOWN') + '</span></td>'
|
||||
+ '<td class="small">' + fmtConsentTs(consent.erase_at) + '</td>'
|
||||
+ '<td class="small">' + fmtConsentTs(consent.valid_from) + '</td>'
|
||||
+ '<td class="small">' + fmtConsentTs(consent.valid_to) + '</td>'
|
||||
+ '<td class="small">' + (consent.is_erase_at_modified ? '<span class="text-primary fw-semibold" title="Original Requested: ' + fmtConsentTs(consent.requested_erase_at) + '">' + fmtConsentTs(consent.erase_at) + ' <i class="fa fa-info-circle text-info ms-1" title="Edited by Patient in PHR"></i></span>' : fmtConsentTs(consent.erase_at)) + '</td>'
|
||||
+ '<td class="small">' + (consent.is_date_from_modified ? '<span class="text-primary fw-semibold" title="Original Requested: ' + fmtConsentTs(consent.requested_from) + '">' + fmtConsentTs(consent.valid_from) + ' <i class="fa fa-info-circle text-info ms-1" title="Edited by Patient in PHR"></i></span>' : fmtConsentTs(consent.valid_from)) + '</td>'
|
||||
+ '<td class="small">' + (consent.is_date_to_modified ? '<span class="text-primary fw-semibold" title="Original Requested: ' + fmtConsentTs(consent.requested_to) + '">' + fmtConsentTs(consent.valid_to) + ' <i class="fa fa-info-circle text-info ms-1" title="Edited by Patient in PHR"></i></span>' : fmtConsentTs(consent.valid_to)) + '</td>'
|
||||
+ '<td><button type="button" class="btn btn-link btn-sm p-0 abdm-view-request-btn" data-idx="' + idx + '">View</button></td>'
|
||||
+ '<td class="text-end">'
|
||||
+ (canFetch ? actionHtml : '<span class="text-muted small">-</span>')
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user