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 0000000..d6c1ec6 Binary files /dev/null and b/adobe_api.py:Zone.Identifier differ diff --git a/adobe_auth.py:Zone.Identifier b/adobe_auth.py:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/adobe_auth.py:Zone.Identifier differ diff --git a/compose_docusign_template.py:Zone.Identifier b/compose_docusign_template.py:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/compose_docusign_template.py:Zone.Identifier differ diff --git a/docs/collab-diagram-log.md b/docs/collab-diagram-log.md new file mode 100644 index 0000000..53d1f58 --- /dev/null +++ b/docs/collab-diagram-log.md @@ -0,0 +1,8 @@ +# Collaboration/Diagram Log + +## Session: 2026-04-14 +- Architecture mermaid diagram block added to docs/architecture.md (data flow, main modules) +- All new regressions and results summarized in validation/ +- This log is ready for markdown, diagrams, and collaborative session notes as agent/project evolves. + +*Agent generated* \ No newline at end of file diff --git a/src/docusign_ingest_stub.py b/src/docusign_ingest_stub.py new file mode 100644 index 0000000..34c529a --- /dev/null +++ b/src/docusign_ingest_stub.py @@ -0,0 +1,26 @@ +# DocuSign Ingest Stub + +""" +Stub for DocuSign API ingest. +- Loads mapped onboarding template data. +- Would POST to DocuSign API if credentials configured +""" +import json +from pathlib import Path +from pprint import pprint + +def push_to_docusign(template): + print("(Stub) Would push the following template to DocuSign:") + pprint(template) + # Here you'd use requests to POST to DocuSign API endpoint + # For MVP: stop here and log intended payload + +if __name__ == "__main__": + mapped = json.loads(Path("../validation/onboarding-mapping-eval.md").read_text(errors="ignore").split('---')[-1]) if False else None + # For showcase: rerun mapping function from test_mapping.py + import sys + sys.path.append("../src") + from test_mapping import map_adobe_fields_to_docusign_tabs + fields = json.loads(Path("../sample-templates/onboarding-template-formfields.json").read_text()) + mapped = map_adobe_fields_to_docusign_tabs(fields) + push_to_docusign(mapped) diff --git a/src/migrate_paul_template.py b/src/migrate_paul_template.py new file mode 100644 index 0000000..3fc7f21 --- /dev/null +++ b/src/migrate_paul_template.py @@ -0,0 +1,176 @@ +""" +migrate_paul_template.py +------------------------ +End-to-end validation: downloads the most recently edited "Paul Adobe Template" +from Adobe Sign, converts it to a DocuSign template JSON, and uploads it. + +Usage: + python3 src/migrate_paul_template.py +""" + +import json +import os +import subprocess +import sys +from pathlib import Path + +from dotenv import load_dotenv + +load_dotenv() +sys.path.insert(0, os.path.dirname(__file__)) + +from adobe_api import adobe_api_get, adobe_api_get_bytes + +DOWNLOADS_DIR = Path(__file__).parent.parent / "downloads" +MIGRATION_OUTPUT_DIR = Path(__file__).parent.parent / "migration-output" +TEMPLATE_NAME = "Paul Adobe Template" + +CLI_PATH = Path(__file__).parent.parent.parent / "docusign-direct" / "packages" / "esign-direct" / "build" / "cli.js" + + +def safe_dirname(name): + return "".join(c if c.isalnum() or c in " -_" else "_" for c in name).strip() + + +def save_json(path, data): + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2) + + +def find_latest_paul_template(): + """Return the metadata dict for the most recently modified 'Paul Adobe Template'.""" + print("Fetching template list from Adobe Sign...") + result = adobe_api_get("libraryDocuments") + all_templates = result.get("libraryDocumentList", []) + print(f" Found {len(all_templates)} total template(s).") + + matches = [t for t in all_templates if t.get("name", "") == TEMPLATE_NAME] + if not matches: + print(f"ERROR: No template named '{TEMPLATE_NAME}' found.") + sys.exit(1) + + print(f" Found {len(matches)} template(s) named '{TEMPLATE_NAME}':") + for t in matches: + print(f" ID: {t['id']} modifiedDate: {t.get('modifiedDate', 'n/a')}") + + # Pick most recently modified + latest = max(matches, key=lambda t: t.get("modifiedDate", "")) + print(f"\n Using most recently modified: ID={latest['id']} modifiedDate={latest.get('modifiedDate', 'n/a')}") + return latest + + +def download_template(template): + template_id = template["id"] + template_name = template["name"] + dir_name = f"{safe_dirname(template_name)}__{template_id[:8]}" + out_dir = DOWNLOADS_DIR / dir_name + out_dir.mkdir(parents=True, exist_ok=True) + + print(f"\nDownloading template to downloads/{dir_name}/") + + # Full metadata + metadata = adobe_api_get(f"libraryDocuments/{template_id}") + save_json(out_dir / "metadata.json", metadata) + + # Form fields + try: + form_fields = adobe_api_get(f"libraryDocuments/{template_id}/formFields") + save_json(out_dir / "form_fields.json", form_fields) + field_count = len(form_fields.get("fields", [])) + print(f" {field_count} form fields saved.") + for f in form_fields.get("fields", []): + val = f.get("validation", "") + val_str = f"/ {val}" if val and val != "NONE" else "" + print(f" {f['inputType']:15} {f.get('contentType',''):20} {val_str:10} '{f['name']}'") + except Exception as e: + print(f" WARNING: Could not fetch form fields: {e}") + save_json(out_dir / "form_fields.json", {"error": str(e)}) + + # Document list + PDFs + docs = adobe_api_get(f"libraryDocuments/{template_id}/documents") + save_json(out_dir / "documents.json", docs) + + for doc in docs.get("documents", []): + doc_id = doc["id"] + doc_name = doc.get("name", doc_id) + if not doc_name.lower().endswith(".pdf"): + doc_name += ".pdf" + safe_name = safe_dirname(doc_name) + pdf_path = out_dir / safe_name + try: + pdf_bytes = adobe_api_get_bytes(f"libraryDocuments/{template_id}/documents/{doc_id}") + with open(pdf_path, "wb") as f: + f.write(pdf_bytes) + print(f" PDF saved ({len(pdf_bytes) // 1024}KB) → {safe_name}") + except Exception as e: + print(f" WARNING: Could not download PDF: {e}") + + return out_dir + + +def run_migration(template_dir: Path) -> 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