first working version for shipping fees

This commit is contained in:
Alexis MÃtaireau 2020-04-05 22:02:02 +02:00
parent 55100260d3
commit 6007a2aa3a
11 changed files with 145 additions and 18 deletions

3
TODO
View file

@ -10,10 +10,11 @@ x Ajouter un moyen d'ajouter un⋅e producteurice
x Ajouter un moyen de changer le nom de la personne référente pour un producteur / productrice
x Faciliter la duplication de distribution
x Si un produit est en rupture de stock, alors il n'est pas compté dans les totaux
x Permettre la supression des produits
x Permettre la suppression de producteurs
x Ajouter une info « prix mis à jour » pour les référent⋅e⋅s
Permettre la supression des produits (terminer)
Gérer le souci d'URL pour l'édition d'Apiluly
Rendre le formulaire d'édition producteur plus compact

View file

@ -37,7 +37,7 @@ def send_from_template(env, template, to, subject, **params):
send(to, subject, body=txt, html=html)
def send_order(request, env, person, delivery, order):
def send_order(request, env, person, delivery, order, group_id):
send_from_template(
env,
"order_summary",
@ -47,4 +47,5 @@ def send_order(request, env, person, delivery, order):
order=order,
delivery=delivery,
request=request,
group_id=group_id,
)

View file

