adobe-to-docusign-migrator/src/create_adobe_template.py

174 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
create_adobe_template.py
------------------------
Creates "Paul Adobe Template" in Adobe Sign by:
1. Using the exact field positions from the downloaded David Tag Demo Form
2. Adding 4 extra fields (Number, Email, Company, Title) in available gaps
Usage:
python3 src/create_adobe_template.py
"""
import json
import os
import sys
from dotenv import load_dotenv
load_dotenv()
sys.path.insert(0, os.path.dirname(__file__))
from adobe_api import adobe_api_post_multipart, adobe_api_post_json, adobe_api_put_json
PDF_PATH = os.path.join(
os.path.dirname(__file__), "..",
"downloads", "David Tag Demo Form__CBJCHBCA", "Tag Demo Form_docx_pdf"
)
FIELDS_JSON_PATH = os.path.join(
os.path.dirname(__file__), "..",
"downloads", "David Tag Demo Form__CBJCHBCA", "form_fields.json"
)
TEMPLATE_NAME = "Paul Adobe Template"
# Keys the Adobe Sign API accepts when writing form fields.
# We strip server-generated metadata (origin, signerIndex, font/border styling).
ALLOWED_KEYS = {
"name", "inputType", "contentType", "validation", "validationData",
"required", "readOnly", "locations", "assignee",
"hiddenOptions", "visibleOptions", "defaultValue",
"masked", "maskingText", "calculated", "urlOverridable",
"minLength", "maxLength", "minValue", "maxValue",
"validationErrMsg", "currency", "conditionalAction",
"radioCheckType",
}
# Extra fields that cover types not present in the original David Tag form:
# - NUMBER validation (TEXT_FIELD / DATA / NUMBER)
# - SIGNER_EMAIL auto-fill
# - COMPANY auto-fill
# - TITLE auto-fill
#
# Placed in the two clear vertical gaps in the original layout:
# Gap A: y=375432 (between checkboxes and Initials 1), right side (left=350)
# Gap B: y=513582 (between Date of Signing 1 and Signature block), left side (left=106)
EXTRA_FIELDS = [
{
"name": "Company",
"inputType": "TEXT_FIELD", "contentType": "COMPANY", "validation": "NONE",
"required": False, "readOnly": False,
"locations": [{"pageNumber": 1, "top": 378, "left": 350, "width": 150, "height": 24}],
"assignee": "recipient0",
},
{
"name": "Title",
"inputType": "TEXT_FIELD", "contentType": "TITLE", "validation": "NONE",
"required": False, "readOnly": False,
"locations": [{"pageNumber": 1, "top": 410, "left": 350, "width": 150, "height": 24}],
"assignee": "recipient0",
},
{
"name": "Number Field",
"inputType": "TEXT_FIELD", "contentType": "DATA", "validation": "NUMBER",
"required": False, "readOnly": False,
"locations": [{"pageNumber": 1, "top": 516, "left": 106, "width": 150, "height": 24}],
"assignee": "recipient0",
},
{
"name": "Recipient Email",
"inputType": "TEXT_FIELD", "contentType": "SIGNER_EMAIL", "validation": "NONE",
"required": False, "readOnly": True,
"locations": [{"pageNumber": 1, "top": 548, "left": 106, "width": 175, "height": 24}],
"assignee": "recipient0",
},
]
def clean_field(f):
"""Strip server-only keys, keep only what the write API accepts."""
out = {k: v for k, v in f.items() if k in ALLOWED_KEYS}
out.setdefault("validation", "NONE")
out.setdefault("required", False)
out.setdefault("readOnly", False)
out.setdefault("masked", False)
out.setdefault("maskingText", "*")
out.setdefault("calculated", False)
out.setdefault("urlOverridable", False)
out.setdefault("minLength", -1)
out.setdefault("maxLength", -1)
out.setdefault("minValue", -1.0)
out.setdefault("maxValue", -1.0)
out.setdefault("validationErrMsg", "")
out.setdefault("conditionalAction", {"anyOrAll": "ANY", "action": "SHOW"})
return out
def load_source_fields():
with open(FIELDS_JSON_PATH) as f:
data = json.load(f)
fields = [clean_field(field) for field in data["fields"]]
groups = data.get("formFieldGroups", [])
print(f" Loaded {len(fields)} fields from David Tag Demo Form download")
return fields, groups
def upload_transient_doc():
print(f"Uploading PDF: {PDF_PATH}")
with open(PDF_PATH, "rb") as f:
files = {"File": ("Tag Demo Form.pdf", f, "application/pdf")}
result = adobe_api_post_multipart("transientDocuments", files=files)
doc_id = result["transientDocumentId"]
print(f" Transient document ID: {doc_id}")
return doc_id
def create_library_doc(transient_id):
print(f"Creating library document '{TEMPLATE_NAME}'...")
body = {
"fileInfos": [{"transientDocumentId": transient_id}],
"name": TEMPLATE_NAME,
"templateTypes": ["DOCUMENT", "FORM_FIELD_LAYER"],
"sharingMode": "USER",
"state": "ACTIVE",
}
result = adobe_api_post_json("libraryDocuments", body)
lib_id = result["id"]
print(f" Library document ID: {lib_id}")
return lib_id
def put_form_fields(lib_id, source_fields, groups):
all_fields = source_fields + [clean_field(f) for f in EXTRA_FIELDS]
print(f"Writing {len(all_fields)} fields ({len(source_fields)} original + {len(EXTRA_FIELDS)} extra)...")
body = {"fields": all_fields}
if groups:
body["formFieldGroups"] = groups
result = adobe_api_put_json(f"libraryDocuments/{lib_id}/formFields", body)
saved = len(result.get("fields", []))
print(f" {saved} fields saved.")
for field in result.get("fields", []):
print(f" {field['inputType']:15} {field.get('contentType',''):20} '{field['name']}'")
return result
def main():
if not os.path.exists(PDF_PATH):
print(f"ERROR: PDF not found at {PDF_PATH}")
sys.exit(1)
if not os.path.exists(FIELDS_JSON_PATH):
print(f"ERROR: form_fields.json not found at {FIELDS_JSON_PATH}")
sys.exit(1)
source_fields, groups = load_source_fields()
transient_id = upload_transient_doc()
lib_id = create_library_doc(transient_id)
put_form_fields(lib_id, source_fields, groups)
print(f"\nDone. Template '{TEMPLATE_NAME}' created (ID: {lib_id})")
print("Note: If Company/Title fields need their contentType set, do so in Adobe Sign UI.")
if __name__ == "__main__":
main()