Contract Signing and contract printing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}'
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user