Update the demo mode

This commit is contained in:
Alexis Métaireau 2023-01-31 01:08:37 +01:00
parent 5e1e88545f
commit ff262418c3
16 changed files with 789 additions and 711 deletions

View file

@ -12,6 +12,10 @@ import yaml
from . import config from . import config
def demo_mode_enabled():
return getattr(config, "DEMO_MODE", False)
class DoesNotExist(ValueError): class DoesNotExist(ValueError):
pass pass
@ -78,13 +82,12 @@ class Base:
@dataclass @dataclass
class PersistedBase(Base): class PersistedBase(Base):
@classmethod @classmethod
def get_root(cls): def get_root(cls):
root = Path(config.DATA_ROOT) root = Path(config.DATA_ROOT)
if getattr(config, 'DEMO_MODE', False): if demo_mode_enabled():
root = root / "demo" root = root / "demo"
return root / cls.__root__ return root / cls.__root__
@ -111,6 +114,7 @@ class SavedConfiguration(PersistedBase):
data = {} data = {}
return cls(**data) return cls(**data)
@dataclass @dataclass
class Person(Base): class Person(Base):
email: str email: str
@ -162,19 +166,18 @@ class Groups(PersistedBase):
data = {"groups": {}} data = {"groups": {}}
groups = cls(**data) groups = cls(**data)
return groups return groups
@classmethod @classmethod
def is_defined(cls): def is_defined(cls):
groups = cls.load() groups = cls.load()
return len(groups.groups) > 0 return len(groups.groups) > 0
def persist(self): def persist(self):
with self.__lock__: with self.__lock__:
self.get_path().write_text(self.dump()) self.get_path().write_text(self.dump())
def add_group(self, group): def add_group(self, group):
assert group.id not in self.groups, "Un groupe avec ce nom existe déjà." assert group.id not in self.groups, "Un foyer avec ce nom existe déjà."
self.groups[group.id] = group self.groups[group.id] = group
def add_user(self, email, group_id): def add_user(self, email, group_id):
@ -294,7 +297,9 @@ class Order(Base):
p.quantity * _get_price(ref) for ref, p in self.products.items() p.quantity * _get_price(ref) for ref, p in self.products.items()
) )
shipping = self.compute_shipping(delivery, producers, email) if include_shipping else 0 shipping = (
self.compute_shipping(delivery, producers, email) if include_shipping else 0
)
return round(total_products + shipping, 2) return round(total_products + shipping, 2)
@ -354,15 +359,19 @@ class Delivery(PersistedBase):
return self.ADJUSTMENT return self.ADJUSTMENT
if self.is_waiting_products: if self.is_waiting_products:
return self.WAITING_PRODUCTS return self.WAITING_PRODUCTS
return self.CLOSED return self.CLOSED
def products_need_price_update(self, products=None): def products_need_price_update(self, products=None):
products = products or self.products products = products or self.products
max_age = self.from_date.date() - timedelta(days=60) max_age = self.from_date.date() - timedelta(days=60)
return any([product.last_update.date() < max_age return any(
for product in products [
if product.producer in self.producers]) product.last_update.date() < max_age
for product in products
if product.producer in self.producers
]
)
@property @property
def dates(self): def dates(self):
@ -373,7 +382,7 @@ class Delivery(PersistedBase):
"price_update_deadline": self.order_before - timedelta(weeks=2), "price_update_deadline": self.order_before - timedelta(weeks=2),
"order_before": self.order_before, "order_before": self.order_before,
"adjustment_deadline": self.order_before + timedelta(days=4), "adjustment_deadline": self.order_before + timedelta(days=4),
"delivery_date": delivery_date "delivery_date": delivery_date,
} }
@property @property
@ -387,16 +396,14 @@ class Delivery(PersistedBase):
@property @property
def is_open(self): def is_open(self):
return datetime.now().date() <= self.order_before.date() return datetime.now().date() <= self.order_before.date()
@property @property
def is_waiting_products(self): def is_waiting_products(self):
return ( return (
datetime.now().date() >= self.order_before.date() datetime.now().date() >= self.order_before.date()
and and datetime.now().date() <= self.from_date.date()
datetime.now().date() <= self.from_date.date()
) )
@property @property
def is_foreseen(self): def is_foreseen(self):
return datetime.now().date() <= self.from_date.date() return datetime.now().date() <= self.from_date.date()
@ -429,49 +436,58 @@ class Delivery(PersistedBase):
def _dedupe_products(raw_data): def _dedupe_products(raw_data):
"""On some rare occasions, different products get """On some rare occasions, different products get
the same identifier (ref). the same identifier (ref).
This function finds them and appends "-dedupe" to it. This function finds them and appends "-dedupe" to it.
This is not ideal but fixes the problem before it causes more This is not ideal but fixes the problem before it causes more
trouble (such as https://github.com/spiral-project/copanier/issues/136) trouble (such as https://github.com/spiral-project/copanier/issues/136)
This function returns True if dupes have been found. This function returns True if dupes have been found.
""" """
if ('products' not in raw_data) or len(raw_data['products']) < 1: if ("products" not in raw_data) or len(raw_data["products"]) < 1:
return False return False
products = raw_data['products']
counter = Counter([p['ref'] for p in products]) products = raw_data["products"]
counter = Counter([p["ref"] for p in products])
most_common = counter.most_common(1)[0] most_common = counter.most_common(1)[0]
number_of_dupes = most_common[1] number_of_dupes = most_common[1]
if number_of_dupes < 2: if number_of_dupes < 2:
return False return False
dupe_id = most_common[0] dupe_id = most_common[0]
# Reconstruct the products list but change the duplicated ID. # Reconstruct the products list but change the duplicated ID.
counter = 0 counter = 0
new_products = [] new_products = []
for product in products: for product in products:
ref = product['ref'] ref = product["ref"]
if ref == dupe_id: if ref == dupe_id:
counter = counter + 1 counter = counter + 1
if counter == number_of_dupes: # Only change the last occurence. if counter == number_of_dupes: # Only change the last occurence.
product['ref'] = f'{ref}-dedupe' product["ref"] = f"{ref}-dedupe"
new_products.append(product) new_products.append(product)
raw_data['products'] = new_products raw_data["products"] = new_products
return True return True
data = yaml.safe_load(path.read_text()) data = yaml.safe_load(path.read_text())
dupe_found = _dedupe_products(data) dupe_found = _dedupe_products(data)
# Tolerate extra fields (but we'll lose them if instance is persisted) # 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__} data = {k: v for k, v in data.items() if k in cls.__dataclass_fields__}
delivery = cls(**data) delivery = cls(**data)
delivery.id = id delivery.id = id
if demo_mode_enabled():
delivery.from_date = datetime.now()
delivery.to_date = datetime.now() + timedelta(days=10)
delivery.order_before = datetime.now() + timedelta(days=5)
delivery.validate_all_prices()
delivery.persist()
if dupe_found: if dupe_found:
delivery.persist() delivery.persist()
return delivery return delivery
@classmethod @classmethod
@ -480,7 +496,7 @@ class Delivery(PersistedBase):
for path in root.glob("*.yml"): for path in root.glob("*.yml"):
id_ = str(path.relative_to(cls.get_root())).replace(".yml", "") id_ = str(path.relative_to(cls.get_root())).replace(".yml", "")
yield Delivery.load(id_) yield Delivery.load(id_)
@classmethod @classmethod
def is_defined(cls): def is_defined(cls):
return len(list(cls.all())) > 0 return len(list(cls.all())) > 0
@ -591,3 +607,7 @@ class Delivery(PersistedBase):
percentage_person = person_amount / producer_total percentage_person = person_amount / producer_total
shipping = percentage_person * producer_shipping shipping = percentage_person * producer_shipping
return shipping return shipping
def validate_all_prices(self):
for product in self.products:
product.last_update = datetime.now()

View file

@ -52,7 +52,7 @@
{% if request.user and (request.user.is_staff or not config.HIDE_GROUPS_LINK) %} {% if request.user and (request.user.is_staff or not config.HIDE_GROUPS_LINK) %}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a class="pure-menu-link" class="pure-menu-link" href="{{ url_for('groups') }}"><i <a class="pure-menu-link" class="pure-menu-link" href="{{ url_for('groups') }}"><i
class="icon-globe"></i>&nbsp;Gérer les groupes</a> class="icon-globe"></i>&nbsp;Gérer les foyers</a>
</li> </li>
{% endif %} {% endif %}
<li class="pure-menu-item"> <li class="pure-menu-item">

View file

@ -9,21 +9,26 @@
<table class="paiements"> <table class="paiements">
<tr> <tr>
<td></td> <td></td>
{% for crediter in crediters %}<td>{% if crediter[0] in crediters_groups %} {{ crediters_groups[crediter[0]] }}*{% else %}{{ crediter[0] }}{% endif %} (+{{ crediter[1] | round(2) }})</td>{% endfor %} {% for crediter in crediters %}<td>{% if crediter[0] in crediters_groups %} {{ crediters_groups[crediter[0]]
}}*{% else %}{{ crediter[0] }}{% endif %} (+{{ crediter[1] | round(2) }})</td>{% endfor %}
</tr> </tr>
{% for debiter in debiters %} {% for debiter in debiters %}
<tr> <tr>
<td>{% if debiter[0] in debiters_groups %} {{ debiters_groups[debiter[0]].name }}{% else %}{{ debiter[0] }}{% endif %} ({{ debiter[1] | round(2) }})</td> <td>{% if debiter[0] in debiters_groups %} {{ debiters_groups[debiter[0]].name }}{% else %}{{ debiter[0] }}{%
endif %} ({{ debiter[1] | round(2) }})</td>
{% for crediter in crediters %} {% for crediter in crediters %}
{% set due_amount = results[debiter[0]][crediter[0]] | round(2) %} {% set due_amount = results[debiter[0]][crediter[0]] | round(2) %}
<td>{% if due_amount != 0.00 %}{{due_amount}}{% endif %}</td> <td>{% if due_amount != 0.00 %}{{due_amount}}{% endif %}</td>
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<p class="info"><i class="icon-lightbulb"></i>&nbsp; <strong>Mais, comment ça marche ?</strong> La répartition des chèques se fait automatiquement, en soustrayant ce que les personnes doivent (au nom de leur coloc / groupe) à ce qui leur est du (dans le cas où elles sont référentes pour certains produits).</p> <p class="info"><i class="icon-lightbulb"></i>&nbsp; <strong>Mais, comment ça marche ?</strong> La répartition des
<p class="info">Les personnes indiquées avec un <code>*</code> à côté de leur nom sont celles qui ont payé cette commande pour leur groupe.</p> chèques se fait automatiquement, en soustrayant ce que les personnes doivent (au nom de leur foyer) à ce
qui leur est du (dans le cas où elles sont référentes pour certains produits).</p>
<p class="info">Les personnes indiquées avec un <code>*</code> à côté de leur nom sont celles qui ont payé cette
commande pour leur foyer.</p>
{% endblock body %} {% endblock body %}

View file

@ -3,20 +3,26 @@
{% block additional_menu %} {% block additional_menu %}
<div class="pure-menu"> <div class="pure-menu">
<ul class="pure-menu-list"> <ul class="pure-menu-list">
<li class="pure-menu-item"><a href="{{ url_for('list_products', id=delivery.id) }}/produits.pdf" class="pure-menu-link"><i class="icon-ribbon"></i>&nbsp;Bons de commande</a></li> <li class="pure-menu-item"><a href="{{ url_for('list_products', id=delivery.id) }}/produits.pdf"
<li class="pure-menu-item"><a href="{{ url_for('show_orders_summary', id=delivery.id) }}" class="pure-menu-link"><i class="icon-grid"></i>&nbsp;Commandes groupes</a></li> class="pure-menu-link"><i class="icon-ribbon"></i>&nbsp;Bons de commande</a></li>
<li class="pure-menu-item"><a href="{{ url_for('compute_payments', id=delivery.id) }}" class="pure-menu-link"><i class="icon-wallet"></i>&nbsp;Paiements</a></li> <li class="pure-menu-item"><a href="{{ url_for('show_orders_summary', id=delivery.id) }}"
class="pure-menu-link"><i class="icon-grid"></i>&nbsp;Commandes foyers</a></li>
<li class="pure-menu-item"><a href="{{ url_for('compute_payments', id=delivery.id) }}" class="pure-menu-link"><i
class="icon-wallet"></i>&nbsp;Paiements</a></li>
{% if request['user'].email == delivery.contact and delivery.status > delivery.EMPTY %} {% if request['user'].email == delivery.contact and delivery.status > delivery.EMPTY %}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a class="pure-menu-link" href="{{ url_for('show_delivery_toolbox', id=delivery.id) }}"><i class="icon-tools"></i>&nbsp;Boîte à outils</a> <a class="pure-menu-link" href="{{ url_for('show_delivery_toolbox', id=delivery.id) }}"><i
class="icon-tools"></i>&nbsp;Boîte à outils</a>
</li> </li>
{% endif %} {% endif %}
{% if request.user and request.user.is_staff %} {% if request.user and request.user.is_staff %}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a class="pure-menu-link" href="{{ url_for('edit_delivery', id=delivery.id) }}"><i class="icon-adjustments"></i>&nbsp;Modifier la distrib</a> <a class="pure-menu-link" href="{{ url_for('edit_delivery', id=delivery.id) }}"><i
class="icon-adjustments"></i>&nbsp;Modifier la distrib</a>
</li> </li>
<li class="pure-menu-item"> <li class="pure-menu-item">
<a class="pure-menu-link" href="{{ url_for('list_products', id=delivery.id) }}"><i class="icon-pricetags"></i>&nbsp;Éditer produits</a> <a class="pure-menu-link" href="{{ url_for('list_products', id=delivery.id) }}"><i
class="icon-pricetags"></i>&nbsp;Éditer produits</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
@ -25,14 +31,16 @@
{% block body %} {% block body %}
{% if delivery.status == delivery.CLOSED %} {% if delivery.status == delivery.CLOSED %}
<div class="important-message">Une fois la distribution terminée, reviens ici pour <a class="button" href="{{ url_for('hand_over_delivery', id=delivery.id) }}">passer le relai !</a></div> <div class="important-message">Une fois la distribution terminée, reviens ici pour <a class="button"
href="{{ url_for('hand_over_delivery', id=delivery.id) }}">passer le relai !</a></div>
{% endif %} {% endif %}
<div class="header"> <div class="header">
<h1>{{ delivery.name }}</h1> <h1>{{ delivery.name }}</h1>
<h2> <h2>
Distribution le <i class="icon-clock"></i> {{ delivery.from_date|date }}, {{ delivery.from_date|time }} - {{ delivery.to_date|time }}, à <i class="icon-streetsign"></i> {{ delivery.where }}. Distribution le <i class="icon-clock"></i> {{ delivery.from_date|date }}, {{ delivery.from_date|time }} - {{
delivery.to_date|time }}, à <i class="icon-streetsign"></i> {{ delivery.where }}.
</h2> </h2>
<h3>{% if delivery.products %} <h3>{% if delivery.products %}
@ -45,13 +53,13 @@
</div> </div>
<article class="delivery"> <article class="delivery">
{% if request['user'].email == delivery.contact %} {% if request['user'].email == delivery.contact %}
<div class="placeholder center"> <div class="placeholder center">
Hey, jettes un coup d'oeil à Hey, jettes un coup d'oeil à
<a href="{{ url_for('show_delivery_toolbox', id=delivery.id) }}">&nbsp;la boîte à outils</a> ! <a href="{{ url_for('show_delivery_toolbox', id=delivery.id) }}">&nbsp;la boîte à outils</a> !
</div> </div>
{% endif %} {% endif %}
{% if delivery.has_products %} {% if delivery.has_products %}
{% for (id, producer) in delivery.get_producers_for_referent(request.user.email).items() %} {% for (id, producer) in delivery.get_producers_for_referent(request.user.email).items() %}
{% if producer.needs_price_update(delivery) %} {% if producer.needs_price_update(delivery) %}
{% set modal_body %} {% set modal_body %}
@ -60,28 +68,32 @@ Hey, jettes un coup d'oeil à
Certains produits dont tu est référent⋅e ont besoin d'être mis à jour.<br /><br /> Certains produits dont tu est référent⋅e ont besoin d'être mis à jour.<br /><br />
Est-ce que tu veux t'en occuper maintenant ?<br /> Est-ce que tu veux t'en occuper maintenant ?<br />
<a class="button" href="{{ url_for('edit_producer', delivery_id=delivery.id, producer_id=producer.id) }}#products">Oui, mettre à jour les prix pour {{ producer.name }}</a> <a class="button"
href="{{ url_for('edit_producer', delivery_id=delivery.id, producer_id=producer.id) }}#products">Oui, mettre à
jour les prix pour {{ producer.name }}</a>
{%- endset %} {%- endset %}
{{ macros.modal(id="update-price", body=modal_body, checked=True) }} {{ macros.modal(id="update-price", body=modal_body, checked=True) }}
{% break %} {% break %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% include "includes/delivery_table.html" %} {% include "includes/delivery_table.html" %}
{% else %} {% else %}
<div class="placeholder"> <div class="placeholder">
<h2>😔 Pour le moment, cette distribution est bien vide…</h2> <h2>😔 Pour le moment, cette distribution est bien vide…</h2>
{% if request.user and request.user.is_staff %} {% if request.user and request.user.is_staff %}
Occupons-nous donc de ça ! Deux options : Occupons-nous donc de ça ! Deux options :
<ol> <ol>
<li><a href="{{ url_for('create_producer', delivery_id=delivery.id) }}">Ajouter les product⋅eurs⋅rices</a> à la main ;</li> <li><a href="{{ url_for('create_producer', delivery_id=delivery.id) }}">Ajouter les product⋅eurs⋅rices</a> à
<li>Ou bien <a href="{{ url_for('copy_products', id=delivery.id) }}">copier les produits d'une autre distribution</a>.</li> la main ;</li>
<li>Ou bien <a href="{{ url_for('copy_products', id=delivery.id) }}">copier les produits d'une autre
distribution</a>.</li>
</ol> </ol>
{% endif %}
</div>
{% endif %} {% endif %}
</div>
{% endif %}
</article> </article>
<hr> <hr>
<ul class="toolbox"> <ul class="toolbox">
</ul> </ul>
{% endblock body %} {% endblock body %}

View file

@ -1,6 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block toplink %}<a href="{{ url_for('show_delivery', id=delivery.id) }}">↶ Retourner à la distribution</a>{% endblock %} {% block toplink %}<a href="{{ url_for('show_delivery', id=delivery.id) }}">↶ Retourner à la distribution</a>{% endblock
%}
{% block body %} {% block body %}
@ -8,7 +9,9 @@
<h1>{{ delivery.name }}</h1> <h1>{{ delivery.name }}</h1>
</div> </div>
<i class="icon-lightbulb"></i> <strong>{{ delivery.orders|length }}</strong> groupes, <strong>{{ delivery.products|length }}</strong> produits et <strong>{{ delivery.producers | length}}</strong> fournisseurs. <br /> Total de la commande : <strong>{{ delivery.total }}€</strong></li> <i class="icon-lightbulb"></i> <strong>{{ delivery.orders|length }}</strong> foyers, <strong>{{ delivery.products|length
}}</strong> produits et <strong>{{ delivery.producers | length}}</strong> fournisseurs. <br /> Total de la commande
: <strong>{{ delivery.total }}€</strong></li>
<h2>Rappel des dates</h2> <h2>Rappel des dates</h2>
{% include "includes/delivery_dates_table.html" %} {% include "includes/delivery_dates_table.html" %}
@ -21,23 +24,31 @@
Avant et pendant la distribution : Avant et pendant la distribution :
<ul> <ul>
<li><a href="{{ url_for('groups', id=delivery.id) }}"><i class="icon-globe"></i>&nbsp; Gérer les groupes / colocs</a></li> <li><a href="{{ url_for('groups', id=delivery.id) }}"><i class="icon-globe"></i>&nbsp; Gérer les foyers /
<li><a href="{{ url_for('edit_delivery', id=delivery.id) }}"><i class="icon-pencil"></i>&nbsp; Modifier la commande (dates, lieu, référent⋅e, etc)</a></li> colocs</a></li>
<li><a href="{{ url_for('list_products', id=delivery.id) }}"><i class="icon-pencil"></i>&nbsp; Gérer les produits</a></li> <li><a href="{{ url_for('edit_delivery', id=delivery.id) }}"><i class="icon-pencil"></i>&nbsp; Modifier la commande
(dates, lieu, référent⋅e, etc)</a></li>
<li><a href="{{ url_for('list_products', id=delivery.id) }}"><i class="icon-pencil"></i>&nbsp; Gérer les
produits</a></li>
</ul> </ul>
Une fois les commandes passées : Une fois les commandes passées :
<ul> <ul>
<li><a href="{{ url_for('list_products', id=delivery.id) }}/produits.pdf"><i class="icon-download"></i>&nbsp; Télécharger la liste des produits commandés</a></li> <li><a href="{{ url_for('list_products', id=delivery.id) }}/produits.pdf"><i class="icon-download"></i>&nbsp;
<li><a href="{{ url_for('generate_report', id=delivery.id) }}"><i class="icon-download"></i>&nbsp; Télécharger le tableau des commandes</a></li> Télécharger la liste des produits commandés</a></li>
<li><a href="{{ url_for('send_referent_emails', id=delivery.id) }}"><i class="icon-envelope"></i>&nbsp; Envoyer les infos de commande aux référent⋅e⋅s</a></li> <li><a href="{{ url_for('generate_report', id=delivery.id) }}"><i class="icon-download"></i>&nbsp; Télécharger le
tableau des commandes</a></li>
<li><a href="{{ url_for('send_referent_emails', id=delivery.id) }}"><i class="icon-envelope"></i>&nbsp; Envoyer les
infos de commande aux référent⋅e⋅s</a></li>
</ul> </ul>
Pour préparer la distribution : Pour préparer la distribution :
<ul> <ul>
<li><a href="{{ url_for('show_orders_summary', id=delivery.id) }}"><i class="icon-document"></i>&nbsp; Fiches de commandes par groupe</a></li> <li><a href="{{ url_for('show_orders_summary', id=delivery.id) }}"><i class="icon-document"></i>&nbsp; Fiches de
<li><a href="{{ url_for('compute_payments', id=delivery.id) }}"><i class="icon-gears"></i>&nbsp; Faire la répartition des paiements</a></li> commandes par foyer</a></li>
<li><a href="{{ url_for('compute_payments', id=delivery.id) }}"><i class="icon-gears"></i>&nbsp; Faire la
répartition des paiements</a></li>
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -3,11 +3,11 @@
{% block body %} {% block body %}
<div class="header"> <div class="header">
{% if group.id %} {% if group.id %}
<h1>Modifier le groupe</h1> <h1>Modifier le foyer</h1>
{% else %} {% else %}
<h1>Créer un nouveau groupe</h1> <h1>Créer un nouveau foyer</h1>
{% endif %} {% endif %}
<h4>Les groupes permettent de gérer les commandes pour d'autres personnes (colocs, familles, etc)</h4> <h4>Les foyers permettent de gérer les commandes pour d'autres personnes (colocs, familles, etc)</h4>
</div> </div>
<form method="post"> <form method="post">
@ -16,7 +16,7 @@
<input type="text" name="name" value="{{ group.name or '' }}" required> <input type="text" name="name" value="{{ group.name or '' }}" required>
</label> </label>
<label> <label>
<p>Membres du groupe (emails, séparés par des virgules)</p> <p>Membres du foyer (emails, séparés par des virgules)</p>
<input type="text" name="members" value="{{ ', '.join(group.members) if group.members else '' }}"> <input type="text" name="members" value="{{ ', '.join(group.members) if group.members else '' }}">
</label> </label>
<div> <div>
@ -27,8 +27,9 @@
{% if group.id %} {% if group.id %}
<ul class="toolbox"> <ul class="toolbox">
<li> <li>
<a href="{{ url_for('delete_group', id=group.id) }}" class="button danger"><i class="icon-hazardous"></i>&nbsp;Supprimer ce groupe</a> <a href="{{ url_for('delete_group', id=group.id) }}" class="button danger"><i
class="icon-hazardous"></i>&nbsp;Supprimer ce foyer</a>
</li> </li>
</ul> </ul>
{% endif %} {% endif %}
{% endblock body %} {% endblock body %}

