diff --git a/src/registrar/abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component.html b/src/registrar/abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component.html new file mode 100644 index 0000000..94588dd --- /dev/null +++ b/src/registrar/abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component.html @@ -0,0 +1,35 @@ +

{{ title }}

+ +

{{ message }}

+
+ + Name (as on Aadhaar) + + Name is required + + + + Gender + + Male + Female + Other + + + + + Year of Birth + + Invalid year + +
+
+ + + + + diff --git a/src/registrar/abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component.ts b/src/registrar/abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component.ts new file mode 100644 index 0000000..eb71b4d --- /dev/null +++ b/src/registrar/abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component.ts @@ -0,0 +1,71 @@ +/* + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import { Component, Inject } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; + +@Component({ + selector: "app-aadhaar-correction-dialog", + templateUrl: "aadhaar-correction-dialog.component.html", +}) +export class AadhaarCorrectionDialogComponent { + form: FormGroup; + title: string; + message: string; + + constructor( + private fb: FormBuilder, + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) { + this.title = data?.title ?? "Aadhaar correction"; + this.message = data?.message ?? "Please correct Aadhaar provided details"; + const formData = data?.formData || {}; + this.form = this.fb.group({ + name: [ + formData.name || "", + [Validators.required, Validators.minLength(1)], + ], + gender: [formData.gender || "", Validators.required], + yearOfBirth: [ + formData.yearOfBirth || new Date().getFullYear(), + [ + Validators.required, + Validators.min(1900), + Validators.max(new Date().getFullYear()), + ], + ], + }); + } + + submit() { + if (this.form.valid) { + this.dialogRef.close(this.form.value); + } else { + this.form.markAllAsTouched(); + } + } + + cancel() { + this.dialogRef.close(); + } +} diff --git a/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.css b/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.css index ef28ff0..3c2578d 100644 --- a/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.css +++ b/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.css @@ -56,4 +56,140 @@ md2-pagination { .overlay:root { padding: 0px; } - \ No newline at end of file + + /* Aadhaar Verification Panel - Material You pastel style */ + + .aadhaar-verify-panel { + background: linear-gradient(135deg, #f3f8ff, #f9fbff); + border: 1px solid #dbe7ff; + border-radius: 12px; + padding: 20px; + margin-top: 20px; + box-shadow: 0 1px 4px rgba(25, 118, 210, 0.08); + transition: box-shadow 0.25s ease, background 0.25s ease; + + &:hover { + background: #ffffff; + box-shadow: 0 3px 10px rgba(25, 118, 210, 0.1); + } + + .aadhaar-header { + display: flex; + justify-content: space-between; + align-items: center; + + .aadhaar-heading { + strong { + font-weight: 600; + color: #1a237e; + font-size: 15px; + } + + .aadhaar-subtext { + font-size: 12.5px; + color: #5f6368; + margin-top: 2px; + } + } + + .aadhaar-reset-btn { + color: #1976d2; + transition: transform 0.15s ease; + + &:hover { + transform: rotate(90deg); + } + } + } + + .aadhaar-verify-form { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-top: 14px; + + .aadhaar-field { + flex: 1 1 240px; + min-width: 180px; + + &.small { + width: 140px; + } + + ::ng-deep .mat-form-field-flex { + background: #ffffff; + border-radius: 6px; + } + } + } + + .aadhaar-actions { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + margin-top: 16px; + + .aadhaar-apply-btn { + background-color: #1976d2; + color: white; + font-weight: 500; + letter-spacing: 0.3px; + border-radius: 8px; + min-width: 190px; + + &:hover { + background-color: #1565c0; + } + + &:disabled { + background-color: #bbdefb; + color: white; + } + } + + .aadhaar-reset-text-btn { + border-color: #1976d2; + color: #1976d2; + + &:hover { + background: rgba(25, 118, 210, 0.05); + } + } + + .aadhaar-note { + margin-left: 10px; + color: #5f6368; + font-size: 12.5px; + flex: 1 1 100%; + } + } + + @media (max-width: 768px) { + padding: 12px; + + .aadhaar-verify-form { + flex-direction: column; + } + + .aadhaar-actions { + flex-direction: column; + align-items: stretch; + + .aadhaar-apply-btn, + .aadhaar-reset-text-btn { + width: 100%; + } + + .aadhaar-note { + text-align: center; + margin-top: 8px; + } + } + } + } + + /* Optional: subtle transition for mat-form-field focus */ + ::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex { + transition: box-shadow 0.15s ease; + } \ No newline at end of file diff --git a/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.html b/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.html index e5d118d..d0a1bb2 100644 --- a/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.html +++ b/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.html @@ -39,11 +39,7 @@

searchDetails.data.length > 0 " > - +
@@ -107,11 +103,7 @@

class="table-responsive vw100" *ngIf="!healthIDMapping && !enablehealthIdOTPForm && !searchPopup" > - +
@@ -197,17 +189,13 @@

class="table-responsive vw100" *ngIf="healthIDMapping && !enablehealthIdOTPForm && !searchPopup" > - - - - - +
- {{ currentLanguageSet?.coreComponents?.sno }} - 1
+ + + +
+ {{ currentLanguageSet?.coreComponents?.sno }} + 1
+ +
+
+
+ {{ + currentLanguageSet?.pleaseVerifyAadhaarHeading || + "Please verify Aadhaar details" + }} +
+ {{ + currentLanguageSet?.pleaseVerifyAadhaarSub || + "Review your Aadhaar-linked details before generating the + token." + }} +
+
+ +
+ +
+ + {{ + currentLanguageSet?.name || "Name (as on Aadhaar)" + }} + + + + + {{ + currentLanguageSet?.gender || "Gender" + }} + + {{ + currentLanguageSet?.male || "Male" + }} + {{ + currentLanguageSet?.female || "Female" + }} + {{ + currentLanguageSet?.other || "Other" + }} + + + + + {{ + currentLanguageSet?.yearOfBirth || "Year of birth" + }} + + +
+ {{ + currentLanguageSet?.aadhaarVerifyNote || + "If the server still reports mismatch, update again or follow + the on-screen suggestions." + }} +
+
+
+
@@ -348,14 +417,13 @@

id="cancelButton" mat-raised-button type="button" - style="margin-right: 3px;" + style="margin-right: 3px" color="primary" class="pull-right mat_blue" (click)="resendOtp()" > {{ currentLanguageSet?.resendOTP }} - diff --git a/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.ts b/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.ts index 9afb45e..595c393 100644 --- a/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.ts +++ b/src/registrar/abha-components/health-id-display-modal/health-id-display-modal.component.ts @@ -19,42 +19,47 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { Component, DoCheck, Inject, OnInit } from '@angular/core'; -import { FormGroup, FormBuilder } from '@angular/forms'; -import { DatePipe } from '@angular/common'; +import { Component, DoCheck, Inject, OnInit } from "@angular/core"; +import { FormGroup, FormBuilder } from "@angular/forms"; +import { DatePipe } from "@angular/common"; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef, -} from '@angular/material/dialog'; -import { MatTableDataSource } from '@angular/material/table'; -import { SetLanguageComponent } from 'src/app/app-modules/core/components/set-language.component'; -import { ConfirmationService } from 'src/app/app-modules/core/services'; -import { HttpServiceService } from 'src/app/app-modules/core/services/http-service.service'; -import { RegistrarService } from '../../services/registrar.service'; +} from "@angular/material/dialog"; +import { MatTableDataSource } from "@angular/material/table"; +import { SetLanguageComponent } from "src/app/app-modules/core/components/set-language.component"; +import { + BeneficiaryDetailsService, + ConfirmationService, +} from "src/app/app-modules/core/services"; +import { HttpServiceService } from "src/app/app-modules/core/services/http-service.service"; +import { RegistrarService } from "../../services/registrar.service"; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, -} from '@angular/material/core'; +} from "@angular/material/core"; import { MomentDateAdapter, MAT_MOMENT_DATE_ADAPTER_OPTIONS, -} from '@angular/material-moment-adapter'; -import { SessionStorageService } from '../../services/session-storage.service'; -import { DownloadSearchAbhaComponent } from '../download-search-abha/download-search-abha.component'; +} from "@angular/material-moment-adapter"; +import { SessionStorageService } from "../../services/session-storage.service"; +import { DownloadSearchAbhaComponent } from "../download-search-abha/download-search-abha.component"; +import { interval, startWith, Subscription } from "rxjs"; +import { AadhaarCorrectionDialogComponent } from "../aadhar-correction-dialog/aadhaar-correction-dialog.component"; @Component({ - selector: 'app-health-id-display-modal', - templateUrl: './health-id-display-modal.component.html', - styleUrls: ['./health-id-display-modal.component.css'], + selector: "app-health-id-display-modal", + templateUrl: "./health-id-display-modal.component.html", + styleUrls: ["./health-id-display-modal.component.css"], providers: [ { provide: DatePipe, }, { provide: MAT_DATE_LOCALE, - useValue: 'en-US', // Set the desired locale (e.g., 'en-GB' for dd/MM/yyyy) + useValue: "en-US", // Set the desired locale (e.g., 'en-GB' for dd/MM/yyyy) }, { provide: DateAdapter, @@ -65,13 +70,13 @@ import { DownloadSearchAbhaComponent } from '../download-search-abha/download-se provide: MAT_DATE_FORMATS, useValue: { parse: { - dateInput: 'LL', + dateInput: "LL", }, display: { - dateInput: 'DD/MM/YYYY', // Set the desired display format - monthYearLabel: 'MMM YYYY', - dateA11yLabel: 'LL', - monthYearA11yLabel: 'MMMM YYYY', + dateInput: "DD/MM/YYYY", // Set the desired display format + monthYearLabel: "MMM YYYY", + dateA11yLabel: "LL", + monthYearA11yLabel: "MMMM YYYY", }, }, }, @@ -91,32 +96,45 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { searchPopup = false; displayedColumns: any = [ - 'sno', - 'abhaNumber', - 'abha', - 'dateOfCreation', - 'abhaMode', + "sno", + "abhaNumber", + "abha", + "dateOfCreation", + "abhaMode", ]; searchDetails = new MatTableDataSource(); displayedColumns1: any = [ - 'sno', - 'abhaNumber', - 'abha', - 'dateOfCreation', - 'abhaMode', - 'action', + "sno", + "abhaNumber", + "abha", + "dateOfCreation", + "abhaMode", + "action", ]; displayedColumns2: any = [ - 'sno', - 'healthIDNo', - 'healthID', - 'createdDate', - 'healthIDMode', - 'rblMode', + "sno", + "healthIDNo", + "healthID", + "createdDate", + "healthIDMode", + "rblMode", ]; healthIDArray = new MatTableDataSource(); + linkToken: any; + beneficiaryDetails: any; + public aadhaarVerification: { + name: string; + gender: string; + yearOfBirth: number | null; + } = { + name: "", + gender: "", + yearOfBirth: null, + }; + public aadhaarVerified = false; + public currentYear = new Date().getFullYear(); constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public input: any, @@ -126,11 +144,28 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { private confirmationService: ConfirmationService, private datePipe: DatePipe, private dialogMd: MatDialog, - private sessionstorage:SessionStorageService, + private sessionstorage: SessionStorageService, + private beneficiaryDetailsService: BeneficiaryDetailsService, ) { dialogRef.disableClose = true; } + private closeConfirmationAlertIfOpen() { + try { + if (this.lastAlertRef) { + this.lastAlertRef.close(); + this.lastAlertRef = null; + } + } catch (e) { + console.warn("Failed to close confirmation alert", e); + } + } + + // Keep a reference to the last confirmationService alert dialog so we can close + // it when another dialog (MatDialog) is opened on top. + private lastAlertRef: MatDialogRef | null = null; + + ngOnInit() { console.log("this.input", this.input); this.searchDetails.data = []; @@ -141,23 +176,21 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { this.input.search !== undefined ? this.input.search : false; this.healthIDMapping = this.input.healthIDMapping; console.log("this.healthIDMapping", this.healthIDMapping); - if ( - this.input.dataList !== undefined && - this.input.search === true - ) { + this.getBeneficiaryDetails(); + if (this.input.dataList !== undefined && this.input.search === true) { let tempVal: any = this.input.dataList; this.benDetails = this.input.dataList; let tempCreatDate: any = this.input.dataList.createdDate; console.log("tempVal", tempVal); - this.searchDetails.data.push(tempVal); - console.log("this.searchDetails.data%%", this.searchDetails.data) - + this.searchDetails.data.push(tempVal); + console.log("this.searchDetails.data%%", this.searchDetails.data); } - if (this.input.dataList !== undefined && + if ( + this.input.dataList !== undefined && this.input.dataList.data?.BenHealthDetails !== undefined - ){ + ) { this.benDetails = this.input.dataList.data.BenHealthDetails; - console.log("this.benDetails1",this.benDetails) + console.log("this.benDetails1", this.benDetails); } this.healthIdOTPForm = this.createOtpGenerationForm(); this.createList(); @@ -180,7 +213,7 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { this.benDetails.forEach((healthID: any) => { healthID.createdDate = this.datePipe.transform( healthID.createdDate, - 'yyyy-MM-dd hh:mm:ss a', + "yyyy-MM-dd hh:mm:ss a", ); this.healthIDArray.data.push(healthID); }); @@ -188,8 +221,611 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { } onRadioChange(data: any) { + console.clear(); + console.log(this.selectedHealthID); this.selectedHealthID = data; + console.log(this.selectedHealthID); + } + + beneficiaryDetailSubscription: any; + getBeneficiaryDetails() { + this.beneficiaryDetailSubscription = + this.beneficiaryDetailsService.beneficiaryDetails$.subscribe( + (beneficiary) => { + if (beneficiary) { + if (beneficiary) { + this.beneficiaryDetails = beneficiary; + this.initAadhaarVerificationFromBeneficiary(); + } + } + }, + ); + } + + private initAadhaarVerificationFromBeneficiary() { + try { + const name = this.beneficiaryDetails?.beneficiaryName ?? ""; + const gender = this.beneficiaryDetails?.genderName ?? ""; + const dob = this.beneficiaryDetails?.dOB ?? null; + + let yob: number | null = null; + if (dob) { + const parsed = new Date(dob); + if (!isNaN(parsed.getTime())) { + yob = parsed.getFullYear(); + } else { + const maybe = parseInt(String(dob), 10); + if (!isNaN(maybe)) yob = maybe; + } + } + + this.aadhaarVerification = { + name, + gender, + yearOfBirth: yob ?? this.currentYear - 30, + }; + this.aadhaarVerified = false; + } catch (e) { + console.warn("initAadhaarVerificationFromBeneficiary failed", e); + } + } + + public onResetAadhaarVerification() { + this.initAadhaarVerificationFromBeneficiary(); + } + + // Apply the verified values into beneficiaryDetails and call generateTokenForLinking() + public applyAndGenerate() { + if ( + !this.aadhaarVerification.name || + !this.aadhaarVerification.gender || + !this.aadhaarVerification.yearOfBirth + ) { + this.confirmationService.alert( + this.currentLanguageSet?.pleaseFillAllFields || + "Please fill all fields", + "error", + ); + return; + } + + // Copy into beneficiaryDetails so existing generateTokenForLinking reads them + this.beneficiaryDetails.beneficiaryName = this.aadhaarVerification.name; + this.beneficiaryDetails.genderName = this.aadhaarVerification.gender; + this.beneficiaryDetails.dOB = `${this.aadhaarVerification.yearOfBirth}-01-01T00:00:00.000Z`; + + this.aadhaarVerified = true; + this.suppressAutoGenerate = true; + if (this.linkPollingSub) { + this.linkPollingSub.unsubscribe(); + this.linkPollingSub = null; + } + // Trigger existing flow + this.generateTokenForLinking(); + } + + private linkPollingSub: Subscription | null = null; + private isPollingLink = false; + private generateResubmitAttempts = 0; + private readonly maxGenerateResubmitAttempts = 3; + // prevents concurrent generate calls + public isGenerating = false; + + // small guard to suppress any automatic re-trigger events while manual resubmit running + private suppressAutoGenerate = false; + + generateTokenForLinking() { + if (this.isGenerating) { + console.debug( + "[link] generateTokenForLinking suppressed because already generating", + ); + return; + } + this.isGenerating = true; + this.showProgressBar = true; + + const abdmFacilityId = this.sessionstorage.getItem("abdmFacilityId"); + const abdmFacilityName = this.sessionstorage.getItem("abdmFacilityName"); + const dateString = this.beneficiaryDetails.dOB; + const yearOfBirth = new Date(dateString).getFullYear(); + + const reqObj = { + abhaNumber: this.selectedHealthID.healthIdNumber + ? this.selectedHealthID.healthIdNumber + : null, + abhaAddress: this.selectedHealthID.healthId + ? this.selectedHealthID.healthId + : null, + name: this.beneficiaryDetails.beneficiaryName, + gender: this.beneficiaryDetails.genderName, + yearOfBirth, + visitCode: this.input.visitCode, + visitCategory: this.beneficiaryDetails.VisitCategory, + abdmFacilityId: + abdmFacilityId !== null && + abdmFacilityId !== undefined && + abdmFacilityId !== "" + ? abdmFacilityId + : null, + abdmFacilityName: + abdmFacilityName !== null && + abdmFacilityName !== undefined && + abdmFacilityName !== "" + ? abdmFacilityName + : null, + }; + + this.registrarService.generateTokenForCareContext(reqObj).subscribe( + (res: any) => { + this.showProgressBar = false; + const data = res?.data ?? null; + + if (data?.linkToken) { + this.linkToken = data.linkToken; + this.startLinkCareContextPolling(); + return; + } + + if (data?.requestId && !data?.linkToken) { + const requestId = data.requestId; + this.resumeViaSecondApi(requestId, reqObj); + return; + } + + const dataError = data?.Error ?? data?.error ?? null; + if (dataError) { + const serverMsg = + dataError?.Message ?? + dataError?.message ?? + "Details do not match Aadhaar"; + this.handleAadhaarMismatchDialog( + { + name: reqObj.name, + gender: reqObj.gender, + yearOfBirth: reqObj.yearOfBirth, + }, + serverMsg, + ); + return; + } + + const fallbackMsg = + res?.errorMessage ?? res?.status ?? "Failed to generate link token"; + + if ( + String(fallbackMsg).toLowerCase().includes("duplicate") || + String(fallbackMsg).toLowerCase().includes("abdm-1092") + ) { + this.confirmationService.alert(fallbackMsg, "error"); + return; + } + + this.confirmationService.alert(fallbackMsg, "error"); + }, + (err) => { + this.showProgressBar = false; + const payloadError = err?.error ?? null; + const errMsg = + payloadError?.Message ?? + payloadError?.message ?? + err?.error?.errorMessage ?? + err?.message ?? + ""; + + if ( + String(errMsg).toLowerCase().includes("duplicate") || + String(errMsg).toLowerCase().includes("abdm-1092") + ) { + this.confirmationService.alert(errMsg, "error"); + return; + } + + if (payloadError && (payloadError?.Code || payloadError?.Message)) { + const serverMsg = + payloadError?.Message ?? + payloadError?.message ?? + "Details do not match Aadhaar"; + this.handleAadhaarMismatchDialog( + { + name: this.beneficiaryDetails.beneficiaryName, + gender: this.beneficiaryDetails.genderName, + yearOfBirth: new Date(this.beneficiaryDetails.dOB).getFullYear(), + }, + serverMsg, + ); + return; + } + + // generic + this.confirmationService.alert( + errMsg || "Something went wrong", + "error", + ); + }, + ); } + + private resumeViaSecondApi(requestId: string, originalReqObj: any) { + const reqObj = { + requestId, + abdmFacilityId: this.sessionstorage.getItem("abdmFacilityId"), + abdmFacilityName: this.sessionstorage.getItem("abdmFacilityName"), + abhaNumber: this.selectedHealthID?.healthIdNumber ?? null, + abhaAddress: this.selectedHealthID?.healthId ?? null, + visitCode: this.input.visitCode, + visitCategory: this.beneficiaryDetails.VisitCategory, + visitReason: this.beneficiaryDetails.VisitReason, + beneficiaryId: this.beneficiaryDetails.beneficiaryID, + }; + + this.pollLinkCareContext(reqObj, { resumeRequestId: requestId }); + } + + private pollLinkCareContext( + reqObj: any, + options?: { isStartup?: boolean; resumeRequestId?: string }, + ) { + const maxAttempts = 3; + const delayMs = 5000; + + if (options?.isStartup) { + if (this.isPollingLink) return; + if (!this.linkToken) { + this.confirmationService.alert( + "No link token available to start linking", + "error", + ); + return; + } + this.isPollingLink = true; + } + + this.isGenerating = false; + this.showProgressBar = true; + + if (this.linkPollingSub) { + this.linkPollingSub.unsubscribe(); + this.linkPollingSub = null; + } + + let attempts = 0; + this.linkPollingSub = interval(delayMs) + .pipe(startWith(0)) + .subscribe(() => { + attempts++; + this.registrarService.linkCareContext(reqObj).subscribe( + (res: any) => { + const data = res?.data ?? null; + const isEmpty = data && Object.keys(data).length === 0; + const respError = data?.Error ?? data?.error ?? null; + + if ( + data?.message && + String(data.message) + .toLowerCase() + .includes("care context added successfully") + ) { + this.stopLinkCareContextPolling(); + this.showProgressBar = false; + this.confirmationService.alert( + data?.message || + this.currentLanguageSet.linkSuccess || + "Care context linked successfully", + "success", + ); + this.linkToken = null; + this.closeDialog(); + return; + } + if (respError) { + const serverMsg = + respError?.Message ?? respError?.message ?? "Linking failed"; + const code = String(respError?.Code ?? "").toLowerCase(); + + if ( + code.includes("1207") || + /aadhar|aadhaar|does not match/i.test(serverMsg) + ) { + this.stopLinkCareContextPolling(); + this.showProgressBar = false; + this.handleAadhaarMismatchDialog( + { + name: this.beneficiaryDetails.beneficiaryName, + gender: this.beneficiaryDetails.genderName, + yearOfBirth: new Date( + this.beneficiaryDetails.dOB, + ).getFullYear(), + }, + serverMsg, + ); + return; + } + + this.stopLinkCareContextPolling(); + this.showProgressBar = false; + this.confirmationService.alert(serverMsg, "error"); + return; + } + + if (isEmpty) { + console.debug( + `[link] pollLinkCareContext: empty response attempt ${attempts}/${maxAttempts}`, + ); + if (attempts >= maxAttempts) { + this.stopLinkCareContextPolling(); + this.showProgressBar = false; + const infoMsg = options?.resumeRequestId + ? `Still processing. Please try again later with RequestId: ${options.resumeRequestId}` + : "Linking is still processing. Please try again later."; + this.confirmationService.alert(infoMsg, "info"); + } + return; + } + + // fallback after all retries + if (attempts >= maxAttempts) { + this.stopLinkCareContextPolling(); + this.showProgressBar = false; + this.confirmationService.alert( + res?.errorMessage ?? "Linking care context failed after maximum retries", + "error", + ); + } + }, + (err) => { + if (attempts >= maxAttempts) { + const msg = + err?.error?.message ?? err?.message ?? "Linking failed after retries"; + this.stopLinkCareContextPolling(); + this.showProgressBar = false; + this.confirmationService.alert(msg, "error"); + } else { + console.warn("[link] pollLinkCareContext retrying after error", err); + } + }, + ); + }); + } + + private startLinkCareContextPolling() { + const makeReqObj = () => ({ + linkToken: this.linkToken, + abhaNumber: this.selectedHealthID?.healthIdNumber ?? null, + abhaAddress: this.selectedHealthID?.healthId ?? null, + abdmFacilityId: this.sessionstorage.getItem("abdmFacilityId"), + abdmFacilityName: this.sessionstorage.getItem("abdmFacilityName"), + visitCode: this.input.visitCode, + visitCategory: this.beneficiaryDetails.VisitCategory, + visitReason: this.beneficiaryDetails.VisitReason, + beneficiaryId: this.beneficiaryDetails.beneficiaryID, + }); + + this.pollLinkCareContext(makeReqObj(), { isStartup: true }); + } + + private stopLinkCareContextPolling() { + if (this.linkPollingSub) { + this.linkPollingSub.unsubscribe(); + this.linkPollingSub = null; + } + this.isPollingLink = false; + this.isGenerating = false; + } + + private isAadhaarMismatch(errorCandidate: any): boolean { + if (!errorCandidate) return false; + + if (typeof errorCandidate === "object") { + const code = String( + errorCandidate.Code ?? errorCandidate.code ?? "", + ).toLowerCase(); + const msg = String( + errorCandidate.Message ?? + errorCandidate.message ?? + errorCandidate.errorMessage ?? + "", + ).toLowerCase(); + + const textMatch = + msg.includes("aadhar") || + msg.includes("aadhaar") || + msg.includes("does not match") || + msg.includes("doesn't match") || + msg.includes("not match") || + msg.includes("match the details"); + + const code1207 = /\b1207\b/.test(code) || /abdm-?1207/.test(code); + + return ( + textMatch || + (code1207 && + (msg.includes("match") || + msg.includes("aadhaar") || + msg.includes("aadhar"))) + ); + } + + const s = String(errorCandidate).toLowerCase(); + return ( + s.includes("aadhar") || + s.includes("aadhaar") || + s.includes("does not match") || + s.includes("doesn't match") || + s.includes("not match") || + s.includes("match the details") + ); + } + + private handleAadhaarMismatchDialog( + prefill: { name: string; gender: string; yearOfBirth: number }, + serverMsg: string, + ) { + if (this.generateResubmitAttempts >= this.maxGenerateResubmitAttempts) { + this.confirmationService.alert( + "Maximum correction attempts reached. " + (serverMsg || ""), + "error", + ); + return; + } + + // Close any existing confirmation alert before opening the Aadhaar correction dialog + this.closeConfirmationAlertIfOpen(); + + const dialogRef = this.dialogMd.open(AadhaarCorrectionDialogComponent, { + width: "420px", + data: { + title: "Aadhaar details mismatch", + message: + serverMsg || + "The entered details do not match Aadhaar. Please provide Aadhaar-provided details.", + formData: prefill, + }, + }); + + dialogRef + .afterClosed() + .subscribe( + ( + result: + | { name?: string; gender?: string; yearOfBirth?: number } + | undefined, + ) => { + if (!result) { + this.confirmationService.alert( + "Linking cancelled by user.", + "info", + ); + return; + } + + this.generateResubmitAttempts++; + this.resubmitGenerateTokenWithCorrectedDetails(result); + }, + ); + } + + private resubmitGenerateTokenWithCorrectedDetails(corrected: { + name?: string; + gender?: string; + yearOfBirth?: number; + }) { + this.showProgressBar = true; + const abdmFacilityId = this.sessionstorage.getItem("abdmFacilityId"); + const abdmFacilityName = this.sessionstorage.getItem("abdmFacilityName"); + + const reqObj = { + abhaNumber: this.selectedHealthID?.healthIdNumber ?? null, + abhaAddress: this.selectedHealthID?.healthId ?? null, + name: corrected.name ?? this.beneficiaryDetails.beneficiaryName, + gender: corrected.gender ?? this.beneficiaryDetails.genderName, + yearOfBirth: + corrected.yearOfBirth ?? + new Date(this.beneficiaryDetails.dOB).getFullYear(), + visitCode: this.input.visitCode, + visitCategory: this.beneficiaryDetails.VisitCategory, + abdmFacilityId: + abdmFacilityId !== null && + abdmFacilityId !== undefined && + abdmFacilityId !== "" + ? abdmFacilityId + : null, + abdmFacilityName: + abdmFacilityName !== null && + abdmFacilityName !== undefined && + abdmFacilityName !== "" + ? abdmFacilityName + : null, + }; + + this.registrarService.generateTokenForCareContext(reqObj).subscribe( + (res: any) => { + this.showProgressBar = false; + const data = res?.data ?? null; + const dataError = data?.Error ?? data?.error ?? null; + + // ⚠️ CASE 1: Aadhaar mismatch response + if (dataError) { + const serverMsg = + dataError?.Message ?? + dataError?.message ?? + "Details do not match Aadhaar"; + if ( + this.generateResubmitAttempts < this.maxGenerateResubmitAttempts + ) { + this.handleAadhaarMismatchDialog( + { + name: reqObj.name, + gender: reqObj.gender, + yearOfBirth: reqObj.yearOfBirth, + }, + serverMsg, + ); + } else { + this.confirmationService.alert(serverMsg, "error"); + } + return; + } + + // CASE 2: Successful token — start polling + if (data?.linkToken) { + this.linkToken = data.linkToken; + this.generateResubmitAttempts = 0; + this.startLinkCareContextPolling(); + return; + } + + // CASE 3: Success (200) but only requestId, no linkToken + if (data?.requestId && !data?.linkToken) { + this.resumeViaSecondApi(data.requestId, reqObj); + return; + } + + // CASE 4: Duplicate or other backend signals + const fallbackMsg = + res?.errorMessage ?? res?.status ?? "Failed to generate link token"; + + if ( + String(fallbackMsg).toLowerCase().includes("duplicate") || + String(fallbackMsg).toLowerCase().includes("abdm-1092") + ) { + // Flow 4 fallback: show error to user (no resume via duplicate) + this.confirmationService.alert(fallbackMsg, "error"); + return; + } + + // CASE 5: Unknown/invalid + this.confirmationService.alert(fallbackMsg, "error"); + }, + (err) => { + this.showProgressBar = false; + const errMsg = + err?.error?.errorMessage ?? err?.message ?? "Something went wrong"; + + if ( + this.isAadhaarMismatch(errMsg) && + this.generateResubmitAttempts < this.maxGenerateResubmitAttempts + ) { + this.handleAadhaarMismatchDialog( + { + name: reqObj.name, + gender: reqObj.gender, + yearOfBirth: reqObj.yearOfBirth, + }, + errMsg, + ); + } else if ( + String(errMsg).toLowerCase().includes("duplicate") || + String(errMsg).toLowerCase().includes("abdm-1092") + ) { + // Flow 4: fallback to showing error instead of resuming + this.confirmationService.alert(errMsg, "error"); + } else { + this.confirmationService.alert(errMsg, "error"); + } + }, + ); + } + generateOtpForMapping() { this.showProgressBar = true; const abdmFacilityId = this.sessionstorage.getItem("abdmFacilityId"); @@ -202,8 +838,18 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { ? this.selectedHealthID.healthIdNumber : null, authenticationMode: this.selectedHealthID.authenticationMode, - abdmFacilityId: (abdmFacilityId !== null && abdmFacilityId !== undefined && abdmFacilityId !== "") ? abdmFacilityId : null, - abdmFacilityName: (abdmFacilityName !== null && abdmFacilityName !== undefined && abdmFacilityName !== "") ? abdmFacilityName : null + abdmFacilityId: + abdmFacilityId !== null && + abdmFacilityId !== undefined && + abdmFacilityId !== "" + ? abdmFacilityId + : null, + abdmFacilityName: + abdmFacilityName !== null && + abdmFacilityName !== undefined && + abdmFacilityName !== "" + ? abdmFacilityName + : null, }; this.registrarService.generateOtpForMappingCareContext(reqObj).subscribe( (receivedOtpResponse: any) => { @@ -211,14 +857,14 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { this.showProgressBar = false; this.confirmationService.alert( this.currentLanguageSet.OTPSentToRegMobNo, - 'success', + "success", ); this.transactionId = receivedOtpResponse.data.txnId; this.enablehealthIdOTPForm = true; } else { this.confirmationService.alert( receivedOtpResponse.errorMessage, - 'error', + "error", ); this.enablehealthIdOTPForm = false; this.showProgressBar = false; @@ -226,7 +872,7 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { }, (err) => { this.showProgressBar = false; - this.confirmationService.alert(err.errorMessage, 'error'); + this.confirmationService.alert(err.errorMessage, "error"); this.enablehealthIdOTPForm = false; }, ); @@ -239,9 +885,9 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { return true; } checkOTP() { - const otp = this.healthIdOTPForm.controls['otp'].value; + const otp = this.healthIdOTPForm.controls["otp"].value; let cflag = false; - if (otp !== '' && otp !== undefined && otp !== null) { + if (otp !== "" && otp !== undefined && otp !== null) { const hid = otp; if (hid.length >= 4 && hid.length <= 32) { for (let i = 0; i < hid.length; i++) { @@ -267,7 +913,7 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { const abdmFacilityId = this.sessionstorage.getItem("abdmFacilityId"); const abdmFacilityName = this.sessionstorage.getItem("abdmFacilityName"); const verifyOtpData = { - otp: this.healthIdOTPForm.controls['otp'].value, + otp: this.healthIdOTPForm.controls["otp"].value, txnId: this.transactionId, beneficiaryID: this.selectedHealthID.beneficiaryRegID, healthID: this.selectedHealthID.healthId @@ -278,11 +924,21 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { : null, visitCode: this.input.visitCode, visitCategory: - this.sessionstorage.getItem('visitCategory') === 'General OPD (QC)' - ? 'Emergency' - : this.sessionstorage.getItem('visitCategory'), - abdmFacilityId: (abdmFacilityId !== null && abdmFacilityId !== undefined && abdmFacilityId !== "") ? abdmFacilityId : null, - abdmFacilityName: (abdmFacilityName !== null && abdmFacilityName !== undefined && abdmFacilityName !== "") ? abdmFacilityName : null + this.sessionstorage.getItem("visitCategory") === "General OPD (QC)" + ? "Emergency" + : this.sessionstorage.getItem("visitCategory"), + abdmFacilityId: + abdmFacilityId !== null && + abdmFacilityId !== undefined && + abdmFacilityId !== "" + ? abdmFacilityId + : null, + abdmFacilityName: + abdmFacilityName !== null && + abdmFacilityName !== undefined && + abdmFacilityName !== "" + ? abdmFacilityName + : null, }; this.registrarService .verifyOtpForMappingCarecontext(verifyOtpData) @@ -292,26 +948,26 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { this.showProgressBar = false; this.confirmationService.alert( verifiedMappingData.data.response, - 'success', + "success", ); this.closeDialog(); } else { this.showProgressBar = false; this.confirmationService.alert( verifiedMappingData.errorMessage, - 'error', + "error", ); } }, (err) => { this.showProgressBar = false; - this.confirmationService.alert(err.errorMessage, 'error'); + this.confirmationService.alert(err.errorMessage, "error"); }, ); } resendOtp() { - this.healthIdOTPForm.controls['otp'].reset; - this.healthIdOTPForm.controls['otp'].patchValue(null); + this.healthIdOTPForm.controls["otp"].reset; + this.healthIdOTPForm.controls["otp"].patchValue(null); this.generateOtpForMapping(); } closeDialog() { @@ -319,14 +975,17 @@ export class HealthIdDisplayModalComponent implements OnInit, DoCheck { } printHealthIDCard(data: any) { + // Close any lingering confirmation alert before opening the print dialog + this.closeConfirmationAlertIfOpen(); + const dialogRefValue = this.dialogMd.open(DownloadSearchAbhaComponent, { - height: '330px', - width: '500px', - disableClose: true, + height: "330px", + width: "500px", + disableClose: true, data: { printCard: true, - healthId: data.healthId - } + healthId: data.healthId, + }, }); } } diff --git a/src/registrar/registration.module.ts b/src/registrar/registration.module.ts index 9524417..9063759 100644 --- a/src/registrar/registration.module.ts +++ b/src/registrar/registration.module.ts @@ -38,6 +38,7 @@ import { DisplayAbhaCardComponent } from './abha-components/display-abha-card/di import { AbhaVerifySuccessComponentComponent } from './abha-components/abha-verify-success-component/abha-verify-success-component.component'; import { AbhaEnterMobileOtpComponentComponent } from './abha-components/abha-enter-mobile-otp-component/abha-enter-mobile-otp-component.component'; import { AbhaConsentFormComponent } from './abha-components/abha-consent-form/abha-consent-form.component'; +import { AadhaarCorrectionDialogComponent } from './abha-components/aadhar-correction-dialog/aadhaar-correction-dialog.component'; @NgModule({ declarations: [ @@ -66,6 +67,7 @@ import { AbhaConsentFormComponent } from './abha-components/abha-consent-form/ab AbhaVerifySuccessComponentComponent, AbhaEnterMobileOtpComponentComponent, AbhaConsentFormComponent, + AadhaarCorrectionDialogComponent, ], imports: [ CommonModule, diff --git a/src/registrar/registration/abha-information/abha-information.component.ts b/src/registrar/registration/abha-information/abha-information.component.ts index 53af336..cb7bb61 100644 --- a/src/registrar/registration/abha-information/abha-information.component.ts +++ b/src/registrar/registration/abha-information/abha-information.component.ts @@ -49,10 +49,12 @@ export class AbhaInformationComponent { (response: any) => { console.log('responseABHACompoenet', response); if (response) { + this.abhaInfoFormGroup.patchValue({ healthId: response.healthId }); this.abhaInfoFormGroup.patchValue({ healthIdNumber: response.healthIdNumber, }); this.abhaInfoFormGroup.controls['healthIdNumber'].disable(); + console.log('abha info group', this.abhaInfoFormGroup.value); } }, ); @@ -61,6 +63,10 @@ export class AbhaInformationComponent { ngOnInit() { console.log("INSIDE ABHA COMPOENENT") this.fetchLanguageResponse(); + // Ensure `healthId` control exists on the passed-in form group + if (this.abhaInfoFormGroup && !this.abhaInfoFormGroup.get('healthId')) { + this.abhaInfoFormGroup.addControl('healthId', new FormControl('', [Validators.required])); + } this.formData.forEach((item: any) => { if (item.fieldName && item.allowText) { this.abhaInfoFormGroup.addControl( @@ -85,7 +91,8 @@ export class AbhaInformationComponent { }); console.log("formDataABHA", this.formData); if (this.patientRevisit) { - this.abhaInfoFormGroup.controls['healthIdNumber'].patchValue(this.revisitData?.abhaDetails[0]?.healthIDNumber); + this.abhaInfoFormGroup.controls['healthIdNumber'].patchValue(this.revisitData?.abhaDetails[0]?.healthIdNumber || ''); + this.abhaInfoFormGroup.controls['healthId']?.patchValue(this.revisitData?.abhaDetails[0]?.healthId || ''); console.log('other Form Data', this.formData); } } @@ -191,8 +198,11 @@ export class AbhaInformationComponent { this.abhaInfoFormGroup.controls['healthId'].patchValue(null); this.abhaInfoFormGroup.controls['healthIdMode'].patchValue(null); } else { - this.abhaInfoFormGroup.patchValue({ healthId: result.healthIdNumber }); - this.abhaInfoFormGroup.patchValue({ healthIdMode: result.healthIdMode }); + this.abhaInfoFormGroup.patchValue({ + healthId: result?.healthId || result?.healthIdNumber || '', + healthIdNumber: result?.healthIdNumber || '' + }); + this.abhaInfoFormGroup.patchValue({ healthIdMode: result?.healthIdMode || '' }); this.abhaInfoFormGroup.controls['healthIdNumber'].disable(); this.abhaInfoFormGroup.markAsDirty(); this.registrarService.changePersonalDetailsData(result); diff --git a/src/registrar/registration/registration.component.ts b/src/registrar/registration/registration.component.ts index 9f0e845..cc677e4 100644 --- a/src/registrar/registration/registration.component.ts +++ b/src/registrar/registration/registration.component.ts @@ -47,6 +47,7 @@ export class RegistrationComponent { consentGranted: any; disableGenerateOTP = false; today = new Date(); + healthIdNumber: any; constructor( @@ -337,27 +338,27 @@ export class RegistrationComponent { const reqObj = { beneficiaryRegID: null, beneficiaryID: numb, - healthId: this.mainForm.controls['abhaInfoForm'].value['healthIdNumber'], - healthIdNumber: this.mainForm.controls['abhaInfoForm'].value['healthIdNumber'], + healthId: this.mainForm.controls['abhaInfoForm'].value['healthId'], + healthIdNumber: this.healthIdNumber, providerServiceMapId: this.sessionstorage.getItem('providerServiceID'), authenticationMode: null, createdBy: this.sessionstorage.getItem('userName'), }; - if - (this.mainForm.controls['abhaInfoForm'].value['healthIdNumber'] !== undefined && - this.mainForm.controls['abhaInfoForm'].value['healthIdNumber'] !== null) { - this.registrarService.mapHealthId(reqObj).subscribe((res: any) => { - if (res.statusCode === 200) { - // this.confirmationService.alert(res.data.response, 'success'); - console.log('success'); - } else { - this.confirmationService.alert( - this.currentLanguageSet.alerts.info.issueInSavngData, - 'error', - ); - } - }); - } + if (this.healthIdNumber != null && this.healthIdNumber !== undefined) { + this.registrarService.mapHealthId(reqObj).subscribe((res: any) => { + console.log('response for benhealthid mapping', res); + if (res.statusCode === 200) { + // this.confirmationService.alert(res.data.response, 'success'); + console.log('success'); + } else { + this.confirmationService.alert( + this.currentLanguageSet.alerts.info.issueInSavngData, + 'error', + ); + } + }); + } + this.mainForm.reset(); this.disableGenerateOTP = false; this.router.navigate(['/registrar/search/']); @@ -433,7 +434,8 @@ export class RegistrationComponent { addressLine3: demographicsForm.controls['addressLine3']?.value || null, religionName: othersForm.controls['religionName']?.value || null, }, - abha: abhaForm.controls['healthIdNumber']?.value || null, + healthIdNumber: this.healthIdNumber, + healthId: abhaForm.controls['healthId']?.value || null, genderID: (() => { const genderName = personalForm.controls['genderName']?.value; if (genderName === 'Female') { @@ -513,8 +515,8 @@ export class RegistrationComponent { const reqObj = { beneficiaryRegID: null, beneficiaryID: personalForm.beneficiaryID, - healthId: this.mainForm.controls['abhaInfoForm'].value['healthIdNumber'], - healthIdNumber: this.mainForm.controls['abhaInfoForm'].value['healthIdNumber'], + healthId: this.mainForm.controls['abhaInfoForm'].value['healthId'], + healthIdNumber: this.healthIdNumber, authenticationMode: null, providerServiceMapId: this.sessionstorage.getItem('providerServiceID'), createdBy: this.sessionstorage.getItem('userName'), @@ -613,6 +615,8 @@ export class RegistrationComponent { addressLine3: demographicsForm.controls['addressLine3']?.value || null, religionName: othersForm.controls['religionName']?.value || null, }, + healthId: abhaForm.controls['healthId']?.value || null, + healthIdNumber: this.healthIdNumber, abha: abhaForm.controls['healthIdNumber']?.value || null, genderID: (() => { const genderName = personalForm.controls['genderName']?.value; @@ -751,9 +755,10 @@ export class RegistrationComponent { } setHealthIdAfterGeneration(result: any) { + this.healthIdNumber = result.healthIdNumber; (( this.mainForm.controls['otherInfoForm'] - )).patchValue({ healthId: result.healthIdNumber }); + )).patchValue({ healthId: result?.healthId || '' }); (( this.mainForm.controls['otherInfoForm'] )).patchValue({ healthIdNumber: result.healthIdNumber }); diff --git a/src/registrar/services/registrar.service.ts b/src/registrar/services/registrar.service.ts index 640429f..801df52 100644 --- a/src/registrar/services/registrar.service.ts +++ b/src/registrar/services/registrar.service.ts @@ -261,6 +261,20 @@ export class RegistrarService { return this.http.get(environment.getAbdmMappedFacility + reqObj); } + generateTokenForCareContext(reqObj: any) { + return this.http.post( + environment.generateLinkTokenForCareContext, + reqObj, + ); + } + + linkCareContext(reqObj: any) { + return this.http.post( + environment.linkCareContext, + reqObj, + ); + } + generateOtpForMappingCareContext(reqObjForMapping: any) { return this.http.post( environment.careContextGenerateOtpUrl,