Compare commits

...

42 commits

Author SHA1 Message Date
Ploc
6b9e3a6e96 feat: add a perfect bug report template 2025-04-09 22:31:08 +02:00
Laetitia
19c4a0b6ed feat: update gitlab links everywhere else 2025-02-16 10:35:00 +00:00
Laetitia
9589bcf48d feat: update gitlab link to framagit 2025-02-16 10:35:00 +00:00
Laetitia
d354708ffe small changes on notice 2025-02-16 10:07:19 +00:00
Laetitia
2ec833fe42 add questions and yellow borders 2025-02-16 10:07:19 +00:00
Laetitia
039ee1ecb8 changer one title of notice 2025-02-16 10:07:19 +00:00
Laetitia
460fe14306 remove statics dans media from gitignore 2025-02-16 10:07:19 +00:00
Laetitia
f67b5f1de0 feat: improve notice and FAQ 2025-02-16 10:07:19 +00:00
Laetitia
8d2a3cf89f reformulation reponse faq et création liens feedback et HelloAsso dans les settings 2025-02-16 10:07:19 +00:00
Laetitia
e6be787798 ajout de la FAQ
contenu à relire, et front à amélirer
2025-02-16 10:07:19 +00:00
Laetitia
34fc3d6426 mise en page du mode d'emploi
Il faut encore écrire la fin du tuto, en copiant depuis jean cloud
2025-02-16 10:07:19 +00:00
Laetitia
b3cc0085ff Make phone not mandatory 2025-02-15 17:43:30 +01:00
xmeunier
b4311f1401 Fix pdf export layout 2025-02-15 14:05:03 +00:00
xmeunier
272df57664 Remove attributes 'target="_blank"' attributes from <a> tags 2025-01-05 13:56:18 +00:00
xmeunier
18249653f9 Prevent conflicting usage of comma in CSV files 2025-01-05 13:46:40 +01:00
xmeunier
10aed1560b Remove useless chariotte-v0-data.sql file 2024-11-05 21:21:13 +01:00
xmeunier
686a31b96a Make aggregated values computed when called instead of stored in DB
- GroupedOrder.total_price
- Order.articles_nb
- Order.price
- Item.ordered_nb
2024-11-05 11:01:02 +00:00
3d38bb56c0 Populate the changelog and bump the version to 1.3.0 2024-11-05 11:00:21 +00:00
Ploc
99db86153d refactor: change license file from text to markdown
Change license file from text to markdown as markdown adds some markup that makes it easier to read than text.
2024-11-02 15:04:01 +00:00
Ploc
8741d6d9b6 fix: license text
Copy license text from https://www.gnu.org/licenses/agpl-3.0.txt
2024-11-02 15:04:01 +00:00
xmeunier
c0c2d40286 Fix change-password and Connect buttons 2024-10-29 22:17:52 +01:00
28290f20de
Fix the password reset form
Django crispy is bow using a filter rather than a tag.

Using the `crispy` tag resulted in two imbricated forms being
generated, and a broken "send" button.
2024-10-27 23:18:58 +01:00
xmeunier
44a2c20f95 Simpler solution to prevent unitended OrderAuthor 2024-10-20 15:06:10 +02:00
mobilinux
84dadd9147 Prevent inintended creation of OrderAuthor before ordering
When a grouped_order_detail view was called with authenticated user,
when prefilling the firstName, lastName and email also inserted a new
OrderAuthor object in the database.
Now use SimpleNamespace, instead of OrderAuthor object, to carry
those values to the template.
2024-10-20 12:50:55 +00:00
xmeunier
34d2db8623 Add summed nb of ordered items of each article in a grouped order 2024-10-20 11:58:05 +00:00
Laetitia
dacafb8c70 utilisation de GITLAB_LINK dans le footer 2024-09-14 19:39:44 +00:00
Laetitia
e77c4d46d8 mise à jour du lien vers le gitlab 2024-09-14 19:39:44 +00:00
mobilinux
f378b1516a Move newsletter link to "Suivez nous" column 2024-09-14 15:05:32 +02:00
mobilinux
66aeea6ad0 Reformat html code in footer 2024-09-14 12:44:43 +00:00
mobilinux
5b95cfe843 Add link to subscribe to newsletter into footer 2024-09-14 12:44:43 +00:00
mobilinux
2a3b7d467c Revert "Add to footer the link to subscribe to Chariotte News"
This reverts commit 502555c90e3e993750708b539af634874ffa8435.
2024-09-14 12:44:43 +00:00
mobilinux
6b7bd335d0 Add to footer the link to subscribe to Chariotte News 2024-09-14 12:44:43 +00:00
mobilinux
9b6cc7604c Remove header in the CSV export of email addressese 2024-09-14 12:29:37 +00:00
mobilinux
193114e1e9 Remove order_name from groupOrder emails export
This column was quite useless, since the export grouped order email addresses is
done for a given grouped order, so the value was the same throughout the CSV.
2024-09-14 12:29:37 +00:00
Laetitia
20823580a9 feat: modification de l'affiche de la date et heure de limite de commande 2024-09-14 10:56:38 +00:00
Laetitia
b57ebafca8 feat: ajout de la date dans la page de gestion de la commande groupée 2024-09-14 10:56:38 +00:00
Laetitia
f242870d50 feat: modification du format de date et heure dans le csv 2024-09-14 10:56:38 +00:00
pauline
8b79492508 correct test unit 2024-09-14 10:56:38 +00:00
pauline
39dcaf637d add date and note in csv 2024-09-14 10:56:38 +00:00
pauline
d20cafe5db commit for rebase 2024-09-14 10:56:38 +00:00
pauline
4181588a3a save 2024-09-14 10:56:38 +00:00
Laetitia
bcfb62e740 Revert "change html classes for appearence in 'voir'"
This reverts commit 1d7412d7109493e7978140237346d43826a3aebc.
2024-09-14 10:56:38 +00:00
58 changed files with 9190 additions and 2347 deletions

3
.gitignore vendored
View file