View file

@ -2,28 +2,31 @@
{% block body %} {% block body %}
<div class="header"> <div class="header">
<h1>Groupes</h1> <h1>Foyers</h1>
<div class="pure-menu pure-menu-horizontal"> <div class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list"> <ul class="pure-menu-list">
<li class="pure-menu-item"> <li class="pure-menu-item">
<a class="pure-menu-link" href="{{ url_for('create_group') }}"><i class="icon-globe"></i>&nbsp;Créer un nouveau groupe</a> <a class="pure-menu-link" href="{{ url_for('create_group') }}"><i class="icon-globe"></i>&nbsp;Créer un
nouveau foyer</a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
{% if not request['user'].group_id %} {% if not request['user'].group_id %}
<p>Bienvenue ! Avant de pouvoir commander, peux-tu nous indiquer pour quelle coloc / famille tu vas passer commande ? </p> <p>Bienvenue ! Avant de pouvoir commander, peux-tu nous indiquer pour quel foyer tu vas passer commande ?
</p>
{% endif %} {% endif %}
{% if not groups.groups %} {% if not groups.groups %}
<p>On dirait que tu arrive parmis les premier⋅es ! <a class="button" href="{{ url_for('create_group') }}"><i class="icon-globe"></i>&nbsp;Créé un nouveau groupe.</a></p> <p>On dirait que tu arrive parmis les premier⋅es ! <a class="button" href="{{ url_for('create_group') }}"><i
class="icon-globe"></i>&nbsp;Créé un nouveau foyer.</a></p>
{% else %} {% else %}
<table id="groups" class="pure-table"> <table id="groups" class="pure-table">
<thead> <thead>
<tr> <tr>
<th>Groupe</th> <th>Foyer</th>
<th>Membres</th> <th>Membres</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
@ -39,13 +42,16 @@
{% endfor %} {% endfor %}
</ul> </ul>
</td> </td>
<td>{% if group.id != request['user'].group_id %}<a class="pure-button" href="{{ url_for('join_group', id=group.id) }}">rejoindre</a>{% endif %} <a class="pure-button" href="{{ url_for('edit_group', id=group.id) }}">éditer</a></td> <td>{% if group.id != request['user'].group_id %}<a class="pure-button"
href="{{ url_for('join_group', id=group.id) }}">rejoindre</a>{% endif %} <a class="pure-button"
href="{{ url_for('edit_group', id=group.id) }}">éditer</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<p>Tu ne fais partie d'aucun des groupes listés ici ? Tu peux <a class="button" href="{{ url_for('create_group') }}"><i class="icon-globe"></i>&nbsp;en créer un nouveau</a></p> <p>Tu ne fais partie d'aucun des foyers listés ici ? Tu peux <a class="button" href="{{ url_for('create_group') }}"><i
class="icon-globe"></i>&nbsp;en créer un nouveau</a></p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -1,26 +1,31 @@
<table class="pure-table fixed-table"> <table class="pure-table fixed-table">
<thead> <thead>
<tr> <tr>
<th></th><th>Dates</th><th>Coord</th><th>Référent⋅e⋅s</th> <th></th>
<th>Dates</th>
<th>Coord</th>
<th>Référent⋅e⋅s</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<th>Création de la distribution</th> <th>Création de la distribution</th>
<td>{{ delivery.dates.creation_date | date}}</td> <td>{{ delivery.dates.creation_date | date}}</td>
<td>Rappeler aux référent⋅e⋅s produit de mettre leurs prix à jour, vérifier que tous les produits sont bien présentes, en ajouter si besoin</td> <td>Rappeler aux référent⋅e⋅s produit de mettre leurs prix à jour, vérifier que tous les produits sont bien
présentes, en ajouter si besoin</td>
<td>-</td> <td>-</td>
</tr> </tr>
<tr> <tr>
<th>Mise à jour des prix</th> <th>Mise à jour des prix</th>
<td>Du {{ delivery.dates.price_update_start | date }} au {{ delivery.dates.price_update_deadline | date}}</td> <td>Du {{ delivery.dates.price_update_start | date }} au {{ delivery.dates.price_update_deadline | date}}
</td>
<td></td> <td></td>
<td>Les référent⋅e⋅s produit mettent les prix à jour.</td> <td>Les référent⋅e⋅s produit mettent les prix à jour.</td>
</tr> </tr>
<tr> <tr>
<th>Commandes</th> <th>Commandes</th>
<td>Du {{ delivery.dates.price_update_deadline | date }} au {{ delivery.dates.order_before | date }}</td> <td>Du {{ delivery.dates.price_update_deadline | date }} au {{ delivery.dates.order_before | date }}</td>
<td>Envoyer le lien de commande aux groupes</td> <td>Envoyer le lien de commande aux foyers</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
@ -32,26 +37,30 @@
<tr> <tr>
<th>Récup des produits</th> <th>Récup des produits</th>
<td>Du {{ delivery.dates.adjustment_deadline | date }} au {{ delivery.dates.delivery_date | date }}</td> <td>Du {{ delivery.dates.adjustment_deadline | date }} au {{ delivery.dates.delivery_date | date }}</td>
<td><a href="{{ url_for('send_referent_emails', id=delivery.id) }}">Envoyer les infos de commande aux référent⋅e⋅s</a></td> <td><a href="{{ url_for('send_referent_emails', id=delivery.id) }}">Envoyer les infos de commande aux
référent⋅e⋅s</a></td>
<td>Transmettre les commandes, <strong>récupérer les produits</strong></td> <td>Transmettre les commandes, <strong>récupérer les produits</strong></td>
</tr> </tr>
<tr> <tr>
<th>Préparation de la distribution</th> <th>Préparation de la distribution</th>
<td>La veille du {{ delivery.dates.delivery_date | date }}</td> <td>La veille du {{ delivery.dates.delivery_date | date }}</td>
<td><a href="{{ url_for('show_orders_summary', id=delivery.id) }}">Imprimer les bons de commandes par groupe</a></td> <td><a href="{{ url_for('show_orders_summary', id=delivery.id) }}">Imprimer les bons de commandes par
foyer</a></td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<th>Distribution</th> <th>Distribution</th>
<td>{{ delivery.dates.delivery_date | date }}</td> <td>{{ delivery.dates.delivery_date | date }}</td>
<td>Coordonner la distribution, <a href="{{ url_for('compute_payments', id=delivery.id) }}">faire la répartition des chèques</a></td> <td>Coordonner la distribution, <a href="{{ url_for('compute_payments', id=delivery.id) }}">faire la
répartition des chèques</a></td>
<td>Arriver 30mn avant le début de la distribution, répartir les produits par coloc</td> <td>Arriver 30mn avant le début de la distribution, répartir les produits par coloc</td>
</tr> </tr>
<tr> <tr>
<th>Transmission</th> <th>Transmission</th>
<td>Après la distribution</td> <td>Après la distribution</td>
<td><a href="{{ url_for('hand_over_delivery', id=delivery.id) }}">Passer le relai à la nouvelle personne référente</a></a></td> <td><a href="{{ url_for('hand_over_delivery', id=delivery.id) }}">Passer le relai à la nouvelle personne
référente</a></a></td>
<td></td> <td></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -1,13 +1,16 @@
{% if deliveries %} {% if deliveries %}
<ul class="delivery"> <ul class="delivery">
{% for delivery in deliveries %} {% for delivery in deliveries %}
<li> <li>
<h3><a href="{{ url_for('show_delivery', id=delivery.id) }}"><i class="icon-hotairballoon"></i> {{ delivery.name }}</a> {% include "includes/order_button.html" %} <a class="button" href="{{ url_for('show_delivery', id=delivery.id) }}">Voir les commandes</a></h3> <h3><a href="{{ url_for('show_delivery', id=delivery.id) }}"><i class="icon-hotairballoon"></i> {{ delivery.name
{% include "includes/delivery_head.html" %} }}</a> {% include "includes/order_button.html" %} <a class="button"
</li> href="{{ url_for('show_delivery', id=delivery.id) }}">Voir les commandes</a></h3>
<hr> {% include "includes/delivery_head.html" %}
{% endfor %} </li>
<hr>
{% endfor %}
</ul> </ul>
{% else %} {% else %}
<p>Il n'y a aucune distribution à venir. Vous pouvez <a href="{{ url_for('new_delivery') }}">en lancer une nouvelle</a> ! <p>🤔 Il n'y a aucune distribution à venir. Vous pouvez <a href="{{ url_for('new_delivery') }}">en lancer une
{% endif %} nouvelle</a> !
{% endif %}

View file

@ -4,9 +4,13 @@
<p>On dirait que vous venez de lancer ce logiciel pour la première fois, bienvenue ici 😀.</p> <p>On dirait que vous venez de lancer ce logiciel pour la première fois, bienvenue ici 😀.</p>
<p>Pour commencer, deux options :</p> <p>Pour commencer, deux options :</p>
<ul> <ul>
<li><a href=" {{ url_for('activate_demo') }}">Activer le mode de démonstration</a>, cela va charger des données de démonstration pour que vous puissiez aller jeter un coup d'oeil au fonctionnement du logiciel ;</li> <li><a class="button" href=" {{ url_for('activate_demo') }}">Activer le mode de démonstration</a> pour jeter un coup
<li>Ou alors si vous avez envie de commencer à utiliser le logiciel, vous devez commencer par <a href="{{ url_for('groups') }}">rejoindre un groupe</a> et commencer une distribution.</p></li> d'œil rapidement aux fonctionalités.</li>
<li>Sinon, vous pouvez commencer par <a class="button" href="{{ url_for('groups') }}">rejoindre un foyer</a> et
commencer une distribution.</p>
</li>
</ul> </ul>
<p>Bon voyage 🙏 !</p> <p>La peinture est encore fraiche, alors si vous avez besoin d'un coup de main, n'hésitez pas à <a
{% endblock body %} href="mailto:alexis@notmyidea.org">m'envoyer un mail</a> ! 🙏</p>
{% endblock body %}

