copanier/copanier/views/delivery.py

353 lines
12 KiB
Python

from collections import defaultdict
from functools import partial
from roll import HttpError
from debts.solver import order_balance, check_balance, reduce_balance
from .core import app, session, env
from ..models import Delivery, Person, Order, ProductOrder, Groups, SavedConfiguration
from .. import utils, reports, emails, config
@app.listen("startup")
async def on_startup():
Delivery.init_fs()
@app.route("/", methods=["GET"])
async def home(request, response):
if not Delivery.is_defined() and not Groups.is_defined():
response.redirect = app.url_for("onboarding")
return
if not request["user"].group_id:
response.redirect = app.url_for("groups")
return
response.html(
"delivery/list_deliveries.html",
incoming=Delivery.incoming(),
former=Delivery.former(),
)
@app.route("/distribution", methods=["GET"])
async def new_delivery(request, response):
response.html("delivery/edit_delivery.html", delivery={})
@app.route("/distribution", methods=["POST"])
async def create_delivery(request, response):
form = request.form
data = {}
data["from_date"] = f"{form.get('date')} {form.get('from_time')}"
data["to_date"] = f"{form.get('date')} {form.get('to_time')}"
for name in Delivery.__dataclass_fields__.keys():
if name in form:
data[name] = form.get(name)
delivery = Delivery(**data)
delivery.persist()
response.message("La distribution a bien été créée!")
response.redirect = f"/distribution/{delivery.id}"
@app.route("/distribution/{id}/{producer}/bon-de-commande.pdf", methods=["GET"])
async def pdf_for_producer(request, response, id, producer):
delivery = Delivery.load(id)
response.pdf(
"list_products.html",
{"list_only": True, "delivery": delivery, "producers": [producer]},
filename=utils.prefix(f"bon-de-commande-{producer}.pdf", delivery),
)
@app.route("/distribution/{id}/gérer", methods=["GET"])
async def show_delivery_toolbox(request, response, id):
delivery = Delivery.load(id)
response.html(
"delivery/show_toolbox.html",
{
"delivery": delivery,
"referents": [p.referent for p in delivery.producers.values()],
},
)
@app.route("/distribution/{id}/envoi-email-referentes", methods=["GET", "POST"])
async def send_referent_emails(request, response, id):
delivery = Delivery.load(id)
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)
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=attachments,
)
response.message(f"Un mail à été envoyé aux {sent_mails} référent⋅e⋅s")
response.redirect = f"/distribution/{id}/gérer"
response.html("delivery/prepare_referent_email.html", {"delivery": delivery})
@app.route("/distribution/{id}/exporter", methods=["GET"])
async def export_products(request, response, id):
delivery = Delivery.load(id)
response.xlsx(reports.products(delivery))
@app.route("/distribution/{id}/edit", methods=["GET"])
async def edit_delivery(request, response, id):
delivery = Delivery.load(id)
response.html("delivery/edit_delivery.html", {"delivery": delivery})
@app.route("/distribution/{id}/edit", methods=["POST"])
async def post_delivery(request, response, id):
delivery = Delivery.load(id)
form = request.form
delivery.from_date = f"{form.get('date')} {form.get('from_time')}"
delivery.to_date = f"{form.get('date')} {form.get('to_time')}"
for name in Delivery.__dataclass_fields__.keys():
if name in form:
setattr(delivery, name, form.get(name))
delivery.persist()
response.message("La distribution a bien été mise à jour!")
response.redirect = f"/distribution/{delivery.id}"
@app.route("/distribution/{id}", methods=["GET"])
async def show_delivery(request, response, id):
delivery = Delivery.load(id)
response.html("delivery/show_delivery.html", {"delivery": delivery})
@app.route("/distribution/{id}/commander", methods=["POST", "GET"])
async def place_order(request, response, id):
delivery = Delivery.load(id)
# email = request.query.get("email", None)
user = session.user.get(None)
orderer = request.query.get("orderer", None)
if orderer:
orderer = Person(email=orderer, group_id=orderer)
delivery_url = f"/distribution/{delivery.id}"
if not orderer and user:
orderer = user
if not orderer:
response.message("Impossible de comprendre pour qui passer commande…", "error")
response.redirect = delivery_url
return
if request.method == "POST":
# When the delivery is closed, only staff can access.
if delivery.status == delivery.CLOSED and not (user and user.is_staff):
response.message("La distribution est fermée", "error")
response.redirect = delivery_url
return
form = request.form
order = Order(phone_number=form.get("phone_number", ""))
for product in delivery.products:
try:
wanted = form.int(f"wanted:{product.ref}", 0)
except HttpError:
continue
try:
adjustment = form.int(f"adjustment:{product.ref}", 0)
except HttpError:
adjustment = 0
if wanted or adjustment:
order.products[product.ref] = ProductOrder(
wanted=wanted, adjustment=adjustment
)
if not delivery.orders:
delivery.orders = {}
if not order.products:
if orderer.id in delivery.orders:
del delivery.orders[orderer.id]
delivery.persist()
response.message("La commande est vide.", status="warning")
response.redirect = delivery_url
return
delivery.orders[orderer.id] = order
delivery.persist()
if user and orderer.id == user.id:
# Send the emails to everyone in the group.
groups = request["groups"].groups
if orderer.group_id in groups.keys():
for email in groups[orderer.group_id].members:
emails.send_order(
request,
env,
person=Person(email=email),
delivery=delivery,
order=order,
group_id=orderer.group_id,
url_for=app.url_for,
)
else:
emails.send_order(
request,
env,
person=Person(email=orderer.email),
delivery=delivery,
order=order,
group_id=orderer.email,
url_for=app.url_for,
)
response.message(
f"La commande pour « {orderer.name} » a bien été prise en compte, "
"on a envoyé un récap par email 😘"
)
response.redirect = f"/distribution/{delivery.id}"
else:
order = delivery.orders.get(orderer.id) or Order()
force_adjustment = "adjust" in request.query and user and user.is_staff
response.html(
"delivery/place_order.html",
delivery=delivery,
person=orderer,
order=order,
force_adjustment=force_adjustment,
)
@app.route("/distribution/{id}/résumé-de-commandes", methods=["GET"])
async def show_orders_summary(request, response, id):
delivery = Delivery.load(id)
response.pdf(
"delivery/show_orders_summary.html",
{"delivery": delivery},
css="order-summary.css",
filename=utils.prefix("résumé-de-commandes.pdf", delivery),
)
@app.route("/distribution/{id}/résumé-de-commandes.html", methods=["GET"])
async def show_orders_summary(request, response, id):
delivery = Delivery.load(id)
response.html(
"delivery/show_orders_summary.html",
delivery=delivery,
display_prices=True
)
@app.route("/distribution/{id}/rapport-complet.xlsx", methods=["GET"])
async def generate_report(request, response, id):
delivery = Delivery.load(id)
date = delivery.to_date.strftime("%Y-%m-%d")
response.xlsx(
reports.full(delivery),
filename=f"{config.SITE_NAME}-{date}-rapport-complet.xlsx",
)
@app.route("/distribution/{id}/ajuster/{ref}", methods=["GET", "POST"])
async def adjust_product(request, response, id, ref):
delivery = Delivery.load(id)
delivery_url = f"/distribution/{delivery.id}"
product = None
for product in delivery.products:
if product.ref == ref:
break
else:
response.message(f"Référence inconnue: {ref}")
response.redirect = delivery_url
return
if request.method == "POST":
form = request.form
for email, order in delivery.orders.items():
choice = order[product]
choice.adjustment = form.int(email, 0)
order[product] = choice
delivery.persist()
response.message(f"Le produit «{product.ref}» a bien été ajusté!")
response.redirect = delivery_url
else:
response.html(
"delivery/adjust_product.html", {"delivery": delivery, "product": product}
)
@app.route("/distribution/{id}/paiements", methods=["GET"])
async def compute_payments(request, response, id):
delivery = Delivery.load(id)
groups = request["groups"]
balance = []
for group_id, order in delivery.orders.items():
balance.append(
(group_id, order.total(delivery.products, delivery, group_id) * -1)
)
producer_groups = {}
for producer in delivery.producers.values():
group = groups.get_user_group(producer.referent)
# When a group contains multiple producer contacts,
# the first one is elected to receive the money,
# and all the other ones are separated in the table.
group_id = None
if hasattr(group, "id"):
if (
group.id not in producer_groups
or producer_groups[group.id] == producer.referent_name
):
producer_groups[group.id] = producer.referent_name
group_id = group.id
if not group_id:
group_id = producer.referent_name
amount = delivery.total_for_producer(producer.id)
print(producer.id, amount)
if amount:
balance.append((group_id, amount))
debiters, crediters = order_balance(balance)
results = reduce_balance(debiters[:], crediters[:])
results_dict = defaultdict(partial(defaultdict, float))
for debiter, amount, crediter in results:
results_dict[debiter][crediter] = amount
template_name = "delivery/compute_balance.html"
template_args = {
"delivery": delivery,
"debiters": debiters,
"crediters": crediters,
"results": results_dict,
"debiters_groups": groups.groups,
"crediters_groups": producer_groups,
}
response.html(template_name, template_args)