Adding chtlawfirm to the api
This commit is contained in:
156
api/rpk-api/firm/contract/print/__init__.py
Normal file
156
api/rpk-api/firm/contract/print/__init__.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import datetime
|
||||
import os
|
||||
import base64
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, Depends
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from weasyprint import HTML, CSS
|
||||
from weasyprint.text.fonts import FontConfiguration
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from firm.core.routes import get_tenant_db_cursor
|
||||
from firm.entity.models import Entity
|
||||
from firm.template.models import ProvisionTemplate
|
||||
from firm.contract.models import ContractDraft, Contract, ContractStatus, replace_variables_in_value
|
||||
|
||||
|
||||
async def build_model(model):
|
||||
parties = []
|
||||
for p in model.parties:
|
||||
party = {
|
||||
"entity": await Entity.get(p.entity_id),
|
||||
"part": p.part
|
||||
}
|
||||
if p.representative_id:
|
||||
party['representative'] = await Entity.get(p.representative_id)
|
||||
|
||||
parties.append(party)
|
||||
|
||||
model.parties = parties
|
||||
|
||||
provisions = []
|
||||
for p in model.provisions:
|
||||
if p.provision.type == "template":
|
||||
provision = await ProvisionTemplate.get(p.provision.provision_template_id)
|
||||
else:
|
||||
provision = p.provision
|
||||
|
||||
provision.title = replace_variables_in_value(model.variables, provision.title)
|
||||
provision.body = replace_variables_in_value(model.variables, provision.body)
|
||||
provisions.append(provision)
|
||||
|
||||
model.provisions = provisions
|
||||
|
||||
model = model.dict()
|
||||
model['location'] = "Los Santos, SA"
|
||||
model['date'] = datetime.date(1970, 1, 1)
|
||||
model['lawyer'] = {'entity_data': {
|
||||
"firstname": "prénom avocat",
|
||||
"lastname": "nom avocat",
|
||||
}}
|
||||
return model
|
||||
|
||||
|
||||
BASE_PATH = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
templates = Jinja2Templates(directory=str(BASE_PATH / "templates"))
|
||||
|
||||
|
||||
async def render_print(root_url, contract):
|
||||
template = templates.get_template("print.html")
|
||||
return template.render({
|
||||
"contract": contract,
|
||||
"root_url": root_url
|
||||
})
|
||||
|
||||
|
||||
async def render_css(root_url, contract):
|
||||
template = templates.get_template("styles.css")
|
||||
return template.render({
|
||||
"contract": contract,
|
||||
"root_url": root_url
|
||||
})
|
||||
|
||||
|
||||
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}'
|
||||
|
||||
|
||||
preview_router = APIRouter()
|
||||
@preview_router.get("/draft/{draft_id}", response_class=HTMLResponse, tags=["Contract Draft"])
|
||||
async def preview_draft(draft_id: str, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
draft = await build_model(await ContractDraft.get(db, draft_id))
|
||||
|
||||
return await render_print('', draft)
|
||||
|
||||
|
||||
@preview_router.get("/signature/{signature_id}", response_class=HTMLResponse, tags=["Signature"])
|
||||
async def preview_contract_by_signature(signature_id: UUID, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.find_by_signature_id(db, 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('', contract)
|
||||
|
||||
|
||||
@preview_router.get("/{contract_id}", response_class=HTMLResponse, tags=["Contract"])
|
||||
async def preview_contract(contract_id: str, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.get(db, contract_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('', contract)
|
||||
|
||||
|
||||
print_router = APIRouter()
|
||||
@print_router.get("/pdf/{contract_id}", response_class=FileResponse, tags=["Contract"])
|
||||
async def create_pdf(contract_id: str, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.get(db, 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")
|
||||
|
||||
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)
|
||||
|
||||
font_config = FontConfiguration()
|
||||
html = HTML(string=await render_print('http://nginx', contract))
|
||||
css = CSS(string=await render_css('http://nginx', contract), font_config=font_config)
|
||||
|
||||
html.write_pdf(contract_path, stylesheets=[css], font_config=font_config)
|
||||
|
||||
await contract.update_status(db, 'printed')
|
||||
|
||||
return FileResponse(
|
||||
contract_path,
|
||||
media_type="application/pdf",
|
||||
filename=contract.label)
|
||||
|
||||
|
||||
@print_router.get("/opengraph/{signature_id}", response_class=HTMLResponse, tags=["Signature"])
|
||||
async def get_signature_opengraph(signature_id: str, request: Request, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.find_by_signature_id(db, signature_id)
|
||||
signature = contract.get_signature(signature_id)
|
||||
template = templates.get_template("opengraph.html")
|
||||
|
||||
signatory = signature.representative.label if signature.representative else signature.entity.label
|
||||
|
||||
return template.render({
|
||||
"signatory": signatory,
|
||||
"title": contract.label,
|
||||
"origin_url": f"{request.url.scheme}://{request.url.hostname}"
|
||||
})
|
||||
10
api/rpk-api/firm/contract/print/templates/opengraph.html
Normal file
10
api/rpk-api/firm/contract/print/templates/opengraph.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="{{ title }}" />
|
||||
<meta property="og:description" content="Cette page est à la destination exclusive de {{ signatory }}
|
||||
Si vous n'êtes pas {{ signatory }}, veuillez fermer cette page immédiatement et surpprimer tous les liens en votre possession menant vers celle-ci.
|
||||
En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour usurpation d'identité et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.
|
||||
Le cabinet Cooper, Hillman & Toshi LLC" />
|
||||
<meta property="og:image" content="{{ origin_url }}/assets/logo.png" />
|
||||
</head>
|
||||
</html>
|
||||
76
api/rpk-api/firm/contract/print/templates/print.html
Normal file
76
api/rpk-api/firm/contract/print/templates/print.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
{% include 'styles.css' %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frontpage">
|
||||
<div id="front-page-header">
|
||||
<table><tr>
|
||||
<td><img id="top-logo" src="{{ root_url }}/assets/logotransparent.png" alt="Cooper, Hillman & Toshi logo"></td>
|
||||
<td id="office-info">Cooper, Hillman & Toshi LLP<br />6834 Innocence Boulevard<br />LOS SANTOS - SA<br /><a href="#">consulting@cht.law.com</a></td>
|
||||
</tr></table>
|
||||
<h1>{{ contract.title|upper }}</h1>
|
||||
</div>
|
||||
<div class="intro">
|
||||
<h2>Introduction</h2>
|
||||
<p>Le {{ contract.date.strftime('%d/%m/%Y') }} à {{ contract.location}}</p>
|
||||
<p>Entre les soussignés :</p>
|
||||
{% for party in contract.parties %}
|
||||
<div class="party">
|
||||
{% if not loop.first %}
|
||||
<p>ET</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% if party.entity.entity_data.type == "corporation" %}
|
||||
{{ party.entity.entity_data.title }} société de {{ party.entity.entity_data.activity }} enregistrée auprès du gouvernement de San Andreas et domiciliée au {{ party.entity.address }}{% if party.representative %}, représentée par {{ party.representative.entity_data.firstname }} {{ party.representative.entity_data.middlenames }} {{ party.representative.entity_data.lastname }}{% endif %}
|
||||
{% elif party.entity.entity_data.type == "individual" %}
|
||||
{{ party.entity.entity_data.firstname }} {{ party.entity.entity_data.middlenames }} {{ party.entity.entity_data.lastname }}
|
||||
{% if party.entity.entity_data.day_of_birth %} né le {{ party.entity.entity_data.day_of_birth.strftime('%d/%m/%Y') }} {% if party.entity.entity_data.place_of_birth %} à {{ party.entity.entity_data.place_of_birth }}{% endif %},{% endif %}
|
||||
{% if party.entity.address %} résidant à {{ party.entity.address }}, {% endif %}
|
||||
{% elif party.entity.entity_data.type == "institution" %}
|
||||
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>Ci-après dénommé <strong>{{ party.part|safe }}</strong></p>
|
||||
{% if loop.first %}
|
||||
<p class="part">d'une part</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<p class="part">d'autre part</p>
|
||||
<p>Sous la supervision légale de Maître <strong>{{ contract.lawyer.entity_data.firstname }} {{ contract.lawyer.entity_data.lastname }}</strong></p>
|
||||
<p>Il a été convenu l'exécution des prestations ci-dessous, conformément aux conditions générales et particulières ci-après:</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Conditions générales & particulières</h2>
|
||||
|
||||
{% for provision in contract.provisions %}
|
||||
<div class="provision">
|
||||
<h3>Article {{loop.index}} - {{ provision.title|safe }}</h3>
|
||||
<p>{{ provision.body|safe }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="footer">
|
||||
<hr/>
|
||||
<p>À {{ contract.location }} le {{ contract.date.strftime('%d/%m/%Y') }}</p>
|
||||
<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 }}:<br/>
|
||||
{% if party.signature_png %}
|
||||
<img src="{{ party.signature_png }}" />
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
147
api/rpk-api/firm/contract/print/templates/styles.css
Normal file
147
api/rpk-api/firm/contract/print/templates/styles.css
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Century Schoolbook';
|
||||
src: url('{{ root_url }}/assets/century-schoolbook/CenturySchoolbookRegular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Century Schoolbook";
|
||||
src: url("{{ root_url }}/assets/century-schoolbook/CenturySchoolbookBold.ttf");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Century Schoolbook";
|
||||
src: url("{{ root_url }}/assets/century-schoolbook/CenturySchoolbookItalic.ttf");
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Century Schoolbook";
|
||||
src: url("{{ root_url }}/assets/century-schoolbook/CenturySchoolbookBoldItalic.ttf");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@page{
|
||||
size: a4 portrait;
|
||||
margin: 2cm 2cm 2cm 2cm;
|
||||
counter-increment: page;
|
||||
@bottom-center {
|
||||
content: "© Cooper, Hillman & Toshi LLC - {{ contract.name }} - Page " counter(page) "/" counter(pages);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
background: url('{{ root_url }}/assets/watermark.png') no-repeat;
|
||||
background-size:contain;
|
||||
}
|
||||
|
||||
@page:first {
|
||||
background: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size:1em;
|
||||
width:17cm;
|
||||
font-family: 'Century Schoolbook';
|
||||
}
|
||||
|
||||
#front-page-header {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
#front-page-header table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#top-logo {
|
||||
width: 5cm;
|
||||
width: 5cm;
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
#office-info {
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
h1 {
|
||||
background: black;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 2.6em;
|
||||
padding: 13px 0;
|
||||
margin: 50px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
background: lightgrey;
|
||||
font-size: 1.6em;
|
||||
padding: 8px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.intro {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.party {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.part {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.content h3 {
|
||||
margin-top: 55px;
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.content p {
|
||||
page-break-inside: avoid;
|
||||
text-indent: 2em;
|
||||
}
|
||||
|
||||
.content td p {
|
||||
text-indent: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.provision {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.mention {
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.signatures {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.signatures td {
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
height: 3cm;
|
||||
}
|
||||
Reference in New Issue
Block a user