mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'beta' into 'master'
Last deployment fixes See merge request bde/nk20!109
This commit is contained in:
commit
863150d200
|
@ -47,6 +47,3 @@ backups/
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
|
||||||
# Ignore migrations during first phase dev
|
|
||||||
migrations/
|
|
||||||
|
|
|
@ -2,6 +2,10 @@ stages:
|
||||||
- test
|
- test
|
||||||
- quality-assurance
|
- quality-assurance
|
||||||
|
|
||||||
|
# Also fetch submodules
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
|
|
||||||
# Debian Buster
|
# Debian Buster
|
||||||
py37-django22:
|
py37-django22:
|
||||||
stage: test
|
stage: test
|
||||||
|
@ -15,7 +19,7 @@ py37-django22:
|
||||||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
||||||
python3-bs4 python3-setuptools tox
|
python3-bs4 python3-setuptools tox
|
||||||
texlive-latex-base texlive-lang-french lmodern texlive-fonts-recommended
|
texlive-latex-base texlive-latex-recommended texlive-lang-french lmodern texlive-fonts-recommended
|
||||||
script: tox -e py37-django22
|
script: tox -e py37-django22
|
||||||
|
|
||||||
# Ubuntu 20.04
|
# Ubuntu 20.04
|
||||||
|
@ -33,7 +37,7 @@ py38-django22:
|
||||||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
||||||
python3-bs4 python3-setuptools tox
|
python3-bs4 python3-setuptools tox
|
||||||
texlive-latex-base texlive-lang-french lmodern texlive-fonts-recommended
|
texlive-latex-base texlive-latex-recommended texlive-lang-french lmodern texlive-fonts-recommended
|
||||||
script: tox -e py38-django22
|
script: tox -e py38-django22
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
|
|
|
@ -12,7 +12,7 @@ RUN apt-get update && \
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
||||||
python3-bs4 python3-setuptools \
|
python3-bs4 python3-setuptools \
|
||||||
uwsgi uwsgi-plugin-python3 \
|
uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-latex-base texlive-lang-french lmodern texlive-fonts-recommended \
|
texlive-latex-base texlive-latex-recommended texlive-lang-french lmodern texlive-fonts-recommended \
|
||||||
gettext libjs-bootstrap4 fonts-font-awesome && \
|
gettext libjs-bootstrap4 fonts-font-awesome && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,6 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
||||||
DJANGO_SECRET_KEY=CHANGE_ME
|
DJANGO_SECRET_KEY=CHANGE_ME
|
||||||
DJANGO_SETTINGS_MODULE="note_kfet.settings
|
DJANGO_SETTINGS_MODULE="note_kfet.settings
|
||||||
NOTE_URL=localhost # URL où accéder à la note
|
NOTE_URL=localhost # URL où accéder à la note
|
||||||
DOMAIN=localhost # note.example.com
|
|
||||||
CONTACT_EMAIL=tresorerie.bde@localhost
|
CONTACT_EMAIL=tresorerie.bde@localhost
|
||||||
# Le reste n'est utile qu'en production, pour configurer l'envoi des mails
|
# Le reste n'est utile qu'en production, pour configurer l'envoi des mails
|
||||||
NOTE_MAIL=notekfet@localhost
|
NOTE_MAIL=notekfet@localhost
|
||||||
|
@ -211,7 +210,6 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
||||||
|
|
||||||
$ source /env/bin/activate
|
$ source /env/bin/activate
|
||||||
(env)$ ./manage.py check # pas de bêtise qui traine
|
(env)$ ./manage.py check # pas de bêtise qui traine
|
||||||
(env)$ ./manage.py makemigrations
|
|
||||||
(env)$ ./manage.py migrate
|
(env)$ ./manage.py migrate
|
||||||
|
|
||||||
7. *Enjoy \o/*
|
7. *Enjoy \o/*
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
- texlive-fonts-recommended
|
- texlive-fonts-recommended
|
||||||
- texlive-lang-french
|
- texlive-lang-french
|
||||||
- texlive-latex-base
|
- texlive-latex-base
|
||||||
|
- texlive-latex-recommended
|
||||||
|
|
||||||
# WSGI server
|
# WSGI server
|
||||||
- uwsgi
|
- uwsgi
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
---
|
---
|
||||||
- name: Make Django migrations
|
|
||||||
command: /var/www/note_kfet/env/bin/python manage.py makemigrations
|
|
||||||
args:
|
|
||||||
chdir: /var/www/note_kfet
|
|
||||||
become_user: www-data
|
|
||||||
|
|
||||||
- name: Migrate Django database
|
- name: Migrate Django database
|
||||||
command: /var/www/note_kfet/env/bin/python manage.py migrate
|
command: /var/www/note_kfet/env/bin/python manage.py migrate
|
||||||
args:
|
args:
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Activity',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||||
|
('description', models.TextField(verbose_name='description')),
|
||||||
|
('location', models.CharField(blank=True, default='', help_text='Place where the activity is organized, eg. Kfet.', max_length=255, verbose_name='location')),
|
||||||
|
('date_start', models.DateTimeField(verbose_name='start date')),
|
||||||
|
('date_end', models.DateTimeField(verbose_name='end date')),
|
||||||
|
('valid', models.BooleanField(default=False, verbose_name='valid')),
|
||||||
|
('open', models.BooleanField(default=False, verbose_name='open')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'activity',
|
||||||
|
'verbose_name_plural': 'activities',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ActivityType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||||
|
('manage_entries', models.BooleanField(default=False, help_text='Enable the support of entries for this activity.', verbose_name='manage entries')),
|
||||||
|
('can_invite', models.BooleanField(default=False, verbose_name='can invite')),
|
||||||
|
('guest_entry_fee', models.PositiveIntegerField(default=0, verbose_name='guest entry fee')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'activity type',
|
||||||
|
'verbose_name_plural': 'activity types',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Entry',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='entry time')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'entry',
|
||||||
|
'verbose_name_plural': 'entries',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Guest',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('last_name', models.CharField(max_length=255, verbose_name='last name')),
|
||||||
|
('first_name', models.CharField(max_length=255, verbose_name='first name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'guest',
|
||||||
|
'verbose_name_plural': 'guests',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,89 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('activity', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('member', '0001_initial'),
|
||||||
|
('note', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GuestTransaction',
|
||||||
|
fields=[
|
||||||
|
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
|
||||||
|
('entry', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='activity.Entry')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('note.transaction',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guest',
|
||||||
|
name='activity',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.Activity'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guest',
|
||||||
|
name='inviter',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='guests', to='note.NoteUser', verbose_name='inviter'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='entry',
|
||||||
|
name='activity',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='activity.Activity', verbose_name='activity'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='entry',
|
||||||
|
name='guest',
|
||||||
|
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='activity.Guest'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='entry',
|
||||||
|
name='note',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.NoteUser', verbose_name='note'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='activity',
|
||||||
|
name='activity_type',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.ActivityType', verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='activity',
|
||||||
|
name='attendees_club',
|
||||||
|
field=models.ForeignKey(help_text='Club that is authorized to join the activity. Mostly the Kfet club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='attendees club'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='activity',
|
||||||
|
name='creater',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='activity',
|
||||||
|
name='organizer',
|
||||||
|
field=models.ForeignKey(help_text='Club that organizes the activity. The entry fees will go to this club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='organizer'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='guest',
|
||||||
|
unique_together={('activity', 'last_name', 'first_name')},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='entry',
|
||||||
|
unique_together={('activity', 'note', 'guest')},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='activity',
|
||||||
|
unique_together={('name', 'date_start', 'date_end')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import os
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
@ -133,9 +134,13 @@ class Activity(models.Model):
|
||||||
if not settings.DEBUG and self.pk and "scripts" in settings.INSTALLED_APPS:
|
if not settings.DEBUG and self.pk and "scripts" in settings.INSTALLED_APPS:
|
||||||
def refresh_activities():
|
def refresh_activities():
|
||||||
from scripts.management.commands.refresh_activities import Command as RefreshActivitiesCommand
|
from scripts.management.commands.refresh_activities import Command as RefreshActivitiesCommand
|
||||||
RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name)
|
# Consider that we can update the wiki iff the WIKI_PASSWORD env var is not empty
|
||||||
RefreshActivitiesCommand.refresh_raw_wiki_page("Modification de l'activité " + self.name)
|
RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name,
|
||||||
Thread(daemon=True, target=refresh_activities).start()
|
False, os.getenv("WIKI_PASSWORD"))
|
||||||
|
RefreshActivitiesCommand.refresh_raw_wiki_page("Modification de l'activité " + self.name,
|
||||||
|
False, os.getenv("WIKI_PASSWORD"))
|
||||||
|
Thread(daemon=True, target=refresh_activities).start()\
|
||||||
|
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Changelog',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='IP Address')),
|
||||||
|
('instance_pk', models.CharField(max_length=255, verbose_name='identifier')),
|
||||||
|
('previous', models.TextField(null=True, verbose_name='previous data')),
|
||||||
|
('data', models.TextField(null=True, verbose_name='new data')),
|
||||||
|
('action', models.CharField(choices=[('create', 'create'), ('edit', 'edit'), ('delete', 'delete')], default='edit', max_length=16, verbose_name='action')),
|
||||||
|
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='timestamp')),
|
||||||
|
('model', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType', verbose_name='model')),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'changelog',
|
||||||
|
'verbose_name_plural': 'changelogs',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,31 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "member.club",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"name": "BDE",
|
|
||||||
"email": "tresorerie.bde@example.com",
|
|
||||||
"require_memberships": true,
|
|
||||||
"membership_fee_paid": 500,
|
|
||||||
"membership_fee_unpaid": 500,
|
|
||||||
"membership_duration": 396,
|
|
||||||
"membership_start": "2019-08-31",
|
|
||||||
"membership_end": "2020-09-30"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "member.club",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"name": "Kfet",
|
|
||||||
"email": "tresorerie.bde@example.com",
|
|
||||||
"parent_club": 1,
|
|
||||||
"require_memberships": true,
|
|
||||||
"membership_fee_paid": 3500,
|
|
||||||
"membership_fee_unpaid": 3500,
|
|
||||||
"membership_duration": 396,
|
|
||||||
"membership_start": "2019-08-31",
|
|
||||||
"membership_end": "2020-09-30"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Club',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
|
||||||
|
('email', models.EmailField(max_length=254, verbose_name='email')),
|
||||||
|
('require_memberships', models.BooleanField(default=True, help_text="Uncheck if this club don't require memberships.", verbose_name='require memberships')),
|
||||||
|
('membership_fee_paid', models.PositiveIntegerField(default=0, verbose_name='membership fee (paid students)')),
|
||||||
|
('membership_fee_unpaid', models.PositiveIntegerField(default=0, verbose_name='membership fee (unpaid students)')),
|
||||||
|
('membership_duration', models.PositiveIntegerField(blank=True, help_text='The longest time (in days) a membership can last (NULL = infinite).', null=True, verbose_name='membership duration')),
|
||||||
|
('membership_start', models.DateField(blank=True, help_text='Date from which the members can renew their membership.', null=True, verbose_name='membership start')),
|
||||||
|
('membership_end', models.DateField(blank=True, help_text='Maximal date of a membership, after which members must renew it.', null=True, verbose_name='membership end')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'club',
|
||||||
|
'verbose_name_plural': 'clubs',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Profile',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=50, null=True, region=None, verbose_name='phone number')),
|
||||||
|
('section', models.CharField(blank=True, help_text='e.g. "1A0", "9A♥", "SAPHIRE"', max_length=255, null=True, verbose_name='section')),
|
||||||
|
('department', models.CharField(choices=[('A0', 'Informatics (A0)'), ('A1', 'Mathematics (A1)'), ('A2', 'Physics (A2)'), ("A'2", "Applied physics (A'2)"), ('A2', "Chemistry (A''2)"), ('A3', 'Biology (A3)'), ('B1234', 'SAPHIRE (B1234)'), ('B1', 'Mechanics (B1)'), ('B2', 'Civil engineering (B2)'), ('B3', 'Mechanical engineering (B3)'), ('B4', 'EEA (B4)'), ('C', 'Design (C)'), ('D2', 'Economy-management (D2)'), ('D3', 'Social sciences (D3)'), ('E', 'English (E)'), ('EXT', 'External (EXT)')], max_length=8, verbose_name='department')),
|
||||||
|
('promotion', models.PositiveSmallIntegerField(default=2020, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion')),
|
||||||
|
('address', models.CharField(blank=True, max_length=255, null=True, verbose_name='address')),
|
||||||
|
('paid', models.BooleanField(default=False, help_text='Tells if the user receive a salary.', verbose_name='paid')),
|
||||||
|
('ml_events_registration', models.CharField(blank=True, choices=[(None, 'No'), ('fr', 'Yes (receive them in french)'), ('en', 'Yes (receive them in english)')], default=None, max_length=2, null=True, verbose_name='Register on the mailing list to stay informed of the events of the campus (1 mail/week)')),
|
||||||
|
('ml_sport_registration', models.BooleanField(default=False, verbose_name='Register on the mailing list to stay informed of the sport events of the campus (1 mail/week)')),
|
||||||
|
('ml_art_registration', models.BooleanField(default=False, verbose_name='Register on the mailing list to stay informed of the art events of the campus (1 mail/week)')),
|
||||||
|
('report_frequency', models.PositiveSmallIntegerField(default=0, verbose_name='report frequency (in days)')),
|
||||||
|
('last_report', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last report date')),
|
||||||
|
('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
|
||||||
|
('registration_valid', models.BooleanField(default=False, verbose_name='registration valid')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user profile',
|
||||||
|
'verbose_name_plural': 'user profile',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Membership',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date_start', models.DateField(default=datetime.date.today, verbose_name='membership starts on')),
|
||||||
|
('date_end', models.DateField(null=True, verbose_name='membership ends on')),
|
||||||
|
('fee', models.PositiveIntegerField(verbose_name='fee')),
|
||||||
|
('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='club')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'membership',
|
||||||
|
'verbose_name_plural': 'memberships',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('permission', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('member', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='membership',
|
||||||
|
name='roles',
|
||||||
|
field=models.ManyToManyField(to='permission.Role', verbose_name='roles'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='membership',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to=settings.AUTH_USER_MODEL, verbose_name='user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='club',
|
||||||
|
name='parent_club',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='parent club'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='profile',
|
||||||
|
index=models.Index(fields=['user'], name='member_prof_user_id_30c316_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='membership',
|
||||||
|
index=models.Index(fields=['user'], name='member_memb_user_id_945dbc_idx'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,57 @@
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def create_bde_and_kfet(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
The clubs BDE and Kfet are pre-injected.
|
||||||
|
"""
|
||||||
|
Club = apps.get_model("member", "club")
|
||||||
|
NoteClub = apps.get_model("note", "noteclub")
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
|
||||||
|
|
||||||
|
Club.objects.get_or_create(
|
||||||
|
id=1,
|
||||||
|
name="BDE",
|
||||||
|
email="tresorerie.bde@example.com",
|
||||||
|
require_memberships=True,
|
||||||
|
membership_fee_paid=500,
|
||||||
|
membership_fee_unpaid=500,
|
||||||
|
membership_duration=396,
|
||||||
|
membership_start="2020-08-01",
|
||||||
|
membership_end="2021-09-30",
|
||||||
|
)
|
||||||
|
Club.objects.get_or_create(
|
||||||
|
id=2,
|
||||||
|
name="Kfet",
|
||||||
|
parent_club_id=1,
|
||||||
|
email="tresorerie.bde@example.com",
|
||||||
|
require_memberships=True,
|
||||||
|
membership_fee_paid=500,
|
||||||
|
membership_fee_unpaid=500,
|
||||||
|
membership_duration=396,
|
||||||
|
membership_start="2020-08-01",
|
||||||
|
membership_end="2021-09-30",
|
||||||
|
)
|
||||||
|
|
||||||
|
NoteClub.objects.get_or_create(
|
||||||
|
id=5,
|
||||||
|
club_id=1,
|
||||||
|
polymorphic_ctype_id=polymorphic_ctype_id,
|
||||||
|
)
|
||||||
|
NoteClub.objects.get_or_create(
|
||||||
|
id=6,
|
||||||
|
club_id=2,
|
||||||
|
polymorphic_ctype_id=polymorphic_ctype_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('member', '0002_auto_20200904_2341'),
|
||||||
|
('note', '0002_create_special_notes'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(create_bde_and_kfet),
|
||||||
|
]
|
|
@ -2,22 +2,22 @@
|
||||||
* On form submit, create a new alias
|
* On form submit, create a new alias
|
||||||
*/
|
*/
|
||||||
function create_alias (e) {
|
function create_alias (e) {
|
||||||
// Do not submit HTML form
|
// Do not submit HTML form
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
// Get data and send to API
|
// Get data and send to API
|
||||||
const formData = new FormData(e.target);
|
const formData = new FormData(e.target)
|
||||||
$.post("/api/note/alias/", {
|
$.post('/api/note/alias/', {
|
||||||
"csrfmiddlewaretoken": formData.get("csrfmiddlewaretoken"),
|
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
|
||||||
"name": formData.get("name"),
|
name: formData.get('name'),
|
||||||
"note": formData.get("note")
|
note: formData.get('note')
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
// Reload table
|
// Reload table
|
||||||
$("#alias_table").load(location.pathname + " #alias_table");
|
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||||
addMsg("Alias ajouté", "success");
|
addMsg('Alias ajouté', 'success')
|
||||||
}).fail(function (xhr, _textStatus, _error) {
|
}).fail(function (xhr, _textStatus, _error) {
|
||||||
errMsg(xhr.responseJSON);
|
errMsg(xhr.responseJSON)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,19 +25,19 @@ function create_alias (e) {
|
||||||
* @param Integer button_id Alias id to remove
|
* @param Integer button_id Alias id to remove
|
||||||
*/
|
*/
|
||||||
function delete_button (button_id) {
|
function delete_button (button_id) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/note/alias/" + button_id + "/",
|
url: '/api/note/alias/' + button_id + '/',
|
||||||
method: "DELETE",
|
method: 'DELETE',
|
||||||
headers: { "X-CSRFTOKEN": CSRF_TOKEN }
|
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg('Alias supprimé', 'success');
|
addMsg('Alias supprimé', 'success')
|
||||||
$("#alias_table").load(location.pathname + " #alias_table");
|
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||||
}).fail(function (xhr, _textStatus, _error) {
|
}).fail(function (xhr, _textStatus, _error) {
|
||||||
errMsg(xhr.responseJSON);
|
errMsg(xhr.responseJSON)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
// Attach event
|
// Attach event
|
||||||
document.getElementById("form_alias").addEventListener("submit", create_alias);
|
document.getElementById('form_alias').addEventListener('submit', create_alias)
|
||||||
})
|
})
|
|
@ -197,7 +197,7 @@ class TestMemberships(TestCase):
|
||||||
# Create a new membership
|
# Create a new membership
|
||||||
response = self.client.post(reverse("member:club_add_member", args=(club.pk,)), data=dict(
|
response = self.client.post(reverse("member:club_add_member", args=(club.pk,)), data=dict(
|
||||||
user=user.pk,
|
user=user.pk,
|
||||||
date_start="{:%Y-%m-%d}".format(timezone.now().date()),
|
date_start="{:%Y-%m-%d}".format(date.today()),
|
||||||
soge=False,
|
soge=False,
|
||||||
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
||||||
credit_amount=4200,
|
credit_amount=4200,
|
||||||
|
@ -236,7 +236,7 @@ class TestMemberships(TestCase):
|
||||||
# Renew membership
|
# Renew membership
|
||||||
response = self.client.post(reverse("member:club_renew_membership", args=(membership.pk,)), data=dict(
|
response = self.client.post(reverse("member:club_renew_membership", args=(membership.pk,)), data=dict(
|
||||||
user=user.pk,
|
user=user.pk,
|
||||||
date_start="{:%Y-%m-%d}".format(timezone.now().date()),
|
date_start="{:%Y-%m-%d}".format(date.today()),
|
||||||
soge=bde_parent,
|
soge=bde_parent,
|
||||||
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
||||||
credit_amount=14242,
|
credit_amount=14242,
|
||||||
|
@ -265,7 +265,7 @@ class TestMemberships(TestCase):
|
||||||
|
|
||||||
response = self.client.post(reverse("member:club_add_member", args=(bde.pk,)), data=dict(
|
response = self.client.post(reverse("member:club_add_member", args=(bde.pk,)), data=dict(
|
||||||
user=user.pk,
|
user=user.pk,
|
||||||
date_start="{:%Y-%m-%d}".format(timezone.now().date()),
|
date_start="{:%Y-%m-%d}".format(date.today()),
|
||||||
soge=True,
|
soge=True,
|
||||||
credit_type=NoteSpecial.objects.get(special_type="Virement bancaire").id,
|
credit_type=NoteSpecial.objects.get(special_type="Virement bancaire").id,
|
||||||
credit_amount=(bde.membership_fee_paid + kfet.membership_fee_paid) / 100,
|
credit_amount=(bde.membership_fee_paid + kfet.membership_fee_paid) / 100,
|
||||||
|
|
|
@ -1,188 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"notespecial"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "",
|
|
||||||
"created_at": "2020-02-20T20:02:48.778Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"notespecial"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "",
|
|
||||||
"created_at": "2020-02-20T20:06:39.546Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"notespecial"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "",
|
|
||||||
"created_at": "2020-02-20T20:06:43.049Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"notespecial"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "",
|
|
||||||
"created_at": "2020-02-20T20:06:50.996Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"noteclub"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "pic/default.png",
|
|
||||||
"created_at": "2020-02-20T20:09:38.615Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.note",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"polymorphic_ctype": [
|
|
||||||
"note",
|
|
||||||
"noteclub"
|
|
||||||
],
|
|
||||||
"balance": 0,
|
|
||||||
"last_negative": null,
|
|
||||||
"is_active": true,
|
|
||||||
"display_image": "pic/default.png",
|
|
||||||
"created_at": "2020-02-20T20:16:14.753Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.noteclub",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"club": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.noteclub",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"club": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.notespecial",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"special_type": "Esp\u00e8ces"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.notespecial",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"special_type": "Carte bancaire"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.notespecial",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"special_type": "Ch\u00e8que"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.notespecial",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"special_type": "Virement bancaire"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.alias",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"name": "Esp\u00e8ces",
|
|
||||||
"normalized_name": "especes",
|
|
||||||
"note": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.alias",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"name": "Carte bancaire",
|
|
||||||
"normalized_name": "cartebancaire",
|
|
||||||
"note": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.alias",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"name": "Ch\u00e8que",
|
|
||||||
"normalized_name": "cheque",
|
|
||||||
"note": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.alias",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"name": "Virement bancaire",
|
|
||||||
"normalized_name": "virementbancaire",
|
|
||||||
"note": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.alias",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"name": "BDE",
|
|
||||||
"normalized_name": "bde",
|
|
||||||
"note": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "note.alias",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"name": "Kfet",
|
|
||||||
"normalized_name": "kfet",
|
|
||||||
"note": 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('member', '0001_initial'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Note',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('balance', models.BigIntegerField(default=0, help_text='in centimes, money credited for this instance', verbose_name='account balance')),
|
||||||
|
('last_negative', models.DateTimeField(blank=True, help_text='last time the balance was negative', null=True, verbose_name='last negative date')),
|
||||||
|
('display_image', models.ImageField(default='pic/default.png', max_length=255, upload_to='pic/', verbose_name='display image')),
|
||||||
|
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this note should be treated as active. Unselect this instead of deleting notes.', verbose_name='active')),
|
||||||
|
('inactivity_reason', models.CharField(choices=[('manual', 'The user blocked his/her note manually, eg. when he/she left the school for holidays. It can be reactivated at any time.'), ('forced', "The note is blocked by the the BDE and can't be manually reactivated.")], default=None, max_length=255, null=True)),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_note.note_set+', to='contenttypes.ContentType')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'note',
|
||||||
|
'verbose_name_plural': 'notes',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TemplateCategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=31, unique=True, verbose_name='name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'transaction category',
|
||||||
|
'verbose_name_plural': 'transaction categories',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Transaction',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('source_alias', models.CharField(default='', max_length=255, verbose_name='used alias')),
|
||||||
|
('destination_alias', models.CharField(default='', max_length=255, verbose_name='used alias')),
|
||||||
|
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
|
||||||
|
('quantity', models.PositiveIntegerField(default=1, verbose_name='quantity')),
|
||||||
|
('amount', models.PositiveIntegerField(verbose_name='amount')),
|
||||||
|
('reason', models.CharField(max_length=255, verbose_name='reason')),
|
||||||
|
('valid', models.BooleanField(default=True, verbose_name='valid')),
|
||||||
|
('invalidity_reason', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='invalidity reason')),
|
||||||
|
('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='destination')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_note.transaction_set+', to='contenttypes.ContentType')),
|
||||||
|
('source', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='source')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'transaction',
|
||||||
|
'verbose_name_plural': 'transactions',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MembershipTransaction',
|
||||||
|
fields=[
|
||||||
|
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'membership transaction',
|
||||||
|
'verbose_name_plural': 'membership transactions',
|
||||||
|
},
|
||||||
|
bases=('note.transaction',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NoteClub',
|
||||||
|
fields=[
|
||||||
|
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'club note',
|
||||||
|
'verbose_name_plural': 'clubs notes',
|
||||||
|
},
|
||||||
|
bases=('note.note',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NoteSpecial',
|
||||||
|
fields=[
|
||||||
|
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')),
|
||||||
|
('special_type', models.CharField(max_length=255, unique=True, verbose_name='type')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'special note',
|
||||||
|
'verbose_name_plural': 'special notes',
|
||||||
|
},
|
||||||
|
bases=('note.note',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NoteUser',
|
||||||
|
fields=[
|
||||||
|
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': "one's note",
|
||||||
|
'verbose_name_plural': 'users note',
|
||||||
|
},
|
||||||
|
bases=('note.note',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RecurrentTransaction',
|
||||||
|
fields=[
|
||||||
|
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'recurrent transaction',
|
||||||
|
'verbose_name_plural': 'recurrent transactions',
|
||||||
|
},
|
||||||
|
bases=('note.transaction',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SpecialTransaction',
|
||||||
|
fields=[
|
||||||
|
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
|
||||||
|
('last_name', models.CharField(max_length=255, verbose_name='name')),
|
||||||
|
('first_name', models.CharField(max_length=255, verbose_name='first_name')),
|
||||||
|
('bank', models.CharField(blank=True, max_length=255, verbose_name='bank')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Special transaction',
|
||||||
|
'verbose_name_plural': 'Special transactions',
|
||||||
|
},
|
||||||
|
bases=('note.transaction',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Alias',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
|
||||||
|
('normalized_name', models.CharField(editable=False, max_length=255, unique=True)),
|
||||||
|
('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'alias',
|
||||||
|
'verbose_name_plural': 'aliases',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TransactionTemplate',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(error_messages={'unique': 'A template with this name already exist'}, max_length=255, unique=True, verbose_name='name')),
|
||||||
|
('amount', models.PositiveIntegerField(verbose_name='amount')),
|
||||||
|
('display', models.BooleanField(default=True, verbose_name='display')),
|
||||||
|
('highlighted', models.BooleanField(default=False, verbose_name='highlighted')),
|
||||||
|
('description', models.CharField(blank=True, max_length=255, verbose_name='description')),
|
||||||
|
('category', models.ForeignKey(max_length=31, on_delete=django.db.models.deletion.PROTECT, related_name='templates', to='note.TemplateCategory', verbose_name='type')),
|
||||||
|
('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.NoteClub', verbose_name='destination')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'transaction template',
|
||||||
|
'verbose_name_plural': 'transaction templates',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='transaction',
|
||||||
|
index=models.Index(fields=['created_at'], name='note_transa_created_bea8b1_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='transaction',
|
||||||
|
index=models.Index(fields=['source'], name='note_transa_source__4a1a1e_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='transaction',
|
||||||
|
index=models.Index(fields=['destination'], name='note_transa_destina_6e1bb4_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='recurrenttransaction',
|
||||||
|
name='template',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.TransactionTemplate'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='noteuser',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to=settings.AUTH_USER_MODEL, verbose_name='user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='noteclub',
|
||||||
|
name='club',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to='member.Club', verbose_name='club'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='membershiptransaction',
|
||||||
|
name='membership',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='transaction', to='member.Membership'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='alias',
|
||||||
|
index=models.Index(fields=['name'], name='note_alias_name_a89405_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='alias',
|
||||||
|
index=models.Index(fields=['normalized_name'], name='note_alias_normali_bd52b4_idx'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def create_special_notes(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
We create the four special note to make transfers.
|
||||||
|
"""
|
||||||
|
NoteSpecial = apps.get_model("note", "notespecial")
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteSpecial).id
|
||||||
|
|
||||||
|
NoteSpecial.objects.get_or_create(id=1, special_type="Espèces", polymorphic_ctype_id=polymorphic_ctype_id)
|
||||||
|
NoteSpecial.objects.get_or_create(id=2, special_type="Carte bancaire", polymorphic_ctype_id=polymorphic_ctype_id)
|
||||||
|
NoteSpecial.objects.get_or_create(id=3, special_type="Chèque", polymorphic_ctype_id=polymorphic_ctype_id)
|
||||||
|
NoteSpecial.objects.get_or_create(id=4, special_type="Virement bancaire", polymorphic_ctype_id=polymorphic_ctype_id)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('note', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(create_special_notes),
|
||||||
|
]
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('member', '0001_initial'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Permission',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('query', models.TextField(verbose_name='query')),
|
||||||
|
('type', models.CharField(choices=[('add', 'add'), ('view', 'view'), ('change', 'change'), ('delete', 'delete')], max_length=15, verbose_name='type')),
|
||||||
|
('field', models.CharField(blank=True, max_length=255, verbose_name='field')),
|
||||||
|
('permanent', models.BooleanField(default=False, help_text='Tells if the permission should be granted even if the membership of the user is expired.', verbose_name='permanent')),
|
||||||
|
('description', models.CharField(blank=True, max_length=255, verbose_name='description')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'permission',
|
||||||
|
'verbose_name_plural': 'permissions',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PermissionMask',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('rank', models.PositiveSmallIntegerField(unique=True, verbose_name='rank')),
|
||||||
|
('description', models.CharField(max_length=255, unique=True, verbose_name='description')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'permission mask',
|
||||||
|
'verbose_name_plural': 'permission masks',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Role',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||||
|
('for_club', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='for club')),
|
||||||
|
('permissions', models.ManyToManyField(to='permission.Permission', verbose_name='permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'role permissions',
|
||||||
|
'verbose_name_plural': 'role permissions',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='permission',
|
||||||
|
name='mask',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='permissions', to='permission.PermissionMask', verbose_name='mask'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='permission',
|
||||||
|
name='model',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.ContentType', verbose_name='model'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='permission',
|
||||||
|
unique_together={('model', 'query', 'type', 'field')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1 +1 @@
|
||||||
Subproject commit 525f091b0caddc69cb2da7eba545ab9609bb1bb0
|
Subproject commit 7479671b3f6c499958d3ad991eb54de3010f4da8
|
|
@ -0,0 +1,107 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('note', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Invoice',
|
||||||
|
fields=[
|
||||||
|
('id', models.PositiveIntegerField(primary_key=True, serialize=False, verbose_name='Invoice identifier')),
|
||||||
|
('bde', models.CharField(choices=[('Saperlistpopette.png', 'Saper[list]popette'), ('Finalist.png', 'Fina[list]'), ('Listorique.png', '[List]orique'), ('Satellist.png', 'Satel[list]'), ('Monopolist.png', 'Monopo[list]'), ('Kataclist.png', 'Katac[list]')], default='Saperlistpopette.png', max_length=32, verbose_name='BDE')),
|
||||||
|
('object', models.CharField(max_length=255, verbose_name='Object')),
|
||||||
|
('description', models.TextField(verbose_name='Description')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||||
|
('address', models.TextField(verbose_name='Address')),
|
||||||
|
('date', models.DateField(default=datetime.date.today, verbose_name='Date')),
|
||||||
|
('acquitted', models.BooleanField(default=False, verbose_name='Acquitted')),
|
||||||
|
('locked', models.BooleanField(default=False, help_text="An invoice can't be edited when it is locked.", verbose_name='Locked')),
|
||||||
|
('tex', models.TextField(default='', verbose_name='tex source')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'invoice',
|
||||||
|
'verbose_name_plural': 'invoices',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Remittance',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date')),
|
||||||
|
('comment', models.CharField(max_length=255, verbose_name='Comment')),
|
||||||
|
('closed', models.BooleanField(default=False, verbose_name='Closed')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'remittance',
|
||||||
|
'verbose_name_plural': 'remittances',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SpecialTransactionProxy',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('remittance', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='treasury.Remittance', verbose_name='Remittance')),
|
||||||
|
('transaction', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.SpecialTransaction')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'special transaction proxy',
|
||||||
|
'verbose_name_plural': 'special transaction proxies',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SogeCredit',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('credit_transaction', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='note.SpecialTransaction', verbose_name='credit transaction')),
|
||||||
|
('transactions', models.ManyToManyField(related_name='_sogecredit_transactions_+', to='note.MembershipTransaction', verbose_name='membership transactions')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Credit from the Société générale',
|
||||||
|
'verbose_name_plural': 'Credits from the Société générale',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RemittanceType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('note', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.NoteSpecial')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'remittance type',
|
||||||
|
'verbose_name_plural': 'remittance types',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='remittance',
|
||||||
|
name='remittance_type',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='treasury.RemittanceType', verbose_name='Type'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Product',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('designation', models.CharField(max_length=255, verbose_name='Designation')),
|
||||||
|
('quantity', models.PositiveIntegerField(verbose_name='Quantity')),
|
||||||
|
('amount', models.IntegerField(verbose_name='Unit price')),
|
||||||
|
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='treasury.Invoice', verbose_name='invoice')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'product',
|
||||||
|
'verbose_name_plural': 'products',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,13 +7,15 @@
|
||||||
\usepackage[T1]{fontenc}
|
\usepackage[T1]{fontenc}
|
||||||
\usepackage[utf8]{inputenc}
|
\usepackage[utf8]{inputenc}
|
||||||
\usepackage[a4paper]{geometry}
|
\usepackage[a4paper]{geometry}
|
||||||
\usepackage{units}
|
%\usepackage{bera}
|
||||||
\usepackage{bera}
|
|
||||||
\usepackage{graphicx}
|
\usepackage{graphicx}
|
||||||
\usepackage{fancyhdr}
|
\usepackage{fancyhdr}
|
||||||
\usepackage{fp}
|
\usepackage{fp}
|
||||||
\usepackage{transparent}
|
\usepackage{transparent}
|
||||||
\usepackage{eso-pic}
|
\usepackage{eso-pic}
|
||||||
|
\usepackage{ifthen}
|
||||||
|
|
||||||
|
\DeclareUnicodeCharacter{00B0}{$^\circ$}
|
||||||
|
|
||||||
\def\TVA{0} % Taux de la TVA
|
\def\TVA{0} % Taux de la TVA
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from unittest import skip
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -143,6 +144,7 @@ class TestInvoices(TestCase):
|
||||||
self.assertRedirects(response, reverse("treasury:invoice_list"), 302, 200)
|
self.assertRedirects(response, reverse("treasury:invoice_list"), 302, 200)
|
||||||
self.assertFalse(Invoice.objects.filter(pk=self.invoice.id).exists())
|
self.assertFalse(Invoice.objects.filter(pk=self.invoice.id).exists())
|
||||||
|
|
||||||
|
@skip("LaTeX is buggy in the CI")
|
||||||
def test_invoice_render_pdf(self):
|
def test_invoice_render_pdf(self):
|
||||||
"""
|
"""
|
||||||
Generate the PDF file of an invoice.
|
Generate the PDF file of an invoice.
|
||||||
|
|
|
@ -217,7 +217,9 @@ class InvoiceRenderView(LoginRequiredMixin, View):
|
||||||
).wait()
|
).wait()
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")")
|
with open("{}/invoice-{:d}.log".format(tmp_dir, pk), "r") as f:
|
||||||
|
log = f.read()
|
||||||
|
raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")\n\n" + log)
|
||||||
|
|
||||||
# Display the generated pdf as a HTTP Response
|
# Display the generated pdf as a HTTP Response
|
||||||
pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
|
pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('permission', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('member', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Bus',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||||
|
('description', models.TextField(blank=True, default='', verbose_name='description')),
|
||||||
|
('information_json', models.TextField(default='{}', help_text='Information about the survey for new members, encoded in JSON', verbose_name='survey information')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Bus',
|
||||||
|
'verbose_name_plural': 'Buses',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BusTeam',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||||
|
('color', models.PositiveIntegerField(help_text='The color of the T-Shirt, stored with its number equivalent', verbose_name='color')),
|
||||||
|
('description', models.TextField(blank=True, default='', verbose_name='description')),
|
||||||
|
('bus', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teams', to='wei.Bus', verbose_name='bus')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Bus team',
|
||||||
|
'verbose_name_plural': 'Bus teams',
|
||||||
|
'unique_together': {('bus', 'name')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WEIClub',
|
||||||
|
fields=[
|
||||||
|
('club_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='member.Club')),
|
||||||
|
('year', models.PositiveIntegerField(default=2020, unique=True, verbose_name='year')),
|
||||||
|
('date_start', models.DateField(verbose_name='date start')),
|
||||||
|
('date_end', models.DateField(verbose_name='date end')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'WEI',
|
||||||
|
'verbose_name_plural': 'WEI',
|
||||||
|
},
|
||||||
|
bases=('member.club',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WEIRole',
|
||||||
|
fields=[
|
||||||
|
('role_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='permission.Role')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'WEI Role',
|
||||||
|
'verbose_name_plural': 'WEI Roles',
|
||||||
|
},
|
||||||
|
bases=('permission.role',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WEIRegistration',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('soge_credit', models.BooleanField(default=False, verbose_name='Credit from Société générale')),
|
||||||
|
('caution_check', models.BooleanField(default=False, verbose_name='Caution check given')),
|
||||||
|
('birth_date', models.DateField(verbose_name='birth date')),
|
||||||
|
('gender', models.CharField(choices=[('male', 'Male'), ('female', 'Female'), ('nonbinary', 'Non binary')], max_length=16, verbose_name='gender')),
|
||||||
|
('clothing_cut', models.CharField(choices=[('male', 'Male'), ('female', 'Female')], max_length=16, verbose_name='clothing cut')),
|
||||||
|
('clothing_size', models.CharField(choices=[('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL')], max_length=4, verbose_name='clothing size')),
|
||||||
|
('health_issues', models.TextField(blank=True, default='', verbose_name='health issues')),
|
||||||
|
('emergency_contact_name', models.CharField(max_length=255, verbose_name='emergency contact name')),
|
||||||
|
('emergency_contact_phone', phonenumber_field.modelfields.PhoneNumberField(max_length=32, region=None, verbose_name='emergency contact phone')),
|
||||||
|
('first_year', models.BooleanField(default=False, help_text='Tells if the user is new in the school.', verbose_name='first year')),
|
||||||
|
('information_json', models.TextField(default='{}', help_text='Information about the registration (buses for old members, survey fot the new members), encoded in JSON', verbose_name='registration information')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='wei', to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||||
|
('wei', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='users', to='wei.WEIClub', verbose_name='WEI')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'WEI User',
|
||||||
|
'verbose_name_plural': 'WEI Users',
|
||||||
|
'unique_together': {('user', 'wei')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WEIMembership',
|
||||||
|
fields=[
|
||||||
|
('membership_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='member.Membership')),
|
||||||
|
('bus', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='wei.Bus', verbose_name='bus')),
|
||||||
|
('registration', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='membership', to='wei.WEIRegistration', verbose_name='WEI registration')),
|
||||||
|
('team', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='wei.BusTeam', verbose_name='team')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'WEI membership',
|
||||||
|
'verbose_name_plural': 'WEI memberships',
|
||||||
|
},
|
||||||
|
bases=('member.membership',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='bus',
|
||||||
|
name='wei',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='buses', to='wei.WEIClub', verbose_name='WEI'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='bus',
|
||||||
|
unique_together={('wei', 'name')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1110,7 +1110,9 @@ class MemberListRenderView(LoginRequiredMixin, View):
|
||||||
).wait()
|
).wait()
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
raise IOError("An error attempted while generating a WEI list (code=" + str(error) + ")")
|
with open("{}/wei-list.log".format(tmp_dir), "r") as f:
|
||||||
|
log = f.read()
|
||||||
|
raise IOError("An error attempted while generating a WEI list (code=" + str(error) + ")\n\n" + log)
|
||||||
|
|
||||||
# Display the generated pdf as a HTTP Response
|
# Display the generated pdf as a HTTP Response
|
||||||
with open("{}/wei-list.pdf".format(tmp_dir), 'rb') as f:
|
with open("{}/wei-list.pdf".format(tmp_dir), 'rb') as f:
|
||||||
|
|
|
@ -14,7 +14,6 @@ fi
|
||||||
# Set up Django project
|
# Set up Django project
|
||||||
python3 manage.py collectstatic --noinput
|
python3 manage.py collectstatic --noinput
|
||||||
python3 manage.py compilemessages
|
python3 manage.py compilemessages
|
||||||
python3 manage.py makemigrations
|
|
||||||
python3 manage.py migrate
|
python3 manage.py migrate
|
||||||
|
|
||||||
if [ "$1" ]; then
|
if [ "$1" ]; then
|
||||||
|
|
|
@ -1,57 +1,53 @@
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$(".autocomplete").keyup(function(e) {
|
$('.autocomplete').keyup(function (e) {
|
||||||
let target = $("#" + e.target.id);
|
const target = $('#' + e.target.id)
|
||||||
let prefix = target.attr("id");
|
const prefix = target.attr('id')
|
||||||
let api_url = target.attr("api_url");
|
const api_url = target.attr('api_url')
|
||||||
let api_url_suffix = target.attr("api_url_suffix");
|
let api_url_suffix = target.attr('api_url_suffix')
|
||||||
if (!api_url_suffix)
|
if (!api_url_suffix) { api_url_suffix = '' }
|
||||||
api_url_suffix = "";
|
let name_field = target.attr('name_field')
|
||||||
let name_field = target.attr("name_field");
|
if (!name_field) { name_field = 'name' }
|
||||||
if (!name_field)
|
const input = target.val()
|
||||||
name_field = "name";
|
target.addClass('is-invalid')
|
||||||
let input = target.val();
|
target.removeClass('is-valid')
|
||||||
target.addClass("is-invalid");
|
$('#' + prefix + '_reset').removeClass('d-none')
|
||||||
target.removeClass("is-valid");
|
|
||||||
$("#" + prefix + "_reset").removeClass("d-none");
|
|
||||||
|
|
||||||
$.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
|
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
|
||||||
let html = "";
|
let html = ''
|
||||||
|
|
||||||
objects.results.forEach(function (obj) {
|
objects.results.forEach(function (obj) {
|
||||||
html += li(prefix + "_" + obj.id, obj[name_field]);
|
html += li(prefix + '_' + obj.id, obj[name_field])
|
||||||
});
|
})
|
||||||
|
|
||||||
let results_list = $("#" + prefix + "_list");
|
const results_list = $('#' + prefix + '_list')
|
||||||
results_list.html(html);
|
results_list.html(html)
|
||||||
|
|
||||||
objects.results.forEach(function (obj) {
|
objects.results.forEach(function (obj) {
|
||||||
$("#" + prefix + "_" + obj.id).click(function() {
|
$('#' + prefix + '_' + obj.id).click(function () {
|
||||||
target.val(obj[name_field]);
|
target.val(obj[name_field])
|
||||||
$("#" + prefix + "_pk").val(obj.id);
|
$('#' + prefix + '_pk').val(obj.id)
|
||||||
|
|
||||||
results_list.html("");
|
results_list.html('')
|
||||||
target.removeClass("is-invalid");
|
target.removeClass('is-invalid')
|
||||||
target.addClass("is-valid");
|
target.addClass('is-valid')
|
||||||
|
|
||||||
if (typeof autocompleted != 'undefined')
|
if (typeof autocompleted !== 'undefined') { autocompleted(obj, prefix) }
|
||||||
autocompleted(obj, prefix)
|
})
|
||||||
});
|
|
||||||
|
|
||||||
if (input === obj[name_field])
|
if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) }
|
||||||
$("#" + prefix + "_pk").val(obj.id);
|
})
|
||||||
});
|
|
||||||
|
|
||||||
if (results_list.children().length === 1 && e.originalEvent.keyCode >= 32) {
|
if (results_list.children().length === 1 && e.originalEvent.keyCode >= 32) {
|
||||||
results_list.children().first().trigger("click");
|
results_list.children().first().trigger('click')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".autocomplete-reset").click(function() {
|
$('.autocomplete-reset').click(function () {
|
||||||
let name = $(this).attr("id").replace("_reset", "");
|
const name = $(this).attr('id').replace('_reset', '')
|
||||||
$("#" + name + "_pk").val("");
|
$('#' + name + '_pk').val('')
|
||||||
$("#" + name).val("");
|
$('#' + name).val('')
|
||||||
$("#" + name + "_list").html("");
|
$('#' + name + '_list').html('')
|
||||||
$(this).addClass("d-none");
|
$(this).addClass('d-none')
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
// Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
// Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert balance in cents to a human readable amount
|
* Convert balance in cents to a human readable amount
|
||||||
* @param value the balance, in cents
|
* @param value the balance, in cents
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function pretty_money (value) {
|
function pretty_money (value) {
|
||||||
if (value % 100 === 0)
|
if (value % 100 === 0) { return (value < 0 ? '- ' : '') + Math.floor(Math.abs(value) / 100) + ' €' } else {
|
||||||
return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + " €";
|
return (value < 0 ? '- ' : '') + Math.floor(Math.abs(value) / 100) + '.' +
|
||||||
else
|
(Math.abs(value) % 100 < 10 ? '0' : '') + (Math.abs(value) % 100) + ' €'
|
||||||
return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + "."
|
}
|
||||||
+ (Math.abs(value) % 100 < 10 ? "0" : "") + (Math.abs(value) % 100) + " €";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,19 +20,19 @@ function pretty_money (value) {
|
||||||
* @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored.
|
* @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored.
|
||||||
*/
|
*/
|
||||||
function addMsg (msg, alert_type, timeout = -1) {
|
function addMsg (msg, alert_type, timeout = -1) {
|
||||||
let msgDiv = $("#messages");
|
const msgDiv = $('#messages')
|
||||||
let html = msgDiv.html();
|
let html = msgDiv.html()
|
||||||
let id = Math.floor(10000 * Math.random() + 1);
|
const id = Math.floor(10000 * Math.random() + 1)
|
||||||
html += "<div class=\"alert alert-" + alert_type + " alert-dismissible\">" +
|
html += '<div class="alert alert-' + alert_type + ' alert-dismissible">' +
|
||||||
"<button id=\"close-message-" + id + "\" class=\"close\" data-dismiss=\"alert\" href=\"#\"><span aria-hidden=\"true\">×</span></button>"
|
'<button id="close-message-' + id + '" class="close" data-dismiss="alert" href="#"><span aria-hidden="true">×</span></button>' +
|
||||||
+ msg + "</div>\n";
|
msg + '</div>\n'
|
||||||
msgDiv.html(html);
|
msgDiv.html(html)
|
||||||
|
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
$("#close-message-" + id).click();
|
$('#close-message-' + id).click()
|
||||||
}, timeout);
|
}, timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,34 +41,34 @@ function addMsg (msg, alert_type, timeout = -1) {
|
||||||
* @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored.
|
* @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored.
|
||||||
*/
|
*/
|
||||||
function errMsg (errs_obj, timeout = -1) {
|
function errMsg (errs_obj, timeout = -1) {
|
||||||
for (const err_msg of Object.values(errs_obj)) {
|
for (const err_msg of Object.values(errs_obj)) {
|
||||||
addMsg(err_msg, 'danger', timeout);
|
addMsg(err_msg, 'danger', timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var reloadWithTurbolinks = (function () {
|
var reloadWithTurbolinks = (function () {
|
||||||
var scrollPosition;
|
var scrollPosition
|
||||||
|
|
||||||
function reload () {
|
function reload () {
|
||||||
scrollPosition = [window.scrollX, window.scrollY];
|
scrollPosition = [window.scrollX, window.scrollY]
|
||||||
Turbolinks.visit(window.location.toString(), { action: 'replace' })
|
Turbolinks.visit(window.location.toString(), { action: 'replace' })
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('turbolinks:load', function () {
|
||||||
|
if (scrollPosition) {
|
||||||
|
window.scrollTo.apply(window, scrollPosition)
|
||||||
|
scrollPosition = null
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
document.addEventListener('turbolinks:load', function () {
|
return reload
|
||||||
if (scrollPosition) {
|
})()
|
||||||
window.scrollTo.apply(window, scrollPosition);
|
|
||||||
scrollPosition = null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return reload;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload the balance of the user on the right top corner
|
* Reload the balance of the user on the right top corner
|
||||||
*/
|
*/
|
||||||
function refreshBalance () {
|
function refreshBalance () {
|
||||||
$("#user_balance").load("/ #user_balance");
|
$('#user_balance').load('/ #user_balance')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,15 +77,15 @@ function refreshBalance () {
|
||||||
* @param fun For each found note with the matched alias `alias`, fun(note, alias) is called.
|
* @param fun For each found note with the matched alias `alias`, fun(note, alias) is called.
|
||||||
*/
|
*/
|
||||||
function getMatchedNotes (pattern, fun) {
|
function getMatchedNotes (pattern, fun) {
|
||||||
$.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club", fun);
|
$.getJSON('/api/note/alias/?format=json&alias=' + pattern + '&search=user|club', fun)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a <li> entry with a given id and text
|
* Generate a <li> entry with a given id and text
|
||||||
*/
|
*/
|
||||||
function li (id, text, extra_css) {
|
function li (id, text, extra_css) {
|
||||||
return "<li class=\"list-group-item py-1 px-2 d-flex justify-content-between align-items-center text-truncate "
|
return '<li class="list-group-item py-1 px-2 d-flex justify-content-between align-items-center text-truncate ' +
|
||||||
+ (extra_css ? extra_css : "") + "\"" + " id=\"" + id + "\">" + text + "</li>\n";
|
(extra_css || '') + '"' + ' id="' + id + '">' + text + '</li>\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,24 +93,13 @@ function li (id, text, extra_css) {
|
||||||
* @param note The concerned note.
|
* @param note The concerned note.
|
||||||
*/
|
*/
|
||||||
function displayStyle (note) {
|
function displayStyle (note) {
|
||||||
if (!note)
|
if (!note) { return '' }
|
||||||
return "";
|
const balance = note.balance
|
||||||
let balance = note.balance;
|
var css = ''
|
||||||
var css = "";
|
if (balance < -5000) { css += ' text-danger bg-dark' } else if (balance < -1000) { css += ' text-danger' } else if (balance < 0) { css += ' text-warning' } else if (!note.email_confirmed) { css += ' text-white bg-primary' } else if (!note.is_active || (note.membership && note.membership.date_end < new Date().toISOString())) { css += 'text-white bg-info' }
|
||||||
if (balance < -5000)
|
return css
|
||||||
css += " text-danger bg-dark";
|
|
||||||
else if (balance < -1000)
|
|
||||||
css += " text-danger";
|
|
||||||
else if (balance < 0)
|
|
||||||
css += " text-warning";
|
|
||||||
else if (!note.email_confirmed)
|
|
||||||
css += " text-white bg-primary";
|
|
||||||
else if (!note.is_active || (note.membership && note.membership.date_end < new Date().toISOString()))
|
|
||||||
css += "text-white bg-info";
|
|
||||||
return css;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render note name and picture
|
* Render note name and picture
|
||||||
* @param note The note to render
|
* @param note The note to render
|
||||||
|
@ -121,23 +108,22 @@ function displayStyle (note) {
|
||||||
* @param profile_pic_field
|
* @param profile_pic_field
|
||||||
*/
|
*/
|
||||||
function displayNote (note, alias, user_note_field = null, profile_pic_field = null) {
|
function displayNote (note, alias, user_note_field = null, profile_pic_field = null) {
|
||||||
if (!note.display_image) {
|
if (!note.display_image) {
|
||||||
note.display_image = '/static/member/img/default_picture.png';
|
note.display_image = '/static/member/img/default_picture.png'
|
||||||
}
|
}
|
||||||
let img = note.display_image;
|
const img = note.display_image
|
||||||
if (alias !== note.name && note.name)
|
if (alias !== note.name && note.name) { alias += ' (aka. ' + note.name + ')' }
|
||||||
alias += " (aka. " + note.name + ")";
|
if (user_note_field !== null) {
|
||||||
if (user_note_field !== null) {
|
$('#' + user_note_field).removeAttr('class')
|
||||||
$("#" + user_note_field).removeAttr('class');
|
$('#' + user_note_field).addClass(displayStyle(note))
|
||||||
$("#" + user_note_field).addClass(displayStyle(note));
|
$('#' + user_note_field).text(alias + (note.balance == null ? '' : (' :\n' + pretty_money(note.balance))))
|
||||||
$("#" + user_note_field).text(alias + (note.balance == null ? "" : (" :\n" + pretty_money(note.balance))));
|
if (profile_pic_field != null) {
|
||||||
if (profile_pic_field != null) {
|
$('#' + profile_pic_field).attr('src', img)
|
||||||
$("#" + profile_pic_field).attr('src', img);
|
$('#' + profile_pic_field + '_link').attr('href', note.resourcetype === 'NoteUser'
|
||||||
$("#" + profile_pic_field + "_link").attr('href', note.resourcetype === "NoteUser" ?
|
? '/accounts/user/' + note.user : note.resourcetype === 'NoteClub'
|
||||||
"/accounts/user/" + note.user : note.resourcetype === "NoteClub" ?
|
? '/accounts/club/' + note.club : '#')
|
||||||
"/accounts/club/" + note.club : "#");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,35 +138,34 @@ function displayNote (note, alias, user_note_field = null, profile_pic_field = n
|
||||||
* (useful in consumptions, put null if not used)
|
* (useful in consumptions, put null if not used)
|
||||||
* @returns an anonymous function to be compatible with jQuery events
|
* @returns an anonymous function to be compatible with jQuery events
|
||||||
*/
|
*/
|
||||||
function removeNote (d, note_prefix = "note", notes_display, note_list_id, user_note_field = null, profile_pic_field = null) {
|
function removeNote (d, note_prefix = 'note', notes_display, note_list_id, user_note_field = null, profile_pic_field = null) {
|
||||||
return (function () {
|
return function () {
|
||||||
let new_notes_display = [];
|
const new_notes_display = []
|
||||||
let html = "";
|
let html = ''
|
||||||
notes_display.forEach(function (disp) {
|
notes_display.forEach(function (disp) {
|
||||||
if (disp.quantity > 1 || disp.id !== d.id) {
|
if (disp.quantity > 1 || disp.id !== d.id) {
|
||||||
disp.quantity -= disp.id === d.id ? 1 : 0;
|
disp.quantity -= disp.id === d.id ? 1 : 0
|
||||||
new_notes_display.push(disp);
|
new_notes_display.push(disp)
|
||||||
html += li(note_prefix + "_" + disp.id, disp.name
|
html += li(note_prefix + '_' + disp.id, disp.name +
|
||||||
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>",
|
'<span class="badge badge-dark badge-pill">' + disp.quantity + '</span>',
|
||||||
displayStyle(disp.note));
|
displayStyle(disp.note))
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
notes_display.length = 0;
|
notes_display.length = 0
|
||||||
new_notes_display.forEach(function (disp) {
|
new_notes_display.forEach(function (disp) {
|
||||||
notes_display.push(disp);
|
notes_display.push(disp)
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#" + note_list_id).html(html);
|
$('#' + note_list_id).html(html)
|
||||||
notes_display.forEach(function (disp) {
|
notes_display.forEach(function (disp) {
|
||||||
let obj = $("#" + note_prefix + "_" + disp.id);
|
const obj = $('#' + note_prefix + '_' + disp.id)
|
||||||
obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field));
|
obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field))
|
||||||
obj.hover(function () {
|
obj.hover(function () {
|
||||||
if (disp.note)
|
if (disp.note) { displayNote(disp.note, disp.name, user_note_field, profile_pic_field) }
|
||||||
displayNote(disp.note, disp.name, user_note_field, profile_pic_field);
|
})
|
||||||
});
|
})
|
||||||
});
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -199,203 +184,193 @@ function removeNote (d, note_prefix = "note", notes_display, note_list_id, user_
|
||||||
* the associated note is not displayed.
|
* the associated note is not displayed.
|
||||||
* Useful for a consumption if the item is selected before.
|
* Useful for a consumption if the item is selected before.
|
||||||
*/
|
*/
|
||||||
function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_prefix = "alias",
|
function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_prefix = 'alias',
|
||||||
note_prefix = "note", user_note_field = null, profile_pic_field = null, alias_click = null) {
|
note_prefix = 'note', user_note_field = null, profile_pic_field = null, alias_click = null) {
|
||||||
let field = $("#" + field_id);
|
const field = $('#' + field_id)
|
||||||
|
|
||||||
// Configure tooltip
|
// Configure tooltip
|
||||||
field.tooltip({
|
field.tooltip({
|
||||||
html: true,
|
html: true,
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
title: 'Loading...',
|
title: 'Loading...',
|
||||||
trigger: 'manual',
|
trigger: 'manual',
|
||||||
container: field.parent(),
|
container: field.parent(),
|
||||||
fallbackPlacement: 'clockwise'
|
fallbackPlacement: 'clockwise'
|
||||||
});
|
})
|
||||||
|
|
||||||
// When the user clicks elsewhere, we hide the tooltip
|
// When the user clicks elsewhere, we hide the tooltip
|
||||||
$(document).click(function(e) {
|
$(document).click(function (e) {
|
||||||
if (!e.target.id.startsWith(alias_prefix)) {
|
if (!e.target.id.startsWith(alias_prefix)) {
|
||||||
field.tooltip("hide");
|
field.tooltip('hide')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
let old_pattern = null;
|
let old_pattern = null
|
||||||
|
|
||||||
// Clear search on click
|
// Clear search on click
|
||||||
field.click(function () {
|
field.click(function () {
|
||||||
field.tooltip('hide');
|
field.tooltip('hide')
|
||||||
field.removeClass('is-invalid');
|
field.removeClass('is-invalid')
|
||||||
field.val("");
|
field.val('')
|
||||||
old_pattern = "";
|
old_pattern = ''
|
||||||
});
|
})
|
||||||
|
|
||||||
// When the user type "Enter", the first alias is clicked
|
// When the user type "Enter", the first alias is clicked
|
||||||
field.keypress(function (event) {
|
field.keypress(function (event) {
|
||||||
if (event.originalEvent.charCode === 13 && notes.length > 0) {
|
if (event.originalEvent.charCode === 13 && notes.length > 0) {
|
||||||
let li_obj = field.parent().find("ul li").first();
|
const li_obj = field.parent().find('ul li').first()
|
||||||
displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field);
|
displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field)
|
||||||
li_obj.trigger("click");
|
li_obj.trigger('click')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// When the user type something, the matched aliases are refreshed
|
// When the user type something, the matched aliases are refreshed
|
||||||
field.keyup(function (e) {
|
field.keyup(function (e) {
|
||||||
field.removeClass('is-invalid');
|
field.removeClass('is-invalid')
|
||||||
|
|
||||||
if (e.originalEvent.charCode === 13)
|
if (e.originalEvent.charCode === 13) { return }
|
||||||
return;
|
|
||||||
|
|
||||||
let pattern = field.val();
|
const pattern = field.val()
|
||||||
|
|
||||||
// If the pattern is not modified, we don't query the API
|
// If the pattern is not modified, we don't query the API
|
||||||
if (pattern === old_pattern)
|
if (pattern === old_pattern) { return }
|
||||||
return;
|
old_pattern = pattern
|
||||||
old_pattern = pattern;
|
notes.length = 0
|
||||||
notes.length = 0;
|
|
||||||
|
|
||||||
// get matched Alias with note associated
|
// get matched Alias with note associated
|
||||||
if (pattern === "") {
|
if (pattern === '') {
|
||||||
field.tooltip('hide');
|
field.tooltip('hide')
|
||||||
notes.length = 0;
|
notes.length = 0
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$.getJSON("/api/note/consumer/?format=json&alias=" + pattern + "&search=user|club",
|
$.getJSON('/api/note/consumer/?format=json&alias=' + pattern + '&search=user|club',
|
||||||
function (consumers) {
|
function (consumers) {
|
||||||
// The response arrived too late, we stop the request
|
// The response arrived too late, we stop the request
|
||||||
if (pattern !== $("#" + field_id).val())
|
if (pattern !== $('#' + field_id).val()) { return }
|
||||||
return;
|
|
||||||
|
|
||||||
// Build tooltip content
|
// Build tooltip content
|
||||||
let aliases_matched_html = '<ul class="list-group list-group-flush">';
|
let aliases_matched_html = '<ul class="list-group list-group-flush">'
|
||||||
consumers.results.forEach(function (consumer) {
|
consumers.results.forEach(function (consumer) {
|
||||||
let note = consumer.note;
|
const note = consumer.note
|
||||||
note.email_confirmed = consumer.email_confirmed;
|
note.email_confirmed = consumer.email_confirmed
|
||||||
if (consumer.hasOwnProperty("membership") && consumer.membership)
|
if (consumer.hasOwnProperty('membership') && consumer.membership) { note.membership = consumer.membership } else { note.membership = undefined }
|
||||||
note.membership = consumer.membership;
|
const extra_css = displayStyle(note)
|
||||||
else
|
aliases_matched_html += li(alias_prefix + '_' + consumer.id,
|
||||||
note.membership = undefined;
|
consumer.name,
|
||||||
let extra_css = displayStyle(note);
|
extra_css)
|
||||||
aliases_matched_html += li(alias_prefix + '_' + consumer.id,
|
notes.push(note)
|
||||||
consumer.name,
|
})
|
||||||
extra_css);
|
aliases_matched_html += '</ul>'
|
||||||
notes.push(note);
|
|
||||||
});
|
|
||||||
aliases_matched_html += '</ul>';
|
|
||||||
|
|
||||||
// Show tooltip
|
// Show tooltip
|
||||||
field.attr('data-original-title', aliases_matched_html).tooltip('show');
|
field.attr('data-original-title', aliases_matched_html).tooltip('show')
|
||||||
|
|
||||||
consumers.results.forEach(function (consumer) {
|
consumers.results.forEach(function (consumer) {
|
||||||
let consumer_obj = $("#" + alias_prefix + "_" + consumer.id);
|
const consumer_obj = $('#' + alias_prefix + '_' + consumer.id)
|
||||||
consumer_obj.hover(function () {
|
consumer_obj.hover(function () {
|
||||||
displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field)
|
displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field)
|
||||||
});
|
})
|
||||||
consumer_obj.click(function () {
|
consumer_obj.click(function () {
|
||||||
var disp = null;
|
var disp = null
|
||||||
notes_display.forEach(function (d) {
|
notes_display.forEach(function (d) {
|
||||||
// We compare the alias ids
|
// We compare the alias ids
|
||||||
if (d.id === consumer.id) {
|
if (d.id === consumer.id) {
|
||||||
d.quantity += 1;
|
d.quantity += 1
|
||||||
disp = d;
|
disp = d
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
// In the other case, we add a new emitter
|
// In the other case, we add a new emitter
|
||||||
if (disp == null) {
|
if (disp == null) {
|
||||||
disp = {
|
disp = {
|
||||||
name: consumer.name,
|
name: consumer.name,
|
||||||
id: consumer.id,
|
id: consumer.id,
|
||||||
note: consumer.note,
|
note: consumer.note,
|
||||||
quantity: 1
|
quantity: 1
|
||||||
};
|
}
|
||||||
notes_display.push(disp);
|
notes_display.push(disp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the function alias_click exists, it is called. If it doesn't return true, then the notes are
|
// If the function alias_click exists, it is called. If it doesn't return true, then the notes are
|
||||||
// note displayed. Useful for a consumption when a button is already clicked
|
// note displayed. Useful for a consumption when a button is already clicked
|
||||||
if (alias_click && !alias_click())
|
if (alias_click && !alias_click()) { return }
|
||||||
return;
|
|
||||||
|
|
||||||
let note_list = $("#" + note_list_id);
|
const note_list = $('#' + note_list_id)
|
||||||
let html = "";
|
let html = ''
|
||||||
notes_display.forEach(function (disp) {
|
notes_display.forEach(function (disp) {
|
||||||
html += li(note_prefix + "_" + disp.id,
|
html += li(note_prefix + '_' + disp.id,
|
||||||
disp.name
|
disp.name +
|
||||||
+ "<span class=\"badge badge-dark badge-pill\">"
|
'<span class="badge badge-dark badge-pill">' +
|
||||||
+ disp.quantity + "</span>",
|
disp.quantity + '</span>',
|
||||||
displayStyle(disp.note));
|
displayStyle(disp.note))
|
||||||
});
|
})
|
||||||
|
|
||||||
// Emitters are displayed
|
// Emitters are displayed
|
||||||
note_list.html(html);
|
note_list.html(html)
|
||||||
|
|
||||||
// Update tooltip position
|
// Update tooltip position
|
||||||
field.tooltip('update');
|
field.tooltip('update')
|
||||||
|
|
||||||
notes_display.forEach(function (disp) {
|
notes_display.forEach(function (disp) {
|
||||||
let line_obj = $("#" + note_prefix + "_" + disp.id);
|
const line_obj = $('#' + note_prefix + '_' + disp.id)
|
||||||
// Hover an emitter display also the profile picture
|
// Hover an emitter display also the profile picture
|
||||||
line_obj.hover(function () {
|
line_obj.hover(function () {
|
||||||
displayNote(disp.note, disp.name, user_note_field, profile_pic_field);
|
displayNote(disp.note, disp.name, user_note_field, profile_pic_field)
|
||||||
});
|
})
|
||||||
|
|
||||||
// When an emitter is clicked, it is removed
|
// When an emitter is clicked, it is removed
|
||||||
line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field,
|
line_obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field,
|
||||||
profile_pic_field));
|
profile_pic_field))
|
||||||
});
|
})
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
})// end getJSON alias
|
||||||
});// end getJSON alias
|
})
|
||||||
});
|
|
||||||
}// end function autocomplete
|
}// end function autocomplete
|
||||||
|
|
||||||
|
|
||||||
// When a validate button is clicked, we switch the validation status
|
// When a validate button is clicked, we switch the validation status
|
||||||
function de_validate (id, validated, resourcetype) {
|
function de_validate (id, validated, resourcetype) {
|
||||||
let validate_obj = $("#validate_" + id);
|
const validate_obj = $('#validate_' + id)
|
||||||
|
|
||||||
if (validate_obj.data("pending"))
|
if (validate_obj.data('pending'))
|
||||||
// The button is already clicked
|
// The button is already clicked
|
||||||
return;
|
{ return }
|
||||||
|
|
||||||
let invalidity_reason = $("#invalidity_reason_" + id).val();
|
const invalidity_reason = $('#invalidity_reason_' + id).val()
|
||||||
validate_obj.html("<strong style=\"font-size: 16pt;\">⟳</strong>");
|
validate_obj.html('<strong style="font-size: 16pt;">⟳</strong>')
|
||||||
validate_obj.data("pending", true);
|
validate_obj.data('pending', true)
|
||||||
|
|
||||||
// Perform a PATCH request to the API in order to update the transaction
|
// Perform a PATCH request to the API in order to update the transaction
|
||||||
// If the user has insufficient rights, an error message will appear
|
// If the user has insufficient rights, an error message will appear
|
||||||
$.ajax({
|
$.ajax({
|
||||||
"url": "/api/note/transaction/transaction/" + id + "/",
|
url: '/api/note/transaction/transaction/' + id + '/',
|
||||||
type: "PATCH",
|
type: 'PATCH',
|
||||||
dataType: "json",
|
dataType: 'json',
|
||||||
headers: {
|
headers: {
|
||||||
"X-CSRFTOKEN": CSRF_TOKEN
|
'X-CSRFTOKEN': CSRF_TOKEN
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
"resourcetype": resourcetype,
|
resourcetype: resourcetype,
|
||||||
"valid": !validated,
|
valid: !validated,
|
||||||
"invalidity_reason": invalidity_reason,
|
invalidity_reason: invalidity_reason
|
||||||
},
|
},
|
||||||
success: function () {
|
success: function () {
|
||||||
refreshBalance();
|
refreshBalance()
|
||||||
// error if this method doesn't exist. Please define it.
|
// error if this method doesn't exist. Please define it.
|
||||||
refreshHistory();
|
refreshHistory()
|
||||||
},
|
},
|
||||||
error: function (err) {
|
error: function (err) {
|
||||||
let errObj = JSON.parse(err.responseText);
|
const errObj = JSON.parse(err.responseText)
|
||||||
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"];
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
if (!error)
|
if (!error) { error = err.responseText }
|
||||||
error = err.responseText;
|
addMsg('Une erreur est survenue lors de la validation/dévalidation ' +
|
||||||
addMsg("Une erreur est survenue lors de la validation/dévalidation " +
|
'de cette transaction : ' + error, 'danger')
|
||||||
"de cette transaction : " + error, "danger");
|
|
||||||
|
|
||||||
refreshBalance();
|
refreshBalance()
|
||||||
// error if this method doesn't exist. Please define it.
|
// error if this method doesn't exist. Please define it.
|
||||||
refreshHistory();
|
refreshHistory()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -404,10 +379,10 @@ function de_validate (id, validated, resourcetype) {
|
||||||
* @param wait Debounced milliseconds
|
* @param wait Debounced milliseconds
|
||||||
*/
|
*/
|
||||||
function debounce (callback, wait) {
|
function debounce (callback, wait) {
|
||||||
let timeout;
|
let timeout
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
const context = this;
|
const context = this
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout)
|
||||||
timeout = setTimeout(() => callback.apply(context, args), wait);
|
timeout = setTimeout(() => callback.apply(context, args), wait)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,95 +2,92 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// When a transaction is performed, lock the interface to prevent spam clicks.
|
// When a transaction is performed, lock the interface to prevent spam clicks.
|
||||||
var LOCK = false;
|
var LOCK = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the history table on the consumptions page.
|
* Refresh the history table on the consumptions page.
|
||||||
*/
|
*/
|
||||||
function refreshHistory() {
|
function refreshHistory () {
|
||||||
$("#history").load("/note/consos/ #history");
|
$('#history').load('/note/consos/ #history')
|
||||||
$("#most_used").load("/note/consos/ #most_used");
|
$('#most_used').load('/note/consos/ #most_used')
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function () {
|
||||||
// If hash of a category in the URL, then select this category
|
// If hash of a category in the URL, then select this category
|
||||||
// else select the first one
|
// else select the first one
|
||||||
if (location.hash) {
|
if (location.hash) {
|
||||||
$("a[href='" + location.hash + "']").tab("show");
|
$("a[href='" + location.hash + "']").tab('show')
|
||||||
} else {
|
} else {
|
||||||
$("a[data-toggle='tab']").first().tab("show");
|
$("a[data-toggle='tab']").first().tab('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
// When selecting a category, change URL
|
||||||
|
$(document.body).on('click', "a[data-toggle='tab']", function () {
|
||||||
|
location.hash = this.getAttribute('href')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Switching in double consumptions mode should update the layout
|
||||||
|
$('#double_conso').change(function () {
|
||||||
|
$('#consos_list_div').removeClass('d-none')
|
||||||
|
$('#user_select_div').attr('class', 'col-xl-4')
|
||||||
|
$('#infos_div').attr('class', 'col-sm-5 col-xl-6')
|
||||||
|
|
||||||
|
const note_list_obj = $('#note_list')
|
||||||
|
if (buttons.length > 0 && note_list_obj.text().length > 0) {
|
||||||
|
$('#consos_list').html(note_list_obj.html())
|
||||||
|
note_list_obj.html('')
|
||||||
|
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
$('#conso_button_' + button.id).click(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
removeNote(button, 'conso_button', buttons, 'consos_list')()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// When selecting a category, change URL
|
$('#single_conso').change(function () {
|
||||||
$(document.body).on("click", "a[data-toggle='tab']", function() {
|
$('#consos_list_div').addClass('d-none')
|
||||||
location.hash = this.getAttribute("href");
|
$('#user_select_div').attr('class', 'col-xl-7')
|
||||||
});
|
$('#infos_div').attr('class', 'col-sm-5 col-md-4')
|
||||||
|
|
||||||
// Switching in double consumptions mode should update the layout
|
const consos_list_obj = $('#consos_list')
|
||||||
$("#double_conso").change(function() {
|
if (buttons.length > 0) {
|
||||||
$("#consos_list_div").removeClass('d-none');
|
if (notes_display.length === 0 && consos_list_obj.text().length > 0) {
|
||||||
$("#user_select_div").attr('class', 'col-xl-4');
|
$('#note_list').html(consos_list_obj.html())
|
||||||
$("#infos_div").attr('class', 'col-sm-5 col-xl-6');
|
consos_list_obj.html('')
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
$('#conso_button_' + button.id).click(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
removeNote(button, 'conso_button', buttons, 'note_list')()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
buttons.length = 0
|
||||||
|
consos_list_obj.html('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let note_list_obj = $("#note_list");
|
// Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS
|
||||||
if (buttons.length > 0 && note_list_obj.text().length > 0) {
|
$("label[for='double_conso']").removeClass('active')
|
||||||
$("#consos_list").html(note_list_obj.html());
|
|
||||||
note_list_obj.html("");
|
|
||||||
|
|
||||||
buttons.forEach(function(button) {
|
$('#consume_all').click(consumeAll)
|
||||||
$("#conso_button_" + button.id).click(function() {
|
})
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
removeNote(button, "conso_button", buttons,"consos_list")();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#single_conso").change(function() {
|
notes = []
|
||||||
$("#consos_list_div").addClass('d-none');
|
notes_display = []
|
||||||
$("#user_select_div").attr('class', 'col-xl-7');
|
buttons = []
|
||||||
$("#infos_div").attr('class', 'col-sm-5 col-md-4');
|
|
||||||
|
|
||||||
let consos_list_obj = $("#consos_list");
|
|
||||||
if (buttons.length > 0) {
|
|
||||||
if (notes_display.length === 0 && consos_list_obj.text().length > 0) {
|
|
||||||
$("#note_list").html(consos_list_obj.html());
|
|
||||||
consos_list_obj.html("");
|
|
||||||
buttons.forEach(function(button) {
|
|
||||||
$("#conso_button_" + button.id).click(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
removeNote(button, "conso_button", buttons,"note_list")();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buttons.length = 0;
|
|
||||||
consos_list_obj.html("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS
|
|
||||||
$("label[for='double_conso']").removeClass('active');
|
|
||||||
|
|
||||||
$("#consume_all").click(consumeAll);
|
|
||||||
});
|
|
||||||
|
|
||||||
notes = [];
|
|
||||||
notes_display = [];
|
|
||||||
buttons = [];
|
|
||||||
|
|
||||||
// When the user searches an alias, we update the auto-completion
|
// When the user searches an alias, we update the auto-completion
|
||||||
autoCompleteNote("note", "note_list", notes, notes_display,
|
autoCompleteNote('note', 'note_list', notes, notes_display,
|
||||||
"alias", "note", "user_note", "profile_pic", function() {
|
'alias', 'note', 'user_note', 'profile_pic', function () {
|
||||||
if (buttons.length > 0 && $("#single_conso").is(":checked")) {
|
if (buttons.length > 0 && $('#single_conso').is(':checked')) {
|
||||||
consumeAll();
|
consumeAll()
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a transaction from a button.
|
* Add a transaction from a button.
|
||||||
|
@ -102,103 +99,98 @@ autoCompleteNote("note", "note_list", notes, notes_display,
|
||||||
* @param template_id The identifier of the button
|
* @param template_id The identifier of the button
|
||||||
* @param template_name The name of the button
|
* @param template_name The name of the button
|
||||||
*/
|
*/
|
||||||
function addConso(dest, amount, type, category_id, category_name, template_id, template_name) {
|
function addConso (dest, amount, type, category_id, category_name, template_id, template_name) {
|
||||||
var button = null;
|
var button = null
|
||||||
buttons.forEach(function(b) {
|
buttons.forEach(function (b) {
|
||||||
if (b.id === template_id) {
|
if (b.id === template_id) {
|
||||||
b.quantity += 1;
|
b.quantity += 1
|
||||||
button = b;
|
button = b
|
||||||
}
|
|
||||||
});
|
|
||||||
if (button == null) {
|
|
||||||
button = {
|
|
||||||
id: template_id,
|
|
||||||
name: template_name,
|
|
||||||
dest: dest,
|
|
||||||
quantity: 1,
|
|
||||||
amount: amount,
|
|
||||||
type: type,
|
|
||||||
category_id: category_id,
|
|
||||||
category_name: category_name
|
|
||||||
};
|
|
||||||
buttons.push(button);
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
let dc_obj = $("#double_conso");
|
if (button == null) {
|
||||||
if (dc_obj.is(":checked") || notes_display.length === 0) {
|
button = {
|
||||||
let list = dc_obj.is(":checked") ? "consos_list" : "note_list";
|
id: template_id,
|
||||||
let html = "";
|
name: template_name,
|
||||||
buttons.forEach(function(button) {
|
dest: dest,
|
||||||
html += li("conso_button_" + button.id, button.name
|
quantity: 1,
|
||||||
+ "<span class=\"badge badge-dark badge-pill\">" + button.quantity + "</span>");
|
amount: amount,
|
||||||
});
|
type: type,
|
||||||
|
category_id: category_id,
|
||||||
$("#" + list).html(html);
|
category_name: category_name
|
||||||
|
|
||||||
buttons.forEach(function(button) {
|
|
||||||
$("#conso_button_" + button.id).click(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
removeNote(button, "conso_button", buttons, list)();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
buttons.push(button)
|
||||||
consumeAll();
|
}
|
||||||
|
|
||||||
|
const dc_obj = $('#double_conso')
|
||||||
|
if (dc_obj.is(':checked') || notes_display.length === 0) {
|
||||||
|
const list = dc_obj.is(':checked') ? 'consos_list' : 'note_list'
|
||||||
|
let html = ''
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
html += li('conso_button_' + button.id, button.name +
|
||||||
|
'<span class="badge badge-dark badge-pill">' + button.quantity + '</span>')
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#' + list).html(html)
|
||||||
|
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
$('#conso_button_' + button.id).click(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
removeNote(button, 'conso_button', buttons, list)()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else { consumeAll() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the page as its initial state.
|
* Reset the page as its initial state.
|
||||||
*/
|
*/
|
||||||
function reset() {
|
function reset () {
|
||||||
notes_display.length = 0;
|
notes_display.length = 0
|
||||||
notes.length = 0;
|
notes.length = 0
|
||||||
buttons.length = 0;
|
buttons.length = 0
|
||||||
$("#note_list").html("");
|
$('#note_list').html('')
|
||||||
$("#consos_list").html("");
|
$('#consos_list').html('')
|
||||||
$("#note").val("");
|
$('#note').val('')
|
||||||
$("#note").attr("data-original-title", "").tooltip("hide");
|
$('#note').attr('data-original-title', '').tooltip('hide')
|
||||||
$("#profile_pic").attr("src", "/static/member/img/default_picture.png");
|
$('#profile_pic').attr('src', '/static/member/img/default_picture.png')
|
||||||
$("#profile_pic_link").attr("href", "#");
|
$('#profile_pic_link').attr('href', '#')
|
||||||
refreshHistory();
|
refreshHistory()
|
||||||
refreshBalance();
|
refreshBalance()
|
||||||
LOCK = false;
|
LOCK = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply all transactions: all notes in `notes` buy each item in `buttons`
|
* Apply all transactions: all notes in `notes` buy each item in `buttons`
|
||||||
*/
|
*/
|
||||||
function consumeAll() {
|
function consumeAll () {
|
||||||
if (LOCK)
|
if (LOCK) { return }
|
||||||
return;
|
|
||||||
|
|
||||||
LOCK = true;
|
LOCK = true
|
||||||
|
|
||||||
let error = false;
|
let error = false
|
||||||
|
|
||||||
if (notes_display.length === 0) {
|
if (notes_display.length === 0) {
|
||||||
$("#note").addClass('is-invalid');
|
$('#note').addClass('is-invalid')
|
||||||
$("#note_list").html(li("", "<strong>Ajoutez des émetteurs.</strong>", "text-danger"));
|
$('#note_list').html(li('', '<strong>Ajoutez des émetteurs.</strong>', 'text-danger'))
|
||||||
error = true;
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttons.length === 0) {
|
if (buttons.length === 0) {
|
||||||
$("#consos_list").html(li("", "<strong>Ajoutez des consommations.</strong>", "text-danger"));
|
$('#consos_list').html(li('', '<strong>Ajoutez des consommations.</strong>', 'text-danger'))
|
||||||
error = true;
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
LOCK = false;
|
LOCK = false
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
notes_display.forEach(function(note_display) {
|
notes_display.forEach(function (note_display) {
|
||||||
buttons.forEach(function(button) {
|
buttons.forEach(function (button) {
|
||||||
consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount,
|
consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount,
|
||||||
button.name + " (" + button.category_name + ")", button.type, button.category_id, button.id);
|
button.name + ' (' + button.category_name + ')', button.type, button.category_id, button.id)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -213,58 +205,60 @@ function consumeAll() {
|
||||||
* @param category The category id of the button (type: int)
|
* @param category The category id of the button (type: int)
|
||||||
* @param template The button id (type: int)
|
* @param template The button id (type: int)
|
||||||
*/
|
*/
|
||||||
function consume(source, source_alias, dest, quantity, amount, reason, type, category, template) {
|
function consume (source, source_alias, dest, quantity, amount, reason, type, category, template) {
|
||||||
$.post("/api/note/transaction/transaction/",
|
$.post('/api/note/transaction/transaction/',
|
||||||
|
{
|
||||||
|
csrfmiddlewaretoken: CSRF_TOKEN,
|
||||||
|
quantity: quantity,
|
||||||
|
amount: amount,
|
||||||
|
reason: reason,
|
||||||
|
valid: true,
|
||||||
|
polymorphic_ctype: type,
|
||||||
|
resourcetype: 'RecurrentTransaction',
|
||||||
|
source: source.id,
|
||||||
|
source_alias: source_alias,
|
||||||
|
destination: dest,
|
||||||
|
template: template
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
if (!isNaN(source.balance)) {
|
||||||
|
const newBalance = source.balance - quantity * amount
|
||||||
|
if (newBalance <= -5000) {
|
||||||
|
addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' +
|
||||||
|
'succès, mais la note émettrice ' + source_alias + ' est en négatif sévère.',
|
||||||
|
'danger', 30000)
|
||||||
|
} else if (newBalance < 0) {
|
||||||
|
addMsg('Attention, La transaction depuis la note ' + source_alias + ' a été réalisée avec ' +
|
||||||
|
'succès, mais la note émettrice ' + source_alias + ' est en négatif.',
|
||||||
|
'warning', 30000)
|
||||||
|
}
|
||||||
|
if (source.membership && source.membership.date_end < new Date().toISOString()) {
|
||||||
|
addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.",
|
||||||
|
'danger', 30000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
}).fail(function (e) {
|
||||||
|
$.post('/api/note/transaction/transaction/',
|
||||||
{
|
{
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
csrfmiddlewaretoken: CSRF_TOKEN,
|
||||||
"quantity": quantity,
|
quantity: quantity,
|
||||||
"amount": amount,
|
amount: amount,
|
||||||
"reason": reason,
|
reason: reason,
|
||||||
"valid": true,
|
valid: false,
|
||||||
"polymorphic_ctype": type,
|
invalidity_reason: 'Solde insuffisant',
|
||||||
"resourcetype": "RecurrentTransaction",
|
polymorphic_ctype: type,
|
||||||
"source": source.id,
|
resourcetype: 'RecurrentTransaction',
|
||||||
"source_alias": source_alias,
|
source: source,
|
||||||
"destination": dest,
|
source_alias: source_alias,
|
||||||
"template": template
|
destination: dest,
|
||||||
})
|
template: template
|
||||||
.done(function () {
|
}).done(function () {
|
||||||
if (!isNaN(source.balance)) {
|
reset()
|
||||||
let newBalance = source.balance - quantity * amount;
|
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", 'danger', 10000)
|
||||||
if (newBalance <= -5000)
|
}).fail(function () {
|
||||||
addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " +
|
reset()
|
||||||
"succès, mais la note émettrice " + source_alias + " est en négatif sévère.",
|
errMsg(e.responseJSON)
|
||||||
"danger", 30000);
|
})
|
||||||
else if (newBalance < 0)
|
})
|
||||||
addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " +
|
|
||||||
"succès, mais la note émettrice " + source_alias + " est en négatif.",
|
|
||||||
"warning", 30000);
|
|
||||||
if (source.membership && source.membership.date_end < new Date().toISOString())
|
|
||||||
addMsg("Attention : la note émettrice " + source.name + " n'est plus adhérente.",
|
|
||||||
"danger", 30000);
|
|
||||||
}
|
|
||||||
reset();
|
|
||||||
}).fail(function (e) {
|
|
||||||
$.post("/api/note/transaction/transaction/",
|
|
||||||
{
|
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
|
||||||
"quantity": quantity,
|
|
||||||
"amount": amount,
|
|
||||||
"reason": reason,
|
|
||||||
"valid": false,
|
|
||||||
"invalidity_reason": "Solde insuffisant",
|
|
||||||
"polymorphic_ctype": type,
|
|
||||||
"resourcetype": "RecurrentTransaction",
|
|
||||||
"source": source,
|
|
||||||
"source_alias": source_alias,
|
|
||||||
"destination": dest,
|
|
||||||
"template": template
|
|
||||||
}).done(function() {
|
|
||||||
reset();
|
|
||||||
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", "danger", 10000);
|
|
||||||
}).fail(function () {
|
|
||||||
reset();
|
|
||||||
errMsg(e.responseJSON);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,241 +9,240 @@
|
||||||
* Licensed under the New BSD License
|
* Licensed under the New BSD License
|
||||||
* See: http://www.opensource.org/licenses/bsd-license.php
|
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||||
*/
|
*/
|
||||||
;(function($) {
|
;(function ($) {
|
||||||
$.fn.formset = function(opts)
|
$.fn.formset = function (opts) {
|
||||||
{
|
var options = $.extend({}, $.fn.formset.defaults, opts)
|
||||||
var options = $.extend({}, $.fn.formset.defaults, opts),
|
var flatExtraClasses = options.extraClasses.join(' ')
|
||||||
flatExtraClasses = options.extraClasses.join(' '),
|
var totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS')
|
||||||
totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
|
var maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS')
|
||||||
maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
|
var minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS')
|
||||||
minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
|
var childElementSelector = 'input,select,textarea,label,div'
|
||||||
childElementSelector = 'input,select,textarea,label,div',
|
var $$ = $(this)
|
||||||
$$ = $(this),
|
|
||||||
|
|
||||||
applyExtraClasses = function(row, ndx) {
|
var applyExtraClasses = function (row, ndx) {
|
||||||
if (options.extraClasses) {
|
if (options.extraClasses) {
|
||||||
row.removeClass(flatExtraClasses);
|
row.removeClass(flatExtraClasses)
|
||||||
row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
|
row.addClass(options.extraClasses[ndx % options.extraClasses.length])
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
updateElementIndex = function(elem, prefix, ndx) {
|
var updateElementIndex = function (elem, prefix, ndx) {
|
||||||
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'),
|
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-')
|
||||||
replacement = prefix + '-' + ndx + '-';
|
var replacement = prefix + '-' + ndx + '-'
|
||||||
if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
|
if (elem.attr('for')) elem.attr('for', elem.attr('for').replace(idRegex, replacement))
|
||||||
if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
|
if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement))
|
||||||
if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
|
if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement))
|
||||||
},
|
}
|
||||||
|
|
||||||
hasChildElements = function(row) {
|
var hasChildElements = function (row) {
|
||||||
return row.find(childElementSelector).length > 0;
|
return row.find(childElementSelector).length > 0
|
||||||
},
|
}
|
||||||
|
|
||||||
showAddButton = function() {
|
var showAddButton = function () {
|
||||||
return maxForms.length == 0 || // For Django versions pre 1.2
|
return maxForms.length === 0 || // For Django versions pre 1.2
|
||||||
(maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
|
(maxForms.val() === '' || (maxForms.val() - totalForms.val() > 0))
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether delete link(s) can be displayed - when total forms > min forms
|
* Indicates whether delete link(s) can be displayed - when total forms > min forms
|
||||||
*/
|
*/
|
||||||
showDeleteLinks = function() {
|
var showDeleteLinks = function () {
|
||||||
return minForms.length == 0 || // For Django versions pre 1.7
|
return minForms.length === 0 || // For Django versions pre 1.7
|
||||||
(minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
|
(minForms.val() === '' || (totalForms.val() - minForms.val() > 0))
|
||||||
},
|
}
|
||||||
|
|
||||||
insertDeleteLink = function(row) {
|
var insertDeleteLink = function (row) {
|
||||||
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.')
|
||||||
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
var addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.')
|
||||||
|
|
||||||
var delButtonHTML = '<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>';
|
var delButtonHTML = '<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + '</a>'
|
||||||
if (options.deleteContainerClass) {
|
if (options.deleteContainerClass) {
|
||||||
// If we have a specific container for the remove button,
|
// If we have a specific container for the remove button,
|
||||||
// place it as the last child of that container:
|
// place it as the last child of that container:
|
||||||
row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML);
|
row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML)
|
||||||
} else if (row.is('TR')) {
|
} else if (row.is('TR')) {
|
||||||
// If the forms are laid out in table rows, insert
|
// If the forms are laid out in table rows, insert
|
||||||
// the remove button into the last table cell:
|
// the remove button into the last table cell:
|
||||||
row.children('td:last').append(delButtonHTML);
|
row.children('td:last').append(delButtonHTML)
|
||||||
} else if (row.is('UL') || row.is('OL')) {
|
} else if (row.is('UL') || row.is('OL')) {
|
||||||
// If they're laid out as an ordered/unordered list,
|
// If they're laid out as an ordered/unordered list,
|
||||||
// insert an <li> after the last list item:
|
// insert an <li> after the last list item:
|
||||||
row.append('<li>' + delButtonHTML + '</li>');
|
row.append('<li>' + delButtonHTML + '</li>')
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, just insert the remove button as the
|
// Otherwise, just insert the remove button as the
|
||||||
// last child element of the form's container:
|
// last child element of the form's container:
|
||||||
row.append(delButtonHTML);
|
row.append(delButtonHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
||||||
if (!showDeleteLinks()){
|
if (!showDeleteLinks()) {
|
||||||
row.find('a.' + delCssSelector).hide();
|
row.find('a.' + delCssSelector).hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
row.find('a.' + delCssSelector).click(function() {
|
row.find('a.' + delCssSelector).click(function () {
|
||||||
var row = $(this).parents('.' + options.formCssClass),
|
var row = $(this).parents('.' + options.formCssClass)
|
||||||
del = row.find('input:hidden[id $= "-DELETE"]'),
|
var del = row.find('input:hidden[id $= "-DELETE"]')
|
||||||
buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
|
var buttonRow = row.siblings('a.' + addCssSelector + ', .' + options.formCssClass + '-add')
|
||||||
forms;
|
var forms
|
||||||
if (del.length) {
|
if (del.length) {
|
||||||
// We're dealing with an inline formset.
|
// We're dealing with an inline formset.
|
||||||
// Rather than remove this form from the DOM, we'll mark it as deleted
|
// Rather than remove this form from the DOM, we'll mark it as deleted
|
||||||
// and hide it, then let Django handle the deleting:
|
// and hide it, then let Django handle the deleting:
|
||||||
del.val('on');
|
del.val('on')
|
||||||
row.hide();
|
row.hide()
|
||||||
forms = $('.' + options.formCssClass).not(':hidden');
|
forms = $('.' + options.formCssClass).not(':hidden')
|
||||||
} else {
|
} else {
|
||||||
row.remove();
|
row.remove()
|
||||||
// Update the TOTAL_FORMS count:
|
// Update the TOTAL_FORMS count:
|
||||||
forms = $('.' + options.formCssClass).not('.formset-custom-template');
|
forms = $('.' + options.formCssClass).not('.formset-custom-template')
|
||||||
totalForms.val(forms.length);
|
totalForms.val(forms.length)
|
||||||
}
|
|
||||||
for (var i=0, formCount=forms.length; i<formCount; i++) {
|
|
||||||
// Apply `extraClasses` to form rows so they're nicely alternating:
|
|
||||||
applyExtraClasses(forms.eq(i), i);
|
|
||||||
if (!del.length) {
|
|
||||||
// Also update names and IDs for all child controls (if this isn't
|
|
||||||
// a delete-able inline formset) so they remain in sequence:
|
|
||||||
forms.eq(i).find(childElementSelector).each(function() {
|
|
||||||
updateElementIndex($(this), options.prefix, i);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if we've reached the minimum number of forms - hide all delete link(s)
|
|
||||||
if (!showDeleteLinks()){
|
|
||||||
$('a.' + delCssSelector).each(function(){$(this).hide();});
|
|
||||||
}
|
|
||||||
// Check if we need to show the add button:
|
|
||||||
if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show();
|
|
||||||
// If a post-delete callback was provided, call it with the deleted form:
|
|
||||||
if (options.removed) options.removed(row);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$$.each(function(i) {
|
|
||||||
var row = $(this),
|
|
||||||
del = row.find('input:checkbox[id $= "-DELETE"]');
|
|
||||||
if (del.length) {
|
|
||||||
// If you specify "can_delete = True" when creating an inline formset,
|
|
||||||
// Django adds a checkbox to each form in the formset.
|
|
||||||
// Replace the default checkbox with a hidden field:
|
|
||||||
if (del.is(':checked')) {
|
|
||||||
// If an inline formset containing deleted forms fails validation, make sure
|
|
||||||
// we keep the forms hidden (thanks for the bug report and suggested fix Mike)
|
|
||||||
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />');
|
|
||||||
row.hide();
|
|
||||||
} else {
|
|
||||||
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
|
|
||||||
}
|
|
||||||
// Hide any labels associated with the DELETE checkbox:
|
|
||||||
$('label[for="' + del.attr('id') + '"]').hide();
|
|
||||||
del.remove();
|
|
||||||
}
|
|
||||||
if (hasChildElements(row)) {
|
|
||||||
row.addClass(options.formCssClass);
|
|
||||||
if (row.is(':visible')) {
|
|
||||||
insertDeleteLink(row);
|
|
||||||
applyExtraClasses(row, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($$.length) {
|
|
||||||
var hideAddButton = !showAddButton(),
|
|
||||||
addButton, template;
|
|
||||||
if (options.formTemplate) {
|
|
||||||
// If a form template was specified, we'll clone it to generate new form instances:
|
|
||||||
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
|
|
||||||
template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
|
|
||||||
template.find(childElementSelector).each(function() {
|
|
||||||
updateElementIndex($(this), options.prefix, '__prefix__');
|
|
||||||
});
|
|
||||||
insertDeleteLink(template);
|
|
||||||
} else {
|
|
||||||
// Otherwise, use the last form in the formset; this works much better if you've got
|
|
||||||
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
|
||||||
if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide();
|
|
||||||
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
|
|
||||||
template.find('input:hidden[id $= "-DELETE"]').remove();
|
|
||||||
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
|
||||||
template.find(childElementSelector).not(options.keepFieldValues).each(function() {
|
|
||||||
var elem = $(this);
|
|
||||||
// If this is a checkbox or radiobutton, uncheck it.
|
|
||||||
// This fixes Issue 1, reported by Wilson.Andrew.J:
|
|
||||||
if (elem.is('input:checkbox') || elem.is('input:radio')) {
|
|
||||||
elem.attr('checked', false);
|
|
||||||
} else {
|
|
||||||
elem.val('');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// FIXME: Perhaps using $.data would be a better idea?
|
|
||||||
options.formTemplate = template;
|
|
||||||
|
|
||||||
var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>';
|
|
||||||
if (options.addContainerClass) {
|
|
||||||
// If we have a specific container for the "add" button,
|
|
||||||
// place it as the last child of that container:
|
|
||||||
var addContainer = $('[class*="' + options.addContainerClass + '"');
|
|
||||||
addContainer.append(addButtonHTML);
|
|
||||||
addButton = addContainer.find('[class="' + options.addCssClass + '"]');
|
|
||||||
} else if ($$.is('TR')) {
|
|
||||||
// If forms are laid out as table rows, insert the
|
|
||||||
// "add" button in a new table row:
|
|
||||||
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
|
|
||||||
buttonRow = $('<tr><td colspan="' + numCols + '">' + addButtonHTML + '</tr>').addClass(options.formCssClass + '-add');
|
|
||||||
$$.parent().append(buttonRow);
|
|
||||||
addButton = buttonRow.find('a');
|
|
||||||
} else {
|
|
||||||
// Otherwise, insert it immediately after the last form:
|
|
||||||
$$.filter(':last').after(addButtonHTML);
|
|
||||||
addButton = $$.filter(':last').next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hideAddButton) addButton.hide();
|
|
||||||
|
|
||||||
addButton.click(function() {
|
|
||||||
var formCount = parseInt(totalForms.val()),
|
|
||||||
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
|
|
||||||
buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this),
|
|
||||||
delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.');
|
|
||||||
applyExtraClasses(row, formCount);
|
|
||||||
row.insertBefore(buttonRow).show();
|
|
||||||
row.find(childElementSelector).each(function() {
|
|
||||||
updateElementIndex($(this), options.prefix, formCount);
|
|
||||||
});
|
|
||||||
totalForms.val(formCount + 1);
|
|
||||||
// Check if we're above the minimum allowed number of forms -> show all delete link(s)
|
|
||||||
if (showDeleteLinks()){
|
|
||||||
$('a.' + delCssSelector).each(function(){$(this).show();});
|
|
||||||
}
|
|
||||||
// Check if we've exceeded the maximum allowed number of forms:
|
|
||||||
if (!showAddButton()) buttonRow.hide();
|
|
||||||
// If a post-add callback was supplied, call it with the added form:
|
|
||||||
if (options.added) options.added(row);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
for (var i = 0, formCount = forms.length; i < formCount; i++) {
|
||||||
|
// Apply `extraClasses` to form rows so they're nicely alternating:
|
||||||
|
applyExtraClasses(forms.eq(i), i)
|
||||||
|
if (!del.length) {
|
||||||
|
// Also update names and IDs for all child controls (if this isn't
|
||||||
|
// a delete-able inline formset) so they remain in sequence:
|
||||||
|
forms.eq(i).find(childElementSelector).each(function () {
|
||||||
|
updateElementIndex($(this), options.prefix, i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if we've reached the minimum number of forms - hide all delete link(s)
|
||||||
|
if (!showDeleteLinks()) {
|
||||||
|
$('a.' + delCssSelector).each(function () { $(this).hide() })
|
||||||
|
}
|
||||||
|
// Check if we need to show the add button:
|
||||||
|
if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show()
|
||||||
|
// If a post-delete callback was provided, call it with the deleted form:
|
||||||
|
if (options.removed) options.removed(row)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return $$;
|
$$.each(function (i) {
|
||||||
};
|
var row = $(this)
|
||||||
|
var del = row.find('input:checkbox[id $= "-DELETE"]')
|
||||||
|
if (del.length) {
|
||||||
|
// If you specify "can_delete = True" when creating an inline formset,
|
||||||
|
// Django adds a checkbox to each form in the formset.
|
||||||
|
// Replace the default checkbox with a hidden field:
|
||||||
|
if (del.is(':checked')) {
|
||||||
|
// If an inline formset containing deleted forms fails validation, make sure
|
||||||
|
// we keep the forms hidden (thanks for the bug report and suggested fix Mike)
|
||||||
|
del.before('<input type="hidden" name="' + del.attr('name') + '" id="' + del.attr('id') + '" value="on" />')
|
||||||
|
row.hide()
|
||||||
|
} else {
|
||||||
|
del.before('<input type="hidden" name="' + del.attr('name') + '" id="' + del.attr('id') + '" />')
|
||||||
|
}
|
||||||
|
// Hide any labels associated with the DELETE checkbox:
|
||||||
|
$('label[for="' + del.attr('id') + '"]').hide()
|
||||||
|
del.remove()
|
||||||
|
}
|
||||||
|
if (hasChildElements(row)) {
|
||||||
|
row.addClass(options.formCssClass)
|
||||||
|
if (row.is(':visible')) {
|
||||||
|
insertDeleteLink(row)
|
||||||
|
applyExtraClasses(row, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/* Setup plugin defaults */
|
if ($$.length) {
|
||||||
$.fn.formset.defaults = {
|
var hideAddButton = !showAddButton()
|
||||||
prefix: 'form', // The form prefix for your django formset
|
var addButton; var template
|
||||||
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
if (options.formTemplate) {
|
||||||
addText: 'add another', // Text for the add link
|
// If a form template was specified, we'll clone it to generate new form instances:
|
||||||
deleteText: 'remove', // Text for the delete link
|
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate)
|
||||||
addContainerClass: null, // Container CSS class for the add link
|
template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template')
|
||||||
deleteContainerClass: null, // Container CSS class for the delete link
|
template.find(childElementSelector).each(function () {
|
||||||
addCssClass: 'add-row', // CSS class applied to the add link
|
updateElementIndex($(this), options.prefix, '__prefix__')
|
||||||
deleteCssClass: 'delete-row', // CSS class applied to the delete link
|
})
|
||||||
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
insertDeleteLink(template)
|
||||||
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
} else {
|
||||||
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
// Otherwise, use the last form in the formset; this works much better if you've got
|
||||||
added: null, // Function called each time a new form is added
|
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
||||||
removed: null, // Function called each time a form is deleted
|
if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide()
|
||||||
hideLastAddForm: false // When set to true, hide last empty add form (becomes visible when clicking on add button)
|
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id')
|
||||||
};
|
template.find('input:hidden[id $= "-DELETE"]').remove()
|
||||||
})(jQuery);
|
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
||||||
|
template.find(childElementSelector).not(options.keepFieldValues).each(function () {
|
||||||
|
var elem = $(this)
|
||||||
|
// If this is a checkbox or radiobutton, uncheck it.
|
||||||
|
// This fixes Issue 1, reported by Wilson.Andrew.J:
|
||||||
|
if (elem.is('input:checkbox') || elem.is('input:radio')) {
|
||||||
|
elem.attr('checked', false)
|
||||||
|
} else {
|
||||||
|
elem.val('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// FIXME: Perhaps using $.data would be a better idea?
|
||||||
|
options.formTemplate = template
|
||||||
|
|
||||||
|
var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>'
|
||||||
|
if (options.addContainerClass) {
|
||||||
|
// If we have a specific container for the "add" button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
var addContainer = $('[class*="' + options.addContainerClass + '"')
|
||||||
|
addContainer.append(addButtonHTML)
|
||||||
|
addButton = addContainer.find('[class="' + options.addCssClass + '"]')
|
||||||
|
} else if ($$.is('TR')) {
|
||||||
|
// If forms are laid out as table rows, insert the
|
||||||
|
// "add" button in a new table row:
|
||||||
|
var numCols = $$.eq(0).children().length // This is a bit of an assumption :|
|
||||||
|
var buttonRow = $('<tr><td colspan="' + numCols + '">' + addButtonHTML + '</tr>').addClass(options.formCssClass + '-add')
|
||||||
|
$$.parent().append(buttonRow)
|
||||||
|
addButton = buttonRow.find('a')
|
||||||
|
} else {
|
||||||
|
// Otherwise, insert it immediately after the last form:
|
||||||
|
$$.filter(':last').after(addButtonHTML)
|
||||||
|
addButton = $$.filter(':last').next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideAddButton) addButton.hide()
|
||||||
|
|
||||||
|
addButton.click(function () {
|
||||||
|
var formCount = parseInt(totalForms.val())
|
||||||
|
var row = options.formTemplate.clone(true).removeClass('formset-custom-template')
|
||||||
|
var buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this)
|
||||||
|
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.')
|
||||||
|
applyExtraClasses(row, formCount)
|
||||||
|
row.insertBefore(buttonRow).show()
|
||||||
|
row.find(childElementSelector).each(function () {
|
||||||
|
updateElementIndex($(this), options.prefix, formCount)
|
||||||
|
})
|
||||||
|
totalForms.val(formCount + 1)
|
||||||
|
// Check if we're above the minimum allowed number of forms -> show all delete link(s)
|
||||||
|
if (showDeleteLinks()) {
|
||||||
|
$('a.' + delCssSelector).each(function () { $(this).show() })
|
||||||
|
}
|
||||||
|
// Check if we've exceeded the maximum allowed number of forms:
|
||||||
|
if (!showAddButton()) buttonRow.hide()
|
||||||
|
// If a post-add callback was supplied, call it with the added form:
|
||||||
|
if (options.added) options.added(row)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return $$
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup plugin defaults */
|
||||||
|
$.fn.formset.defaults = {
|
||||||
|
prefix: 'form', // The form prefix for your django formset
|
||||||
|
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
||||||
|
addText: 'add another', // Text for the add link
|
||||||
|
deleteText: 'remove', // Text for the delete link
|
||||||
|
addContainerClass: null, // Container CSS class for the add link
|
||||||
|
deleteContainerClass: null, // Container CSS class for the delete link
|
||||||
|
addCssClass: 'add-row', // CSS class applied to the add link
|
||||||
|
deleteCssClass: 'delete-row', // CSS class applied to the delete link
|
||||||
|
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
||||||
|
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
||||||
|
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
||||||
|
added: null, // Function called each time a new form is added
|
||||||
|
removed: null, // Function called each time a form is deleted
|
||||||
|
hideLastAddForm: false // When set to true, hide last empty add form (becomes visible when clicking on add button)
|
||||||
|
}
|
||||||
|
})(jQuery)
|
||||||
|
|
|
@ -6,40 +6,40 @@
|
||||||
let cursor = 0
|
let cursor = 0
|
||||||
const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]
|
const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]
|
||||||
|
|
||||||
function afterKonami() {
|
function afterKonami () {
|
||||||
// Load Rythm.js
|
// Load Rythm.js
|
||||||
var rythmScript = document.createElement('script')
|
var rythmScript = document.createElement('script')
|
||||||
rythmScript.setAttribute('src','//unpkg.com/rythm.js@2.2.5/rythm.min.js')
|
rythmScript.setAttribute('src', '//unpkg.com/rythm.js@2.2.5/rythm.min.js')
|
||||||
document.head.appendChild(rythmScript)
|
document.head.appendChild(rythmScript)
|
||||||
|
|
||||||
rythmScript.addEventListener('load', function() {
|
rythmScript.addEventListener('load', function () {
|
||||||
// Ker-Lyon audio courtesy of @adalan, ker-lyon.fr
|
// Ker-Lyon audio courtesy of @adalan, ker-lyon.fr
|
||||||
const audioElement = new Audio('/static/song/konami.ogg')
|
const audioElement = new Audio('/static/song/konami.ogg')
|
||||||
audioElement.loop = true
|
audioElement.loop = true
|
||||||
audioElement.play()
|
audioElement.play()
|
||||||
|
|
||||||
const rythm = new Rythm()
|
const rythm = new Rythm()
|
||||||
rythm.connectExternalAudioElement(audioElement)
|
rythm.connectExternalAudioElement(audioElement)
|
||||||
rythm.addRythm('card', 'pulse', 50, 50, {
|
rythm.addRythm('card', 'pulse', 50, 50, {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 1.1
|
max: 1.1
|
||||||
})
|
})
|
||||||
rythm.addRythm('d-flex', 'color', 50, 50, {
|
rythm.addRythm('d-flex', 'color', 50, 50, {
|
||||||
from: [64,64,64],
|
from: [64, 64, 64],
|
||||||
to:[128,64,128]
|
to: [128, 64, 128]
|
||||||
})
|
})
|
||||||
rythm.addRythm('nav-link', 'jump', 150, 50, {
|
rythm.addRythm('nav-link', 'jump', 150, 50, {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 10
|
max: 10
|
||||||
})
|
})
|
||||||
rythm.start()
|
rythm.start()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register custom event
|
// Register custom event
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
cursor = (e.keyCode == KONAMI_CODE[cursor]) ? cursor + 1 : 0;
|
cursor = (e.keyCode === KONAMI_CODE[cursor]) ? cursor + 1 : 0
|
||||||
if (cursor == KONAMI_CODE.length) {
|
if (cursor === KONAMI_CODE.length) {
|
||||||
afterKonami()
|
afterKonami()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,440 +1,422 @@
|
||||||
var LOCK = false;
|
var LOCK = false
|
||||||
|
|
||||||
sources = [];
|
sources = []
|
||||||
sources_notes_display = [];
|
sources_notes_display = []
|
||||||
dests = [];
|
dests = []
|
||||||
dests_notes_display = [];
|
dests_notes_display = []
|
||||||
|
|
||||||
function refreshHistory() {
|
function refreshHistory () {
|
||||||
$("#history").load("/note/transfer/ #history");
|
$('#history').load('/note/transfer/ #history')
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset(refresh=true) {
|
function reset (refresh = true) {
|
||||||
sources_notes_display.length = 0;
|
sources_notes_display.length = 0
|
||||||
sources.length = 0;
|
sources.length = 0
|
||||||
dests_notes_display.length = 0;
|
dests_notes_display.length = 0
|
||||||
dests.length = 0;
|
dests.length = 0
|
||||||
$("#source_note_list").html("");
|
$('#source_note_list').html('')
|
||||||
$("#dest_note_list").html("");
|
$('#dest_note_list').html('')
|
||||||
let source_field = $("#source_note");
|
const source_field = $('#source_note')
|
||||||
source_field.val("");
|
source_field.val('')
|
||||||
let event = jQuery.Event("keyup");
|
const event = jQuery.Event('keyup')
|
||||||
event.originalEvent = {charCode: 97};
|
event.originalEvent = { charCode: 97 }
|
||||||
source_field.trigger(event);
|
source_field.trigger(event)
|
||||||
source_field.removeClass('is-invalid');
|
source_field.removeClass('is-invalid')
|
||||||
source_field.attr("data-original-title", "").tooltip("hide");
|
source_field.attr('data-original-title', '').tooltip('hide')
|
||||||
let dest_field = $("#dest_note");
|
const dest_field = $('#dest_note')
|
||||||
dest_field.val("");
|
dest_field.val('')
|
||||||
dest_field.trigger(event);
|
dest_field.trigger(event)
|
||||||
dest_field.removeClass('is-invalid');
|
dest_field.removeClass('is-invalid')
|
||||||
dest_field.attr("data-original-title", "").tooltip("hide");
|
dest_field.attr('data-original-title', '').tooltip('hide')
|
||||||
let amount_field = $("#amount");
|
const amount_field = $('#amount')
|
||||||
amount_field.val("");
|
amount_field.val('')
|
||||||
amount_field.removeClass('is-invalid');
|
amount_field.removeClass('is-invalid')
|
||||||
$("#amount-required").html("");
|
$('#amount-required').html('')
|
||||||
let reason_field = $("#reason");
|
const reason_field = $('#reason')
|
||||||
reason_field.val("");
|
reason_field.val('')
|
||||||
reason_field.removeClass('is-invalid');
|
reason_field.removeClass('is-invalid')
|
||||||
$("#reason-required").html("");
|
$('#reason-required').html('')
|
||||||
$("#last_name").val("");
|
$('#last_name').val('')
|
||||||
$("#first_name").val("");
|
$('#first_name').val('')
|
||||||
$("#bank").val("");
|
$('#bank').val('')
|
||||||
$("#user_note").val("");
|
$('#user_note').val('')
|
||||||
$("#profile_pic").attr("src", "/static/member/img/default_picture.png");
|
$('#profile_pic').attr('src', '/static/member/img/default_picture.png')
|
||||||
$("#profile_pic_link").attr("href", "#");
|
$('#profile_pic_link').attr('href', '#')
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
refreshBalance();
|
refreshBalance()
|
||||||
refreshHistory();
|
refreshHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK = false;
|
LOCK = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function () {
|
||||||
/**
|
/**
|
||||||
* If we are in credit/debit mode, check that only one note is entered.
|
* If we are in credit/debit mode, check that only one note is entered.
|
||||||
* More over, get first name and last name to autocomplete fields.
|
* More over, get first name and last name to autocomplete fields.
|
||||||
*/
|
*/
|
||||||
function checkUniqueNote() {
|
function checkUniqueNote () {
|
||||||
if ($("#type_credit").is(":checked") || $("#type_debit").is(":checked")) {
|
if ($('#type_credit').is(':checked') || $('#type_debit').is(':checked')) {
|
||||||
let arr = $("#type_credit").is(":checked") ? dests_notes_display : sources_notes_display;
|
const arr = $('#type_credit').is(':checked') ? dests_notes_display : sources_notes_display
|
||||||
|
|
||||||
if (arr.length === 0)
|
if (arr.length === 0) { return }
|
||||||
return;
|
|
||||||
|
|
||||||
let last = arr[arr.length - 1];
|
const last = arr[arr.length - 1]
|
||||||
arr.length = 0;
|
arr.length = 0
|
||||||
arr.push(last);
|
arr.push(last)
|
||||||
|
|
||||||
last.quantity = 1;
|
last.quantity = 1
|
||||||
|
|
||||||
if (!last.note.user) {
|
if (!last.note.user) {
|
||||||
$.getJSON("/api/note/note/" + last.note.id + "/?format=json", function(note) {
|
$.getJSON('/api/note/note/' + last.note.id + '/?format=json', function (note) {
|
||||||
last.note.user = note.user;
|
last.note.user = note.user
|
||||||
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
$.getJSON('/api/user/' + last.note.user + '/', function (user) {
|
||||||
$("#last_name").val(user.last_name);
|
$('#last_name').val(user.last_name)
|
||||||
$("#first_name").val(user.first_name);
|
$('#first_name').val(user.first_name)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
} else {
|
||||||
|
$.getJSON('/api/user/' + last.note.user + '/', function (user) {
|
||||||
|
$('#last_name').val(user.last_name)
|
||||||
|
$('#first_name').val(user.first_name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
autoCompleteNote('source_note', 'source_note_list', sources, sources_notes_display,
|
||||||
|
'source_alias', 'source_note', 'user_note', 'profile_pic', checkUniqueNote)
|
||||||
|
autoCompleteNote('dest_note', 'dest_note_list', dests, dests_notes_display,
|
||||||
|
'dest_alias', 'dest_note', 'user_note', 'profile_pic', checkUniqueNote)
|
||||||
|
|
||||||
|
const source = $('#source_note')
|
||||||
|
const dest = $('#dest_note')
|
||||||
|
|
||||||
|
$('#type_transfer').change(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
|
||||||
|
$('#source_me_div').removeClass('d-none')
|
||||||
|
$('#source_note').removeClass('is-invalid')
|
||||||
|
$('#dest_note').removeClass('is-invalid')
|
||||||
|
$('#special_transaction_div').addClass('d-none')
|
||||||
|
source.removeClass('d-none')
|
||||||
|
$('#source_note_list').removeClass('d-none')
|
||||||
|
$('#credit_type').addClass('d-none')
|
||||||
|
dest.removeClass('d-none')
|
||||||
|
$('#dest_note_list').removeClass('d-none')
|
||||||
|
$('#debit_type').addClass('d-none')
|
||||||
|
|
||||||
|
$('#source_note_label').text(select_emitters_label)
|
||||||
|
$('#dest_note_label').text(select_receveirs_label)
|
||||||
|
|
||||||
|
location.hash = 'transfer'
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#type_credit').change(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
|
||||||
|
$('#source_me_div').addClass('d-none')
|
||||||
|
$('#source_note').removeClass('is-invalid')
|
||||||
|
$('#dest_note').removeClass('is-invalid')
|
||||||
|
$('#special_transaction_div').removeClass('d-none')
|
||||||
|
$('#source_note_list').addClass('d-none')
|
||||||
|
$('#dest_note_list').removeClass('d-none')
|
||||||
|
source.addClass('d-none')
|
||||||
|
source.tooltip('hide')
|
||||||
|
$('#credit_type').removeClass('d-none')
|
||||||
|
dest.removeClass('d-none')
|
||||||
|
dest.val('')
|
||||||
|
dest.tooltip('hide')
|
||||||
|
$('#debit_type').addClass('d-none')
|
||||||
|
|
||||||
|
$('#source_note_label').text(transfer_type_label)
|
||||||
|
$('#dest_note_label').text(select_receveir_label)
|
||||||
|
|
||||||
|
if (dests_notes_display.length > 1) {
|
||||||
|
$('#dest_note_list').html('')
|
||||||
|
dests_notes_display.length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
location.hash = 'credit'
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#type_debit').change(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
|
||||||
|
$('#source_me_div').addClass('d-none')
|
||||||
|
$('#source_note').removeClass('is-invalid')
|
||||||
|
$('#dest_note').removeClass('is-invalid')
|
||||||
|
$('#special_transaction_div').removeClass('d-none')
|
||||||
|
$('#source_note_list').removeClass('d-none')
|
||||||
|
$('#dest_note_list').addClass('d-none')
|
||||||
|
source.removeClass('d-none')
|
||||||
|
source.val('')
|
||||||
|
source.tooltip('hide')
|
||||||
|
$('#credit_type').addClass('d-none')
|
||||||
|
dest.addClass('d-none')
|
||||||
|
dest.tooltip('hide')
|
||||||
|
$('#debit_type').removeClass('d-none')
|
||||||
|
|
||||||
|
$('#source_note_label').text(select_emitter_label)
|
||||||
|
$('#dest_note_label').text(transfer_type_label)
|
||||||
|
|
||||||
|
if (sources_notes_display.length > 1) {
|
||||||
|
$('#source_note_list').html('')
|
||||||
|
sources_notes_display.length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
location.hash = 'debit'
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#credit_type').change(function () {
|
||||||
|
const type = $('#credit_type option:selected').text()
|
||||||
|
if ($('#type_credit').is(':checked')) { source.val(type) } else { dest.val(type) }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ensure we begin in transfer mode. Removing these lines may cause problems when reloading.
|
||||||
|
const type_transfer = $('#type_transfer') // Default mode
|
||||||
|
type_transfer.removeAttr('checked')
|
||||||
|
$('#type_credit').removeAttr('checked')
|
||||||
|
$('#type_debit').removeAttr('checked')
|
||||||
|
|
||||||
|
if (location.hash) { $('#type_' + location.hash.substr(1)).click() } else { type_transfer.click() }
|
||||||
|
|
||||||
|
$('#source_me').click(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
|
||||||
|
// Shortcut to set the current user as the only emitter
|
||||||
|
sources_notes_display.length = 0
|
||||||
|
sources.length = 0
|
||||||
|
$('#source_note_list').html('')
|
||||||
|
|
||||||
|
const source_note = $('#source_note')
|
||||||
|
source_note.focus()
|
||||||
|
source_note.val('')
|
||||||
|
let event = jQuery.Event('keyup')
|
||||||
|
event.originalEvent = { charCode: 97 }
|
||||||
|
source_note.trigger(event)
|
||||||
|
source_note.val(username)
|
||||||
|
event = jQuery.Event('keyup')
|
||||||
|
event.originalEvent = { charCode: 97 }
|
||||||
|
source_note.trigger(event)
|
||||||
|
const fill_note = function () {
|
||||||
|
if (sources.length === 0) {
|
||||||
|
setTimeout(fill_note, 100)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event = jQuery.Event('keypress')
|
||||||
|
event.originalEvent = { charCode: 13 }
|
||||||
|
source_note.trigger(event)
|
||||||
|
|
||||||
|
source_note.tooltip('hide')
|
||||||
|
source_note.val('')
|
||||||
|
$('#dest_note').focus()
|
||||||
|
}
|
||||||
|
fill_note()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#btn_transfer').click(function () {
|
||||||
|
if (LOCK) { return }
|
||||||
|
|
||||||
|
LOCK = true
|
||||||
|
|
||||||
|
let error = false
|
||||||
|
|
||||||
|
const amount_field = $('#amount')
|
||||||
|
amount_field.removeClass('is-invalid')
|
||||||
|
$('#amount-required').html('')
|
||||||
|
|
||||||
|
const reason_field = $('#reason')
|
||||||
|
reason_field.removeClass('is-invalid')
|
||||||
|
$('#reason-required').html('')
|
||||||
|
|
||||||
|
if (!amount_field.val() || isNaN(amount_field.val()) || amount_field.val() <= 0) {
|
||||||
|
amount_field.addClass('is-invalid')
|
||||||
|
$('#amount-required').html('<strong>Ce champ est requis et doit comporter un nombre décimal strictement positif.</strong>')
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = Math.floor(100 * amount_field.val())
|
||||||
|
if (amount > 2147483647) {
|
||||||
|
amount_field.addClass('is-invalid')
|
||||||
|
$('#amount-required').html('<strong>Le montant ne doit pas excéder 21474836.47 €.</strong>')
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reason_field.val()) {
|
||||||
|
reason_field.addClass('is-invalid')
|
||||||
|
$('#reason-required').html('<strong>Ce champ est requis.</strong>')
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sources_notes_display.length && !$('#type_credit').is(':checked')) {
|
||||||
|
$('#source_note').addClass('is-invalid')
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dests_notes_display.length && !$('#type_debit').is(':checked')) {
|
||||||
|
$('#dest_note').addClass('is-invalid')
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
LOCK = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let reason = reason_field.val()
|
||||||
|
|
||||||
|
if ($('#type_transfer').is(':checked')) {
|
||||||
|
// We copy the arrays to ensure that transactions are well-processed even if the form is reset
|
||||||
|
[...sources_notes_display].forEach(function (source) {
|
||||||
|
[...dests_notes_display].forEach(function (dest) {
|
||||||
|
if (source.note.id === dest.note.id) {
|
||||||
|
addMsg('Attention : la transaction de ' + pretty_money(amount) + ' de la note ' + source.name +
|
||||||
|
' vers la note ' + dest.name + " n'a pas été faite car il s'agit de la même note au départ" +
|
||||||
|
" et à l'arrivée.", 'warning', 10000)
|
||||||
|
LOCK = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post('/api/note/transaction/transaction/',
|
||||||
|
{
|
||||||
|
csrfmiddlewaretoken: CSRF_TOKEN,
|
||||||
|
quantity: source.quantity * dest.quantity,
|
||||||
|
amount: amount,
|
||||||
|
reason: reason,
|
||||||
|
valid: true,
|
||||||
|
polymorphic_ctype: TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
resourcetype: 'Transaction',
|
||||||
|
source: source.note.id,
|
||||||
|
source_alias: source.name,
|
||||||
|
destination: dest.note.id,
|
||||||
|
destination_alias: dest.name
|
||||||
|
}).done(function () {
|
||||||
|
if (source.note.membership && source.note.membership.date_end < new Date().toISOString()) {
|
||||||
|
addMsg('Attention : la note émettrice ' + source.name + " n'est plus adhérente.",
|
||||||
|
'danger', 30000)
|
||||||
|
}
|
||||||
|
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString()) {
|
||||||
|
addMsg('Attention : la note destination ' + dest.name + " n'est plus adhérente.",
|
||||||
|
'danger', 30000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNaN(source.note.balance)) {
|
||||||
|
const newBalance = source.note.balance - source.quantity * dest.quantity * amount
|
||||||
|
if (newBalance <= -5000) {
|
||||||
|
addMsg('Le transfert de ' +
|
||||||
|
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' +
|
||||||
|
source.name + ' vers la note ' + dest.name + ' a été fait avec succès, ' +
|
||||||
|
'mais la note émettrice est en négatif sévère.', 'danger', 10000)
|
||||||
|
reset()
|
||||||
|
return
|
||||||
|
} else if (newBalance < 0) {
|
||||||
|
addMsg('Le transfert de ' +
|
||||||
|
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' +
|
||||||
|
source.name + ' vers la note ' + dest.name + ' a été fait avec succès, ' +
|
||||||
|
'mais la note émettrice est en négatif.', 'warning', 10000)
|
||||||
|
reset()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
addMsg('Le transfert de ' +
|
||||||
$("#last_name").val(user.last_name);
|
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||||
$("#first_name").val(user.first_name);
|
' vers la note ' + dest.name + ' a été fait avec succès !', 'success', 10000)
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
reset()
|
||||||
}
|
}).fail(function (err) { // do it again but valid = false
|
||||||
|
const errObj = JSON.parse(err.responseText)
|
||||||
|
if (errObj.non_field_errors) {
|
||||||
|
addMsg('Le transfert de ' +
|
||||||
|
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||||
|
' vers la note ' + dest.name + ' a échoué : ' + errObj.non_field_errors, 'danger')
|
||||||
|
LOCK = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
autoCompleteNote("source_note", "source_note_list", sources, sources_notes_display,
|
$.post('/api/note/transaction/transaction/',
|
||||||
"source_alias", "source_note", "user_note", "profile_pic", checkUniqueNote);
|
|
||||||
autoCompleteNote("dest_note", "dest_note_list", dests, dests_notes_display,
|
|
||||||
"dest_alias", "dest_note", "user_note", "profile_pic", checkUniqueNote);
|
|
||||||
|
|
||||||
let source = $("#source_note");
|
|
||||||
let dest = $("#dest_note");
|
|
||||||
|
|
||||||
$("#type_transfer").change(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
$("#source_me_div").removeClass('d-none');
|
|
||||||
$("#source_note").removeClass('is-invalid');
|
|
||||||
$("#dest_note").removeClass('is-invalid');
|
|
||||||
$("#special_transaction_div").addClass('d-none');
|
|
||||||
source.removeClass('d-none');
|
|
||||||
$("#source_note_list").removeClass('d-none');
|
|
||||||
$("#credit_type").addClass('d-none');
|
|
||||||
dest.removeClass('d-none');
|
|
||||||
$("#dest_note_list").removeClass('d-none');
|
|
||||||
$("#debit_type").addClass('d-none');
|
|
||||||
|
|
||||||
$("#source_note_label").text(select_emitters_label);
|
|
||||||
$("#dest_note_label").text(select_receveirs_label);
|
|
||||||
|
|
||||||
location.hash = "transfer";
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#type_credit").change(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
$("#source_me_div").addClass('d-none');
|
|
||||||
$("#source_note").removeClass('is-invalid');
|
|
||||||
$("#dest_note").removeClass('is-invalid');
|
|
||||||
$("#special_transaction_div").removeClass('d-none');
|
|
||||||
$("#source_note_list").addClass('d-none');
|
|
||||||
$("#dest_note_list").removeClass('d-none');
|
|
||||||
source.addClass('d-none');
|
|
||||||
source.tooltip('hide');
|
|
||||||
$("#credit_type").removeClass('d-none');
|
|
||||||
dest.removeClass('d-none');
|
|
||||||
dest.val('');
|
|
||||||
dest.tooltip('hide');
|
|
||||||
$("#debit_type").addClass('d-none');
|
|
||||||
|
|
||||||
$("#source_note_label").text(transfer_type_label);
|
|
||||||
$("#dest_note_label").text(select_receveir_label);
|
|
||||||
|
|
||||||
if (dests_notes_display.length > 1) {
|
|
||||||
$("#dest_note_list").html('');
|
|
||||||
dests_notes_display.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
location.hash = "credit";
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#type_debit").change(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
$("#source_me_div").addClass('d-none');
|
|
||||||
$("#source_note").removeClass('is-invalid');
|
|
||||||
$("#dest_note").removeClass('is-invalid');
|
|
||||||
$("#special_transaction_div").removeClass('d-none');
|
|
||||||
$("#source_note_list").removeClass('d-none');
|
|
||||||
$("#dest_note_list").addClass('d-none');
|
|
||||||
source.removeClass('d-none');
|
|
||||||
source.val('');
|
|
||||||
source.tooltip('hide');
|
|
||||||
$("#credit_type").addClass('d-none');
|
|
||||||
dest.addClass('d-none');
|
|
||||||
dest.tooltip('hide');
|
|
||||||
$("#debit_type").removeClass('d-none');
|
|
||||||
|
|
||||||
$("#source_note_label").text(select_emitter_label);
|
|
||||||
$("#dest_note_label").text(transfer_type_label);
|
|
||||||
|
|
||||||
if (sources_notes_display.length > 1) {
|
|
||||||
$("#source_note_list").html('');
|
|
||||||
sources_notes_display.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
location.hash = "debit";
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#credit_type").change(function() {
|
|
||||||
let type = $("#credit_type option:selected").text();
|
|
||||||
if ($("#type_credit").is(":checked"))
|
|
||||||
source.val(type);
|
|
||||||
else
|
|
||||||
dest.val(type);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure we begin in transfer mode. Removing these lines may cause problems when reloading.
|
|
||||||
let type_transfer = $("#type_transfer"); // Default mode
|
|
||||||
type_transfer.removeAttr('checked');
|
|
||||||
$("#type_credit").removeAttr('checked');
|
|
||||||
$("#type_debit").removeAttr('checked');
|
|
||||||
|
|
||||||
if (location.hash)
|
|
||||||
$("#type_" + location.hash.substr(1)).click();
|
|
||||||
else
|
|
||||||
type_transfer.click();
|
|
||||||
|
|
||||||
$("#source_me").click(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Shortcut to set the current user as the only emitter
|
|
||||||
sources_notes_display.length = 0;
|
|
||||||
sources.length = 0;
|
|
||||||
$("#source_note_list").html("");
|
|
||||||
|
|
||||||
let source_note = $("#source_note");
|
|
||||||
source_note.focus();
|
|
||||||
source_note.val("");
|
|
||||||
let event = jQuery.Event("keyup");
|
|
||||||
event.originalEvent = {charCode: 97};
|
|
||||||
source_note.trigger(event);
|
|
||||||
source_note.val(username);
|
|
||||||
event = jQuery.Event("keyup");
|
|
||||||
event.originalEvent = {charCode: 97};
|
|
||||||
source_note.trigger(event);
|
|
||||||
let fill_note = function() {
|
|
||||||
if (sources.length === 0) {
|
|
||||||
setTimeout(fill_note, 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event = jQuery.Event("keypress");
|
|
||||||
event.originalEvent = {charCode: 13};
|
|
||||||
source_note.trigger(event);
|
|
||||||
|
|
||||||
source_note.tooltip('hide');
|
|
||||||
source_note.val('');
|
|
||||||
$("#dest_note").focus();
|
|
||||||
};
|
|
||||||
fill_note();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#btn_transfer").click(function() {
|
|
||||||
if (LOCK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LOCK = true;
|
|
||||||
|
|
||||||
let error = false;
|
|
||||||
|
|
||||||
let amount_field = $("#amount");
|
|
||||||
amount_field.removeClass('is-invalid');
|
|
||||||
$("#amount-required").html("");
|
|
||||||
|
|
||||||
let reason_field = $("#reason");
|
|
||||||
reason_field.removeClass('is-invalid');
|
|
||||||
$("#reason-required").html("");
|
|
||||||
|
|
||||||
if (!amount_field.val() || isNaN(amount_field.val()) || amount_field.val() <= 0) {
|
|
||||||
amount_field.addClass('is-invalid');
|
|
||||||
$("#amount-required").html("<strong>Ce champ est requis et doit comporter un nombre décimal strictement positif.</strong>");
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let amount = Math.floor(100 * amount_field.val());
|
|
||||||
if (amount > 2147483647) {
|
|
||||||
amount_field.addClass('is-invalid');
|
|
||||||
$("#amount-required").html("<strong>Le montant ne doit pas excéder 21474836.47 €.</strong>");
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reason_field.val()) {
|
|
||||||
reason_field.addClass('is-invalid');
|
|
||||||
$("#reason-required").html("<strong>Ce champ est requis.</strong>");
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sources_notes_display.length && !$("#type_credit").is(':checked')) {
|
|
||||||
$("#source_note").addClass('is-invalid');
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dests_notes_display.length && !$("#type_debit").is(':checked')) {
|
|
||||||
$("#dest_note").addClass('is-invalid');
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
LOCK = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let reason = reason_field.val();
|
|
||||||
|
|
||||||
if ($("#type_transfer").is(':checked')) {
|
|
||||||
// We copy the arrays to ensure that transactions are well-processed even if the form is reset
|
|
||||||
[...sources_notes_display].forEach(function (source) {
|
|
||||||
[...dests_notes_display].forEach(function (dest) {
|
|
||||||
if (source.note.id === dest.note.id) {
|
|
||||||
addMsg("Attention : la transaction de " + pretty_money(amount) + " de la note " + source.name
|
|
||||||
+ " vers la note " + dest.name + " n'a pas été faite car il s'agit de la même note au départ" +
|
|
||||||
" et à l'arrivée.","warning", 10000);
|
|
||||||
LOCK = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.post("/api/note/transaction/transaction/",
|
|
||||||
{
|
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
|
||||||
"quantity": source.quantity * dest.quantity,
|
|
||||||
"amount": amount,
|
|
||||||
"reason": reason,
|
|
||||||
"valid": true,
|
|
||||||
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
|
||||||
"resourcetype": "Transaction",
|
|
||||||
"source": source.note.id,
|
|
||||||
"source_alias": source.name,
|
|
||||||
"destination": dest.note.id,
|
|
||||||
"destination_alias": dest.name
|
|
||||||
}).done(function () {
|
|
||||||
if (source.note.membership && source.note.membership.date_end < new Date().toISOString())
|
|
||||||
addMsg("Attention : la note émettrice " + source.name + " n'est plus adhérente.",
|
|
||||||
"danger", 30000);
|
|
||||||
if (dest.note.membership && dest.note.membership.date_end < new Date().toISOString())
|
|
||||||
addMsg("Attention : la note destination " + dest.name + " n'est plus adhérente.",
|
|
||||||
"danger", 30000);
|
|
||||||
|
|
||||||
if (!isNaN(source.note.balance)) {
|
|
||||||
let newBalance = source.note.balance - source.quantity * dest.quantity * amount;
|
|
||||||
if (newBalance <= -5000) {
|
|
||||||
addMsg("Le transfert de "
|
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note "
|
|
||||||
+ source.name + " vers la note " + dest.name + " a été fait avec succès, " +
|
|
||||||
"mais la note émettrice est en négatif sévère.", "danger", 10000);
|
|
||||||
reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (newBalance < 0) {
|
|
||||||
addMsg("Le transfert de "
|
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note "
|
|
||||||
+ source.name + " vers la note " + dest.name + " a été fait avec succès, " +
|
|
||||||
"mais la note émettrice est en négatif.", "warning", 10000);
|
|
||||||
reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addMsg("Le transfert de "
|
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
|
||||||
+ " vers la note " + dest.name + " a été fait avec succès !", "success", 10000);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
}).fail(function (err) { // do it again but valid = false
|
|
||||||
let errObj = JSON.parse(err.responseText);
|
|
||||||
if (errObj["non_field_errors"]) {
|
|
||||||
addMsg("Le transfert de "
|
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
|
||||||
+ " vers la note " + dest.name + " a échoué : " + errObj["non_field_errors"], "danger");
|
|
||||||
LOCK = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.post("/api/note/transaction/transaction/",
|
|
||||||
{
|
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
|
||||||
"quantity": source.quantity * dest.quantity,
|
|
||||||
"amount": amount,
|
|
||||||
"reason": reason,
|
|
||||||
"valid": false,
|
|
||||||
"invalidity_reason": "Solde insuffisant",
|
|
||||||
"polymorphic_ctype": TRANSFER_POLYMORPHIC_CTYPE,
|
|
||||||
"resourcetype": "Transaction",
|
|
||||||
"source": source.note.id,
|
|
||||||
"source_alias": source.name,
|
|
||||||
"destination": dest.note.id,
|
|
||||||
"destination_alias": dest.name
|
|
||||||
}).done(function () {
|
|
||||||
addMsg("Le transfert de "
|
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
|
||||||
+ " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger", 10000);
|
|
||||||
reset();
|
|
||||||
}).fail(function (err) {
|
|
||||||
let errObj = JSON.parse(err.responseText);
|
|
||||||
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"]
|
|
||||||
if (!error)
|
|
||||||
error = err.responseText;
|
|
||||||
addMsg("Le transfert de "
|
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
|
||||||
+ " vers la note " + dest.name + " a échoué : " + error, "danger");
|
|
||||||
LOCK = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) {
|
|
||||||
let special_note;
|
|
||||||
let user_note;
|
|
||||||
let alias;
|
|
||||||
let given_reason = reason;
|
|
||||||
let source_id, dest_id;
|
|
||||||
if ($("#type_credit").is(':checked')) {
|
|
||||||
special_note = $("#credit_type").val();
|
|
||||||
user_note = dests_notes_display[0].note;
|
|
||||||
alias = dests_notes_display[0].name;
|
|
||||||
source_id = special_note;
|
|
||||||
dest_id = user_note.id;
|
|
||||||
reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase();
|
|
||||||
if (given_reason.length > 0)
|
|
||||||
reason += " (" + given_reason + ")";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
special_note = $("#debit_type").val();
|
|
||||||
user_note = sources_notes_display[0].note;
|
|
||||||
alias = sources_notes_display[0].name;
|
|
||||||
source_id = user_note.id;
|
|
||||||
dest_id = special_note;
|
|
||||||
reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase();
|
|
||||||
if (given_reason.length > 0)
|
|
||||||
reason += " (" + given_reason + ")";
|
|
||||||
}
|
|
||||||
$.post("/api/note/transaction/transaction/",
|
|
||||||
{
|
{
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
csrfmiddlewaretoken: CSRF_TOKEN,
|
||||||
"quantity": 1,
|
quantity: source.quantity * dest.quantity,
|
||||||
"amount": amount,
|
amount: amount,
|
||||||
"reason": reason,
|
reason: reason,
|
||||||
"valid": true,
|
valid: false,
|
||||||
"polymorphic_ctype": SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
invalidity_reason: 'Solde insuffisant',
|
||||||
"resourcetype": "SpecialTransaction",
|
polymorphic_ctype: TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
"source": source_id,
|
resourcetype: 'Transaction',
|
||||||
"source_alias": sources_notes_display.length ? alias : null,
|
source: source.note.id,
|
||||||
"destination": dest_id,
|
source_alias: source.name,
|
||||||
"destination_alias": dests_notes_display.length ? alias : null,
|
destination: dest.note.id,
|
||||||
"last_name": $("#last_name").val(),
|
destination_alias: dest.name
|
||||||
"first_name": $("#first_name").val(),
|
|
||||||
"bank": $("#bank").val()
|
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg("Le crédit/retrait a bien été effectué !", "success", 10000);
|
addMsg('Le transfert de ' +
|
||||||
if (user_note.membership && user_note.membership.date_end < new Date().toISOString())
|
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||||
addMsg("Attention : la note " + alias + " n'est plus adhérente.", "danger", 10000);
|
' vers la note ' + dest.name + ' a échoué : Solde insuffisant', 'danger', 10000)
|
||||||
reset();
|
reset()
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
let errObj = JSON.parse(err.responseText);
|
const errObj = JSON.parse(err.responseText)
|
||||||
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"]
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
if (!error)
|
if (!error) { error = err.responseText }
|
||||||
error = err.responseText;
|
addMsg('Le transfert de ' +
|
||||||
addMsg("Le crédit/retrait a échoué : " + error, "danger", 10000);
|
pretty_money(source.quantity * dest.quantity * amount) + ' de la note ' + source.name +
|
||||||
LOCK = false;
|
' vers la note ' + dest.name + ' a échoué : ' + error, 'danger')
|
||||||
});
|
LOCK = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if ($('#type_credit').is(':checked') || $('#type_debit').is(':checked')) {
|
||||||
|
let special_note
|
||||||
|
let user_note
|
||||||
|
let alias
|
||||||
|
const given_reason = reason
|
||||||
|
let source_id, dest_id
|
||||||
|
if ($('#type_credit').is(':checked')) {
|
||||||
|
special_note = $('#credit_type').val()
|
||||||
|
user_note = dests_notes_display[0].note
|
||||||
|
alias = dests_notes_display[0].name
|
||||||
|
source_id = special_note
|
||||||
|
dest_id = user_note.id
|
||||||
|
reason = 'Crédit ' + $('#credit_type option:selected').text().toLowerCase()
|
||||||
|
if (given_reason.length > 0) { reason += ' (' + given_reason + ')' }
|
||||||
|
} else {
|
||||||
|
special_note = $('#debit_type').val()
|
||||||
|
user_note = sources_notes_display[0].note
|
||||||
|
alias = sources_notes_display[0].name
|
||||||
|
source_id = user_note.id
|
||||||
|
dest_id = special_note
|
||||||
|
reason = 'Retrait ' + $('#credit_type option:selected').text().toLowerCase()
|
||||||
|
if (given_reason.length > 0) { reason += ' (' + given_reason + ')' }
|
||||||
}
|
}
|
||||||
});
|
$.post('/api/note/transaction/transaction/',
|
||||||
|
{
|
||||||
|
csrfmiddlewaretoken: CSRF_TOKEN,
|
||||||
|
quantity: 1,
|
||||||
|
amount: amount,
|
||||||
|
reason: reason,
|
||||||
|
valid: true,
|
||||||
|
polymorphic_ctype: SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
|
resourcetype: 'SpecialTransaction',
|
||||||
|
source: source_id,
|
||||||
|
source_alias: sources_notes_display.length ? alias : null,
|
||||||
|
destination: dest_id,
|
||||||
|
destination_alias: dests_notes_display.length ? alias : null,
|
||||||
|
last_name: $('#last_name').val(),
|
||||||
|
first_name: $('#first_name').val(),
|
||||||
|
bank: $('#bank').val()
|
||||||
|
}).done(function () {
|
||||||
|
addMsg('Le crédit/retrait a bien été effectué !', 'success', 10000)
|
||||||
|
if (user_note.membership && user_note.membership.date_end < new Date().toISOString()) { addMsg('Attention : la note ' + alias + " n'est plus adhérente.", 'danger', 10000) }
|
||||||
|
reset()
|
||||||
|
}).fail(function (err) {
|
||||||
|
const errObj = JSON.parse(err.responseText)
|
||||||
|
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||||
|
if (!error) { error = err.responseText }
|
||||||
|
addMsg('Le crédit/retrait a échoué : ' + error, 'danger', 10000)
|
||||||
|
LOCK = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
1
tox.ini
1
tox.ini
|
@ -15,7 +15,6 @@ deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
coverage
|
coverage
|
||||||
commands =
|
commands =
|
||||||
./manage.py makemigrations
|
|
||||||
coverage run --omit='*migrations*,apps/scripts*' --source=apps,note_kfet ./manage.py test apps/
|
coverage run --omit='*migrations*,apps/scripts*' --source=apps,note_kfet ./manage.py test apps/
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue