---
# System prepended metadata

title: Project Documents and Signatures

---

# Project Documents and Signatures

## Requirements

1. Documents must be digitally signed;
2. There should be a completeness mechanism that can be used to determine when a document is fully signed, e.g. how many signatures are required and from whom and how many signatures have been collected so far and from whom. This is to support async signing flows;
3. Once the signed PDF document is generated the actual user signature images should be removed from the PDF (compliance?);
4. 

## `DocumentType`

Provides the mean to configure a given document type behavior, providing answers to the following questions:

 - Who is supposed to sign a document of this type (via its relationship with `SignaturePosition#signatory`)? e.g.:
   - `remodeling_consultant`;
   - `signatory_1`;
   - `signatory_2`;
   - `applicant_1`;
   - `applicant_2`;
   - `signatory_any`: I believe this may indicate that ANY signatory can fullfil the signature on cases where only one signatory is required to sign;
   - `applicant_any`: I believe this may indicate that ANY loan applicant can fullfil the signature on cases where only one applicant is required to sign;
 - How many signatures are required in order to completely sign the document? There are flags on the document type that can be used to determine if one or more signatures are required, like `requires_only_one_homeowner_signature`, however, it may be more interesting to rely on the signatory `*_any` to make that more configurable and decoupled from homeowners;
 - What data is required for the document (via its relationship with `SignaturePosition#signature_type`)? e.g.:
   - `signature`;
   - `date`;
   - `initials`;
   - `confirmation_text`;
 
 ## `SignaturePosition`

 Allow configuring which data annotation should be collected and applied to a given document type instance. A document type can have many `SignaturePosition`, and we can actually abstract this as being an `AnnotationPosition` since it can represent things other than signatures. Sach annotation position, has information about:

  - The type of the annotation, via `SignaturePosition#signature_type`:
    - `signature`;
    - `date`;
    - `initials`;
    - `confirmation_text`;
  - The `signatory` type whose the annotation belongs to, via `SignaturePosition#signatory`, and can have the following values:
    - `remodeling_consultant`;
    - `signatory_1`;
    - `signatory_2`;
    - `applicant_1`;
    - `applicant_2`;
    - `signatory_any`;
    - `applicant_any`;
  - The x, y coordinates on the document where the annotation should be placed, via `SignaturePosition#x_pos` and `SignaturePosition#y_pos`;
  - The page of the document where the annotation should be placed, via `SignaturePosition#page`;
  - The width and height of the document section where the annotation will be placed, via `SignaturePosition#width` and `SignaturePosition#height`;

## Signing a document

Signing a pdf document in Nitro in very simple terms is the simple act of merging an existing PDF document that was generated by Nitro via the `DocumentType` settings, with another PDF document holding nothing but the required image signatures positioned on the correct coordinates and sized accordingly based on the document's `DocumentType` settings.

There's already a fairly reusable [`Documents::SignDocumentService`](https://github.com/powerhome/nitro-web/blob/master/components/documents/app/services/documents/sign_document_service.rb#L7) in Nitro we may be able to leverage in order to centralize this siging process and make the process more reusable.

## Spike

Sketch the algorithm and building blocks needed to determine which document annotations (`SignaturePosition`) are required on a given document:

```rb=
sig_positions = project_document.document_type.signature_positions

people = project_document.people_required_to_sign

pulse_users = Pulse::User.find_by(person: people)
```

## Entity Relation

```ts=
type Signatory = "signatory_1" | "signatory_2" | "applicant_1" | "applicant_2" | "signatory_any" | "applicant_any"
type SignatureType = "signature" | "initials" | "date" | "completion_text"
```

```mermaid
classDiagram
    Document <|-- ProjectDocument
    Document <|-- StreamDocument
    DocumentType --> SignaturePosition : has_many
    Document --> DocumentSignature : has_many
    DocumentSignature --> Document : belongs_to
    DocumentSignature --> SignaturePosition : belongs_to
    
    class DocumentType {
        signatory: Signatory
        signature_type: SignatureType
    }

    class SignaturePosition {
        
    }
    
    class Document {
    
    }
    
    class StreamDocument {
    
    }

    class ProjectDocument {
        
    }

    class DocumentSignature {
    
    }
```

## NitroPdf

Nitro component that provides PDF manipulation capabilities, such as combining pdfs, adding annotations, etc...

```rb=
module NitroPdf
  class Annotation
    attr_reader :x, :y, :width, :height, :page, :content

    def initialize(x:, y:, width:, height:, page:, content:)
        @x = x
        @y = y
        @width = width
        @height = height
        @page = page
        @content = content
    end
    
    def with_font(font_name)
      previous_font_name = font
      font font_name
      yield
      font previous_font_name
    end
    
    def annotate(pdf)
        raise NotImplemented, "A subclass must implement this behavior"
    end
  end
  
  class TextAnnotation < Annotation
      attr_reader :font
      
      def initialize(x:, y:, width:, height:, page:, content:, font:)
        super
        @font = font
      end
      
      def annotate(combiner)
        temp_signature_pdf = ::Prawn::Document.new(margin: [0, 0, 0, 0], page_size: "LETTER")

        with_font(font) do
            temp_signature_pdf.draw_text content, at: [50,50],
                                                  width: width,
                                                  height: height,
                                                  size: 16
        end

        temp_combine_signature_pdf = CombinePDF.parse(temp_signature_pdf.render)
        combiner.pages[page - 1] << temp_combine_signature_pdf.pages[0]
      end
  end
  
  class ImageAnnotation < Annotation
      attr_reader :content
      
      def annotate(combiner)
        temp_signature_pdf = ::Prawn::Document.new(margin: [0, 0, 0, 0], page_size: "LETTER")

        temp_signature_pdf.image(content, at: [x, y],
                                          width: width,
                                          height: height)

        temp_combine_signature_pdf = CombinePDF.parse(temp_signature_pdf.render)
        combiner.pages[page - 1] << temp_combine_signature_pdf.pages[0]
      end
  end
  
  def self.annotate(file:, annotations:)
      combiner = CombinePDF.parse(file)

      annotations.each do |annotation|
          annotation.annotate(combiner)
      end
      
      combiner.to_pdf
  end
end

signature = NitroPdf::ImageAnnotation.new(
    content: signature_string_io_image,
    x: 123,
    y: 321,
    width: 200,
    height: 100,
    page: 2
)

initials = NitroPdf::ImageAnnotation.new(
    content: initials_string_io_image,
    x: 321,
    y: 321,
    width: 60,
    height: 100,
    page: 1
)

# Applying annotations to a document (where in this case annotations are signature images)
#
annotated_pdf = NitroPdf.annotate!(
  pdf: project_document.doc,
  annotations: [signature, initials]
)

# Combining multiple pdfs
#
combined_pdf = NitroPdf.combine(pdf1, pdf2, pdf3)


# Hiding sections of a pdf
#
geometry = NitroPdf::GeometryAnnotation.new(
    x: 123,
    y: 321,
    width: 123,
    height: 321,
    page: 2,
    color: "#fff"
)

annotated_pdf = NitroPdf.annotate!(
  pdf: project_document.doc,
  annotations: [geometry]
)
```

## Doc Sign Completeness

1. Completeness logic that determines whether a doc has been partially, fully signed or not signed at all.
1. Generating the final signed doc
