diff --git a/healthcare/healthcare/doctype/abdm_request/__init__.py b/healthcare/healthcare/doctype/abdm_request/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/healthcare/doctype/abdm_request/abdm_request.js b/healthcare/healthcare/doctype/abdm_request/abdm_request.js new file mode 100644 index 0000000000..c071c50148 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_request/abdm_request.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, healthcare and contributors +// For license information, please see license.txt + +frappe.ui.form.on('ABDM Request', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/abdm_request/abdm_request.json b/healthcare/healthcare/doctype/abdm_request/abdm_request.json new file mode 100644 index 0000000000..ab90fa1f3f --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_request/abdm_request.json @@ -0,0 +1,107 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2022-03-15 18:16:17.060579", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "request_name", + "request_date", + "column_break_3", + "status", + "url", + "section_break_4", + "request", + "traceback", + "column_break_6", + "response" + ], + "fields": [ + { + "fieldname": "request_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Request Name", + "read_only": 1 + }, + { + "default": "Now", + "fieldname": "request_date", + "fieldtype": "Datetime", + "label": "Request Date", + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Requested\nGranted\nRevoked\nExpired\nDenied", + "read_only": 1 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "request", + "fieldtype": "Code", + "label": "Request", + "read_only": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "response", + "fieldtype": "Code", + "label": "Response", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "url", + "fieldtype": "Data", + "label": "URL" + }, + { + "fieldname": "traceback", + "fieldtype": "Code", + "label": "Traceback", + "read_only": 1 + } + ], + "in_create": 1, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2022-03-24 11:11:01.555036", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "ABDM Request", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/abdm_request/abdm_request.py b/healthcare/healthcare/doctype/abdm_request/abdm_request.py new file mode 100644 index 0000000000..36cf999d95 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_request/abdm_request.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, healthcare and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ABDMRequest(Document): + pass diff --git a/healthcare/healthcare/doctype/abdm_request/test_abdm_request.py b/healthcare/healthcare/doctype/abdm_request/test_abdm_request.py new file mode 100644 index 0000000000..3bd4bc5cc8 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_request/test_abdm_request.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, healthcare and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestABDMRequest(unittest.TestCase): + pass diff --git a/healthcare/healthcare/doctype/abdm_settings/__init__.py b/healthcare/healthcare/doctype/abdm_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/healthcare/doctype/abdm_settings/abdm_settings.js b/healthcare/healthcare/doctype/abdm_settings/abdm_settings.js new file mode 100644 index 0000000000..9901933895 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_settings/abdm_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, healthcare and contributors +// For license information, please see license.txt + +frappe.ui.form.on('ABDM Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/abdm_settings/abdm_settings.json b/healthcare/healthcare/doctype/abdm_settings/abdm_settings.json new file mode 100644 index 0000000000..6dd37fbca2 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_settings/abdm_settings.json @@ -0,0 +1,131 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:{gateway_name}-{company}", + "creation": "2022-03-14 20:32:55.651045", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "gateway_name", + "default", + "column_break_2", + "company", + "authorization_section", + "auth_base_url", + "column_break_7", + "client_id", + "client_secret", + "consent_section_section", + "patient_aadhaar_consent", + "health_id_service_section", + "health_id_base_url", + "consent_management_section", + "consent_base_url" + ], + "fields": [ + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "default": "0", + "fieldname": "default", + "fieldtype": "Check", + "label": "Default" + }, + { + "fieldname": "client_id", + "fieldtype": "Data", + "label": "Client ID" + }, + { + "fieldname": "client_secret", + "fieldtype": "Data", + "label": "Client Secret" + }, + { + "fieldname": "auth_base_url", + "fieldtype": "Data", + "label": "Base URL" + }, + { + "fieldname": "gateway_name", + "fieldtype": "Data", + "label": "Gateway Name" + }, + { + "fieldname": "authorization_section", + "fieldtype": "Section Break", + "label": "Authorization" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "health_id_service_section", + "fieldtype": "Section Break", + "label": "Health ID Service" + }, + { + "fieldname": "health_id_base_url", + "fieldtype": "Data", + "label": "Base URL" + }, + { + "fieldname": "consent_management_section", + "fieldtype": "Section Break", + "label": "Consent Management" + }, + { + "fieldname": "consent_base_url", + "fieldtype": "Data", + "label": "Base URL" + }, + { + "collapsible": 1, + "fieldname": "consent_section_section", + "fieldtype": "Section Break", + "label": "Consent Section" + }, + { + "description": "Patient Consent To Use Aadhaar For ABHA Creation", + "fieldname": "patient_aadhaar_consent", + "fieldtype": "Link", + "label": "Patient Aadhaar Consent", + "options": "Terms and Conditions" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2022-04-18 14:59:24.279125", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "ABDM Settings", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/abdm_settings/abdm_settings.py b/healthcare/healthcare/doctype/abdm_settings/abdm_settings.py new file mode 100644 index 0000000000..9bdd8bc5b1 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_settings/abdm_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, healthcare and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ABDMSettings(Document): + pass diff --git a/healthcare/healthcare/doctype/abdm_settings/test_abdm_settings.py b/healthcare/healthcare/doctype/abdm_settings/test_abdm_settings.py new file mode 100644 index 0000000000..1e81c1ac95 --- /dev/null +++ b/healthcare/healthcare/doctype/abdm_settings/test_abdm_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, healthcare and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestABDMSettings(unittest.TestCase): + pass diff --git a/healthcare/healthcare/doctype/patient/patient.js b/healthcare/healthcare/doctype/patient/patient.js index 9bef27b2e3..ab78fca03c 100644 --- a/healthcare/healthcare/doctype/patient/patient.js +++ b/healthcare/healthcare/doctype/patient/patient.js @@ -1,5 +1,6 @@ // Copyright (c) 2016, ESS LLP and contributors // For license information, please see license.txt +{% include 'healthcare/regional/india/abdm/js/patient.js' %} frappe.ui.form.on('Patient', { refresh: function (frm) { diff --git a/healthcare/hooks.py b/healthcare/hooks.py index 97a5c58363..830adc954c 100644 --- a/healthcare/hooks.py +++ b/healthcare/hooks.py @@ -111,9 +111,17 @@ "on_submit": "healthcare.healthcare.utils.manage_invoice_submit_cancel", "on_cancel": "healthcare.healthcare.utils.manage_invoice_submit_cancel", }, + "Payment Entry": { + "on_submit": "healthcare.healthcare.doctype.insurance_claim.insurance_claim.validate_payment_entry_and_set_claim_fields", + "on_submit": "healthcare.healthcare.doctype.insurance_claim.insurance_claim.update_claim_paid_amount", + "on_cancel": "healthcare.healthcare.doctype.insurance_claim.insurance_claim.update_claim_paid_amount", + }, "Company": { "after_insert": "healthcare.healthcare.utils.create_healthcare_service_unit_tree_root" }, + "Patient": { + "after_insert": "healthcare.regional.india.abdm.utils.set_consent_attachment_details" + }, } scheduler_events = { diff --git a/healthcare/modules.txt b/healthcare/modules.txt index 915d8d2745..6be675a39a 100644 --- a/healthcare/modules.txt +++ b/healthcare/modules.txt @@ -1 +1 @@ -Healthcare \ No newline at end of file +Healthcare diff --git a/healthcare/patches.txt b/healthcare/patches.txt index e69de29bb2..1b72a66fd3 100644 --- a/healthcare/patches.txt +++ b/healthcare/patches.txt @@ -0,0 +1 @@ +healthcare.patches.v0_0.setup_abdm_custom_fields \ No newline at end of file diff --git a/healthcare/patches/v0_0/setup_abdm_custom_fields.py b/healthcare/patches/v0_0/setup_abdm_custom_fields.py new file mode 100644 index 0000000000..69b1508567 --- /dev/null +++ b/healthcare/patches/v0_0/setup_abdm_custom_fields.py @@ -0,0 +1,11 @@ +import frappe + + +def execute(): + company = frappe.get_all("Company", filters={"country": "India"}) + if not company: + return + + from healthcare.regional.india.abdm.setup import setup + + setup() diff --git a/healthcare/regional/__init__.py b/healthcare/regional/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/regional/india/__init__.py b/healthcare/regional/india/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/regional/india/abdm/__init__.py b/healthcare/regional/india/abdm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/regional/india/abdm/abdm_config.py b/healthcare/regional/india/abdm/abdm_config.py new file mode 100644 index 0000000000..622a441ccb --- /dev/null +++ b/healthcare/regional/india/abdm/abdm_config.py @@ -0,0 +1,92 @@ +config = { + "authorization": {"method": "POST", "url": "/v0.5/sessions", "encrypted": False}, + "exists_by_health_id": { + "method": "POST", + "url": "/v1/search/existsByHealthId", + "encrypted": False, + }, + "verify_health_id": {"method": "POST", "url": "/v1/search/searchByHealthId", "encrypted": False}, + "generate_aadhaar_otp": { + "method": "POST", + "url": "/v1/registration/aadhaar/generateOtp", + "encrypted": False, + }, + "generate_mobile_otp": { + "method": "POST", + "url": "/v2/registration/mobile/generateOtp", + "encrypted": False, + }, + "verify_mobile_otp": { + "method": "POST", + "url": "/v2/registration/mobile/verifyOtp", + "encrypted": True, + }, + "resend_mobile_otp": { + "method": "POST", + "url": "/v2/registration/mobile/resendOtp", + "encrypted": False, + }, + "resend_aadhaar_otp": { + "method": "POST", + "url": "/v2/registration/aadhaar/resendAadhaarOtp", + "encrypted": False, + }, + "create_abha_w_aadhaar": { + "method": "POST", + "url": "/v1/registration/aadhaar/createHealthIdWithAadhaarOtp", + "encrypted": False, + }, + "create_abha_w_mobile": { + "method": "POST", + "url": "/v2/registration/mobile/createHidViaMobile", + "encrypted": False, + }, + "auth_cert": {"method": "GET", "url": "/v2/auth/cert", "encrypted": False}, + "auth_init": {"method": "POST", "url": "/v2/auth/init", "encrypted": False}, + "confirm_w_aadhaar_otp": { + "method": "POST", + "url": "/v2/auth/confirmWithAadhaarOtp", + "encrypted": True, + }, + "confirm_w_mobile_otp": { + "method": "POST", + "url": "/v2/auth/confirmWithMobileOTP", + "encrypted": True, + }, + "get_acc_info": {"method": "GET", "url": "/v2/account/profile", "encrypted": False}, + "resend_aadhaar_otp": { + "method": "POST", + "url": "/v1/registration/aadhaar/resendAadhaarOtp", + "encrypted": False, + }, + "generate_aadhaar_mobile_otp": { + "method": "POST", + "url": "/v1/registration/aadhaar/generateMobileOTP", + "encrypted": False, + }, + "verify_aadhaar_mobile_otp": { + "method": "POST", + "url": "/v1/registration/aadhaar/verifyMobileOTP", + "encrypted": False, + }, + "verify_aadhaar_otp": { + "method": "POST", + "url": "/v1/registration/aadhaar/verifyOTP", + "encrypted": False, + }, + "generate_mobile_otp_for_linking": { + "method": "POST", + "url": "/v2/document/generate/mobile/otp", + "encrypted": False, + }, + "verify_mobile_otp_for_linking": { + "method": "POST", + "url": "/v2/document/verify/mobile/otp", + "encrypted": True, + }, + "get_card": {"method": "GET", "url": "/v1/account/getPngCard", "encrypted": False}, +} + + +def get_url(key): + return config.get(key) diff --git a/healthcare/regional/india/abdm/js/patient.js b/healthcare/regional/india/abdm/js/patient.js new file mode 100644 index 0000000000..a61ad261e1 --- /dev/null +++ b/healthcare/regional/india/abdm/js/patient.js @@ -0,0 +1,960 @@ +frappe.ui.form.on('Patient', { + refresh: function (frm) { + if (frappe.boot.sysdefaults.country == 'India') { + unhide_field(['abha_number', 'abha_address']); + if (!frm.doc.abha_address && !frm.doc.abha_number) { + frm.add_custom_button(__('Verify ABHA'), function () { + search_by_abha_address(frm) + }, 'ABDM'); + } + if (frm.doc.abha_number) { + frm.add_custom_button(__('Verify ABHA Number'), function () { + verify_health_id(frm) + }, 'ABDM'); + } + if (!(frm.doc.abha_address || frm.doc.abha_number)) { + frm.add_custom_button(__('Create ABHA'), function () { + create_abha(frm) + }, 'ABDM'); + } + } else { + hide_field(['abha_number', 'abha_address']); + } + } +}); + +// search by ABHA address. If know ABHA number, can be verified +let search_by_abha_address = function (frm) { + let dialog = new frappe.ui.Dialog({ + title: 'Enter ABHA Address / Number', + fields: [ + { + label: 'ABHA Address / Number', + fieldname: 'abha_address', + fieldtype: 'Data' + }, + { + label: 'ABHA Number', + fieldname: 'abha_number', + fieldtype: 'Data', + hidden: 1 + } + ], + secondary_action_label: 'Search ABHA', + secondary_action(values) { + if (!dialog.get_value('abha_address')) { + frappe.throw({ + message: __(`ABHA Address is required to search`), + title: __("ABHA Addressent Required") + }); + } else { + show_message(dialog, 'Searching...', 'black', '', 'abha_address') + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + "healthId": dialog.get_value('abha_address') + }, + 'url_key': 'verify_health_id', + 'req_type': 'Health ID' + }, + freeze: true, + freeze_message: __('

Searching...'), + callback: function (data) { + if (data.message['healthIdNumber']) { + show_message(dialog, 'Status:' + data.message['status'], 'green', '', 'abha_address') + dialog.set_values ({ + 'abha_number': data.message['healthIdNumber'] + }) + dialog.hide(); + if (data.message['healthIdNumber']) { + verify_health_id(frm, data.message['healthIdNumber']) + } + } else { + show_message(dialog, data.message.message, '#fa6969', + data.message.details[0]['message'], 'abha_address') + } + } + }); + } + }, + }); + dialog.show(); +} + + +let verify_health_id = function (frm, recieved_abha_number = '') { + let d = new frappe.ui.Dialog({ + title: 'Verify ABHA', + fields: [ + { + label: 'ABHA Number', + fieldname: 'healthid', + fieldtype: 'Data' + }, + { + label: 'Authentication Method', + fieldname: 'auth_method', + fieldtype: 'Select', + options: ['AADHAAR_OTP', 'MOBILE_OTP'], + default: 'AADHAAR_OTP' + }, + { + label: 'Mobile number to save patient', + fieldname: 'sb1', + fieldtype: 'Section Break', + collapsible: 1 + }, + { + label: 'Mobile', + fieldname: 'mobile', + fieldtype: 'Data', + }, + { + fieldname: 'sb2', + fieldtype: 'Section Break' + }, + { + fieldname: 'qr_data', + fieldtype: 'HTML' + }, + { + fieldname: 'scanned_data', + fieldtype: 'Small Text', + hidden: 1 + }, + { + fieldname: 'abha_card', + fieldtype: 'Attach', + hidden: 1 + } + ], + primary_action_label: 'Send Auth OTP', + primary_action(values) { + d.get_primary_btn().attr('disabled', true); + show_message(d, '', '', '', 'auth_method') + frappe.run_serially([ + () =>frappe.db.get_value('Patient', {abha_number: d.get_value('healthid'), name: ['!=', frm.doc.name] }, ['name', 'abha_card']) + .then(r =>{ + if (r.message.name) { + frappe.set_route("Form", "Patient", r.message.name); + if (r.message.abha_card) { + frappe.throw({ + message: __(`{0}`, + [""]), + title: __("Patient already exist") + }); + } else { + frappe.throw({ + message: __(`{0}`, + ['' + r.message.name + '']), + title: __("Patient already exist") + }); + } + } + }), + () => {show_message(d, 'Sending Auth OTP...', 'black', '', 'auth_method') + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + "authMethod": d.get_value('auth_method'), + "healthid": d.get_value('healthid') + }, + 'url_key': 'auth_init', + 'req_type': 'Health ID' + }, + freeze: true, + freeze_message: __('Generating OTP...'), + callback: function (r) { + let txn_id = r.message['txnId']; + if (txn_id) { + show_message(d, 'Successfully Sent OTP', 'green', '', 'auth_method') + verify_auth_otp(r, d) + } else { + if (r.message.message && r.message.details[0]['message']) { + show_message(d, r.message.message, 'red', r.message.details[0]['message'], 'auth_method') + } + frappe.show_alert({ + message:__('OTP Generation Failed, Please try again later'), + indicator:'red' + }, 10); + d.get_primary_btn().attr('disabled', false); + } + } + }); + } + ]) + }, + secondary_action_label: 'Save', + secondary_action(values) { + // save data from qr_scan/api fetch, save to form + var scanned_data = JSON.parse(d.get_value('scanned_data')); + set_data_to_form(frm, scanned_data, d, '') + frm.save(); + d.hide(); + } + }); + + // QR scanner field + setup_qr_scanner(d) + //verify btn + setup_send_otp_btn(d) + if (recieved_abha_number) { + d.set_values({ + 'healthid': recieved_abha_number + }); + } + d.get_secondary_btn().attr('disabled', true); + d.fields_dict['scanned_data'].df.onchange = () => { + if (d.get_value('scanned_data')) { + d.get_secondary_btn().attr('disabled', false); + } + } + d.fields_dict['healthid'].df.onchange = () => { + d.get_primary_btn().attr('disabled', false); + } + + d.show(); +} + +// authorization otp verification +let verify_auth_otp = function(r, d) { + let dialog = new frappe.ui.Dialog({ + title: 'Authentication OTP', + fields: [ + { + label: 'OTP', + fieldname: 'otp', + fieldtype: 'Data', + reqd: 1 + } + ], + primary_action_label: 'Verify', + primary_action(values) { + // sending otp received to call 2 apis and receive health_data + show_message(d, 'Verifying...', 'black', '', 'auth_method') + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.get_health_data', + args: { + 'otp': dialog.get_value('otp'), + 'txnId': r.message['txnId'], + 'auth_method': d.get_value('auth_method') + }, + freeze: true, + freeze_message: __(`

