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}" response.redirect = f"/livraison/{delivery.id}"
@app.route("/livraison/{id}/importer/produits", methods=["POST"]) @app.route("/livraison/{id}/importer/produits", methods=["POST"])
@staff_only @staff_only
async def import_products(request, response, id): async def import_products(request, response, id):
@ -309,12 +312,9 @@ async def import_products(request, response, id):
data = request.files.get("data") data = request.files.get("data")
error_path = f"/livraison/{delivery.id}/edit" error_path = f"/livraison/{delivery.id}/edit"
if data.filename.endswith((".csv", ".xlsx")): if data.filename.endswith(".xlsx"):
try: try:
if data.filename.endswith(".csv"): imports.products_and_producers_from_xlsx(delivery, data)
imports.products_from_csv(delivery, data.read().decode())
else:
imports.products_from_xlsx(delivery, data)
except ValueError as err: except ValueError as err:
message = f"Impossible d'importer le fichier. {err.args[0]}" message = f"Impossible d'importer le fichier. {err.args[0]}"
response.message(message, status="error") 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.message("Format de fichier inconnu", status="error")
response.redirect = error_path response.redirect = error_path
return 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}" response.redirect = f"/livraison/{delivery.id}"

View file

@ -9,15 +9,15 @@ from .models import Product, Producer
PRODUCT_FIELDS = {"ref", "name", "price"} PRODUCT_FIELDS = {"ref", "name", "price"}
PRODUCER_FIELDS = {"id", "name"} PRODUCER_FIELDS = {"id"}
def append_list(field, item): def append_list(field, item):
return field.append(item) field.append(item)
def append_dict(field, 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): 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 raise ValueError
headers = data[0] headers = data[0]
if not set(headers) >= required_fields: 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:]: for row in data[1:]:
raw = {k: v for k, v in dict(zip(headers, row)).items() if v} raw = {k: v for k, v in dict(zip(headers, row)).items() if v}
try: try:
@ -43,9 +43,13 @@ def products_and_producers_from_xlsx(delivery, data):
raise ValueError("Impossible de lire le fichier") raise ValueError("Impossible de lire le fichier")
sheet_names = data.get_sheet_names() 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. # 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 # 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() delivery.persist()

View file

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

View file

@ -3,7 +3,7 @@ from dataclasses import fields as get_fields
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_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): def summary_for_products(wb, title, delivery, total=None, products=None):
if products == None: if products == None:
@ -87,10 +87,17 @@ def products(delivery):
wb = Workbook() wb = Workbook()
ws = wb.active ws = wb.active
ws.title = f"{delivery.name} produits" ws.title = f"{delivery.name} produits"
fields = [f.name for f in get_fields(Product)] product_fields = [f.name for f in get_fields(Product)]
ws.append(fields) ws.append(product_fields)
for product in delivery.products: 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) 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_label %}<i class="icon-upload"></i> Importer les produits{% endblock modal_label %}
{% block modal_body %} {% block modal_body %}
<h3>Importer des produits</h3> <h3>Importer des produits</h3>
<p>Formats pris en charge: xlsx, csv</p> <p>Format pris en charge: xlsx</p>
<details> <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> <dl>
<dt class="mandatory">ref</dt> <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> <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> <dt>img</dt>
<dd>Une URL éventuelle pointant sur une image du produit (attention, utiliser seulement des liens https).</dd> <dd>Une URL éventuelle pointant sur une image du produit (attention, utiliser seulement des liens https).</dd>
</dl> </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> </details>
<form action="/livraison/{{ delivery.id }}/importer/produits" method="post" enctype="multipart/form-data"> <form action="/livraison/{{ delivery.id }}/importer/produits" method="post" enctype="multipart/form-data">
<input type="file" name="data" required> <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> </form>
{% endblock modal_body %} {% endblock modal_body %}