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 Ajouter un moyen de changer le nom de la personne référente pour un producteur / productrice
x Faciliter la duplication de distribution 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 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 Permettre la suppression de producteurs
x Ajouter une info « prix mis à jour » pour les référent⋅e⋅s 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 Gérer le souci d'URL pour l'édition d'Apiluly
Rendre le formulaire d'édition producteur plus compact 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) 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( send_from_template(
env, env,
"order_summary", "order_summary",
@ -47,4 +47,5 @@ def send_order(request, env, person, delivery, order):
order=order, order=order,
delivery=delivery, delivery=delivery,
request=request, request=request,
group_id=group_id,
) )

View file

@ -240,16 +240,25 @@ class Order(Base):
def __iter__(self): def __iter__(self):
yield from self.products.items() yield from self.products.items()
def total(self, products): def total(self, products, delivery, email=None, include_shipping=True):
def _get_price(ref): def _get_price(ref):
product = products.get(ref) product = products.get(ref)
return product.price if product and not product.rupture else 0 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} 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 @property
def has_adjustments(self): def has_adjustments(self):
return any(choice.adjustment for email, choice in self) return any(choice.adjustment for email, choice in self)
@ -277,6 +286,7 @@ class Delivery(PersistedBase):
products: List[Product] = field(default_factory=list) products: List[Product] = field(default_factory=list)
producers: Dict[str, Producer] = field(default_factory=dict) producers: Dict[str, Producer] = field(default_factory=dict)
orders: Dict[str, Order] = 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): def __post_init__(self):
self.id = None # Not a field because we don't want to persist it. self.id = None # Not a field because we don't want to persist it.
@ -307,7 +317,7 @@ class Delivery(PersistedBase):
@property @property
def total(self): 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 @property
def is_open(self): def is_open(self):
@ -431,11 +441,20 @@ class Delivery(PersistedBase):
self.products.remove(product) self.products.remove(product)
return 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] producer_products = [p for p in self.products if p.producer == producer]
if person: if person:
return self.orders.get(person).total(producer_products) return self.orders.get(person).total(
return round(sum(o.total(producer_products) for o in self.orders.values()), 2) 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): def get_producers_for_referent(self, referent):
return { return {
@ -450,4 +469,24 @@ class Delivery(PersistedBase):
def total_for(self, person): def total_for(self, person):
if person.email not in self.orders: if person.email not in self.orders:
return 0 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" %} {% 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 %} {% block body %}
<p class="info">{{ delivery.name }} du {{ delivery.to_date | date }}.</p> <div class="header">
<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> <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"> <table class="paiements">
<tr> <tr>
<td></td> <td></td>
@ -20,4 +28,7 @@
{% endfor %} {% endfor %}
</table> </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 %} {% 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 }} {% for product in delivery.products %}{% if order[product].quantity %}{{ product.name }} | {{ product.price }} € | {{ order[product].quantity }} x {{ product.unit }}
{% endif %}{% endfor %} {% 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 }} Distribution: {{ delivery.where }}, le {{ delivery.from_date|date }} de {{ delivery.from_date|time }} à {{ delivery.to_date|time }}

View file

@ -61,6 +61,21 @@
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% 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> <tr>
<th class="total"><i class="icon-pricetags"></i> Total</th><td></td> <th class="total"><i class="icon-pricetags"></i> Total</th><td></td>
{% if delivery.has_packing %} {% if delivery.has_packing %}
@ -70,7 +85,7 @@
<th class="total">{{ delivery.total_for_producer(producer) }} €</th> <th class="total">{{ delivery.total_for_producer(producer) }} €</th>
{% if not list_only %} {% if not list_only %}
{% for email, order in delivery.orders.items() %} {% 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 %} {% endfor %}
{% endif %} {% endif %}
</tr> </tr>

View file

@ -9,4 +9,4 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</table> </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"> <li class="pure-menu-item">
<a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/supprimer">Supprimer</a> <a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/supprimer">Supprimer</a>
</li> </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) %} {% if producer.needs_price_update(delivery) %}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/valider-prix">Marquer les prix comme OK</a> <a class="pure-menu-link" href="/distribution/{{ delivery.id}}/{{ producer.id }}/valider-prix">Marquer les prix comme OK</a>
@ -61,6 +64,12 @@
</div> </div>
</form> </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 %} {% if products %}
<h3>Produits <h3>Produits
<a class="button" href="/distribution/{{ delivery.id}}/{{ producer.id }}/ajouter-produit">Ajouter un produit</a> <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), person=Person(email=email),
delivery=delivery, delivery=delivery,
order=order, order=order,
group_id=orderer.group_id,
) )
else: else:
emails.send_order( emails.send_order(
@ -252,6 +253,7 @@ async def place_order(request, response, id):
person=Person(email=orderer.email), person=Person(email=orderer.email),
delivery=delivery, delivery=delivery,
order=order, order=order,
group_id=orderer.email,
) )
response.message( response.message(
f"La commande pour « {orderer.name} » a bien été prise en compte, " f"La commande pour « {orderer.name} » a bien été prise en compte, "
@ -326,7 +328,9 @@ async def delivery_balance(request, response, id):
balance = [] balance = []
for group_id, order in delivery.orders.items(): 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 = {} producer_groups = {}
@ -347,6 +351,7 @@ async def delivery_balance(request, response, id):
group_id = producer.referent_name group_id = producer.referent_name
amount = delivery.total_for_producer(producer.id) amount = delivery.total_for_producer(producer.id)
print(producer.id, amount)
if amount: if amount:
balance.append((group_id, 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"]) @app.route("/distribution/{id}/copier", methods=["GET"])
async def copy_products(request, response, id): async def copy_products(request, response, id):
deliveries = Delivery.all() deliveries = Delivery.all()