diff --git a/back/Dockerfile b/back/Dockerfile index f43f98d6..b1d70e26 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -1,6 +1,8 @@ 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 # copy both 'package.json' and 'package-lock.json' (if available) diff --git a/back/app/contract/__init__.py b/back/app/contract/__init__.py index 944807a6..a386e75f 100644 --- a/back/app/contract/__init__.py +++ b/back/app/contract/__init__.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from .routes_draft import draft_router -from .routes_print import print_router +from .print import print_router contract_router = APIRouter() diff --git a/back/app/contract/models.py b/back/app/contract/models.py index cfc78bc8..495b7c31 100644 --- a/back/app/contract/models.py +++ b/back/app/contract/models.py @@ -1,3 +1,4 @@ +import datetime from typing import List, Literal from enum import Enum @@ -28,6 +29,15 @@ class Party(BaseModel): } ) part: str + representative_id: str = Field( + foreignKey={ + "reference": { + "resource": "entity", + "schema": "Entity", + } + }, + default="" + ) class ProvisionGenuine(BaseModel): @@ -64,3 +74,5 @@ class ContractDraft(CrudDocument): format="dictionary", ) status: ContractDraftStatus = Field(default=ContractDraftStatus.draft) + location: str = "" + date: datetime.date = datetime.date(1970, 1, 1) diff --git a/back/app/contract/print/__init__.py b/back/app/contract/print/__init__.py new file mode 100644 index 00000000..c16f4969 --- /dev/null +++ b/back/app/contract/print/__init__.py @@ -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) diff --git a/back/app/contract/print/templates/content.html b/back/app/contract/print/templates/content.html new file mode 100644 index 00000000..c7fdff47 --- /dev/null +++ b/back/app/contract/print/templates/content.html @@ -0,0 +1,30 @@ + + + + + +
+

Conditions générales & particulières

+ + {% for provision in draft.provisions %} +
+

Article {{loop.index}} - {{ provision.title|safe }}

+

{{ provision.body|safe }}

+
+ {% endfor %} + + +
+ + \ No newline at end of file diff --git a/back/app/contract/print/templates/footer.html b/back/app/contract/print/templates/footer.html new file mode 100644 index 00000000..109e120f --- /dev/null +++ b/back/app/contract/print/templates/footer.html @@ -0,0 +1,12 @@ + + + + + + + +
+ + \ No newline at end of file diff --git a/back/app/contract/print/templates/frontpage.html b/back/app/contract/print/templates/frontpage.html new file mode 100644 index 00000000..4aecf4f4 --- /dev/null +++ b/back/app/contract/print/templates/frontpage.html @@ -0,0 +1,48 @@ + + + + + +
+
+ + + +
Cooper, Hillman & Toshi LLP
6834 Innocence Boulevard
LOS SANTOS - SA
consulting@cht.law.com
+
+

{{ draft.title|upper }}

+
+

Introduction

+

Le {{ draft.date }} à {{ draft.location}}

+

Entre les soussignés :

+ {% for party in draft.parties %} +
+ {% if not loop.first %} +

ET

+ {% endif %} +

+ {% 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 %} +

+

Ci-après dénommé {{ party.part|safe }}

+ {% if loop.first %} +

d'une part

+ {% endif %} +
+ {% endfor %} +

d'autre part

+

Sous la supervision légale de Maître {{ lawyer.firstname }} {{ lawyer.lastname }}

+

Il a été convenu l'exécution des prestations ci-dessous, conformément aux conditions générales et particulières ci-après:

+
+
+ + \ No newline at end of file diff --git a/back/app/contract/print/templates/print.html b/back/app/contract/print/templates/print.html new file mode 100644 index 00000000..697ac923 --- /dev/null +++ b/back/app/contract/print/templates/print.html @@ -0,0 +1,69 @@ + + + + + +
+
+ + + +
Cooper, Hillman & Toshi LLP
6834 Innocence Boulevard
LOS SANTOS - SA
consulting@cht.law.com
+

{{ draft.title|upper }}

+
+
+

Introduction

+

Le {{ draft.date }} à {{ draft.location}}

+

Entre les soussignés :

+ {% for party in draft.parties %} +
+ {% if not loop.first %} +

ET

+ {% endif %} +

+ {% 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 %} +

+

Ci-après dénommé {{ party.part|safe }}

+ {% if loop.first %} +

d'une part

+ {% endif %} +
+ {% endfor %} +

d'autre part

+

Sous la supervision légale de Maître {{ lawyer.firstname }} {{ lawyer.lastname }}

+

Il a été convenu l'exécution des prestations ci-dessous, conformément aux conditions générales et particulières ci-après:

+
+
+
+

Conditions générales & particulières

+ + {% for provision in draft.provisions %} +
+

Article {{loop.index}} - {{ provision.title|safe }}

+

{{ provision.body|safe }}

+
+ {% endfor %} + + +
+ + diff --git a/back/app/contract/print/templates/styles.css b/back/app/contract/print/templates/styles.css new file mode 100644 index 00000000..876ba814 --- /dev/null +++ b/back/app/contract/print/templates/styles.css @@ -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; +} \ No newline at end of file diff --git a/back/app/entity/models.py b/back/app/entity/models.py index 9e461beb..69967be9 100644 --- a/back/app/entity/models.py +++ b/back/app/entity/models.py @@ -20,6 +20,7 @@ class Individual(EntityType): lastname: Indexed(str) surnames: List[Indexed(str)] = [] day_of_birth: date + place_of_birth: str = "" @property diff --git a/back/app/template/models.py b/back/app/template/models.py index 00a05e20..8461b613 100644 --- a/back/app/template/models.py +++ b/back/app/template/models.py @@ -17,6 +17,15 @@ class PartyTemplate(BaseModel): default="" ) part: str + representative_id: str = Field( + foreignKey={ + "reference": { + "resource": "entity", + "schema": "Entity", + } + }, + default="" + ) def remove_html_tags(text): diff --git a/back/requirements.txt b/back/requirements.txt index f3c74a10..d32fc587 100644 --- a/back/requirements.txt +++ b/back/requirements.txt @@ -3,4 +3,6 @@ fastapi_users==10.2.1 fastapi_users_db_beanie==1.1.2 motor==3.1.1 fastapi-paginate==0.1.0 -uvicorn \ No newline at end of file +uvicorn +jinja2 +weasyprint diff --git a/front/Dockerfile b/front/Dockerfile index b2e4a86c..6ae2f635 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -21,4 +21,4 @@ RUN npm run build 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" ] diff --git a/front/app/src/assets/SCHLBKB.TTF b/front/app/src/assets/SCHLBKB.TTF deleted file mode 100644 index cc1bfea6..00000000 Binary files a/front/app/src/assets/SCHLBKB.TTF and /dev/null differ diff --git a/front/app/src/assets/logotransparent.png b/front/app/src/assets/logotransparent.png new file mode 100644 index 00000000..c5460e75 Binary files /dev/null and b/front/app/src/assets/logotransparent.png differ diff --git a/front/app/src/assets/tampon.png b/front/app/src/assets/tampon.png new file mode 100644 index 00000000..7a6e30df Binary files /dev/null and b/front/app/src/assets/tampon.png differ diff --git a/front/app/src/assets/watermark.png b/front/app/src/assets/watermark.png new file mode 100644 index 00000000..ff93d68f Binary files /dev/null and b/front/app/src/assets/watermark.png differ