mirror of
https://github.com/almet/copanier.git
synced 2025-04-28 19:42:37 +02:00
Add support for product referents
This commit is contained in:
parent
f609c51366
commit
83b78b3684
5 changed files with 62 additions and 31 deletions
|
@ -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}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in a new issue