diff --git a/README.md b/README.md index 329733f..95c4138 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,16 @@ It downloads templates via the Adobe Sign API, converts them to DocuSign format, ## What it does 1. **Authenticates** with Adobe Sign via OAuth (one-time browser flow, tokens saved to `.env`) -2. **Downloads** all visible library templates — PDF, metadata, and form field definitions +2. **Downloads** templates — PDF, metadata, and form field definitions 3. **Converts** each template to a DocuSign `envelopeTemplate` JSON, mapping all field types, coordinates, and recipient roles -4. **Uploads** the converted template to DocuSign +4. **Authenticates** with DocuSign via JWT grant (one-time browser consent, then fully automated) +5. **Uploads** the converted template to DocuSign via the REST API --- ## Prerequisites - Python 3.10+ -- Node.js 18+ (for the DocuSign upload CLI — uses the `esign-direct` package) - An Adobe Sign OAuth app (EU2 shard) with scopes: `library_read:self library_write:self user_read:self` - A DocuSign developer account with an integration key and RSA keypair @@ -32,33 +32,56 @@ pip install -r requirements.txt **2. Create a `.env` file** in the project root (never commit this): ``` +# Adobe Sign ADOBE_CLIENT_ID=your-adobe-client-id ADOBE_CLIENT_SECRET=your-adobe-client-secret -ADOBE_REDIRECT_URI=https://localhost:8080/callback -ADOBE_BASE_URL=https://api.eu2.adobesign.com/api/rest/v6/ +# DocuSign +DOCUSIGN_CLIENT_ID=your-integration-key +DOCUSIGN_CLIENT_SECRET=your-client-secret +DOCUSIGN_USER_ID=your-docusign-user-guid DOCUSIGN_ACCOUNT_ID=your-docusign-account-id -DOCUSIGN_INTEGRATION_KEY=your-integration-key -DOCUSIGN_BASE_URL=https://demo.docusign.net/restapi DOCUSIGN_PRIVATE_KEY_PATH=/path/to/private.key -DOCUSIGN_USER_ID=your-docusign-user-id +DOCUSIGN_AUTH_SERVER=account-d.docusign.com +DOCUSIGN_BASE_URL=https://demo.docusign.net/restapi +DOCUSIGN_REDIRECT_URI=http://localhost:8080/callback ``` +Use `account-d.docusign.com` and `https://demo.docusign.net/restapi` for sandbox. +For production replace with `account.docusign.com` and your account's base URL (e.g. `https://na3.docusign.net/restapi`). + **3. Authenticate with Adobe Sign** (one-time): ```bash python3 src/auth_adobe.py ``` -This opens a browser. After authorizing, paste the redirect URL back into the terminal. Tokens are saved to `.env` and auto-refreshed on subsequent runs. +Opens a browser. After authorizing, paste the redirect URL back into the terminal. +Tokens are saved to `.env` and auto-refreshed on subsequent runs. + +**4. Grant consent for DocuSign** (one-time per user): +```bash +python3 src/docusign_auth.py --consent +``` +Opens a browser for the DocuSign OAuth consent screen. After approving, paste the +redirect URL back into the terminal. This grants the `impersonation` scope required +for JWT grant. After this runs once, all subsequent API calls use JWT automatically — +no further browser interaction needed. --- ## Running a migration -**Download all templates from Adobe Sign:** +**List available templates in Adobe Sign:** ```bash -python3 src/download_templates.py +python3 src/download_templates.py list ``` -Downloads to `downloads/__/` — one folder per template containing `metadata.json`, `form_fields.json`, `documents.json`, and the PDF. + +**Download templates:** +```bash +python3 src/download_templates.py download # all templates +python3 src/download_templates.py download "Template Name" # one specific template +``` +Downloads to `downloads/__/` — one folder per template containing +`metadata.json`, `form_fields.json`, `documents.json`, and the PDF. **Convert a downloaded template to DocuSign format:** ```bash @@ -68,24 +91,28 @@ Writes DocuSign template JSONs to `migration-output//docusign-tem **Upload to DocuSign:** ```bash -node packages/esign-direct/build/cli.js templates create --file migration-output//docusign-template.json +python3 src/upload_docusign_template.py --file migration-output//docusign-template.json ``` -**Or run the full pipeline end-to-end for a specific template:** +**Or run the full pipeline end-to-end:** ```bash -python3 src/migrate_paul_template.py +python3 src/migrate_template.py --list # show available templates +python3 src/migrate_template.py --template "Template Name" # download → convert → upload +python3 src/migrate_template.py --template "Template Name" --skip-upload # convert only ``` -(Edit the `TEMPLATE_NAME` constant at the top of the script to target a different template. If multiple templates share the same name, the most recently modified one is used.) +If multiple templates share the same name, the most recently modified one is used. --- ## Field type mapping -See [field-mapping.md](field-mapping.md) for the full Adobe Sign → DocuSign tab type table, including edge cases and known gaps. +See [field-mapping.md](field-mapping.md) for the full Adobe Sign → DocuSign tab type table, +including edge cases and known gaps. ## Known API quirks and bugs -See [tests/PLATFORM-QUIRKS.md](tests/PLATFORM-QUIRKS.md) for documented platform bugs, unexpected API behaviors, and the fixes applied. +See [tests/PLATFORM-QUIRKS.md](tests/PLATFORM-QUIRKS.md) for documented platform bugs, +unexpected API behaviors, and the fixes applied. --- @@ -93,16 +120,21 @@ See [tests/PLATFORM-QUIRKS.md](tests/PLATFORM-QUIRKS.md) for documented platform ``` src/ - auth_adobe.py # One-time OAuth flow for Adobe Sign - adobe_api.py # Adobe Sign API client (auto token refresh) - download_templates.py # Download all templates from Adobe Sign - compose_docusign_template.py # Core conversion logic: Adobe → DocuSign JSON - migrate_paul_template.py # End-to-end runner (download → convert → upload) + auth_adobe.py # One-time OAuth flow for Adobe Sign + adobe_api.py # Adobe Sign API client (auto token refresh) + download_templates.py # List and download templates from Adobe Sign + compose_docusign_template.py # Core conversion: Adobe Sign → DocuSign JSON + docusign_auth.py # DocuSign JWT auth + one-time consent flow + upload_docusign_template.py # Upload a template JSON to DocuSign REST API + migrate_template.py # End-to-end runner (download → convert → upload) + create_adobe_template.py # Utility: create a test template in Adobe Sign + generate_pdfs.py # Utility: generate sample PDFs for offline testing -downloads/ # Downloaded Adobe Sign templates (gitignored) -migration-output/ # Converted DocuSign template JSONs (gitignored) +downloads/ # Downloaded Adobe Sign templates (gitignored) +migration-output/ # Converted DocuSign template JSONs (gitignored) +sample-templates/ # JSON fixtures for offline testing (PDFs gitignored) -field-mapping.md # Field type mapping table + edge case log -tests/PLATFORM-QUIRKS.md # Known bugs and API quirks -requirements.txt # Python dependencies +field-mapping.md # Field type mapping table + edge case log +tests/PLATFORM-QUIRKS.md # Known bugs and API quirks +requirements.txt # Python dependencies ``` diff --git a/field-mapping.md b/field-mapping.md index 693311e..615abc8 100644 --- a/field-mapping.md +++ b/field-mapping.md @@ -45,17 +45,52 @@ Source: Adobe Sign UI "Change field type" dropdown (all 15 types) + API field da - Parallel routing → Recipient routing order logic (sequential/parallel in DocuSign) - Conditional logic → Needs review, possible via DocuSign conditional tabs/logic -## Transform Formulas & Known Mapping Gaps +## Coordinate System -- **Coordinate translation:** If Adobe origin differs from DocuSign, map as: - `docusign_left = adobe_left // or apply offset, scale, etc.` -- **Radio group flattening:** Merge Adobe radios with `radioGroup` into DocuSign `radio` tab, setting all options explicitly. -- **Missing/ambiguous features:** - - DocuSign formulas (no mapping in Adobe Sign) — flag for manual rewrite - - Adobe advanced field validations (regex, custom scripts) — usually skipped or mapped to best-effort validation in DocuSign +Both Adobe Sign and DocuSign measure field positions from the **top-left corner** of the +page with y increasing downward — no coordinate inversion is needed. The conversion is +a direct pass-through: + +``` +docusign_xPosition = adobe_location.left +docusign_yPosition = adobe_location.top +docusign_width = max(adobe_location.width, MIN_TEXT_WIDTH) # 120pt floor +docusign_height = adobe_location.height +``` + +`MIN_TEXT_WIDTH = 120` is enforced on all text-entry tabs so they render as visible +input boxes rather than vertical lines (DocuSign renders zero-width tabs as a thin line). + +## Multi-location (Cloned) Fields + +Adobe Sign allows a single field definition to have multiple `locations` — the field +appears in several places on the document and all instances stay in sync. + +DocuSign replicates this via **tab merging**: multiple tabs that share the same +`tabLabel` automatically sync their value at signing time. The compose script emits +one tab per location for all data-entry tab types. Radio groups are handled differently +— each location is a separate radio button within the same group, not a clone. + +Tab types that support merging (one tab emitted per location): +`textTabs`, `numberTabs`, `dateTabs`, `dateSignedTabs`, `fullNameTabs`, +`emailAddressTabs`, `companyTabs`, `titleTabs`, `listTabs`, `checkboxTabs` + +Tab types that do not merge (only first location used or handled specially): +`signHereTabs`, `initialHereTabs` — each location is an independent signing action +`radioGroupTabs` — each location is one radio button within the group +`signerAttachmentTabs` — each location is an independent attachment request + +## Known Gaps + +- **Conditional logic**: Adobe Sign conditional show/hide rules are not mapped. + DocuSign supports conditional tabs but the logic structure differs — manual + rewrite required per template. +- **DocuSign formula fields**: No Adobe Sign equivalent — flag for manual rewrite. +- **Advanced field validation**: Adobe regex/custom script validation is not mapped; + best-effort via standard DocuSign validation types only. +- **Radio group flattening**: Adobe radios with `radioGroup` are merged into a single + DocuSign `radioGroupTabs` entry with per-location radio button coordinates. ## To Do -- Add table for conditional logic/rule mapping -- Add validation/transforms needed for field masks, validation, default values -- Document more edge cases as they are discovered in real samples -- Collect pain points/edge cases for high-fidelity migration +- Add conditional logic/rule mapping table +- Document field mask and default value transforms