Adapter rapports de rencontre au modele papier
This commit is contained in:
parent
e1b80793ee
commit
968079c671
3 changed files with 112 additions and 74 deletions
|
|
@ -101,66 +101,91 @@ async def _html_rapport_rencontre(reunion: Reunion, membre, db: AsyncSession) ->
|
||||||
)
|
)
|
||||||
|
|
||||||
annonces = reunion.annonces or []
|
annonces = reunion.annonces or []
|
||||||
annonces_html = "".join(f"<li>{escape(a)}</li>" for a in annonces) or "<li>Aucune annonce.</li>"
|
annonces_texte = "\n".join(annonces)
|
||||||
|
collecte_texte = "Aucune collecte saisie."
|
||||||
|
if collecte:
|
||||||
|
detenteur = await db.get(Membre, collecte.detenteur_id) if collecte.detenteur_id else None
|
||||||
|
collecte_texte = (
|
||||||
|
f"{float(collecte.montant):.2f} $"
|
||||||
|
f"\nDétenteur: {detenteur.prenom if detenteur else '—'}"
|
||||||
|
f"\nStatut: {collecte.statut}"
|
||||||
|
)
|
||||||
|
ventes_lit_texte = "\n".join(
|
||||||
|
row.replace("<tr><td>", "")
|
||||||
|
.replace("</td><td>", " · ")
|
||||||
|
.replace("</td><td class=\"montant\">", " · ")
|
||||||
|
.replace("</td></tr>", "")
|
||||||
|
for row in ventes_lit_rows
|
||||||
|
)
|
||||||
|
jetons_texte = "\n".join(
|
||||||
|
row.replace("<tr><td>", "")
|
||||||
|
.replace("</td><td>", " · ")
|
||||||
|
.replace("</td></tr>", "")
|
||||||
|
for row in jetons_rows
|
||||||
|
)
|
||||||
|
ventes_jetons_texte = "\n".join(
|
||||||
|
row.replace("<tr><td>", "")
|
||||||
|
.replace("</td><td>", " · ")
|
||||||
|
.replace("</td><td class=\"montant\">", " · ")
|
||||||
|
.replace("</td></tr>", "")
|
||||||
|
for row in ventes_jetons_rows
|
||||||
|
)
|
||||||
|
notes_texte = "\n\n".join([texte for texte in [reunion.note, annonces_texte] if texte])
|
||||||
|
|
||||||
|
def bloc(label: str, contenu: str = "", cls: str = "") -> str:
|
||||||
|
return (
|
||||||
|
f"<div class=\"field {cls}\"><div class=\"box\">"
|
||||||
|
f"{escape(contenu or '')}</div><div class=\"label\">{escape(label)}</div></div>"
|
||||||
|
)
|
||||||
|
|
||||||
css = """
|
css = """
|
||||||
body { font-family: 'DejaVu Sans', sans-serif; font-size: 10.5pt; color: #27323a; margin: 1.6cm; }
|
@page { size: letter; margin: 0.55in 0.65in; }
|
||||||
.header { border-bottom: 3px solid #1a5276; padding-bottom: 12pt; margin-bottom: 18pt; }
|
body { font-family: Arial, 'DejaVu Sans', sans-serif; color: #111; font-size: 10pt; }
|
||||||
.groupe { color: #1a5276; font-size: 20pt; font-weight: 800; }
|
.header { text-align: center; margin: 12pt 0 40pt; }
|
||||||
.coords { color: #667; font-size: 9pt; margin-top: 3pt; }
|
.groupe { font-size: 21pt; font-weight: 400; margin-bottom: 16pt; }
|
||||||
h1 { font-size: 16pt; color: #1a5276; margin: 0 0 12pt; }
|
.district { font-size: 16pt; margin-bottom: 18pt; }
|
||||||
h2 { font-size: 12pt; color: #1a5276; margin: 16pt 0 6pt; border-bottom: 1px solid #d8e2ea; padding-bottom: 3pt; }
|
h1 { font-size: 14pt; font-weight: 500; letter-spacing: 0; margin: 0; }
|
||||||
.meta { display: grid; grid-template-columns: 1fr 1fr; gap: 8pt; }
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18pt 18pt; }
|
||||||
.box { border: 1px solid #d8e2ea; border-radius: 6pt; padding: 8pt; background: #f8fafc; }
|
.field { break-inside: avoid; }
|
||||||
.label { font-size: 8pt; color: #667; text-transform: uppercase; font-weight: 700; }
|
.field.full { grid-column: 1 / -1; }
|
||||||
.value { font-size: 11pt; font-weight: 700; margin-top: 2pt; }
|
.box {
|
||||||
table { width: 100%; border-collapse: collapse; margin-top: 5pt; }
|
border: 1pt solid #222;
|
||||||
th { text-align: left; background: #1a5276; color: white; padding: 5pt; font-size: 8.5pt; }
|
min-height: 27pt;
|
||||||
td { border-bottom: 1px solid #e5e9ef; padding: 5pt; vertical-align: top; }
|
padding: 6pt 8pt;
|
||||||
.montant { text-align: right; white-space: nowrap; }
|
white-space: pre-wrap;
|
||||||
.note { white-space: pre-wrap; }
|
line-height: 1.25;
|
||||||
.page-break { break-before: page; page-break-before: always; }
|
overflow: hidden;
|
||||||
.footer { margin-top: 20pt; color: #889; font-size: 8pt; text-align: center; }
|
}
|
||||||
|
.label { font-style: italic; font-size: 8.5pt; margin: 4pt 0 0 1pt; }
|
||||||
|
.short .box { min-height: 24pt; }
|
||||||
|
.theme .box { min-height: 58pt; }
|
||||||
|
.medium .box { min-height: 58pt; }
|
||||||
|
.deep .box { min-height: 95pt; }
|
||||||
|
.notes .box { min-height: 150pt; }
|
||||||
|
.footer { margin-top: 10pt; color: #777; font-size: 7.5pt; text-align: center; }
|
||||||
"""
|
"""
|
||||||
groupe_nom = groupe.nom if groupe and groupe.nom else "Groupe"
|
groupe_nom = groupe.nom if groupe and groupe.nom else "Groupe"
|
||||||
coords = " · ".join([
|
district = f"District {groupe.district}" if groupe and groupe.district else "District"
|
||||||
item for item in [
|
|
||||||
groupe.adresse_local if groupe else None,
|
|
||||||
f"District {groupe.district}" if groupe and groupe.district else None,
|
|
||||||
] if item
|
|
||||||
])
|
|
||||||
ventes_lit_html = (
|
|
||||||
"<table><tr><th>Titre</th><th>Qté</th><th>Montant</th><th>Vendu par</th><th>Statut</th></tr>"
|
|
||||||
+ "".join(ventes_lit_rows) + "</table>"
|
|
||||||
if ventes_lit_rows else "<p>Aucune vente de littérature.</p>"
|
|
||||||
)
|
|
||||||
ventes_jetons_html = (
|
|
||||||
"<table><tr><th>Type</th><th>Qté</th><th>Montant</th><th>Vendu par</th><th>Statut</th></tr>"
|
|
||||||
+ "".join(ventes_jetons_rows) + "</table>"
|
|
||||||
if ventes_jetons_rows else "<p>Aucune vente de jetons.</p>"
|
|
||||||
)
|
|
||||||
jetons_html = (
|
|
||||||
"<table><tr><th>Type</th><th>Membre</th><th>Détail</th></tr>" + "".join(jetons_rows) + "</table>"
|
|
||||||
if jetons_rows else "<p>Aucun jeton/gâteau comptabilisé.</p>"
|
|
||||||
)
|
|
||||||
html = f"""<!doctype html><html><head><meta charset="utf-8"><style>{css}</style></head><body>
|
html = f"""<!doctype html><html><head><meta charset="utf-8"><style>{css}</style></head><body>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="groupe">{escape(groupe_nom)}</div>
|
<div class="groupe">{escape(groupe_nom)}</div>
|
||||||
<div class="coords">{escape(coords)}</div>
|
<div class="district">{escape(district)}</div>
|
||||||
|
<h1>RÉUNION HEBDOMADAIRE</h1>
|
||||||
</div>
|
</div>
|
||||||
<h1>Rapport de rencontre</h1>
|
<div class="grid">
|
||||||
<div class="meta">
|
{bloc("DATE ( AAAA / MM / JJ)", reunion.date_reunion.date().isoformat(), "short")}
|
||||||
<div class="box"><div class="label">Date</div><div class="value">{reunion.date_reunion.date().isoformat()}</div></div>
|
{bloc("PRÉSENCES", "", "short")}
|
||||||
<div class="box"><div class="label">Heure</div><div class="value">{reunion.date_reunion.strftime('%H:%M')}</div></div>
|
{bloc("SALLE & CAFÉ", groupe.adresse_local if groupe else "", "short")}
|
||||||
<div class="box"><div class="label">Sujet</div><div class="value">{escape(reunion.theme or 'Rencontre hebdomadaire')}</div></div>
|
{bloc("ANIMATION", animateur.prenom if animateur else "", "short")}
|
||||||
<div class="box"><div class="label">Animé par</div><div class="value">{escape(animateur.prenom if animateur else '—')}</div></div>
|
{bloc("THÈME DE LA DISCUSSION", reunion.theme or "", "full theme")}
|
||||||
|
{bloc("NOUVEAU(X)", "", "medium")}
|
||||||
|
{bloc("ANNIVERSAIRES / JETONS", jetons_texte, "medium")}
|
||||||
|
{bloc("COLLECTE", collecte_texte, "short")}
|
||||||
|
{bloc("DÉPENSES", "", "deep")}
|
||||||
|
{bloc("VENTES (Littérature)", ventes_lit_texte, "short")}
|
||||||
|
{bloc("VENTES (Jetons)", ventes_jetons_texte, "short")}
|
||||||
|
{bloc("NOTES", notes_texte, "full notes")}
|
||||||
</div>
|
</div>
|
||||||
<h2>Annonces</h2><ul>{annonces_html}</ul>
|
|
||||||
<h2>Notes</h2><div class="note">{escape(reunion.note or 'Aucune note.')}</div>
|
|
||||||
<h2>Collecte</h2>{collecte_html}
|
|
||||||
<div class="page-break"></div>
|
|
||||||
<h2>Ventes de littérature</h2>{ventes_lit_html}
|
|
||||||
<h2>Ventes de jetons</h2>{ventes_jetons_html}
|
|
||||||
<h2>Jetons/Gâteaux</h2>{jetons_html}
|
|
||||||
<div class="footer">Rapport généré automatiquement · {datetime.now().strftime('%Y-%m-%d %H:%M')}</div>
|
<div class="footer">Rapport généré automatiquement · {datetime.now().strftime('%Y-%m-%d %H:%M')}</div>
|
||||||
</body></html>"""
|
</body></html>"""
|
||||||
return html, jour
|
return html, jour
|
||||||
|
|
|
||||||
BIN
data/import/Modèle de Saint-Basile.pdf
Normal file
BIN
data/import/Modèle de Saint-Basile.pdf
Normal file
Binary file not shown.
|
|
@ -20,36 +20,48 @@ const formulaires = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const lignes = (n) => Array.from({ length: n }, (_, i) => `<div class="line">${i + 1}.</div>`).join('');
|
|
||||||
|
|
||||||
function htmlFormulaire(type, groupe) {
|
function htmlFormulaire(type, groupe) {
|
||||||
const nomGroupe = groupe?.nom || 'Groupe';
|
const nomGroupe = groupe?.nom || 'Groupe';
|
||||||
const commun = `
|
const district = groupe?.district ? `District ${groupe.district}` : 'District';
|
||||||
|
const lieu = groupe?.adresse_local || '';
|
||||||
|
const bloc = (label, contenu = '', cls = '') => `
|
||||||
|
<div class="field ${cls}">
|
||||||
|
<div class="box">${contenu}</div>
|
||||||
|
<div class="label">${label}</div>
|
||||||
|
</div>`;
|
||||||
|
const lignes = (n) => Array.from({ length: n }, (_, i) => `<div class="line">${i + 1}.</div>`).join('');
|
||||||
|
const entete = (titre) => `
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<h1>${nomGroupe}</h1>
|
<h1>${nomGroupe}</h1>
|
||||||
<p>Formulaire papier de reprise en main</p>
|
<p>${district}</p>
|
||||||
</div>
|
<h2>${titre}</h2>
|
||||||
<div class="meta">
|
|
||||||
<div>Date: ____________________</div>
|
|
||||||
<div>Responsable: ____________________</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
const sections = {
|
const sections = {
|
||||||
rencontre: `
|
rencontre: `
|
||||||
<h2>Rencontre hebdomadaire</h2>
|
${entete('RÉUNION HEBDOMADAIRE')}
|
||||||
<label>Sujet / thème</label><div class="box"></div>
|
<div class="form-grid">
|
||||||
<label>Collecte</label><div class="grid"><div>Montant: __________</div><div>Détenteur: __________</div></div>
|
${bloc('DATE ( AAAA / MM / JJ)', '', 'short')}
|
||||||
<label>Ventes littérature / jetons / gâteau</label>${lignes(6)}
|
${bloc('PRÉSENCES', '', 'short')}
|
||||||
<label>Remises de jetons / anniversaires</label>${lignes(6)}
|
${bloc('SALLE & CAFÉ', lieu, 'short')}
|
||||||
<label>Notes</label><div class="big"></div>`,
|
${bloc('ANIMATION', '', 'short')}
|
||||||
|
${bloc('THÈME DE LA DISCUSSION', '', 'full theme')}
|
||||||
|
${bloc('NOUVEAU(X)', '', 'medium')}
|
||||||
|
${bloc('ANNIVERSAIRES / JETONS', '', 'medium')}
|
||||||
|
${bloc('COLLECTE', '', 'short')}
|
||||||
|
${bloc('DÉPENSES', '', 'deep')}
|
||||||
|
${bloc('VENTES (Littérature)', '', 'short')}
|
||||||
|
${bloc('VENTES (Jetons)', '', 'short')}
|
||||||
|
${bloc('NOTES', '', 'full notes')}
|
||||||
|
</div>`,
|
||||||
assemblee: `
|
assemblee: `
|
||||||
<h2>Assemblée d'affaires</h2>
|
${entete("ASSEMBLÉE D'AFFAIRES")}
|
||||||
<label>Présences</label>${lignes(12)}
|
<label>Présences</label>${lignes(12)}
|
||||||
<label>Rapport du trésorier</label><div class="big"></div>
|
<label>Rapport du trésorier</label><div class="big"></div>
|
||||||
<label>Propositions / décisions</label>${lignes(10)}
|
<label>Propositions / décisions</label>${lignes(10)}
|
||||||
<label>Postes à pourvoir</label>${lignes(6)}
|
<label>Postes à pourvoir</label>${lignes(6)}
|
||||||
<label>Suites à donner</label><div class="big"></div>`,
|
<label>Suites à donner</label><div class="big"></div>`,
|
||||||
tresorerie: `
|
tresorerie: `
|
||||||
<h2>Trésorerie</h2>
|
${entete('TRÉSORERIE')}
|
||||||
<label>Encaisse / banque / réserves</label>${lignes(8)}
|
<label>Encaisse / banque / réserves</label>${lignes(8)}
|
||||||
<label>Collectes et ventes reçues</label>${lignes(8)}
|
<label>Collectes et ventes reçues</label>${lignes(8)}
|
||||||
<label>Dépenses et contributions</label>${lignes(8)}
|
<label>Dépenses et contributions</label>${lignes(8)}
|
||||||
|
|
@ -57,13 +69,14 @@ function htmlFormulaire(type, groupe) {
|
||||||
};
|
};
|
||||||
return `<!doctype html><html lang="fr"><head><meta charset="utf-8"><title>${nomGroupe} - ${type}</title>
|
return `<!doctype html><html lang="fr"><head><meta charset="utf-8"><title>${nomGroupe} - ${type}</title>
|
||||||
<style>
|
<style>
|
||||||
@page{size:letter;margin:0.6in} body{font-family:Arial,sans-serif;color:#111;line-height:1.35}
|
@page{size:letter;margin:0.55in 0.65in} body{font-family:Arial,sans-serif;color:#111;line-height:1.35}
|
||||||
.head{border-bottom:2px solid #111;padding-bottom:10px;margin-bottom:14px}.head h1{margin:0;font-size:22px}.head p{margin:4px 0 0;color:#555}
|
.head{text-align:center;margin:12pt 0 40pt}.head h1{margin:0 0 16pt;font-size:21pt;font-weight:400}.head p{margin:0 0 18pt;font-size:16pt}.head h2{font-size:14pt;font-weight:500;margin:0}
|
||||||
h2{font-size:18px;margin:14px 0 10px}.meta{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px}
|
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:18pt}
|
||||||
label{display:block;font-weight:bold;margin-top:14px}.box{height:42px;border:1px solid #999;margin-top:6px}.big{height:120px;border:1px solid #999;margin-top:6px}
|
.field{break-inside:avoid}.field.full{grid-column:1 / -1}.box{min-height:27pt;border:1pt solid #222;padding:6pt 8pt;white-space:pre-wrap}.label{font-style:italic;font-size:8.5pt;margin:4pt 0 0 1pt}
|
||||||
.grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;border:1px solid #999;padding:10px;margin-top:6px}
|
.short .box{min-height:24pt}.theme .box{min-height:58pt}.medium .box{min-height:58pt}.deep .box{min-height:95pt}.notes .box{min-height:150pt}
|
||||||
|
label{display:block;font-weight:bold;margin-top:14px}.big{height:120px;border:1px solid #999;margin-top:6px}
|
||||||
.line{height:28px;border-bottom:1px solid #bbb;padding-top:6px}
|
.line{height:28px;border-bottom:1px solid #bbb;padding-top:6px}
|
||||||
</style></head><body>${commun}${sections[type]}<script>window.print()</script></body></html>`;
|
</style></head><body>${sections[type]}<script>window.print()</script></body></html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModePapier() {
|
export default function ModePapier() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue