Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ It works with any DocType, standard or custom, that has a Signature field and a
- **Draw, type or upload a signature.** The app replaces Frappe's Signature control, in the desk and on the signing page, with a dialog that offers a drawing pad, a typed signature in one of four bundled script fonts (Pacifico, Dancing Script, Great Vibes, Caveat), or an uploaded image.
- **Send for eSign from the document.** Forms whose DocType has a published eSign web form get a **Send for eSign** button. Its dialog picks the sending email account, recipients, CC and BCC, an eSign email template, the web form and the print format, and previews the link. If the message does not already contain the link, a **Sign Document** button is added to the email. When **To** holds a single address, the link carries it as its `email` parameter.
- **Signed PDFs.** After signing, a background job renders the document in the web form's print format, attaches the PDF to the document as a private file and records its SHA-256 hash.
- **Audit trail in the timeline.** Each signing creates an eSign Communication with the signer's email, IP address and browser, the time, the web form, the print format, the fields signed and the PDF hash. It shows as a card in the document timeline with links to open and download the signed PDF.
- **Audit trail in the timeline.** Each signing creates an eSign Communication with the signer's name and email, IP address and browser, the time, the web form, the print format, the fields signed and the PDF hash. It shows as a card in the document timeline, "Document signed by" the signer, with links to open and download the signed PDF.
- **Guest signers by name.** A web form can name the fields its signer types their name and email into, and the audit trail records what the guest entered there, so a signer without an account is named in the timeline card and the notification email, not only by email.
- **Actions after signing.** A web form can set a field to a value (for example, **Status** to "Signed"), submit the document, and send a Notification with the signed PDF attached and the audit trail added to the email.
- **Completed documents.** Once every signature field on the form is filled, the signing page shows a **Document Signed** card instead of the form, with the signed PDF and a download button once the PDF exists.
- **Signing on a phone.** **Only allow Signing on Mobile** hides the form on large screens and shows a QR code that opens the same page on a phone.
Expand Down Expand Up @@ -73,12 +74,18 @@ Until the first release creates the `version-16` branch, install from `develop`
## Configuration

1. **The DocType.** Give the DocType you want signed a **Signature** field. To sign documents that are already submitted, tick **Allow on Submit** on the Signature field and on every other field the web form changes.
2. **The web form.** Create a **Web Form** for the DocType and tick **Enable eSign**. Add the Signature field, and any fields the signer should fill in, such as their name. To record the signer's email in the audit trail, add a Data field whose options are `Email`. Then set:
2. **The web form.** Create a **Web Form** for the DocType and tick **Enable eSign**. Add the Signature field, and any fields the signer should fill in, such as their name and email (Data fields; give the email field the options `Email`). Then set:
- **Print Format**, the web form's own field, which shows once eSign is enabled: the format of the preview and of the signed PDF.
- In the **eSign Settings** section:
- **Submit on Response**: submit the document when it is signed (submittable DocTypes only).
- **Set Property After Response** and **Value To Be Set**: set a Select, Link, Data or Text field to a value when the document is signed. Only those fields are offered, and saving the web form with any other field is refused.
- **Only allow Signing on Mobile**: show a QR code instead of the form on large screens.
- In the **Signer** section, which names the signer in the audit trail:
- **Signer Name Field**: the form field the signer types their name into. Its value is recorded as the signer's name.
- **Signer Email Field**: the form field the signer types their email into. A valid address in it is recorded as the signer's email, and the link's `email` parameter pre-fills it.

Both offer only the web form's own Data fields, and saving the web form with any other field is refused. Make the fields mandatory on the web form if every signer must give them. When they are not set, the signer is identified as before: by the link's `email` parameter or a Data field whose options are `Email`, and named after a matching User.

- In the **Notification Settings** section, **Send Notification on Response** and **Notification**: send an enabled Notification of this DocType whose event is **Custom** and whose channel is **Email**, with the signed PDF attached.