@ -3,11 +3,8 @@ coverage.xml
.coverage .coverage
la_chariotte.egg-info/ la_chariotte.egg-info/
node_modules node_modules
/static/*
/media/*
local_settings.py local_settings.py
.venv .venv
*venv* *venv*
mails.sqlite mails.sqlite
package-lock.json package-lock.json
static

View file

@ -0,0 +1,97 @@
# title
Short and specific: **a clear summary of the bug**.
It should also be **searchable**, so developers can find it in a pinch.
> Tips on writing great titles:
>
> 1. Keep it simple, but descriptive.
> 2. Dont abbreviate.
> 3. Make it searchable.
> 4. Focus on the technical problem.
> 5. Highlight the specific feature/component you have issues with.
## description
Small text to describe the bug with human words.
## steps to reproduce
**Describe how you found the bug**, so the developer can try to reproduce it.
> Tips on writing reproducible steps:
>
> 1. Use a numbered list so its easy to follow.
> 2. This is your chance to be comprehensive and (reasonably) verbose. Dont leave out any details!
## expected vs. actual results
Take some time to explain what **should** happen vs. what **actually** happened.
If you just describe the bug, some people might think youre describing the expected behavior.
### expected results
### actual results
> Tips for writing expected vs. actual results:
>
> 1. Use a direct comparison format. For example, "*The button should turn green*" vs. "*The button is turning blue*".
> 2. Be precise. Simply stating "*it went wrong*" instead of *"the page loads indefinitely*" means you are leaving out very valuable information!
## visual proof/screenshot
Screenshots and annotations help developers **visualize the bug**, and pinpoint its location on the page.
> Tips on taking great screenshots:
>
> 1. Annotations go a long way towards driving your point across.
> 2. Highlight the problematic element. Dont be ambiguous.
> 3. Use big fonts, different colors, etc. - the bug needs to be even more obvious here than in your summary.
> 4. For complicated issues, record a short video! This adds a ton of helpful context when trying to reproduce bugs.
## priority
The urgency and **potential impact of the bug**. Determines how quickly it needs to be fixed.
> How to determine priority/severity:
>
> 1. Critical: blocking bugs that **directly prevent business**. Example: a checkout page not loading.
> 2. High: affects **major features**, but non-breaking. E.g.: the search bar on an e-commerce website.
> 3. Medium: noticeable bugs that **disrupt normal use**. E.g.: broken link, long loading times.
> 4. Low: **small issues** and enhancements: typos, missing images...
## environment
For developers to reproduce and fix your bug, theyll need to know your **browser version**, **screen size**, **operating system**...
Some bugs only occur within specific environments.
- source url:
- operating system:
- browser:
- viewport:
> How to find your environment info:
>
> 1. **Browser and version**: look for a “Help” or “About” option in your browsers menu.
> 2. **Operating system**: on PC, press the Windows key + Pause/break. On a Mac, click the Apple logo and choose “About this Mac”.
> 3. **Screen size**: look in your computers display settings, or search online for “screen resolution” along with your device model.
## console logs
This is where your web browser **shows errors or warnings**.
Console logs can help developers figure out what went wrong.
```log
console log
```
> How to access your console logs:
>
> 1. Right-click the page, select “Inspect” or “Inspect Element”, then click on the “Console” tab.
> 2. Try to make the bug happen again and see if any messages pop up there.
## credits https://www.perfectbugreport.io/

View file

@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.3.0 (2024-11-02)
This is a small release, with a few features and bugfixes. The code is now
hosted on https://framagit.org rather than https://gitlab.com.
### Added
- Changed hour and date format in the generated CSV files
- Added the date in the grouped order dashboard
- Changed the display of the last possible date and hour to place an order
- Added the total number of ordered items in the grouped orders overview
- Removed header line and column for grouped order name in the CSV export for emails
### Fixed
- The "reset password" form is now fixed, it was broken since the last release.
## 1.2.0 (2024-07-12) ## 1.2.0 (2024-07-12)
A small release, with a few features, a new footer and a `/stats` endpoint. A small release, with a few features, a new footer and a `/stats` endpoint.

1324
LICENSE

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ started.
The first step is to clone the project, you can find more information about that in The first step is to clone the project, you can find more information about that in
the [getting started guide](../install.md). Once that's done, you can: the [getting started guide](../install.md). Once that's done, you can:
- choose a task [on the board](https://gitlab.com/la-chariotte/la_chariotte/-/boards) and assign it to - choose a task [on the board](https://framagit.org/la-chariotte/la-chariotte/-/boards) and assign it to
yourself - if you don't know which task to do, feel free to reach to yourself - if you don't know which task to do, feel free to reach to
us. us.
- create a new branch **from develop** naming it to reflect what you want to do - create a new branch **from develop** naming it to reflect what you want to do

View file

@ -11,7 +11,7 @@
- **docs.chariotte.fr**, the docs you are reading now. It's handled by [readthedocs.org](https://readthedocs.org). - **docs.chariotte.fr**, the docs you are reading now. It's handled by [readthedocs.org](https://readthedocs.org).
- **chariotte.fr**, the main instance. It's deployed on Alwaysdata - **chariotte.fr**, the main instance. It's deployed on Alwaysdata
- **blog.chariotte.fr**, our blog. It's [a static website](https://gitlab.com/la-chariotte/la-chariotte.gitlab.io) deployed on Gitlab pages. - **blog.chariotte.fr**, our blog. It's [a static website](https://framagit.org/la-chariotte/la-chariotte.frama.io) deployed on Gitlab pages.
## The main instance ## The main instance

View file

@ -22,22 +22,21 @@ classDiagram
name name
deadline : DateTime deadline : DateTime
delivery_date : Date delivery_date : Date
delivery_slot
place place
description description
orga : CustomUser orga : CustomUser
total_price
} }
class Item{ class Item{
name name
grouped_order : GroupedOrder grouped_order : GroupedOrder
ordered_nb ordered_nb
total_price price
max_limit max_limit
} }
class Order{ class Order{
grouped_order : GroupedOrder grouped_order : GroupedOrder
author : OrderAuthor author : OrderAuthor
price
created_date created_date
note note
} }

View file

@ -1,7 +1,7 @@
First, clone the project First, clone the project
```bash ```bash
git clone git@gitlab.com:la-chariotte/la_chariotte.git git clone https://framagit.org/la-chariotte/la-chariotte.git
``` ```
## Virtual environment ## Virtual environment

View file

@ -1 +1 @@
__version__ = "1.2.0" __version__ = "1.3.0"

View file

@ -8,6 +8,6 @@
</p> </p>
<div class="box"> <div class="box">
<p>Votre mot de passe a été correctement réinitialisé. Vous pouvez maintenant vous connecter.</p> <p>Votre mot de passe a été correctement réinitialisé. Vous pouvez maintenant vous connecter.</p>
<button class="button is-primary" href="{% url 'accounts:login'%}">Se connecter</button> <a class="button is-primary" href="{% url 'accounts:login' %}">Se connecter</a>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -9,8 +9,8 @@
{% if validlink %} {% if validlink %}
<form method="POST"> <form method="POST">
<p>Veuillez entrer un nouveau mot de passe tout neuf.</p> <p>Veuillez entrer un nouveau mot de passe tout neuf.</p>
{{ form | crispy }}
{% csrf_token %} {% csrf_token %}
{% crispy form %}
<input class="button is-primary" <input class="button is-primary"
type="submit" type="submit"
value="Changer le mot de passe"> value="Changer le mot de passe">

View file

@ -8,7 +8,8 @@
<div class="box"> <div class="box">
<form method="POST"> <form method="POST">
<p>Entrez votre mail pour recevoir les instructions pour le réinitialiser.</p> <p>Entrez votre mail pour recevoir les instructions pour le réinitialiser.</p>
{% crispy form %} {{ form | crispy }}
{% csrf_token %}
<div class="buttons"> <div class="buttons">
<input class="button is-primary" type="submit" value="Envoyer"> <input class="button is-primary" type="submit" value="Envoyer">
</div> </div>

View file

@ -18,6 +18,10 @@ class GroupedOrderForm(forms.ModelForm):
widget=forms.TimeInput(attrs={"type": "time"}), widget=forms.TimeInput(attrs={"type": "time"}),
initial=datetime.time(hour=23, minute=59, second=59), initial=datetime.time(hour=23, minute=59, second=59),
) )
is_phone_mandatory = forms.BooleanField(
label="Numéro de téléphone obligatoire pour les participants",
required=False,
)
class Meta: class Meta:
model = GroupedOrder model = GroupedOrder
@ -29,6 +33,7 @@ class GroupedOrderForm(forms.ModelForm):
"delivery_slot", "delivery_slot",
"place", "place",
"description", "description",
"is_phone_mandatory",
] ]
widgets = { widgets = {
"name": forms.TextInput( "name": forms.TextInput(

View file

@ -0,0 +1,36 @@
# Generated by Django 4.2.16 on 2024-10-31 21:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("order", "0026_groupedorder_delivery_slot"),
]
operations = [
migrations.RemoveField(
model_name="groupedorder",
name="total_price",
),
migrations.RemoveField(
model_name="item",
name="ordered_nb",
),
migrations.RemoveField(
model_name="order",
name="articles_nb",
),
migrations.RemoveField(
model_name="order",
name="price",
),
migrations.AlterField(
model_name="order",
name="created_date",
field=models.DateTimeField(
auto_now_add=True, verbose_name="Date et heure de commande"
),
),
]

View file

@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2024-12-08 15:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("order", "0027_remove_groupedorder_total_price_and_more"),
]
operations = [
migrations.AddField(
model_name="groupedorder",
name="is_phone_mandatory",
field=models.BooleanField(
default=False, verbose_name="Numéro de téléphone obligatoire"
),
),
]

View file

@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2024-12-08 15:41
from django.db import migrations
def set_phone_mandatory(apps, schema_editor):
"""For continuity, force mandatory phone for the orders that were created so far."""
GroupedOrder = apps.get_model("order", "GroupedOrder")
for grouped_order in GroupedOrder.objects.all():
grouped_order.is_phone_mandatory = True
grouped_order.save()
class Migration(migrations.Migration):
dependencies = [
("order", "0028_groupedorder_is_phone_mandatory"),
]
operations = [
migrations.RunPython(set_phone_mandatory),
]

View file

@ -25,8 +25,10 @@ class GroupedOrder(models.Model):
max_length=100, null=True, blank=True, verbose_name="Lieu de livraison" max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
) )
description = models.TextField("Description", null=True, blank=True) description = models.TextField("Description", null=True, blank=True)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
code = models.CharField(auto_created=True) code = models.CharField(auto_created=True)
is_phone_mandatory = models.BooleanField(
default=False, verbose_name="Numéro de téléphone obligatoire"
)
def create_code_from_pk(self): def create_code_from_pk(self):
"""When a grouped order is created, a unique code is generated, to be used to """When a grouped order is created, a unique code is generated, to be used to
@ -52,16 +54,18 @@ class GroupedOrder(models.Model):
) )
self.code = f"{base_36_pk}{random_string}"[:code_length] self.code = f"{base_36_pk}{random_string}"[:code_length]
def compute_total_price(self): @property
def total_price(self):
price = 0 price = 0
for order in self.order_set.all(): for order in self.order_set.all():
price += order.price price += order.price
self.total_price = price return price
self.save()
def compute_items_ordered_nb(self): def get_total_ordered_items(self):
total_nb = 0
for item in self.item_set.all(): for item in self.item_set.all():
item.compute_ordered_nb() total_nb += item.ordered_nb
return total_nb
def is_open(self): def is_open(self):
return self.deadline >= timezone.now() return self.deadline >= timezone.now()
@ -117,26 +121,24 @@ class Order(models.Model):
GroupedOrder, on_delete=models.CASCADE, related_name="order_set" GroupedOrder, on_delete=models.CASCADE, related_name="order_set"
) )
author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE) author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE)
articles_nb = models.PositiveIntegerField(default=0) created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
created_date = models.DateTimeField("Date de la commande", auto_now_add=True)
note = models.TextField(max_length=200, null=True, blank=True) note = models.TextField(max_length=200, null=True, blank=True)
def compute_order_articles_nb(self): @property
def articles_nb(self):
"""Computes the number of articles in this order""" """Computes the number of articles in this order"""
articles_nb = 0 articles_nb = 0
for ord_item in self.ordered_items.all(): for ord_item in self.ordered_items.all():
articles_nb += ord_item.nb articles_nb += ord_item.nb
self.articles_nb = articles_nb return articles_nb
self.save()
def compute_order_price(self): @property
def price(self):
"""Computes the total price of the order""" """Computes the total price of the order"""
price = 0 price = 0
for ord_item in self.ordered_items.all(): for ord_item in self.ordered_items.all():
price += ord_item.get_price() price += ord_item.get_price()
self.price = price return price
self.save()
def __str__(self): # pragma: no cover def __str__(self): # pragma: no cover
return ( return (
@ -151,15 +153,13 @@ class Item(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2) price = models.DecimalField(max_digits=10, decimal_places=2)
max_limit = models.PositiveSmallIntegerField(null=True, blank=True) max_limit = models.PositiveSmallIntegerField(null=True, blank=True)
ordered_nb = models.IntegerField(default=0) @property
def ordered_nb(self):
def compute_ordered_nb(self):
"""Computes the number of times this item has been ordered""" """Computes the number of times this item has been ordered"""
ordered_nb = 0 ordered_nb = 0
for order in self.orders.all(): for order in self.orders.all():
ordered_nb += order.nb ordered_nb += order.nb
self.ordered_nb = ordered_nb return ordered_nb
self.save()
def get_total_price(self): def get_total_price(self):
"""Returns the total price of all orders on this item""" """Returns the total price of all orders on this item"""

View file

@ -1,5 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Nouvelle commande groupée{% endblock %} {% block title %}Nouvelle commande groupée{% endblock %}
{% block content %} {% block content %}
@ -10,8 +12,9 @@
<p class="title">Nouvelle commande groupée</p> <p class="title">Nouvelle commande groupée</p>
<div class="columns"> <div class="columns">
<div class="column is-8"> <div class="column is-8">
<form method="post" onsubmit="deadlinePassedCheck(event)">{% csrf_token %} <form method="post" onsubmit="deadlinePassedCheck(event)">
{{ form.as_p }} {% csrf_token %}
{{ form | crispy }}
<div class="buttons"> <div class="buttons">
<a class="button is-light" href="{% url 'order:index' %}">Annuler</a> <a class="button is-light" href="{% url 'order:index' %}">Annuler</a>
<input class="button is-primary" type="submit" value="Suivant"> <input class="button is-primary" type="submit" value="Suivant">

View file

@ -33,7 +33,7 @@
{% endif %} {% endif %}
<p><i class="fa fa-calendar-check-o mr-3" aria-label="Date limite de commande" <p><i class="fa fa-calendar-check-o mr-3" aria-label="Date limite de commande"
title="Date limite de commande" aria-hidden="true"></i> title="Date limite de commande" aria-hidden="true"></i>
Commandes avant le {{ grouped_order.deadline }} Commandes avant le {{ grouped_order.deadline|date:'d M Y' }} à {{ grouped_order.deadline|date:'H:i' }}
</p> </p>
<p><i class="fa fa-truck mr-3" aria-label="Date de livraison" title="Date de livraison" <p><i class="fa fa-truck mr-3" aria-label="Date de livraison" title="Date de livraison"
aria-hidden="true"></i> aria-hidden="true"></i>
@ -158,16 +158,18 @@
value="{{ order_author.last_name }}" required></p> value="{{ order_author.last_name }}" required></p>
</div> </div>
<div class="column"> <div class="column">
<p><label for="phone">Numéro de téléphone :</label> <p><label for="phone">Numéro de téléphone {% if not is_phone_mandatory %}<em>(facultatif)</em> {% endif %}:</label>
<input id="phone" type="tel" pattern="[0-9]{10}" placeholder="0601020304" name="phone" <input id="phone" type="tel" pattern="[0-9]{10}"
value="{{ order_author.phone }}" required></p> placeholder="0601020304" name="phone"
value="{{ order_author.phone }}"
{% if is_phone_mandatory %}required{% endif %}></p>
<p><label for="email">Adresse mail : </label> <p><label for="email">Adresse mail : </label>
<input id="email" type="email" placeholder="exemple@mail.fr" name="email" <input id="email" type="email" placeholder="exemple@mail.fr" name="email"
value="{{ order_author.email }}" required></p> value="{{ order_author.email }}" required></p>
</div> </div>
</div> </div>
<p><label for="note">Note à l'organisateur·ice</label> <p><label for="note">Note à l'organisateur·ice<em> (facultatif)</em> :</label>
<textarea id="note" rows=3 placeholder="(facultatif)" name="note">{{ note }}</textarea></p> <textarea id="note" rows=3 name="note">{{ note }}</textarea></p>
<div class="buttons"> <div class="buttons">
<button id="submit" type="submit" value="Order" class="button is-primary"> <button id="submit" type="submit" value="Order" class="button is-primary">
@ -186,7 +188,6 @@
// Compute total price whenever a value in input is modified // Compute total price whenever a value in input is modified
document.getElementById("inputs-parent").addEventListener("change", function () { document.getElementById("inputs-parent").addEventListener("change", function () {
inputs = [...document.getElementsByTagName("input")].filter(input => input.getAttribute("name").indexOf("quantity_") === 0); //filter the inputs to get the quantity inputs only inputs = [...document.getElementsByTagName("input")].filter(input => input.getAttribute("name").indexOf("quantity_") === 0); //filter the inputs to get the quantity inputs only
prices = {{ prices_dict | safe }}; prices = {{ prices_dict | safe }};
let total_price = 0; let total_price = 0;

View file

@ -25,7 +25,7 @@
<p><i class="fa fa-map-pin mr-3" aria-label="Lieu" title="Lieu" aria-hidden="true"></i>{{ grouped_order.place }}</p> <p><i class="fa fa-map-pin mr-3" aria-label="Lieu" title="Lieu" aria-hidden="true"></i>{{ grouped_order.place }}</p>
{% endif %} {% endif %}
<p><i class="fa fa-calendar-check-o mr-3" aria-label="Date limite de commande" title="Date limite de commande" aria-hidden="true"></i> <p><i class="fa fa-calendar-check-o mr-3" aria-label="Date limite de commande" title="Date limite de commande" aria-hidden="true"></i>
Commandes avant le {{ grouped_order.deadline }} Commandes avant le {{ grouped_order.deadline|date:'d M Y' }} à {{ grouped_order.deadline|date:'H:i' }}
</p> </p>
<p><i class="fa fa-truck mr-3" aria-label="Date de livraison" title="Date de livraison" aria-hidden="true"></i> <p><i class="fa fa-truck mr-3" aria-label="Date de livraison" title="Date de livraison" aria-hidden="true"></i>
Livraison le {{ grouped_order.delivery_date }}{% if grouped_order.delivery_slot %}, {{ grouped_order.delivery_slot }}{% endif %} Livraison le {{ grouped_order.delivery_date }}{% if grouped_order.delivery_slot %}, {{ grouped_order.delivery_slot }}{% endif %}
@ -90,10 +90,10 @@
<div class="column"> <div class="column">
<p>Pour vous aider à distribuer les produits le jour J, vous pouvez télécharger la liste des commandes <p>Pour vous aider à distribuer les produits le jour J, vous pouvez télécharger la liste des commandes
au format PDF pour l'<strong>imprimer</strong>, ou au format CSV pour l'<strong>afficher dans un tableur</strong> :</p> au format PDF pour l'<strong>imprimer</strong>, ou au format CSV pour l'<strong>afficher dans un tableur</strong> :</p>
<a class="button is-info" href="{% url 'order:grouped_order_sheet' grouped_order.code %}" target="_blank"> <a class="button is-info" href="{% url 'order:grouped_order_sheet' grouped_order.code %}">
<i class="fa fa-file-pdf-o mr-3" aria-hidden="true"></i>Commandes en PDF <i class="fa fa-file-pdf-o mr-3" aria-hidden="true"></i>Commandes en PDF
</a> </a>
<a class="button is-info" href="{% url 'order:grouped_order_csv_export' grouped_order.code %}" target="_blank"> <a class="button is-info" href="{% url 'order:grouped_order_csv_export' grouped_order.code %}">
<i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Commandes en CSV <i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Commandes en CSV
</a> </a>
</div> </div>
@ -126,7 +126,7 @@
<tfoot> <tfoot>
<th>Total</th> <th>Total</th>
<th></th> <th></th>
<th></th> <th>{{ total_ordered_items }}</th>
<th>{{ grouped_order.total_price }} €</th> <th>{{ grouped_order.total_price }} €</th>
</tfoot> </tfoot>
</table> </table>
@ -137,11 +137,11 @@
<div id="commandes" class="box tabcontent"> <div id="commandes" class="box tabcontent">
<div class="buttons is-pulled-right"> <div class="buttons is-pulled-right">
<a class="button is-info" href="{% url 'order:email_list' grouped_order.code %}?format=csv" target="_blank"> <a class="button is-info" href="{% url 'order:email_list' grouped_order.code %}?format=csv">
<i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Emails en CSV <i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Emails en CSV
</a> </a>
<input id="email_list" name="email_list" hidden="true" value="{{ emails_list|join:';' }}" /> <input id="email_list" name="email_list" hidden="true" value="{{ emails_list|join:';' }}" />
<a class="button is-info" onclick="copyText('email_list')" target="_blank"> <a class="button is-info" onclick="copyText('email_list')">
<i class="fa fa-files-o mr-3" aria-hidden="true"></i>Copier les emails <i class="fa fa-files-o mr-3" aria-hidden="true"></i>Copier les emails
</a> </a>
</div> </div>
@ -161,7 +161,7 @@
<tr> <tr>
<td>{{ order.author }}</td> <td>{{ order.author }}</td>
<td>{{ order.price }} €</td> <td>{{ order.price }} €</td>
<td><a href="mailto:{{ order.author.email }}">{{ order.author.email }}</a></td> <td><a href="mailto:{{ order.author.email }}">{{ order.author.email }}</a>{% if order.author.phone %} / {{ order.author.phone }}{% endif %}</td>
<td> <td>
<button class="button is-info is-small js-modal-trigger" data-target="order-detail-{{ order.id }}"> <button class="button is-info is-small js-modal-trigger" data-target="order-detail-{{ order.id }}">
Voir Voir
@ -180,43 +180,36 @@
<!-- Order detail modal --> <!-- Order detail modal -->
<div id="order-detail-{{ order.id }}" class="modal"> <div id="order-detail-{{ order.id }}" class="modal">
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card ">
<header class="modal-card-head has-background-info"> <header class="modal-card-head has-background-info">
<div class="modal-card-title-container"> <div class="modal-card-title-container">
<p class="modal-card-title">Commande de {{ order.author }}</p> <p class="modal-card-title mb-2">Commande de {{ order.author }}</p>
<p class="has-text-grey-dark">Le {{ order.created_date|date:'d M Y' }} à {{ order.created_date|date:'H:i' }}</p>
</div> </div>
<button class="delete" aria-label="close"></button> <button class="delete" aria-label="close"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div class="columns is-multiline"> <div class="columns">
<div class="column is-full"> <div class="column">
{% for item in order.ordered_items.all %} {% for item in order.ordered_items.all %}
{{ item.nb }} × {{ item.item.name }} {{ item.nb }} × {{ item.item.name }}
<hr class="mb-1 mt-1"> <hr class="mb-0 mt-1">
{% endfor %} {% endfor %}
</div> </div>
{% if order.note %} {% if order.note %}
<div class="column is-full"> <div class="column is-4">
<p class="mini-title">Note à l'organisateur·ice</p> <p class="mini-title">Note à l'organisateur·ice</p>
<p>{{ order.note }}</p> <p>{{ order.note }}</p>
</div> </div>
{% endif %}
<div class="column is-full">
<p class="mini-title">Numéro de téléphone</p>
<p>{{ order.author.phone }}</p>
</div>
</div> </div>
{% endif %}
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<div class="columns"> <p class="mini-title">Total à payer</p>
<div class="column is-half"> <p>{{ order.price }} €</p>
<p class="mini-title">Total à payer</p>
<p>{{ order.price }} €</p>
</div>
</div>
</footer> </footer>
</div> </div>
</div> </div>
<!-- Confirm delete order modal --> <!-- Confirm delete order modal -->
<div id="confirm-delete-{{ order.id }}" class="modal"> <div id="confirm-delete-{{ order.id }}" class="modal">

View file

@ -21,26 +21,22 @@
} }
{% if items|length > 10 %} {% if items|length > 10 %}
.item_name { .item_name {
text-align:center;
white-space:nowrap;
-webkit-transform: rotate(-90deg); -webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg); -moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg); -ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg); -o-transform: rotate(-90deg);
transform: rotate(-90deg); transform: rotate(-90deg);
font-size: 8pt; font-size: 8pt;
height:220px;
} }
.item_name div { .item_name div {
margin:-10px -120% ; width:200px;
display:inline-block; -webkit-transform: translateX(-40%);
} -moz-transform: translateX(-40%);
.item_name div:before{ -ms-transform: translateX(-40%);
content:''; -o-transform: translateX(-40%);
width:0; transform: translateX(-40%);
padding-top:110%;
display:inline-block;
vertical-align:middle;
} }
{% endif %} {% endif %}
@ -53,20 +49,19 @@
border: 1px black solid; border: 1px black solid;
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
table-layout: fixed
} }
th, td { th, td {
vertical-align: center;
padding: 3px 2px 2px 2px; padding: 3px 2px 2px 2px;
border: 1px black solid; border: 1px black solid;
font-weight: normal; font-weight: normal;
text-align: center; text-align: center;
word-wrap: break-word;
} }
th { th {
page-break-inside: avoid; page-break-inside: avoid;
word-break: break-all;
word-wrap: break-word;
font-weight: bold; font-weight: bold;
} }
@ -85,7 +80,7 @@
<thead> <thead>
<tr> <tr>
<th style="font-size: 0.5em; width: 2em">OK</th> <th style="font-size: 0.5em; width: 2em">OK</th>
<th style="text-align: center">Nom</th> <th style="text-align: center; width: 20%">Nom</th>
{% for item in items %} {% for item in items %}
<th class="item_name" style="font-weight: normal;"> <th class="item_name" style="font-weight: normal;">
<div>{{ item.name }}</div> <div>{{ item.name }}</div>

View file

@ -1,5 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Modifier la commande groupée{% endblock %} {% block title %}Modifier la commande groupée{% endblock %}
{% block content %} {% block content %}
@ -9,13 +11,14 @@
<div class="box"> <div class="box">
<p class="title">{{ grouped_order.name }} - modifier</p> <p class="title">{{ grouped_order.name }} - modifier</p>
<form method="post" onsubmit="deadlinePassedCheck(event)">{% csrf_token %} <form method="post" onsubmit="deadlinePassedCheck(event)">{% csrf_token %}
{{ form.as_p }} {{ form | crispy }}
<div class="buttons"> <div class="buttons">
<a class="button is-light" href="{% url 'order:index' %}">Annuler</a> <a class="button is-light" href="{% url 'order:index' %}">Annuler</a>
<input class="button is-primary" type="submit" value="Suivant"> <input class="button is-primary" type="submit" value="Suivant">
</div> </div>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}

View file

@ -112,18 +112,18 @@ class GroupedOrderDetailView(generic.DetailView):
return get_object_or_404(GroupedOrder, code=self.kwargs.get("code")) return get_object_or_404(GroupedOrder, code=self.kwargs.get("code"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
items = self.get_object().item_set.all() grouped_order = self.get_object()
items = grouped_order.item_set.all()
remaining_qty = {item.id: item.get_remaining_nb() for item in items} remaining_qty = {item.id: item.get_remaining_nb() for item in items}
prices_dict = {item.id: item.price for item in items} prices_dict = {item.id: item.price for item in items}
if self.request.user.is_authenticated: if self.request.user.is_authenticated:
order_author = OrderAuthor.objects.create( order_author = OrderAuthor(
first_name=self.request.user.first_name, first_name=self.request.user.first_name,
last_name=self.request.user.last_name, last_name=self.request.user.last_name,
email=self.request.user.username, email=self.request.user.username,
) )
order_author.save()
else: else:
order_author = None order_author = None
@ -134,6 +134,8 @@ class GroupedOrderDetailView(generic.DetailView):
"prices_dict": json.dumps(prices_dict, cls=DjangoJSONEncoder), "prices_dict": json.dumps(prices_dict, cls=DjangoJSONEncoder),
"remaining_qty": remaining_qty, "remaining_qty": remaining_qty,
"order_author": order_author, "order_author": order_author,
# Used to set if the phone is required in the form
"is_phone_mandatory": grouped_order.is_phone_mandatory,
} }
) )
return context return context
@ -151,12 +153,6 @@ class GroupedOrderOverview(UserIsOrgaMixin, generic.DetailView):
# Staff can see but not edit grouped orders # Staff can see but not edit grouped orders
return super().test_func() or self.request.user.is_staff return super().test_func() or self.request.user.is_staff
def get(self, request, *args, **kwargs):
# Compute grouped order total price before display
self.get_object().compute_total_price()
self.get_object().compute_items_ordered_nb()
return super().get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GroupedOrderOverview, self).get_context_data(**kwargs) context = super(GroupedOrderOverview, self).get_context_data(**kwargs)
# Add share link to context # Add share link to context
@ -167,6 +163,7 @@ class GroupedOrderOverview(UserIsOrgaMixin, generic.DetailView):
order__in=self.get_object().order_set.all() order__in=self.get_object().order_set.all()
) )
context["emails_list"] = set(participant.email for participant in participants) context["emails_list"] = set(participant.email for participant in participants)
context["total_ordered_items"] = self.get_object().get_total_ordered_items()
return context return context
@ -228,7 +225,6 @@ class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
# duplicate each item and add it to new_grouped_order # duplicate each item and add it to new_grouped_order
for item in initial_grouped_order.item_set.all(): for item in initial_grouped_order.item_set.all():
item.pk = None item.pk = None
item.ordered_nb = 0
item.save() item.save()
new_grouped_order.item_set.add(item) new_grouped_order.item_set.add(item)
@ -300,7 +296,11 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
context = super(GroupedOrderExportView, self).get_context_data(**kwargs) context = super(GroupedOrderExportView, self).get_context_data(**kwargs)
grouped_order = self.get_object() grouped_order = self.get_object()
items = grouped_order.item_set.filter(ordered_nb__gt=0).order_by("name") items = [
item
for item in grouped_order.item_set.all().order_by("name")
if item.ordered_nb > 0
]
orders = grouped_order.order_set.all().order_by( orders = grouped_order.order_set.all().order_by(
"author__last_name", "author__first_name" "author__last_name", "author__first_name"
) )
@ -344,10 +344,9 @@ class ExportGroupOrderEmailAdressesToDownloadView(UserPassesTestMixin, generic.V
response = http.HttpResponse(content_type="text/csv") response = http.HttpResponse(content_type="text/csv")
filename = f"commande _{grouped_order.name.replace(' ', '_')}" filename = f"commande _{grouped_order.name.replace(' ', '_')}"
response["Content-Disposition"] = f'attachment; filename="{filename}.csv"' response["Content-Disposition"] = f'attachment; filename="{filename}.csv"'
writer = csv.writer(response) writer = csv.writer(response, delimiter=";")
writer.writerow(["order_name", "email"])
for email in email_list: for email in email_list:
writer.writerow([grouped_order.name, email]) writer.writerow([email])
return response return response
else: else:
email_list = ";\n".join(email_list) email_list = ";\n".join(email_list)
@ -362,10 +361,10 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
response = http.HttpResponse( response = http.HttpResponse(
content_type="text/csv", content_type="text/csv",
headers={ headers={
"Content-Disposition": f'attachment; filename="{ context["object"].name }-commandes"' "Content-Disposition": f'attachment; filename="{context["object"].name}-commandes"'
}, },
) )
writer = csv.writer(response) writer = csv.writer(response, delimiter=";")
# write headers rows # write headers rows
row = ["", ""] row = ["", ""]
@ -374,6 +373,9 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
row.append("Prix de la commande") row.append("Prix de la commande")
row.append("Mail") row.append("Mail")
row.append("Téléphone") row.append("Téléphone")
row.append("Note")
row.append("Date")
row.append("Heure")
writer.writerow(row) writer.writerow(row)
row = ["", "Prix unitaire TTC (€)"] row = ["", "Prix unitaire TTC (€)"]
@ -393,6 +395,9 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
row.append(str(order.price).replace(".", ",")) row.append(str(order.price).replace(".", ","))
row.append(order.author.email) row.append(order.author.email)
row.append(f"'{order.author.phone}") row.append(f"'{order.author.phone}")
row.append(order.note)
row.append(order.created_date.strftime("%d/%m/%Y"))
row.append(order.created_date.strftime("%H:%M"))
writer.writerow(row) writer.writerow(row)
# write total row # write total row

View file

@ -2,6 +2,7 @@ from django import http
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views import generic from django.views import generic
from la_chariotte.mail.utils import send_order_confirmation_mail from la_chariotte.mail.utils import send_order_confirmation_mail
@ -48,7 +49,12 @@ def place_order(request, code):
author = OrderAuthor.objects.create( author = OrderAuthor.objects.create(
first_name=first_name, last_name=last_name, email=email, phone=phone first_name=first_name, last_name=last_name, email=email, phone=phone
) )
order = Order.objects.create(author=author, grouped_order=grouped_order, note=note) order = Order.objects.create(
author=author,
grouped_order=grouped_order,
note=note,
created_date=timezone.now(),
)
# add items to the order # add items to the order
error_message = None error_message = None
@ -66,7 +72,6 @@ def place_order(request, code):
if error_message: if error_message:
order.delete() order.delete()
author.delete() author.delete()
grouped_order.compute_items_ordered_nb()
return render( return render(
request, request,
"order/grouped_order_detail.html", "order/grouped_order_detail.html",
@ -97,8 +102,6 @@ def place_order(request, code):
) )
# Send confirmation mail and redirect to confirmation page # Send confirmation mail and redirect to confirmation page
order.compute_order_price()
grouped_order.compute_items_ordered_nb()
send_order_confirmation_mail(order) send_order_confirmation_mail(order)
# Redirect to prevent data from being posted twice when the user hits the Back # Redirect to prevent data from being posted twice when the user hits the Back
@ -117,7 +120,6 @@ def validate_item_ordered_nb(item, ordered_nb):
def validate_articles_ordered_nb(order): def validate_articles_ordered_nb(order):
"""Return an error if no items are ordered""" """Return an error if no items are ordered"""
order.compute_order_articles_nb()
if order.articles_nb == 0: if order.articles_nb == 0:
return "Veuillez commander au moins un produit" return "Veuillez commander au moins un produit"
return None return None

View file

@ -7,8 +7,10 @@ from sentry_sdk.integrations.django import DjangoIntegration
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
BASE_URL = os.getenv("BASE_URL", "http://127.0.0.1:8000") BASE_URL = os.getenv("BASE_URL", "http://127.0.0.1:8000")
PROJECT_NAME = os.getenv("PROJECT_NAME", "La Chariotte") PROJECT_NAME = os.getenv("PROJECT_NAME", "La Chariotte")
GITLAB_LINK = "https://gitlab.com/hashbangfr/la_chariotte" GITLAB_LINK = "https://framagit.org/la-chariotte/la-chariotte"
CONTACT_MAIL = "contact@chariotte.fr" CONTACT_MAIL = "contact@chariotte.fr"
HELLOASSO_LINK = "https://www.helloasso.com/associations/la-chariotte/"
FEEDBACK_LINK = "https://framaforms.org/votre-avis-sur-la-chariotte-1709742328"
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv( SECRET_KEY = os.getenv(

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View file

@ -0,0 +1,14 @@
.accordion
transition: max-height 0.2s ease-out
border: none
outline: none
transition: 0.4s
.message-header
cursor: pointer
background: $white-ter
border: 1px solid $primary
color: $primary
p
margin: 0
.message-body
display: none

View file

@ -9,4 +9,8 @@
padding-top: $navbar-height padding-top: $navbar-height
.formatted-text .formatted-text
white-space: pre-wrap white-space: pre-wrap
img.notice-img
border: $info 3px solid
margin-bottom: 1em

View file

@ -17,3 +17,4 @@
@import "./base/titles" @import "./base/titles"
@import "./base/tabs" @import "./base/tabs"
@import "./base/footer" @import "./base/footer"
@import "./base/accordion"

View file

@ -93,6 +93,9 @@
<a class="navbar-item" href="{% url 'notice' %}"> <a class="navbar-item" href="{% url 'notice' %}">
<i class="fa fa-cog mr-3" aria-hidden="true"></i>Comment ça marche&nbsp;? <i class="fa fa-cog mr-3" aria-hidden="true"></i>Comment ça marche&nbsp;?
</a> </a>
<a class="navbar-item" href="{% url 'faq' %}">
<i class="fa fa-question mr-3" aria-hidden="true"></i>FAQ
</a>
<a class="navbar-item" href="https://blog.chariotte.fr/last"> <a class="navbar-item" href="https://blog.chariotte.fr/last">
<i class="fa fa-newspaper-o mr-3" aria-hidden="true"></i>Actualités</a> <i class="fa fa-newspaper-o mr-3" aria-hidden="true"></i>Actualités</a>
</div> </div>
@ -128,7 +131,7 @@
<div class="logo mb-4"> <div class="logo mb-4">
<p> <p>
{% settings_value "PROJECT_NAME" %} {% settings_value "PROJECT_NAME" %}
| <a href="https://gitlab.com/la-chariotte/">version {{ version }}</a> | <a href='{% settings_value "GITLAB_LINK" %}'>version {{ version }}</a>
</p> </p>
</div> </div>
<ul class="list-unstyled footer-menu lh-35"> <ul class="list-unstyled footer-menu lh-35">
@ -166,16 +169,14 @@
<h4 class="mb-4">Soutenez-nous</h4> <h4 class="mb-4">Soutenez-nous</h4>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li> <li>
<a href="https://framaforms.org/votre-avis-sur-la-chariotte-1709742328"><i class="fa fa-comments mr-2 text-muted"></i>Que pensez-vous de la Chariotte ?</a> <a href="{% settings_value "FEEDBACK_LINK" %}"><i class="fa fa-comments mr-2 text-muted"></i>Que pensez-vous de la Chariotte ?</a>
</li> </li>
<li><a href="https://www.helloasso.com/associations/la-chariotte/"><i class="fa fa-heart mr-2 text-muted"></i>Faire un (petit) don</a></li> <li><a href="{% settings_value "HELLOASSO_LINK" %}"><i class="fa fa-heart mr-2 text-muted"></i>Faire un (petit) don</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </footer>
</div> </body>
</footer>
</body>
</html> </html>
<script> <script>
// For responsive menu // For responsive menu

View file

@ -31,7 +31,7 @@
<p> <p>
La Chariotte est développée sous licence libre Affero GPL. La Chariotte est développée sous licence libre Affero GPL.
Cela signifie que vous pouvez Cela signifie que vous pouvez
<a href="{{ gitlab }}" rel="noopener" target="_blank">aller voir le code</a> si vous êtes curieux, et y contribuer si vous êtes motivé·e ! <a href="{{ gitlab }}" rel="noopener">aller voir le code</a> si vous êtes curieux, et y contribuer si vous êtes motivé·e !
</p> </p>
</div> </div>
@ -45,7 +45,7 @@
Nous sommes très, très preneurs de vos suggestions, critiques constructives et demandes, et ouvert·e·s à toute question ! Nous sommes très, très preneurs de vos suggestions, critiques constructives et demandes, et ouvert·e·s à toute question !
</p> </p>
<p> <p>
Faites des retours sur la version actuelle <a href="https://framaforms.org/vos-retours-sur-la-chariotte-1692345453" rel="noopener" target="_blank">ici</a>, ou par mail à <a href="mailto:{{ mail }}">{{ mail }}</a>. Faites des retours sur la version actuelle <a href="https://framaforms.org/vos-retours-sur-la-chariotte-1692345453" rel="noopener">ici</a>, ou par mail à <a href="mailto:{{ mail }}">{{ mail }}</a>.
</p> </p>
</div> </div>

View file

@ -0,0 +1,241 @@
{% extends 'base.html' %}
{% block title %}FAQ{% endblock %}
{% block content %}
{% load static %}
{% load settings %}
<p class="desktop-hidden mobile-content-title">
{% block content_title %}La Chariotte - Facile A Qomprendre{% endblock %}
</p>
<div class="box">
<div class="accordion">
<article class="message">
<div class="message-header">
<p>C'est quoi l'outil la Chariotte ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
La Chariotte c'est une application web qui permet de simplifier la gestion et l'organisation de commandes groupées.
Voyez ça comme un excel amélioré spécifique aux besoins de l'organisation d'une commande groupée.
</div>
</article>
<article class="message">
<div class="message-header">
<p>C'est quoi la Chariotte plus largement ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
C'est avant tout un logiciel libre dont vous pouvez utiliser l'instance officielle sur chariotte.fr.
C'est aussi le nom de l'association responsable de la maintenance de ce logiciel. Tous bénévoles,
les membres de la Chariotte font vivre et améliorent continuellement l'outil pour répondre à vos besoins.
</div>
</article>
<article class="message">
<div class="message-header">
<p>Est-ce que je peux rejoindre une commande commande créée par une personne que je ne connais pas ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
C'est prévu a long terme mais pour l'instant non ! Aujourd'hui la Chariotte n'a pas pour visée de
mettre en relation des organisateurs/producteurs et des participants. Il n'est donc pas
possible de trouver une liste de commandes groupées existantes que vous pourriez rejoindre à volonté.
</div>
</article>
<article class="message">
<div class="message-header">
<p>Faut-il faire partie de l'asso pour utiliser la chariotte ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
Non !
</div>
</article>
<article class="message">
<div class="message-header">
<p>Puis-je rejoindre l'association ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
Bien sûr et avec grand plaisir !
Si vous êtes intéressés pour rejoindre cette aventure,
contactez-nous sur <a href="mailto:{% settings_value "CONTACT_MAIL" %}">contact@chariotte.fr</a>.
</div>
</article>
<article class="message">
<div class="message-header">
<p>C'est quoi un logiciel libre ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
La Chariotte est développée sous licence libre Affero GPL.
Cela signifie que vous pouvez <a href="{% settings_value "GITLAB_LINK" %}">aller voir le code</a>
si vous êtes curieux, y contribuer si vous êtes motivé·e, ou même créer votre propre instance si vous le souhaitez !
</div>
</article>
<article class="message">
<div class="message-header">
<p>C'est quoi une commande groupée ? Ça s'organise comment ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
C'est la mise en relation ponctuelle d'un producteur, ou plutôt ses produits,
avec des particuliers, pour permettre une consommation en circuit court et hors
des modes de consommation de masse conventionnels.
<br><br>
Pour faire simple, imaginons que vous connaissiez l'apicultrice du coin et que plusieurs de
vos voisins vous aient dit être prêts à acheter du miel.
Vous pouvez alors commander 40 pots de miel à votre amie et les redistribuer à tout le quartier,
tout ça en un seul voyage !
</div>
</article>
<article class="message">
<div class="message-header">
<p>Quelle est la différence avec une AMAP ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
Dans les faits, la plupart des AMAPs fonctionnent sur la base de commandes
groupées régulières et à destination d'un public d'adhérents fixe.<br>
La Chariotte n'a pas vocation à remplacer le système des AMAPs, qui apporte un revenu régulier aux producteur.ice.s !
L'idée est d'apporter une solution complémentaire pour les endroits où les AMAPs ne sont pas établies, ou pour un produit en particulier qui n'est pas distribué régulièrement dans votre AMAP.
Il arrive aussi parfois que la Chariotte soit choisie comme outil de gestion pour l'AMAP si elle n'en avait pas encore.
</div>
</article>
<article class="message">
<div class="message-header">
<p>Quel est l'avantage de la Chariotte par rapport au logiciel de mon AMAP ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
Nous avons conçu la Chariotte spécifiquement pour répondre aux besoins des
organisateur.ice.s et des participant.e.s à une commande groupée, en particulier des commandes groupées ponctuelles.
Il s'agit d'un outil spécialisé dont la seule ambition est de simplifier la vie de ses utilisateurs.
Libre à vous d'évaluer vos options et de choisir l'outil qui convient le mieux à votre organisation !
</div>
</article>
<article class="message">
<div class="message-header">
<p>Je veux organiser une commande groupée, où est-ce que je trouve des producteurs ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
La Chariotte ne fournit pour l'instant pas de liste de producteurs ;
c'est une bonne occasion de faire connaissance avec ceux près de chez vous !
</div>
</article>
<article class="message">
<div class="message-header">
<p>Suis-je obligé de créer un compte pour commander ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
Même pas ! La création d'un compte n'est obligatoire que pour les utilisateurs souhaitant organiser
leurs propres commandes groupées. La participation à une commande existante
se veut à destination du plus grand nombre et ne nécessite donc pas de compte sur le site.
</div>
</article>
<article class="message">
<div class="message-header">
<p>Comment puis-je soutenir la Chariotte ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
<p>Nous sommes une jeune association, et nous avons besoin de trois choses : de la visibilité,
des retours d'utilisateurs et un peu d'argent !</li>
<ul><li>Vous pouvez nous rejoindre sur les réseaux (Facebook, Mastodon, LinkedIn) pour aimer
et partager nos posts, ou encore parler de la Chariotte à vos amis !</li>
<li>Si vous saisissez une occasion d'utiliser la Chariotte, nous avons besoin de vous !
N'hésitez pas à nous faire un rapport de votre expérience de la Chariotte en pointant
les bons et les mauvais côtés de l'application. Par exemple en remplissant
<a href="{% settings_value "FEEDBACK_LINK" %}">ce formulaire</a> !</li>
<li>Si le projet vous parle et que vous avez envie de nous soutenir de manière plus significative,
nous avons besoin d'un peu d'argent de poche pour soutenir les frais de l'association ;
<a href ="{% settings_value "HELLOASSO_LINK" %}">par ici</a> pour en savoir plus !</li>
</ul>
<p>Et enfin, si vous souhaitez en savoir plus sur l'association en elle-même et que vous
souhaitez faire partie intégrante du projet, la Chariotte est à la recherche de bénévoles motivé·e·s !
N'hésitez pas à nous écrire un mot sur contact@chariotte.fr pour en savoir plus et rentrer en contact directement avec nous.</p>
</div>
</article>
</div>
</div>
{% endblock %}
<script>
{% block extra_js %}
document.addEventListener('DOMContentLoaded', () => {
var acc = document.getElementsByClassName("message-header");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
}
});
{% endblock %}
</script>

View file

@ -34,7 +34,7 @@
<p class="title">Licence</p> <p class="title">Licence</p>
<p> <p>
La Chariotte est développée sous licence libre <strong>Affero GPL</strong>. Le code source est La Chariotte est développée sous licence libre <strong>Affero GPL</strong>. Le code source est
consultable <a href="{{ gitlab }}" rel="noopener" target="_blank">sur Gitlab</a> et ouvert à toute contribution ou suggestion. consultable <a href="{{ gitlab }}" rel="noopener">sur Gitlab</a> et ouvert à toute contribution ou suggestion.
</p> </p>
<p class="title">Conditions d'utilisation</p> <p class="title">Conditions d'utilisation</p>
<p> <p>

View file

@ -9,11 +9,126 @@
{% block content_title %}La Chariotte - mode d'emploi{% endblock %} {% block content_title %}La Chariotte - mode d'emploi{% endblock %}
</p> </p>
<div class="box"> <div class="box">
<p>Voilà un petit dessin pour vous expliquer le déroulé d'une commande groupée avec la Chariotte.</p> <p>Comment marche la Chariotte et qu'est-ce qu'on peut y faire ? Voilà comment vous pouvez :</p>
<figure class="image"> <ul>
<img src="{% static 'img/notice_1.jpg' %}"/> <li><a href="#join_grouped_order">Rejoindre une commande et commander des produits</a></li>
<img src="{% static 'img/notice_2.jpg' %}"/> <li><a href="#pay">Payer (pas encore !)</a></li>
<img src="{% static 'img/notice_3.jpg' %}"/> <li><a href="#create_account">Créer un compte (nécessaire seulement pour organiser une commande groupée)</a></li>
</figure> <li><a href="#create_grouped_order">Créer une commande groupée</a></li>
<li><a href="#add_item">Ajouter des produits à une commande groupée</a></li>
<li><a href="#go_to_overview">Accéder à la page de gestion de ma commande groupée</a></li>
<li><a href="#delete_item">Modifier des produits d'une commande après sa création</a></li>
<li><a href="#edit_grouped_order">Modifier des informations de la commande</a></li>
<li><a href="#share_grouped_order">Partager une commande à des participants</a></li>
<li><a href="#print_orders_list">Imprimer ma liste de commandes</a></li>
<li><a href="#get_spreadsheet">Voir les commandes sous forme de tableur</a></li>
</ul>
</div>
<div class="box" id="join_grouped_order">
<p class="title">Rejoindre une commande et commander des produits</p>
<p>Pour rejoindre une commande groupée, il faut y avoir été invité.e par son organisateur.ice.
Si vous avez un code à 6 caractères, vous pouvez l'entrer dans l'encadré "Rejoindre une commande groupée"
sur la page d'accueil de la Chariotte.</p>
<img class="notice-img" src="{% static 'img/notice/join_grouped_order.png' %}"/>
<p>Sinon, cliquez sur le lien que l'organisateur.ice vous a envoyé. Vous arriverez sur la page de commande.</p>
<img class="notice-img" src="{% static 'img/notice/order_form.png' %}"/>
<p>Une fois la commande validée, vous recevrez un mail de confirmation.</p>
</div>
<div class="box" id="pay">
<p class="title">Payer (pas encore !)</p>
<p>Pour linstant, le paiement nest pas pris en compte par le site de La Chariotte,
cest lorganisateur.ice qui doit vous indiquer les modalités de paiement.</p>
</div>
<div class="box" id="create_account">
<p class="title">Créer un compte (nécessaire seulement pour organiser une commande groupée)</p>
<p>1. Cliquez sur “Créer un compte”.</p>
<img class="notice-img" src="{% static 'img/notice/create_account.jpg' %}"/>
<p>2. Remplissez toutes les informations du formulaire, puis cliquez sur valider.</p>
<img class="notice-img" src="{% static 'img/notice/create_account_form.jpg' %}"/>
<p>3. Vous pouvez maintenant vous connecter.</p>
</div>
<div class="box" id="create_grouped_order">
<p class="title">Créer une commande groupée</p>
<p>1. En ayant un compte (nécessaire seulement pour les organisateurs!), vous aurez accès à une page de gestion des commandes groupées en cliquant sur le menu “Mes commandes groupées”.</p>
<img class="notice-img" src="{% static 'img/notice/my_grouped_orders.jpg' %}"/>
<p>2. Cest depuis cette page que vous pourrez créer une nouvelle commande en cliquant sur le bouton “Créer une nouvelle commande groupée” : </p>
<img class="notice-img" src="{% static 'img/notice/create_grouped_order.jpg' %}"/>
<p>3. Quelques informations vous seront alors demandées :</p>
<img class="notice-img" src="{% static 'img/notice/create_grouped_order_form.jpg' %}"/>
<p>4. En cliquant sur "Suivant", votre commande groupée sera validée.</p>
</div>
<div class="box" id="add_item">
<p class="title">Ajouter des produits à une commande groupée</p>
<p>Directement après avoir validé la commande groupée, vous accédez à la page de gestion des produits.</p>
<img class="notice-img" src="{% static 'img/notice/add_item.png' %}"/>
<p>Ici, vous pouvez renseigner les produits qui constitueront votre commande groupée.
Indiquez le nom du produit, son prix et la quantité disponible.
Cliquez sur “Ajouter” pour valider lajout.</p>
<img class="notice-img" src="{% static 'img/notice/add_item_2.png' %}"/>
</div>
<div class="box" id="go_to_overview">
<p class="title">Accéder à la page de gestion de ma commande groupée</p>
<p>Depuis le menu “Mes commandes groupées”, puis en cliquant sur le nom de la commande groupée
que vous voulez gérer, vous pourrez cliquer ensuite sur “Gestion de la commande groupee”.
</p>
<img class="notice-img" src="{% static 'img/notice/go_to_overview.png' %}"/>
<p>Sinon depuis la page “Mes commandes groupées” il est aussi possible de cliquer directement sur
licone de la ligne "liste" de la commande groupée que vous voulez gérer.</p>
</div>
<div class="box" id="delete_item">
<p class="title">Modifier des produits d'une commande après sa création</p>
<p>1. Si vous souhaitez modifier une information concernant la commande (titre, date, description, lieu de livraison, …),
il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Ensuite, dans "Résumé de la commande”, cliquez sur “Gérer les produits”.</p>
<img class="notice-img" src="{% static 'img/notice/grouped_order_sum_up.png' %}"/>
<img class="notice-img" src="{% static 'img/notice/items_overview.png' %}"/>
<p>3. Vous êtes maintenant sur la page de gestion des produits depuis laquelle vous pouvez ajouter ou supprimer des produits.
Cliquez sur “Valider” pour sauvegarder les modifications.</p>
<p>Attention : actuellement, il n'est pas possible de supprimer un produit une fois qu'il a été commandé par un.e participant.e.
Si vous voulez vraiment le supprimer, il faudra d'abord supprimer les commandes des participant.e.s concerné.e.s.</p>
</div>
<div class="box" id="edit_grouped_order">
<p class="title">Modifier des informations de la commande</p>
<p>1. Si vous souhaitez modifier une information concernant la commande (titre, date, description, lieu de livraison, …),
il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Vous pouvez ensuite cliquer sur le bouton “Modifier la commande”
qui vous amenera vers le formulaire pre-rempli des informations de la commande. </p>
<img class="notice-img" src="{% static 'img/notice/edit_grouped_order.png' %}"/>
<p>3. Modifiez les informations que vous voulez, puis cliquez sur “Suivant” pour valider.</p>
</div>
<div class="box" id="share_grouped_order">
<p class="title">Partager une commande à des participants</p>
<p>Une fois votre commande bien préparée (quantité et prix des produits corrects, informations de la commande justes, …),
il faut maintenant la partager à des participants pour quils puissent commander.</p>
<p>1. Depuis la page de <a href="#go_to_overview">gestion de la commande</a>,
allez dans longlet “partager et exporter”.</p>
<img class="notice-img" src="{% static 'img/notice/share_grouped_order.png' %}"/>
<p>2. Vous pouvez copier le lien, et lenvoyer à vos connaissances.</p>
<img class="notice-img" src="{% static 'img/notice/copy_link.png' %}"/>
</div>
<div class="box" id="print_orders_list">
<p class="title">Imprimer ma liste de commandes</p>
<p>1. Pour cela, il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Ensuite, dans "Partager et exporter”, cliquez sur “Commandes en PDF”.</p>
<img class="notice-img" src="{% static 'img/notice/print_orders_list.png' %}"/>
<p>3. Cela lance le téléchargement de votre liste de commandes en PDF.</p>
</div>
<div class="box" id="get_spreadsheet">
<p class="title">Voir les commandes sous forme de tableur</p>
<p>1. Pour cela, il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Ensuite, dans "Partager et exporter”, cliquez sur “Commandes en CSV”.</p>
<img class="notice-img" src="{% static 'img/notice/get_spreadsheet.png' %}"/>
<p>3. Le format CSV vous permet d'ouvrir le fichier avec le tableur de votre choix (libreoffice, excel, ...).</p>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -662,6 +662,36 @@ class TestGroupedOrderDetailView:
assert order.price == 4 assert order.price == 4
assert item.get_remaining_nb() == 16 assert item.get_remaining_nb() == 16
def test_phone_not_required_display(self, client, other_user):
"""a user orders something without entering phone when it is required"""
grouped_order = create_grouped_order(
days_before_delivery_date=5,
days_before_deadline=2,
name="gr order test",
orga_user=other_user,
)
assert grouped_order.is_phone_mandatory == True
item = models.Item.objects.create(
name="test item 1", grouped_order=grouped_order, price=1, max_limit=2
)
detail_url = reverse(
"order:grouped_order_detail",
kwargs={
"code": grouped_order.code,
},
)
response = client.get(detail_url)
assert response.status_code == 200
assert "gr order test" in response.content.decode()
assert (
"Numéro de téléphone <em>(facultatif)</em>" not in response.content.decode()
)
grouped_order.is_phone_mandatory = False
grouped_order.save()
response = client.get(detail_url)
assert "gr order test" in response.content.decode()
assert "Numéro de téléphone <em>(facultatif)</em>" in response.content.decode()
class TestGroupedOrderOverview: class TestGroupedOrderOverview:
def test_get_overview(self, client_log): def test_get_overview(self, client_log):
@ -706,6 +736,7 @@ class TestGroupedOrderOverview:
assert response.status_code == 200 assert response.status_code == 200
assert "test item" in response.content.decode() assert "test item" in response.content.decode()
assert {"test@mail.fr"} == response.context["emails_list"] assert {"test@mail.fr"} == response.context["emails_list"]
assert 4 == response.context["total_ordered_items"]
assert "gr order test" in response.content.decode() assert "gr order test" in response.content.decode()
item.refresh_from_db() item.refresh_from_db()
assert item.get_total_price() == 8 assert item.get_total_price() == 8
@ -1395,7 +1426,7 @@ class TestGroupedOrderSheetView:
response = client_log.get(generate_sheet_url) response = client_log.get(generate_sheet_url)
assert response.status_code == 200 assert response.status_code == 200
assert response.context["grouped_order"] == grouped_order assert response.context["grouped_order"] == grouped_order
assert response.context["items"].count() == 0 assert len(response.context["items"]) == 0
assert len(response.context["orders_dict"]) == 0 assert len(response.context["orders_dict"]) == 0
# we order some items in the grouped order # we order some items in the grouped order
@ -1403,7 +1434,7 @@ class TestGroupedOrderSheetView:
response = client_log.get(generate_sheet_url) response = client_log.get(generate_sheet_url)
assert response.status_code == 200 assert response.status_code == 200
assert response.context["grouped_order"] == grouped_order assert response.context["grouped_order"] == grouped_order
assert response.context["items"].count() == 2 assert len(response.context["items"]) == 2
assert response.context["orders_dict"][order] == [3, 2] assert response.context["orders_dict"][order] == [3, 2]
assert response.context["grouped_order"].total_price == 35 assert response.context["grouped_order"].total_price == 35
@ -1521,8 +1552,7 @@ class TestExportGroupOrderEmailAdressesToDownloadView:
assert response.status_code == 200 assert response.status_code == 200
assert response["Content-Type"] == "text/csv" assert response["Content-Type"] == "text/csv"
content = response.content.decode() content = response.content.decode()
assert "order_name,email" in content assert "test@mail.fr\r\n" in content
assert "order_name,email\r\ngr order test,test@mail.fr\r\n" in content
def test_export_format_default(self, client_log): def test_export_format_default(self, client_log):
grouped_order = create_grouped_order( grouped_order = create_grouped_order(
@ -1629,8 +1659,10 @@ class TestExportGroupedOrderToCSVView:
assert response.status_code == 200 assert response.status_code == 200
content = response.content.decode("utf-8") content = response.content.decode("utf-8")
csv_reader = csv.reader(StringIO(content)) csv_reader = csv.reader(StringIO(content), delimiter=";")
body = list(csv_reader) body = list(csv_reader)
created_date = f"{timezone.now().strftime('%d/%m/%Y')}"
created_time = f"{timezone.now().strftime('%H:%M')}"
assert body == [ assert body == [
[ [
"", "",
@ -1640,11 +1672,47 @@ class TestExportGroupedOrderToCSVView:
"Prix de la commande", "Prix de la commande",
"Mail", "Mail",
"Téléphone", "Téléphone",
"Note",
"Date",
"Heure",
], ],
["", "Prix unitaire TTC (€)", "2,00", "9,00"], ["", "Prix unitaire TTC (€)", "2,00", "9,00"],
["Nom", "Prénom"], ["Nom", "Prénom"],
["alescargot", "bob", "1", "0", "2,00", "bob2@escargot.fr", "'000"], [
["alescargot", "bobby", "0", "1", "9,00", "bob3@escargot.fr", "'000"], "alescargot",
["lescargot", "bob", "3", "2", "24,00", "bob@escargot.fr", "'000"], "bob",
"1",
"0",
"2,00",
"bob2@escargot.fr",
"'000",
"",
created_date,
created_time,
],
[
"alescargot",
"bobby",
"0",
"1",
"9,00",
"bob3@escargot.fr",
"'000",
"",
created_date,
created_time,
],
[
"lescargot",
"bob",
"3",
"2",
"24,00",
"bob@escargot.fr",
"'000",
"",
created_date,
created_time,
],
["", "TOTAL", "4", "3", "35,00"], ["", "TOTAL", "4", "3", "35,00"],
] ]

View file

@ -9,12 +9,20 @@ pytestmark = pytest.mark.django_db
def create_grouped_order( def create_grouped_order(
days_before_delivery_date, days_before_deadline, name, orga_user days_before_delivery_date,
days_before_deadline,
name,
orga_user,
is_phone_mandatory=True,
): ):
date = timezone.now().date() + datetime.timedelta(days=days_before_delivery_date) date = timezone.now().date() + datetime.timedelta(days=days_before_delivery_date)
deadline = timezone.now() + datetime.timedelta(days=days_before_deadline) deadline = timezone.now() + datetime.timedelta(days=days_before_deadline)
grouped_order = models.GroupedOrder.objects.create( grouped_order = models.GroupedOrder.objects.create(
name=name, orga=orga_user, delivery_date=date, deadline=deadline name=name,
orga=orga_user,
delivery_date=date,
deadline=deadline,
is_phone_mandatory=is_phone_mandatory,
) )
grouped_order.create_code_from_pk() grouped_order.create_code_from_pk()
grouped_order.save() grouped_order.save()
@ -44,11 +52,4 @@ def order_items_in_grouped_order(grouped_order):
models.OrderedItem.objects.create(order=order, item=item_2, nb=2) models.OrderedItem.objects.create(order=order, item=item_2, nb=2)
models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1) models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1)
models.OrderedItem.objects.create(order=order_3, item=item_2, nb=1) models.OrderedItem.objects.create(order=order_3, item=item_2, nb=1)
item_1.compute_ordered_nb()
item_2.compute_ordered_nb()
order.compute_order_price()
order_2.compute_order_price()
order_3.compute_order_price()
grouped_order.compute_total_price()
grouped_order.save()
return order return order

View file

@ -62,6 +62,11 @@ urlpatterns = [
TemplateView.as_view(template_name="help/notice.html"), TemplateView.as_view(template_name="help/notice.html"),
name="notice", name="notice",
), ),
path(
"faq",
TemplateView.as_view(template_name="help/faq.html"),
name="faq",
),
path( path(
"mentions-legales", "mentions-legales",
TemplateView.as_view( TemplateView.as_view(

View file

@ -1,7 +1,7 @@
site_name: La chariotte site_name: La chariotte
site_description: An application for grouped-orders site_description: An application for grouped-orders
repo_name: la-chariotte/la_chariotte repo_name: la-chariotte/la_chariotte
repo_url: https://gitlab.com/la-chariotte/la_chariotte repo_url: https://framagit.org/la-chariotte/la-chariotte
nav: nav:
- How-tos: - How-tos:
- Getting started: install.md - Getting started: install.md

7991
static/sass/style.css Normal file

File diff suppressed because it is too large Load diff

81
static/sass/style.css.map Normal file

File diff suppressed because one or more lines are too long