Draft for contract printing
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
96
back/app/contract/print/__init__.py
Normal file
96
back/app/contract/print/__init__.py
Normal 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)
|
||||||
30
back/app/contract/print/templates/content.html
Normal file
30
back/app/contract/print/templates/content.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
{% include 'styles.css' %}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Conditions générales & particuliè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>
|
||||||
12
back/app/contract/print/templates/footer.html
Normal file
12
back/app/contract/print/templates/footer.html
Normal 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>
|
||||||
48
back/app/contract/print/templates/frontpage.html
Normal file
48
back/app/contract/print/templates/frontpage.html
Normal 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 }} à {{ draft.location}}</p>
|
||||||
|
<p>Entre les soussigné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é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 true %} à {{ 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>{{ lawyer.firstname }} {{ lawyer.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>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
back/app/contract/print/templates/print.html
Normal file
69
back/app/contract/print/templates/print.html
Normal 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 }} à {{ draft.location}}</p>
|
||||||
|
<p>Entre les soussigné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é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 true %} à {{ 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>{{ lawyer.firstname }} {{ lawyer.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 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>
|
||||||
139
back/app/contract/print/templates/styles.css
Normal file
139
back/app/contract/print/templates/styles.css
Normal 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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
BIN
front/app/src/assets/logotransparent.png
Normal file
BIN
front/app/src/assets/logotransparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
front/app/src/assets/tampon.png
Normal file
BIN
front/app/src/assets/tampon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 287 KiB |
BIN
front/app/src/assets/watermark.png
Normal file
BIN
front/app/src/assets/watermark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
Reference in New Issue
Block a user