View file

@ -20,7 +20,7 @@ async def join_group(request, response, id):
request["groups"].persist() request["groups"].persist()
redirect = "/" if not request["user"].group_id else "/groupes" redirect = "/" if not request["user"].group_id else "/groupes"
response.message(f"Vous avez bien rejoint le groupe « {group.name} »") response.message(f"Vous avez bien rejoint le foyer « {group.name} »")
response.redirect = redirect response.redirect = redirect
@ -41,14 +41,14 @@ async def create_group(request, response):
) )
request["groups"].add_group(group) request["groups"].add_group(group)
request["groups"].persist() request["groups"].persist()
response.message(f"Le groupe {group.name} à bien été créé") response.message(f"Le foyer {group.name} à bien été créé")
response.redirect = "/" response.redirect = "/"
response.html("groups/edit_group.html", group=group) response.html("groups/edit_group.html", group=group)
@app.route("/groupes/{id}/éditer", methods=["GET", "POST"]) @app.route("/groupes/{id}/éditer", methods=["GET", "POST"])
async def edit_group(request, response, id): async def edit_group(request, response, id):
assert id in request["groups"].groups, "Impossible de trouver le groupe" assert id in request["groups"].groups, "Impossible de trouver le foyer"
group = request["groups"].groups[id] group = request["groups"].groups[id]
if request.method == "POST": if request.method == "POST":
form = request.form form = request.form
@ -65,8 +65,8 @@ async def edit_group(request, response, id):
@app.route("/groupes/{id}/supprimer", methods=["GET"]) @app.route("/groupes/{id}/supprimer", methods=["GET"])
async def delete_group(request, response, id): async def delete_group(request, response, id):
assert id in request["groups"].groups, "Impossible de trouver le groupe" assert id in request["groups"].groups, "Impossible de trouver le foyer"
deleted = request["groups"].groups.pop(id) deleted = request["groups"].groups.pop(id)
request["groups"].persist() request["groups"].persist()
response.message(f"Le groupe {deleted.name} à bien été supprimé") response.message(f"Le foyer {deleted.name} à bien été supprimé")
response.redirect = "/groupes" response.redirect = "/groupes"

