mirror of
https://github.com/almet/copanier.git
synced 2025-04-28 11:32:38 +02:00
first working version for shipping fees
This commit is contained in:
parent
55100260d3
commit
6007a2aa3a
11 changed files with 145 additions and 18 deletions
3
TODO
3
TODO
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}<h1><i class="icon-wallet"></i> 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> {{ 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>
|
||||
|
@ -13,11 +21,14 @@
|
|||
<td>{% if debiter[0] in debiters_groups %} {{ debiters_groups[debiter[0]].name }}{% else %}{{ debiter[0] }}{% endif %} ({{ debiter[1] | round(2) }})</td>
|
||||
{% for crediter in crediters %}
|
||||
{% set due_amount = results[debiter[0]][crediter[0]] | round(2) %}
|
||||
|
||||
|
||||
<td>{% if due_amount != 0.00 %}{{due_amount}}{% endif %}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<p class="info"><i class="icon-lightbulb"></i> <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 %}
|
||||
|
|
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
18
copanier/templates/products/shipping_fees.html
Normal file
18
copanier/templates/products/shipping_fees.html
Normal 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 %}
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue