Contract Signing and contract printing

This commit is contained in:
2023-03-08 21:59:10 +01:00
parent eaa79c3541
commit 5605ee9497
10 changed files with 230 additions and 46 deletions

View File

@@ -1,11 +1,12 @@
import uuid
from fastapi import Depends, HTTPException
from fastapi import Depends, HTTPException, File, UploadFile
import shutil
from ..core.routes import get_crud_router
from .routes_draft import draft_router
from .print import print_router
from .models import Contract, ContractDraft, replace_variables_in_value
from .models import Contract, ContractDraft, Party, replace_variables_in_value
from .schemas import ContractCreate, ContractRead, ContractUpdate
from ..entity.models import Entity
@@ -72,10 +73,31 @@ async def update(id: str, contract_form: ContractUpdate, user=Depends(get_curren
@contract_router.get("/signature/{signature_id}", response_description="")
async def get_signature(signature_id: str) -> ContractRead:
raise HTTPException(status_code=500, detail="Not implemented")
async def get_signature(signature_id: str) -> Party:
contract = await Contract.find_by_signature_id(signature_id)
signature = contract.get_signature(signature_id)
return signature
@contract_router.post("/signature/{signature_id}", response_description="")
async def affix_signature(signature_id: str, signature_form: ContractCreate) -> ContractRead:
raise HTTPException(status_code=500, detail="Not implemented")
async def affix_signature(signature_id: str, signature_file: UploadFile = File(...)) -> bool:
contract = await Contract.find_by_signature_id(signature_id)
signature_index = contract.get_signature_index(signature_id)
signature = contract.parties[signature_index]
if signature.signature_affixed:
raise HTTPException(status_code=400, detail="Signature already affixed")
with open("media/signatures/{}.png".format(signature_id), "wb") as buffer:
shutil.copyfileobj(signature_file.file, buffer)
update_query = {"$set": {
'parties.{}.signature_affixed'.format(signature_index): True
}}
signature.signature_affixed = True
if contract.is_signed():
update_query["$set"]['status'] = 'signed'
await contract.update(update_query)
return True

View File

@@ -1,10 +1,9 @@
import uuid
import datetime
from typing import List, Literal
from enum import Enum
from pydantic import BaseModel, Field
from beanie.operators import ElemMatch
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
from ..entity.models import Entity
@@ -13,6 +12,7 @@ from ..entity.models import Entity
class ContractStatus(str, Enum):
published = 'published'
signed = 'signed'
printed = 'printed'
executed = 'executed'
@@ -54,6 +54,7 @@ class Party(BaseModel):
representative: Entity = None
signature_uuid: str
signature_affixed: bool = False
signature_png: str = None
class ProvisionGenuine(BaseModel):
@@ -127,6 +128,27 @@ class Contract(CrudDocument):
hour=0, minute=0, second=0)
}
@classmethod
def find_by_signature_id(cls, signature_id: str):
crit = ElemMatch(cls.parties, {"signature_uuid": signature_id})
return cls.find_one(crit)
def get_signature(self, signature_id: str):
for p in self.parties:
if p.signature_uuid == signature_id:
return p
def get_signature_index(self, signature_id: str):
for i, p in enumerate(self.parties):
if p.signature_uuid == signature_id:
return i
def is_signed(self):
for p in self.parties:
if not p.signature_affixed:
return False
return True
def replace_variables_in_value(variables, value: str):
for v in variables:

View File

@@ -1,11 +1,11 @@
import datetime
import os
import base64
from fastapi import APIRouter
from fastapi import APIRouter, HTTPException
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.templating import Jinja2Templates
from beanie.operators import ElemMatch
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
@@ -13,7 +13,7 @@ from pathlib import Path
from app.entity.models import Entity
from app.template.models import ProvisionTemplate
from ..models import ContractDraft, Contract, replace_variables_in_value
from ..models import ContractDraft, Contract, ContractStatus, replace_variables_in_value
async def build_model(model):
@@ -77,32 +77,55 @@ async def render_css(host, contract):
})
@print_router.get("/preview/draft/{draftId}", response_class=HTMLResponse)
async def preview_draft(draftId: str) -> str:
draft = await build_model(await ContractDraft.get(draftId))
@print_router.get("/preview/draft/{draft_id}", response_class=HTMLResponse)
async def preview_draft(draft_id: str) -> str:
draft = await build_model(await ContractDraft.get(draft_id))
return await render_print('localhost', draft)
@print_router.get("/preview/{signature_id}", response_class=HTMLResponse)
async def preview_contract(signature_id: str) -> str:
crit = ElemMatch(Contract.parties, {"signature_uuid": signature_id})
contract = await Contract.find_one(crit)
contract = await Contract.find_by_signature_id(signature_id)
for p in contract.parties:
if p.signature_affixed:
p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png')
return await render_print('localhost', contract)
@print_router.get("/pdf", response_class=FileResponse)
async def create_pdf() -> str:
draft = await build_model(await ContractDraft.get("63e92534aafed8b509f229c4"))
@print_router.get("/pdf/{contract_id}", response_class=FileResponse)
async def create_pdf(contract_id: str) -> str:
contract = await Contract.get(contract_id)
contract_path = "media/contracts/{}.pdf".format(contract_id)
if not os.path.isfile(contract_path):
if contract.status != ContractStatus.signed:
raise HTTPException(status_code=400, detail="Contract is not in a printable state")
font_config = FontConfiguration()
html = HTML(string=await render_print('nginx', draft))
css = CSS(string=await render_css('nginx', draft), font_config=font_config)
for p in contract.parties:
signature_path = f'media/signatures/{p.signature_uuid}.png'
p.signature_png = retrieve_signature_png(signature_path)
# os.remove(signature_path)
html.write_pdf('out.pdf', stylesheets=[css], font_config=font_config)
font_config = FontConfiguration()
html = HTML(string=await render_print('nginx', contract))
css = CSS(string=await render_css('nginx', contract), font_config=font_config)
html.write_pdf(contract_path, stylesheets=[css], font_config=font_config)
update_query = {"$set": {
'status': 'printed'
}}
await contract.update(update_query)
return FileResponse(
"out.pdf",
contract_path,
media_type="application/pdf",
filename=draft.name)
filename=contract.name)
def retrieve_signature_png(filepath):
with open(filepath, "rb") as f:
b_content = f.read()
base64_utf8_str = base64.b64encode(b_content).decode('utf-8')
ext = filepath.split('.')[-1]
return f'data:image/{ext};base64,{base64_utf8_str}'

View File

@@ -60,7 +60,14 @@
<p class="mention">(Signatures précédée de la mention « Lu et approuvé »)</p>
<table class="signatures">
<tr>
{% for party in contract.parties %}<td>{{ party.part|safe }}:</td>{% endfor %}
{% for party in contract.parties %}
<td>
{{ party.part|safe }}:<br/>
{% if party.signature_png %}
<img src="{{ party.signature_png }}" />
{% endif %}
</td>
{% endfor %}
</tr>
</table>
</div>