From 15add89906dd3378597f3b13ed8f12d968b128b2 Mon Sep 17 00:00:00 2001 From: RomanBachaloSigmaSoftware Date: Tue, 14 Oct 2025 16:51:04 +0300 Subject: [PATCH 01/12] add connected fields --- backend/api/urls.py | 1 + backend/api/views.py | 22 ++- backend/docusign/ds_config.py | 9 +- backend/docusign/extensions.py | 66 ++++++++ .../pdf/Application_for_participation_v2.pdf | Bin 88773 -> 86989 bytes backend/docusign/template.py | 4 +- ...ke_application_for_participation_signer.py | 156 ++++++++++++++---- frontend/public/locales/en/Common.json | 6 +- frontend/src/api/healthcareAPI.js | 14 ++ .../src/assets/scss/components/_seeMore.scss | 3 + frontend/src/components/extensionModal.js | 35 ++++ frontend/src/components/header.js | 4 +- frontend/src/components/seeMore.js | 2 +- .../pages/applyForPatientAssistance/index.js | 45 ++++- 14 files changed, 319 insertions(+), 48 deletions(-) create mode 100644 backend/docusign/extensions.py create mode 100644 frontend/src/components/extensionModal.js diff --git a/backend/api/urls.py b/backend/api/urls.py index 4cfd2be..f6e8c8e 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -5,4 +5,5 @@ path('request-medical-records', views.request_medical_records), path('covid19-consent-form', views.covid19_consent_form), path('apply-for-patient-assistance', views.apply_for_patient_assistance), + path('extensions', views.get_extension_apps), ] diff --git a/backend/api/views.py b/backend/api/views.py index 24f4e01..00f1d13 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -5,6 +5,7 @@ from docusign.template import Template from docusign.envelope import Envelope from docusign.workflow import get_idv_workflow, is_sms_workflow +from docusign.extensions import Extensions from .serializers import ( RequestMedicalRecordsSerializer, @@ -77,7 +78,7 @@ def apply_for_patient_assistance(request): args = serializer.validated_data.copy() - template_request = Template.make_application_for_participation(args) + template_request = Template.make_application_for_participation(args, request.session) template_id = Template.create(request.session, template_request) args["template_id"] = template_id @@ -88,3 +89,22 @@ def apply_for_patient_assistance(request): view_url = Envelope.get_view_url(request.session, envelope_id, args) return Response({"view_url": view_url, "client_id": os.environ.get('CLIENT_ID')}) + +@api_view(['GET']) +@error_processing +def get_extension_apps(request): + """ + Retrieve the list of extensions + """ + extensions = Extensions.get_extension_apps(request.session) + actual_extension_app_ids = [item["appId"] for item in extensions] + + required_ids = { + Extensions.get_address_extension_id(), + Extensions.get_phone_extension_id(), + Extensions.get_ssn_extension_id(), + } + + has_all_app_ids = all(app_id in actual_extension_app_ids for app_id in required_ids) + + return Response({"areExtensionsPresent": has_all_app_ids}) diff --git a/backend/docusign/ds_config.py b/backend/docusign/ds_config.py index 1dcf6d9..43998e4 100644 --- a/backend/docusign/ds_config.py +++ b/backend/docusign/ds_config.py @@ -6,8 +6,13 @@ TOKEN_EXPIRATION_IN_SECONDS = 3600 TOKEN_REPLACEMENT_IN_SECONDS = 10 * 60 -CODE_GRANT_SCOPES = ['signature', 'click.manage'] -PERMISSION_SCOPES = ['signature', 'impersonation', 'click.manage'] +CODE_GRANT_SCOPES = ['signature', 'click.manage', 'adm_store_unified_repo_read'] +PERMISSION_SCOPES = ['signature', 'impersonation', 'click.manage', 'adm_store_unified_repo_read'] + +CONNECTED_FIELDS_BASE_HOST = 'https://api-d.docusign.com' +PHONE_EXTENSION_ID = "d16f398f-8b9a-4f94-b37c-af6f9c910c04" +SMARTY_EXTENSION_ID = "04bfc1ae-1ba0-42d0-8c02-264417a7b234" +SSN_EXTENSION_ID = "b1adf7ad-38fd-44ba-85f5-38ea87f4af23" DS_RETURN_URL = os.environ.get('REACT_APP_DS_RETURN_URL') DS_AUTH_SERVER = os.environ.get('DS_AUTH_SERVER') diff --git a/backend/docusign/extensions.py b/backend/docusign/extensions.py new file mode 100644 index 0000000..e6cb0ef --- /dev/null +++ b/backend/docusign/extensions.py @@ -0,0 +1,66 @@ +import requests +from docusign.ds_config import SMARTY_EXTENSION_ID, PHONE_EXTENSION_ID, SSN_EXTENSION_ID, CONNECTED_FIELDS_BASE_HOST + +class Extensions: + @staticmethod + def get_extension_apps(session): + headers = { + "Authorization": "Bearer " + session['access_token'], + "Accept": "application/json", + "Content-Type": "application/json" + } + + url = f"{CONNECTED_FIELDS_BASE_HOST}/v1/accounts/{session['account_id']}/connected-fields/tab-groups" + response = requests.get(url, headers=headers) + + return response.json() + + @staticmethod + def get_address_extension_id(): + return SMARTY_EXTENSION_ID + + @staticmethod + def get_phone_extension_id(): + return PHONE_EXTENSION_ID + + @staticmethod + def get_ssn_extension_id(): + return SSN_EXTENSION_ID + + @staticmethod + def get_extension_by_app_id(objects, app_id): + return next((obj for obj in objects if obj["appId"] == app_id), None) + + @staticmethod + def extract_verification_data(selected_app_id, tab): + extension_data = tab["extensionData"] + + return { + "app_id": selected_app_id, + "extension_group_id": extension_data["extensionGroupId"] if "extensionGroupId" in extension_data else "", + "publisher_name": extension_data["publisherName"] if "publisherName" in extension_data else "", + "application_name": extension_data["applicationName"] if "applicationName" in extension_data else "", + "action_name": extension_data["actionName"] if "actionName" in extension_data else "", + "action_input_key": extension_data["actionInputKey"] if "actionInputKey" in extension_data else "", + "action_contract": extension_data["actionContract"] if "actionContract" in extension_data else "", + "extension_name": extension_data["extensionName"] if "extensionName" in extension_data else "", + "extension_contract": extension_data["extensionContract"] if "extensionContract" in extension_data else "", + "required_for_extension": extension_data["requiredForExtension"] if "requiredForExtension" in extension_data else "", + "tab_label": tab["tabLabel"], + } + + @staticmethod + def get_extension_data(verification_data): + return { + "extensionGroupId": verification_data["extension_group_id"], + "publisherName": verification_data["publisher_name"], + "applicationId": verification_data["app_id"], + "applicationName": verification_data["application_name"], + "actionName": verification_data["action_name"], + "actionContract": verification_data["action_contract"], + "extensionName": verification_data["extension_name"], + "extensionContract": verification_data["extension_contract"], + "requiredForExtension": verification_data["required_for_extension"], + "actionInputKey": verification_data["action_input_key"], + "extensionPolicy": 'MustVerifyToSign' + } \ No newline at end of file diff --git a/backend/docusign/pdf/Application_for_participation_v2.pdf b/backend/docusign/pdf/Application_for_participation_v2.pdf index d62f0f9853fb9adbdca8ed458aec270822ca16c5..f13b32c583923356db66bbfbcdf17faecec2a752 100644 GIT binary patch delta 3726 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 a!AZn%X&f$(BMltuh{nKy=H~8RMBwlJYO+%R delta 5827 zcmcIoOOM>f5rzU7MkX@+ItCKju$4#%mh1=FB!PvHeQ4KfYo*1`#zMTA81yW)VX`}1 z%y1nk7v6FXFu%db4+xALa`GXkAg7#jFapHLR!>i^(Ss zi|Hp9>9owp`9(eQnY=ad52nS*i@mbOr|RaNgbPMH|^oBPbaS4_{-GdnA`4sC7FqDe2{eDm_+gKC-=#WQ>Pd z$&O7P{G;qq$^5-@Ox$e#ZZ&`RMg#xOqjGmxre#K^yNXd#4hQ~j%;KuKO&CS%5GnC@ zib+WmcLEmFPj^BVHg}ro<}PAUZ9z8s*-4&m7LV95MM>k>Q6jPP7S_rpYG|h!f!np(I9V$%@9-q|lz?ahOmbv-6LbkkaE3S3)5$>Bak5 ze)_YLX)Qg$69wCPhm8r*(wYEQkdK?DIM;$C^1=|Su{Ge=(*f5CaZ$?SJcL1^;wu^Apdo*^!KlZOR?_AoomK|zl}>EPcCynTNPPR=K%>}G3* z!QmwcKRc%ga~#WmK)^kYqR5Mtvftt-U0XA{ntj zVFe^d=qQOsEELYn5%SEALr&`kf%4)Y(o{?KUUqxevt*RH&e+G!7D!mE7|oAE7Hg*B z1iN#%97ljjyW$AR+YQR9jncNwD0pas1^a_>yW z#R;XN%i!Akb*;m%6vc*o1K+|B>*)If|ET!UB!^AL1bOUoMc3Tc=l}e%3*GO1{Mm=| zpT2ReYPtt4YHQL(=;lg#AOEJGc8Dg=66SW1q> z3J#dYcorauWYGY!=~nYAZ@%pwbP0m8={q({Pd;^*S%8hUdQRw7-z@fkF@_2N_zc*b zawkNEq4q>5RbUVm5rV1~P7ysv%q*xn;G%fwNe2o1c7JuiA2GsI&|=CW-@%q$q(7Yg;weHl}%cwzgTEomiMbe>M#;;h`rZ6<~xA+rUhCi9nxf z`(K0th(sKPPW!7^BtkC+b3cPyM+>OK& z5@cGXB^f3g;b>8}^87CexMjRmFVpO(yVX!@8wFy)aO4hSOeTxX4^6H^B!Jd*nW}4= zwl~I|sIUed&TQXA^*LP>(z)raExxTY1n=&#<0L>{)$(P;A`Ri~z&u7*kK3+TgIn1> zsBIqGrfIjbykR#aKY{a(f83_}hI!YMRvp$|RZ81cKW1C25N}PKc18N6kwHsMc4Z+w z8|((_o-{hH4|ejvEpaRD=70LT3-(vP{_DvK&XQJ3I7hHxa69g{!vZfw02L|V08oee zBCTr$@beLX4j_M_bU^?z@FLRDEdhXgpo>-9CjcWwtnjUXh?NxGR~gI%rKfxskKSeY zg_>>k4GCQ|SNhkrvJqZ?k2@`kCl&$S9kg&*G38iMgI1A)We6CRxC88`eR4=NzUb z498M-``bGsPrC7rJUc;BFH@X%>z)V zO2o2=LBhL=-W^t2^2G(? zC;@`{^EelHL0=q`!IFbq)y`BsuS1}6!B_=)(QB@OoW*%M4v=lSoG74wE{sVYLqpkl z*q?KWPZ!XVbalp3gi9fmQlR4c^8yiacR_j@h$vh&i$SL0Tl232LJ#7 diff --git a/backend/docusign/template.py b/backend/docusign/template.py index 7be7493..df0b929 100644 --- a/backend/docusign/template.py +++ b/backend/docusign/template.py @@ -79,12 +79,12 @@ def make_covid_19_consent_form(cls, args): return cls.make_request(template_name, document, signer) @classmethod - def make_application_for_participation(cls, args): + def make_application_for_participation(cls, args, session): """ Make template_request for application_for_participation endpoint """ template_name = "ApplicationForParticipationTemplate" document = create_document("Application_for_participation_v2.pdf") - signer = make_application_for_participation_signer(args) + signer = make_application_for_participation_signer(args, session) return cls.make_request(template_name, document, signer) diff --git a/backend/docusign/templates/make_application_for_participation_signer.py b/backend/docusign/templates/make_application_for_participation_signer.py index 3de4b41..66fe0b5 100644 --- a/backend/docusign/templates/make_application_for_participation_signer.py +++ b/backend/docusign/templates/make_application_for_participation_signer.py @@ -1,8 +1,127 @@ from datetime import datetime from typing import Optional from docusign_esign import Signer, Checkbox, Text, TabGroup, Number, Tabs, SignHere, DateSigned, SignerAttachment +from docusign.extensions import Extensions -def make_application_for_participation_signer(args): +def make_tabs_with_extensions(session): + extensions = Extensions.get_extension_apps(session) + phone_extension = Extensions.get_extension_by_app_id(extensions, Extensions.get_phone_extension_id()) + address_extension = Extensions.get_extension_by_app_id(extensions, Extensions.get_address_extension_id()) + ssn_extension = Extensions.get_extension_by_app_id(extensions, Extensions.get_ssn_extension_id()) + + # define phone tab + for tab in (t for t in phone_extension["tabs"] if "VerifyPhoneNumberInput[0].phoneNumber" in t["tabLabel"]): + verification_data = Extensions.extract_verification_data(phone_extension["appId"], tab) + extension_data = Extensions.get_extension_data(verification_data) + phone = Number( + name=verification_data["application_name"], + tab_label=verification_data["tab_label"], + tooltip=verification_data["action_input_key"], + document_id='1', + page_number='1', + anchor_string='/phone/', + anchor_units='pixels', + required=True, + locked=False, + anchor_y_offset='-5', + extension_data=extension_data + ) + + # define address tabs + address_fields = { + "address": "VerifyPostalAddressInput[0].street1", + "state": "VerifyPostalAddressInput[0].subdivision", + "city": "VerifyPostalAddressInput[0].locality", + "zip": "VerifyPostalAddressInput[0].postalCode", + } + address_fields_width = { + "address": "320", + "state": "30", + "city": "150", + "zip": "60", + } + address_tabs = [] + for field, label_pattern in address_fields.items(): + for tab in (t for t in address_extension["tabs"] if label_pattern in t["tabLabel"]): + verification_data = Extensions.extract_verification_data(address_extension["appId"], tab) + extension_data = Extensions.get_extension_data(verification_data) + address_tabs.append( + Text( + name=verification_data["application_name"], + tab_label=verification_data["tab_label"], + tooltip=verification_data["action_input_key"], + document_id="1", + page_number="1", + anchor_string=f"/{field}/", + anchor_units="pixels", + required=True, + locked=False, + anchor_y_offset="-5", + anchor_x_offset="-5", + width=address_fields_width[field], + extension_data=extension_data, + ) + ) + + # define SSN tab + for tab in (t for t in ssn_extension["tabs"] if "VerifySocialSecurityNumberInput[0].socialSecurityNumber" in t["tabLabel"]): + verification_data = Extensions.extract_verification_data(ssn_extension["appId"], tab) + extension_data = Extensions.get_extension_data(verification_data) + ssn = Text( + name=verification_data["application_name"], + tab_label=verification_data["tab_label"], + tooltip=verification_data["action_input_key"], + document_id='1', + page_number='1', + anchor_string='/ssn/', + anchor_units='pixels', + required=True, + locked=False, + anchor_y_offset='-5', + extension_data=extension_data + ) + + return phone, address_tabs, ssn + +def make_tabs_without_extensions(): + ssn = Text( + document_id="1", page_number="1", anchor_string='/ssn/', anchor_units='pixels', + required="true", tab_label="ssn", height="12", width="150", + validation_message="SSN format: xxx-xx-xxxx", + validation_pattern="^[0-9]{3}-[0-9]{2}-[0-9]{4}$" + ) + + address_tabs = [] + address_tabs.append(Text( + document_id="1", page_number="1", anchor_string="/address/", anchor_units="pixels", + required="true", tab_label="address", height="12", width="320", anchor_y_offset="-5", + anchor_x_offset="-5" + )) + address_tabs.append(Text( + document_id="1", page_number="1", anchor_string="/city/", anchor_units="pixels", + required="true", tab_label="city", height="12", width="150", anchor_y_offset="-5", + anchor_x_offset="-5" + )) + address_tabs.append(Text( + document_id="1", page_number="1", anchor_string="/state/", anchor_units="pixels", + required="true", tab_label="state", height="12", width="30", anchor_y_offset="-5", + anchor_x_offset="-5" + )) + address_tabs.append(Text( + document_id="1", page_number="1", anchor_string="/zip/", anchor_units="pixels", + required="true", tab_label="zip", height="12", width="60", anchor_y_offset="-5", + anchor_x_offset="-5" + )) + + phone = Number( + document_id="1", page_number="1", anchor_string="/phone/", anchor_units="pixels", + required="true", tab_label="phone1", height="12", width="150", anchor_y_offset="-5", + anchor_x_offset="-5" + ) + + return phone, address_tabs, ssn + +def make_application_for_participation_signer(args, session): """ Create signer and fields using absolute positioning Add the tabs model to the signer @@ -24,34 +143,6 @@ def make_application_for_participation_signer(args): document_id="1", page_number="1", x_position="231", y_position="168", required="true", tab_label="last_name", height="12", width="60" ) - ssn = Text( - document_id="1", page_number="1", x_position="399", y_position="168", - required="true", tab_label="ssn", height="12", width="150", - validation_message="SSN format: xxx-xx-xxxx", - validation_pattern="^[0-9]{3}-[0-9]{2}-[0-9]{4}$" - ) - - address = Text( - document_id="1", page_number="1", x_position="47", y_position="211", - required="true", tab_label="address", height="12", width="320" - ) - city = Text( - document_id="1", page_number="1", x_position="47", y_position="251", - required="true", tab_label="city", height="12", width="150" - ) - state = Text( - document_id="1", page_number="1", x_position="228", y_position="251", - required="true", tab_label="state", height="12", width="30" - ) - zip = Text( - document_id="1", page_number="1", x_position="287", y_position="251", - required="true", tab_label="zip", height="12", width="60" - ) - - phone = Number( - document_id="1", page_number="1", x_position="399", y_position="210", - required="true", tab_label="phone1", height="12", width="150" - ) family_size = Number( document_id="1", page_number="1", x_position="399", y_position="252", @@ -435,13 +526,14 @@ def make_application_for_participation_signer(args): document_id="1", page_number="2", x_position="456", y_position="587" ) + phone, address_tabs, ssn = make_tabs_without_extensions() if args.get("useWithoutExtension", False) else make_tabs_with_extensions(session) + # The Tabs object requires arrays of the different field/tab types signer.tabs = Tabs( - text_tabs=[last_name, first_name, ssn, address, city, state, - zip, individual_assets, family_assets, asset1, asset2, asset3, + text_tabs=[last_name, first_name, ssn, individual_assets, family_assets, asset1, asset2, asset3, asset4, asset5, asset6, asset_total, last_12, income1, income2, income3, income4, income5, income6, income7, income8, income9, - income10, income11, income12, income13, income_total], + income10, income11, income12, income13, income_total, *address_tabs], number_tabs=[phone, family_size], checkbox_tabs=[ check1, check2, check3, check4, check5, check6, diff --git a/frontend/public/locales/en/Common.json b/frontend/public/locales/en/Common.json index d1c2e95..54e154c 100644 --- a/frontend/public/locales/en/Common.json +++ b/frontend/public/locales/en/Common.json @@ -5,5 +5,9 @@ "GitHubLink": "https://github.com/docusign/sample-app-healthcare-python", "AboutLinkText": "About", "LogInText": "Log in", - "LogOutText": "Log out" + "LogOutText": "Log out", + "DownloadExtensionsButton": "Download Extensions and try again", + "DownloadExtensionsHeader": "Extensions Missing", + "ContinueWithoutExtensionsButton": "Continue Without Extensions", + "DownloadExtensionsMessage": "Extension apps not installed for this account. Either go to App Center to install them and try again, or you can continue to use this without the Data Validation apps.

Links to extension apps:
- SSN and FEIN Verification (for SSN verification)
- Vonage (for phone verification)
- Smarty (for address verification)" } diff --git a/frontend/src/api/healthcareAPI.js b/frontend/src/api/healthcareAPI.js index b1cfa82..38bae92 100644 --- a/frontend/src/api/healthcareAPI.js +++ b/frontend/src/api/healthcareAPI.js @@ -16,3 +16,17 @@ export async function sendRequest(request, urlPath) { handleError(error); } } + +export async function getExtensions(urlPath) { + try { + const response = await axios.get( + process.env.REACT_APP_API_BASE_URL + urlPath, + { + withCredentials: true + } + ); + return handleResponse(response); + } catch (error) { + handleError(error); + } +} diff --git a/frontend/src/assets/scss/components/_seeMore.scss b/frontend/src/assets/scss/components/_seeMore.scss index 1fa8e5d..6811fb5 100644 --- a/frontend/src/assets/scss/components/_seeMore.scss +++ b/frontend/src/assets/scss/components/_seeMore.scss @@ -22,4 +22,7 @@ .bs h5 { font-size: 1rem; font-weight: 700; +} +.container .row { + align-items: flex-start; } \ No newline at end of file diff --git a/frontend/src/components/extensionModal.js b/frontend/src/components/extensionModal.js new file mode 100644 index 0000000..b00d6e1 --- /dev/null +++ b/frontend/src/components/extensionModal.js @@ -0,0 +1,35 @@ +import { useTranslation } from "react-i18next"; +import { Modal, Button, Container } from "react-bootstrap"; + +const ExtensionsModal = ({ show, onDownloadExtensions, onHide, title, message }) => { + const { t } = useTranslation("Common"); + + const handleContinue = () => { + onHide(); + }; + + return ( + + + {title} + + + + +

+ + + + + + + + + ); +}; + +export default ExtensionsModal; diff --git a/frontend/src/components/header.js b/frontend/src/components/header.js index 079e718..872d112 100644 --- a/frontend/src/components/header.js +++ b/frontend/src/components/header.js @@ -27,8 +27,8 @@ const Header = () => {