mirror of
https://github.com/almet/copanier.git
synced 2025-04-28 19:42:37 +02:00
Online edition + manage delivery + send emails.
This commit is contained in:
parent
ff79712fb5
commit
f96ca0a192
16 changed files with 398 additions and 23 deletions
|
@ -341,7 +341,114 @@ async def import_products(request, response, id):
|
|||
response.redirect = f"/livraison/{delivery.id}"
|
||||
|
||||
|
||||
@app.route("/livraison/{id}/exporter/produits", methods=["GET"])
|
||||
@app.route("/livraison/{delivery_id}/producteurices")
|
||||
async def list_producers(request, response, delivery_id):
|
||||
delivery = Delivery.load(delivery_id)
|
||||
response.html("list_products.html", {
|
||||
'list_only': True,
|
||||
'edit_mode': True,
|
||||
'delivery': delivery,
|
||||
'referent': request.query.get('referent', None),
|
||||
})
|
||||
|
||||
|
||||
@app.route("/livraison/{delivery_id}/{producer_id}/éditer", methods=["GET", "POST"])
|
||||
async def edit_producer(request, response, delivery_id, producer_id):
|
||||
delivery = Delivery.load(delivery_id)
|
||||
producer = delivery.producers.get(producer_id)
|
||||
if request.method == 'POST':
|
||||
form = request.form
|
||||
producer.referent = form.get('referent')
|
||||
producer.tel_referent = form.get('tel_referent')
|
||||
producer.description = form.get('description')
|
||||
producer.contact = form.get('contact')
|
||||
delivery.producers[producer_id] = producer
|
||||
delivery.persist()
|
||||
|
||||
response.html("edit_producer.html", {
|
||||
'delivery': delivery,
|
||||
'producer': producer,
|
||||
'products': delivery.get_products_by(producer.id)
|
||||
})
|
||||
|
||||
|
||||
@app.route("/livraison/{delivery_id}/{producer_id}/{product_ref}/éditer", methods=["GET", "POST"])
|
||||
async def edit_product(request, response, delivery_id, producer_id, product_ref):
|
||||
delivery = Delivery.load(delivery_id)
|
||||
product = delivery.get_product(product_ref)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = request.form
|
||||
product.name = form.get('name')
|
||||
product.price = form.float('price')
|
||||
product.unit = form.get('unit')
|
||||
product.description = form.get('description')
|
||||
product.url = form.get('url')
|
||||
if form.get('packing'):
|
||||
product.packing = form.int('packing')
|
||||
if 'rupture' in form:
|
||||
product.rupture = form.get('rupture')
|
||||
else:
|
||||
product.rupture = None
|
||||
delivery.persist()
|
||||
response.message('Le produit à bien été édité')
|
||||
response.redirect = f'/livraison/{delivery_id}/producteurice/{producer_id}/éditer'
|
||||
|
||||
response.html("edit_product.html", {
|
||||
'delivery': delivery,
|
||||
'product': product
|
||||
})
|
||||
|
||||
@app.route("/livraison/{delivery_id}/{producer_id}/ajouter-produit", methods=["GET", "POST"])
|
||||
async def create_product(request, response, delivery_id, producer_id):
|
||||
delivery = Delivery.load(delivery_id)
|
||||
product = Product(name="", ref="", price=0)
|
||||
|
||||
if request.method == 'POST':
|
||||
product.producer = producer_id
|
||||
form = request.form
|
||||
product.update_from_form(form)
|
||||
product.ref = slugify(f"{producer_id}-{product.name}")
|
||||
|
||||
delivery.products.append(product)
|
||||
delivery.persist()
|
||||
response.message('Le produit à bien été créé')
|
||||
response.redirect = f'/livraison/{delivery_id}/producteurice/{producer_id}/éditer'
|
||||
|
||||
response.html("edit_product.html", {
|
||||
'delivery': delivery,
|
||||
'producer_id': producer_id,
|
||||
'product': product,
|
||||
})
|
||||
|
||||
@app.route("/livraison/{id}/gérer", methods=['GET'])
|
||||
async def manage_delivery(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.html("manage_delivery.html",{
|
||||
'delivery': delivery
|
||||
})
|
||||
|
||||
@app.route("/livraison/{id}/envoi-email-referentes", methods=['GET', 'POST'])
|
||||
async def send_referent_emails(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
date = delivery.to_date.strftime("%Y-%m-%d")
|
||||
if request.method == 'POST':
|
||||
email_body = request.form.get('email_body')
|
||||
email_subject = request.form.get('email_subject')
|
||||
for referent in delivery.get_referents():
|
||||
producers = delivery.get_producers_for_referent(referent)
|
||||
summary = reports.summary(delivery, producers)
|
||||
emails.send(referent, email_subject, email_body, copy=delivery.contact, attachments=[
|
||||
(f"{config.SITE_NAME}-{date}-{referent}.xlsx", summary)
|
||||
])
|
||||
response.message("Le mail à bien été envoyé")
|
||||
response.redirect = f"/livraison/{id}/gérer"
|
||||
|
||||
response.html("prepare_referent_email.html", {
|
||||
'delivery': delivery
|
||||
})
|
||||
|
||||
@app.route("/livraison/{id}/exporter", methods=["GET"])
|
||||
async def export_products(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.xlsx(reports.products(delivery))
|
||||
|
@ -570,7 +677,10 @@ async def delivery_balance(request, response, id):
|
|||
|
||||
for producer in delivery.producers.values():
|
||||
group = groups.get_user_group(producer.referent)
|
||||
group_id = group.id if group else producer.referent
|
||||
if hasattr(group, 'id'):
|
||||
group_id = group.id
|
||||
else:
|
||||
group_id = group
|
||||
amount = delivery.total_for_producer(producer.id)
|
||||
if amount:
|
||||
balance.append((group_id, amount))
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.base import MIMEBase
|
||||
from email import encoders
|
||||
|
||||
from . import config
|
||||
|
||||
|
||||
def send(to, subject, body, html=None):
|
||||
msg = EmailMessage()
|
||||
msg.set_content(body)
|
||||
def send(to, subject, body, html=None, copy=None, attachments=None):
|
||||
msg = MIMEMultipart()
|
||||
msg.attach(MIMEText(body, "plain"))
|
||||
if html:
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = config.FROM_EMAIL
|
||||
msg["To"] = to
|
||||
msg["Bcc"] = config.FROM_EMAIL
|
||||
if html:
|
||||
msg.add_alternative(html, subtype="html")
|
||||
msg["Bcc"] = copy if copy else config.FROM_EMAIL
|
||||
|
||||
for file_name, attachment in attachments:
|
||||
part = MIMEBase('application','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8')
|
||||
part.set_payload(attachment)
|
||||
part.add_header('Content-Disposition',
|
||||
'attachment',
|
||||
filename=file_name)
|
||||
encoders.encode_base64(part)
|
||||
msg.attach(part)
|
||||
if not config.SEND_EMAILS:
|
||||
return print("Sending email", str(msg))
|
||||
try:
|
||||
|
|
|
@ -152,7 +152,7 @@ class Groups(PersistedBase):
|
|||
for group in self.groups.values():
|
||||
if email in group.members:
|
||||
return group
|
||||
return None
|
||||
return email
|
||||
|
||||
@classmethod
|
||||
def init_fs(cls):
|
||||
|
@ -164,7 +164,7 @@ class Producer(Base):
|
|||
referent: str = ""
|
||||
tel_referent: str = ""
|
||||
contact: str = ""
|
||||
location: str = ""
|
||||
description: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -186,6 +186,20 @@ class Product(Base):
|
|||
# out += f" ({self.unit})"
|
||||
return out
|
||||
|
||||
def update_from_form(self, form):
|
||||
self.name = form.get('name')
|
||||
self.price = form.float('price')
|
||||
self.unit = form.get('unit')
|
||||
self.description = form.get('description')
|
||||
self.url = form.get('url')
|
||||
if form.get('packing'):
|
||||
self.packing = form.int('packing')
|
||||
if 'rupture' in form:
|
||||
self.rupture = form.get('rupture')
|
||||
else:
|
||||
self.rupture = None
|
||||
return self
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProductOrder(Base):
|
||||
|
@ -388,8 +402,23 @@ class Delivery(PersistedBase):
|
|||
def get_products_by(self, producer):
|
||||
return [p for p in self.products if p.producer == producer]
|
||||
|
||||
def get_product(self, ref):
|
||||
products = [p for p in self.products if p.ref == ref]
|
||||
if products:
|
||||
return products[0]
|
||||
|
||||
def total_for_producer(self, producer, person=None):
|
||||
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)
|
||||
|
||||
def get_producers_for_referent(self, referent):
|
||||
return {
|
||||
id: producer
|
||||
for id, producer in self.producers.items()
|
||||
if producer.referent == referent
|
||||
}
|
||||
|
||||
def get_referents(self):
|
||||
return [producer.referent for producer in self.producers.values()]
|
|
@ -35,11 +35,12 @@ def summary_for_products(wb, title, delivery, total=None, products=None):
|
|||
ws.append(["", "", "", "", "Total", total])
|
||||
|
||||
|
||||
|
||||
def summary(delivery):
|
||||
def summary(delivery, producers=None):
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active)
|
||||
for producer in delivery.producers:
|
||||
if not producers:
|
||||
producers = delivery.producers
|
||||
for producer in producers:
|
||||
summary_for_products(
|
||||
wb,
|
||||
producer,
|
||||
|
|
|
@ -450,6 +450,11 @@ hr {
|
|||
.notification.warning {
|
||||
background-color: #f9b42d;
|
||||
}
|
||||
.notification.info {
|
||||
background-color: #aed1b175;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.notification i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
@ -515,6 +520,7 @@ ul.delivery-head li {
|
|||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
ul.delivery-head li i{
|
||||
float: left;
|
||||
|
|
|
@ -3,13 +3,18 @@
|
|||
{% block body %}
|
||||
<h3>{{ delivery.name }} {% include "includes/order_button.html" %}</h3>
|
||||
{% include "includes/delivery_head.html" %}
|
||||
{% if request['user'].email == delivery.contact %}
|
||||
<div class="notification info"><i class="icon-lightbulb"></i> Vous êtes la personne référente de cette distribution <a class="button" href="/livraison/{{ delivery.id }}/gérer">Gérer la distribution</a></div>
|
||||
{% elif request['user'].email in delivery.get_referents() %}
|
||||
<div class="notification info"><i class="icon-lightbulb"></i> Vous êtes référent⋅e pour cette distribution (merci !). Voici <a class="button" href="/livraison/{{ delivery.id }}/producteurices?referent={{request['user'].email}}">un petit lien pour aller voir les produits dont vous vous occupez !</a></div>
|
||||
{% endif %}
|
||||
<article class="delivery">
|
||||
{% if delivery.has_products %}
|
||||
{% include "includes/delivery_table.html" %}
|
||||
{% else %}
|
||||
<p>Aucun produit n'est encore défini pour cette livraison.</p>
|
||||
{% if request.user and request.user.is_staff %}
|
||||
<div class="inline-text">Pour rajouter des produits, il faut d'abord <a href="/livraison/{{ delivery.id }}/exporter/produits"><i class="icon-layers"></i> télécharger le tableur avec les produits</a>, le modifier localement sur votre machine puis </div> <div class="inline-text">{% with unique_id="import-products" %}{% include "includes/modal_import_products.html" %}{% endwith %}.</div>
|
||||
<div class="inline-text">Pour rajouter des produits, il faut d'abord <a href="/livraison/{{ delivery.id }}/exporter"><i class="icon-layers"></i> télécharger le tableur avec les produits</a>, le modifier localement sur votre machine puis </div> <div class="inline-text">{% with unique_id="import-products" %}{% include "includes/modal_import_products.html" %}{% endwith %}.</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</article>
|
||||
|
@ -28,7 +33,7 @@
|
|||
{% endif %}
|
||||
{% if request.user and request.user.is_staff %}
|
||||
<li>
|
||||
<a href="/livraison/{{ delivery.id }}/exporter/produits"><i class="icon-layers"></i> Liste des produits</a>
|
||||
<a href="/livraison/{{ delivery.id }}/exporter"><i class="icon-layers"></i> Liste des produits</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/livraison/{{ delivery.id }}/edit"><i class="icon-adjustments"></i> Modifier la livraison</a>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<input type="text" name="description" value="{{ delivery.description or '' }}">
|
||||
</label>
|
||||
<label>
|
||||
<p>Référent</p>
|
||||
<p>Référent⋅e</p>
|
||||
<input type="email" name="contact" value="{{ delivery.contact or request.user.email }}" required>
|
||||
</label>
|
||||
<label>
|
||||
|
@ -56,6 +56,9 @@
|
|||
{% include "includes/modal_import_products.html" %}
|
||||
{% endwith %}
|
||||
</li>
|
||||
<li>
|
||||
<a href="/livraison/{{ delivery.id }}/producteurices"><i class="icon-pencil"></i> Gérer les produits / product⋅eur⋅rice⋅s</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock body %}
|
||||
|
|
69
copanier/templates/edit_producer.html
Normal file
69
copanier/templates/edit_producer.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
{% if producer.id %}
|
||||
<h1>Modifier les informations pour « {{ producer.id }} »</h1>
|
||||
{% else %}
|
||||
<h1>Nouve⋅eau⋅lle product⋅eur⋅rice</h1>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
{% if producer.id %}
|
||||
<input type="hidden" name="id" value="{{ producer.id }}">
|
||||
{% else %}
|
||||
<label>
|
||||
<p>Nom du / de la product⋅eur⋅rice</p>
|
||||
<input type="text" name="id" value="{{ producer.id or '' }}">
|
||||
</label>
|
||||
{% endif %}
|
||||
<label>
|
||||
<h5>Description</h5>
|
||||
<input type="text" name="description" value="{{ producer.description or '' }}">
|
||||
</label>
|
||||
<label>
|
||||
<p>Email de la personne référente</p>
|
||||
<input type="email" name="referent" value="{{ producer.referent or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<p>Téléphone de la personne référente</p>
|
||||
<input type="tel" name="tel_referent" value="{{ producer.tel_referent or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<h5>Contact du⋅la product⋅eur⋅rice</h5>
|
||||
<input type="text" name="contact" value="{{ producer.contact or '' }}">
|
||||
</label>
|
||||
<div>
|
||||
<input type="submit" name="submit" value="Valider" class="primary">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if products %}
|
||||
<h3>Produits <a class="button" href="/livraison/{{ delivery.id}}/{{ producer.id }}/ajouter-produit">Ajouter un produit</a></h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Produit</th>
|
||||
<th>Prix</th>
|
||||
<th>Unité</th>
|
||||
<th>Description</th>
|
||||
<th>Packaging</th>
|
||||
<th>Rupture ?</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for product in products %}
|
||||
<tr>
|
||||
<td><a href="/livraison/{{ delivery.id }}/{{ producer.id }}/{{ product.ref }}/éditer">{{ product.name }}</a></td>
|
||||
<td>{{ product.price }}€</td>
|
||||
<td>{{ product.unit }}</td>
|
||||
<td>{{ product.description }}</td>
|
||||
<td>{% if product.packing %}{{ product.packing }}{% endif %}</td>
|
||||
<td>{% if product.rupture %}RUPTURE !!{% endif %}</td>
|
||||
<td><a href="/livraison/{{ delivery.id }}/{{ producer.id }}/{{ product.ref }}/éditer">éditer</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
{% endblock body %}
|
75
copanier/templates/edit_product.html
Normal file
75
copanier/templates/edit_product.html
Normal file
|
@ -0,0 +1,75 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
{% if product.ref %}
|
||||
<h1>Modifier le produit « {{ product.name }} »</h1>
|
||||
{% else %}
|
||||
<h1>{{ producer_id }} : Nouveau produit</h1>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<label>
|
||||
<p>Nom</p>
|
||||
<input type="text" name="name" value="{{ product.name or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<p>Prix (en €), pour une unité</p>
|
||||
<input type="number" step="0.01" name="price" value="{{ product.price or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<p>Unité de commande</p>
|
||||
<input type="text" name="unit" placeholder="sachet de 200g" value="{{ product.unit or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<h5>Description du produit</h5>
|
||||
<input type="text" name="description" placeholder="Bière type American Pale Ale" value="{{ product.description or '' }}">
|
||||
</label>
|
||||
<label>
|
||||
<h5>URL d'une image du produit</h5>
|
||||
<input type="url" name="url" placeholder="https://example.com/image.png" value="{{ product.url or '' }}">
|
||||
</label>
|
||||
<label>
|
||||
<h5>Conditionnement (par combien d'unités est vendu ce produit ?)</h5>
|
||||
<h5><em>par exemple, entrez 6 si ce sont des cartons de 6 bouteilles</em></h5>
|
||||
<input type="number" name="packing" placeholder="6" value="{{ product.packing or '' }}">
|
||||
</label>
|
||||
<br />
|
||||
<label>
|
||||
Produit actuellement en rupture ?
|
||||
<input type="checkbox" name="rupture" value="RUPTURE" {% if product.rupture %}checked="true"{% endif %}>
|
||||
</label>
|
||||
<div>
|
||||
<input type="submit" name="submit" value="Valider" class="primary">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if products %}
|
||||
<h3>Produits</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Produit</th>
|
||||
<th>Prix</th>
|
||||
<th>Unité</th>
|
||||
<th>Description</th>
|
||||
<th>Packaging</th>
|
||||
<th>Rupture ?</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for product in products %}
|
||||
<tr>
|
||||
<td><a href="/livraison/{{ delivery.id }}/produit/{{ product.ref }}/éditer">{{ product.name }}</a></td>
|
||||
<td>{{ product.price }}€</td>
|
||||
<td>{{ product.unit }}</td>
|
||||
<td>{{ product.description }}</td>
|
||||
<td>{% if product.packing %}{{ product.packing }}{% endif %}</td>
|
||||
<td>{% if product.rupture %}RUPTURE !!{% endif %}</td>
|
||||
<td><a href="/livraison/{{ delivery.id }}/produit/{{ product.ref }}/éditer">éditer</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
{% endblock body %}
|
|
@ -1,6 +1,11 @@
|
|||
{% for producer in delivery.producers %}
|
||||
<h2>{{ producer }}</h2>
|
||||
<h4>Référent⋅e : {{ delivery.producers[producer].referent }} / {{ delivery.producers[producer].tel_referent }}</h4>
|
||||
{% if referent %}
|
||||
{% set producers = delivery.get_producers_for_referent(referent) %}
|
||||
{% else %}
|
||||
{% set producers = delivery.producers %}
|
||||
{% endif %}
|
||||
{% for producer in producers %}
|
||||
<h2>{{ producer }} {% if edit_mode %}<a class="button" href="/livraison/{{ delivery.id }}/{{ producer }}/éditer"><i class="icon-ribbon"></i>Éditer</a> <a class="button" href="/livraison/{{ delivery.id }}/{{ producer }}/ajouter-produit"><i class="icon-puzzle"></i>Ajouter un produit </a>{% endif %}</h2>
|
||||
<h5>{% if delivery.producers[producer].description %}{{ delivery.producers[producer].description }}{% endif %}. Référent⋅e : {{ delivery.producers[producer].referent }} / {{ delivery.producers[producer].tel_referent }}</h5>
|
||||
<table class="delivery">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -9,6 +14,8 @@
|
|||
{% if delivery.has_packing %}
|
||||
<th class="packing">Conditionnement</th>
|
||||
{% endif %}
|
||||
{% if edit_mode %}<th>Éditer</th>{% endif %}
|
||||
{% if not list_only %}
|
||||
<th class="amount">Total</th>
|
||||
{% for orderer, order in delivery.orders.items() %}
|
||||
<th class="person{% if delivery.is_passed and not order.paid %} not-paid{% endif %}">
|
||||
|
@ -19,16 +26,18 @@
|
|||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for product in delivery.get_products_by(producer) %}
|
||||
<tr>
|
||||
<th class="product {% if product.rupture %}rupture{% endif %}">{{ product }} {% if product.rupture %}(RUPTURE !){% endif %}
|
||||
<th class="product {% if product.rupture %}rupture{% endif %}">{% if edit_mode %}<a href="/livraison/{{ delivery.id }}/{{ product.producer }}/{{ product.ref }}/éditer">{% endif %}{{ product }}{% if edit_mode %}</a>{% endif %}{% if product.rupture %} {{ product.rupture }}{% endif %}
|
||||
<td>{{ product.price | round(2) }} €</td>
|
||||
{% if delivery.has_packing %}
|
||||
<td class="packing">{% if product.packing %}{{ product.packing }} x {% endif %} {{ product.unit }}</td>
|
||||
{% endif %}
|
||||
{% if not list_only %}
|
||||
<th{% if delivery.status == delivery.ADJUSTMENT and delivery.product_missing(product) %} class="missing" title="Les commandes individuelles ne correspondent pas aux conditionnements"{% endif %}>
|
||||
{{ delivery.product_wanted(product) }}
|
||||
{% if delivery.status == delivery.ADJUSTMENT and delivery.product_missing(product) %} (−{{ delivery.product_missing(product) }})
|
||||
|
@ -38,8 +47,11 @@
|
|||
{% for email, order in delivery.orders.items() %}
|
||||
<td title="Commandé: {{ order[product.ref].wanted }} — Ajusté: {{ "%+d"|format(order[product.ref].adjustment) }}">{{ order[product.ref].quantity or "—" }}</td>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if edit_mode %}<td><a href="/livraison/{{ delivery.id }}/{{ product.producer }}/{{ product.ref }}/éditer">modifier</a></td>{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not list_only %}
|
||||
<tr>
|
||||
<th class="total"><i class="icon-pricetags"></i> Total</th><td>—</td>
|
||||
{% if delivery.has_packing %}
|
||||
|
@ -50,6 +62,7 @@
|
|||
<td>{{ order.total(delivery.get_products_by(producer)) }} €</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "includes/modal.html" %}
|
||||
|
||||
{% block modal_label %}<i class="icon-upload"></i> Importer les produits{% endblock modal_label %}
|
||||
{% block modal_label %}<i class="icon-upload"></i> Importer les produits depuis un tableur{% endblock modal_label %}
|
||||
{% block modal_body %}
|
||||
<h3>Importer des produits</h3>
|
||||
<p>Format pris en charge: xlsx</p>
|
||||
|
|
9
copanier/templates/list_products.html
Normal file
9
copanier/templates/list_products.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<h3>{{ delivery.name }}</h3>
|
||||
{% if referent %}<p>Voici la liste des product⋅eurs⋅rices dont {{ referent }} est référent⋅e. <a class="button" href="/livraison/{{delivery.id}}/producteurices">voir tous les produits</a></p>{% endif %}
|
||||
<article class="delivery">
|
||||
{% include "includes/delivery_table.html" %}
|
||||
</article>
|
||||
{% endblock body %}
|
20
copanier/templates/manage_delivery.html
Normal file
20
copanier/templates/manage_delivery.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Gérer « {{ delivery.name }} »</h1>
|
||||
|
||||
<h3>Avant et pendant la distribution</h3>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/edit"><i class="icon-pencil"></i> Modifier la commande (dates, lieu, référent⋅e, etc)</a>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/producteurices"><i class="icon-pencil"></i> Modifier les produits, les product⋅rices⋅eurs</a>
|
||||
<a class="button" href="/groupes"><i class="icon-globe"></i> Gérer les groupes / colocs</a>
|
||||
|
||||
<h3>Une fois les commandes passées (après le {{ delivery.order_before|date }})</h3>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/rapport-complet.xlsx"><i class="icon-download"></i> Télécharger le récap (global) des commandes</a>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/envoi-email-referentes"><i class="icon-envelope"></i> Envoyer les infos de commande aux référent⋅e⋅s</a>
|
||||
|
||||
<h3>Pour préparer la distribution (le {{ delivery.from_date|date }})</h3>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/émargement"><i class="icon-document"></i> Fiches de commandes par groupe</a>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/rapport-complet.xlsx"><i class="icon-grid"></i> Télécharger le résumé général des commandes</a>
|
||||
<a class="button" href="/livraison/{{ delivery.id }}/solde"><i class="icon-gears"></i> Faire la répartition des paiements</a>
|
||||
|
||||
{% endblock %}
|
21
copanier/templates/prepare_referent_email.html
Normal file
21
copanier/templates/prepare_referent_email.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Envoi d'un mail aux référent⋅e⋅s</h1>
|
||||
<p>Le mail ci dessous (vous pouvez le modifier !) va être envoyé aux référent⋅e⋅s, avec en pièce jointe le tableau comportant les commandes pour les product⋅eurs⋅rices.</p>
|
||||
|
||||
<form method="post">
|
||||
<input type="text" name="email_subject" style="width:800px" value="{{ config.SITE_NAME }} - Les commandes pour vos producteurs⋅rices" /><br />
|
||||
<textarea name="email_body" cols="80" rows="10">
|
||||
Bonjour,
|
||||
|
||||
Et voilà, les commandes maintenant terminées, il est maintenant temps de passer à l'action !
|
||||
En pièce-jointe, les informations pour les producteurs⋅rices dont tu est référent⋅e.
|
||||
|
||||
Rendez-vous pour la distribution, le {{ delivery.from_date|date }} de {{ delivery.from_date|time }} à {{ delivery.to_date|time }} à {{ delivery.where }}.
|
||||
|
||||
A bientôt !
|
||||
</textarea><br />
|
||||
<input type="submit" name="submit" value="Envoyer le mail aux référent⋅e⋅s (avec le tableur en PJ)" class="primary">
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -9,7 +9,7 @@
|
|||
<body>
|
||||
<h2>{{ delivery.name }} {{ delivery.from_date.date() }} - liste d'émargement</h2>
|
||||
{% for email, order in delivery.orders.items() %}
|
||||
<h3>{{ email }}</h3>
|
||||
<h3>{{ request.groups.groups[email].name }}</h3>
|
||||
{% include "includes/order_summary.html" %}
|
||||
<hr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -239,7 +239,7 @@ async def test_post_delivery_balance(client, delivery):
|
|||
|
||||
async def test_export_products(client, delivery):
|
||||
delivery.persist()
|
||||
resp = await client.get(f"/livraison/{delivery.id}/exporter/produits")
|
||||
resp = await client.get(f"/livraison/{delivery.id}/exporter")
|
||||
wb = load_workbook(filename=BytesIO(resp.body))
|
||||
assert list(wb.active.values) == [
|
||||
("name", "ref", "price", "unit", "description", "url", "img", "packing", "producer"),
|
||||
|
|
Loading…
Reference in a new issue