View file

@ -1,6 +1,6 @@
from .core import app, session, env, url from .core import app, session, env, url
from ..models import Groups, Person, SavedConfiguration from ..models import Groups, Person, SavedConfiguration, Delivery
from .. import utils, emails, config from .. import utils, emails, config
@ -14,9 +14,9 @@ async def auth_required(request, response):
saved_config = SavedConfiguration.load() saved_config = SavedConfiguration.load()
if saved_config.demo_mode_enabled: if saved_config.demo_mode_enabled:
setattr(config, 'DEMO_MODE', True) setattr(config, "DEMO_MODE", True)
else: else:
setattr(config, 'DEMO_MODE', False) setattr(config, "DEMO_MODE", False)
if request.route.payload and not request.route.payload.get("unprotected"): if request.route.payload and not request.route.payload.get("unprotected"):
token = request.cookies.get("token") token = request.cookies.get("token")
@ -96,13 +96,16 @@ async def logout(request, response):
async def onboarding(request, response): async def onboarding(request, response):
response.html("onboarding.html") response.html("onboarding.html")
@app.route("/premier-lancement/demo", methods=["GET"]) @app.route("/premier-lancement/demo", methods=["GET"])
async def activate_demo(request, response): async def activate_demo(request, response):
saved_config = SavedConfiguration.load() saved_config = SavedConfiguration.load()
saved_config.demo_mode_enabled = True saved_config.demo_mode_enabled = True
saved_config.persist() saved_config.persist()
response.redirect = "/" response.redirect = "/"
@app.route("/premier-lancement/demo/désactiver", methods=["GET"]) @app.route("/premier-lancement/demo/désactiver", methods=["GET"])
async def desactivate_demo(request, response): async def desactivate_demo(request, response):
saved_config = SavedConfiguration.load() saved_config = SavedConfiguration.load()