Enabling eSign also turns on **Allow Editing After Submit** for the web form, which the signing page needs.
Expand All @@ -105,14 +112,14 @@ The signed PDF and the timeline entry are written by background jobs on the `sho
- A signing link is `/<web form route>/<document>/edit?key=<share key>`, with optional `format` (print format) and `email` parameters. The web form's `get_context` checks the key, or without one whether the logged-in user may open the document through the web form or print it, and renders the app's signing page. The page loads the document preview from `get_print_html` into a shadow root, so the print format's styles do not touch the page.
- Signing posts the form to the app's own `accept` endpoint rather than Frappe's. It checks the share key or the user's permission, writes the form's fields, applies **Set Property After Response** and **Submit on Response**, and saves the document in one go.
- After the save commits, `attach_print_to_document` renders and attaches the PDF. It then queues `record_signature`, which creates the eSign Communication with the audit data as JSON, attaches a copy of the PDF to it (or, with cloud_storage, an association to the same file), and sends the configured Notification. The `additional_timeline_content` hook turns each eSign Communication into the timeline card.
- The signer's email comes from the link's `email` parameter, then from a Data field with options `Email` on the form, then from the logged-in user. Their name is looked up from a matching User. The IP address is read from `CF-Connecting-IP`, `X-Real-IP` or `X-Forwarded-For`, then the connection.
- The signer's email comes from the web form's **Signer Email Field** when one is set (the link's `email` parameter only pre-fills it and is not recorded on its own), otherwise from the link's `email` parameter, then from a Data field with options `Email` on the form; failing those, from the logged-in user. Their name comes from the **Signer Name Field** when one is set and filled, otherwise from the User with the signer's email. Both are recorded in the eSign Communication (as its sender and sender name too), shown on the timeline card and added to the notification email. The IP address is read from `CF-Connecting-IP`, `X-Real-IP` or `X-Forwarded-For`, then the connection.
- `download_signed_pdf` serves the latest signed PDF to a guest with the share key, and logs the download in the Access Log.

## Limitations

- **Not a digital signature.** The PDF is not signed with a certificate, and it carries no visual audit page. Its SHA-256 hash and the audit data are stored in Frappe, in the eSign Communication and the timeline, and in the notification email when one is sent.
- **A Send for eSign email to several recipients has no `email` parameter.** One link goes to everyone in **To**, so it names nobody, and the signer's email is only recorded when the form has an email field or the signer is logged in. Send to one recipient at a time, or use `get_esign_link` with `recipient_email` (for example in a Notification), to pre-fill it.
- **Guest signers have no name in the audit trail.** The signer's name is only looked up from a User with the signer's email, so the timeline card names a guest by email. A name field on the form is saved to the document but not to the audit record.
- **The signer's name and email are what they typed.** The Signer Name and Signer Email fields record what the signer entered on the form, not a verified identity: the app does not confirm the email address or check the name against anything. The share key in the link is what limits who can sign.
- **A form with no Signature field never shows as completed,** and a document whose signature fields are already filled opens as completed even if it was not signed through the app.
- **Only allow Signing on Mobile** goes by screen width, not by touch support: the form is hidden on large screens and shown on small ones.
- **Notifications** are sent only for an enabled Notification of the web form's DocType, with the **Custom** event and the **Email** channel. Others are skipped and an error is logged.
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ This app turns a Frappe web form into a signing page. You send the signer a link
- A **Send for eSign** button on the document, with a dialog that sends the link using an eSign email template.
- A signature dialog with three ways to sign: draw, type in one of four script fonts, or upload an image.
- A signed PDF in the web form's print format, attached to the document with its SHA-256 hash.
- An audit trail in the document timeline: the signer's email, IP address and browser, the time, the fields signed and the PDF hash.
- An audit trail in the document timeline: the signer's name and email, IP address and browser, the time, the fields signed and the PDF hash. Guest signers are named by what they type into the form's signer name and email fields.
- Optional actions after signing: set a field, submit the document, and send a notification with the signed PDF attached.

## Setting it up

1. Give the DocType a **Signature** field.
2. Create a **Web Form** for it, tick **Enable eSign**, and add the Signature field and anything else the signer fills in.
2. Create a **Web Form** for it, tick **Enable eSign**, and add the Signature field and anything else the signer fills in, such as their name and email. In the **Signer** section, pick those fields as the **Signer Name Field** and **Signer Email Field** so the audit trail names the signer.
3. Create an **Email Template** with **eSign Request** ticked.
4. Open a document and click **Send for eSign**.

Expand Down
Binary file modified docs/screenshots/audit-trail-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/audit-trail-light.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 24 additions & 4 deletions docs/screenshots/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
{
"name": "audit-trail",
"theme": "dark",
"alt": "A signed agreement's timeline: the eSign card with the PDF's SHA-256 hash, the signer's email, IP address, web form, signed fields and browser",
"alt": "A signed agreement's timeline: the eSign card naming the signer, with the PDF's SHA-256 hash, the signer's email, IP address, web form, signed fields and browser",
"featured": true,
"readme": "feature",
"width": 2880,
"height": 1800,
"sha256": "bd6a91fb9e16bac8bd6edecda59d01a635af43a9ca798777c459856bb4a6e7de"
"sha256": "98f589f2a6959029e303ac189efc2a188adab504c42521b4708ff032366ad624"
},
{
"name": "audit-trail",
"theme": "light",
"alt": "A signed agreement's timeline: the eSign card with the PDF's SHA-256 hash, the signer's email, IP address, web form, signed fields and browser",
"alt": "A signed agreement's timeline: the eSign card naming the signer, with the PDF's SHA-256 hash, the signer's email, IP address, web form, signed fields and browser",
"featured": true,
"readme": "feature",
"width": 2880,
"height": 1800,
"sha256": "dad94cc41f84272dd348684a0fad913c2d3f6331ba6a7a0e9e703084504eae16"
"sha256": "5efc1116a089533a39a51dbb8fa5ce0ba5765c8d8f2d26c42e023dd1d12e5eb8"
},
{
"name": "send-for-esign",
Expand Down Expand Up @@ -49,6 +49,26 @@
"height": 1800,
"sha256": "b61d5ba5433a8b3db74386a804ec5cb504a0a99aff61262176e6ac10248f8fdc"
},
{
"name": "signer-settings",
"theme": "dark",
"alt": "The eSign web form's settings: Submit on Response, Set Property After Response, and the Signer Name Field and Signer Email Field that name the form fields the audit trail records",
"featured": false,
"readme": "feature",
"width": 2880,
"height": 1800,
"sha256": "73c89ccda81ba499cc663a196e2207d29c82bef378399ea5867be9d269c04445"
},
{
"name": "signer-settings",
"theme": "light",
"alt": "The eSign web form's settings: Submit on Response, Set Property After Response, and the Signer Name Field and Signer Email Field that name the form fields the audit trail records",
"featured": false,
"readme": "feature",
"width": 2880,
"height": 1800,
"sha256": "c65e2399ef5c33db0982a7501a25966c6666530bb38dadc5949bd53b8706e3b0"
},
{
"name": "signing-page",
"theme": "light",
Expand Down
Binary file added docs/screenshots/signer-settings-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/signer-settings-light.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions esign_webforms/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
starting {{ frappe.utils.formatdate(start_date, "MMMM d, yyyy") }}, is ready to sign.</p>
<p>Use the button below to read it and sign it on any device.</p>"""

# The signing request and signer the signed agreement's audit record describes. The values
# The signing request and signer the signed agreement's audit record describes: a guest,
# named by what they typed into the web form's Signer Name and Signer Email fields. The values
# are the ones the signing page records (_collect_audit_data); 203.0.113.0/24 is a
# documentation range.
SIGNER_EMAIL = "dana.whitfield@example.com"
Expand Down Expand Up @@ -343,6 +344,8 @@ def _ensure_web_form() -> None:
"esign_submit_on_response": 1,
"esign_update_field": "status",
"esign_update_value": "Signed",
"esign_signer_name_field": "signer_name",
"esign_signer_email_field": "signer_email",
"introduction_text": "Read the agreement, then sign it below.",
"button_label": "Sign & Submit",
"success_title": "Agreement signed",
Expand Down Expand Up @@ -416,7 +419,7 @@ def _sign(name: str, today: datetime.date) -> None:
"ip_address": "203.0.113.42",
"user_agent": SIGNER_AGENT,
"signer_email": SIGNER_EMAIL,
"signer_name": None,
"signer_name": "Dana Whitfield",
"web_form": "Service Agreement",
"print_format": PRINT_FORMAT,
"signed_fields": ["Signature"],
Expand Down
46 changes: 44 additions & 2 deletions esign_webforms/esign/custom/web_form.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,52 @@
{
"depends_on": "eval:doc.esign_enabled==1",
"dt": "Web Form",
"fieldname": "esign_notification_section",
"fieldname": "esign_signer_section",
"fieldtype": "Section Break",
"insert_after": "esign_update_value",
"is_system_generated": 0,
"label": "Signer",
"module": "eSign",
"name": "Web Form-esign_signer_section"
},
{
"description": "The form field the signer types their name into. Its value is recorded as the signer's name in the audit trail.",
"dt": "Web Form",
"fieldname": "esign_signer_name_field",
"fieldtype": "Select",
"insert_after": "esign_signer_section",
"is_system_generated": 0,
"label": "Signer Name Field",
"module": "eSign",
"name": "Web Form-esign_signer_name_field"
},
{
"dt": "Web Form",
"fieldname": "esign_signer_column_break",
"fieldtype": "Column Break",
"insert_after": "esign_signer_name_field",
"is_system_generated": 0,
"module": "eSign",
"name": "Web Form-esign_signer_column_break"
},
{
"description": "The form field the signer types their email into. A valid address in it is recorded as the signer's email in the audit trail.",
"dt": "Web Form",
"fieldname": "esign_signer_email_field",
"fieldtype": "Select",
"insert_after": "esign_signer_column_break",
"is_system_generated": 0,
"label": "Signer Email Field",
"module": "eSign",
"name": "Web Form-esign_signer_email_field"
},
{
"depends_on": "eval:doc.esign_enabled==1",
"dt": "Web Form",
"fieldname": "esign_notification_section",
"fieldtype": "Section Break",
"insert_after": "esign_signer_email_field",
"is_system_generated": 0,
"label": "Notification Settings",
"module": "eSign",
"name": "Web Form-esign_notification_section"
Expand Down Expand Up @@ -122,4 +164,4 @@
}
],
"sync_on_migrate": 1
}
}
Loading
Loading