Digital Signature for Filament GitHub

#Integrating documents

How an app makes its own documents signable: route them to the right people, sign them in order, and keep the signed copies as a history.

The package owns everything that is the same for every document: routing, the checks before routing, signing, the document of record, and the wording. Your app supplies what only it knows. It does that by implementing contracts and binding classes in the container, not by copying routing code.

New in 2.0. Upgrading from 1.x? See UPGRADE-2.0.md, then the walkthrough of a real move.


#The five things an app writes

#WhatWhere
1A model that uses HasPdfTemplate, HasSignatories and, if it freezes who signs, SnapshotsSignatoriesapp/Models/
2A template (slots and view), registered in config('signature.templates')app/Pdf/
3A Blade view with data-signature-slot="<key>" on every signature spaceresources/views/
4A document definition extending AbstractSignableDocument, registered in config('signature.documents')app/Signatures/
5A service provider binding how a signatory becomes a login, if they aren't User rowsapp/Providers/

Then put RouteForSignaturesAction and the document-of-record actions on a page, and add the shared contract test.


#The injection map

Every app-specific part is a contract with a default. Override only what differs in your app.

ContractPackage defaultOverride it byNeeded when
Contracts\SignableDocument—a class in config('signature.documents')always, one per kind of document
Contracts\PdfTemplatePdf\BladePdfTemplatea class in config('signature.templates')always, one per kind of document
Contracts\SignatoryUserMapperSignatories\IdentityUserMapper$this->app->bind(...) in your provideryour signatories aren't User rows
Contracts\SignatoryDefaults—a class your definition takes in its constructorthe document freezes who signs it
Contracts\PreflightGuard / Contracts\RoutingGuard3 stock guards always runlisted by your definition, built by the containerthe document has its own preconditions
Pdf\Renderers\PdfRendererDomPdfRendererrenderer: on the templateyou need a fixed paper size or another renderer
Contracts\DocumentOfRecordGateDocumentOfRecord\DefaultDocumentOfRecordGate$this->app->bind(...) in your provideryour viewing or deletion rules differ
Wordinglang/en/routing.phplang/vendor/signature/…, or the definition's messages()always optional

Your one service provider is where the bindings live: the composition root. See Service provider.


#How routing runs

Text
RouteForSignaturesAction / routeDocument() / DocumentRouter::route('dtr', $subject, $context)
  │
  ├─ preflight guards ......................... nothing written yet
  ├─ BEGIN
  ├─ definition->open($subject, $context) ...... find or create the record; snapshot signatories
  ├─ already an open session? → already_routed, COMMIT
  ├─ definition guards, then the package's:
  │     RequiredSignatoriesAssigned → SignatureMarkersPresent → SignatoriesReady
  │     first refusal → ROLLBACK (no row left behind)
  ├─ open the signing session .................. freeze the PDF, one request per slot, notify
  └─ COMMIT → routed

Details in Signable documents. After routing, the record has a document of record: View and Download serve its stored PDF and its history instead of a fresh render. See Document of record.


#Pages in this section

PageWhat it covers
Service providerThe composition root: every binding, and the three shapes of signatory
Signable documentsSignableDocument, guards, DocumentRouter, RoutingResult and wording
Document of recordStates, versions, verify(), retention, the gate, the Filament UI
TestingSignableDocumentContract, the checks every document gets for free
Reference integrationThe Accomplishment Report in uplb-performance, end to end, as it runs
Walkthrough: 1.x to 2.0How the Accomplishment Report moved off its 1.x code, step by step, with the gotchas
Recipe: generated from a periodA document built from a person and a period (DTR)
Recipe: existing recordA document that already is a row (Travel Request)
Recipe: external signatoriesApprovers who live in another system