1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-24 19:20:29 +02:00

Compare commits

..

3 Commits

Author SHA1 Message Date
d3080ad830 Merge branch 'nix-shell' into 'main'
Nix shell

See merge request bde/nk20!201
2024-03-24 22:50:41 +01:00
dde1baa25c typo 2022-08-21 19:50:53 +02:00
7a7ee47e0b Add two shell.nix to enable easier development on nixos. 2022-08-21 19:46:11 +02:00
36 changed files with 980 additions and 1478 deletions

1
.gitignore vendored
View File

@ -48,7 +48,6 @@ backups/
env/ env/
venv/ venv/
db.sqlite3 db.sqlite3
shell.nix
# ansibles customs host # ansibles customs host
ansible/host_vars/*.yaml ansible/host_vars/*.yaml

View File

@ -8,19 +8,19 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Debian Buster # Debian Buster
# py37-django22: py37-django22:
# stage: test stage: test
# image: debian:buster-backports image: debian:buster-backports
# before_script: before_script:
# - > - >
# apt-get update && apt-get update &&
# apt-get install --no-install-recommends -t buster-backports -y apt-get install --no-install-recommends -t buster-backports -y
# python3-django python3-django-crispy-forms python3-django python3-django-crispy-forms
# python3-django-extensions python3-django-filters python3-django-polymorphic python3-django-extensions python3-django-filters python3-django-polymorphic
# python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
# python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
# python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
# script: tox -e py37-django22 script: tox -e py37-django22
# Ubuntu 20.04 # Ubuntu 20.04
py38-django22: py38-django22:
@ -56,7 +56,7 @@ py39-django22:
linters: linters:
stage: quality-assurance stage: quality-assurance
image: debian:bullseye image: debian:buster-backports
before_script: before_script:
- apt-get update && apt-get install -y tox - apt-get update && apt-get install -y tox
script: tox -e linters script: tox -e linters

View File

@ -17,27 +17,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</form> </form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extrajavascript %}
<script>
var date_end = document.getElementById("id_date_end");
var date_start = document.getElementById("id_date_start");
function update_date_end (){
if(date_end.value=="" || date_end.value<date_start.value){
date_end.value = date_start.value;
};
};
function update_date_start (){
if(date_start.value=="" || date_end.value<date_start.value){
date_start.value = date_end.value;
};
};
date_start.addEventListener('focusout', update_date_end);
date_end.addEventListener('focusout', update_date_start);
</script>
{% endblock %}

View File

@ -46,4 +46,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3> </h3>
{% render_table table %} {% render_table table %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -17,8 +17,7 @@ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView from django.views.generic import DetailView, TemplateView, UpdateView
from django.views.generic.list import ListView from django_tables2.views import SingleTableView
from django_tables2.views import MultiTableMixin
from note.models import Alias, NoteSpecial, NoteUser from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
@ -58,40 +57,27 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
Displays all Activities, and classify if they are on-going or upcoming ones. Displays all Activities, and classify if they are on-going or upcoming ones.
""" """
model = Activity model = Activity
tables = [ActivityTable, ActivityTable] table_class = ActivityTable
ordering = ('-date_start',)
extra_context = {"title": _("Activities")} extra_context = {"title": _("Activities")}
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).distinct() return super().get_queryset(**kwargs).distinct()
def get_tables(self):
tables = super().get_tables()
tables[0].prefix = "all-"
tables[1].prefix = "upcoming-"
return tables
def get_tables_data(self):
# first table = all activities, second table = upcoming
return [
self.get_queryset().order_by("-date_start"),
Activity.objects.filter(date_end__gt=timezone.now())
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
.distinct()
.order_by("date_start")
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
tables = context["tables"] upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
for name, table in zip(["table", "upcoming"], tables): context['upcoming'] = ActivityTable(
context[name] = table data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
prefix='upcoming-',
order_by='date_start',
)
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
context["started_activities"] = started_activities context["started_activities"] = started_activities

View File

@ -138,9 +138,6 @@ class ImageForm(forms.Form):
return cleaned_data return cleaned_data
def is_valid(self):
return super().is_valid() or super().clean().get('image') is None
class ClubForm(forms.ModelForm): class ClubForm(forms.ModelForm):
def clean(self): def clean(self):
@ -154,7 +151,7 @@ class ClubForm(forms.ModelForm):
class Meta: class Meta:
model = Club model = Club
exclude = ("add_registration_form",) fields = '__all__'
widgets = { widgets = {
"membership_fee_paid": AmountInput(), "membership_fee_paid": AmountInput(),
"membership_fee_unpaid": AmountInput(), "membership_fee_unpaid": AmountInput(),

View File

@ -1,18 +0,0 @@
# Generated by Django 2.2.28 on 2024-07-15 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0011_profile_vss_charter_read'),
]
operations = [
migrations.AddField(
model_name='club',
name='add_registration_form',
field=models.BooleanField(default=False, verbose_name='add to registration form'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 2.2.28 on 2024-08-07 12:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0012_club_add_registration_form'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2024, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View File

@ -259,11 +259,6 @@ class Club(models.Model):
help_text=_('Maximal date of a membership, after which members must renew it.'), help_text=_('Maximal date of a membership, after which members must renew it.'),
) )
add_registration_form = models.BooleanField(
verbose_name=_("add to registration form"),
default=False,
)
class Meta: class Meta:
verbose_name = _("club") verbose_name = _("club")
verbose_name_plural = _("clubs") verbose_name_plural = _("clubs")

View File

@ -14,9 +14,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
<form method="post" enctype="multipart/form-data" id="formUpload"> <form method="post" enctype="multipart/form-data" id="formUpload">
{% csrf_token %} {% csrf_token %}
{{ form |crispy }} {{ form |crispy }}
{% if user.note.display_image != "pic/default.png" %}
<input type="submit" class="btn btn-primary" value="{% trans "Remove" %}">
{% endif %}
</form> </form>
</div> </div>
<!-- MODAL TO CROP THE IMAGE --> <!-- MODAL TO CROP THE IMAGE -->

View File

@ -26,7 +26,7 @@ from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
CustomAuthenticationForm, MembershipRolesForm CustomAuthenticationForm, MembershipRolesForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -326,15 +326,12 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
"""Save image to note""" """Save image to note"""
image = form.cleaned_data['image'] image = form.cleaned_data['image']
if image is None: # Rename as a PNG or GIF
image = "pic/default.png" extension = image.name.split(".")[-1]
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else: else:
# Rename as a PNG or GIF image.name = "{}_pic.png".format(self.object.note.pk)
extension = image.name.split(".")[-1]
if extension == "gif":
image.name = "{}_pic.gif".format(self.object.note.pk)
else:
image.name = "{}_pic.png".format(self.object.note.pk)
# Save # Save
self.object.note.display_image = image self.object.note.display_image = image

View File

@ -13,7 +13,7 @@ from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \ from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \ TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
TrustSerializer TrustSerializer
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust

View File

@ -23,7 +23,7 @@
<p> <p>
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
est inférieur à 0 €. est inférieur à 0 € depuis plus de 24h.
</p> </p>
<p> <p>
@ -43,4 +43,4 @@
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p> </p>
</body> </body>
</html> </html>

View File

@ -1,65 +0,0 @@
{% load pretty_money %}
{% load i18n %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>[Note Kfet] Récapitulatif de trésorerie</title>
</head>
<body>
<h1>
Récapitulatif de trésorerie au {{ summary.date|date:"d/m/Y" }} à {{ summary.date|date:"H:i:s" }} :
</h1>
<h2>
Tous les utilisateur⋅rices :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_user }}</li>
<li>Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user / 100 }} €</li>
</ul>
<h2>
Les {{ summary.total_positive_user_bde + summary.total_zero_user_bde + summary.total_negative_user_bde }} adhérent⋅es BDE :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_user_bde }}</li>
<li>Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde / 100 }} €</li>
</ul>
<h2>
Clubs :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_club }}</li>
<li>Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club / 100 }} €</li>
</ul>
<h2>
Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_club_nbde }}</li>
<li>Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde / 100 }} €</li>
</ul>
<h2>
Progression :
</h2>
<ul>
<li>Ceci correspond à une différence de {{ balance_difference_user / 100 }} € pour les utilisateur⋅rices</li>
<li>Ceci correspond à une différence de {{ balance_difference_club / 100 }} € pour les clubs</li>
</ul>
--
<p>
Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p>
</body>
</html>

View File

@ -1,33 +0,0 @@
{% load pretty_money %}
{% load i18n %}
Récapitulatif de trésorerie au {{ summary.date|date:"d/m/Y" }} à {{ summary.date|date:"H:i:s" }} :
Tous les utilisateur⋅rices :
- Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user / 100 }} €
- Neutres : {{ summary.total_zero_user }}
- Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user / 100 }} €
Les {{ summary.total_positive_user_bde + summary.total_zero_user_bde + summary.total_negative_user_bde }} adhérent⋅es BDE :
- Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde / 100 }} €
- Neutres : {{ summary.total_zero_user_bde }}
- Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde /100 }} €
Clubs :
- Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club / 100 }} €
- Neutres : {{ summary.total_zero_club }}
- Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club / 100 }} €
Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" :
- Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde / 100 }} €
- Neutres : {{ summary.total_zero_club_nbde }}
- Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde / 100 }} €
Progression :
- Ceci correspond à une différence de {{ balance_difference_user / 100 }} € pour les utilisateur⋅rices
- Ceci correspond à une différence de {{ balance_difference_club / 100 }} € pour les clubs
--
Le BDE
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

View File

@ -10,7 +10,7 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from permission.models import Role from permission.models import Role
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \ from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
TransactionTemplateViewSet, TransactionViewSet TransactionTemplateViewSet, TransactionViewSet
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \ from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note

View File

@ -2591,12 +2591,12 @@
"note", "note",
"transaction" "transaction"
], ],
"query": "[\"OR\", {\"source__balance__gte\": 0}, [\"AND\", [\"NOT\", {\"recurrenttransaction__template__category__name\": \"Alcool\"}], {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}], {\"valid\": false}]", "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]",
"type": "add", "type": "add",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Créer une transaction quelconque tant que la source reste positive s'il s'agit d'alcool, sinon au-dessus de -20€" "description": "Créer une transaction quelconque tant que la source reste au-dessus de -20 €"
} }
}, },
{ {

View File

@ -5,6 +5,7 @@ from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -114,3 +115,12 @@ class ValidationForm(forms.Form):
required=False, required=False,
initial=True, initial=True,
) )
# If the bda exists
if Club.objects.filter(name__iexact="bda").exists():
# The user can join the bda club at the inscription
join_bda = forms.BooleanField(
label=_("Join BDA Club"),
required=False,
initial=True,
)

View File

@ -1,7 +1,6 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -239,8 +238,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
kfet = Club.objects.get(name="Kfet") kfet = Club.objects.get(name="Kfet")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
for club in Club.objects.filter(add_registration_form=True): if Club.objects.filter(name__iexact="BDA").exists():
fee += club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid bda = Club.objects.get(name__iexact="BDA")
fee += bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
ctx["total_fee"] = "{:.02f}".format(fee / 100, ) ctx["total_fee"] = "{:.02f}".format(fee / 100, )
# ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists() # ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
@ -249,16 +249,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
# add clubs that are in registration form
for club in Club.objects.filter(add_registration_form=True).order_by("name"):
form_join_club = forms.BooleanField(
label=_("Join %(club)s Club") % {'club': club.name},
required=False,
initial=False,
)
form.fields.update({f"join_{club.id}": form_join_club})
user = self.get_object() user = self.get_object()
form.fields["last_name"].initial = user.last_name form.fields["last_name"].initial = user.last_name
form.fields["first_name"].initial = user.first_name form.fields["first_name"].initial = user.first_name
@ -276,6 +266,11 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
form.add_error(None, _("An alias with a similar name already exists.")) form.add_error(None, _("An alias with a similar name already exists."))
return self.form_invalid(form) return self.form_invalid(form)
# Check if BDA exist to propose membership at regisration
bda_exists = False
if Club.objects.filter(name__iexact="BDA").exists():
bda_exists = True
# Get form data # Get form data
# soge = form.cleaned_data["soge"] # soge = form.cleaned_data["soge"]
credit_type = form.cleaned_data["credit_type"] credit_type = form.cleaned_data["credit_type"]
@ -285,9 +280,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
bank = form.cleaned_data["bank"] bank = form.cleaned_data["bank"]
join_bde = form.cleaned_data["join_bde"] join_bde = form.cleaned_data["join_bde"]
join_kfet = form.cleaned_data["join_kfet"] join_kfet = form.cleaned_data["join_kfet"]
if bda_exists:
clubs_registration = Club.objects.filter(add_registration_form=True).order_by("name") join_bda = form.cleaned_data["join_bda"]
join_clubs = [(club, form.cleaned_data[f"join_{club.id}"]) for club in clubs_registration]
# if soge: # if soge:
# # If Société Générale pays the inscription, the user automatically joins the two clubs. # # If Société Générale pays the inscription, the user automatically joins the two clubs.
@ -309,12 +303,11 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
# Add extra fee for the full membership # Add extra fee for the full membership
fee += kfet_fee if join_kfet else 0 fee += kfet_fee if join_kfet else 0
clubs_fee = dict() if bda_exists:
for club, join_club in join_clubs: bda = Club.objects.get(name__iexact="BDA")
club_fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid bda_fee = bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
# Add extra fee for the club membership # Add extra fee for the bda membership
clubs_fee[club] = club_fee fee += bda_fee if join_bda else 0
fee += club_fee if join_club else 0
# # If the bank pays, then we don't credit now. Treasurers will validate the transaction # # If the bank pays, then we don't credit now. Treasurers will validate the transaction
# # and credit the note later. # # and credit the note later.
@ -394,18 +387,17 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
membership.roles.add(Role.objects.get(name="Adhérent Kfet")) membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
membership.save() membership.save()
for club, join_club in join_clubs: if bda_exists and join_bda:
if join_club: # Create membership for the user to the BDA starting today
# Create membership for the user to the BDA starting today membership = Membership(
membership = Membership( club=bda,
club=club, user=user,
user=user, fee=bda_fee,
fee=clubs_fee[club], )
) membership.save()
membership.save() membership.refresh_from_db()
membership.refresh_from_db() membership.roles.add(Role.objects.get(name="Membre de club"))
membership.roles.add(Role.objects.get(name="Membre de club")) membership.save()
membership.save()
# if soge: # if soge:
# soge_credit = SogeCredit.objects.get(user=user) # soge_credit = SogeCredit.objects.get(user=user)

View File

@ -5,13 +5,13 @@ from django.contrib import admin
from note_kfet.admin import admin_site from note_kfet.admin import admin_site
from .forms import ProductForm from .forms import ProductForm
from .models import Invoice, NoteSummary, Product, RemittanceType, Remittance, SogeCredit from .models import RemittanceType, Remittance, SogeCredit, Invoice, Product
@admin.register(RemittanceType, site=admin_site) @admin.register(RemittanceType, site=admin_site)
class RemittanceTypeAdmin(admin.ModelAdmin): class RemittanceTypeAdmin(admin.ModelAdmin):
""" """
Admin customisation for RemittanceType Admin customisation for RemiitanceType
""" """
list_display = ('note', ) list_display = ('note', )
@ -55,19 +55,3 @@ class InvoiceAdmin(admin.ModelAdmin):
""" """
list_display = ('object', 'id', 'bde', 'name', 'date', 'acquitted',) list_display = ('object', 'id', 'bde', 'name', 'date', 'acquitted',)
inlines = (ProductInline,) inlines = (ProductInline,)
@admin.register(NoteSummary, site=admin_site)
class NoteSummaryAdmin(admin.ModelAdmin):
"""
Admin customisation for NoteSummary
"""
list_display = (
'date', 'total_positive_user', 'balance_positive_user', 'total_positive_user_bde',
'balance_positive_user_bde', 'total_zero_user', 'total_zero_user_bde', 'total_negative_user',
'balance_negative_user', 'total_negative_user_bde', 'balance_negative_user_bde',
'total_vnegative_user', 'balance_vnegative_user', 'total_vnegative_user_bde',
'balance_vnegative_user_bde', 'total_positive_club', 'balance_positive_club',
'total_positive_club_nbde', 'balance_positive_club_nbde', 'total_zero_club', 'total_zero_club_nbde',
'total_negative_club', 'balance_negative_club', 'total_negative_club_nbde', 'balance_negative_club_nbde',
)

View File

@ -5,7 +5,7 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet
from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer, \ from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\
SogeCreditSerializer SogeCreditSerializer
from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit

View File

@ -1,23 +0,0 @@
# Generated by Django 2.2.28 on 2024-03-21 23:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0007_auto_20240311_1549'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='payment_date',
field=models.CharField(default='', max_length=255, verbose_name='Payment date'),
),
migrations.AddField(
model_name='invoice',
name='quotation',
field=models.BooleanField(default=False, verbose_name='Quotation'),
),
]

View File

@ -1,49 +0,0 @@
# Generated by Django 2.2.28 on 2024-08-07 12:09
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0008_auto_20240322_0045'),
]
operations = [
migrations.CreateModel(
name='NoteSummary',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='Date')),
('total_positive_user', models.PositiveIntegerField(verbose_name='Total positive user')),
('balance_positive_user', models.PositiveIntegerField(verbose_name='Balance positive user')),
('total_positive_user_bde', models.PositiveIntegerField(verbose_name='Total positive user BDE')),
('balance_positive_user_bde', models.PositiveIntegerField(verbose_name='Balance positive user BDE')),
('total_zero_user', models.PositiveIntegerField(verbose_name='Total zero user')),
('total_zero_user_bde', models.PositiveIntegerField(verbose_name='Total zero user BDE')),
('total_negative_user', models.PositiveIntegerField(verbose_name='Total negative user')),
('balance_negative_user', models.PositiveIntegerField(verbose_name='Balance negative user')),
('total_negative_user_bde', models.PositiveIntegerField(verbose_name='Total negative user BDE')),
('balance_negative_user_bde', models.PositiveIntegerField(verbose_name='Balance negative user BDE')),
('total_vnegative_user', models.PositiveIntegerField(verbose_name='Total very negative user')),
('balance_vnegative_user', models.PositiveIntegerField(verbose_name='Balance very negative user')),
('total_vnegative_user_bde', models.PositiveIntegerField(verbose_name='Total very negative user BDE')),
('balance_vnegative_user_bde', models.PositiveIntegerField(verbose_name='Balance very negative user BDE')),
('total_positive_club', models.PositiveIntegerField(verbose_name='Total positive club')),
('balance_positive_club', models.PositiveIntegerField(verbose_name='Balance positive club')),
('total_positive_club_nbde', models.PositiveIntegerField(verbose_name='Total positive club nbde')),
('balance_positive_club_nbde', models.PositiveIntegerField(verbose_name='Balance positive club nbde')),
('total_zero_club', models.PositiveIntegerField(verbose_name='Total zero club')),
('total_zero_club_nbde', models.PositiveIntegerField(verbose_name='Total zero club nbde')),
('total_negative_club', models.PositiveIntegerField(verbose_name='Total negative club')),
('balance_negative_club', models.PositiveIntegerField(verbose_name='Balance negative club')),
('total_negative_club_nbde', models.PositiveIntegerField(verbose_name='Total negative club nbde')),
('balance_negative_club_nbde', models.PositiveIntegerField(verbose_name='Balance negative club nbde')),
],
options={
'verbose_name': 'Summary',
'verbose_name_plural': 'Summaries',
},
),
]

View File

@ -41,10 +41,6 @@ class Invoice(models.Model):
), ),
verbose_name=_("BDE"), verbose_name=_("BDE"),
) )
quotation = models.BooleanField(
default=False,
verbose_name=_("Quotation"),
)
object = models.CharField( object = models.CharField(
max_length=255, max_length=255,
@ -69,12 +65,6 @@ class Invoice(models.Model):
verbose_name=_("Date"), verbose_name=_("Date"),
) )
payment_date = models.CharField(
default="",
max_length=255,
verbose_name=_("Payment date"),
)
acquitted = models.BooleanField( acquitted = models.BooleanField(
verbose_name=_("Acquitted"), verbose_name=_("Acquitted"),
default=False, default=False,
@ -460,117 +450,3 @@ class SogeCredit(models.Model):
self.credit_transaction._force_save = True self.credit_transaction._force_save = True
self.credit_transaction.save() self.credit_transaction.save()
super().delete(**kwargs) super().delete(**kwargs)
class NoteSummary(models.Model):
"""
Summary of every notes
"""
date = models.DateField(
default=date.today,
verbose_name=_("Date"),
)
total_positive_user = models.PositiveIntegerField(
verbose_name=_("Total positive user"),
)
balance_positive_user = models.PositiveIntegerField(
verbose_name=_("Balance positive user"),
)
total_positive_user_bde = models.PositiveIntegerField(
verbose_name=_("Total positive user BDE"),
)
balance_positive_user_bde = models.PositiveIntegerField(
verbose_name=_("Balance positive user BDE"),
)
total_zero_user = models.PositiveIntegerField(
verbose_name=_("Total zero user"),
)
total_zero_user_bde = models.PositiveIntegerField(
verbose_name=_("Total zero user BDE"),
)
total_negative_user = models.PositiveIntegerField(
verbose_name=_("Total negative user"),
)
balance_negative_user = models.PositiveIntegerField(
verbose_name=_("Balance negative user"),
)
total_negative_user_bde = models.PositiveIntegerField(
verbose_name=_("Total negative user BDE"),
)
balance_negative_user_bde = models.PositiveIntegerField(
verbose_name=_("Balance negative user BDE"),
)
total_vnegative_user = models.PositiveIntegerField(
verbose_name=_("Total very negative user"),
)
balance_vnegative_user = models.PositiveIntegerField(
verbose_name=_("Balance very negative user"),
)
total_vnegative_user_bde = models.PositiveIntegerField(
verbose_name=_("Total very negative user BDE"),
)
balance_vnegative_user_bde = models.PositiveIntegerField(
verbose_name=_("Balance very negative user BDE"),
)
total_positive_club = models.PositiveIntegerField(
verbose_name=_("Total positive club"),
)
balance_positive_club = models.PositiveIntegerField(
verbose_name=_("Balance positive club"),
)
total_positive_club_nbde = models.PositiveIntegerField(
verbose_name=_("Total positive club nbde"),
)
balance_positive_club_nbde = models.PositiveIntegerField(
verbose_name=_("Balance positive club nbde"),
)
total_zero_club = models.PositiveIntegerField(
verbose_name=_("Total zero club"),
)
total_zero_club_nbde = models.PositiveIntegerField(
verbose_name=_("Total zero club nbde"),
)
total_negative_club = models.PositiveIntegerField(
verbose_name=_("Total negative club"),
)
balance_negative_club = models.PositiveIntegerField(
verbose_name=_("Balance negative club"),
)
total_negative_club_nbde = models.PositiveIntegerField(
verbose_name=_("Total negative club nbde"),
)
balance_negative_club_nbde = models.PositiveIntegerField(
verbose_name=_("Balance negative club nbde"),
)
class Meta:
verbose_name = _("Summary")
verbose_name_plural = _("Summaries")
def __str__(self):
return "Note summary of {date}".format(date=self.date)

View File

@ -76,11 +76,8 @@
\def\FactureNum {{"{"}}{{ obj.id }}} % Numéro de facture \def\FactureNum {{"{"}}{{ obj.id }}} % Numéro de facture
\def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non \def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non
\def\Devis {% if obj.quotation %} {oui} {% else %} {non} {% endif %}
% Devis : oui/non
\def\FactureLieu {{"{"}}{{ obj.place|escape_tex }}} % Lieu de l'édition de la facture \def\FactureLieu {{"{"}}{{ obj.place|escape_tex }}} % Lieu de l'édition de la facture
\def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture \def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture
\def\FacturePaymentDate {{"{"}}{{ obj.payment_date|escape_tex }}} % Date de paiement de la facture
\def\FactureObjet {{"{"}}{{ obj.object|escape_tex }} } % Objet du document \def\FactureObjet {{"{"}}{{ obj.object|escape_tex }} } % Objet du document
% Description de la facture % Description de la facture
\def\FactureDescr {{"{"}}{{ obj.description|escape_tex }}} \def\FactureDescr {{"{"}}{{ obj.description|escape_tex }}}
@ -121,12 +118,10 @@
% Nom et adresse de la société % Nom et adresse de la société
\MonNom \\ \MonNom \\
\MonAdresseRue \\ \MonAdresseRue \\
\MonAdresseVille \\ \MonAdresseVille
\ifthenelse{\equal{\Devis}{oui}}{
Devis n°\FactureNum
}{
Facture n°\FactureNum Facture n°\FactureNum
}
{\addtolength{\leftskip}{10.5cm} %in ERT {\addtolength{\leftskip}{10.5cm} %in ERT
\ClientNom \\ \ClientNom \\
@ -144,7 +139,6 @@ Facture n°\FactureNum
\textnormal{\FactureDescr} \textnormal{\FactureDescr}
~\\ ~\\
\begin{center} \begin{center}
@ -160,11 +154,6 @@ Facture n°\FactureNum
\ifthenelse{\equal{\FactureAcquittee}{oui}}{ \ifthenelse{\equal{\FactureAcquittee}{oui}}{
Facture acquittée. Facture acquittée.
}{ }{
Echéance de paiement : \FacturePaymentDate
Conditions d'escompte : Aucune
Taux de pénalité en cas de non paiement ou retard de paiement : 0 \%
À régler par chèque ou par virement bancaire : À régler par chèque ou par virement bancaire :
@ -187,6 +176,5 @@ Facture n°\FactureNum
TVA non applicable, article 293 B du CGI. TVA non applicable, article 293 B du CGI.
\end{center} \end{center}
\end{document} \end{document}
{% endlanguage %} {% endlanguage %}

View File

@ -69,11 +69,9 @@ class TestInvoices(TestCase):
response = self.client.post(reverse("treasury:invoice_create"), data={ response = self.client.post(reverse("treasury:invoice_create"), data={
"id": 42, "id": 42,
"object": "Same object", "object": "Same object",
"quotation": True,
"description": "Longer description", "description": "Longer description",
"name": "Me and others", "name": "Me and others",
"address": "Alwways earth", "address": "Alwways earth",
"payment_date": "Maybe someday...",
"acquitted": True, "acquitted": True,
"products-0-designation": "Designation", "products-0-designation": "Designation",
"products-0-quantity": 1, "products-0-quantity": 1,
@ -99,10 +97,8 @@ class TestInvoices(TestCase):
"object": "Same object", "object": "Same object",
"description": "Longer description", "description": "Longer description",
"name": "Me and others", "name": "Me and others",
"quotation": False,
"address": "Always earth", "address": "Always earth",
"acquitted": True, "acquitted": True,
"payment_date": "Never",
"locked": True, "locked": True,
"products-0-designation": "Designation", "products-0-designation": "Designation",
"products-0-quantity": 1, "products-0-quantity": 1,

View File

@ -3,8 +3,8 @@
from django.urls import path from django.urls import path
from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView, \ from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView,\
RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, \ RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView,\
UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView
app_name = 'treasury' app_name = 'treasury'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,6 @@ MAILTO=notekfet2020@lists.crans.org
00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0 00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0
# Envoyer le rapport mensuel aux trésoriers et respos info # Envoyer le rapport mensuel aux trésoriers et respos info
00 8 * * 5 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0 00 8 * * 5 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0
# Envoyer le recap de tresorerie
00 8 * * 5 root cd /var/www/note_kfet && env/bin/python manage.py send_summary_notes_report --negative-amount 2000
# Envoyer les rapports aux gens # Envoyer les rapports aux gens
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0 55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0
# Mettre à jour les boutons mis en avant # Mettre à jour les boutons mis en avant

View File

@ -4,7 +4,7 @@
"pk": 1, "pk": 1,
"fields": { "fields": {
"domain": "note.crans.org", "domain": "note.crans.org",
"name": "La Note Kfet 🍪" "name": "La Note Kfet \ud83c\udf7b"
} }
} }
] ]

View File

@ -194,8 +194,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
class="text-muted">{% trans "Contact us" %}</a> &mdash; class="text-muted">{% trans "Contact us" %}</a> &mdash;
<a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}" <a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}"
class="text-muted">{% trans "Technical Support" %}</a> &mdash; class="text-muted">{% trans "Technical Support" %}</a> &mdash;
<a href="https://perso.crans.org/club-bde/charte_informatique.pdf"
class="text-muted">{% trans "Charte Info (FR)" %}</a> &mdash;
<a href="https://note.crans.org/doc/faq/" <a href="https://note.crans.org/doc/faq/"
class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash; class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash;
</span> </span>

34
shell-static.nix Executable file
View File

@ -0,0 +1,34 @@
# This is a workaround meant for use with the nix package manager. If you don't know what it is or don't use it, please ignore this file.
#
# The nk20 javascript static location are hardcoded for imperative system.
# This make ./manage.py collectstatic hard to use with nixos.
#
# A workaround is to enter a FHSUserEnv with the static placed under /share/javascript/<static>.
# This emulate a debian like system and enable collecting static normally with ./manage.py collectstatics.
# The regular shell.nix should be enough for other configurations.
#
# Warning, you are still supposed to use pip package with a venv !
{ pkgs ? import <nixpkgs> {} }:
(pkgs.buildFHSUserEnv {
name = "pipzone";
targetPkgs = pkgs: (with pkgs;
let
fhs-static = stdenv.mkDerivation {
name = "fhs-static";
buildCommand = ''
mkdir -p $out/share/javascript/bootstrap4
mkdir -p $out/share/javascript/jquery
ln -s ${python39Packages.xstatic-bootstrap}/lib/python3.9/site-packages/xstatic/pkg/bootstrap/data/* $out/share/javascript/bootstrap4
ln -s ${python39Packages.xstatic-jquery}/lib/python3.9/site-packages/xstatic/pkg/jquery/data/* $out/share/javascript/jquery
'';
};
in [
fhs-static
python39
gettext
python39Packages.pip
python39Packages.virtualenv
python39Packages.setuptools
]);
runScript = "bash";
}).env

23
shell.nix Executable file
View File

@ -0,0 +1,23 @@
# This is meant for use with the nix package manager. If you don't know what it is or don't use it, please ignore this file.
#
# This shell.nix contains all dependencies require to create a venv and pip install -r requirements.txt.
#
# Please check shell-static.nix for running ./manage.py collectstatics.
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
python39
python39Packages.pip
python39Packages.setuptools
gettext
];
shellHook = ''
# Tells pip to put packages into $PIP_PREFIX instead of the usual locations.
# See https://pip.pypa.io/en/stable/user_guide/#environment-variables.
export PIP_PREFIX=$(pwd)/_build/pip_packages
export PYTHONPATH="$PIP_PREFIX/${pkgs.python39.sitePackages}:$PYTHONPATH"
export PATH="$PIP_PREFIX/bin:$PATH"
unset SOURCE_DATE_EPOCH
'';
}