View file

@ -134,9 +134,7 @@ async def validate_producer_prices(request, response, delivery_id, producer_id):
@app.route("/produits/{delivery_id}/valider-prix", methods=["GET"]) @app.route("/produits/{delivery_id}/valider-prix", methods=["GET"])
async def mark_all_prices_as_ok(request, response, delivery_id): async def mark_all_prices_as_ok(request, response, delivery_id):
delivery = Delivery.load(delivery_id) delivery = Delivery.load(delivery_id)
delivery.validate_all_prices()
for product in delivery.products:
product.last_update = datetime.now()
delivery.persist() delivery.persist()
response.message(f"Les prix ont été marqués comme OK pour toute la distribution !") response.message(f"Les prix ont été marqués comme OK pour toute la distribution !")
@ -156,8 +154,12 @@ async def create_product(request, response, delivery_id, producer_id):
product.producer = producer_id product.producer = producer_id
form = request.form form = request.form
product.update_from_form(form) product.update_from_form(form)
random_string = "".join(random.choices(string.ascii_lowercase + string.digits, k=8)) random_string = "".join(
product.ref = slugify(f"{producer_id}-{product.name}-{product.unit}-{random_string}") random.choices(string.ascii_lowercase + string.digits, k=8)
)
product.ref = slugify(
f"{producer_id}-{product.name}-{product.unit}-{random_string}"
)
delivery.products.append(product) delivery.products.append(product)
delivery.persist() delivery.persist()

File diff suppressed because it is too large Load diff

View file

@ -5,64 +5,38 @@ groups:
- gilki@tenhe.ls - gilki@tenhe.ls
- mohu@zab.tj - mohu@zab.tj
- nazhap@opolarti.ly - nazhap@opolarti.ly
- youpi@notmyidea.org
name: Permuflard name: Permuflard
john:
id: john
members:
- voige@sida.li
name: John
pre-du-fond:
id: pre-du-fond
members:
- sakzace@cetbageg.ne
- es@worru.cx
name: Pré du fond
chez-louise:
id: chez-louise
members:
- suznala@iflavra.ch
- sihvo@vo.gn
name: Chez Louise
les-filles-du-bout:
id: les-filles-du-bout
members:
- tecuhmiz@ilmifhaf.edu
- pi@hozep.sj
name: Les filles du bout
chaton: chaton:
id: chaton id: chaton
members: members:
- puet@helzet.ax - puet@helzet.ax
- bi@noto.fm - bi@noto.fm
name: Châton name: Châton
chez-louise:
id: chez-louise
members:
- suznala@iflavra.ch
- sihvo@vo.gn
name: Chez Louise
chez-pascale: chez-pascale:
id: chez-pascale id: chez-pascale
members: members:
- gawumnud@izkep.bb - gawumnud@izkep.bb
name: Chez Pascale name: Chez Pascale
laxe: john:
id: laxe id: john
members: members:
- couv@rujli.lr - voige@sida.li
- casceci@ziceda.mv name: John
name: Laxe la-bas-au-loin:
mouin: id: la-bas-au-loin
id: mouin
members: members:
- rop@uznofkoz.za - uwuvenfo@dunam.na
- ga@ma.gb - va@nowuk.th
- fuatogi@bip.sb - ohu@vukuk.vu
- te@itiorapa.gn - ewmu@migo.hm
name: Mouin name: La Bas au loin
le-chauffage:
id: le-chauffage
members:
- jaigo@hevef.gl
- du@nozcoze.lt
- em@ca.fk
- ifegomcic@pi.lt
- zow@hanheh.tn
name: Le chauffage
la-lointaine: la-lointaine:
id: la-lointaine id: la-lointaine
members: members:
@ -72,14 +46,6 @@ groups:
- viw@iwfifbe.ua - viw@iwfifbe.ua
- enji@ladbaped.lt - enji@ladbaped.lt
name: La lointaine name: La lointaine
la-bas-au-loin:
id: la-bas-au-loin
members:
- uwuvenfo@dunam.na
- va@nowuk.th
- ohu@vukuk.vu
- ewmu@migo.hm
name: La Bas au loin
la-lumiere: la-lumiere:
id: la-lumiere id: la-lumiere
members: members:
@ -88,6 +54,12 @@ groups:
- uveruopo@gic.org - uveruopo@gic.org
- zimpok@gogav.sy - zimpok@gogav.sy
name: La lumière name: La lumière
la-moins:
id: la-moins
members:
- cuud@cof.sc
- gavciawu@huzip.ga
name: La Moins
la-reclue: la-reclue:
id: la-reclue id: la-reclue
members: members:
@ -100,12 +72,21 @@ groups:
- pej@je.pk - pej@je.pk
- fotopesu@sumfu.sm - fotopesu@sumfu.sm
name: la Ville Z name: la Ville Z
la-moins: laxe:
id: la-moins id: laxe
members: members:
- cuud@cof.sc - couv@rujli.lr
- gavciawu@huzip.ga - casceci@ziceda.mv
name: La Moins name: Laxe
le-chauffage:
id: le-chauffage
members:
- jaigo@hevef.gl
- du@nozcoze.lt
- em@ca.fk
- ifegomcic@pi.lt
- zow@hanheh.tn
name: Le chauffage
le-foin: le-foin:
id: le-foin id: le-foin
members: members:
@ -126,12 +107,20 @@ groups:
- rocvibuv@nosmijij.tz - rocvibuv@nosmijij.tz
- it@za.rs - it@za.rs
name: Le grand champ name: Le grand champ
ttre: les-filles-du-bout:
id: ttre id: les-filles-du-bout
members: members:
- hutfiro@aje.gov - tecuhmiz@ilmifhaf.edu
- ce@bowzodda.in - pi@hozep.sj
name: TTRE name: Les filles du bout
mouin:
id: mouin
members:
- rop@uznofkoz.za
- ga@ma.gb
- fuatogi@bip.sb
- te@itiorapa.gn
name: Mouin
passage-creuse: passage-creuse:
id: passage-creuse id: passage-creuse
members: members:
@ -153,12 +142,6 @@ groups:
- budavwa@ciwun.mt - budavwa@ciwun.mt
- jisafu@huvogu.jm - jisafu@huvogu.jm
name: Pataudes name: Pataudes
rs:
id: rs
members:
- bok@rebu.de
- beko@noza.bz
name: R&S
peug: peug:
id: peug id: peug
members: members:
@ -169,3 +152,21 @@ groups:
- zahpepez@toteppe.hu - zahpepez@toteppe.hu
- fiodvif@fafij.cm - fiodvif@fafij.cm
name: Peug' name: Peug'
pre-du-fond:
id: pre-du-fond
members:
- sakzace@cetbageg.ne
- es@worru.cx
name: Pré du fond
rs:
id: rs
members:
- bok@rebu.de
- beko@noza.bz
name: R&S
ttre:
id: ttre
members:
- hutfiro@aje.gov
- ce@bowzodda.in
name: TTRE

View file

@ -40,7 +40,7 @@ Avant et pendant la distrib :
Modifier la commande (dates, lieu, référent⋅e, etc) Modifier la commande (dates, lieu, référent⋅e, etc)
Modifier les produits, les product⋅rices⋅eurs Modifier les produits, les product⋅rices⋅eurs
Gérer les groupes / colocs Gérer les foyers
Une fois les commandes passées (après le dimanche 26 janvier) Une fois les commandes passées (après le dimanche 26 janvier)
Télécharger les bons de distribution Télécharger les bons de distribution