Add support for product referents

This commit is contained in:
Alexis M 2019-09-16 23:09:23 +02:00
parent f609c51366
commit 83b78b3684
5 changed files with 62 additions and 31 deletions

View file

@ -301,6 +301,9 @@ async def create_delivery(request, response):
response.redirect = f"/livraison/{delivery.id}"
@app.route("/livraison/{id}/importer/produits", methods=["POST"])
@staff_only
async def import_products(request, response, id):
@ -309,12 +312,9 @@ async def import_products(request, response, id):
data = request.files.get("data")
error_path = f"/livraison/{delivery.id}/edit"
if data.filename.endswith((".csv", ".xlsx")):
if data.filename.endswith(".xlsx"):
try:
if data.filename.endswith(".csv"):
imports.products_from_csv(delivery, data.read().decode())
else:
imports.products_from_xlsx(delivery, data)
imports.products_and_producers_from_xlsx(delivery, data)
except ValueError as err:
message = f"Impossible d'importer le fichier. {err.args[0]}"
response.message(message, status="error")
@ -324,7 +324,7 @@ async def import_products(request, response, id):
response.message("Format de fichier inconnu", status="error")
response.redirect = error_path
return
response.message("Les produits de la livraison ont bien été mis à jour!")
response.message("Les produits et producteur⋅ice⋅s ont bien été mis à jour!")
response.redirect = f"/livraison/{delivery.id}"

View file

@ -9,15 +9,15 @@ from .models import Product, Producer
PRODUCT_FIELDS = {"ref", "name", "price"}
PRODUCER_FIELDS = {"id", "name"}
PRODUCER_FIELDS = {"id"}
def append_list(field, item):
return field.append(item)
field.append(item)
def append_dict(field, item):
return field(item.id, item)
field[item.id] = item
def items_from_xlsx(data, items, model_class, required_fields, append_method):
@ -25,7 +25,7 @@ def items_from_xlsx(data, items, model_class, required_fields, append_method):
raise ValueError
headers = data[0]
if not set(headers) >= required_fields:
raise ValueError(f"Colonnes obligatoires: {', '.join(required_fields)}")
raise ValueError(f"Colonnes obligatoires: {', '.join(required_fields)}.")
for row in data[1:]:
raw = {k: v for k, v in dict(zip(headers, row)).items() if v}
try:
@ -37,15 +37,19 @@ def items_from_xlsx(data, items, model_class, required_fields, append_method):
def products_and_producers_from_xlsx(delivery, data):
if not isinstance(data, Workbook):
try:
data = load_workbook(data)
except BadZipFile:
raise ValueError("Impossible de lire le fichier")
try:
data = load_workbook(data)
except BadZipFile:
raise ValueError("Impossible de lire le fichier")
sheet_names = data.get_sheet_names()
if len(sheet_names) != 2:
raise ValueError("Le fichier doit comporter deux onglets.")
# First, get the products data from the first tab.
delivery.products = items_from_xlsx(list(data.active.values), [], Products, PRODUCT_FIELDS, append_list)
products_sheet = data.get_sheet_by_name(sheet_names[0])
delivery.products = items_from_xlsx(list(products_sheet.values), [], Product, PRODUCT_FIELDS, append_list)
# Then import producers info
delivery.producers = items_from_xlsx(list(data.active.values), {}, Producer, PRODUCER_FIELDS, append_dict)
producers_sheet = data.get_sheet_by_name(sheet_names[1])
delivery.producers = items_from_xlsx(list(producers_sheet.values), {}, Producer, PRODUCER_FIELDS, append_dict)
delivery.persist()

View file

