diff --git a/copanier/__init__.py b/copanier/__init__.py
index 733d353..141d501 100644
--- a/copanier/__init__.py
+++ b/copanier/__init__.py
@@ -80,7 +80,7 @@ async def auth_required(request, response):
# Should be handler Roll side?
# In dev mode, we serve the static, but we don't have yet a way to mark static
# route as unprotected.
- if request.path.startswith('/static/'):
+ if request.path.startswith("/static/"):
return
if request.route.payload and not request.route.payload.get("unprotected"):
token = request.cookies.get("token")
@@ -198,7 +198,7 @@ async def import_products(request, response, id):
response.message(err, status="error")
response.redirect = path
return
- elif data.filename.endswith('.xlsx'):
+ elif data.filename.endswith(".xlsx"):
try:
imports.products_from_xlsx(delivery, data)
except ValueError as err:
@@ -258,15 +258,25 @@ async def place_order(request, response, id):
response.redirect = delivery_url
return
if request.method == "POST":
+ if not (user and user.is_staff) and delivery.status == delivery.CLOSED:
+ response.message("La livraison est fermée", "error")
+ response.redirect = delivery_url
+ return
form = request.form
order = Order(paid=form.bool("paid", False))
for product in delivery.products:
try:
- quantity = form.int(product.ref, 0)
+ wanted = form.int(f"wanted:{product.ref}", 0)
except HttpError:
continue
- if quantity:
- order.products[product.ref] = ProductOrder(wanted=quantity)
+ try:
+ adjustment = form.int(f"adjustment:{product.ref}", 0)
+ except HttpError:
+ adjustment = 0
+ if wanted or adjustment:
+ order.products[product.ref] = ProductOrder(
+ wanted=wanted, adjustment=adjustment
+ )
if not delivery.orders:
delivery.orders = {}
if not order.products:
diff --git a/copanier/models.py b/copanier/models.py
index ceef0f1..f0d87f3 100644
--- a/copanier/models.py
+++ b/copanier/models.py
@@ -33,7 +33,6 @@ def price_field(value):
@dataclass
class Base:
-
@classmethod
def create(cls, data=None, **kwargs):
if isinstance(data, Base):
@@ -109,7 +108,11 @@ class Product(Base):
@dataclass
class ProductOrder(Base):
wanted: int
- ordered: int = 0
+ adjustment: int = 0
+
+ @property
+ def quantity(self):
+ return self.wanted + self.adjustment
@dataclass
@@ -117,14 +120,15 @@ class Order(Base):
products: Dict[str, ProductOrder] = field(default_factory=dict)
paid: bool = False
- def get_quantity(self, product):
- choice = self.products.get(product.ref)
- return choice.wanted if choice else 0
+ def __getitem__(self, ref):
+ if isinstance(ref, Product):
+ ref = ref.ref
+ return self.products.get(ref, ProductOrder(wanted=0))
def total(self, products):
products = {p.ref: p for p in products}
return round(
- sum(p.wanted * products[ref].price for ref, p in self.products.items()), 2
+ sum(p.quantity * products[ref].price for ref, p in self.products.items()), 2
)
@@ -133,6 +137,9 @@ class Delivery(Base):
__root__ = "delivery"
__lock__ = threading.Lock()
+ CLOSED = 0
+ OPEN = 1
+ ADJUSTMENT = 2
producer: str
from_date: datetime_field
@@ -145,6 +152,14 @@ class Delivery(Base):
orders: Dict[str, Order] = field(default_factory=dict)
id: str = field(default_factory=lambda *a, **k: uuid.uuid4().hex)
+ @property
+ def status(self):
+ if self.is_open:
+ return self.OPEN
+ if self.needs_adjustment:
+ return self.ADJUSTMENT
+ return self.CLOSED
+
@property
def total(self):
return round(sum(o.total(self.products) for o in self.orders.values()), 2)
@@ -165,6 +180,10 @@ class Delivery(Base):
def has_packing(self):
return any(p.packing for p in self.products)
+ @property
+ def needs_adjustment(self):
+ return self.has_packing and any(self.product_missing(p) for p in self.products)
+
@classmethod
def init_fs(cls):
cls.get_root().mkdir(parents=True, exist_ok=True)
@@ -202,7 +221,7 @@ class Delivery(Base):
total = 0
for order in self.orders.values():
if product.ref in order.products:
- total += order.products[product.ref].wanted
+ total += order.products[product.ref].quantity
return total
def product_missing(self, product):
@@ -211,3 +230,6 @@ class Delivery(Base):
wanted = self.product_wanted(product)
orphan = wanted % product.packing
return product.packing - orphan if orphan else 0
+
+ def has_order(self, person):
+ return person.email in self.orders
diff --git a/copanier/reports.py b/copanier/reports.py
index ec25008..dde7e2a 100644
--- a/copanier/reports.py
+++ b/copanier/reports.py
@@ -10,7 +10,15 @@ def summary(delivery):
wb = Workbook()
ws = wb.active
ws.title = f"{delivery.producer} {delivery.from_date.date()}"
- ws.append(["ref", "produit", "prix", "unités", "total"])
+ headers = [
+ "ref",
+ "produit",
+ "prix unitaire",
+ "quantité commandée",
+ "unité",
+ "total",
+ ]
+ ws.append(headers)
for product in delivery.products:
wanted = delivery.product_wanted(product)
ws.append(
@@ -19,10 +27,11 @@ def summary(delivery):
product.label,
product.price,
wanted,
+ product.unit,
round(product.price * wanted, 2),
]
)
- ws.append(["", "", "", "Total", delivery.total])
+ ws.append(["", "", "", "", "Total", delivery.total])
return save_virtual_workbook(wb)
@@ -36,12 +45,14 @@ def full(delivery):
row = [product.ref, product.label, product.price]
for order in delivery.orders.values():
wanted = order.products.get(product.ref)
- row.append(wanted.wanted if wanted else 0)
+ row.append(wanted.quantity if wanted else 0)
row.append(delivery.product_wanted(product))
ws.append(row)
- footer = ["Total", "", ""] + [
- o.total(delivery.products) for o in delivery.orders.values()
- ] + [delivery.total]
+ footer = (
+ ["Total", "", ""]
+ + [o.total(delivery.products) for o in delivery.orders.values()]
+ + [delivery.total]
+ )
ws.append(footer)
return save_virtual_workbook(wb)
diff --git a/copanier/static/app.css b/copanier/static/app.css
index 087f87d..4e1ceac 100644
--- a/copanier/static/app.css
+++ b/copanier/static/app.css
@@ -236,7 +236,6 @@ textarea {
line-height: 1rem;
background-color: #fff;
border: .05rem solid #bbc;
- border-radius: .1rem;
box-sizing: border-box;
}
@@ -276,6 +275,9 @@ input:focus,
select:focus {
box-shadow: 0 0 .1rem var(--primary-color);
}
+input[readonly] {
+ background-color: #eee;
+}
table {
border-collapse: collapse;
@@ -355,6 +357,7 @@ td.with-input {
}
td.with-input input {
width: 100%;
+ text-align: center;
}
article.delivery {
width: 100%;
diff --git a/copanier/templates/delivery.html b/copanier/templates/delivery.html
index 0133af1..8ac5824 100644
--- a/copanier/templates/delivery.html
+++ b/copanier/templates/delivery.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block body %}
-
{{ delivery.producer }} {% if delivery.is_open %}Gérer ma commande{% endif %}
+{{ delivery.producer }} {% include "includes/order_button.html" %}
{% include "includes/delivery_head.html" %}
@@ -11,7 +11,7 @@
Produit |
Prix |
{% if delivery.has_packing %}
- Lot |
+ Conditionnement |
{% endif %}
Total |
{% for email, order in delivery.orders.items() %}
@@ -32,13 +32,9 @@
{% if delivery.has_packing %}
{{ product.packing or '—'}} |
{% endif %}
- {{ delivery.product_wanted(product) }}{% if delivery.product_missing(product) %} ({{ delivery.product_missing(product) }}){% endif %} |
+ {{ delivery.product_wanted(product) }}{% if delivery.status == delivery.ADJUSTMENT and delivery.product_missing(product) %} (−{{ delivery.product_missing(product) }}){% endif %} |
{% for email, order in delivery.orders.items() %}
- {% if product.ref in order.products %}
- {{ order.products[product.ref].wanted }} |
- {% else %}
- — |
- {% endif %}
+ {{ order[product.ref].quantity or "—" }} |
{% endfor %}
{% endfor %}
@@ -57,7 +53,7 @@