From befb5fd1de6c1be8e625b1dcc1fd4456d8bb3f2e Mon Sep 17 00:00:00 2001 From: Alexis M Date: Sat, 5 Oct 2019 20:20:13 +0200 Subject: [PATCH] Generate PDFs rather than XLS files. Closes #14. --- copanier/__init__.py | 163 +++++++++++------- copanier/emails.py | 9 +- copanier/static/app.css | 53 ++++++ copanier/static/page.css | 28 +++ copanier/static/signing-sheet.css | 8 +- copanier/templates/base.html | 7 +- copanier/templates/delivery.html | 14 +- copanier/templates/delivery_balance.html | 3 +- ...ge_delivery.html => delivery_toolbox.html} | 5 +- copanier/templates/home.html | 6 +- .../templates/includes/delivery_head.html | 7 +- .../templates/includes/delivery_table.html | 31 ++-- copanier/templates/list_products.html | 7 +- .../templates/prepare_referent_email.html | 2 +- copanier/templates/signing_sheet.html | 16 +- copanier/utils.py | 5 + 16 files changed, 249 insertions(+), 115 deletions(-) create mode 100644 copanier/static/page.css rename copanier/templates/{manage_delivery.html => delivery_toolbox.html} (81%) diff --git a/copanier/__init__.py b/copanier/__init__.py index fd97e6d..84faa14 100644 --- a/copanier/__init__.py +++ b/copanier/__init__.py @@ -11,22 +11,48 @@ from roll.extensions import traceback, simple_server, static from slugify import slugify from debts.solver import order_balance, check_balance, reduce_balance +from weasyprint import HTML from . import config, reports, session, utils, emails, loggers, imports from .models import Delivery, Order, Person, Product, ProductOrder, Groups, Group class Response(RollResponse): - def html(self, template_name, *args, **kwargs): - self.headers["Content-Type"] = "text/html; charset=utf-8" + def render_template(self, template_name, *args, **kwargs): context = app.context() context.update(kwargs) context["request"] = self.request + context["config"] = config + context["request"] = self.request if self.request.cookies.get("message"): context["message"] = json.loads(self.request.cookies["message"]) self.cookies.set("message", "") - context["config"] = config - self.body = env.get_template(template_name).render(*args, **context) + return env.get_template(template_name).render(*args, **context) + + def html(self, template_name, *args, **kwargs): + self.headers["Content-Type"] = "text/html; charset=utf-8" + self.body = self.render_template(template_name, *args, **kwargs) + + def render_pdf(self, template_name, *args, **kwargs): + html = self.render_template(template_name, *args, **kwargs) + + static_folder = Path(__file__).parent / "static" + stylesheets = [ + static_folder / "app.css", + static_folder / "icomoon.css", + static_folder / "page.css", + ] + if "css" in kwargs: + stylesheets.append(static_folder / kwargs["css"]) + + return HTML(string=html).write_pdf(stylesheets=stylesheets) + + def pdf(self, template_name, *args, **kwargs): + self.body = self.render_pdf(template_name, *args, **kwargs) + mimetype = "application/pdf" + filename = kwargs.get("filename", "file.pdf") + self.headers["Content-Disposition"] = f'attachment; filename="{filename}"' + self.headers["Content-Type"] = f"{mimetype}; charset=utf-8" def xlsx(self, body, filename=f"{config.SITE_NAME}.xlsx"): self.body = body @@ -228,9 +254,7 @@ async def create_group(request, response): members.append(request["user"].email) group = Group.create( - id=slugify(form.get("name")), - name=form.get("name"), - members=members, + id=slugify(form.get("name")), name=form.get("name"), members=members ) request["groups"].add_group(group) request["groups"].persist() @@ -340,15 +364,31 @@ async def import_products(request, response, id): @app.route("/livraison/{id}/producteurices") +@app.route("/livraison/{id}/producteurices.pdf") async def list_producers(request, response, id): delivery = Delivery.load(id) - response.html( - "list_products.html", - { + template_name = "list_products.html" + template_params = { "edit_mode": True, + "list_only": True, "delivery": delivery, "referent": request.query.get("referent", None), - }, + }, + + if request.url.endswith(b".pdf"): + response.pdf(template_name, template_params, filename=utils.prefix("producteurices.pdf", delivery),) + else: + response.html(template_name, template_params) + + +@app.route("/livraison/{id}/{producer}/bon-de-commande.pdf", methods=["GET"]) +async def pdf_for_producer(request, response, id, producer): + delivery = Delivery.load(id) + date = delivery.to_date.strftime("%Y-%m-%d") + response.pdf( + "list_products.html", + {"list_only": True, "delivery": delivery, "producers": [producer]}, + filename=utils.prefix(f"bon-de-commande-{producer}.pdf", delivery), ) @@ -432,10 +472,10 @@ async def create_product(request, response, delivery_id, producer_id): @app.route("/livraison/{id}/gérer", methods=["GET"]) -async def manage_delivery(request, response, id): +async def delivery_toolbox(request, response, id): delivery = Delivery.load(id) response.html( - "manage_delivery.html", + "delivery_toolbox.html", { "delivery": delivery, "referents": [p.referent for p in delivery.producers.values()], @@ -446,56 +486,47 @@ async def manage_delivery(request, response, id): @app.route("/livraison/{id}/envoi-email-referentes", methods=["GET", "POST"]) async def send_referent_emails(request, response, id): delivery = Delivery.load(id) - date = delivery.to_date.strftime("%Y-%m-%d") if request.method == "POST": email_body = request.form.get("email_body") email_subject = request.form.get("email_subject") + sent_mails = 0 for referent in delivery.get_referents(): producers = delivery.get_producers_for_referent(referent) - if any( - [delivery.producers[p].has_active_products(delivery) for p in producers] - ): - summary = reports.summary(delivery, producers) + attachments = [] + for producer in producers: + if delivery.producers[producer].has_active_products(delivery): + pdf_file = response.render_pdf( + "list_products.html", + { + "list_only": True, + "delivery": delivery, + "producers": [producer], + }, + ) + + attachments.append( + ( + utils.prefix(f"{producer}.pdf", delivery), + pdf_file, + "application/pdf", + ) + ) + + if attachments: + sent_mails = sent_mails + 1 emails.send( referent, email_subject, email_body, copy=delivery.contact, - attachments=[ - (f"{config.SITE_NAME}-{date}-{referent}.xlsx", summary) - ], + attachments=attachments, ) - response.message("Le mail à bien été envoyé") + response.message(f"Un mail à été envoyé aux {sent_mails} référent⋅e⋅s") response.redirect = f"/livraison/{id}/gérer" response.html("prepare_referent_email.html", {"delivery": delivery}) -@app.route("/livraison/{id}/bon-de-commande-referent⋅e", methods=["GET"]) -async def download_referent_summary(request, response, id): - delivery = Delivery.load(id) - date = delivery.to_date.strftime("%Y-%m-%d") - if not request["user"].is_referent(delivery): - return - referent = request["user"].email - producers = delivery.get_producers_for_referent(referent) - summary = reports.summary(delivery, producers) - response.xlsx(summary, filename=f"{config.SITE_NAME}-{date}-{referent}.xlsx") - - -@app.route( - "/livraison/{id}/product⋅eur⋅rice/{producer}/bon-de-commande", - methods=["GET"], -) -async def download_producer_report(request, response, id, producer): - delivery = Delivery.load(id) - summary = reports.summary(delivery, [producer]) - date = delivery.to_date.strftime("%Y-%m-%d") - response.xlsx( - summary, filename=f"{config.SITE_NAME}-{date}-{producer}-bon-de-commande.xlsx" - ) - - @app.route("/livraison/{id}/exporter", methods=["GET"]) async def export_products(request, response, id): delivery = Delivery.load(id) @@ -636,7 +667,12 @@ async def send_order(request, response, id): @app.route("/livraison/{id}/émargement", methods=["GET"]) async def signing_sheet(request, response, id): delivery = Delivery.load(id) - response.html("signing_sheet.html", {"delivery": delivery}) + response.pdf( + "signing_sheet.html", + {"delivery": delivery}, + css="signing-sheet.css", + filename=utils.prefix("commandes-par-groupe.pdf", delivery), + ) @app.route("/livraison/{id}/importer/commande", methods=["POST"]) @@ -730,6 +766,7 @@ async def adjust_product(request, response, id, ref): @app.route("/livraison/{id}/solde", methods=["GET"]) +@app.route("/livraison/{id}/solde.pdf", methods=["GET"]) @staff_only async def delivery_balance(request, response, id): delivery = Delivery.load(id) @@ -770,17 +807,25 @@ async def delivery_balance(request, response, id): for debiter, amount, crediter in results: results_dict[debiter][crediter] = amount - response.html( - "delivery_balance.html", - { - "delivery": delivery, - "debiters": debiters, - "crediters": crediters, - "results": results_dict, - "debiters_groups": groups.groups, - "crediters_groups": producer_groups, - }, - ) + template_name = "delivery_balance.html" + template_args = { + "delivery": delivery, + "debiters": debiters, + "crediters": crediters, + "results": results_dict, + "debiters_groups": groups.groups, + "crediters_groups": producer_groups, + } + + if request.url.endswith(b".pdf"): + date = delivery.to_date.strftime("%Y-%m-%d") + response.pdf( + template_name, + template_args, + filename=utils.prefix("répartition-des-chèques.pdf", delivery), + ) + else: + response.html(template_name, template_args) @app.route("/livraison/{id}/solde.xlsx", methods=["GET"]) diff --git a/copanier/emails.py b/copanier/emails.py index 88ca8d1..7036699 100644 --- a/copanier/emails.py +++ b/copanier/emails.py @@ -11,13 +11,10 @@ def send(to, subject, body, html=None, copy=None, attachments=None): text=body, html=html, subject=subject, mail_from=config.FROM_EMAIL ) - for filename, attachment in attachments: - message.attach( - filename=filename, - data=attachment, - mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8", - ) + for filename, attachment, mime in attachments: + message.attach(filename=filename, data=attachment, mime=f"{mime} charset=utf-8") + config.SEND_EMAILS = False if not config.SEND_EMAILS: body = body.replace("https", "http") return print("Sending email", str(body)) diff --git a/copanier/static/app.css b/copanier/static/app.css index f62e43a..998bbcf 100644 --- a/copanier/static/app.css +++ b/copanier/static/app.css @@ -597,4 +597,57 @@ table.paiements td { } table.paiements tr:hover td { background: #f2f2f2; +} + +.container { + width: 600px; + margin: 100px auto; +} +.progressbar { + counter-reset: step; +} +.progressbar li { + list-style-type: none; + width: 20%; + float: left; + font-size: 12px; + position: relative; + text-align: center; + text-transform: uppercase; + color: #7d7d7d; +} +.progressbar li:before { + width: 30px; + height: 30px; + content: counter(step); + counter-increment: step; + line-height: 25px; + border: 2px solid #7d7d7d; + display: block; + text-align: center; + margin: 0 auto 10px auto; + border-radius: 50%; + background-color: white; +} +.progressbar li:after { + width: 100%; + height: 2px; + content: ''; + position: absolute; + background-color: #7d7d7d; + top: 15px; + left: -50%; + z-index: -1; +} +.progressbar li:first-child:after { + content: none; +} +.progressbar li.active { + color: green; +} +.progressbar li.active:before { + border-color: #55b776; +} +.progressbar li.active + li:after { + background-color: #55b776; } \ No newline at end of file diff --git a/copanier/static/page.css b/copanier/static/page.css new file mode 100644 index 0000000..5af0576 --- /dev/null +++ b/copanier/static/page.css @@ -0,0 +1,28 @@ +@page { + size: "A4"; + margin: 0cm; + size: landscape; +} + +#download { + display: none; +} + +@media print { + nav, .notification, .toolbox, .edit, .hide-on-print, .button{ + display: none; + } + + ul.delivery-head { + display: block !important; + } + ul.delivery-head li { + margin-bottom: 1.5em !important; + } + table.delivery { + page-break-after: always; + } + .page-break { + page-break-after: always; + } +} \ No newline at end of file diff --git a/copanier/static/signing-sheet.css b/copanier/static/signing-sheet.css index 52994f4..281cb37 100644 --- a/copanier/static/signing-sheet.css +++ b/copanier/static/signing-sheet.css @@ -1,5 +1,3 @@ -table, tr, td { - border: 0px; -} -tr:nth-child(even) {background: #CCC} -tr:nth-child(odd) {background: #FFF} \ No newline at end of file +@page { + size: portrait !important; +} \ No newline at end of file diff --git a/copanier/templates/base.html b/copanier/templates/base.html index 7527d54..5183da6 100644 --- a/copanier/templates/base.html +++ b/copanier/templates/base.html @@ -18,12 +18,11 @@ {% endif %} diff --git a/copanier/templates/delivery.html b/copanier/templates/delivery.html index dfcd162..6e5484c 100644 --- a/copanier/templates/delivery.html +++ b/copanier/templates/delivery.html @@ -1,13 +1,23 @@ {% extends "base.html" %} +{% block actions %} +{% include "includes/order_button.html" %} +{% endblock %} {% block body %} -

{{ delivery.name }} {% include "includes/order_button.html" %}

{% include "includes/delivery_head.html" %} + {% if request['user'].email == delivery.contact %}
Vous êtes la personne référente de cette distribution Voir la boîte à outils
{% elif request['user'].email in delivery.get_referents() %}
Vous êtes référent⋅e pour cette distribution (merci !). Gérer les produits dont vous vous occupez
{% endif %} +
{% if delivery.has_products %} {% include "includes/delivery_table.html" %} @@ -22,7 +32,7 @@