Draft for contract printing

This commit is contained in:
2023-02-17 19:46:22 +01:00
committed by Guillaume G
parent 749794a5f8
commit 74d12ec78e
17 changed files with 424 additions and 4 deletions

View File

@@ -1,6 +1,8 @@
FROM python:3.10 FROM python:3.10
# make the 'app' folder the current working directory RUN apt update && apt install -y xfonts-base xfonts-75dpi python3-pip python3-cffi python3-brotli libpango-1.0-0 libpangoft2-1.0-0 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /code WORKDIR /code
# copy both 'package.json' and 'package-lock.json' (if available) # copy both 'package.json' and 'package-lock.json' (if available)

View File

@@ -1,7 +1,7 @@
from fastapi import APIRouter from fastapi import APIRouter
from .routes_draft import draft_router from .routes_draft import draft_router
from .routes_print import print_router from .print import print_router
contract_router = APIRouter() contract_router = APIRouter()

View File

@@ -1,3 +1,4 @@
import datetime
from typing import List, Literal from typing import List, Literal
from enum import Enum from enum import Enum
@@ -28,6 +29,15 @@ class Party(BaseModel):
} }
) )
part: str part: str
representative_id: str = Field(
foreignKey={
"reference": {
"resource": "entity",
"schema": "Entity",
}
},
default=""
)
class ProvisionGenuine(BaseModel): class ProvisionGenuine(BaseModel):
@@ -64,3 +74,5 @@ class ContractDraft(CrudDocument):
format="dictionary", format="dictionary",
) )
status: ContractDraftStatus = Field(default=ContractDraftStatus.draft) status: ContractDraftStatus = Field(default=ContractDraftStatus.draft)
location: str = ""
date: datetime.date = datetime.date(1970, 1, 1)

View File

@@ -0,0 +1,96 @@
from fastapi import APIRouter
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 app.entity.models import Entity
from app.template.models import ProvisionTemplate
from ..schemas import ContractDraft
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":
provisions.append(await ProvisionTemplate.get(p.provision.provision_template_id))
else:
provisions.append(p.provision)
model.provisions = provisions
model.location = "Toulouse"
model.date = "01/01/1970"
return model
BASE_PATH = Path(__file__).resolve().parent
print_router = APIRouter()
templates = Jinja2Templates(directory=str(BASE_PATH / "templates"))
async def render_print(host, draft, lawyer):
template = templates.get_template("print.html")
return template.render({
"draft": draft,
"lawyer": lawyer,
"static_host": host
})
async def render_css(host, draft):
template = templates.get_template("styles.css")
return template.render({
"draft": draft,
"static_host": host
})
@print_router.get("/", response_class=HTMLResponse)
async def create() -> str:
draft = await build_model(await ContractDraft.get("63e92534aafed8b509f229c4"))
lawyer = {
"firstname": "Nathaniel",
"lastname": "Toshi",
}
return await render_print('localhost', draft, lawyer)
@print_router.get("/pdf", response_class=FileResponse)
async def create_pdf() -> str:
draft = await build_model(await ContractDraft.get("63e92534aafed8b509f229c4"))
lawyer = {
"firstname": "Nathaniel",
"lastname": "Toshi",
}
font_config = FontConfiguration()
html = HTML(string=await render_print('nginx', draft, lawyer))
css = CSS(string=await render_css('nginx', draft), font_config=font_config)
html.write_pdf('out.pdf', stylesheets=[css], font_config=font_config)
return FileResponse(
"out.pdf",
media_type="application/pdf",
filename=draft.name)

View File

@@ -0,0 +1,30 @@
<html>
<head>
<style>
{% include 'styles.css' %}
</style>
</head>
<body>
<div class="content">
<h2>Conditions g&eacute;n&eacute;rales & particuli&egrave;res</h2>
{% for provision in draft.provisions %}
<div class="provision">
<h3>Article {{loop.index}} - {{ provision.title|safe }}</h3>
<p>{{ provision.body|safe }}</p>
</div>
{% endfor %}
<div class="footer">
<hr/>
<p>À {{ draft.location }} le {{ draft.date }}</p>
<p class="mention">(Signatures précédée de la mention « Lu et approuvé »)</p>
<table class="signatures">
<tr>
{% for party in draft.parties %}<td>{{ party.part|safe }}:</td>{% endfor %}
</tr>
</table>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
{% include 'styles.css' %}
</style>
</head>
<body>
<hr/>
</body>
</html>

View File

@@ -0,0 +1,48 @@
<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="http://{{ static_host }}/assets/logotransparent.png"></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>
</div>
<h1>{{ draft.title|upper }}</h1>
<div class="intro">
<h2>Introduction</h2>
<p>Le {{ draft.date }} &agrave; {{ draft.location}}</p>
<p>Entre les soussign&eacute;s :</p>
{% for party in draft.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&eacute;t&eacute; de {{ party.entity.entity_data.activity }} enregistr&eacute;e aupr&egrave;s du gouvernement de San Andreas et domicili&eacute;e au {{ party.entity.address }}{% if party.representative %}, repr&eacute;sent&eacute;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&eacute; le {{ party.entity.entity_data.day_of_birth.strftime('%d/%m/%Y') }} {% if true %} &agrave; {{ party.entity.entity_data.place_of_birth }}{% endif %},{% endif %}
{% if party.entity.address %} r&eacute;sidant &agrave; {{ party.entity.address }}, {% endif %}
{% elif party.entity.entity_data.type == "institution" %}
{% endif %}
</p>
<p>Ci-apr&egrave;s d&eacute;nomm&eacute; <strong>{{ party.part|safe }}</strong></p>
{% if loop.first %}
<p class="part">d&apos;une part</p>
{% endif %}
</div>
{% endfor %}
<p class="part">d&apos;autre part</p>
<p>Sous la supervision l&eacute;gale de Ma&icirc;tre <strong>{{ lawyer.firstname }} {{ lawyer.lastname }}</strong></p>
<p>Il a &eacute;t&eacute; convenu l&apos;ex&eacute;cution des prestations ci-dessous, conform&eacute;ment aux conditions g&eacute;n&eacute;rales et particuli&egrave;res ci-apr&egrave;s:</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,69 @@
<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="http://{{ static_host }}/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>{{ draft.title|upper }}</h1>
</div>
<div class="intro">
<h2>Introduction</h2>
<p>Le {{ draft.date }} &agrave; {{ draft.location}}</p>
<p>Entre les soussign&eacute;s :</p>
{% for party in draft.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&eacute;t&eacute; de {{ party.entity.entity_data.activity }} enregistr&eacute;e aupr&egrave;s du gouvernement de San Andreas et domicili&eacute;e au {{ party.entity.address }}{% if party.representative %}, repr&eacute;sent&eacute;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&eacute; le {{ party.entity.entity_data.day_of_birth.strftime('%d/%m/%Y') }} {% if true %} &agrave; {{ party.entity.entity_data.place_of_birth }}{% endif %},{% endif %}
{% if party.entity.address %} r&eacute;sidant &agrave; {{ party.entity.address }}, {% endif %}
{% elif party.entity.entity_data.type == "institution" %}
{% endif %}
</p>
<p>Ci-apr&egrave;s d&eacute;nomm&eacute; <strong>{{ party.part|safe }}</strong></p>
{% if loop.first %}
<p class="part">d&apos;une part</p>
{% endif %}
</div>
{% endfor %}
<p class="part">d&apos;autre part</p>
<p>Sous la supervision l&eacute;gale de Ma&icirc;tre <strong>{{ lawyer.firstname }} {{ lawyer.lastname }}</strong></p>
<p>Il a &eacute;t&eacute; convenu l&apos;ex&eacute;cution des prestations ci-dessous, conform&eacute;ment aux conditions g&eacute;n&eacute;rales et particuli&egrave;res ci-apr&egrave;s:</p>
</div>
</div>
<div class="content">
<h2>Conditions g&eacute;n&eacute;rales & particuli&egrave;res</h2>
{% for provision in draft.provisions %}
<div class="provision">
<h3>Article {{loop.index}} - {{ provision.title|safe }}</h3>
<p>{{ provision.body|safe }}</p>
</div>
{% endfor %}
<div class="footer">
<hr/>
<p>À {{ draft.location }} le {{ draft.date }}</p>
<p class="mention">(Signatures précédée de la mention « Lu et approuvé »)</p>
<table class="signatures">
<tr>
{% for party in draft.parties %}<td>{{ party.part|safe }}:</td>{% endfor %}
</tr>
</table>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,139 @@
@font-face {
font-family: 'Century Schoolbook';
src: url('http://{{ static_host }}/assets/century-schoolbook/CenturySchoolbookRegular.ttf');
}
@font-face {
font-family: "Century Schoolbook";
src: url("http://{{ static_host }}/assets/century-schoolbook/CenturySchoolbookBold.ttf");
font-weight: bold;
}
@font-face {
font-family: "Century Schoolbook";
src: url("http://{{ static_host }}/assets/century-schoolbook/CenturySchoolbookItalic.ttf");
font-style: italic;
}
@font-face {
font-family: "Century Schoolbook";
src: url("http://{{ static_host }}/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 - {{ draft.name }} - Page " counter(page) "/" counter(pages);
font-size: 0.8em;
}
background: url('http://{{ static_host }}/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;
}
.provision {
page-break-inside: avoid;
}
p {
text-align: justify;
}
.footer {
margin-top: 30px;
page-break-inside: avoid;
}
.mention {
margin: 0px;
font-size: 0.9em;
}
.signatures {
width: 100%;
}
.signatures td {
vertical-align: top;
text-align: center;
height: 3cm;
}

View File

@@ -20,6 +20,7 @@ class Individual(EntityType):
lastname: Indexed(str) lastname: Indexed(str)
surnames: List[Indexed(str)] = [] surnames: List[Indexed(str)] = []
day_of_birth: date day_of_birth: date
place_of_birth: str = ""
@property @property

View File

@@ -17,6 +17,15 @@ class PartyTemplate(BaseModel):
default="" default=""
) )
part: str part: str
representative_id: str = Field(
foreignKey={
"reference": {
"resource": "entity",
"schema": "Entity",
}
},
default=""
)
def remove_html_tags(text): def remove_html_tags(text):

View File

@@ -4,3 +4,5 @@ fastapi_users_db_beanie==1.1.2
motor==3.1.1 motor==3.1.1
fastapi-paginate==0.1.0 fastapi-paginate==0.1.0
uvicorn uvicorn
jinja2
weasyprint

View File

@@ -21,4 +21,4 @@ RUN npm run build
EXPOSE 4200 EXPOSE 4200
CMD [ "npm", "run", "ng", "serve", "--", "--host", "0.0.0.0" ] CMD [ "npm", "run", "ng", "serve", "--", "--host", "0.0.0.0", "--disable-host-check" ]

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB