Add self-hosted e-signature workflow (canvas pad + PyMuPDF embedding)

- fl.signature.request: token-protected request linking an fl.document to a
  signer (res.partner). State machine: draft → prepared → sent → signed |
  declined | expired. Per-report _SIGNATURE_COORDS map plus DOC_TYPE_TO_REPORT
  for resolving the QWeb report template and the signature block rectangle
- action_prepare renders the QWeb PDF and stores it; action_send_to_signer
  emails a one-time link /familylaw/sign/<token> (256-bit token, 14-day expiry,
  hourly cron sweeps stale links)
- apply_signature decodes the canvas-pad PNG, embeds it at the page-relative
  rectangle via PyMuPDF, attaches the signed PDF to the fl.document, marks
  the document signed, and audits the signer IP + timestamp
- Public portal controller (/familylaw/sign/<token>): GET shows the unsigned
  PDF in an iframe + inline HTML5 canvas pad (no external JS, mouse + touch);
  POST submits the PNG; separate decline endpoint. Token+state checks gate
  every transition
- action_validate_pdfa on the signed request reuses the e-filing pikepdf
  check (markers + OutputIntents) so e-filing-bound docs can be re-validated
- Wiring: models/__init__, controllers/__init__, manifest entry, ACL for the
  request, Signature Requests menu under Cases, signature_request_ids on
  fl.case with a Filings-tab list, "Request Signature" header button, and a
  cron for expiry
- Note: Odoo Sign / DocuSign / HelloSign deliberately NOT used per CLAUDE.md
  spec (HIPAA + FL court e-signature compliance)
- Verified: throwaway-DB install passes cleanly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 20:43:09 +00:00
parent c1b5947b7c
commit 49358183e7
10 changed files with 742 additions and 0 deletions

View File

@@ -69,6 +69,8 @@ access_fl_efiling_submission_admin,fl.efiling.submission admin,model_fl_efiling_
access_fl_efiling_submission_paralegal,fl.efiling.submission paralegal,model_fl_efiling_submission,group_paralegal,1,1,1,0
access_fl_efiling_wizard_admin,fl.efiling.wizard admin,model_fl_efiling_wizard,group_admin,1,1,1,1
access_fl_efiling_wizard_paralegal,fl.efiling.wizard paralegal,model_fl_efiling_wizard,group_paralegal,1,1,1,1
access_fl_signature_request_admin,fl.signature.request admin,model_fl_signature_request,group_admin,1,1,1,1
access_fl_signature_request_paralegal,fl.signature.request paralegal,model_fl_signature_request,group_paralegal,1,1,1,0
access_fl_intake_wizard_admin,fl.intake.wizard admin,model_fl_intake_wizard,group_admin,1,1,1,1
access_fl_intake_wizard_paralegal,fl.intake.wizard paralegal,model_fl_intake_wizard,group_paralegal,1,1,1,1
access_fl_analysis_wizard_admin,fl.analysis.wizard admin,model_fl_analysis_wizard,group_admin,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
69 access_fl_efiling_submission_paralegal fl.efiling.submission paralegal model_fl_efiling_submission group_paralegal 1 1 1 0
70 access_fl_efiling_wizard_admin fl.efiling.wizard admin model_fl_efiling_wizard group_admin 1 1 1 1
71 access_fl_efiling_wizard_paralegal fl.efiling.wizard paralegal model_fl_efiling_wizard group_paralegal 1 1 1 1
72 access_fl_signature_request_admin fl.signature.request admin model_fl_signature_request group_admin 1 1 1 1
73 access_fl_signature_request_paralegal fl.signature.request paralegal model_fl_signature_request group_paralegal 1 1 1 0
74 access_fl_intake_wizard_admin fl.intake.wizard admin model_fl_intake_wizard group_admin 1 1 1 1
75 access_fl_intake_wizard_paralegal fl.intake.wizard paralegal model_fl_intake_wizard group_paralegal 1 1 1 1
76 access_fl_analysis_wizard_admin fl.analysis.wizard admin model_fl_analysis_wizard group_admin 1 1 1 1