@ -240,16 +240,25 @@ class Order(Base):
def __iter__(self):
yield from self.products.items()
def total(self, products):
def total(self, products, delivery, email=None, include_shipping=True):
def _get_price(ref):
product = products.get(ref)
return product.price if product and not product.rupture else 0
producers = set([p.producer for p in products])
products = {p.ref: p for p in products}
return round(
sum(p.quantity * _get_price(ref) for ref, p in self.products.items()), 2
total_products = sum(
p.quantity * _get_price(ref) for ref, p in self.products.items()
)
total_shipping = 0
if include_shipping:
for producer in producers:
total_shipping = total_shipping + delivery.shipping_for(email, producer)
return round(total_products + total_shipping, 2)
@property
def has_adjustments(self):
return any(choice.adjustment for email, choice in self)
@ -277,6 +286,7 @@ class Delivery(PersistedBase):
products: List[Product] = field(default_factory=list)
producers: Dict[str, Producer] = field(default_factory=dict)
orders: Dict[str, Order] = field(default_factory=dict)
shipping: Dict[str, price_field] = field(default_factory=dict)
def __post_init__(self):
self.id = None # Not a field because we don't want to persist it.
@ -307,7 +317,7 @@ class Delivery(PersistedBase):
@property
def total(self):
return round(sum(o.total(self.products) for o in self.orders.values()), 2)
return round(sum(o.total(self.products, self) for o in self.orders.values()), 2)
@property
def is_open(self):
@ -431,11 +441,20 @@ class Delivery(PersistedBase):
self.products.remove(product)
return product
def total_for_producer(self, producer, person=None):
def total_for_producer(self, producer, person=None, include_shipping=True):
producer_products = [p for p in self.products if p.producer == producer]
if person:
return self.orders.get(person).total(producer_products)
return round(sum(o.total(producer_products) for o in self.orders.values()), 2)
return self.orders.get(person).total(
producer_products, self, person, include_shipping
)
return round(
sum(
o.total(producer_products, self, person, include_shipping=False)
for o in self.orders.values()
)
+ self.shipping.get(producer, 0),
2,
)
def get_producers_for_referent(self, referent):
return {
@ -450,4 +469,24 @@ class Delivery(PersistedBase):
def total_for(self, person):
if person.email not in self.orders:
return 0
return self.orders[person.email].total(self.products)
return self.orders[person.email].total(self.products, self)
def shipping_for(self, person, producer):
producer_shipping = self.shipping.get(producer)
if not producer_shipping:
return 0
if not person:
return producer_shipping
producer_total = (
self.total_for_producer(producer, include_shipping=False)
- producer_shipping
)
person_amount = self.total_for_producer(
producer, person=person, include_shipping=False
)
percentage_person = person_amount / producer_total
shipping = percentage_person * producer_shipping
return shipping

View file

@ -1,8 +1,16 @@
{% extends "base.html" %}
{% block title %}<h1><i class="icon-wallet"></i>&nbsp; Répartition des paiements <a id="download" class="button" href="/distribution/{{ delivery.id }}/solde.pdf">Télécharger</a></h1>{% endblock %}
{% block body %}
<p class="info">{{ delivery.name }} du {{ delivery.to_date | date }}.</p>
<p class="info">Les personnes indiquées avec un <code>*</code> à côté de leur nom sont celles qui ont payé cette commande pour leur groupe.</p>
<div class="header">
<h1><i class="icon-wallet"></i>&nbsp; {{ delivery.name }} : Répartition des paiements</h1>
<div class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list">
<li class="pure-menu-item">
<a class="pure-menu-link" href="/distribution/{{ delivery.id }}/solde.pdf">Télécharger</a>
</li>
</ul>
</div>
</div>
<table class="paiements">
<tr>
<td></td>
@ -20,4 +28,7 @@
{% endfor %}
</table>
<p class="info"><i class="icon-lightbulb"></i>&nbsp; <strong>Mais, comment ça marche ?</strong> La répartition des chèques se fait automatiquement, en soustrayant ce que les personnes doivent (au nom de leur coloc / groupe) à ce qui leur est du (dans le cas où elles sont référentes pour certains produits).</p>
<p class="info">Les personnes indiquées avec un <code>*</code> à côté de leur nom sont celles qui ont payé cette commande pour leur groupe.</p>
{% endblock body %}

View file

@ -7,7 +7,7 @@ Produit | Prix unitaire | Quantité
{% for product in delivery.products %}{% if order[product].quantity %}{{ product.name }} | {{ product.price }} € | {{ order[product].quantity }} x {{ product.unit }}
{% endif %}{% endfor %}
Total: {{ order.total(delivery.products) if order else 0 }} €
Total: {{ order.total(delivery.products, delivery, group_id) if order else 0 }} €
Distribution: {{ delivery.where }}, le {{ delivery.from_date|date }} de {{ delivery.from_date|time }} à {{ delivery.to_date|time }}

View file

@ -61,6 +61,21 @@
{% endif %}
</tr>
{% endfor %}
{% if delivery.shipping.get(producer) %}
<tr>
<th class="shipping"><i class="icon-map"></i>Frais de livraison</th>
<td></td>
{% if delivery.has_packing %}
<td></td>
{% endif %}
<th class="shipping">{{ delivery.shipping[producer] }} €</th>
{% if not list_only %}
{% for email, order in delivery.orders.items() %}
<td>{{ delivery.shipping_for(email, producer)|round(2) }} €</td>
{% endfor %}
{% endif %}
</tr>
{% endif %}
<tr>
<th class="total"><i class="icon-pricetags"></i> Total</th><td></td>
{% if delivery.has_packing %}
@ -70,7 +85,7 @@
<th class="total">{{ delivery.total_for_producer(producer) }} €</th>
{% if not list_only %}
{% for email, order in delivery.orders.items() %}
<td>{{ order.total(delivery.get_products_by(producer)) }} €</td>
<td>{{ order.total(delivery.get_products_by(producer), delivery, email) }} €</td>
{% endfor %}
{% endif %}
</tr>

View file

@ -9,4 +9,4 @@
{% endif %}
{% endfor %}
</table>
<p>Total: {{ order.total(delivery.products) if order else 0 }} €</p>
<p>Total: {{ order.total(delivery.products, delivery, group_id) if order else 0 }} €</p>

View file

@ -13,6 +13,9 @@
<li class="pure-menu-item">
<a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/supprimer">Supprimer</a>
</li>
<li class="pure-menu-list">
<a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/frais-de-livraison">Gérer des frais de livraison</a>
</li>
{% if producer.needs_price_update(delivery) %}
<li class="pure-menu-item">
<a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/valider-prix">Marquer les prix comme OK</a>
@ -61,6 +64,12 @@
</div>
</form>
{% if delivery.shipping.get(producer.id) %}
<h3>Frais de livraison</h3>
Des frais de livraison de {{ delivery.shipping[producer.id] }}€ ont été enregistrés pour ce producteur.
<a class="button" href="/distribution/{{ delivery.id}}/{{ producer.id }}/frais-de-livraison">Modifier</a>
{% endif %}
{% if products %}
<h3>Produits
<a class="button" href="/distribution/{{ delivery.id}}/{{ producer.id }}/ajouter-produit">Ajouter un produit</a>

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block toplink %}<a href="/distribution/{{ delivery.id }}/produits">↶ Retourner aux produits</a>{% endblock %}
{% block body %}
<div class="header">
<h1>Frais de port pour « {{ producer.name }} »</h1>
</div>
<form method="post">
<label>
<h5>Coût des frais de port (ne les indiquer qu'une fois connus)</h5>
<input type="number" name="shipping" value="{{ shipping }}">
</label>
<div>
<input type="submit" name="submit" value="Valider" class="primary">
</div>
</form>
{% endblock body %}

View file

@ -244,6 +244,7 @@ async def place_order(request, response, id):
person=Person(email=email),
delivery=delivery,
order=order,
group_id=orderer.group_id,
)
else:
emails.send_order(
@ -252,6 +253,7 @@ async def place_order(request, response, id):
person=Person(email=orderer.email),
delivery=delivery,
order=order,
group_id=orderer.email,
)
response.message(
f"La commande pour « {orderer.name} » a bien été prise en compte, "
@ -326,7 +328,9 @@ async def delivery_balance(request, response, id):
balance = []
for group_id, order in delivery.orders.items():
balance.append((group_id, order.total(delivery.products) * -1))
balance.append(
(group_id, order.total(delivery.products, delivery, group_id) * -1)
)
producer_groups = {}
@ -347,6 +351,7 @@ async def delivery_balance(request, response, id):
group_id = producer.referent_name
amount = delivery.total_for_producer(producer.id)
print(producer.id, amount)
if amount:
balance.append((group_id, amount))

View file

@ -228,6 +228,34 @@ async def create_product(request, response, delivery_id, producer_id):
)
@app.route(
"/distribution/{delivery_id}/{producer_id}/frais-de-livraison",
methods=["GET", "POST"],
)
async def set_shipping_price(request, response, delivery_id, producer_id):
delivery = Delivery.load(delivery_id)
producer = delivery.producers.get(producer_id)
if request.method == "POST":
form = request.form
shipping = form.float("shipping")
delivery.shipping[producer_id] = shipping
delivery.persist()
response.message("Les frais de livraison ont bien été enregistrés, merci !")
response.redirect = f"/distribution/{delivery_id}/produits"
return
response.html(
"products/shipping_fees.html",
{
"delivery": delivery,
"producer": producer,
"shipping": delivery.shipping.get(producer_id, ""),
},
)
@app.route("/distribution/{id}/copier", methods=["GET"])
async def copy_products(request, response, id):
deliveries = Delivery.all()