@ -82,6 +82,7 @@ class PersistedBase(Base):
return Path(config.DATA_ROOT) / cls.__root__
@dataclass
class Person(Base):
email: str
@ -149,6 +150,14 @@ class Groups(PersistedBase):
def init_fs(cls):
cls.get_root().mkdir(parents=True, exist_ok=True)
@dataclass
class Producer(Base):
id: str
referent: str = ""
contact: str = ""
location: str = ""
@dataclass
class Product(Base):
name: str
@ -230,6 +239,7 @@ class Delivery(PersistedBase):
instructions: str = ""
where: str = "Marché de la Briche"
products: List[Product] = field(default_factory=list)
producers: Dict[str, Producer] = field(default_factory=dict)
orders: Dict[str, Order] = field(default_factory=dict)
infos_url: str = ""
@ -255,13 +265,13 @@ class Delivery(PersistedBase):
def total(self):
return round(sum(o.total(self.products) for o in self.orders.values()), 2)
@property
def producers(self):
return list(set([p.producer for p in self.products]))
# @property
# def producers(self):
# return list(set([p.producer for p in self.products]))
@property
def has_multiple_producers(self):
return len(self.producers) > 1
# @property
# def has_multiple_producers(self):
# return len(self.producers) > 1
@property
def is_open(self):

View file

@ -3,7 +3,7 @@ from dataclasses import fields as get_fields
from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook
from .models import Product
from .models import Product, Producer
def summary_for_products(wb, title, delivery, total=None, products=None):
if products == None:
@ -87,10 +87,17 @@ def products(delivery):
wb = Workbook()
ws = wb.active
ws.title = f"{delivery.name} produits"
fields = [f.name for f in get_fields(Product)]
ws.append(fields)
product_fields = [f.name for f in get_fields(Product)]
ws.append(product_fields)
for product in delivery.products:
ws.append([getattr(product, field) for field in fields])
ws.append([getattr(product, field) for field in product_fields])
producer_sheet = wb.create_sheet(f"producteur⋅ice⋅s et référent⋅e⋅s")
producer_fields = [f.name for f in get_fields(Producer)]
producer_sheet.append(producer_fields)
for producer in delivery.producers.values():
producer_sheet.append([getattr(producer, field) for field in producer_fields])
return save_virtual_workbook(wb)
@ -103,4 +110,4 @@ def balance(delivery):
ws.append(
[email, order.total(delivery.products), "oui" if order.paid else "non"]
)
return save_virtual_workbook(wb)
return save_virtual_workbook(wb)

View file

@ -3,9 +3,10 @@
{% block modal_label %}<i class="icon-upload"></i> Importer les produits{% endblock modal_label %}
{% block modal_body %}
<h3>Importer des produits</h3>
<p>Formats pris en charge: xlsx, csv</p>
<p>Format pris en charge: xlsx</p>
<details>
<summary>Détails des colonnes</summary>
<p>Le tableur doit contenir deux onglets, le premier avec les produits, le second avec les producteurs.</p>
<summary>Détails des colonnes produits</summary>
<dl>
<dt class="mandatory">ref</dt>
<dd>Référence unique du produit (permet de mettre à jour les produits en cours de commande ou d'importer des commandes individuelles).</dd>
@ -26,9 +27,18 @@
<dt>img</dt>
<dd>Une URL éventuelle pointant sur une image du produit (attention, utiliser seulement des liens https).</dd>
</dl>
<summary>Détails des colonnes producteur</summary>
<dl>
<dt class="mandatory">id</dt>
<dd>Nom du producteur (doit être le même que le premier onglet).</dd>
<dt>referent</dt>
<dd>Le nom du ou de la référent⋅e</dd>
<dt>contact</dt>
<dd>Un moyen de contacter le⋅a product⋅eur⋅rice</dd>
</dl>
</details>
<form action="/livraison/{{ delivery.id }}/importer/produits" method="post" enctype="multipart/form-data">
<input type="file" name="data" required>
<input type="submit" value="Mettre à jour les produits">
<input type="submit" value="Mettre à jour les produits et product⋅eur⋅rice⋅s">
</form>
{% endblock modal_body %}