mirror of
https://github.com/almet/copanier.git
synced 2025-04-28 19:42:37 +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 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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
{% extends "base.html" %}
|
{% 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 %}
|
{% 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> {{ 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> <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 %}
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
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),
|
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))
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue