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/
|
||||
venv/
|
||||
db.sqlite3
|
||||
|
||||
# Ignore migrations during first phase dev
|
||||
migrations/
|
||||
|
|
|
@ -2,6 +2,10 @@ stages:
|
|||
- test
|
||||
- quality-assurance
|
||||
|
||||
# Also fetch submodules
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Debian Buster
|
||||
py37-django22:
|
||||
stage: test
|
||||
|
@ -15,7 +19,7 @@ py37-django22:
|
|||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
||||
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
|
||||
|
||||
# Ubuntu 20.04
|
||||
|
@ -33,7 +37,7 @@ py38-django22:
|
|||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
||||
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
|
||||
|
||||
linters:
|
||||
|
|
|
@ -12,7 +12,7 @@ RUN apt-get update && \
|
|||
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
||||
python3-bs4 python3-setuptools \
|
||||
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 && \
|
||||
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_SETTINGS_MODULE="note_kfet.settings
|
||||
NOTE_URL=localhost # URL où accéder à la note
|
||||
DOMAIN=localhost # note.example.com
|
||||
CONTACT_EMAIL=tresorerie.bde@localhost
|
||||
# Le reste n'est utile qu'en production, pour configurer l'envoi des mails
|
||||
NOTE_MAIL=notekfet@localhost
|
||||
|
@ -211,7 +210,6 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
|||
|
||||
$ source /env/bin/activate
|
||||
(env)$ ./manage.py check # pas de bêtise qui traine
|
||||
(env)$ ./manage.py makemigrations
|
||||
(env)$ ./manage.py migrate
|
||||
|
||||
7. *Enjoy \o/*
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
- texlive-fonts-recommended
|
||||
- texlive-lang-french
|
||||
- texlive-latex-base
|
||||
- texlive-latex-recommended
|
||||
|
||||
# WSGI server
|
||||
- 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
|
||||
command: /var/www/note_kfet/env/bin/python manage.py migrate
|
||||
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
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
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:
|
||||
def refresh_activities():
|
||||
from scripts.management.commands.refresh_activities import Command as RefreshActivitiesCommand
|
||||
RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name)
|
||||
RefreshActivitiesCommand.refresh_raw_wiki_page("Modification de l'activité " + self.name)
|
||||
Thread(daemon=True, target=refresh_activities).start()
|
||||
# Consider that we can update the wiki iff the WIKI_PASSWORD env var is not empty
|
||||
RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name,
|
||||
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
|
||||
|
||||
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),
|
||||
]
|
|
@ -3,21 +3,21 @@
|
|||
*/
|
||||
function create_alias (e) {
|
||||
// Do not submit HTML form
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
|
||||
// Get data and send to API
|
||||
const formData = new FormData(e.target);
|
||||
$.post("/api/note/alias/", {
|
||||
"csrfmiddlewaretoken": formData.get("csrfmiddlewaretoken"),
|
||||
"name": formData.get("name"),
|
||||
"note": formData.get("note")
|
||||
const formData = new FormData(e.target)
|
||||
$.post('/api/note/alias/', {
|
||||
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
|
||||
name: formData.get('name'),
|
||||
note: formData.get('note')
|
||||
}).done(function () {
|
||||
// Reload table
|
||||
$("#alias_table").load(location.pathname + " #alias_table");
|
||||
addMsg("Alias ajouté", "success");
|
||||
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||
addMsg('Alias ajouté', 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON);
|
||||
});
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,18 +26,18 @@ function create_alias (e) {
|
|||
*/
|
||||
function delete_button (button_id) {
|
||||
$.ajax({
|
||||
url: "/api/note/alias/" + button_id + "/",
|
||||
method: "DELETE",
|
||||
headers: { "X-CSRFTOKEN": CSRF_TOKEN }
|
||||
url: '/api/note/alias/' + button_id + '/',
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRFTOKEN': CSRF_TOKEN }
|
||||
}).done(function () {
|
||||
addMsg('Alias supprimé', 'success');
|
||||
$("#alias_table").load(location.pathname + " #alias_table");
|
||||
addMsg('Alias supprimé', 'success')
|
||||
$('#alias_table').load(location.pathname + ' #alias_table')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON);
|
||||
});
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// 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
|
||||
response = self.client.post(reverse("member:club_add_member", args=(club.pk,)), data=dict(
|
||||
user=user.pk,
|
||||
date_start="{:%Y-%m-%d}".format(timezone.now().date()),
|
||||
date_start="{:%Y-%m-%d}".format(date.today()),
|
||||
soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
||||
credit_amount=4200,
|
||||
|
@ -236,7 +236,7 @@ class TestMemberships(TestCase):
|
|||
# Renew membership
|
||||
response = self.client.post(reverse("member:club_renew_membership", args=(membership.pk,)), data=dict(
|
||||
user=user.pk,
|
||||
date_start="{:%Y-%m-%d}".format(timezone.now().date()),
|
||||
date_start="{:%Y-%m-%d}".format(date.today()),
|
||||
soge=bde_parent,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
||||
credit_amount=14242,
|
||||
|
@ -265,7 +265,7 @@ class TestMemberships(TestCase):
|
|||
|
||||
response = self.client.post(reverse("member:club_add_member", args=(bde.pk,)), data=dict(
|
||||
user=user.pk,
|
||||
date_start="{:%Y-%m-%d}".format(timezone.now().date()),
|
||||
date_start="{:%Y-%m-%d}".format(date.today()),
|
||||
soge=True,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Virement bancaire").id,
|
||||
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[utf8]{inputenc}
|
||||
\usepackage[a4paper]{geometry}
|
||||
\usepackage{units}
|
||||
\usepackage{bera}
|
||||
%\usepackage{bera}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{fp}
|
||||
\usepackage{transparent}
|
||||
\usepackage{eso-pic}
|
||||
\usepackage{ifthen}
|
||||
|
||||
\DeclareUnicodeCharacter{00B0}{$^\circ$}
|
||||
|
||||
\def\TVA{0} % Taux de la TVA
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from unittest import skip
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -143,6 +144,7 @@ class TestInvoices(TestCase):
|
|||
self.assertRedirects(response, reverse("treasury:invoice_list"), 302, 200)
|
||||
self.assertFalse(Invoice.objects.filter(pk=self.invoice.id).exists())
|
||||
|
||||
@skip("LaTeX is buggy in the CI")
|
||||
def test_invoice_render_pdf(self):
|
||||
"""
|
||||
Generate the PDF file of an invoice.
|
||||
|
|
|
@ -217,7 +217,9 @@ class InvoiceRenderView(LoginRequiredMixin, View):
|
|||
).wait()
|
||||
|
||||
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
|
||||
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()
|
||||
|
||||
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
|
||||
with open("{}/wei-list.pdf".format(tmp_dir), 'rb') as f:
|
||||
|
|
|
@ -14,7 +14,6 @@ fi
|
|||
# Set up Django project
|
||||
python3 manage.py collectstatic --noinput
|
||||
python3 manage.py compilemessages
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
|
||||
if [ "$1" ]; then
|
||||
|
|
|
@ -1,57 +1,53 @@
|
|||
$(document).ready(function () {
|
||||
$(".autocomplete").keyup(function(e) {
|
||||
let target = $("#" + e.target.id);
|
||||
let prefix = target.attr("id");
|
||||
let api_url = target.attr("api_url");
|
||||
let api_url_suffix = target.attr("api_url_suffix");
|
||||
if (!api_url_suffix)
|
||||
api_url_suffix = "";
|
||||
let name_field = target.attr("name_field");
|
||||
if (!name_field)
|
||||
name_field = "name";
|
||||
let input = target.val();
|
||||
target.addClass("is-invalid");
|
||||
target.removeClass("is-valid");
|
||||
$("#" + prefix + "_reset").removeClass("d-none");
|
||||
$('.autocomplete').keyup(function (e) {
|
||||
const target = $('#' + e.target.id)
|
||||
const prefix = target.attr('id')
|
||||
const api_url = target.attr('api_url')
|
||||
let api_url_suffix = target.attr('api_url_suffix')
|
||||
if (!api_url_suffix) { api_url_suffix = '' }
|
||||
let name_field = target.attr('name_field')
|
||||
if (!name_field) { name_field = 'name' }
|
||||
const input = target.val()
|
||||
target.addClass('is-invalid')
|
||||
target.removeClass('is-valid')
|
||||
$('#' + prefix + '_reset').removeClass('d-none')
|
||||
|
||||
$.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
|
||||
let html = "";
|
||||
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
|
||||
let html = ''
|
||||
|
||||
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");
|
||||
results_list.html(html);
|
||||
const results_list = $('#' + prefix + '_list')
|
||||
results_list.html(html)
|
||||
|
||||
objects.results.forEach(function (obj) {
|
||||
$("#" + prefix + "_" + obj.id).click(function() {
|
||||
target.val(obj[name_field]);
|
||||
$("#" + prefix + "_pk").val(obj.id);
|
||||
$('#' + prefix + '_' + obj.id).click(function () {
|
||||
target.val(obj[name_field])
|
||||
$('#' + prefix + '_pk').val(obj.id)
|
||||
|
||||
results_list.html("");
|
||||
target.removeClass("is-invalid");
|
||||
target.addClass("is-valid");
|
||||
results_list.html('')
|
||||
target.removeClass('is-invalid')
|
||||
target.addClass('is-valid')
|
||||
|
||||
if (typeof autocompleted != 'undefined')
|
||||
autocompleted(obj, prefix)
|
||||
});
|
||||
if (typeof autocompleted !== 'undefined') { autocompleted(obj, prefix) }
|
||||
})
|
||||
|
||||
if (input === obj[name_field])
|
||||
$("#" + prefix + "_pk").val(obj.id);
|
||||
});
|
||||
if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) }
|
||||
})
|
||||
|
||||
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() {
|
||||
let name = $(this).attr("id").replace("_reset", "");
|
||||
$("#" + name + "_pk").val("");
|
||||
$("#" + name).val("");
|
||||
$("#" + name + "_list").html("");
|
||||
$(this).addClass("d-none");
|
||||
});
|
||||
});
|
||||
$('.autocomplete-reset').click(function () {
|
||||
const name = $(this).attr('id').replace('_reset', '')
|
||||
$('#' + name + '_pk').val('')
|
||||
$('#' + name).val('')
|
||||
$('#' + name + '_list').html('')
|
||||
$(this).addClass('d-none')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
// Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
/**
|
||||
* Convert balance in cents to a human readable amount
|
||||
* @param value the balance, in cents
|
||||
* @returns {string}
|
||||
*/
|
||||
function pretty_money (value) {
|
||||
if (value % 100 === 0)
|
||||
return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + " €";
|
||||
else
|
||||
return (value < 0 ? "- " : "") + Math.floor(Math.abs(value) / 100) + "."
|
||||
+ (Math.abs(value) % 100 < 10 ? "0" : "") + (Math.abs(value) % 100) + " €";
|
||||
if (value % 100 === 0) { return (value < 0 ? '- ' : '') + Math.floor(Math.abs(value) / 100) + ' €' } else {
|
||||
return (value < 0 ? '- ' : '') + Math.floor(Math.abs(value) / 100) + '.' +
|
||||
(Math.abs(value) % 100 < 10 ? '0' : '') + (Math.abs(value) % 100) + ' €'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,18 +20,18 @@ function pretty_money (value) {
|
|||
* @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) {
|
||||
let msgDiv = $("#messages");
|
||||
let html = msgDiv.html();
|
||||
let id = Math.floor(10000 * Math.random() + 1);
|
||||
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>"
|
||||
+ msg + "</div>\n";
|
||||
msgDiv.html(html);
|
||||
const msgDiv = $('#messages')
|
||||
let html = msgDiv.html()
|
||||
const id = Math.floor(10000 * Math.random() + 1)
|
||||
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>' +
|
||||
msg + '</div>\n'
|
||||
msgDiv.html(html)
|
||||
|
||||
if (timeout > 0) {
|
||||
setTimeout(function () {
|
||||
$("#close-message-" + id).click();
|
||||
}, timeout);
|
||||
$('#close-message-' + id).click()
|
||||
}, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,33 +42,33 @@ function addMsg (msg, alert_type, timeout = -1) {
|
|||
*/
|
||||
function errMsg (errs_obj, timeout = -1) {
|
||||
for (const err_msg of Object.values(errs_obj)) {
|
||||
addMsg(err_msg, 'danger', timeout);
|
||||
addMsg(err_msg, 'danger', timeout)
|
||||
}
|
||||
}
|
||||
|
||||
var reloadWithTurbolinks = (function () {
|
||||
var scrollPosition;
|
||||
var scrollPosition
|
||||
|
||||
function reload () {
|
||||
scrollPosition = [window.scrollX, window.scrollY];
|
||||
scrollPosition = [window.scrollX, window.scrollY]
|
||||
Turbolinks.visit(window.location.toString(), { action: 'replace' })
|
||||
}
|
||||
|
||||
document.addEventListener('turbolinks:load', function () {
|
||||
if (scrollPosition) {
|
||||
window.scrollTo.apply(window, scrollPosition);
|
||||
window.scrollTo.apply(window, scrollPosition)
|
||||
scrollPosition = null
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return reload;
|
||||
})();
|
||||
return reload
|
||||
})()
|
||||
|
||||
/**
|
||||
* Reload the balance of the user on the right top corner
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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 "
|
||||
+ (extra_css ? extra_css : "") + "\"" + " id=\"" + id + "\">" + text + "</li>\n";
|
||||
return '<li class="list-group-item py-1 px-2 d-flex justify-content-between align-items-center text-truncate ' +
|
||||
(extra_css || '') + '"' + ' id="' + id + '">' + text + '</li>\n'
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,24 +93,13 @@ function li (id, text, extra_css) {
|
|||
* @param note The concerned note.
|
||||
*/
|
||||
function displayStyle (note) {
|
||||
if (!note)
|
||||
return "";
|
||||
let balance = note.balance;
|
||||
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";
|
||||
return css;
|
||||
if (!note) { return '' }
|
||||
const balance = note.balance
|
||||
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' }
|
||||
return css
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render note name and picture
|
||||
* @param note The note to render
|
||||
|
@ -122,20 +109,19 @@ function displayStyle (note) {
|
|||
*/
|
||||
function displayNote (note, alias, user_note_field = null, profile_pic_field = null) {
|
||||
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;
|
||||
if (alias !== note.name && note.name)
|
||||
alias += " (aka. " + note.name + ")";
|
||||
const img = note.display_image
|
||||
if (alias !== note.name && note.name) { alias += ' (aka. ' + note.name + ')' }
|
||||
if (user_note_field !== null) {
|
||||
$("#" + user_note_field).removeAttr('class');
|
||||
$("#" + user_note_field).addClass(displayStyle(note));
|
||||
$("#" + user_note_field).text(alias + (note.balance == null ? "" : (" :\n" + pretty_money(note.balance))));
|
||||
$('#' + user_note_field).removeAttr('class')
|
||||
$('#' + user_note_field).addClass(displayStyle(note))
|
||||
$('#' + user_note_field).text(alias + (note.balance == null ? '' : (' :\n' + pretty_money(note.balance))))
|
||||
if (profile_pic_field != null) {
|
||||
$("#" + profile_pic_field).attr('src', img);
|
||||
$("#" + profile_pic_field + "_link").attr('href', note.resourcetype === "NoteUser" ?
|
||||
"/accounts/user/" + note.user : note.resourcetype === "NoteClub" ?
|
||||
"/accounts/club/" + note.club : "#");
|
||||
$('#' + profile_pic_field).attr('src', img)
|
||||
$('#' + profile_pic_field + '_link').attr('href', note.resourcetype === 'NoteUser'
|
||||
? '/accounts/user/' + note.user : note.resourcetype === 'NoteClub'
|
||||
? '/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)
|
||||
* @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) {
|
||||
return (function () {
|
||||
let new_notes_display = [];
|
||||
let html = "";
|
||||
function removeNote (d, note_prefix = 'note', notes_display, note_list_id, user_note_field = null, profile_pic_field = null) {
|
||||
return function () {
|
||||
const new_notes_display = []
|
||||
let html = ''
|
||||
notes_display.forEach(function (disp) {
|
||||
if (disp.quantity > 1 || disp.id !== d.id) {
|
||||
disp.quantity -= disp.id === d.id ? 1 : 0;
|
||||
new_notes_display.push(disp);
|
||||
html += li(note_prefix + "_" + disp.id, disp.name
|
||||
+ "<span class=\"badge badge-dark badge-pill\">" + disp.quantity + "</span>",
|
||||
displayStyle(disp.note));
|
||||
disp.quantity -= disp.id === d.id ? 1 : 0
|
||||
new_notes_display.push(disp)
|
||||
html += li(note_prefix + '_' + disp.id, disp.name +
|
||||
'<span class="badge badge-dark badge-pill">' + disp.quantity + '</span>',
|
||||
displayStyle(disp.note))
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
notes_display.length = 0;
|
||||
notes_display.length = 0
|
||||
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) {
|
||||
let obj = $("#" + note_prefix + "_" + disp.id);
|
||||
obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field));
|
||||
const obj = $('#' + note_prefix + '_' + disp.id)
|
||||
obj.click(removeNote(disp, note_prefix, notes_display, note_list_id, user_note_field, profile_pic_field))
|
||||
obj.hover(function () {
|
||||
if (disp.note)
|
||||
displayNote(disp.note, disp.name, user_note_field, profile_pic_field);
|
||||
});
|
||||
});
|
||||
});
|
||||
if (disp.note) { displayNote(disp.note, disp.name, user_note_field, profile_pic_field) }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,9 +184,9 @@ function removeNote (d, note_prefix = "note", notes_display, note_list_id, user_
|
|||
* the associated note is not displayed.
|
||||
* Useful for a consumption if the item is selected before.
|
||||
*/
|
||||
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) {
|
||||
let field = $("#" + field_id);
|
||||
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) {
|
||||
const field = $('#' + field_id)
|
||||
|
||||
// Configure tooltip
|
||||
field.tooltip({
|
||||
|
@ -211,96 +196,90 @@ function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_p
|
|||
trigger: 'manual',
|
||||
container: field.parent(),
|
||||
fallbackPlacement: 'clockwise'
|
||||
});
|
||||
})
|
||||
|
||||
// When the user clicks elsewhere, we hide the tooltip
|
||||
$(document).click(function(e) {
|
||||
$(document).click(function (e) {
|
||||
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
|
||||
field.click(function () {
|
||||
field.tooltip('hide');
|
||||
field.removeClass('is-invalid');
|
||||
field.val("");
|
||||
old_pattern = "";
|
||||
});
|
||||
field.tooltip('hide')
|
||||
field.removeClass('is-invalid')
|
||||
field.val('')
|
||||
old_pattern = ''
|
||||
})
|
||||
|
||||
// When the user type "Enter", the first alias is clicked
|
||||
field.keypress(function (event) {
|
||||
if (event.originalEvent.charCode === 13 && notes.length > 0) {
|
||||
let li_obj = field.parent().find("ul li").first();
|
||||
displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field);
|
||||
li_obj.trigger("click");
|
||||
const li_obj = field.parent().find('ul li').first()
|
||||
displayNote(notes[0], li_obj.text(), user_note_field, profile_pic_field)
|
||||
li_obj.trigger('click')
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// When the user type something, the matched aliases are refreshed
|
||||
field.keyup(function (e) {
|
||||
field.removeClass('is-invalid');
|
||||
field.removeClass('is-invalid')
|
||||
|
||||
if (e.originalEvent.charCode === 13)
|
||||
return;
|
||||
if (e.originalEvent.charCode === 13) { return }
|
||||
|
||||
let pattern = field.val();
|
||||
const pattern = field.val()
|
||||
|
||||
// If the pattern is not modified, we don't query the API
|
||||
if (pattern === old_pattern)
|
||||
return;
|
||||
old_pattern = pattern;
|
||||
notes.length = 0;
|
||||
if (pattern === old_pattern) { return }
|
||||
old_pattern = pattern
|
||||
notes.length = 0
|
||||
|
||||
// get matched Alias with note associated
|
||||
if (pattern === "") {
|
||||
field.tooltip('hide');
|
||||
notes.length = 0;
|
||||
return;
|
||||
if (pattern === '') {
|
||||
field.tooltip('hide')
|
||||
notes.length = 0
|
||||
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) {
|
||||
// The response arrived too late, we stop the request
|
||||
if (pattern !== $("#" + field_id).val())
|
||||
return;
|
||||
if (pattern !== $('#' + field_id).val()) { return }
|
||||
|
||||
// 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) {
|
||||
let note = consumer.note;
|
||||
note.email_confirmed = consumer.email_confirmed;
|
||||
if (consumer.hasOwnProperty("membership") && consumer.membership)
|
||||
note.membership = consumer.membership;
|
||||
else
|
||||
note.membership = undefined;
|
||||
let extra_css = displayStyle(note);
|
||||
const note = consumer.note
|
||||
note.email_confirmed = consumer.email_confirmed
|
||||
if (consumer.hasOwnProperty('membership') && consumer.membership) { note.membership = consumer.membership } else { note.membership = undefined }
|
||||
const extra_css = displayStyle(note)
|
||||
aliases_matched_html += li(alias_prefix + '_' + consumer.id,
|
||||
consumer.name,
|
||||
extra_css);
|
||||
notes.push(note);
|
||||
});
|
||||
aliases_matched_html += '</ul>';
|
||||
extra_css)
|
||||
notes.push(note)
|
||||
})
|
||||
aliases_matched_html += '</ul>'
|
||||
|
||||
// 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) {
|
||||
let consumer_obj = $("#" + alias_prefix + "_" + consumer.id);
|
||||
const consumer_obj = $('#' + alias_prefix + '_' + consumer.id)
|
||||
consumer_obj.hover(function () {
|
||||
displayNote(consumer.note, consumer.name, user_note_field, profile_pic_field)
|
||||
});
|
||||
})
|
||||
consumer_obj.click(function () {
|
||||
var disp = null;
|
||||
var disp = null
|
||||
notes_display.forEach(function (d) {
|
||||
// We compare the alias ids
|
||||
if (d.id === consumer.id) {
|
||||
d.quantity += 1;
|
||||
disp = d;
|
||||
d.quantity += 1
|
||||
disp = d
|
||||
}
|
||||
});
|
||||
})
|
||||
// In the other case, we add a new emitter
|
||||
if (disp == null) {
|
||||
disp = {
|
||||
|
@ -308,94 +287,90 @@ function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_p
|
|||
id: consumer.id,
|
||||
note: consumer.note,
|
||||
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
|
||||
// note displayed. Useful for a consumption when a button is already clicked
|
||||
if (alias_click && !alias_click())
|
||||
return;
|
||||
if (alias_click && !alias_click()) { return }
|
||||
|
||||
let note_list = $("#" + note_list_id);
|
||||
let html = "";
|
||||
const note_list = $('#' + note_list_id)
|
||||
let html = ''
|
||||
notes_display.forEach(function (disp) {
|
||||
html += li(note_prefix + "_" + disp.id,
|
||||
disp.name
|
||||
+ "<span class=\"badge badge-dark badge-pill\">"
|
||||
+ disp.quantity + "</span>",
|
||||
displayStyle(disp.note));
|
||||
});
|
||||
html += li(note_prefix + '_' + disp.id,
|
||||
disp.name +
|
||||
'<span class="badge badge-dark badge-pill">' +
|
||||
disp.quantity + '</span>',
|
||||
displayStyle(disp.note))
|
||||
})
|
||||
|
||||
// Emitters are displayed
|
||||
note_list.html(html);
|
||||
note_list.html(html)
|
||||
|
||||
// Update tooltip position
|
||||
field.tooltip('update');
|
||||
field.tooltip('update')
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
// When a validate button is clicked, we switch the validation status
|
||||
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
|
||||
return;
|
||||
{ return }
|
||||
|
||||
let invalidity_reason = $("#invalidity_reason_" + id).val();
|
||||
validate_obj.html("<strong style=\"font-size: 16pt;\">⟳</strong>");
|
||||
validate_obj.data("pending", true);
|
||||
const invalidity_reason = $('#invalidity_reason_' + id).val()
|
||||
validate_obj.html('<strong style="font-size: 16pt;">⟳</strong>')
|
||||
validate_obj.data('pending', true)
|
||||
|
||||
// Perform a PATCH request to the API in order to update the transaction
|
||||
// If the user has insufficient rights, an error message will appear
|
||||
$.ajax({
|
||||
"url": "/api/note/transaction/transaction/" + id + "/",
|
||||
type: "PATCH",
|
||||
dataType: "json",
|
||||
url: '/api/note/transaction/transaction/' + id + '/',
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
"X-CSRFTOKEN": CSRF_TOKEN
|
||||
'X-CSRFTOKEN': CSRF_TOKEN
|
||||
},
|
||||
data: {
|
||||
"resourcetype": resourcetype,
|
||||
"valid": !validated,
|
||||
"invalidity_reason": invalidity_reason,
|
||||
resourcetype: resourcetype,
|
||||
valid: !validated,
|
||||
invalidity_reason: invalidity_reason
|
||||
},
|
||||
success: function () {
|
||||
refreshBalance();
|
||||
refreshBalance()
|
||||
// error if this method doesn't exist. Please define it.
|
||||
refreshHistory();
|
||||
refreshHistory()
|
||||
},
|
||||
error: function (err) {
|
||||
let errObj = JSON.parse(err.responseText);
|
||||
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"];
|
||||
if (!error)
|
||||
error = err.responseText;
|
||||
addMsg("Une erreur est survenue lors de la validation/dévalidation " +
|
||||
"de cette transaction : " + error, "danger");
|
||||
const errObj = JSON.parse(err.responseText)
|
||||
let error = errObj.detail ? errObj.detail : errObj.non_field_errors
|
||||
if (!error) { error = err.responseText }
|
||||
addMsg('Une erreur est survenue lors de la validation/dévalidation ' +
|
||||
'de cette transaction : ' + error, 'danger')
|
||||
|
||||
refreshBalance();
|
||||
refreshBalance()
|
||||
// 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
|
||||
*/
|
||||
function debounce (callback, wait) {
|
||||
let timeout;
|
||||
let timeout
|
||||
return (...args) => {
|
||||
const context = this;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => callback.apply(context, args), wait);
|
||||
};
|
||||
const context = this
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => callback.apply(context, args), wait)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,95 +2,92 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// 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.
|
||||
*/
|
||||
function refreshHistory() {
|
||||
$("#history").load("/note/consos/ #history");
|
||||
$("#most_used").load("/note/consos/ #most_used");
|
||||
function refreshHistory () {
|
||||
$('#history').load('/note/consos/ #history')
|
||||
$('#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
|
||||
// else select the first one
|
||||
if (location.hash) {
|
||||
$("a[href='" + location.hash + "']").tab("show");
|
||||
$("a[href='" + location.hash + "']").tab('show')
|
||||
} 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");
|
||||
});
|
||||
$(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');
|
||||
$('#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')
|
||||
|
||||
let note_list_obj = $("#note_list");
|
||||
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("");
|
||||
$('#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")();
|
||||
});
|
||||
});
|
||||
buttons.forEach(function (button) {
|
||||
$('#conso_button_' + button.id).click(function () {
|
||||
if (LOCK) { return }
|
||||
removeNote(button, 'conso_button', buttons, 'consos_list')()
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
$("#single_conso").change(function() {
|
||||
$("#consos_list_div").addClass('d-none');
|
||||
$("#user_select_div").attr('class', 'col-xl-7');
|
||||
$("#infos_div").attr('class', 'col-sm-5 col-md-4');
|
||||
$('#single_conso').change(function () {
|
||||
$('#consos_list_div').addClass('d-none')
|
||||
$('#user_select_div').attr('class', 'col-xl-7')
|
||||
$('#infos_div').attr('class', 'col-sm-5 col-md-4')
|
||||
|
||||
let consos_list_obj = $("#consos_list");
|
||||
const 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("");
|
||||
$('#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');
|
||||
$("label[for='double_conso']").removeClass('active')
|
||||
|
||||
$("#consume_all").click(consumeAll);
|
||||
});
|
||||
$('#consume_all').click(consumeAll)
|
||||
})
|
||||
|
||||
notes = [];
|
||||
notes_display = [];
|
||||
buttons = [];
|
||||
notes = []
|
||||
notes_display = []
|
||||
buttons = []
|
||||
|
||||
// When the user searches an alias, we update the auto-completion
|
||||
autoCompleteNote("note", "note_list", notes, notes_display,
|
||||
"alias", "note", "user_note", "profile_pic", function() {
|
||||
if (buttons.length > 0 && $("#single_conso").is(":checked")) {
|
||||
consumeAll();
|
||||
return false;
|
||||
autoCompleteNote('note', 'note_list', notes, notes_display,
|
||||
'alias', 'note', 'user_note', 'profile_pic', function () {
|
||||
if (buttons.length > 0 && $('#single_conso').is(':checked')) {
|
||||
consumeAll()
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
/**
|
||||
* Add a transaction from a button.
|
||||
|
@ -102,14 +99,14 @@ autoCompleteNote("note", "note_list", notes, notes_display,
|
|||
* @param template_id The identifier of the button
|
||||
* @param template_name The name of the button
|
||||
*/
|
||||
function addConso(dest, amount, type, category_id, category_name, template_id, template_name) {
|
||||
var button = null;
|
||||
buttons.forEach(function(b) {
|
||||
function addConso (dest, amount, type, category_id, category_name, template_id, template_name) {
|
||||
var button = null
|
||||
buttons.forEach(function (b) {
|
||||
if (b.id === template_id) {
|
||||
b.quantity += 1;
|
||||
button = b;
|
||||
b.quantity += 1
|
||||
button = b
|
||||
}
|
||||
});
|
||||
})
|
||||
if (button == null) {
|
||||
button = {
|
||||
id: template_id,
|
||||
|
@ -120,85 +117,80 @@ function addConso(dest, amount, type, category_id, category_name, template_id, t
|
|||
type: type,
|
||||
category_id: category_id,
|
||||
category_name: category_name
|
||||
};
|
||||
buttons.push(button);
|
||||
}
|
||||
buttons.push(button)
|
||||
}
|
||||
|
||||
let dc_obj = $("#double_conso");
|
||||
if (dc_obj.is(":checked") || notes_display.length === 0) {
|
||||
let 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>");
|
||||
});
|
||||
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);
|
||||
$('#' + list).html(html)
|
||||
|
||||
buttons.forEach(function(button) {
|
||||
$("#conso_button_" + button.id).click(function() {
|
||||
if (LOCK)
|
||||
return;
|
||||
removeNote(button, "conso_button", buttons, list)();
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
consumeAll();
|
||||
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.
|
||||
*/
|
||||
function reset() {
|
||||
notes_display.length = 0;
|
||||
notes.length = 0;
|
||||
buttons.length = 0;
|
||||
$("#note_list").html("");
|
||||
$("#consos_list").html("");
|
||||
$("#note").val("");
|
||||
$("#note").attr("data-original-title", "").tooltip("hide");
|
||||
$("#profile_pic").attr("src", "/static/member/img/default_picture.png");
|
||||
$("#profile_pic_link").attr("href", "#");
|
||||
refreshHistory();
|
||||
refreshBalance();
|
||||
LOCK = false;
|
||||
function reset () {
|
||||
notes_display.length = 0
|
||||
notes.length = 0
|
||||
buttons.length = 0
|
||||
$('#note_list').html('')
|
||||
$('#consos_list').html('')
|
||||
$('#note').val('')
|
||||
$('#note').attr('data-original-title', '').tooltip('hide')
|
||||
$('#profile_pic').attr('src', '/static/member/img/default_picture.png')
|
||||
$('#profile_pic_link').attr('href', '#')
|
||||
refreshHistory()
|
||||
refreshBalance()
|
||||
LOCK = false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply all transactions: all notes in `notes` buy each item in `buttons`
|
||||
*/
|
||||
function consumeAll() {
|
||||
if (LOCK)
|
||||
return;
|
||||
function consumeAll () {
|
||||
if (LOCK) { return }
|
||||
|
||||
LOCK = true;
|
||||
LOCK = true
|
||||
|
||||
let error = false;
|
||||
let error = false
|
||||
|
||||
if (notes_display.length === 0) {
|
||||
$("#note").addClass('is-invalid');
|
||||
$("#note_list").html(li("", "<strong>Ajoutez des émetteurs.</strong>", "text-danger"));
|
||||
error = true;
|
||||
$('#note').addClass('is-invalid')
|
||||
$('#note_list').html(li('', '<strong>Ajoutez des émetteurs.</strong>', 'text-danger'))
|
||||
error = true
|
||||
}
|
||||
|
||||
if (buttons.length === 0) {
|
||||
$("#consos_list").html(li("", "<strong>Ajoutez des consommations.</strong>", "text-danger"));
|
||||
error = true;
|
||||
$('#consos_list').html(li('', '<strong>Ajoutez des consommations.</strong>', 'text-danger'))
|
||||
error = true
|
||||
}
|
||||
|
||||
if (error) {
|
||||
LOCK = false;
|
||||
return;
|
||||
LOCK = false
|
||||
return
|
||||
}
|
||||
|
||||
notes_display.forEach(function(note_display) {
|
||||
buttons.forEach(function(button) {
|
||||
notes_display.forEach(function (note_display) {
|
||||
buttons.forEach(function (button) {
|
||||
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 template The button id (type: int)
|
||||
*/
|
||||
function consume(source, source_alias, dest, quantity, amount, reason, type, category, template) {
|
||||
$.post("/api/note/transaction/transaction/",
|
||||
function consume (source, source_alias, dest, quantity, amount, reason, type, category, template) {
|
||||
$.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
|
||||
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)) {
|
||||
let 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);
|
||||
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)
|
||||
}
|
||||
reset();
|
||||
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/",
|
||||
$.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);
|
||||
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);
|
||||
});
|
||||
});
|
||||
reset()
|
||||
errMsg(e.responseJSON)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,121 +9,120 @@
|
|||
* Licensed under the New BSD License
|
||||
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
;(function($) {
|
||||
$.fn.formset = function(opts)
|
||||
{
|
||||
var options = $.extend({}, $.fn.formset.defaults, opts),
|
||||
flatExtraClasses = options.extraClasses.join(' '),
|
||||
totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
|
||||
maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
|
||||
minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
|
||||
childElementSelector = 'input,select,textarea,label,div',
|
||||
$$ = $(this),
|
||||
;(function ($) {
|
||||
$.fn.formset = function (opts) {
|
||||
var options = $.extend({}, $.fn.formset.defaults, opts)
|
||||
var flatExtraClasses = options.extraClasses.join(' ')
|
||||
var totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS')
|
||||
var maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS')
|
||||
var minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS')
|
||||
var childElementSelector = 'input,select,textarea,label,div'
|
||||
var $$ = $(this)
|
||||
|
||||
applyExtraClasses = function(row, ndx) {
|
||||
var applyExtraClasses = function (row, ndx) {
|
||||
if (options.extraClasses) {
|
||||
row.removeClass(flatExtraClasses);
|
||||
row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
|
||||
row.removeClass(flatExtraClasses)
|
||||
row.addClass(options.extraClasses[ndx % options.extraClasses.length])
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateElementIndex = function(elem, prefix, ndx) {
|
||||
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'),
|
||||
replacement = prefix + '-' + ndx + '-';
|
||||
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('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
|
||||
},
|
||||
var updateElementIndex = function (elem, prefix, ndx) {
|
||||
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-')
|
||||
var replacement = prefix + '-' + ndx + '-'
|
||||
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('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement))
|
||||
}
|
||||
|
||||
hasChildElements = function(row) {
|
||||
return row.find(childElementSelector).length > 0;
|
||||
},
|
||||
var hasChildElements = function (row) {
|
||||
return row.find(childElementSelector).length > 0
|
||||
}
|
||||
|
||||
showAddButton = function() {
|
||||
return maxForms.length == 0 || // For Django versions pre 1.2
|
||||
(maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
|
||||
},
|
||||
var showAddButton = function () {
|
||||
return maxForms.length === 0 || // For Django versions pre 1.2
|
||||
(maxForms.val() === '' || (maxForms.val() - totalForms.val() > 0))
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether delete link(s) can be displayed - when total forms > min forms
|
||||
*/
|
||||
showDeleteLinks = function() {
|
||||
return minForms.length == 0 || // For Django versions pre 1.7
|
||||
(minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
|
||||
},
|
||||
var showDeleteLinks = function () {
|
||||
return minForms.length === 0 || // For Django versions pre 1.7
|
||||
(minForms.val() === '' || (totalForms.val() - minForms.val() > 0))
|
||||
}
|
||||
|
||||
insertDeleteLink = function(row) {
|
||||
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
||||
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
||||
var insertDeleteLink = function (row) {
|
||||
var delCssSelector = $.trim(options.deleteCssClass).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 we have a specific container for the remove button,
|
||||
// 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')) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// 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')) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li>' + delButtonHTML + '</li>');
|
||||
row.append('<li>' + delButtonHTML + '</li>')
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// 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
|
||||
if (!showDeleteLinks()){
|
||||
row.find('a.' + delCssSelector).hide();
|
||||
if (!showDeleteLinks()) {
|
||||
row.find('a.' + delCssSelector).hide()
|
||||
}
|
||||
|
||||
row.find('a.' + delCssSelector).click(function() {
|
||||
var row = $(this).parents('.' + options.formCssClass),
|
||||
del = row.find('input:hidden[id $= "-DELETE"]'),
|
||||
buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
|
||||
forms;
|
||||
row.find('a.' + delCssSelector).click(function () {
|
||||
var row = $(this).parents('.' + options.formCssClass)
|
||||
var del = row.find('input:hidden[id $= "-DELETE"]')
|
||||
var buttonRow = row.siblings('a.' + addCssSelector + ', .' + options.formCssClass + '-add')
|
||||
var forms
|
||||
if (del.length) {
|
||||
// We're dealing with an inline formset.
|
||||
// Rather than remove this form from the DOM, we'll mark it as deleted
|
||||
// and hide it, then let Django handle the deleting:
|
||||
del.val('on');
|
||||
row.hide();
|
||||
forms = $('.' + options.formCssClass).not(':hidden');
|
||||
del.val('on')
|
||||
row.hide()
|
||||
forms = $('.' + options.formCssClass).not(':hidden')
|
||||
} else {
|
||||
row.remove();
|
||||
row.remove()
|
||||
// Update the TOTAL_FORMS count:
|
||||
forms = $('.' + options.formCssClass).not('.formset-custom-template');
|
||||
totalForms.val(forms.length);
|
||||
forms = $('.' + options.formCssClass).not('.formset-custom-template')
|
||||
totalForms.val(forms.length)
|
||||
}
|
||||
for (var i=0, formCount=forms.length; i<formCount; i++) {
|
||||
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);
|
||||
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);
|
||||
});
|
||||
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();});
|
||||
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 (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;
|
||||
});
|
||||
};
|
||||
if (options.removed) options.removed(row)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
$$.each(function(i) {
|
||||
var row = $(this),
|
||||
del = row.find('input:checkbox[id $= "-DELETE"]');
|
||||
$$.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.
|
||||
|
@ -131,103 +130,103 @@
|
|||
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();
|
||||
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') +'" />');
|
||||
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();
|
||||
$('label[for="' + del.attr('id') + '"]').hide()
|
||||
del.remove()
|
||||
}
|
||||
if (hasChildElements(row)) {
|
||||
row.addClass(options.formCssClass);
|
||||
row.addClass(options.formCssClass)
|
||||
if (row.is(':visible')) {
|
||||
insertDeleteLink(row);
|
||||
applyExtraClasses(row, i);
|
||||
insertDeleteLink(row)
|
||||
applyExtraClasses(row, i)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if ($$.length) {
|
||||
var hideAddButton = !showAddButton(),
|
||||
addButton, template;
|
||||
var hideAddButton = !showAddButton()
|
||||
var addButton; var 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);
|
||||
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();
|
||||
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);
|
||||
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);
|
||||
elem.attr('checked', false)
|
||||
} else {
|
||||
elem.val('');
|
||||
elem.val('')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
// FIXME: Perhaps using $.data would be a better idea?
|
||||
options.formTemplate = template;
|
||||
options.formTemplate = template
|
||||
|
||||
var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>';
|
||||
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 + '"]');
|
||||
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');
|
||||
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();
|
||||
$$.filter(':last').after(addButtonHTML)
|
||||
addButton = $$.filter(':last').next()
|
||||
}
|
||||
|
||||
if (hideAddButton) addButton.hide();
|
||||
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);
|
||||
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();});
|
||||
if (showDeleteLinks()) {
|
||||
$('a.' + delCssSelector).each(function () { $(this).show() })
|
||||
}
|
||||
// Check if we've exceeded the maximum allowed number of forms:
|
||||
if (!showAddButton()) buttonRow.hide();
|
||||
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;
|
||||
});
|
||||
if (options.added) options.added(row)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
return $$;
|
||||
};
|
||||
return $$
|
||||
}
|
||||
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
|
@ -245,5 +244,5 @@
|
|||
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);
|
||||
}
|
||||
})(jQuery)
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
let cursor = 0
|
||||
const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]
|
||||
|
||||
function afterKonami() {
|
||||
function afterKonami () {
|
||||
// Load Rythm.js
|
||||
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)
|
||||
|
||||
rythmScript.addEventListener('load', function() {
|
||||
rythmScript.addEventListener('load', function () {
|
||||
// Ker-Lyon audio courtesy of @adalan, ker-lyon.fr
|
||||
const audioElement = new Audio('/static/song/konami.ogg')
|
||||
audioElement.loop = true
|
||||
|
@ -25,21 +25,21 @@ function afterKonami() {
|
|||
max: 1.1
|
||||
})
|
||||
rythm.addRythm('d-flex', 'color', 50, 50, {
|
||||
from: [64,64,64],
|
||||
to:[128,64,128]
|
||||
from: [64, 64, 64],
|
||||
to: [128, 64, 128]
|
||||
})
|
||||
rythm.addRythm('nav-link', 'jump', 150, 50, {
|
||||
min: 0,
|
||||
max: 10
|
||||
})
|
||||
rythm.start()
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// Register custom event
|
||||
document.addEventListener('keydown', (e) => {
|
||||
cursor = (e.keyCode == KONAMI_CODE[cursor]) ? cursor + 1 : 0;
|
||||
if (cursor == KONAMI_CODE.length) {
|
||||
cursor = (e.keyCode === KONAMI_CODE[cursor]) ? cursor + 1 : 0
|
||||
if (cursor === KONAMI_CODE.length) {
|
||||
afterKonami()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
|
@ -1,440 +1,422 @@
|
|||
var LOCK = false;
|
||||
var LOCK = false
|
||||
|
||||
sources = [];
|
||||
sources_notes_display = [];
|
||||
dests = [];
|
||||
dests_notes_display = [];
|
||||
sources = []
|
||||
sources_notes_display = []
|
||||
dests = []
|
||||
dests_notes_display = []
|
||||
|
||||
function refreshHistory() {
|
||||
$("#history").load("/note/transfer/ #history");
|
||||
function refreshHistory () {
|
||||
$('#history').load('/note/transfer/ #history')
|
||||
}
|
||||
|
||||
function reset(refresh=true) {
|
||||
sources_notes_display.length = 0;
|
||||
sources.length = 0;
|
||||
dests_notes_display.length = 0;
|
||||
dests.length = 0;
|
||||
$("#source_note_list").html("");
|
||||
$("#dest_note_list").html("");
|
||||
let source_field = $("#source_note");
|
||||
source_field.val("");
|
||||
let event = jQuery.Event("keyup");
|
||||
event.originalEvent = {charCode: 97};
|
||||
source_field.trigger(event);
|
||||
source_field.removeClass('is-invalid');
|
||||
source_field.attr("data-original-title", "").tooltip("hide");
|
||||
let dest_field = $("#dest_note");
|
||||
dest_field.val("");
|
||||
dest_field.trigger(event);
|
||||
dest_field.removeClass('is-invalid');
|
||||
dest_field.attr("data-original-title", "").tooltip("hide");
|
||||
let amount_field = $("#amount");
|
||||
amount_field.val("");
|
||||
amount_field.removeClass('is-invalid');
|
||||
$("#amount-required").html("");
|
||||
let reason_field = $("#reason");
|
||||
reason_field.val("");
|
||||
reason_field.removeClass('is-invalid');
|
||||
$("#reason-required").html("");
|
||||
$("#last_name").val("");
|
||||
$("#first_name").val("");
|
||||
$("#bank").val("");
|
||||
$("#user_note").val("");
|
||||
$("#profile_pic").attr("src", "/static/member/img/default_picture.png");
|
||||
$("#profile_pic_link").attr("href", "#");
|
||||
function reset (refresh = true) {
|
||||
sources_notes_display.length = 0
|
||||
sources.length = 0
|
||||
dests_notes_display.length = 0
|
||||
dests.length = 0
|
||||
$('#source_note_list').html('')
|
||||
$('#dest_note_list').html('')
|
||||
const source_field = $('#source_note')
|
||||
source_field.val('')
|
||||
const event = jQuery.Event('keyup')
|
||||
event.originalEvent = { charCode: 97 }
|
||||
source_field.trigger(event)
|
||||
source_field.removeClass('is-invalid')
|
||||
source_field.attr('data-original-title', '').tooltip('hide')
|
||||
const dest_field = $('#dest_note')
|
||||
dest_field.val('')
|
||||
dest_field.trigger(event)
|
||||
dest_field.removeClass('is-invalid')
|
||||
dest_field.attr('data-original-title', '').tooltip('hide')
|
||||
const amount_field = $('#amount')
|
||||
amount_field.val('')
|
||||
amount_field.removeClass('is-invalid')
|
||||
$('#amount-required').html('')
|
||||
const reason_field = $('#reason')
|
||||
reason_field.val('')
|
||||
reason_field.removeClass('is-invalid')
|
||||
$('#reason-required').html('')
|
||||
$('#last_name').val('')
|
||||
$('#first_name').val('')
|
||||
$('#bank').val('')
|
||||
$('#user_note').val('')
|
||||
$('#profile_pic').attr('src', '/static/member/img/default_picture.png')
|
||||
$('#profile_pic_link').attr('href', '#')
|
||||
if (refresh) {
|
||||
refreshBalance();
|
||||
refreshHistory();
|
||||
refreshBalance()
|
||||
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.
|
||||
* More over, get first name and last name to autocomplete fields.
|
||||
*/
|
||||
function checkUniqueNote() {
|
||||
if ($("#type_credit").is(":checked") || $("#type_debit").is(":checked")) {
|
||||
let arr = $("#type_credit").is(":checked") ? dests_notes_display : sources_notes_display;
|
||||
function checkUniqueNote () {
|
||||
if ($('#type_credit').is(':checked') || $('#type_debit').is(':checked')) {
|
||||
const arr = $('#type_credit').is(':checked') ? dests_notes_display : sources_notes_display
|
||||
|
||||
if (arr.length === 0)
|
||||
return;
|
||||
if (arr.length === 0) { return }
|
||||
|
||||
let last = arr[arr.length - 1];
|
||||
arr.length = 0;
|
||||
arr.push(last);
|
||||
const last = arr[arr.length - 1]
|
||||
arr.length = 0
|
||||
arr.push(last)
|
||||
|
||||
last.quantity = 1;
|
||||
last.quantity = 1
|
||||
|
||||
if (!last.note.user) {
|
||||
$.getJSON("/api/note/note/" + last.note.id + "/?format=json", function(note) {
|
||||
last.note.user = note.user;
|
||||
$.getJSON("/api/user/" + last.note.user + "/", function(user) {
|
||||
$("#last_name").val(user.last_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);
|
||||
});
|
||||
$.getJSON('/api/note/note/' + last.note.id + '/?format=json', function (note) {
|
||||
last.note.user = note.user
|
||||
$.getJSON('/api/user/' + last.note.user + '/', function (user) {
|
||||
$('#last_name').val(user.last_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;
|
||||
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);
|
||||
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)
|
||||
|
||||
let source = $("#source_note");
|
||||
let dest = $("#dest_note");
|
||||
const source = $('#source_note')
|
||||
const dest = $('#dest_note')
|
||||
|
||||
$("#type_transfer").change(function() {
|
||||
if (LOCK)
|
||||
return;
|
||||
$('#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_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);
|
||||
$('#source_note_label').text(select_emitters_label)
|
||||
$('#dest_note_label').text(select_receveirs_label)
|
||||
|
||||
location.hash = "transfer";
|
||||
});
|
||||
location.hash = 'transfer'
|
||||
})
|
||||
|
||||
$("#type_credit").change(function() {
|
||||
if (LOCK)
|
||||
return;
|
||||
$('#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_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);
|
||||
$('#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;
|
||||
$('#dest_note_list').html('')
|
||||
dests_notes_display.length = 0
|
||||
}
|
||||
|
||||
location.hash = "credit";
|
||||
});
|
||||
location.hash = 'credit'
|
||||
})
|
||||
|
||||
$("#type_debit").change(function() {
|
||||
if (LOCK)
|
||||
return;
|
||||
$('#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_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);
|
||||
$('#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;
|
||||
$('#source_note_list').html('')
|
||||
sources_notes_display.length = 0
|
||||
}
|
||||
|
||||
location.hash = "debit";
|
||||
});
|
||||
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);
|
||||
});
|
||||
$('#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.
|
||||
let type_transfer = $("#type_transfer"); // Default mode
|
||||
type_transfer.removeAttr('checked');
|
||||
$("#type_credit").removeAttr('checked');
|
||||
$("#type_debit").removeAttr('checked');
|
||||
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();
|
||||
if (location.hash) { $('#type_' + location.hash.substr(1)).click() } else { type_transfer.click() }
|
||||
|
||||
$("#source_me").click(function() {
|
||||
if (LOCK)
|
||||
return;
|
||||
$('#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("");
|
||||
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() {
|
||||
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;
|
||||
setTimeout(fill_note, 100)
|
||||
return
|
||||
}
|
||||
event = jQuery.Event("keypress");
|
||||
event.originalEvent = {charCode: 13};
|
||||
source_note.trigger(event);
|
||||
event = jQuery.Event('keypress')
|
||||
event.originalEvent = { charCode: 13 }
|
||||
source_note.trigger(event)
|
||||
|
||||
source_note.tooltip('hide');
|
||||
source_note.val('');
|
||||
$("#dest_note").focus();
|
||||
};
|
||||
fill_note();
|
||||
});
|
||||
});
|
||||
source_note.tooltip('hide')
|
||||
source_note.val('')
|
||||
$('#dest_note').focus()
|
||||
}
|
||||
fill_note()
|
||||
})
|
||||
})
|
||||
|
||||
$("#btn_transfer").click(function() {
|
||||
if (LOCK)
|
||||
return;
|
||||
$('#btn_transfer').click(function () {
|
||||
if (LOCK) { return }
|
||||
|
||||
LOCK = true;
|
||||
LOCK = true
|
||||
|
||||
let error = false;
|
||||
let error = false
|
||||
|
||||
let amount_field = $("#amount");
|
||||
amount_field.removeClass('is-invalid');
|
||||
$("#amount-required").html("");
|
||||
const amount_field = $('#amount')
|
||||
amount_field.removeClass('is-invalid')
|
||||
$('#amount-required').html('')
|
||||
|
||||
let reason_field = $("#reason");
|
||||
reason_field.removeClass('is-invalid');
|
||||
$("#reason-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;
|
||||
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());
|
||||
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;
|
||||
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;
|
||||
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 (!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 (!dests_notes_display.length && !$('#type_debit').is(':checked')) {
|
||||
$('#dest_note').addClass('is-invalid')
|
||||
error = true
|
||||
}
|
||||
|
||||
if (error) {
|
||||
LOCK = false;
|
||||
return;
|
||||
LOCK = false
|
||||
return
|
||||
}
|
||||
|
||||
let reason = reason_field.val();
|
||||
let reason = reason_field.val()
|
||||
|
||||
if ($("#type_transfer").is(':checked')) {
|
||||
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;
|
||||
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/",
|
||||
$.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
|
||||
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 (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;
|
||||
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;
|
||||
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);
|
||||
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();
|
||||
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;
|
||||
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
|
||||
}
|
||||
|
||||
$.post("/api/note/transaction/transaction/",
|
||||
$.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
|
||||
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();
|
||||
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 + ")";
|
||||
const 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
|
||||
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 + ')' }
|
||||
}
|
||||
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/",
|
||||
$.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()
|
||||
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();
|
||||
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) {
|
||||
let 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;
|
||||
});
|
||||
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
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue