From 39982008d395562eef7e4dc8e297b448f742546f Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Mon, 20 Apr 2026 01:12:46 -0400 Subject: [PATCH] Add latest migration files and validation outputs --- .env-adobe | 50 +++++ adobe_api.py:Zone.Identifier | Bin 0 -> 25 bytes adobe_auth.py:Zone.Identifier | Bin 0 -> 25 bytes compose_docusign_template.py:Zone.Identifier | Bin 0 -> 25 bytes docs/collab-diagram-log.md | 8 + src/docusign_ingest_stub.py | 26 +++ src/migrate_paul_template.py | 176 ++++++++++++++++++ validation/compose-doc-template-complete.json | 143 ++++++++++++++ validation/compose-doc-template.json | 106 +++++++++++ 9 files changed, 509 insertions(+) create mode 100644 .env-adobe create mode 100644 adobe_api.py:Zone.Identifier create mode 100644 adobe_auth.py:Zone.Identifier create mode 100644 compose_docusign_template.py:Zone.Identifier create mode 100644 docs/collab-diagram-log.md create mode 100644 src/docusign_ingest_stub.py create mode 100644 src/migrate_paul_template.py create mode 100644 validation/compose-doc-template-complete.json create mode 100644 validation/compose-doc-template.json diff --git a/.env-adobe b/.env-adobe new file mode 100644 index 0000000..48e9229 --- /dev/null +++ b/.env-adobe @@ -0,0 +1,50 @@ +# Adobe Sign → DocuSign Migrator — Environment Variables +# Copy this file to .env and fill in your values. +# Never commit .env to version control. + +# ─── Adobe Sign ────────────────────────────────────────────────────────────── + +# OAuth app credentials from the Adobe Sign developer console +ADOBE_CLIENT_ID=ats-58a336e4-3dd5-466d-bc5d-ba341a012694 +ADOBE_CLIENT_SECRET=4c9SRsLNEBn953hzR1wa7wL5VzHnD5k_ +# Auto-written by src/adobe_auth.py after the one-time OAuth flow. +# Leave blank; they will be populated on first run. +ADOBE_ACCESS_TOKEN="3AAABLblqZhDON9k_91_RhgUlZbpHx6luaPSmu7_Jj1hrPdmqCQ6ciQDVJVVvLMr__4v161k3kZc6c2fYbxsl5tA1IbmQni9T" +ADOBE_REFRESH_TOKEN="3AAABLblqZhB6qLQOQ2H5oax-Ed3E6Nc0IqFupdB9UlKzAoWQ3Cb2u3lla4d6Vuquf9xHhGMfn68*" +ADOBE_SIGN_BASE_URL=https://api.eu2.adobesign.com/api/rest/v6 + +# ─── DocuSign ──────────────────────────────────────────────────────────────── + +# Integration key (client ID) from the DocuSign developer console +DOCUSIGN_CLIENT_ID=your-integration-key + +# Client secret — only needed for the one-time Auth Code Grant consent flow +DOCUSIGN_CLIENT_SECRET=your-client-secret + +# GUID of the DocuSign user to impersonate via JWT grant +# Found in the DocuSign admin UI under Users → user details +DOCUSIGN_USER_ID=your-docusign-user-guid + +# Account ID of the target DocuSign account +# Found in the DocuSign admin UI under Settings → Account Profile +DOCUSIGN_ACCOUNT_ID=your-docusign-account-id + +# Path to the RSA private key file used for JWT signing +# Generate a keypair in the DocuSign developer console and save the private key here +DOCUSIGN_PRIVATE_KEY_PATH=/path/to/private.key + +# OAuth auth server — use account-d.docusign.com for sandbox, account.docusign.com for production +DOCUSIGN_AUTH_SERVER=account-d.docusign.com + +# eSignature REST API base URL +# Sandbox: https://demo.docusign.net/restapi +# Production: https://na3.docusign.net/restapi (replace na3 with your shard) +DOCUSIGN_BASE_URL=https://demo.docusign.net/restapi + +# Redirect URI registered in your DocuSign app (used only during one-time consent flow) +DOCUSIGN_REDIRECT_URI=http://localhost:8080/callback + +# Auto-written by src/docusign_auth.py to cache the JWT access token. +# Leave blank; they will be populated automatically. +DOCUSIGN_ACCESS_TOKEN= +DOCUSIGN_TOKEN_EXPIRY= diff --git a/adobe_api.py:Zone.Identifier b/adobe_api.py:Zone.Identifier new file mode 100644 index 0000000000000000000000000000000000000000..d6c1ec682968c796b9f5e9e080cc6f674b57c766 GIT binary patch literal 25 dcma!!%Fjy;DN4*MPD?F{<>dl#JyUFr831@K2xdl#JyUFr831@K2xdl#JyUFr831@K2x Path: + """Convert the downloaded template folder to a DocuSign template JSON.""" + sys.path.insert(0, str(Path(__file__).parent)) + from compose_docusign_template import compose_template + + output_path = MIGRATION_OUTPUT_DIR / template_dir.name / "docusign-template.json" + print(f"\nRunning migration: {template_dir.name}") + template_dict, warnings = compose_template(str(template_dir), str(output_path)) + + print(f" Written: {output_path}") + if warnings: + print(" Warnings:") + for w in warnings: + print(f" WARNING: {w}") + + # Print tab summary + signers = template_dict.get("recipients", {}).get("signers", []) + for signer in signers: + tabs = signer.get("tabs", {}) + print(f" Tabs for '{signer['roleName']}':") + for tab_type, tab_list in sorted(tabs.items()): + for tab in tab_list: + label = tab.get("tabLabel") or tab.get("groupName", "?") + print(f" {tab_type:25} '{label}'") + + return output_path + + +def upload_to_docusign(output_path: Path): + if not CLI_PATH.exists(): + print(f"\nWARNING: DocuSign CLI not found at {CLI_PATH}") + print(f" Skipping upload. To upload manually:") + print(f" node {CLI_PATH} templates create --file {output_path}") + return + + print(f"\nUploading to DocuSign...") + # Use nvm's node (system node may be too old for the ?? operator in the CLI) + nvm_node = Path.home() / ".nvm" / "alias" / "default" + nvm_sh = Path.home() / ".nvm" / "nvm.sh" + cmd = f'source {nvm_sh} && node {CLI_PATH} templates create --file "{output_path}"' + result = subprocess.run( + cmd, shell=True, executable="/bin/bash", capture_output=True, text=True + ) + if result.returncode == 0: + print(" Upload successful.") + print(result.stdout) + else: + print(" Upload FAILED.") + print(result.stdout) + print(result.stderr) + + +def main(): + DOWNLOADS_DIR.mkdir(exist_ok=True) + MIGRATION_OUTPUT_DIR.mkdir(exist_ok=True) + + template = find_latest_paul_template() + template_dir = download_template(template) + output_path = run_migration(template_dir) + upload_to_docusign(output_path) + + print(f"\nDone. DocuSign template JSON: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/validation/compose-doc-template-complete.json b/validation/compose-doc-template-complete.json new file mode 100644 index 0000000..e64e3f0 --- /dev/null +++ b/validation/compose-doc-template-complete.json @@ -0,0 +1,143 @@ +{ + "name": "Employee Onboarding Form", + "description": "Migrated from Adobe Sign", + "documents": [ + { + "documentBase64": "JVBERi0xLjMKJZOMi54gUmVwb3J0TGFiIEdlbmVyYXRlZCBQREYgZG9jdW1lbnQgKG9wZW5zb3VyY2UpCjEgMCBvYmoKPDwKL0YxIDIgMCBSIC9GMiAzIDAgUiAvRjMgNSAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL0Jhc2VGb250IC9IZWx2ZXRpY2EgL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcgL05hbWUgL0YxIC9TdWJ0eXBlIC9UeXBlMSAvVHlwZSAvRm9udAo+PgplbmRvYmoKMyAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYS1Cb2xkIC9FbmNvZGluZyAvV2luQW5zaUVuY29kaW5nIC9OYW1lIC9GMiAvU3VidHlwZSAvVHlwZTEgL1R5cGUgL0ZvbnQKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0NvbnRlbnRzIDEwIDAgUiAvTWVkaWFCb3ggWyAwIDAgNjEyIDc5MiBdIC9QYXJlbnQgOSAwIFIgL1Jlc291cmNlcyA8PAovRm9udCAxIDAgUiAvUHJvY1NldCBbIC9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUkgXQo+PiAvUm90YXRlIDAgL1RyYW5zIDw8Cgo+PiAKICAvVHlwZSAvUGFnZQo+PgplbmRvYmoKNSAwIG9iago8PAovQmFzZUZvbnQgL0hlbHZldGljYS1PYmxpcXVlIC9FbmNvZGluZyAvV2luQW5zaUVuY29kaW5nIC9OYW1lIC9GMyAvU3VidHlwZSAvVHlwZTEgL1R5cGUgL0ZvbnQKPj4KZW5kb2JqCjYgMCBvYmoKPDwKL0NvbnRlbnRzIDExIDAgUiAvTWVkaWFCb3ggWyAwIDAgNjEyIDc5MiBdIC9QYXJlbnQgOSAwIFIgL1Jlc291cmNlcyA8PAovRm9udCAxIDAgUiAvUHJvY1NldCBbIC9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUkgXQo+PiAvUm90YXRlIDAgL1RyYW5zIDw8Cgo+PiAKICAvVHlwZSAvUGFnZQo+PgplbmRvYmoKNyAwIG9iago8PAovUGFnZU1vZGUgL1VzZU5vbmUgL1BhZ2VzIDkgMCBSIC9UeXBlIC9DYXRhbG9nCj4+CmVuZG9iago4IDAgb2JqCjw8Ci9BdXRob3IgKGFub255bW91cykgL0NyZWF0aW9uRGF0ZSAoRDoyMDI2MDQxNDIzNTM0OC0wNCcwMCcpIC9DcmVhdG9yIChhbm9ueW1vdXMpIC9LZXl3b3JkcyAoKSAvTW9kRGF0ZSAoRDoyMDI2MDQxNDIzNTM0OC0wNCcwMCcpIC9Qcm9kdWNlciAoUmVwb3J0TGFiIFBERiBMaWJyYXJ5IC0gXChvcGVuc291cmNlXCkpIAogIC9TdWJqZWN0ICh1bnNwZWNpZmllZCkgL1RpdGxlICh1bnRpdGxlZCkgL1RyYXBwZWQgL0ZhbHNlCj4+CmVuZG9iago5IDAgb2JqCjw8Ci9Db3VudCAyIC9LaWRzIFsgNCAwIFIgNiAwIFIgXSAvVHlwZSAvUGFnZXMKPj4KZW5kb2JqCjEwIDAgb2JqCjw8Ci9GaWx0ZXIgWyAvQVNDSUk4NURlY29kZSAvRmxhdGVEZWNvZGUgXSAvTGVuZ3RoIDc5NAo+PgpzdHJlYW0KR2F0biRfLEIjQSY7S1kmTUVRKWNML0I0IVtVa0lCVGxkL3IlL0E3YkhPdSNIZ3RGTWBTLSpdZVEwXklTTzpJc1kiQktdUV1Ebz9VOElSInJSSyNMVCFRRzslL2Y2OFBgPWVPXCFmRztYL0wjLHJebmU7WDdhZy1nKF9AMCYhUDxBZj08RVBwNjFRUlovQjBxNF1WIWFjPFYqLkg+YXI+cGZnVFVVRTcqPjchdSp0bjMjT0tFLWhXbycyU09dWm0pUzlXanA+UWxxIlYwZlgwbVwtSzZfUj41dWVZJXJIaElIXW1zW25YMlEiRm9Xa18/KmgvLVhQP2taJTpsY2t1NV5pLFNPYltMUzhrZiQ7QDVBQ21sbzhZM1hyRixjcGdjJ11Ya0wnODYmJFB1Yyxbbk1eRlktWSZqY0p0SyVpRiMlISZQJiciUU8oNlQ9XUc2N0YwXk89LyRIVTIjKWxWR0VqKFFsYXVhbVo0bkRWWHFpX0VJOU1FZTFhKV4tUzdvZmpnSjB0U0xBOlxJKFFzLjlkWlg9cSJcb19XIzEkVHMhM0pFVTFWWl1GSWRFP1BZaXJKOkxTIT1WOXMwXWtrWTArPmkkR0Q7V05VJCVWakBRMz5tQkg5JjRpU1VbcExdJ0NsVmUvNDRYREA0bnNmZzNcJkwnNV5wI28+XU1KQV9GNVlEbGs2Ni0vdSgqaz1vNiNZXitoMlkkUGBvRDNnTHAsREtIVFZibSxvU0FgWFpQNGZRPy1OZy1DSidLOS4zRCFkI3BMWT0pOmBtYFtFTzEsTWNHPmUlYFBKSFZaT1FCSTU0UjN0Zi8mSkovVGxZbz8pYy84JUghIUVpNWt0aktdIiRXKEJfL1FfKm5WXiMmazArTVwnLCVpJCY7IjZMYSZxaTVsSkFZZmJtaUdtL1JaXnVsbFIlLHA6SjAxMHQlX1RNc2RMQD9zT1FOT2wvaVVbVUVJajk/VEstLUdwb15nPmcwPmAqXCVyTW82VmY9W2xNOFRWOlpvYE8lWD9nV1M9IihrLmM4MHVQZ2wpZ0MhUzJDdGdBfj5lbmRzdHJlYW0KZW5kb2JqCjExIDAgb2JqCjw8Ci9GaWx0ZXIgWyAvQVNDSUk4NURlY29kZSAvRmxhdGVEZWNvZGUgXSAvTGVuZ3RoIDY3Ngo+PgpzdHJlYW0KR2F0VXA6TityUCZCNCpjTUtfYWdiNFZvU15xQ0kzLkAkcEEsLzA8YDJZSSxnbTlPNkEsLy9KPEdQVThxJkh0VD9iOFQoJnJGVTRiIkBFKG9HTCgmNFAyWUdSSjw3aGYmOS83K3FoRnVhaThUbkUvajldYHFmajRwOC1CNmZWMHRsI2pocmhfYGFPTnRiWCsxKW1kIyIsRyF1LCFUOFtOQjtEN1FKUk1wNXI3c0hTTz4nNjxoZzA4KjtPUE8oJGNxL0xjQi5dV3JgLS89Zk8hSTJnR0ZkJUtoL3EuVUllLVFgZTdfayxEZ042aUwiVmZEW04/Ii4nSyklNUZBRVZtY1I4b28jOCpRdU81UjRKcjUwKDtSZDtJYyJGYClPVkJtJ2JtPStkYScpN0E+Sm9oLm0oS09AWlNPYyg+N1Vja0VqUiwwKW5YZXVhKmxsRVFsTFNzaEBeKUJfQEFTKHVrRDonRTBGcVo9VCZdXFFVXjFJZ1BRbyQocFJFWEA8ZXFjTUZpVEBYXHAzUls6K2tmWDJrPTZEPHFQZ2VVOHVfUldnaURPPk03P3U/WXVBYi5bRiV0bVUjNT42bHUzVzUvIXFWLTJkR05eJHRgWipFbDReOkA4Y3FrNG8raiduSGpJU0sjNmpTJUBeW2xDYi1hVi1gWUVYMCs4dGxjKmJhSmRSYkAmTy8oN14zaEVWMmlbKS44Zihmbk5IU0YnL2Itcl1fU25JRlRWaCs4LjMoZnBsRiMmJVhBKV5XLSsqY05aY2JnY2RhaSkkQ0wwIys4blFqXk5LJTNWMkcpMzI1Y2xXRypTOT5vWjhOSE5OLlJEXVtcUlFcRUxjPFwncFhyS0ItT2xwKjViUlssWTQ6K2h1IzdKVi1WYnFgOEReZXIrYCN+PmVuZHN0cmVhbQplbmRvYmoKeHJlZgowIDEyCjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDA2MSAwMDAwMCBuIAowMDAwMDAwMTEyIDAwMDAwIG4gCjAwMDAwMDAyMTkgMDAwMDAgbiAKMDAwMDAwMDMzMSAwMDAwMCBuIAowMDAwMDAwNTI1IDAwMDAwIG4gCjAwMDAwMDA2NDAgMDAwMDAgbiAKMDAwMDAwMDgzNCAwMDAwMCBuIAowMDAwMDAwOTAyIDAwMDAwIG4gCjAwMDAwMDExNjMgMDAwMDAgbiAKMDAwMDAwMTIyOCAwMDAwMCBuIAowMDAwMDAyMTEzIDAwMDAwIG4gCnRyYWlsZXIKPDwKL0lEIApbPDA0YTQ1NjEzNWZkMGRhOGQ1OWIzNTNiMTliMjFhZGVlPjwwNGE0NTYxMzVmZDBkYThkNTliMzUzYjE5YjIxYWRlZT5dCiUgUmVwb3J0TGFiIGdlbmVyYXRlZCBQREYgZG9jdW1lbnQgLS0gZGlnZXN0IChvcGVuc291cmNlKQoKL0luZm8gOCAwIFIKL1Jvb3QgNyAwIFIKL1NpemUgMTIKPj4Kc3RhcnR4cmVmCjI4ODAKJSVFT0YK", + "name": "OnboardingForm.pdf", + "fileExtension": "pdf", + "documentId": "1" + } + ], + "recipients": { + "signers": [ + { + "roleName": "SIGNER", + "recipientId": "1", + "routingOrder": "1", + "tabs": { + "textTabs": [ + { + "tabLabel": "EmployeeName", + "required": "true", + "locked": "false", + "documentId": "1", + "pageNumber": "1", + "xPosition": "100", + "yPosition": "577" + } + ], + "dateSignedTabs": [ + { + "tabLabel": "StartDate", + "documentId": "1", + "pageNumber": "1", + "xPosition": "100", + "yPosition": "537" + } + ], + "listTabs": [ + { + "tabLabel": "Position", + "required": "true", + "documentId": "1", + "pageNumber": "1", + "xPosition": "100", + "yPosition": "497", + "listItems": [ + { + "text": "Manager", + "value": "Manager" + }, + { + "text": "Engineer", + "value": "Engineer" + }, + { + "text": "Tech", + "value": "Tech" + }, + { + "text": "HR", + "value": "HR" + } + ] + } + ], + "checkboxTabs": [ + { + "tabLabel": "Benefits", + "required": "false", + "documentId": "1", + "pageNumber": "1", + "xPosition": "100", + "yPosition": "460" + } + ], + "radioGroupTabs": [ + { + "groupName": "CommuteGroup", + "documentId": "1", + "radios": [ + { + "pageNumber": "1", + "xPosition": "100", + "yPosition": "420", + "value": "Car" + }, + { + "pageNumber": "1", + "xPosition": "140", + "yPosition": "420", + "value": "Transit" + }, + { + "pageNumber": "1", + "xPosition": "180", + "yPosition": "420", + "value": "Bike" + } + ] + } + ], + "signHereTabs": [ + { + "tabLabel": "EmployeeSignature", + "documentId": "1", + "pageNumber": "2", + "xPosition": "100", + "yPosition": "460" + } + ] + } + }, + { + "roleName": "APPROVER", + "recipientId": "2", + "routingOrder": "2", + "tabs": { + "textTabs": [ + { + "tabLabel": "HRNotes", + "required": "false", + "locked": "true", + "documentId": "1", + "pageNumber": "2", + "xPosition": "100", + "yPosition": "532" + } + ], + "signHereTabs": [ + { + "tabLabel": "HRSignature", + "documentId": "1", + "pageNumber": "2", + "xPosition": "300", + "yPosition": "460" + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/validation/compose-doc-template.json b/validation/compose-doc-template.json new file mode 100644 index 0000000..f2feeaf --- /dev/null +++ b/validation/compose-doc-template.json @@ -0,0 +1,106 @@ +{ + "name": "Employee Onboarding Form", + "description": "Migrated from Adobe Sign", + "documents": [ + { + "documentBase64": "...", + "name": "OnboardingForm.pdf", + "fileExtension": "pdf", + "documentId": "1" + } + ], + "recipients": { + "signers": [ + { + "roleName": "SIGNER", + "recipientId": "1", + "email": "employee@example.com", + "name": "Employee", + "tabs": [ + { + "tabLabel": "EmployeeName", + "tabType": "text", + "required": true, + "recipientIndex": 0, + "items": null, + "readOnly": false + }, + { + "tabLabel": "StartDate", + "tabType": "dateSigned", + "required": true, + "recipientIndex": 0, + "items": null, + "readOnly": false + }, + { + "tabLabel": "Position", + "tabType": "list", + "required": true, + "recipientIndex": 0, + "items": [ + "Manager", + "Engineer", + "Tech", + "HR" + ], + "readOnly": false + }, + { + "tabLabel": "Benefits", + "tabType": "checkbox", + "required": false, + "recipientIndex": 0, + "items": null, + "readOnly": false + }, + { + "tabLabel": "CommuteOption", + "tabType": "radio", + "required": false, + "recipientIndex": 0, + "items": [ + "Car", + "Transit", + "Bike" + ], + "readOnly": false + }, + { + "tabLabel": "EmployeeSignature", + "tabType": "signHere", + "required": true, + "recipientIndex": 0, + "items": null, + "readOnly": false + } + ] + }, + { + "roleName": "APPROVER", + "recipientId": "2", + "email": "hr@example.com", + "name": "HR Representative", + "tabs": [ + { + "tabLabel": "HRNotes", + "tabType": "text", + "required": false, + "recipientIndex": 1, + "items": null, + "readOnly": true + }, + { + "tabLabel": "HRSignature", + "tabType": "signHere", + "required": true, + "recipientIndex": 1, + "items": null, + "readOnly": false + } + ] + } + ] + }, + "status": "created" +} \ No newline at end of file