diff --git a/copanier/__init__.py b/copanier/__init__.py
index 0b4e568..5684d2d 100644
--- a/copanier/__init__.py
+++ b/copanier/__init__.py
@@ -177,6 +177,35 @@ async def home(request, response):
response.html("home.html", incoming=Delivery.incoming(), former=Delivery.former())
+@app.route("/archives", methods=["GET"])
+async def view_archives(request, response):
+ response.html("archive.html", {"deliveries": Delivery.all(is_archived=True)})
+
+
+@app.route("/livraison/archive/{id}", methods=["GET"])
+async def view_archive(request, response, id):
+ delivery = Delivery.load(f"archive/{id}")
+ response.html("delivery.html", {"delivery": delivery})
+
+
+@app.route("/livraison/{id}/archiver", methods=["GET"])
+@staff_only
+async def archive_delivery(request, response, id):
+ delivery = Delivery.load(id)
+ delivery.archive()
+ response.message("La livraison a été archivée")
+ response.redirect = f"/livraison/{delivery.id}"
+
+
+@app.route("/livraison/archive/{id}/désarchiver", methods=["GET"])
+@staff_only
+async def unarchive_delivery(request, response, id):
+ delivery = Delivery.load(f"archive/{id}")
+ delivery.unarchive()
+ response.message("La livraison a été désarchivée")
+ response.redirect = f"/livraison/{delivery.id}"
+
+
@app.route("/livraison", methods=["GET"])
async def new_delivery(request, response):
response.html("edit_delivery.html", delivery={})
diff --git a/copanier/models.py b/copanier/models.py
index a95b9d9..14b2bc2 100644
--- a/copanier/models.py
+++ b/copanier/models.py
@@ -151,6 +151,7 @@ class Delivery(Base):
CLOSED = 0
OPEN = 1
ADJUSTMENT = 2
+ ARCHIVED = 3
producer: str
from_date: datetime_field
@@ -162,11 +163,16 @@ class Delivery(Base):
where: str = "Marché de la Briche"
products: List[Product] = field(default_factory=list)
orders: Dict[str, Order] = field(default_factory=dict)
- id: str = field(default_factory=lambda *a, **k: uuid.uuid4().hex)
infos_url: str = ""
+ def __post_init__(self):
+ self.id = None # Not a field because we don't want to persist it.
+ super().__post_init__()
+
@property
def status(self):
+ if self.is_archived:
+ return self.ARCHIVED
if self.is_open:
return self.OPEN
if self.needs_adjustment:
@@ -197,9 +203,14 @@ class Delivery(Base):
def needs_adjustment(self):
return self.has_packing and any(self.product_missing(p) for p in self.products)
+ @property
+ def is_archived(self):
+ return self.id and self.id.startswith("archive/")
+
@classmethod
def init_fs(cls):
cls.get_root().mkdir(parents=True, exist_ok=True)
+ cls.get_root().joinpath("archive").mkdir(exist_ok=True)
@classmethod
def get_root(cls):
@@ -210,12 +221,21 @@ class Delivery(Base):
path = cls.get_root() / f"{id}.yml"
if not path.exists():
raise DoesNotExist
- return cls(**yaml.safe_load(path.read_text()))
+ data = yaml.safe_load(path.read_text())
+ # Tolerate extra fields (but we'll lose them if instance is persisted)
+ data = {k: v for k, v in data.items() if k in cls.__dataclass_fields__}
+ delivery = cls(**data)
+ delivery.id = id
+ return delivery
@classmethod
- def all(cls):
- for path in cls.get_root().glob("*.yml"):
- yield Delivery.load(path.stem)
+ def all(cls, is_archived=False):
+ root = cls.get_root()
+ if is_archived:
+ root = root / "archive"
+ for path in root.glob("*.yml"):
+ id = str(path.relative_to(cls.get_root()))[:-4]
+ yield Delivery.load(id)
@classmethod
def incoming(cls):
@@ -225,10 +245,30 @@ class Delivery(Base):
def former(cls):
return [d for d in cls.all() if not d.is_foreseen]
+ @property
+ def path(self):
+ assert self.id, "Cannot operate on unsaved deliveries"
+ return self.get_root() / f"{self.id}.yml"
+
def persist(self):
with self.__lock__:
- path = self.get_root() / f"{self.id}.yml"
- path.write_text(self.dump())
+ if not self.id:
+ self.id = uuid.uuid4().hex
+ self.path.write_text(self.dump())
+
+ def archive(self):
+ if self.is_archived:
+ raise ValueError("La livraison est déjà archivée")
+ current = self.path
+ self.id = f"archive/{self.id}"
+ current.rename(self.path)
+
+ def unarchive(self):
+ if not self.is_archived:
+ raise ValueError("La livraison n'est pas archivée")
+ current = self.path
+ self.id = self.path.stem
+ current.rename(self.path)
def product_wanted(self, product):
total = 0
diff --git a/copanier/templates/archive.html b/copanier/templates/archive.html
new file mode 100644
index 0000000..6daffec
--- /dev/null
+++ b/copanier/templates/archive.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+{% block body %}
+
Livraisons archivées
+ {% include "includes/delivery_list.html" %}
+{% endblock body %}
diff --git a/copanier/templates/delivery.html b/copanier/templates/delivery.html
index 8738fec..17e94d7 100644
--- a/copanier/templates/delivery.html
+++ b/copanier/templates/delivery.html
@@ -58,6 +58,7 @@
{% endblock body %}
diff --git a/copanier/templates/edit_delivery.html b/copanier/templates/edit_delivery.html
index 9aa44a5..eb93aaf 100644
--- a/copanier/templates/edit_delivery.html
+++ b/copanier/templates/edit_delivery.html
@@ -45,32 +45,17 @@
{% if delivery %}
-Importer des produits
-Formats pris en charge: xlsx, csv
-
- Détails des colonnes
-
- - ref
- - Référence unique du produit (permet de mettre à jour les produits en cours de commande ou d'importer des commandes individuelles).
- - name
- - Nom du produit: mettre juste assez d'info pour distinguer les produits les uns des autres.
- - price
- - Prix d'une unité, en euros.
- - unit
- - Conditionnement d'une unité: 1kg, 33cl…
- - description
- - Plus de détails sur le produit.
- - packing
- - Contionnement final pour grouper les commandes, le cas échéant, en nombre d'unités.
- - url
- - Une URL éventuelle pointant sur une page présentant le produit.
- - img
- - Une URL éventuelle pointant sur une image du produit (attention, utiliser seulement des liens https).
-
-
-
+
{% endif %}
{% endblock body %}
diff --git a/copanier/templates/home.html b/copanier/templates/home.html
index d08ae7a..d57ebb3 100644
--- a/copanier/templates/home.html
+++ b/copanier/templates/home.html
@@ -8,4 +8,5 @@
{% with deliveries=former %}
{% include "includes/delivery_list.html" %}
{% endwith %}
+ Voir les livraisons archivées
{% endblock body %}
diff --git a/copanier/templates/includes/delivery_head.html b/copanier/templates/includes/delivery_head.html
index 2b5cbd8..0e9c9af 100644
--- a/copanier/templates/includes/delivery_head.html
+++ b/copanier/templates/includes/delivery_head.html
@@ -3,7 +3,7 @@
Lieu {{ delivery.where }}
Référent {{ delivery.contact }}
Date de livraison
- {% if delivery.status == delivery.OPEN %}Date limite de commande {% elif delivery.status == delivery.ADJUSTMENT %}Ajustement en cours{% else %}Fermée{% endif %}
+ {% if delivery.status == delivery.OPEN %}Date limite de commande {% elif delivery.status == delivery.ADJUSTMENT %}Ajustement en cours{% elif delivery.status == delivery.CLOSED %}Fermée{% else %}Archivée{% endif %}
{% if delivery.instructions %} À savoir {{ delivery.instructions }}{% endif %}
{% if delivery.infos_url %}Plus d'infos {{ delivery.infos_url|truncate(20)}}{% endif %}
diff --git a/copanier/templates/includes/modal_import_products.html b/copanier/templates/includes/modal_import_products.html
new file mode 100644
index 0000000..ca2f0cc
--- /dev/null
+++ b/copanier/templates/includes/modal_import_products.html
@@ -0,0 +1,32 @@
+{% extends "includes/modal.html" %}
+
+{% block modal_label %} Importer les produits{% endblock modal_label %}
+{% block modal_body %}
+Importer des produits
+Formats pris en charge: xlsx, csv
+
+ Détails des colonnes
+
+ - ref
+ - Référence unique du produit (permet de mettre à jour les produits en cours de commande ou d'importer des commandes individuelles).
+ - name
+ - Nom du produit: mettre juste assez d'info pour distinguer les produits les uns des autres.
+ - price
+ - Prix d'une unité, en euros.
+ - unit
+ - Conditionnement d'une unité: 1kg, 33cl…
+ - description
+ - Plus de détails sur le produit.
+ - packing
+ - Contionnement final pour grouper les commandes, le cas échéant, en nombre d'unités.
+ - url
+ - Une URL éventuelle pointant sur une page présentant le produit.
+ - img
+ - Une URL éventuelle pointant sur une image du produit (attention, utiliser seulement des liens https).
+
+
+
+{% endblock modal_body %}
diff --git a/copanier/templates/includes/order_button.html b/copanier/templates/includes/order_button.html
index 56d41e5..bd45f48 100644
--- a/copanier/templates/includes/order_button.html
+++ b/copanier/templates/includes/order_button.html
@@ -1,4 +1,4 @@
-{% if delivery.status != delivery.CLOSED %}
+{% if delivery.status == delivery.OPEN or delivery.status == delivery.ADJUSTMENT %}
{% if delivery.status == delivery.ADJUSTMENT %}
Ajuster ma commande
diff --git a/tests/test_models.py b/tests/test_models.py
index 941c74e..4cc531d 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -20,7 +20,7 @@ def test_can_create_delivery():
assert delivery.producer == "Andines"
assert delivery.where == "Marché de la Briche"
assert delivery.from_date.year == now().year
- assert delivery.id
+ assert not delivery.id
def test_wrong_datetime_raise_valueerror():
@@ -106,7 +106,12 @@ def test_order_has_adjustments():
def test_can_persist_delivery(delivery):
+ with pytest.raises(AssertionError):
+ delivery.path
+ assert not delivery.id
delivery.persist()
+ assert delivery.id
+ assert delivery.path.exists()
def test_can_load_delivery(delivery):
@@ -138,3 +143,23 @@ def test_productorder_quantity():
assert choice.quantity == 5
choice = ProductOrder(wanted=3, adjustment=-1)
assert choice.quantity == 2
+
+
+def test_archive_delivery(delivery):
+ delivery.persist()
+ old_id = delivery.id
+ old_path = delivery.path
+ assert str(old_path).endswith(f"delivery/{delivery.id}.yml")
+ assert old_path.exists()
+ delivery.archive()
+ assert delivery.is_archived
+ assert delivery.id.startswith("archive/")
+ new_path = delivery.path
+ assert str(new_path).endswith(f"delivery/archive/{old_id}.yml")
+ assert not old_path.exists()
+ assert new_path.exists()
+ delivery.unarchive()
+ assert not delivery.id.startswith("archive/")
+ assert old_path.exists()
+ assert not new_path.exists()
+ assert not delivery.is_archived