Verifying OTP...
+ Please note, this may take a while`), + callback: function (data) { + if (data.message) { + if (data.message[0] && data.message[0]['healthIdNumber']) { + d.get_primary_btn().attr('hidden', true); + if (!data.message[1] && !data.message[1]['file_url']) { + set_qr_scanned_data(d, data.message[0]) + } + d.set_values({ + 'scanned_data': JSON.stringify(data.message[0]) + }); + show_message(d, '', '', '', 'auth_method') + } else { + if (data.message[0].details[0]['message']) { + show_message(d, data.message[0].message, 'red', data.message[0].details[0]['message'], 'auth_method') + } + frappe.show_alert({ + message:__('Failed to fetch health Data, Please try again later'), + indicator:'red' + }, 10); + } + if (data.message[1]) { + $(d.fields_dict.qr_data.$wrapper).html("") + d.set_values({ + 'abha_card': data.message[1] + }); + show_message(d, '', '', '', 'auth_method') + } + } + } + }); + dialog.hide(); + } + }); + dialog.show(); +} + + +let create_abha = function (frm) { + let d = new frappe.ui.Dialog({ + title: 'Create ABHA', + fields: [ + { + label: 'Enter Aadhaar', + fieldname: 'aadhaar', + fieldtype: 'Data', + mandatory: 1 + }, + { + label:'Patient Consent', + fieldname: 'patient_consent', + fieldtype: 'Section Break' + }, + { + fieldname: 'patient_consent', + fieldtype: 'Link', + options: 'Terms and Conditions', + read_only: 0 + }, + { + fieldname: 'cb1', + fieldtype: 'Column Break', + }, + { + label: 'Button', + fieldname: 'print_btn', + fieldtype: 'HTML' + }, + { + fieldname: 'sb2', + fieldtype: 'Section Break', + hide_border: 1 + }, + { + fieldname: 'patient_consent_attach', + fieldtype: 'Attach', + description: `Please attach patient's signed consent for using + their Aadhaar for ABHA creation` + }, + { + label: 'OR', + fieldname: 'or', + fieldtype: 'Heading', + hidden: 1 + }, + { + label: 'Received Consent', + fieldname: 'received_consent', + fieldtype: 'Check', + default: 0, + description: `Check this to confirm that patient has + provided consent to use Aadhaar for ABHA Registration`, + hidden: 1 + }, + ], + primary_action_label: 'Send OTP', + primary_action(values) { + if (!d.get_value('received_consent') && !d.get_value('patient_consent_attach')) { + frappe.throw({ + message: __(`Patient Consent is required for ABHA creation`), + title: __("Consent Required") + }); + } else { + create_abha_with_aadhaar(frm, d) + d.hide(); + } + } + }); + + let print_button = d.fields_dict.print_btn.$wrapper; + + print_button.html( + `
` + ); + + print_button.on('click', 'button', function() { + frappe.db.get_value('Terms and Conditions', d.get_value('patient_consent'), 'terms') + .then(r => { + let result = frappe.render_template(r.message.terms, {"doc" : {}}) + frappe.render_pdf(result, {orientation:"Portrait"}); + }) + }) + + frappe.db.get_value('ABDM Settings', { + company: frappe.defaults.get_user_default("Company"), + default: 1 + }, 'patient_aadhaar_consent') + .then(r => { + if (r.message.patient_aadhaar_consent) { + d.set_values({ + 'patient_consent': r.message.patient_aadhaar_consent + }); + } + }) + d.show(); +} + + +// to create html table +let set_qr_scanned_data = function(d, scanned_data) { + let wrapper = $(d.fields_dict['qr_data'].wrapper).empty(); + let dob = ''; + if (scanned_data['dob']) { + dob = scanned_data['dob'] + } else { + dob = `${scanned_data['dayOfBirth'] ? scanned_data['dayOfBirth'] : '-'} - + ${scanned_data['monthOfBirth'] ? scanned_data['monthOfBirth'] : '-'} - + ${scanned_data['yearOfBirth']}`; + } + + let qr_table = $(` + `).appendTo(wrapper); + const row = + $(` + + + + + + + + + + + + + + + + + + + `); + qr_table.find('tbody').append(row); +} + + +let set_data_to_form = function(frm, scanned_data, dialog, d) { + if (scanned_data) { + let dob = ''; + if (scanned_data['dob']) { + dob = scanned_data['dob']; + } else { + dob = `${scanned_data['dayOfBirth'] ? scanned_data['dayOfBirth'] : '01'}- + ${scanned_data['monthOfBirth'] ? scanned_data['monthOfBirth'] : '01'}- + ${scanned_data['yearOfBirth']}`; + } + for (var k in scanned_data) { + if (k == 'hid' || k == 'healthId'){frm.set_value('abha_address', scanned_data[k])} + if (k == 'hidn' || k == 'healthIdNumber'){frm.set_value('abha_number', scanned_data[k])} + if (!frm.doc.first_name) { + if (k == 'name'){frm.set_value('first_name', scanned_data[k])} + } + if (!frm.doc.dob) { + if (dob){frm.set_value('dob', moment(dob, 'DD/MM/YYYY').format('YYYY-MM-DD'))} + } + if (!frm.doc.email) { + if (k == 'email'){frm.set_value('email', scanned_data[k])} + } + if (dialog.get_value('mobile')) { + frm.set_value('mobile', dialog.get_value('mobile')) + } else if (k == 'mobile'){ + frm.set_value('mobile', scanned_data[k]) + } + if (k == 'gender'){ + let gender = scanned_data[k] == 'M' ? 'Male' : + scanned_data[k] == 'F' ? 'Female' : + scanned_data[k] == 'U' ? 'Prefer not to say' : 'Other' + frm.set_value('sex', gender) + } + if (d && d.get_value('patient_consent_attach')) { + frm.set_value('consent_for_aadhaar_use', d.get_value('patient_consent_attach')) + } + } + if (dialog.get_value('abha_card')) { + frm.set_value('abha_card', dialog.get_value('abha_card')) + } + } +} + + +let setup_qr_scanner = function(dialog) { + dialog.fields_dict.healthid.$wrapper.find('.control-input').append( + ` + + ${frappe.utils.icon('scan', 'sm')} + + ` + ); + let scan_btn = dialog.$body.find('.link-btn'); + scan_btn.toggle(true); + + scan_btn.on('click', 'a', () => { + new frappe.ui.Scanner({ + dialog: true, + multiple: false, + on_scan(data) { + if (data && data.result && data.result.text) { + var scanned_data = JSON.parse(data.decodedText); + dialog.set_values({ + 'scanned_data': data.decodedText, + 'healthid': (scanned_data['hidn'] ? scanned_data['hidn'] : '') + }); + set_qr_scanned_data(dialog, scanned_data) + } + } + }); + }); +} + + +let show_message = function(dialog, message, color, details, field) { + var field = dialog.get_field(field); + field.df.description = `
${message}
+ ${details ? 'Details: '+details+'
': ''}` + field.refresh(); +} + + +let setup_search_btn = function(dialog) { + dialog.fields_dict.username.$wrapper.find('.control-input').append( + ` + + ${frappe.utils.icon("search", "sm")} + + ` + ); + let search_btn = dialog.$body.find('.search'); + search_btn.toggle(true); + + search_btn.on('click', 'a', () => { + if (dialog.get_value('username')) { + show_message(dialog, 'Verifying...', 'black', '', 'username') + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + "healthId": dialog.get_value('username') + }, + 'url_key': 'exists_by_health_id', + 'req_type': 'Health ID' + }, + freeze: true, + freeze_message: __('

Verifying...'), + callback: function (data) { + if (data.message['status'] == false) { + show_message(dialog, 'ABHA Address can be used', 'green', '', 'username') + dialog.get_primary_btn().attr('disabled', false); + } else if (data.message['status'] == true) { + show_message(dialog, 'ABHA Address is already existing', 'red', '', 'username') + dialog.get_primary_btn().attr('disabled', true); + } + } + }); + } + }); +} + + +let create_abha_with_aadhaar = function(frm, d) { + let txn_id = '' + let error_msg = '' + frappe.run_serially([ + () => frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + "aadhaar": d.get_value('aadhaar') + }, + 'url_key': 'generate_aadhaar_otp', + 'req_type': 'Health ID' + }, + freeze: true, + freeze_message: __('Sending OTP...'), + callback: function (r) { + if (r.message['txnId']) { + txn_id = r.message['txnId']; + } else { + error_msg = r.message + } + } + }), + () => { + if (txn_id) { + let dialog = new frappe.ui.Dialog({ + title: 'Create', + fields: [ + { + label: 'Aadhaar OTP', + fieldname: 'otp', + fieldtype: 'Data', + reqd: 1 + }, + { + fieldname: 'resent_txn_id', + fieldtype: 'Data', + hidden: 1 + }, + { + label: 'Use Another Mobile Number For ABHA', + fieldname: 'sb1', + fieldtype: 'Section Break', + collapsible: 1 + }, + { + label: 'Mobile', + fieldname: 'mobile', + fieldtype: 'Data', + }, + { + fieldname: 'sb2', + fieldtype: 'Section Break' + }, + { + label: 'Choose ABHA Address', + fieldname: 'sb5', + fieldtype: 'Section Break', + collapsible: 1 + }, + { + label: 'Choose ABHA Address (Optional)', + fieldname: 'username', + fieldtype: 'Data' + }, + { + fieldname: 'sb3', + fieldtype: 'Section Break', + hide_border: 1 + } + ], + primary_action_label: 'Create ABHA ID', + primary_action(values) { + dialog.get_primary_btn().attr('disabled', true); + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + "email": frm.doc.email || '', + "firstName": frm.doc.first_name || '', + "lastName": frm.doc.last_name || '', + "middleName": frm.doc.middle_name || '', + "mobile": dialog.get_value('mobile') ? + dialog.get_value('mobile') : frm.doc.mobile, + "otp": dialog.get_value('otp'), + "password": dialog.get_value('password'), + "txnId": txn_id, + "username": dialog.get_value('username') + }, + 'url_key': 'create_abha_w_aadhaar', + 'req_type': 'Health ID' + }, + freeze: true, + freeze_message: __(`


Creating Health ID
+ Please note, this may take a while`), + callback: function (data) { + if (data.message['healthIdNumber']) { + dialog.hide() + frappe.run_serially([ + () =>frappe.db.get_value('Patient', {abha_number: data.message['healthIdNumber'], + name: ['!=', frm.doc.name] }, ['name', 'abha_card']) + .then(r =>{ + if (r.message.name) { + frappe.set_route("Form", "Patient", r.message.name); + if (r.message.abha_card) { + frappe.throw({ + message: __(`{0}`, + [""]), + title: __("Patient already exist") + }); + } else { + frappe.throw({ + message: __(`{0}`, + ['' + r.message.name + '']), + title: __("Patient already exist") + }); + } + } + }), + () => { + set_data_to_form(frm, data.message, dialog, d) + if (data.message['token']) { + show_id_card_dialog(frm, data.message['token']) + } + if (data.message['new'] == false) { + frappe.show_alert({ + message: __('Fetched existing ABHA of aadhaar provided'), + indicator: 'green' }, 5); + } else { + frappe.show_alert({ + message: __('ABHA ID created successfully'), + indicator: 'green' }, 5); + } + // frm.save() + dialog.hide(); + }, + ]) + } else { + dialog.get_primary_btn().attr('disabled', false); + if (data.message && data.message.details[0]['message']) { + show_message(dialog, data.message.message, 'red', + data.message.details[0]['message'], 'otp') + } + frappe.show_alert({ + message: __('ABHA ID not Created'), + indicator: 'red' }, 5); + } + } + }); + } + }); + + setup_search_btn(dialog) + setup_resend_otp_btn(dialog, txn_id) + setup_send_otp_btn(dialog, txn_id) + + // clear response_message + dialog.fields_dict['username'].df.onchange = () => { + show_message(dialog, '', '', '', 'otp') + dialog.get_primary_btn().attr('disabled', true); + } + dialog.show(); + } else { + if (error_msg) { + if (error_msg.details[0]['message']) { + frappe.show_alert({ + message: __(error_msg.details[0]['message']), + indicator: 'red' }, 5); + } else if (error_msg.message) { + frappe.show_alert({ + message: __(error_msg.message), + indicator: 'red' }, 5); + } + } + } + } + ]); + +} + + +let setup_resend_otp_btn = function(dialog, txn_id) { + dialog.fields_dict.otp.$wrapper.find('.control-input').append( + ` + + Resend OTP + + ` + ); + let search_btn = dialog.$body.find('.resend-btn'); + search_btn.toggle(true); + + search_btn.on('click', 'a', () => { + if (txn_id) { + show_message(dialog, 'Resending Aadhaar OTP ...', 'black', '', 'otp') + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + "txnId": txn_id + }, + 'url_key': 'resend_aadhaar_otp', + 'req_type': 'Health ID' + }, + freeze: true, + freeze_message: __('

Resending Aadhaar OTP...'), + callback: function (data) { + if (data.message['txnId']) { + show_message(dialog, 'Successfully Resent Aadhaar OTP', 'green', '', 'otp') + dialog.get_primary_btn().attr('disabled', false); + dialog.set_values({ + 'resent_txn_id': data.message['txnId'] + }); + } else { + show_message(dialog, 'Resending Aadhaar OTP Failed', 'red', '', 'otp') + dialog.get_primary_btn().attr('disabled', true); + } + } + }); + } + }); +} + + +let setup_send_otp_btn = function(dialog, txn_id = '') { + dialog.fields_dict.mobile.$wrapper.find('.control-input').append( + ` + + Verify + + ` + ); + let search_btn = dialog.$body.find('.send-a-m-otp'); + search_btn.toggle(true); + + search_btn.on('click', 'a', () => { + if (dialog.get_value('mobile')) { + let args = {}; + let url_key = ''; + if (txn_id) { + args = { + 'payload': { + "mobile": dialog.get_value('mobile'), + "txnId": txn_id + }, + 'url_key': 'generate_aadhaar_mobile_otp', + 'req_type': 'Health ID' + } + url_key = 'verify_aadhaar_mobile_otp' + } else { + args = { + 'payload': { + "mobile": dialog.get_value('mobile') + }, + 'url_key': 'generate_mobile_otp_for_linking', + 'req_type': 'Health ID' + } + url_key = 'verify_mobile_otp_for_linking' + } + dialog.fields_dict.mobile.$wrapper.find("span").remove(); + show_message(dialog, 'Sending Mobile OTP...', 'black', '', 'mobile') + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: args, + freeze: true, + freeze_message: __('

Verifying...'), + callback: function (data) { + if (data.message['txnId']) { + // setup_verify_otp_btn(dialog, data.message['txnId']) + verify_mobile_otp_dialog(dialog, data.message['txnId'], url_key) + show_message(dialog, 'Successfully Sent OTP', 'green', '', 'mobile') + } else { + // recreate send otp btn if otp sending fails + setup_send_otp_btn(dialog, txn_id) + if (data.message && data.message.details[0]['message']) { + show_message(dialog, data.message.message, 'red', + data.message.details[0]['message'], 'mobile') + } else { + show_message(dialog, 'Sending OTP Failed', 'red', '', 'mobile') + } + } + } + }); + } else { + show_message(dialog, 'Please Enter Mobile Number', 'red', '', 'mobile') + } + }); +} + + +let verify_mobile_otp_dialog = function(dialog, txn_id, url_key) { + let otp_dialog = new frappe.ui.Dialog({ + title: 'Mobile Verification', + fields: [ + { + label: 'OTP', + fieldname: 'otp', + fieldtype: 'Data', + reqd: 1 + } + ], + primary_action_label: 'Verify', + primary_action(values) { + show_message(dialog, 'Verifying OTP...', 'black', '', 'mobile') + let args = {}; + if (url_key == 'verify_aadhaar_mobile_otp') { + args = { + 'payload': { + "otp": otp_dialog.get_value('otp'), + "txnId": txn_id + }, + 'url_key': url_key, + 'req_type': 'Health ID' + } + } else if (url_key == 'verify_mobile_otp_for_linking'){ + args = { + 'payload': { + "to_encrypt": otp_dialog.get_value('otp'), + "txnId": txn_id + }, + 'url_key': url_key, + 'req_type': 'Health ID', + 'to_be_enc': 'otp' + } + } + frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: args, + freeze: true, + freeze_message: __('

Verifying...'), + callback: function (data) { + show_message(dialog, '', '', '', 'mobile') + if (data.message['txnId'] || data.message['token']) { + dialog.fields_dict.mobile.$wrapper.find("span").remove(); + dialog.fields_dict.mobile.$wrapper.find('.control-input').append( + ` + + + + ` + ); + } else { + dialog.fields_dict.mobile.$wrapper.find("span").remove(); + dialog.fields_dict.mobile.$wrapper.find('.control-input').append( + ` + + + + ` + ); + let x_btn = dialog.$body.find('.p-x-btn'); + x_btn.toggle(true); + + x_btn.on('click', 'a', () => { + dialog.fields_dict.mobile.$wrapper.find("span").remove(); + if (url_key == 'verify_aadhaar_mobile_otp') { + setup_send_otp_btn(dialog, txn_id) + } else if (url_key == 'verify_mobile_otp_for_linking'){ + setup_send_otp_btn(dialog) + } + }); + } + } + }); + otp_dialog.hide(); + } + }); + otp_dialog.show(); +} + +let show_id_card_dialog = function(frm, token) { + frappe.run_serially([ + () =>frm.save(), + () =>{frappe.call({ + method: 'healthcare.regional.india.abdm.utils.abdm_request', + args: { + 'payload': { + }, + 'url_key': 'get_card', + 'req_type': 'Health ID', + 'rec_headers': { + 'X-Token': 'Bearer '+ token + }, + 'patient_name': frm.doc.name + }, + freeze: true, + freeze_message: __(`


Getting Health ID`), + callback: function (data) { + if (data.message['file_url']) { + // frm.set_value('abha_card', data.message['file_url']) + let abha_id_dialog = new frappe.ui.Dialog({ + title: 'ABHA Card', + fields: [ + { + fieldname: 'abha_card_html', + fieldtype: 'HTML', + } + ], + primary_action_label: 'Print', + primary_action(values) { + let result = "" + frappe.render_pdf(result, {orientation:"Landscape"}); + }, + }) + $(abha_id_dialog.fields_dict.abha_card_html.$wrapper).html("") + abha_id_dialog.show(); + } + } + }) + } +]) +} diff --git a/healthcare/regional/india/abdm/setup.py b/healthcare/regional/india/abdm/setup.py new file mode 100644 index 0000000000..db7ecd9e95 --- /dev/null +++ b/healthcare/regional/india/abdm/setup.py @@ -0,0 +1,51 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def setup(): + if not frappe.db.exists("Custom Field", "Patient-abha_address"): + make_custom_fields() + + +def make_custom_fields(): + company = frappe.get_all("Company", filters={"country": "India"}) + if not company: + return + custom_fields = get_custom_fields() + create_custom_fields(custom_fields) + + +def get_custom_fields(): + custom_fields = { + "Patient": [ + dict( + fieldname="abha_address", + label="PHR Address", + fieldtype="Data", + insert_after="status", + read_only=1, + ), + dict( + fieldname="abha_number", + label="ABHA Number", + fieldtype="Data", + insert_after="abha_address", + read_only=1, + ), + dict( + fieldname="abha_card", + label="ABHA Card", + fieldtype="Attach", + insert_after="patient_details", + hidden=1, + ), + dict( + fieldname="consent_for_aadhaar_use", + label="Consent For Aadhaar Use", + fieldtype="Attach", + insert_after="abha_card", + hidden=1, + ), + ] + } + return custom_fields diff --git a/healthcare/regional/india/abdm/test_abdm.py b/healthcare/regional/india/abdm/test_abdm.py new file mode 100644 index 0000000000..f5237706d0 --- /dev/null +++ b/healthcare/regional/india/abdm/test_abdm.py @@ -0,0 +1,41 @@ +import frappe +from frappe.tests.utils import FrappeTestCase + +import responses +from healthcare.regional.india.abdm.utils import abdm_request + + +class TestPatient(FrappeTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + doc = frappe.get_doc( + { + "doctype": "ABDM Settings", + "default": 1, + "auth_base_url": "https://dev.abdm.gov.in/gateway/", + "health_id_base_url": "https://healthidsbx.abdm.gov.in/api/", + "Company": frappe.defaults.get_user_default("Company"), + } + ) + doc.insert() + + @responses.activate + def test_aadhar_otp_flow(self): + responses.add( + responses.POST, + "https://dev.abdm.gov.in/gateway/v0.5/sessions", + json={"accessToken": "foo.bar"}, + status=200, + ) + responses.add( + responses.POST, + "https://healthidsbx.abdm.gov.in/api/v1/registration/aadhaar/generateOtp", + json={"txnId": "37ca-41de"}, + status=200, + ) + + payload = {"aadhaar": "123400001234"} + response = abdm_request(payload, "generate_aadhaar_otp", "Health ID") + assert "txnId" in response diff --git a/healthcare/regional/india/abdm/utils.py b/healthcare/regional/india/abdm/utils.py new file mode 100644 index 0000000000..5b5f78274a --- /dev/null +++ b/healthcare/regional/india/abdm/utils.py @@ -0,0 +1,258 @@ +import json + +import frappe +import requests +from healthcare.regional.india.abdm.abdm_config import get_url + + +@frappe.whitelist() +def get_authorization_token(): + client_id, client_secret, auth_base_url = frappe.db.get_value( + "ABDM Settings", + {"company": frappe.defaults.get_user_default("Company"), "default": 1}, + ["client_id", "client_secret", "auth_base_url"], + ) + + config = get_url("authorization") + auth_base_url = auth_base_url.rstrip("/") + url = auth_base_url + config.get("url") + payload = {"clientId": client_id, "clientSecret": client_secret} + if not auth_base_url: + frappe.throw( + title="Not Configured", + msg="Base URL not configured in ABDM Settings!", + ) + + req = frappe.new_doc("ABDM Request") + req.request = json.dumps(payload, indent=4) + req.url = url + req.request_name = "Authorization Token" + try: + response = requests.request( + method=config.get("method"), + url=url, + headers={"Content-Type": "application/json; charset=UTF-8"}, + data=json.dumps(payload), + ) + response.raise_for_status() + response = response.json() + req.response = json.dumps(response, indent=4) + req.status = "Granted" + req.insert(ignore_permissions=True) + return response.get("accessToken"), response.get("tokenType") + + except Exception as e: + try: + req.response = json.dumps(response.json(), indent=4) + except json.decoder.JSONDecodeError: + req.response = response.text + req.traceback = e + req.status = "Revoked" + req.insert(ignore_permissions=True) + traceback = f"Remote URL {url}\nPayload: {payload}\nTraceback: {e}" + frappe.log_error(message=traceback, title="Cant create session") + return auth_base_url, None, None + + +@frappe.whitelist() +def abdm_request(payload, url_key, req_type, rec_headers=None, to_be_enc=None, patient_name=None): + if payload and isinstance(payload, str): + payload = json.loads(payload) + + if req_type == "Health ID": + url_type = "health_id_base_url" + + base_url = frappe.db.get_value( + "ABDM Settings", + {"company": frappe.defaults.get_user_default("Company"), "default": 1}, + [url_type], + ) + if not base_url: + frappe.throw(title="Not Configured", msg="Base URL not configured in ABDM Settings!") + + config = get_url(url_key) + base_url = base_url.rstrip("/") + url = base_url + config.get("url") + # Check the abdm_config, if the data need to be encypted, encrypts message + # Build payload with encrypted message + if config.get("encrypted"): + message = payload.get("to_encrypt") + encrypted = get_encrypted_message(message) + if "encrypted_msg" in encrypted and encrypted["encrypted_msg"]: + payload[to_be_enc] = payload.pop("to_encrypt") + payload[to_be_enc] = encrypted["encrypted_msg"] + + access_token, token_type = get_authorization_token() + + if not access_token: + frappe.throw( + title="Authorization Failed", + msg="Access token generation for authorization failed, Please try again.", + ) + + authorization = ("Bearer " if token_type == "bearer" else "") + access_token + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": authorization, + } + if rec_headers: + if isinstance(rec_headers, str): + rec_headers = json.loads(rec_headers) + headers.update(rec_headers) + req = frappe.new_doc("ABDM Request") + req.status = "Requested" + # TODO: skip saving or encrypt the data saved + req.request = json.dumps(payload, indent=4) + req.url = url + req.request_name = url_key + try: + response = requests.request( + method=config.get("method"), url=url, headers=headers, data=json.dumps(payload) + ) + response.raise_for_status() + if url_key == "get_card": + pdf = response.content + _file = frappe.get_doc( + { + "doctype": "File", + "file_name": "abha_card{}.png".format(patient_name), + "attached_to_doctype": "Patient", + "attached_to_name": patient_name, + "attached_to_field": "abha_card", + "is_private": 0, + "content": pdf, + } + ) + _file.save() + frappe.db.commit() + return _file + req.response = json.dumps(response.json(), indent=4) + req.status = "Granted" + req.insert(ignore_permissions=True) + return response.json() + + except Exception as e: + req.traceback = e + req.response = json.dumps(response.json(), indent=4) + req.status = "Revoked" + req.insert(ignore_permissions=True) + traceback = f"Remote URL {url}\nPayload: {payload}\nTraceback: {e}" + frappe.log_error(message=traceback, title="Cant complete API call") + return response.json() + + +def get_encrypted_message(message): + base_url = frappe.db.get_value( + "ABDM Settings", + {"company": frappe.defaults.get_user_default("Company"), "default": 1}, + ["health_id_base_url"], + ) + + config = get_url("auth_cert") + url = base_url + config.get("url") + req = frappe.new_doc("ABDM Request") + req.status = "Requested" + req.url = url + req.request_name = "auth_cert" + try: + response = requests.request( + method=config.get("method"), url=url, headers={"Content-Type": "application/json"} + ) + + response.raise_for_status() + pub_key = response.text + pub_key = ( + pub_key.replace("\n", "") + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + ) + if pub_key: + encrypted_msg = get_rsa_encrypted_message(message, pub_key) + req.response = encrypted_msg + req.status = "Granted" + req.insert(ignore_permissions=True) + encrypted = {"public_key": pub_key, "encrypted_msg": encrypted_msg} + return encrypted + + except Exception as e: + req.traceback = e + req.response = json.dumps(response.json(), indent=4) + req.status = "Revoked" + req.insert(ignore_permissions=True) + traceback = f"Remote URL {url}\nTraceback: {e}" + frappe.log_error(message=traceback, title="Cant complete API call") + return None + + +def get_rsa_encrypted_message(message, pub_key): + # TODO:- Use cryptography + from Crypto.Cipher import PKCS1_v1_5 + from Crypto.PublicKey import RSA + from base64 import b64decode, b64encode + + message = bytes(message, "utf-8") + pubkey = b64decode(pub_key) + rsa_key = RSA.importKey(pubkey) + cipher = PKCS1_v1_5.new(rsa_key) + ciphertext = cipher.encrypt(message) + emsg = b64encode(ciphertext) + encrypted_msg = emsg.decode("UTF-8") + return encrypted_msg + + +@frappe.whitelist() +def get_health_data(otp, txnId, auth_method): + confirm_w_otp_payload = {"to_encrypt": otp, "txnId": txnId} + if auth_method == "AADHAAR_OTP": + url_key = "confirm_w_aadhaar_otp" + elif auth_method == "MOBILE_OTP": + url_key = "confirm_w_mobile_otp" + # returns X-Token + response = abdm_request(confirm_w_otp_payload, url_key, "Health ID", "", "otp") + abha_url = "" + if response and response.get("token"): + abha_url = get_abha_card(response["token"]) + header = {"X-Token": "Bearer " + response["token"]} + response = abdm_request("", "get_acc_info", "Health ID", header, "") + return response, abha_url + + +# patient after_insert +def set_consent_attachment_details(doc, method=None): + if frappe.db.exists( + "ABDM Settings", + {"company": frappe.defaults.get_user_default("Company"), "default": 1}, + ): + if doc.consent_for_aadhaar_use: + file_name = frappe.db.get_value("File", {"file_url": doc.consent_for_aadhaar_use}, "name") + if file_name: + frappe.db.set_value( + "File", + file_name, + { + "attached_to_doctype": "Patient", + "attached_to_name": doc.name, + "attached_to_field": doc.consent_for_aadhaar_use, + }, + ) + if doc.abha_card: + abha_file_name = frappe.db.get_value( + "File", {"file_url": doc.abha_card, "attached_to_name": None}, "name" + ) + if abha_file_name: + frappe.db.set_value( + "File", + abha_file_name, + { + "attached_to_doctype": "Patient", + "attached_to_name": doc.name, + "attached_to_field": doc.abha_card, + }, + ) + + +def get_abha_card(token): + header = {"X-Token": "Bearer " + token} + response = abdm_request("", "get_card", "Health ID", header, "") + return response.get("file_url") diff --git a/healthcare/setup.py b/healthcare/setup.py index 46eba2ba34..d0b92777f8 100644 --- a/healthcare/setup.py +++ b/healthcare/setup.py @@ -105,6 +105,11 @@ def setup_healthcare(): if frappe.db.exists("Medical Department", "Cardiology"): # already setup return + + from healthcare.regional.india.abdm.setup import setup as abdm_setup + + abdm_setup() + create_custom_records() create_default_root_service_units() diff --git a/requirements.txt b/requirements.txt index 7668191f9c..4735625037 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -# frappe -- https://github.com/frappe/frappe is installed via 'bench init' \ No newline at end of file +# frappe -- https://github.com/frappe/frappe is installed via 'bench init' +# erpnext -- https://github.com/frappe/erpnext is installed automatically +responses==0.20.0 \ No newline at end of file
Name${scanned_data['name']}
Gender${scanned_data['gender'] || '-'}
Mobile${scanned_data['mobile'] || '-'}
DOB${dob}
ABHA Address${scanned_data['healthId'] || scanned_data['hid'] || '-'}