Initial commit
This commit is contained in:
commit
8bc69e9b15
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.venv/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
__pycache__/
|
||||||
|
.envrc
|
4
MANIFEST.in
Normal file
4
MANIFEST.in
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include quittance/templates/quittance.html
|
||||||
|
include quittance/templates/quittance.css
|
||||||
|
include quittance/templates/*.ttf
|
||||||
|
include quittance/templates/*.otf
|
35
README.md
Normal file
35
README.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Quittance
|
||||||
|
|
||||||
|
Génère des quittances de loyers, au format PDF.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
pip install quittance
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Il faut d’abord configurer quelques informations (addresses, noms…),
|
||||||
|
le plus simple est d’exécuter :
|
||||||
|
|
||||||
|
quittance config
|
||||||
|
|
||||||
|
qui vous génèrera un fichier de configuration et vous proposera de
|
||||||
|
l’éditer.
|
||||||
|
|
||||||
|
Il est possible d’éditer la configuration en ligne de commande :
|
||||||
|
|
||||||
|
quittance config --bailleur-city Lyon --bailleur-country France
|
||||||
|
|
||||||
|
C’est pratique pour de petites corrections, mais pour tout configurer
|
||||||
|
préférez éditer le fichier `toml`.
|
||||||
|
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
Pour générer une nouvelle quittance, exécutez :
|
||||||
|
|
||||||
|
quittance
|
||||||
|
|
||||||
|
Une nouvelle quittance sera générée au format PDF.
|
36
pyproject.toml
Normal file
36
pyproject.toml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "quittance"
|
||||||
|
description = "Génère des quittances de loyers."
|
||||||
|
readme = "README.md"
|
||||||
|
license = {text = "MIT License"}
|
||||||
|
authors = [
|
||||||
|
{name = "Julien Palard", email = "julien@palard.fr"},
|
||||||
|
]
|
||||||
|
requires-python = ">= 3.7"
|
||||||
|
dependencies = [
|
||||||
|
"weasyprint",
|
||||||
|
"tomlkit",
|
||||||
|
"jinja2",
|
||||||
|
]
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://git.afpy.org/mdk/quittance"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
quittance = "quittance.__main__:main"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = true
|
||||||
|
|
||||||
|
[tool.setuptools.packages]
|
||||||
|
find = {}
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic.version]
|
||||||
|
attr = "quittance.__version__"
|
||||||
|
|
||||||
|
[tool.black]
|
1
quittance/__init__.py
Normal file
1
quittance/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "0.1"
|
111
quittance/__main__.py
Normal file
111
quittance/__main__.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import date, timedelta
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import locale
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
from weasyprint import HTML
|
||||||
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||||
|
import tomlkit as toml
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = ArgumentParser(description="Génère une quittance de loyer")
|
||||||
|
subparsers = parser.add_subparsers(required=False)
|
||||||
|
|
||||||
|
parser_conf = subparsers.add_parser(
|
||||||
|
"config",
|
||||||
|
help="Quittance configuration. Run without argument to see the current configuration.",
|
||||||
|
)
|
||||||
|
parser_conf.add_argument("--bailleur-name", metavar="'Ano Nymous'")
|
||||||
|
parser_conf.add_argument("--bailleur-address", metavar="'10 route de la corniche'")
|
||||||
|
parser_conf.add_argument("--bailleur-code-postal", metavar="69000")
|
||||||
|
parser_conf.add_argument("--bailleur-city", metavar="'Lyon'")
|
||||||
|
parser_conf.add_argument("--bailleur-country", metavar="'France'")
|
||||||
|
parser_conf.add_argument("--locataire-name", metavar="'Ano Nymous'")
|
||||||
|
parser_conf.add_argument("--locataire-address", metavar="'10 route de la corniche'")
|
||||||
|
parser_conf.add_argument("--locataire-code-postal", metavar="'69000'")
|
||||||
|
parser_conf.add_argument("--locataire-city", metavar="'Lyon'")
|
||||||
|
parser_conf.add_argument("--locataire-country", metavar="'France'")
|
||||||
|
parser_conf.add_argument("--dernier-numero", type=int, metavar="123")
|
||||||
|
parser_conf.add_argument("--loyer-hors-charges", type=int, metavar="123")
|
||||||
|
parser_conf.add_argument("--charges", type=int, metavar="123")
|
||||||
|
parser_conf.set_defaults(func=main_config)
|
||||||
|
|
||||||
|
parser.set_defaults(func=main_new)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
func = args.func
|
||||||
|
del args.func
|
||||||
|
return func(args)
|
||||||
|
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
try:
|
||||||
|
with open(Path.home() / ".config" / "quittance.toml", "r") as f:
|
||||||
|
config = toml.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
config = {}
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def save_config(config):
|
||||||
|
with open(Path.home() / ".config" / "quittance.toml", "w") as f:
|
||||||
|
toml.dump(config, f)
|
||||||
|
|
||||||
|
|
||||||
|
def main_config(args):
|
||||||
|
keys = set(vars(args).keys())
|
||||||
|
args = {key: value for key, value in vars(args).items() if value is not None}
|
||||||
|
config = load_config()
|
||||||
|
config.update(args)
|
||||||
|
if "dernier_numero" not in config:
|
||||||
|
config["dernier_numero"] = 0
|
||||||
|
for missing_key in sorted(keys - set(config)):
|
||||||
|
config[missing_key] = ""
|
||||||
|
save_config(config)
|
||||||
|
print(
|
||||||
|
f"You can edit the configuration file: {Path.home() / '.config' / 'quittance.toml'}"
|
||||||
|
)
|
||||||
|
print(toml.dumps(config))
|
||||||
|
|
||||||
|
|
||||||
|
def get_prev_month():
|
||||||
|
today = date.today()
|
||||||
|
first_of_month = today.replace(day=1)
|
||||||
|
last_day_of_last_month = first_of_month - timedelta(days=1)
|
||||||
|
first_day_of_last_month = last_day_of_last_month.replace(day=1)
|
||||||
|
return f"du {first_day_of_last_month:%A %d %B %Y} au {last_day_of_last_month:%A %d %B %Y}"
|
||||||
|
|
||||||
|
|
||||||
|
def main_new(args):
|
||||||
|
locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8")
|
||||||
|
env = Environment(loader=PackageLoader("quittance"), autoescape=select_autoescape())
|
||||||
|
template = env.get_template("quittance.html")
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
env = config.copy()
|
||||||
|
|
||||||
|
env["date"] = date.today().strftime("%A %d %B %Y")
|
||||||
|
env["periode"] = get_prev_month()
|
||||||
|
|
||||||
|
config["dernier_numero"] += 1
|
||||||
|
env["quittance_no"] = config["dernier_numero"]
|
||||||
|
save_config(config)
|
||||||
|
|
||||||
|
output = f"{date.today():%Y-%m-%d} quittance-{env['quittance_no']}.pdf"
|
||||||
|
HTML(
|
||||||
|
string=template.render(**env), base_url=str(Path(template.filename).parent)
|
||||||
|
).write_pdf(output)
|
||||||
|
print("Nouvelle quittance :", output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
BIN
quittance/templates/pacifico.ttf
Normal file
BIN
quittance/templates/pacifico.ttf
Normal file
Binary file not shown.
82
quittance/templates/quittance.css
Normal file
82
quittance/templates/quittance.css
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: Pacifico;
|
||||||
|
src: url(pacifico.ttf);
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(sourcesanspro-regular.otf);
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(sourcesanspro-bold.otf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
font-family: Pacifico;
|
||||||
|
margin: 3cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
color: #14213d;
|
||||||
|
font-family: Source Sans Pro;
|
||||||
|
font-size: 11pt;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #1ee494;
|
||||||
|
font-family: Pacifico;
|
||||||
|
font-size: 40pt;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
display: flex;
|
||||||
|
margin: 2em 0 4em;
|
||||||
|
}
|
||||||
|
aside address {
|
||||||
|
font-style: normal;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
aside address#from {
|
||||||
|
color: #a9a;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
aside address#to {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl#informations {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
text-align: right;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, dd {
|
||||||
|
display: inline;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
dt {
|
||||||
|
color: #a9a;
|
||||||
|
}
|
||||||
|
dt::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
dt::after {
|
||||||
|
content: ' : ';
|
||||||
|
}
|
||||||
|
|
||||||
|
b#recu {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
p#legal {
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
71
quittance/templates/quittance.html
Normal file
71
quittance/templates/quittance.html
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link href="quittance.css" media="print" rel="stylesheet">
|
||||||
|
<title>Quittance de loyer</title>
|
||||||
|
<meta name="description" content="Quittance de loyer">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Quittance de loyer</h1>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<address id="from">
|
||||||
|
<b>Bailleur</b>
|
||||||
|
{{ bailleur_name }}
|
||||||
|
{{ bailleur_address }}
|
||||||
|
{{ bailleur_code_postal }} {{ bailleur_city }}
|
||||||
|
{{ bailleur_country }}
|
||||||
|
</address>
|
||||||
|
|
||||||
|
<address id="to">
|
||||||
|
<b>Locataire Destinataire</b>
|
||||||
|
{{ locataire_name }}
|
||||||
|
{{ locataire_address }}
|
||||||
|
{{ locataire_code_postal}} {{ locataire_city }}
|
||||||
|
{{ locataire_country }}
|
||||||
|
</address>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<dl id="informations">
|
||||||
|
<dt>Quittance №</dt>
|
||||||
|
<dd>{{ quittance_no }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl id="payment">
|
||||||
|
<dt><b id="recu">Reçu</b> de</dt>
|
||||||
|
<dd>{{ locataire_name }}</dd>
|
||||||
|
<dt>La somme de</dt>
|
||||||
|
<dd>{{ loyer_hors_charges + charges }} €</dd>
|
||||||
|
<dt>Au titre du paiement du loyer et des charges du logement sis</dt>
|
||||||
|
<dd>{{ locataire_address }} {{locataire_city }}</dd>
|
||||||
|
<dt>Pour la période de location</dt>
|
||||||
|
<dd>{{ periode }}</dd>
|
||||||
|
<dt>Date du règlement</dt>
|
||||||
|
<dd>{{ date }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<h3>Détail de la quittance de loyer</h3>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>Loyer hors charge</dt><dd>{{ loyer_hors_charges}} €</dd>
|
||||||
|
<dt>Charges / Provisions de charges</dt><dd>{{ charges }} €</dd>
|
||||||
|
<dt>Régularisation annuelle</dt><dd>ø</dd>
|
||||||
|
<dt>Total</dt><dd>{{ loyer_hors_charges + charges}} €</dd>
|
||||||
|
</dl>
|
||||||
|
<br/>
|
||||||
|
<dl id="where">
|
||||||
|
<dt>Fait à</dt>
|
||||||
|
<dd>{{ bailleur_city }}</dd>
|
||||||
|
<dt>Le</dt>
|
||||||
|
<dd>{{ date }}</dd>
|
||||||
|
<dt>Signature du bailleur</dt>
|
||||||
|
<dd>{{ bailleur_name }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<p id="legal">
|
||||||
|
Le paiement de la présente n'emporte pas présomption de paiement des termes antérieurs. Cette quittance ou ce reçu annule tous les reçus qui auraient pu être donnés pour acompte versé sur le présent terme. En cas de congé précédemment donné, cette quittance ou ce reçu représenterait l'indemnité d'occupation et ne saurait être considéré comme un titre d'occupation. Sous réserve d'encaissement.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
quittance/templates/sourcesanspro-bold.otf
Normal file
BIN
quittance/templates/sourcesanspro-bold.otf
Normal file
Binary file not shown.
BIN
quittance/templates/sourcesanspro-regular.otf
Normal file
BIN
quittance/templates/sourcesanspro-regular.otf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user