Files
erp/correspondance/lettre.py
T

97 lines
4.4 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Produit une lettre commerciale Arcodange en PDF, depuis le gabarit maison.
POURQUOI CE SCRIPT EXISTE. Deux choses ne se font pas à la main sans se tromper.
1. `weasyprint` NE SAIT PAS rendre Apple Color Emoji : c'est un format bitmap
`sbix` qu'il ignore, et les trois emblèmes 🏹💻🪽 sortent en carrés vides.
Il faut les incorporer en images. Elles sont ici en PNG, rendues une fois
pour toutes par `test/emoji2png.ts` (Chromium, lui, lit la police système).
2. Un gabarit qui porterait ces images en base64 pèserait 400 Ko dont 97 % de
charabia. On les garde en fichiers et on les incorpore À LA GÉNÉRATION,
pour que le gabarit reste relisible et que le PDF reste autonome.
Usage :
python3 correspondance/lettre.py corps.html sortie.pdf \
--titre "Contracts and invoices — cycles M1 to M4" \
--date "24 August 2026" --prenom Evan \
--destinataire "Evan Sforzo" --fonction "Chief Executive Officer" \
--societe "Kissmetrics Inc." \
--adresse "2850 34th Street North, 307 — St. Petersburg, Florida 33713 — United States" \
--copie "Hendrik Rootering" \
--pied "Arcodange × Kissmetrics Inc. — 24 August 2026"
`corps.html` ne contient que le corps : les <h2>, <p>, <table>, encadrés. L'en-tête,
le bloc destinataire, la signature et le pied de page viennent du gabarit.
Sans `--copie`, la ligne « cc » disparaît.
"""
import argparse, base64, pathlib, re, subprocess, sys
ICI = pathlib.Path(__file__).parent
GABARIT = ICI / "lettre.template.html"
EMBLEMES = {"ARC": "arc.png", "LAPTOP": "laptop.png", "AILE": "aile.png"}
def data_uri(chemin: pathlib.Path) -> str:
return "data:image/png;base64," + base64.b64encode(chemin.read_bytes()).decode()
def main() -> int:
a = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
a.add_argument("corps"); a.add_argument("sortie")
for champ in ("titre", "date", "prenom", "destinataire", "fonction",
"societe", "adresse", "pied"):
a.add_argument(f"--{champ}", required=True)
a.add_argument("--copie", default="")
a.add_argument("--garder-html", action="store_true",
help="conserve le HTML intermédiaire à côté du PDF")
o = a.parse_args()
html = GABARIT.read_text(encoding="utf-8")
for cle, fichier in EMBLEMES.items():
p = ICI / "emblemes" / fichier
if not p.exists():
print(f"emblème manquant : {p}\n"
f" le régénérer : cd test && deno run -A emoji2png.ts <dossier> 🏹 💻 🪽",
file=sys.stderr)
return 2
html = html.replace("{{" + cle + "}}", data_uri(p))
for cle, val in (("TITRE", o.titre), ("DATE", o.date), ("PRENOM", o.prenom),
("DESTINATAIRE", o.destinataire), ("FONCTION", o.fonction),
("SOCIETE", o.societe), ("ADRESSE", o.adresse),
("COPIE", o.copie), ("PIED", o.pied)):
html = html.replace("{{" + cle + "}}", val)
# Pas de destinataire en copie : on retire la ligne entière, pas seulement
# son contenu, sinon il reste un « cc » orphelin.
if not o.copie:
html = re.sub(r'<br>\s*<span class="a"[^>]*>cc</span>\s*&nbsp;\s*', "", html)
# Le corps remplace tout ce qui sépare le sous-titre de la signature.
corps = pathlib.Path(o.corps).read_text(encoding="utf-8")
deb = html.index("<h1>")
fin = html.index('<div class="signature">')
entete = html[deb:html.index("<p>Dear ")]
html = html[:deb] + entete + f"<p>Dear {o.prenom},</p>\n\n" + corps + "\n\n" + html[fin:]
tmp = pathlib.Path(o.sortie).with_suffix(".html")
tmp.write_text(html, encoding="utf-8")
r = subprocess.run(["weasyprint", str(tmp), o.sortie])
if r.returncode == 0 and not o.garder_html:
tmp.unlink()
if r.returncode == 0:
pages = subprocess.run(["pdfinfo", o.sortie], capture_output=True, text=True).stdout
n = next((l.split()[1] for l in pages.splitlines() if l.startswith("Pages")), "?")
print(f"ok — {o.sortie} ({n} page(s))")
if n not in ("1", "2"):
print(" ATTENTION : au-delà de deux pages, une lettre d'affaires se lit mal. "
"Resserrer le corps plutôt que la typographie.", file=sys.stderr)
return r.returncode
if __name__ == "__main__":
raise SystemExit(main())