From 71346476dfa494bf083aaf494408b49b95ba87ad Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 21 Mar 2020 00:30:49 +0100 Subject: [PATCH 01/37] Starting treasury app --- apps/treasury/__init__.py | 0 apps/treasury/api/__init__.py | 0 apps/treasury/apps.py | 11 +++ apps/treasury/migrations/__init__.py | 0 apps/treasury/models.py | 113 +++++++++++++++++++++++++++ apps/treasury/tables.py | 20 +++++ apps/treasury/urls.py | 11 +++ apps/treasury/views.py | 16 ++++ note_kfet/settings/base.py | 1 + note_kfet/urls.py | 1 + templates/base.html | 3 + templates/treasury/billing_list.html | 21 +++++ 12 files changed, 197 insertions(+) create mode 100644 apps/treasury/__init__.py create mode 100644 apps/treasury/api/__init__.py create mode 100644 apps/treasury/apps.py create mode 100644 apps/treasury/migrations/__init__.py create mode 100644 apps/treasury/models.py create mode 100644 apps/treasury/tables.py create mode 100644 apps/treasury/urls.py create mode 100644 apps/treasury/views.py create mode 100644 templates/treasury/billing_list.html diff --git a/apps/treasury/__init__.py b/apps/treasury/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/treasury/api/__init__.py b/apps/treasury/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/treasury/apps.py b/apps/treasury/apps.py new file mode 100644 index 00000000..5f7f6dc9 --- /dev/null +++ b/apps/treasury/apps.py @@ -0,0 +1,11 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.apps import AppConfig +from django.db.models.signals import pre_save, post_save, post_delete +from django.utils.translation import gettext_lazy as _ + + +class TreasuryConfig(AppConfig): + name = 'treasury' + verbose_name = _('Treasury') diff --git a/apps/treasury/migrations/__init__.py b/apps/treasury/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/treasury/models.py b/apps/treasury/models.py new file mode 100644 index 00000000..51974ec0 --- /dev/null +++ b/apps/treasury/models.py @@ -0,0 +1,113 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class Billing(models.Model): + id = models.PositiveIntegerField( + primary_key=True, + verbose_name=_("Billing identifier"), + ) + + subject = models.CharField( + max_length=255, + verbose_name=_("Subject"), + ) + + description = models.TextField( + verbose_name=_("Description") + ) + + name = models.CharField( + max_length=255, + verbose_name=_("Name"), + ) + + address = models.TextField( + verbose_name=_("Address"), + ) + + date = models.DateField( + auto_now_add=True, + verbose_name=_("Place"), + ) + + acquitted = models.BooleanField( + verbose_name=_("Acquitted"), + ) + + place = models.CharField( + max_length=255, + default="Cachan", + verbose_name=_("Place"), + ) + + my_name = models.CharField( + max_length=255, + default="BDE ENS Cachan", + verbose_name=_("My name"), + ) + + my_address_street = models.CharField( + max_length=255, + default="61 avenue du Président Wilson", + verbose_name=_("My street address"), + ) + + my_city = models.CharField( + max_length=255, + default="94230 Cachan", + verbose_name=_("My city"), + ) + + bank_code = models.IntegerField( + default=30003, + verbose_name=_("Bank code"), + ) + + desk_code = models.IntegerField( + default=3894, + verbose_name=_("Desk code"), + ) + + account_number = models.IntegerField( + default=37280662, + verbose_name=_("Account number"), + ) + + rib_key = models.SmallIntegerField( + default=14, + verbose_name=_("RIB Key") + ) + + bic = models.CharField( + max_length=16, + default="SOGEFRPP", + verbose_name=_("BIC Code") + ) + + +class Product(models.Model): + billing = models.ForeignKey( + Billing, + on_delete=models.PROTECT, + ) + + designation = models.CharField( + max_length=255, + verbose_name=_("Designation"), + ) + + quantity = models.PositiveIntegerField( + verbose_name=_("Quantity") + ) + + amount = models.PositiveIntegerField( + verbose_name=_("Unit price") + ) + + @property + def total(self): + return self.quantity * self.amount diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py new file mode 100644 index 00000000..b0b4f1db --- /dev/null +++ b/apps/treasury/tables.py @@ -0,0 +1,20 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django_tables2 import tables + +from .models import Billing + + +class BillingTable(tables.Table): + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + model = Billing + template_name = 'django_tables2/bootstrap4.html' + fields = ('id', 'name', 'subject', 'acquitted', ) + row_attrs = { + 'class': 'table-row', + 'data-href': lambda record: record.pk + } \ No newline at end of file diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py new file mode 100644 index 00000000..7cd68ec0 --- /dev/null +++ b/apps/treasury/urls.py @@ -0,0 +1,11 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.urls import path + +from .views import BillingListView + +app_name = 'treasury' +urlpatterns = [ + path('billing/', BillingListView.as_view(), name='billing'), +] diff --git a/apps/treasury/views.py b/apps/treasury/views.py new file mode 100644 index 00000000..435f8cdb --- /dev/null +++ b/apps/treasury/views.py @@ -0,0 +1,16 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.contrib.auth.mixins import LoginRequiredMixin +from django_tables2 import SingleTableView + +from .models import Billing +from .tables import BillingTable + + +class BillingListView(LoginRequiredMixin, SingleTableView): + """ + List existing Billings + """ + model = Billing + table_class = BillingTable diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 0694390d..d85071c5 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -61,6 +61,7 @@ INSTALLED_APPS = [ 'activity', 'member', 'note', + 'treasury', 'api', 'logs', ] diff --git a/note_kfet/urls.py b/note_kfet/urls.py index da2f9d6c..80808bbe 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ # Include project routers path('note/', include('note.urls')), + path('treasury/', include('treasury.urls')), # Include Django Contrib and Core routers path('i18n/', include('django.conf.urls.i18n')), diff --git a/templates/base.html b/templates/base.html index d57dab89..88ad67f9 100644 --- a/templates/base.html +++ b/templates/base.html @@ -81,6 +81,9 @@ SPDX-License-Identifier: GPL-3.0-or-later + diff --git a/templates/treasury/billing_form.html b/templates/treasury/invoice_form.html similarity index 95% rename from templates/treasury/billing_form.html rename to templates/treasury/invoice_form.html index 9c1416ed..2177d0f7 100644 --- a/templates/treasury/billing_form.html +++ b/templates/treasury/invoice_form.html @@ -3,7 +3,7 @@ {% load i18n %} {% load crispy_forms_tags pretty_money %} {% block content %} -

{% trans "Billings list" %}

+

{% trans "Invoices list" %}

{% csrf_token %} {% crispy form %} @@ -31,7 +31,7 @@ - {{ form.billing }} + {{ form.invoice }} {{ form.id }} {% endfor %} @@ -63,7 +63,7 @@ - {{ formset.empty_form.billing }} + {{ formset.empty_form.invoice }} {{ formset.empty_form.id }} diff --git a/templates/treasury/billing_list.html b/templates/treasury/invoice_list.html similarity index 59% rename from templates/treasury/billing_list.html rename to templates/treasury/invoice_list.html index 749c0767..f80e2342 100644 --- a/templates/treasury/billing_list.html +++ b/templates/treasury/invoice_list.html @@ -5,6 +5,6 @@ {% render_table table %} -{% trans "New billing" %} +{% trans "New invoice" %} {% endblock %} diff --git a/templates/treasury/billing_sample.tex b/templates/treasury/invoice_sample.tex similarity index 100% rename from templates/treasury/billing_sample.tex rename to templates/treasury/invoice_sample.tex From 70cf186233c889d15e71fb1f81da3eabcc0d973b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 14:12:45 +0100 Subject: [PATCH 13/37] Fix fixtures, another time... --- apps/note/fixtures/initial.json | 12 ++--- apps/permission/fixtures/initial.json | 66 +++++++++++++-------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/note/fixtures/initial.json b/apps/note/fixtures/initial.json index 3654fa2f..119e1c28 100644 --- a/apps/note/fixtures/initial.json +++ b/apps/note/fixtures/initial.json @@ -3,7 +3,7 @@ "model": "note.note", "pk": 1, "fields": { - "polymorphic_ctype": 40, + "polymorphic_ctype": 23, "balance": 0, "is_active": true, "display_image": "", @@ -14,7 +14,7 @@ "model": "note.note", "pk": 2, "fields": { - "polymorphic_ctype": 40, + "polymorphic_ctype": 23, "balance": 0, "is_active": true, "display_image": "", @@ -25,7 +25,7 @@ "model": "note.note", "pk": 3, "fields": { - "polymorphic_ctype": 40, + "polymorphic_ctype": 23, "balance": 0, "is_active": true, "display_image": "", @@ -36,7 +36,7 @@ "model": "note.note", "pk": 4, "fields": { - "polymorphic_ctype": 40, + "polymorphic_ctype": 23, "balance": 0, "is_active": true, "display_image": "", @@ -47,7 +47,7 @@ "model": "note.note", "pk": 5, "fields": { - "polymorphic_ctype": 39, + "polymorphic_ctype": 22, "balance": 0, "is_active": true, "display_image": "", @@ -58,7 +58,7 @@ "model": "note.note", "pk": 6, "fields": { - "polymorphic_ctype": 39, + "polymorphic_ctype": 22, "balance": 0, "is_active": true, "display_image": "", diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 69900372..da1fa486 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -83,7 +83,7 @@ "model": "permission.permission", "pk": 1, "fields": { - "model": 21, + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "view", "mask": 1, @@ -95,7 +95,7 @@ "model": "permission.permission", "pk": 2, "fields": { - "model": 31, + "model": 14, "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -107,7 +107,7 @@ "model": "permission.permission", "pk": 3, "fields": { - "model": 34, + "model": 24, "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", "type": "view", "mask": 1, @@ -119,7 +119,7 @@ "model": "permission.permission", "pk": 4, "fields": { - "model": 25, + "model": 8, "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -131,7 +131,7 @@ "model": "permission.permission", "pk": 5, "fields": { - "model": 36, + "model": 19, "query": "[\"OR\", {\"source\": [\"user\", \"note\"]}, {\"destination\": [\"user\", \"note\"]}]", "type": "view", "mask": 1, @@ -143,7 +143,7 @@ "model": "permission.permission", "pk": 6, "fields": { - "model": 33, + "model": 16, "query": "[\"OR\", {\"note__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club__name\": \"Kfet\"}], [\"all\"]]}, {\"note__in\": [\"NoteClub\", \"objects\", [\"all\"]]}]", "type": "view", "mask": 1, @@ -155,7 +155,7 @@ "model": "permission.permission", "pk": 7, "fields": { - "model": 21, + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -167,7 +167,7 @@ "model": "permission.permission", "pk": 8, "fields": { - "model": 21, + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -179,7 +179,7 @@ "model": "permission.permission", "pk": 9, "fields": { - "model": 21, + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -191,7 +191,7 @@ "model": "permission.permission", "pk": 10, "fields": { - "model": 21, + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -203,7 +203,7 @@ "model": "permission.permission", "pk": 11, "fields": { - "model": 21, + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -215,7 +215,7 @@ "model": "permission.permission", "pk": 12, "fields": { - "model": 25, + "model": 8, "query": "{\"user\": [\"user\"]}", "type": "delete", "mask": 1, @@ -227,7 +227,7 @@ "model": "permission.permission", "pk": 13, "fields": { - "model": 25, + "model": 8, "query": "{\"user\": [\"user\"]}", "type": "add", "mask": 1, @@ -239,7 +239,7 @@ "model": "permission.permission", "pk": 14, "fields": { - "model": 33, + "model": 16, "query": "{\"note\": [\"user\", \"note\"]}", "type": "delete", "mask": 1, @@ -251,7 +251,7 @@ "model": "permission.permission", "pk": 15, "fields": { - "model": 33, + "model": 16, "query": "{\"note\": [\"user\", \"note\"]}", "type": "add", "mask": 1, @@ -263,7 +263,7 @@ "model": "permission.permission", "pk": 16, "fields": { - "model": 34, + "model": 24, "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", "type": "change", "mask": 1, @@ -275,7 +275,7 @@ "model": "permission.permission", "pk": 17, "fields": { - "model": 36, + "model": 19, "query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, {\"amount__lte\": [\"user\", \"note\", \"balance\"]}]", "type": "add", "mask": 1, @@ -287,7 +287,7 @@ "model": "permission.permission", "pk": 18, "fields": { - "model": 34, + "model": 17, "query": "{}", "type": "change", "mask": 1, @@ -299,7 +299,7 @@ "model": "permission.permission", "pk": 19, "fields": { - "model": 34, + "model": 17, "query": "[\"OR\", {\"pk\": [\"club\", \"note\", \"pk\"]}, {\"pk__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club\": [\"club\"]}], [\"all\"]]}]", "type": "view", "mask": 2, @@ -311,7 +311,7 @@ "model": "permission.permission", "pk": 20, "fields": { - "model": 36, + "model": 19, "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]", "type": "add", "mask": 2, @@ -323,7 +323,7 @@ "model": "permission.permission", "pk": 21, "fields": { - "model": 42, + "model": 25, "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]", "type": "add", "mask": 2, @@ -335,7 +335,7 @@ "model": "permission.permission", "pk": 22, "fields": { - "model": 29, + "model": 12, "query": "{\"pk\": [\"club\", \"pk\"]}", "type": "view", "mask": 1, @@ -347,7 +347,7 @@ "model": "permission.permission", "pk": 23, "fields": { - "model": 36, + "model": 19, "query": "{}", "type": "change", "mask": 1, @@ -359,7 +359,7 @@ "model": "permission.permission", "pk": 24, "fields": { - "model": 36, + "model": 19, "query": "{}", "type": "view", "mask": 2, @@ -371,7 +371,7 @@ "model": "permission.permission", "pk": 25, "fields": { - "model": 40, + "model": 23, "query": "{}", "type": "view", "mask": 2, @@ -383,7 +383,7 @@ "model": "permission.permission", "pk": 26, "fields": { - "model": 43, + "model": 26, "query": "{}", "type": "add", "mask": 2, @@ -395,7 +395,7 @@ "model": "permission.permission", "pk": 27, "fields": { - "model": 35, + "model": 18, "query": "{}", "type": "view", "mask": 2, @@ -407,7 +407,7 @@ "model": "permission.permission", "pk": 28, "fields": { - "model": 35, + "model": 18, "query": "{}", "type": "change", "mask": 3, @@ -419,7 +419,7 @@ "model": "permission.permission", "pk": 29, "fields": { - "model": 35, + "model": 18, "query": "{}", "type": "add", "mask": 3, @@ -431,7 +431,7 @@ "model": "permission.permission", "pk": 30, "fields": { - "model": 37, + "model": 20, "query": "{}", "type": "view", "mask": 2, @@ -443,7 +443,7 @@ "model": "permission.permission", "pk": 31, "fields": { - "model": 37, + "model": 20, "query": "{}", "type": "add", "mask": 3, @@ -455,7 +455,7 @@ "model": "permission.permission", "pk": 32, "fields": { - "model": 37, + "model": 20, "query": "{}", "type": "change", "mask": 3, @@ -467,7 +467,7 @@ "model": "permission.permission", "pk": 33, "fields": { - "model": 36, + "model": 19, "query": "{}", "type": "add", "mask": 2, From c941585850afffe6679b162279fc2387929e63aa Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 14:15:19 +0100 Subject: [PATCH 14/37] Fix production installation --- requirements/production.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/production.txt b/requirements/production.txt index f0b52228..fe939cce 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -1 +1 @@ -psycopg2==2.8.4 +psycopg2-binary==2.8.4 From 1e5065b80bb39a9b322c853ec2b6a117f10b3240 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 14:54:05 +0100 Subject: [PATCH 15/37] Fixtures are more natural --- apps/note/fixtures/initial.json | 475 ++++++++++++++------------ apps/permission/fixtures/initial.json | 165 +++++++-- 2 files changed, 389 insertions(+), 251 deletions(-) diff --git a/apps/note/fixtures/initial.json b/apps/note/fixtures/initial.json index 119e1c28..a0682dae 100644 --- a/apps/note/fixtures/initial.json +++ b/apps/note/fixtures/initial.json @@ -1,220 +1,259 @@ [ - { - "model": "note.note", - "pk": 1, - "fields": { - "polymorphic_ctype": 23, - "balance": 0, - "is_active": true, - "display_image": "", - "created_at": "2020-02-20T20:02:48.778Z" - } - }, - { - "model": "note.note", - "pk": 2, - "fields": { - "polymorphic_ctype": 23, - "balance": 0, - "is_active": true, - "display_image": "", - "created_at": "2020-02-20T20:06:39.546Z" - } - }, - { - "model": "note.note", - "pk": 3, - "fields": { - "polymorphic_ctype": 23, - "balance": 0, - "is_active": true, - "display_image": "", - "created_at": "2020-02-20T20:06:43.049Z" - } - }, - { - "model": "note.note", - "pk": 4, - "fields": { - "polymorphic_ctype": 23, - "balance": 0, - "is_active": true, - "display_image": "", - "created_at": "2020-02-20T20:06:50.996Z" - } - }, - { - "model": "note.note", - "pk": 5, - "fields": { - "polymorphic_ctype": 22, - "balance": 0, - "is_active": true, - "display_image": "", - "created_at": "2020-02-20T20:09:38.615Z" - } - }, - { - "model": "note.note", - "pk": 6, - "fields": { - "polymorphic_ctype": 22, - "balance": 0, - "is_active": true, - "display_image": "", - "created_at": "2020-02-20T20:16:14.753Z" - } - }, - { - "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.noteclub", - "pk": 5, - "fields": { - "club": 1 - } - }, - { - "model": "note.noteclub", - "pk": 6, - "fields": { - "club": 2 - } - }, - { - "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 - } - }, - { - "model": "note.templatecategory", - "pk": 1, - "fields": { - "name": "Soft" - } - }, - { - "model": "note.templatecategory", - "pk": 2, - "fields": { - "name": "Pulls" - } - }, - { - "model": "note.templatecategory", - "pk": 3, - "fields": { - "name": "Gala" - } - }, - { - "model": "note.templatecategory", - "pk": 4, - "fields": { - "name": "Clubs" - } - }, - { - "model": "note.templatecategory", - "pk": 5, - "fields": { - "name": "Bouffe" - } - }, - { - "model": "note.templatecategory", - "pk": 6, - "fields": { - "name": "BDA" - } - }, - { - "model": "note.templatecategory", - "pk": 7, - "fields": { - "name": "Autre" - } - }, - { - "model": "note.templatecategory", - "pk": 8, - "fields": { - "name": "Alcool" - } + { + "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": "", + "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": "", + "created_at": "2020-02-20T20:16:14.753Z" + } + }, + { + "model": "note.note", + "pk": 7, + "fields": { + "polymorphic_ctype": [ + "note", + "noteuser" + ], + "balance": 0, + "last_negative": null, + "is_active": true, + "display_image": "pic/default.png", + "created_at": "2020-03-22T13:01:35.680Z" + } + }, + { + "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 + } + }, + { + "model": "note.templatecategory", + "pk": 1, + "fields": { + "name": "Soft" + } + }, + { + "model": "note.templatecategory", + "pk": 2, + "fields": { + "name": "Pulls" + } + }, + { + "model": "note.templatecategory", + "pk": 3, + "fields": { + "name": "Gala" + } + }, + { + "model": "note.templatecategory", + "pk": 4, + "fields": { + "name": "Clubs" + } + }, + { + "model": "note.templatecategory", + "pk": 5, + "fields": { + "name": "Bouffe" + } + }, + { + "model": "note.templatecategory", + "pk": 6, + "fields": { + "name": "BDA" + } + }, + { + "model": "note.templatecategory", + "pk": 7, + "fields": { + "name": "Autre" + } + }, + { + "model": "note.templatecategory", + "pk": 8, + "fields": { + "name": "Alcool" + } + } +] \ No newline at end of file diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index da1fa486..3ca2b248 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -83,7 +83,10 @@ "model": "permission.permission", "pk": 1, "fields": { - "model": 4, + "model": [ + "auth", + "user" + ], "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "view", "mask": 1, @@ -95,7 +98,10 @@ "model": "permission.permission", "pk": 2, "fields": { - "model": 14, + "model": [ + "member", + "profile" + ], "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -107,7 +113,10 @@ "model": "permission.permission", "pk": 3, "fields": { - "model": 24, + "model": [ + "note", + "noteuser" + ], "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", "type": "view", "mask": 1, @@ -119,7 +128,10 @@ "model": "permission.permission", "pk": 4, "fields": { - "model": 8, + "model": [ + "authtoken", + "token" + ], "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -131,7 +143,10 @@ "model": "permission.permission", "pk": 5, "fields": { - "model": 19, + "model": [ + "note", + "transaction" + ], "query": "[\"OR\", {\"source\": [\"user\", \"note\"]}, {\"destination\": [\"user\", \"note\"]}]", "type": "view", "mask": 1, @@ -143,7 +158,10 @@ "model": "permission.permission", "pk": 6, "fields": { - "model": 16, + "model": [ + "note", + "alias" + ], "query": "[\"OR\", {\"note__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club__name\": \"Kfet\"}], [\"all\"]]}, {\"note__in\": [\"NoteClub\", \"objects\", [\"all\"]]}]", "type": "view", "mask": 1, @@ -155,7 +173,10 @@ "model": "permission.permission", "pk": 7, "fields": { - "model": 4, + "model": [ + "auth", + "user" + ], "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -167,7 +188,10 @@ "model": "permission.permission", "pk": 8, "fields": { - "model": 4, + "model": [ + "auth", + "user" + ], "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -179,7 +203,10 @@ "model": "permission.permission", "pk": 9, "fields": { - "model": 4, + "model": [ + "auth", + "user" + ], "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -191,7 +218,10 @@ "model": "permission.permission", "pk": 10, "fields": { - "model": 4, + "model": [ + "auth", + "user" + ], "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -203,7 +233,10 @@ "model": "permission.permission", "pk": 11, "fields": { - "model": 4, + "model": [ + "auth", + "user" + ], "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -215,7 +248,10 @@ "model": "permission.permission", "pk": 12, "fields": { - "model": 8, + "model": [ + "authtoken", + "token" + ], "query": "{\"user\": [\"user\"]}", "type": "delete", "mask": 1, @@ -227,7 +263,10 @@ "model": "permission.permission", "pk": 13, "fields": { - "model": 8, + "model": [ + "authtoken", + "token" + ], "query": "{\"user\": [\"user\"]}", "type": "add", "mask": 1, @@ -239,7 +278,10 @@ "model": "permission.permission", "pk": 14, "fields": { - "model": 16, + "model": [ + "note", + "alias" + ], "query": "{\"note\": [\"user\", \"note\"]}", "type": "delete", "mask": 1, @@ -251,7 +293,10 @@ "model": "permission.permission", "pk": 15, "fields": { - "model": 16, + "model": [ + "note", + "alias" + ], "query": "{\"note\": [\"user\", \"note\"]}", "type": "add", "mask": 1, @@ -263,7 +308,10 @@ "model": "permission.permission", "pk": 16, "fields": { - "model": 24, + "model": [ + "note", + "noteuser" + ], "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", "type": "change", "mask": 1, @@ -275,7 +323,10 @@ "model": "permission.permission", "pk": 17, "fields": { - "model": 19, + "model": [ + "note", + "transaction" + ], "query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, {\"amount__lte\": [\"user\", \"note\", \"balance\"]}]", "type": "add", "mask": 1, @@ -287,7 +338,10 @@ "model": "permission.permission", "pk": 18, "fields": { - "model": 17, + "model": [ + "note", + "note" + ], "query": "{}", "type": "change", "mask": 1, @@ -299,7 +353,10 @@ "model": "permission.permission", "pk": 19, "fields": { - "model": 17, + "model": [ + "note", + "note" + ], "query": "[\"OR\", {\"pk\": [\"club\", \"note\", \"pk\"]}, {\"pk__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club\": [\"club\"]}], [\"all\"]]}]", "type": "view", "mask": 2, @@ -311,7 +368,10 @@ "model": "permission.permission", "pk": 20, "fields": { - "model": 19, + "model": [ + "note", + "transaction" + ], "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]", "type": "add", "mask": 2, @@ -323,7 +383,10 @@ "model": "permission.permission", "pk": 21, "fields": { - "model": 25, + "model": [ + "note", + "recurrenttransaction" + ], "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]", "type": "add", "mask": 2, @@ -335,7 +398,10 @@ "model": "permission.permission", "pk": 22, "fields": { - "model": 12, + "model": [ + "member", + "club" + ], "query": "{\"pk\": [\"club\", \"pk\"]}", "type": "view", "mask": 1, @@ -347,7 +413,10 @@ "model": "permission.permission", "pk": 23, "fields": { - "model": 19, + "model": [ + "note", + "transaction" + ], "query": "{}", "type": "change", "mask": 1, @@ -359,7 +428,10 @@ "model": "permission.permission", "pk": 24, "fields": { - "model": 19, + "model": [ + "note", + "transaction" + ], "query": "{}", "type": "view", "mask": 2, @@ -371,7 +443,10 @@ "model": "permission.permission", "pk": 25, "fields": { - "model": 23, + "model": [ + "note", + "notespecial" + ], "query": "{}", "type": "view", "mask": 2, @@ -383,7 +458,10 @@ "model": "permission.permission", "pk": 26, "fields": { - "model": 26, + "model": [ + "note", + "specialtransaction" + ], "query": "{}", "type": "add", "mask": 2, @@ -395,7 +473,10 @@ "model": "permission.permission", "pk": 27, "fields": { - "model": 18, + "model": [ + "note", + "templatecategory" + ], "query": "{}", "type": "view", "mask": 2, @@ -407,7 +488,10 @@ "model": "permission.permission", "pk": 28, "fields": { - "model": 18, + "model": [ + "note", + "templatecategory" + ], "query": "{}", "type": "change", "mask": 3, @@ -419,7 +503,10 @@ "model": "permission.permission", "pk": 29, "fields": { - "model": 18, + "model": [ + "note", + "templatecategory" + ], "query": "{}", "type": "add", "mask": 3, @@ -431,7 +518,10 @@ "model": "permission.permission", "pk": 30, "fields": { - "model": 20, + "model": [ + "note", + "transactiontemplate" + ], "query": "{}", "type": "view", "mask": 2, @@ -443,7 +533,10 @@ "model": "permission.permission", "pk": 31, "fields": { - "model": 20, + "model": [ + "note", + "transactiontemplate" + ], "query": "{}", "type": "add", "mask": 3, @@ -455,7 +548,10 @@ "model": "permission.permission", "pk": 32, "fields": { - "model": 20, + "model": [ + "note", + "transactiontemplate" + ], "query": "{}", "type": "change", "mask": 3, @@ -467,7 +563,10 @@ "model": "permission.permission", "pk": 33, "fields": { - "model": 19, + "model": [ + "note", + "transaction" + ], "query": "{}", "type": "add", "mask": 2, From f6027e9edd984ed96dcbb46e7bda38ccdc453b16 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 14:57:51 +0100 Subject: [PATCH 16/37] Fix CI --- apps/permission/admin.py | 1 - apps/permission/api/views.py | 2 +- apps/permission/models.py | 2 -- apps/permission/signals.py | 3 +-- apps/permission/templatetags/perms.py | 4 +--- tox.ini | 2 +- 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/permission/admin.py b/apps/permission/admin.py index aaa6f661..4312f4b0 100644 --- a/apps/permission/admin.py +++ b/apps/permission/admin.py @@ -28,4 +28,3 @@ class RolePermissionsAdmin(admin.ModelAdmin): Admin customisation for RolePermissions """ list_display = ('role', ) - diff --git a/apps/permission/api/views.py b/apps/permission/api/views.py index 6087c83e..965e82c9 100644 --- a/apps/permission/api/views.py +++ b/apps/permission/api/views.py @@ -2,8 +2,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django_filters.rest_framework import DjangoFilterBackend - from api.viewsets import ReadOnlyProtectedModelViewSet + from .serializers import PermissionSerializer from ..models import Permission diff --git a/apps/permission/models.py b/apps/permission/models.py index 109c1875..205f5b41 100644 --- a/apps/permission/models.py +++ b/apps/permission/models.py @@ -10,7 +10,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.db.models import F, Q, Model from django.utils.translation import gettext_lazy as _ - from member.models import Role @@ -281,4 +280,3 @@ class RolePermissions(models.Model): def __str__(self): return str(self.role) - diff --git a/apps/permission/signals.py b/apps/permission/signals.py index aebca39d..1e30f56f 100644 --- a/apps/permission/signals.py +++ b/apps/permission/signals.py @@ -3,10 +3,9 @@ from django.core.exceptions import PermissionDenied from django.db.models.signals import pre_save, pre_delete, post_save, post_delete - from logs import signals as logs_signals -from permission.backends import PermissionBackend from note_kfet.middlewares import get_current_authenticated_user +from permission.backends import PermissionBackend EXCLUDED = [ diff --git a/apps/permission/templatetags/perms.py b/apps/permission/templatetags/perms.py index 8f2a0006..8bcd3597 100644 --- a/apps/permission/templatetags/perms.py +++ b/apps/permission/templatetags/perms.py @@ -3,10 +3,8 @@ from django.contrib.contenttypes.models import ContentType from django.template.defaultfilters import stringfilter - -from note_kfet.middlewares import get_current_authenticated_user, get_current_session from django import template - +from note_kfet.middlewares import get_current_authenticated_user, get_current_session from permission.backends import PermissionBackend diff --git a/tox.ini b/tox.ini index bbfab4e7..01bf4edb 100644 --- a/tox.ini +++ b/tox.ini @@ -30,7 +30,7 @@ deps = pep8-naming pyflakes commands = - flake8 apps/activity apps/api apps/logs apps/member apps/note apps/treasury + flake8 apps/activity apps/api apps/logs apps/member apps/note apps/permission apps/treasury [flake8] # Ignore too many errors, should be reduced in the future From 15bfdd9f96d2c876f247f7460e7dac80d9c476ca Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 15:24:54 +0100 Subject: [PATCH 17/37] Repair Invoice model --- apps/treasury/admin.py | 2 +- apps/treasury/forms.py | 10 +++++ apps/treasury/models.py | 54 +-------------------------- apps/treasury/tables.py | 2 +- apps/treasury/views.py | 32 +++++++++------- locale/de/LC_MESSAGES/django.po | 2 +- locale/fr/LC_MESSAGES/django.po | 2 +- templates/treasury/invoice_sample.tex | 2 +- 8 files changed, 35 insertions(+), 71 deletions(-) diff --git a/apps/treasury/admin.py b/apps/treasury/admin.py index 74b52150..cef9e37d 100644 --- a/apps/treasury/admin.py +++ b/apps/treasury/admin.py @@ -8,7 +8,7 @@ from .models import Invoice, Product @admin.register(Invoice) class InvoiceAdmin(admin.ModelAdmin): - list_display = ('id', 'name', 'subject', 'acquitted', ) + list_display = ('id', 'name', 'object', 'acquitted', ) @admin.register(Product) diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index d4b763ea..56fcce27 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -1,6 +1,8 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +import datetime + from crispy_forms.helper import FormHelper from django import forms @@ -8,6 +10,14 @@ from .models import Invoice, Product class InvoiceForm(forms.ModelForm): + date = forms.DateField( + initial=datetime.date.today, + widget=forms.TextInput(attrs={'type': 'date'}) + ) + + def clean_date(self): + self.instance.date = self.data.get("date") + class Meta: model = Invoice fields = '__all__' diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 3d1d0c48..52caeb0b 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -25,9 +25,9 @@ class Invoice(models.Model): verbose_name=_("BDE"), ) - subject = models.CharField( + object = models.CharField( max_length=255, - verbose_name=_("Subject"), + verbose_name=_("Object"), ) description = models.TextField( @@ -52,56 +52,6 @@ class Invoice(models.Model): verbose_name=_("Acquitted"), ) - place = models.CharField( - max_length=255, - default="Cachan", - verbose_name=_("Place"), - ) - - my_name = models.CharField( - max_length=255, - default="BDE ENS Cachan", - verbose_name=_("My name"), - ) - - my_address_street = models.CharField( - max_length=255, - default="61 avenue du Président Wilson", - verbose_name=_("My street address"), - ) - - my_city = models.CharField( - max_length=255, - default="94230 Cachan", - verbose_name=_("My city"), - ) - - bank_code = models.IntegerField( - default=30003, - verbose_name=_("Bank code"), - ) - - desk_code = models.IntegerField( - default=3894, - verbose_name=_("Desk code"), - ) - - account_number = models.IntegerField( - default=37280662, - verbose_name=_("Account number"), - ) - - rib_key = models.SmallIntegerField( - default=14, - verbose_name=_("RIB Key") - ) - - bic = models.CharField( - max_length=16, - default="SOGEFRPP", - verbose_name=_("BIC Code") - ) - class Product(models.Model): invoice = models.ForeignKey( diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index 156f6dcd..5dcfbcd6 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -29,4 +29,4 @@ class InvoiceTable(tables.Table): } model = Invoice template_name = 'django_tables2/bootstrap4.html' - fields = ('id', 'name', 'subject', 'acquitted', 'invoice',) + fields = ('id', 'name', 'object', 'acquitted', 'invoice',) diff --git a/apps/treasury/views.py b/apps/treasury/views.py index dcf4e913..8b5446d6 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -47,9 +47,9 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView): kwargs = {} for key in self.request.POST: value = self.request.POST[key] - if key.endswith("amount"): + if key.endswith("amount") and value: kwargs[key] = str(int(100 * float(value))) - else: + elif value: kwargs[key] = value formset = ProductFormSet(kwargs, instance=form.instance) @@ -87,6 +87,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): form = context['form'] form.helper = FormHelper() form.helper.form_tag = False + form.fields['date'].initial = form.instance.date form_set = ProductFormSet(instance=form.instance) context['formset'] = form_set context['helper'] = ProductFormSetHelper() @@ -100,26 +101,22 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): kwargs = {} for key in self.request.POST: value = self.request.POST[key] - if key.endswith("amount"): + if key.endswith("amount") and value: kwargs[key] = str(int(100 * float(value))) - else: + elif value: kwargs[key] = value formset = ProductFormSet(kwargs, instance=form.instance) saved = [] - print(formset.errors) if formset.is_valid(): for f in formset: if f.is_valid() and f.instance.designation: - if type(f.instance.pk) == 'number' and f.instance.pk <= 0: - f.instance.pk = None f.save() f.instance.save() saved.append(f.instance.pk) else: f.instance = None - - Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete() + Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete() return ret @@ -137,8 +134,18 @@ class InvoiceRenderView(LoginRequiredMixin, View): invoice = Invoice.objects.get(pk=pk) products = Product.objects.filter(invoice=invoice).all() - invoice.description = invoice.description.replace("\n", "\\newline\n") - invoice.address = invoice.address.replace("\n", "\\newline\n") + invoice.place = "Cachan" + invoice.my_name = "BDE ENS Cachan" + invoice.my_address_street = "61 avenue du Président Wilson" + invoice.my_city = "94230 Cachan" + invoice.bank_code = 30003 + invoice.desk_code = 3894 + invoice.account_number = 37280662 + invoice.rib_key = 14 + invoice.bic = "SOGEFRPP" + + invoice.description = invoice.description.replace("\r", "").replace("\n", "\\\\ ") + invoice.address = invoice.address.replace("\r", "").replace("\n", "\\\\ ") tex = render_to_string("treasury/invoice_sample.tex", dict(obj=invoice, products=products)) try: os.mkdir(BASE_DIR + "/tmp") @@ -155,9 +162,6 @@ class InvoiceRenderView(LoginRequiredMixin, View): error = subprocess.Popen( ["pdflatex", "invoice-{}.tex".format(pk)], cwd=tmp_dir, - stdin=open(os.devnull, "r"), - stderr=open(os.devnull, "wb"), - stdout=open(os.devnull, "wb") ).wait() if error: diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 29ec1bdc..4e950765 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -505,7 +505,7 @@ msgid "BDE" msgstr "" #: apps/treasury/models.py:30 -msgid "Subject" +msgid "Object" msgstr "" #: apps/treasury/models.py:34 diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index b137cc0e..8770d433 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -505,7 +505,7 @@ msgid "BDE" msgstr "BDE" #: apps/treasury/models.py:30 -msgid "Subject" +msgid "Object" msgstr "Objet" #: apps/treasury/models.py:34 diff --git a/templates/treasury/invoice_sample.tex b/templates/treasury/invoice_sample.tex index 625d10ba..3c76403e 100644 --- a/templates/treasury/invoice_sample.tex +++ b/templates/treasury/invoice_sample.tex @@ -85,7 +85,7 @@ \def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non \def\FactureLieu {{"{"}}{{ obj.place }}} % Lieu de l'édition de la facture \def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture -\def\FactureObjet {{"{"}}{{ obj.subject|safe }} } % Objet du document +\def\FactureObjet {{"{"}}{{ obj.object|safe }} } % Objet du document % Description de la facture \def\FactureDescr {{"{"}}{{ obj.description|safe }}} From d641b4cc1cd03aa57979ea94715f8209cfaeaf44 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 15:33:48 +0100 Subject: [PATCH 18/37] Can't change which BDE has editted the invoice --- apps/treasury/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index 56fcce27..ad7e2c5c 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -20,7 +20,7 @@ class InvoiceForm(forms.ModelForm): class Meta: model = Invoice - fields = '__all__' + exclude = ('bde', ) ProductFormSet = forms.inlineformset_factory( From 5ac10b58d5692b2aed1ac158f29c57cdb9b3351c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 16:04:09 +0100 Subject: [PATCH 19/37] API Support (useless, but...) --- apps/api/urls.py | 2 ++ apps/treasury/api/serializers.py | 34 ++++++++++++++++++++++++++++++++ apps/treasury/api/urls.py | 12 +++++++++++ apps/treasury/api/views.py | 33 +++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 apps/treasury/api/serializers.py create mode 100644 apps/treasury/api/urls.py create mode 100644 apps/treasury/api/views.py diff --git a/apps/api/urls.py b/apps/api/urls.py index b275a0b8..67fdba30 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -12,6 +12,7 @@ from activity.api.urls import register_activity_urls from api.viewsets import ReadProtectedModelViewSet from member.api.urls import register_members_urls from note.api.urls import register_note_urls +from treasury.api.urls import register_treasury_urls from logs.api.urls import register_logs_urls from permission.api.urls import register_permission_urls @@ -74,6 +75,7 @@ router.register('user', UserViewSet) register_members_urls(router, 'members') register_activity_urls(router, 'activity') register_note_urls(router, 'note') +register_treasury_urls(router, 'treasury') register_permission_urls(router, 'permission') register_logs_urls(router, 'logs') diff --git a/apps/treasury/api/serializers.py b/apps/treasury/api/serializers.py new file mode 100644 index 00000000..c835fd80 --- /dev/null +++ b/apps/treasury/api/serializers.py @@ -0,0 +1,34 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from rest_framework import serializers + +from ..models import Invoice, Product + + +class ProductSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Product types. + The djangorestframework plugin will analyse the model `Product` and parse all fields in the API. + """ + + class Meta: + model = Product + fields = '__all__' + + +class InvoiceSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Invoice types. + The djangorestframework plugin will analyse the model `Invoice` and parse all fields in the API. + """ + class Meta: + model = Invoice + fields = '__all__' + read_only_fields = ('bde',) + + products = serializers.SerializerMethodField() + + def get_products(self, obj): + return serializers.ListSerializer(child=ProductSerializer())\ + .to_representation(Product.objects.filter(invoice=obj).all()) diff --git a/apps/treasury/api/urls.py b/apps/treasury/api/urls.py new file mode 100644 index 00000000..ff3a606f --- /dev/null +++ b/apps/treasury/api/urls.py @@ -0,0 +1,12 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import InvoiceViewSet, ProductViewSet + + +def register_treasury_urls(router, path): + """ + Configure router for treasury REST API. + """ + router.register(path + '/invoice', InvoiceViewSet) + router.register(path + '/product', ProductViewSet) diff --git a/apps/treasury/api/views.py b/apps/treasury/api/views.py new file mode 100644 index 00000000..2f35b92c --- /dev/null +++ b/apps/treasury/api/views.py @@ -0,0 +1,33 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.filters import SearchFilter +from api.viewsets import ReadProtectedModelViewSet + +from .serializers import InvoiceSerializer, ProductSerializer +from ..models import Invoice, Product + + +class InvoiceViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer, + then render it on /api/treasury/invoice/ + """ + queryset = Invoice.objects.all() + serializer_class = InvoiceSerializer + filter_backends = [DjangoFilterBackend] + filterset_fields = ['bde', ] + + +class ProductViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer, + then render it on /api/treasury/product/ + """ + queryset = Product.objects.all() + serializer_class = ProductSerializer + filter_backends = [SearchFilter] + search_fields = ['$designation', ] From 3551568de5fe362f86236f1ca3a23992ff950f31 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 17:29:31 +0100 Subject: [PATCH 20/37] Add remittance model --- apps/note/models/transactions.py | 8 +++++++ apps/treasury/models.py | 38 +++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 0e40edf6..6dbf3504 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -6,6 +6,7 @@ from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from polymorphic.models import PolymorphicModel +from treasury.models import Remittance from .notes import Note, NoteClub, NoteSpecial @@ -209,6 +210,13 @@ class SpecialTransaction(Transaction): blank=True, ) + remittance = models.ForeignKey( + Remittance, + on_delete=models.PROTECT, + null=True, + verbose_name=_("Remittance"), + ) + @property def type(self): return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 52caeb0b..7c634fdd 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -1,8 +1,10 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later - +from django.core.exceptions import ValidationError from django.db import models +from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from note.models import NoteSpecial class Invoice(models.Model): @@ -83,3 +85,37 @@ class Product(models.Model): @property def total_euros(self): return self.total / 100 + + +class Remittance(models.Model): + date = models.DateField( + auto_now_add=True, + verbose_name=_("Date"), + ) + + type = models.ForeignKey( + NoteSpecial, + on_delete=models.PROTECT, + verbose_name=_("Type"), + ) + + comment = models.CharField( + max_length=255, + verbose_name=_("Comment"), + ) + + @property + def size(self): + return self.specialtransaction_set.count() + + @property + def amount(self): + return sum(transaction.total for transaction in self.specialtransaction_set.all()) + + def full_clean(self, exclude=None, validate_unique=True): + ret = super().full_clean(exclude, validate_unique) + + if self.specialtransaction_set.filter(~Q(source=self.type)).exists(): + raise ValidationError("All transactions in a remittance must have the same type") + + return ret From 5fd472d408ae13e89412134b5059735fd8302157 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 22 Mar 2020 18:27:22 +0100 Subject: [PATCH 21/37] Create, view and update remittances --- apps/treasury/forms.py | 15 +++++++++- apps/treasury/models.py | 8 ++++- apps/treasury/tables.py | 12 +++++++- apps/treasury/urls.py | 9 ++++-- apps/treasury/views.py | 40 +++++++++++++++++++++---- templates/base.html | 2 +- templates/treasury/invoice_form.html | 2 +- templates/treasury/remittance_form.html | 8 +++++ templates/treasury/remittance_list.html | 10 +++++++ 9 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 templates/treasury/remittance_form.html create mode 100644 templates/treasury/remittance_list.html diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index ad7e2c5c..3189694d 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -4,9 +4,11 @@ import datetime from crispy_forms.helper import FormHelper +from crispy_forms.layout import Submit from django import forms +from django.utils.translation import gettext_lazy as _ -from .models import Invoice, Product +from .models import Invoice, Product, Remittance class InvoiceForm(forms.ModelForm): @@ -38,3 +40,14 @@ class ProductFormSetHelper(FormHelper): self.form_method = 'POST' self.form_class = 'form-inline' self.template = 'bootstrap4/table_inline_formset.html' + + +class RemittanceForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) + + class Meta: + model = Remittance + fields = ('type', 'comment', ) diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 7c634fdd..52b25157 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -1,5 +1,6 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q @@ -88,7 +89,7 @@ class Product(models.Model): class Remittance(models.Model): - date = models.DateField( + date = models.DateTimeField( auto_now_add=True, verbose_name=_("Date"), ) @@ -104,6 +105,11 @@ class Remittance(models.Model): verbose_name=_("Comment"), ) + closed = models.BooleanField( + default=False, + verbose_name=_("Closed"), + ) + @property def size(self): return self.specialtransaction_set.count() diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index 5dcfbcd6..ebc642c7 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -5,7 +5,7 @@ import django_tables2 as tables from django.utils.translation import gettext_lazy as _ from django_tables2 import A -from .models import Invoice +from .models import Invoice, Remittance class InvoiceTable(tables.Table): @@ -30,3 +30,13 @@ class InvoiceTable(tables.Table): model = Invoice template_name = 'django_tables2/bootstrap4.html' fields = ('id', 'name', 'object', 'acquitted', 'invoice',) + + +class RemittanceTable(tables.Table): + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + model = Remittance + template_name = 'django_tables2/bootstrap4.html' + fields = ('id', 'date', 'type', 'comment', 'size', 'amount', 'edit',) diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py index 1afccabb..029466e0 100644 --- a/apps/treasury/urls.py +++ b/apps/treasury/urls.py @@ -3,12 +3,17 @@ from django.urls import path -from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceRenderView +from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceRenderView, RemittanceListView,\ + RemittanceCreateView, RemittanceUpdateView app_name = 'treasury' urlpatterns = [ - path('invoice/', InvoiceListView.as_view(), name='invoice'), + path('invoice/', InvoiceListView.as_view(), name='invoice_list'), path('invoice/create/', InvoiceCreateView.as_view(), name='invoice_create'), path('invoice//', InvoiceUpdateView.as_view(), name='invoice_update'), path('invoice/render//', InvoiceRenderView.as_view(), name='invoice_render'), + + path('remittance/', RemittanceListView.as_view(), name='remittance_list'), + path('remittance/create/', RemittanceCreateView.as_view(), name='remittance_create'), + path('remittance//', RemittanceUpdateView.as_view(), name='remittance_update'), ] diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 8b5446d6..6f6c256c 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -17,9 +17,9 @@ from django.views.generic.base import View from django_tables2 import SingleTableView from note_kfet.settings.base import BASE_DIR -from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper -from .models import Invoice, Product -from .tables import InvoiceTable +from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm +from .models import Invoice, Product, Remittance +from .tables import InvoiceTable, RemittanceTable class InvoiceCreateView(LoginRequiredMixin, CreateView): @@ -64,7 +64,7 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView): return ret def get_success_url(self): - return reverse_lazy('treasury:invoice') + return reverse_lazy('treasury:invoice_list') class InvoiceListView(LoginRequiredMixin, SingleTableView): @@ -121,7 +121,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): return ret def get_success_url(self): - return reverse_lazy('treasury:invoice') + return reverse_lazy('treasury:invoice_list') class InvoiceRenderView(LoginRequiredMixin, View): @@ -176,3 +176,33 @@ class InvoiceRenderView(LoginRequiredMixin, View): shutil.rmtree(tmp_dir) return response + + +class RemittanceCreateView(LoginRequiredMixin, CreateView): + """ + Create Remittance + """ + model = Remittance + form_class = RemittanceForm + + def get_success_url(self): + return reverse_lazy('treasury:remittance_list') + + +class RemittanceListView(LoginRequiredMixin, SingleTableView): + """ + List existing Remittances + """ + model = Remittance + table_class = RemittanceTable + + +class RemittanceUpdateView(LoginRequiredMixin, UpdateView): + """ + Update Remittance + """ + model = Remittance + form_class = RemittanceForm + + def get_success_url(self): + return reverse_lazy('treasury:remittance_list') diff --git a/templates/base.html b/templates/base.html index c6bb88b3..384535b2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -99,7 +99,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% if "treasury.invoice"|not_empty_model_change_list %} {% endif %} diff --git a/templates/treasury/invoice_form.html b/templates/treasury/invoice_form.html index 2177d0f7..f6e2a106 100644 --- a/templates/treasury/invoice_form.html +++ b/templates/treasury/invoice_form.html @@ -3,7 +3,7 @@ {% load i18n %} {% load crispy_forms_tags pretty_money %} {% block content %} -

{% trans "Invoices list" %}

+

{% trans "Invoices list" %}

{% csrf_token %} {% crispy form %} diff --git a/templates/treasury/remittance_form.html b/templates/treasury/remittance_form.html new file mode 100644 index 00000000..50071136 --- /dev/null +++ b/templates/treasury/remittance_form.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} +{% load crispy_forms_tags pretty_money %} +{% block content %} +

{% trans "Remittances list" %}

+ {% crispy form %} +{% endblock %} diff --git a/templates/treasury/remittance_list.html b/templates/treasury/remittance_list.html new file mode 100644 index 00000000..e775fbaa --- /dev/null +++ b/templates/treasury/remittance_list.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load render_table from django_tables2 %} +{% load i18n %} +{% block content %} + +{% render_table table %} + +{% trans "New remittance" %} + +{% endblock %} From 884a7d0f08e18258f172354c365e1600dddf36d4 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 23 Mar 2020 21:15:59 +0100 Subject: [PATCH 22/37] Use a proxy for special transactions in treasury app for modularity (not a clean way, but without any other solution...) --- apps/note/models/__init__.py | 4 ++-- apps/note/models/transactions.py | 8 -------- apps/treasury/models.py | 26 ++++++++++++++++++++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/apps/note/models/__init__.py b/apps/note/models/__init__.py index 8f1921f9..e9c8a0a9 100644 --- a/apps/note/models/__init__.py +++ b/apps/note/models/__init__.py @@ -3,12 +3,12 @@ from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser from .transactions import MembershipTransaction, Transaction, \ - TemplateCategory, TransactionTemplate, RecurrentTransaction + TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction __all__ = [ # Notes 'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser', # Transactions 'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate', - 'RecurrentTransaction', + 'RecurrentTransaction', 'SpecialTransaction', ] diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 6dbf3504..0e40edf6 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -6,7 +6,6 @@ from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ from polymorphic.models import PolymorphicModel -from treasury.models import Remittance from .notes import Note, NoteClub, NoteSpecial @@ -210,13 +209,6 @@ class SpecialTransaction(Transaction): blank=True, ) - remittance = models.ForeignKey( - Remittance, - on_delete=models.PROTECT, - null=True, - verbose_name=_("Remittance"), - ) - @property def type(self): return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 52b25157..359a3219 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q from django.utils.translation import gettext_lazy as _ -from note.models import NoteSpecial +from note.models import NoteSpecial, SpecialTransaction class Invoice(models.Model): @@ -110,18 +110,36 @@ class Remittance(models.Model): verbose_name=_("Closed"), ) + @property + def transactions(self): + return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self) + @property def size(self): - return self.specialtransaction_set.count() + return self.transactions.count() @property def amount(self): - return sum(transaction.total for transaction in self.specialtransaction_set.all()) + return sum(transaction.total for transaction in self.transactions.all()) def full_clean(self, exclude=None, validate_unique=True): ret = super().full_clean(exclude, validate_unique) - if self.specialtransaction_set.filter(~Q(source=self.type)).exists(): + if self.transactions.filter(~Q(source=self.type)).exists(): raise ValidationError("All transactions in a remittance must have the same type") return ret + + +class SpecialTransactionProxy(models.Model): + transaction = models.OneToOneField( + SpecialTransaction, + on_delete=models.CASCADE, + ) + + remittance = models.ForeignKey( + Remittance, + on_delete=models.PROTECT, + null=True, + verbose_name=_("Remittance"), + ) From cf45b08c5bd79299afa7f8159544cedc08a5713d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 23 Mar 2020 21:30:57 +0100 Subject: [PATCH 23/37] Automatically link SpecialTransactions and their proxies --- apps/treasury/apps.py | 15 +++++++++++++++ apps/treasury/models.py | 18 ++++++++++++++++++ apps/treasury/signals.py | 12 ++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 apps/treasury/signals.py diff --git a/apps/treasury/apps.py b/apps/treasury/apps.py index 96bab8a0..bc492aa7 100644 --- a/apps/treasury/apps.py +++ b/apps/treasury/apps.py @@ -2,9 +2,24 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django.apps import AppConfig +from django.db.models.signals import post_save from django.utils.translation import gettext_lazy as _ class TreasuryConfig(AppConfig): name = 'treasury' verbose_name = _('Treasury') + + def ready(self): + """ + Define app internal signals to interact with other apps + """ + + from . import signals + from note.models import SpecialTransaction + from treasury.models import SpecialTransactionProxy + post_save.connect(signals.save_special_transaction, sender=SpecialTransaction) + + # If the treasury app was disabled, we ensure that each special transaction is linked to a proxy + for transaction in SpecialTransaction.objects.filter(specialtransactionproxy=None): + SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None) diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 359a3219..569fc07f 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -9,6 +9,10 @@ from note.models import NoteSpecial, SpecialTransaction class Invoice(models.Model): + """ + An invoice model that can generate a true invoice + """ + id = models.PositiveIntegerField( primary_key=True, verbose_name=_("Invoice identifier"), @@ -57,6 +61,10 @@ class Invoice(models.Model): class Product(models.Model): + """ + Product that appear on an invoice. + """ + invoice = models.ForeignKey( Invoice, on_delete=models.PROTECT, @@ -89,6 +97,10 @@ class Product(models.Model): class Remittance(models.Model): + """ + Treasurers want to regroup checks or bank transfers in bank remittances. + """ + date = models.DateTimeField( auto_now_add=True, verbose_name=_("Date"), @@ -132,6 +144,12 @@ class Remittance(models.Model): class SpecialTransactionProxy(models.Model): + """ + In order to keep modularity, we don't that the Note app depends on the treasury app. + That's why we create a proxy in this app, to link special transactions and remittances. + If it isn't very clean, that makes what we want. + """ + transaction = models.OneToOneField( SpecialTransaction, on_delete=models.CASCADE, diff --git a/apps/treasury/signals.py b/apps/treasury/signals.py new file mode 100644 index 00000000..e1183d6b --- /dev/null +++ b/apps/treasury/signals.py @@ -0,0 +1,12 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from treasury.models import SpecialTransactionProxy + + +def save_special_transaction(instance, created, **kwargs): + """ + When a special transaction is created, we create its linked proxy + """ + if created: + SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save() From d6e00f7bbaa8b817d293df44a51e5df7fd40c619 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 23 Mar 2020 22:13:16 +0100 Subject: [PATCH 24/37] Fix SpecialTransaction proxies --- apps/treasury/apps.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/treasury/apps.py b/apps/treasury/apps.py index bc492aa7..14ca36e7 100644 --- a/apps/treasury/apps.py +++ b/apps/treasury/apps.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django.apps import AppConfig -from django.db.models.signals import post_save +from django.db.models.signals import post_save, post_migrate from django.utils.translation import gettext_lazy as _ @@ -20,6 +20,9 @@ class TreasuryConfig(AppConfig): from treasury.models import SpecialTransactionProxy post_save.connect(signals.save_special_transaction, sender=SpecialTransaction) - # If the treasury app was disabled, we ensure that each special transaction is linked to a proxy - for transaction in SpecialTransaction.objects.filter(specialtransactionproxy=None): - SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None) + def setup_specialtransactions_proxies(**kwargs): + # If the treasury app was disabled, we ensure that each special transaction is linked to a proxy + for transaction in SpecialTransaction.objects.filter(specialtransactionproxy=None): + SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None) + + post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy) From 2defb04d52747e44708474109268f70e4272192b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 23 Mar 2020 22:42:56 +0100 Subject: [PATCH 25/37] Remove my own VPS DNS --- static/js/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/base.js b/static/js/base.js index f7085850..bace7a9f 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -62,7 +62,7 @@ function li(id, text) { */ function displayNote(note, alias, user_note_field=null, profile_pic_field=null) { if (!note.display_image) { - note.display_image = 'https://nk20.ynerant.fr/media/pic/default.png'; + note.display_image = '/media/pic/default.png'; $.getJSON("/api/note/note/" + note.id + "/?format=json", function(new_note) { note.display_image = new_note.display_image.replace("http:", "https:"); note.name = new_note.name; From 95888ea3163884f5cfbbd25e5f1b54e6b91ce9a3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 23 Mar 2020 22:43:16 +0100 Subject: [PATCH 26/37] Add some tables for special transactions --- apps/treasury/tables.py | 39 ++++++++++++++++++++++ apps/treasury/urls.py | 2 +- apps/treasury/views.py | 44 ++++++++++++++++++++++--- templates/treasury/remittance_form.html | 2 ++ templates/treasury/remittance_list.html | 19 +++++++++-- 5 files changed, 98 insertions(+), 8 deletions(-) diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index ebc642c7..bcbbd8a4 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -4,6 +4,8 @@ import django_tables2 as tables from django.utils.translation import gettext_lazy as _ from django_tables2 import A +from note.models import SpecialTransaction +from note.templatetags.pretty_money import pretty_money from .models import Invoice, Remittance @@ -33,6 +35,14 @@ class InvoiceTable(tables.Table): class RemittanceTable(tables.Table): + edit = tables.LinkColumn("treasury:remittance_update", + verbose_name=_("Edit"), + args=[A("pk")], + text=_("Edit"), + attrs={ + 'a': {'class': 'btn btn-primary'} + }, ) + class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' @@ -40,3 +50,32 @@ class RemittanceTable(tables.Table): model = Remittance template_name = 'django_tables2/bootstrap4.html' fields = ('id', 'date', 'type', 'comment', 'size', 'amount', 'edit',) + + +class SpecialTransactionTable(tables.Table): + remittance_add = tables.LinkColumn("treasury:remittance_update", + verbose_name=_("Remittance"), + args=[A("pk")], + text=_("Add"), + attrs={ + 'a': {'class': 'btn btn-primary'} + }, ) + + remittance_remove = tables.LinkColumn("treasury:remittance_update", + verbose_name=_("Remittance"), + args=[A("pk")], + text=_("Remove"), + attrs={ + 'a': {'class': 'btn btn-primary btn-danger'} + }, ) + + def render_amount(self, value): + return pretty_money(value) + + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + model = SpecialTransaction + template_name = 'django_tables2/bootstrap4.html' + fields = ('id', 'source', 'destination', 'amount', 'last_name', 'first_name', 'bank',) diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py index 029466e0..1901732b 100644 --- a/apps/treasury/urls.py +++ b/apps/treasury/urls.py @@ -4,7 +4,7 @@ from django.urls import path from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceRenderView, RemittanceListView,\ - RemittanceCreateView, RemittanceUpdateView + RemittanceCreateView, RemittanceUpdateView app_name = 'treasury' urlpatterns = [ diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 6f6c256c..3415a2b2 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -13,13 +13,14 @@ from django.http import HttpResponse from django.template.loader import render_to_string from django.urls import reverse_lazy from django.views.generic import CreateView, UpdateView -from django.views.generic.base import View +from django.views.generic.base import View, TemplateView from django_tables2 import SingleTableView +from note.models import SpecialTransaction from note_kfet.settings.base import BASE_DIR from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm from .models import Invoice, Product, Remittance -from .tables import InvoiceTable, RemittanceTable +from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable class InvoiceCreateView(LoginRequiredMixin, CreateView): @@ -188,13 +189,36 @@ class RemittanceCreateView(LoginRequiredMixin, CreateView): def get_success_url(self): return reverse_lazy('treasury:remittance_list') + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) -class RemittanceListView(LoginRequiredMixin, SingleTableView): + ctx["table"] = RemittanceTable(data=Remittance.objects.all()) + ctx["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none()) + + return ctx + + +class RemittanceListView(LoginRequiredMixin, TemplateView): """ List existing Remittances """ - model = Remittance - table_class = RemittanceTable + template_name = "treasury/remittance_list.html" + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + + ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all()) + ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all()) + ctx["special_transactions_no_remittance"] = SpecialTransactionTable( + data=SpecialTransaction.objects.filter(source__polymorphic_ctype__model="notespecial", + specialtransactionproxy__remittance=None).all(), + exclude=('remittance_remove', )) + ctx["special_transactions_with_remittance"] = SpecialTransactionTable( + data=SpecialTransaction.objects.filter(source__polymorphic_ctype__model="notespecial", + specialtransactionproxy__remittance__closed=False).all(), + exclude=('remittance_add', )) + + return ctx class RemittanceUpdateView(LoginRequiredMixin, UpdateView): @@ -206,3 +230,13 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView): def get_success_url(self): return reverse_lazy('treasury:remittance_list') + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + + ctx["table"] = RemittanceTable(data=Remittance.objects.all()) + ctx["special_transactions"] = SpecialTransactionTable( + data=SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all(), + exclude=('remittance_add', )) + + return ctx diff --git a/templates/treasury/remittance_form.html b/templates/treasury/remittance_form.html index 50071136..99830601 100644 --- a/templates/treasury/remittance_form.html +++ b/templates/treasury/remittance_form.html @@ -2,7 +2,9 @@ {% load static %} {% load i18n %} {% load crispy_forms_tags pretty_money %} +{% load render_table from django_tables2 %} {% block content %}

{% trans "Remittances list" %}

{% crispy form %} + {% render_table special_transactions %} {% endblock %} diff --git a/templates/treasury/remittance_list.html b/templates/treasury/remittance_list.html index e775fbaa..dfada279 100644 --- a/templates/treasury/remittance_list.html +++ b/templates/treasury/remittance_list.html @@ -3,8 +3,23 @@ {% load i18n %} {% block content %} -{% render_table table %} +

{% trans "Opened remittances" %}

+ {% render_table opened_remittances %} -{% trans "New remittance" %} + {% trans "New remittance" %} +
+ +

{% trans "Transfers without remittances" %}

+ {% render_table special_transactions_no_remittance %} + +
+ +

{% trans "Transfers with opened remittances" %}

+ {% render_table special_transactions_with_remittance %} + +
+ +

{% trans "Closed remittances" %}

+ {% render_table closed_remittances %} {% endblock %} From 414722df188982ac95c6a26e437f4b523e727d01 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 23 Mar 2020 23:42:37 +0100 Subject: [PATCH 27/37] Link transactions and remittances --- apps/treasury/forms.py | 37 ++++++++++++++++- apps/treasury/models.py | 6 ++- apps/treasury/tables.py | 18 +++++--- apps/treasury/urls.py | 5 ++- apps/treasury/views.py | 41 ++++++++++++++++++- templates/treasury/remittance_list.html | 24 +++++++++-- .../specialtransactionproxy_form.html | 9 ++++ 7 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 templates/treasury/specialtransactionproxy_form.html diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index 3189694d..a06601b1 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -8,7 +8,7 @@ from crispy_forms.layout import Submit from django import forms from django.utils.translation import gettext_lazy as _ -from .models import Invoice, Product, Remittance +from .models import Invoice, Product, Remittance, SpecialTransactionProxy class InvoiceForm(forms.ModelForm): @@ -51,3 +51,38 @@ class RemittanceForm(forms.ModelForm): class Meta: model = Remittance fields = ('type', 'comment', ) + + +class LinkTransactionToRemittanceForm(forms.ModelForm): + last_name = forms.CharField(label=_("Last name")) + + first_name = forms.Field(label=_("First name")) + + bank = forms.Field(label=_("Bank")) + + amount = forms.IntegerField(label=_("Amount"), min_value=0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) + + def clean_last_name(self): + self.instance.transaction.last_name = self.data.get("last_name") + self.instance.transaction.clean() + + def clean_first_name(self): + self.instance.transaction.first_name = self.data.get("first_name") + self.instance.transaction.clean() + + def clean_bank(self): + self.instance.transaction.bank = self.data.get("bank") + self.instance.transaction.clean() + + def clean_amount(self): + self.instance.transaction.amount = self.data.get("amount") + self.instance.transaction.clean() + + class Meta: + model = SpecialTransactionProxy + fields = ('remittance', ) diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 569fc07f..e2be5af7 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -126,8 +126,7 @@ class Remittance(models.Model): def transactions(self): return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self) - @property - def size(self): + def count(self): return self.transactions.count() @property @@ -142,6 +141,9 @@ class Remittance(models.Model): return ret + def __str__(self): + return _("Remittance #{:d}: {}").format(self.id, self.comment, ) + class SpecialTransactionProxy(models.Model): """ diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index bcbbd8a4..3c302813 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -43,32 +43,38 @@ class RemittanceTable(tables.Table): 'a': {'class': 'btn btn-primary'} }, ) + def render_amount(self, value): + return pretty_money(value) + class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' } model = Remittance template_name = 'django_tables2/bootstrap4.html' - fields = ('id', 'date', 'type', 'comment', 'size', 'amount', 'edit',) + fields = ('id', 'date', 'type', 'comment', 'count', 'amount', 'edit',) class SpecialTransactionTable(tables.Table): - remittance_add = tables.LinkColumn("treasury:remittance_update", + remittance_add = tables.LinkColumn("treasury:link_transaction", verbose_name=_("Remittance"), - args=[A("pk")], + args=[A("specialtransactionproxy.pk")], text=_("Add"), attrs={ 'a': {'class': 'btn btn-primary'} }, ) - remittance_remove = tables.LinkColumn("treasury:remittance_update", + remittance_remove = tables.LinkColumn("treasury:unlink_transaction", verbose_name=_("Remittance"), - args=[A("pk")], + args=[A("specialtransactionproxy.pk")], text=_("Remove"), attrs={ 'a': {'class': 'btn btn-primary btn-danger'} }, ) + def render_id(self, record): + return record.specialtransactionproxy.pk + def render_amount(self, value): return pretty_money(value) @@ -78,4 +84,4 @@ class SpecialTransactionTable(tables.Table): } model = SpecialTransaction template_name = 'django_tables2/bootstrap4.html' - fields = ('id', 'source', 'destination', 'amount', 'last_name', 'first_name', 'bank',) + fields = ('id', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',) diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py index 1901732b..fa5ef0e4 100644 --- a/apps/treasury/urls.py +++ b/apps/treasury/urls.py @@ -4,7 +4,7 @@ from django.urls import path from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceRenderView, RemittanceListView,\ - RemittanceCreateView, RemittanceUpdateView + RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, UnlinkTransactionToRemittanceView app_name = 'treasury' urlpatterns = [ @@ -16,4 +16,7 @@ urlpatterns = [ path('remittance/', RemittanceListView.as_view(), name='remittance_list'), path('remittance/create/', RemittanceCreateView.as_view(), name='remittance_create'), path('remittance//', RemittanceUpdateView.as_view(), name='remittance_update'), + path('remittance/link_transaction//', LinkTransactionToRemittanceView.as_view(), name='link_transaction'), + path('remittance/unlink_transaction//', UnlinkTransactionToRemittanceView.as_view(), + name='unlink_transaction'), ] diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 3415a2b2..89a009cf 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -8,8 +8,10 @@ from tempfile import mkdtemp from crispy_forms.helper import FormHelper from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import ValidationError from django.db.models import Q from django.http import HttpResponse +from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy from django.views.generic import CreateView, UpdateView @@ -18,8 +20,8 @@ from django_tables2 import SingleTableView from note.models import SpecialTransaction from note_kfet.settings.base import BASE_DIR -from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm -from .models import Invoice, Product, Remittance +from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm +from .models import Invoice, Product, Remittance, SpecialTransactionProxy from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable @@ -240,3 +242,38 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView): exclude=('remittance_add', )) return ctx + + +class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView): + model = SpecialTransactionProxy + form_class = LinkTransactionToRemittanceForm + + def get_success_url(self): + return reverse_lazy('treasury:remittance_list') + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + + form = ctx["form"] + form.fields["last_name"].initial = self.object.transaction.last_name + form.fields["first_name"].initial = self.object.transaction.first_name + form.fields["bank"].initial = self.object.transaction.bank + form.fields["amount"].initial = self.object.transaction.amount + form.fields["remittance"].queryset = form.fields["remittance"] \ + .queryset.filter(type=self.object.transaction.source) + + return ctx + + +class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View): + def get(self, *args, **kwargs): + pk = kwargs["pk"] + transaction = SpecialTransactionProxy.objects.get(pk=pk) + + if transaction.remittance and transaction.remittance.closed: + raise ValidationError("Remittance is already closed.") + + transaction.remittance = None + transaction.save() + + return redirect('treasury:remittance_list') diff --git a/templates/treasury/remittance_list.html b/templates/treasury/remittance_list.html index dfada279..cb06bbd0 100644 --- a/templates/treasury/remittance_list.html +++ b/templates/treasury/remittance_list.html @@ -4,19 +4,37 @@ {% block content %}

{% trans "Opened remittances" %}

- {% render_table opened_remittances %} + {% if opened_remittances.data %} + {% render_table opened_remittances %} + {% else %} +
+ {% trans "There is no opened remittance." %} +
+ {% endif %} {% trans "New remittance" %}

{% trans "Transfers without remittances" %}

- {% render_table special_transactions_no_remittance %} + {% if special_transactions_no_remittance.data %} + {% render_table special_transactions_no_remittance %} + {% else %} +
+ {% trans "There is no transaction without any linked remittance." %} +
+ {% endif %}

{% trans "Transfers with opened remittances" %}

- {% render_table special_transactions_with_remittance %} + {% if special_transactions_with_remittance.data %} + {% render_table special_transactions_with_remittance %} + {% else %} +
+ {% trans "There is no transaction without an opened linked remittance." %} +
+ {% endif %}
diff --git a/templates/treasury/specialtransactionproxy_form.html b/templates/treasury/specialtransactionproxy_form.html new file mode 100644 index 00000000..4e7758ae --- /dev/null +++ b/templates/treasury/specialtransactionproxy_form.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} +{% load crispy_forms_tags pretty_money %} +{% load render_table from django_tables2 %} +{% block content %} +

{% trans "Remittances list" %}

+ {% crispy form %} +{% endblock %} From 5682c5489ef7eaafd7c1e8302429fee7dfd9d58a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 00:50:55 +0100 Subject: [PATCH 28/37] Close remittances --- apps/treasury/forms.py | 29 ++++++++++++++++++++++++- apps/treasury/tables.py | 8 +++---- apps/treasury/views.py | 6 +++-- templates/treasury/remittance_form.html | 29 ++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index a06601b1..65fae3a4 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -45,8 +45,35 @@ class ProductFormSetHelper(FormHelper): class RemittanceForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.helper = FormHelper() - self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) + + if self.instance.pk: + self.fields["type"].disabled = True + self.fields["type"].required = False + + if not self.instance.closed: + self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) + if self.instance.transactions: + self.helper.add_input(Submit("close", _("Close"), css_class='btn btn-success')) + else: + self.fields["comment"].disabled = True + self.fields["comment"].required = False + + def clean(self): + if self.instance.closed: + self.add_error("comment", _("Remittance is already closed.")) + + cleaned_data = super().clean() + + if "type" in self.changed_data: + self.add_error("type", _("You can't change the type of the remittance.")) + + if "close" in self.data: + self.instance.closed = True + self.cleaned_data["closed"] = True + + return cleaned_data class Meta: model = Remittance diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index 3c302813..d5f3f105 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -35,10 +35,10 @@ class InvoiceTable(tables.Table): class RemittanceTable(tables.Table): - edit = tables.LinkColumn("treasury:remittance_update", - verbose_name=_("Edit"), + view = tables.LinkColumn("treasury:remittance_update", + verbose_name=_("View"), args=[A("pk")], - text=_("Edit"), + text=_("View"), attrs={ 'a': {'class': 'btn btn-primary'} }, ) @@ -52,7 +52,7 @@ class RemittanceTable(tables.Table): } model = Remittance template_name = 'django_tables2/bootstrap4.html' - fields = ('id', 'date', 'type', 'comment', 'count', 'amount', 'edit',) + fields = ('id', 'date', 'type', 'comment', 'count', 'amount', 'view',) class SpecialTransactionTable(tables.Table): diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 89a009cf..71b07b0b 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -236,10 +236,12 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView): def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) + form = ctx["form"] ctx["table"] = RemittanceTable(data=Remittance.objects.all()) + data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all() ctx["special_transactions"] = SpecialTransactionTable( - data=SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all(), - exclude=('remittance_add', )) + data=data, + exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )) return ctx diff --git a/templates/treasury/remittance_form.html b/templates/treasury/remittance_form.html index 99830601..af4170f4 100644 --- a/templates/treasury/remittance_form.html +++ b/templates/treasury/remittance_form.html @@ -4,7 +4,34 @@ {% load crispy_forms_tags pretty_money %} {% load render_table from django_tables2 %} {% block content %} +

{% trans "Remittance #" %}{{ object.pk }}

+

{% trans "Remittances list" %}

+ + {% if object.pk %} +
+
+ +
+
+ +
+
+ +
+
+ {% endif %} + {% crispy form %} - {% render_table special_transactions %} + +
+ +

{% trans "Linked transactions" %}

+ {% if special_transactions.data %} + {% render_table special_transactions %} + {% else %} +
+ {% trans "There is no transaction linked with this remittance." %} +
+ {% endif %} {% endblock %} From d25d342d28d4035f58e380b8913116242a42c7c5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 01:04:01 +0100 Subject: [PATCH 29/37] Update translations --- apps/treasury/tables.py | 4 + locale/de/LC_MESSAGES/django.po | 221 +++++++++++++++++++++---------- locale/fr/LC_MESSAGES/django.po | 223 +++++++++++++++++++++----------- 3 files changed, 304 insertions(+), 144 deletions(-) diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index d5f3f105..f5d1a472 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -35,6 +35,10 @@ class InvoiceTable(tables.Table): class RemittanceTable(tables.Table): + count = tables.Column(verbose_name=_("Transaction count")) + + amount = tables.Column(verbose_name=_("Amount")) + view = tables.LinkColumn("treasury:remittance_update", verbose_name=_("View"), args=[A("pk")], diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 4e950765..5b7047ae 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-22 01:15+0100\n" +"POT-Creation-Date: 2020-03-24 01:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -47,7 +47,7 @@ msgid "activity types" msgstr "" #: apps/activity/models.py:48 apps/note/models/transactions.py:69 -#: apps/permission/models.py:91 +#: apps/permission/models.py:90 msgid "description" msgstr "" @@ -210,7 +210,7 @@ msgstr "" msgid "clubs" msgstr "" -#: apps/member/models.py:120 apps/permission/models.py:276 +#: apps/member/models.py:120 apps/permission/models.py:275 msgid "role" msgstr "" @@ -474,112 +474,157 @@ msgstr "" msgid "Consumptions" msgstr "" -#: apps/permission/models.py:70 apps/permission/models.py:263 +#: apps/permission/models.py:69 apps/permission/models.py:262 #, python-brace-format msgid "Can {type} {model}.{field} in {query}" msgstr "" -#: apps/permission/models.py:72 apps/permission/models.py:265 +#: apps/permission/models.py:71 apps/permission/models.py:264 #, python-brace-format msgid "Can {type} {model} in {query}" msgstr "" -#: apps/permission/models.py:85 +#: apps/permission/models.py:84 msgid "rank" msgstr "" -#: apps/permission/models.py:148 +#: apps/permission/models.py:147 msgid "Specifying field applies only to view and change permission types." msgstr "" -#: apps/treasury/apps.py:10 templates/base.html:102 +#: apps/treasury/apps.py:11 templates/base.html:102 msgid "Treasury" msgstr "" -#: apps/treasury/models.py:11 +#: apps/treasury/forms.py:56 apps/treasury/forms.py:95 +#: templates/django_filters/rest_framework/form.html:5 +#: templates/member/club_form.html:10 templates/treasury/invoice_form.html:47 +msgid "Submit" +msgstr "" + +#: apps/treasury/forms.py:58 +msgid "Close" +msgstr "" + +#: apps/treasury/forms.py:65 +msgid "Remittance is already closed." +msgstr "" + +#: apps/treasury/forms.py:70 +msgid "You can't change the type of the remittance." +msgstr "" + +#: apps/treasury/forms.py:84 +msgid "Last name" +msgstr "" + +#: apps/treasury/forms.py:86 templates/note/transaction_form.html:92 +msgid "First name" +msgstr "" + +#: apps/treasury/forms.py:88 templates/note/transaction_form.html:98 +msgid "Bank" +msgstr "" + +#: apps/treasury/forms.py:90 apps/treasury/tables.py:40 +#: templates/note/transaction_form.html:128 +#: templates/treasury/remittance_form.html:18 +msgid "Amount" +msgstr "" + +#: apps/treasury/models.py:18 msgid "Invoice identifier" msgstr "" -#: apps/treasury/models.py:25 +#: apps/treasury/models.py:32 msgid "BDE" msgstr "" -#: apps/treasury/models.py:30 +#: apps/treasury/models.py:37 msgid "Object" msgstr "" -#: apps/treasury/models.py:34 +#: apps/treasury/models.py:41 msgid "Description" msgstr "" -#: apps/treasury/models.py:39 templates/note/transaction_form.html:86 +#: apps/treasury/models.py:46 templates/note/transaction_form.html:86 msgid "Name" msgstr "" -#: apps/treasury/models.py:43 +#: apps/treasury/models.py:50 msgid "Address" msgstr "" -#: apps/treasury/models.py:48 apps/treasury/models.py:58 +#: apps/treasury/models.py:55 msgid "Place" msgstr "" -#: apps/treasury/models.py:52 +#: apps/treasury/models.py:59 msgid "Acquitted" msgstr "" -#: apps/treasury/models.py:64 -msgid "My name" -msgstr "" - -#: apps/treasury/models.py:70 -msgid "My street address" -msgstr "" - -#: apps/treasury/models.py:76 -msgid "My city" -msgstr "" - -#: apps/treasury/models.py:81 -msgid "Bank code" -msgstr "" - -#: apps/treasury/models.py:86 -msgid "Desk code" -msgstr "" - -#: apps/treasury/models.py:91 -msgid "Account number" -msgstr "" - -#: apps/treasury/models.py:96 -msgid "RIB Key" -msgstr "" - -#: apps/treasury/models.py:102 -msgid "BIC Code" -msgstr "" - -#: apps/treasury/models.py:114 +#: apps/treasury/models.py:75 msgid "Designation" msgstr "" -#: apps/treasury/models.py:118 +#: apps/treasury/models.py:79 msgid "Quantity" msgstr "" -#: apps/treasury/models.py:122 +#: apps/treasury/models.py:83 msgid "Unit price" msgstr "" -#: apps/treasury/tables.py:14 +#: apps/treasury/models.py:106 +msgid "Date" +msgstr "" + +#: apps/treasury/models.py:112 +msgid "Type" +msgstr "" + +#: apps/treasury/models.py:117 +msgid "Comment" +msgstr "" + +#: apps/treasury/models.py:122 +msgid "Closed" +msgstr "" + +#: apps/treasury/models.py:145 +msgid "Remittance #{:d}: {}" +msgstr "" + +#: apps/treasury/models.py:164 apps/treasury/tables.py:64 +#: apps/treasury/tables.py:72 +msgid "Remittance" +msgstr "" + +#: apps/treasury/tables.py:16 msgid "Invoice #{:d}" msgstr "" -#: apps/treasury/tables.py:17 +#: apps/treasury/tables.py:19 msgid "Invoice" msgstr "" +#: apps/treasury/tables.py:38 +msgid "Transaction count" +msgstr "" + +#: apps/treasury/tables.py:43 apps/treasury/tables.py:45 +msgid "View" +msgstr "" + +#: apps/treasury/tables.py:66 +msgid "Add" +msgstr "" + +#: apps/treasury/tables.py:74 +msgid "Remove" +msgstr "" + #: note_kfet/settings/__init__.py:63 msgid "" "The Central Authentication Service grants you access to most of our websites " @@ -670,11 +715,6 @@ msgstr "" msgid "Field filters" msgstr "" -#: templates/django_filters/rest_framework/form.html:5 -#: templates/member/club_form.html:10 templates/treasury/invoice_form.html:47 -msgid "Submit" -msgstr "" - #: templates/member/club_detail.html:10 msgid "Membership starts on" msgstr "" @@ -800,24 +840,12 @@ msgstr "" msgid "Transfer type" msgstr "" -#: templates/note/transaction_form.html:92 -msgid "First name" -msgstr "" - -#: templates/note/transaction_form.html:98 -msgid "Bank" -msgstr "" - #: templates/note/transaction_form.html:111 #: templates/note/transaction_form.html:169 #: templates/note/transaction_form.html:176 msgid "Select receivers" msgstr "" -#: templates/note/transaction_form.html:128 -msgid "Amount" -msgstr "" - #: templates/note/transaction_form.html:138 msgid "Reason" msgstr "" @@ -927,3 +955,56 @@ msgstr "" #: templates/treasury/invoice_list.html:8 msgid "New invoice" msgstr "" + +#: templates/treasury/remittance_form.html:7 +msgid "Remittance #" +msgstr "" + +#: templates/treasury/remittance_form.html:9 +#: templates/treasury/specialtransactionproxy_form.html:7 +msgid "Remittances list" +msgstr "" + +#: templates/treasury/remittance_form.html:12 +msgid "Count" +msgstr "" + +#: templates/treasury/remittance_form.html:29 +msgid "Linked transactions" +msgstr "" + +#: templates/treasury/remittance_form.html:34 +msgid "There is no transaction linked with this remittance." +msgstr "" + +#: templates/treasury/remittance_list.html:6 +msgid "Opened remittances" +msgstr "" + +#: templates/treasury/remittance_list.html:11 +msgid "There is no opened remittance." +msgstr "" + +#: templates/treasury/remittance_list.html:15 +msgid "New remittance" +msgstr "" + +#: templates/treasury/remittance_list.html:19 +msgid "Transfers without remittances" +msgstr "" + +#: templates/treasury/remittance_list.html:24 +msgid "There is no transaction without any linked remittance." +msgstr "" + +#: templates/treasury/remittance_list.html:30 +msgid "Transfers with opened remittances" +msgstr "" + +#: templates/treasury/remittance_list.html:35 +msgid "There is no transaction without an opened linked remittance." +msgstr "" + +#: templates/treasury/remittance_list.html:41 +msgid "Closed remittances" +msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 8770d433..b96f3f44 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-22 01:15+0100\n" +"POT-Creation-Date: 2020-03-24 01:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -42,7 +42,7 @@ msgid "activity types" msgstr "types d'activité" #: apps/activity/models.py:48 apps/note/models/transactions.py:69 -#: apps/permission/models.py:91 +#: apps/permission/models.py:90 msgid "description" msgstr "description" @@ -209,7 +209,7 @@ msgstr "club" msgid "clubs" msgstr "clubs" -#: apps/member/models.py:120 apps/permission/models.py:276 +#: apps/member/models.py:120 apps/permission/models.py:275 msgid "role" msgstr "rôle" @@ -474,112 +474,157 @@ msgstr "Transférer de l'argent" msgid "Consumptions" msgstr "Consommations" -#: apps/permission/models.py:70 apps/permission/models.py:263 +#: apps/permission/models.py:69 apps/permission/models.py:262 #, python-brace-format msgid "Can {type} {model}.{field} in {query}" msgstr "" -#: apps/permission/models.py:72 apps/permission/models.py:265 +#: apps/permission/models.py:71 apps/permission/models.py:264 #, python-brace-format msgid "Can {type} {model} in {query}" msgstr "" -#: apps/permission/models.py:85 +#: apps/permission/models.py:84 msgid "rank" msgstr "Rang" -#: apps/permission/models.py:148 +#: apps/permission/models.py:147 msgid "Specifying field applies only to view and change permission types." msgstr "" -#: apps/treasury/apps.py:10 templates/base.html:102 +#: apps/treasury/apps.py:11 templates/base.html:102 msgid "Treasury" msgstr "Trésorerie" -#: apps/treasury/models.py:11 +#: apps/treasury/forms.py:56 apps/treasury/forms.py:95 +#: templates/django_filters/rest_framework/form.html:5 +#: templates/member/club_form.html:10 templates/treasury/invoice_form.html:47 +msgid "Submit" +msgstr "Envoyer" + +#: apps/treasury/forms.py:58 +msgid "Close" +msgstr "Fermer" + +#: apps/treasury/forms.py:65 +msgid "Remittance is already closed." +msgstr "La remise est déjà fermée." + +#: apps/treasury/forms.py:70 +msgid "You can't change the type of the remittance." +msgstr "Vous ne pouvez pas changer le type de la remise." + +#: apps/treasury/forms.py:84 +msgid "Last name" +msgstr "Nom de famille" + +#: apps/treasury/forms.py:86 templates/note/transaction_form.html:92 +msgid "First name" +msgstr "Prénom" + +#: apps/treasury/forms.py:88 templates/note/transaction_form.html:98 +msgid "Bank" +msgstr "Banque" + +#: apps/treasury/forms.py:90 apps/treasury/tables.py:40 +#: templates/note/transaction_form.html:128 +#: templates/treasury/remittance_form.html:18 +msgid "Amount" +msgstr "Montant" + +#: apps/treasury/models.py:18 msgid "Invoice identifier" msgstr "Numéro de facture" -#: apps/treasury/models.py:25 +#: apps/treasury/models.py:32 msgid "BDE" msgstr "BDE" -#: apps/treasury/models.py:30 +#: apps/treasury/models.py:37 msgid "Object" msgstr "Objet" -#: apps/treasury/models.py:34 +#: apps/treasury/models.py:41 msgid "Description" msgstr "Description" -#: apps/treasury/models.py:39 templates/note/transaction_form.html:86 +#: apps/treasury/models.py:46 templates/note/transaction_form.html:86 msgid "Name" msgstr "Nom" -#: apps/treasury/models.py:43 +#: apps/treasury/models.py:50 msgid "Address" msgstr "Adresse" -#: apps/treasury/models.py:48 apps/treasury/models.py:58 +#: apps/treasury/models.py:55 msgid "Place" msgstr "Lieu" -#: apps/treasury/models.py:52 +#: apps/treasury/models.py:59 msgid "Acquitted" msgstr "Acquittée" -#: apps/treasury/models.py:64 -msgid "My name" -msgstr "Mon nom" - -#: apps/treasury/models.py:70 -msgid "My street address" -msgstr "Ma rue" - -#: apps/treasury/models.py:76 -msgid "My city" -msgstr "Ma ville" - -#: apps/treasury/models.py:81 -msgid "Bank code" -msgstr "Code banque" - -#: apps/treasury/models.py:86 -msgid "Desk code" -msgstr "Code guichet" - -#: apps/treasury/models.py:91 -msgid "Account number" -msgstr "Numéro de compte" - -#: apps/treasury/models.py:96 -msgid "RIB Key" -msgstr "Clé RIB" - -#: apps/treasury/models.py:102 -msgid "BIC Code" -msgstr "Code BIC" - -#: apps/treasury/models.py:114 +#: apps/treasury/models.py:75 msgid "Designation" msgstr "Désignation" -#: apps/treasury/models.py:118 +#: apps/treasury/models.py:79 msgid "Quantity" msgstr "Quantité" -#: apps/treasury/models.py:122 +#: apps/treasury/models.py:83 msgid "Unit price" msgstr "Prix unitaire" -#: apps/treasury/tables.py:14 +#: apps/treasury/models.py:106 +msgid "Date" +msgstr "Date" + +#: apps/treasury/models.py:112 +msgid "Type" +msgstr "Type" + +#: apps/treasury/models.py:117 +msgid "Comment" +msgstr "Commentaire" + +#: apps/treasury/models.py:122 +msgid "Closed" +msgstr "Fermée" + +#: apps/treasury/models.py:145 +msgid "Remittance #{:d}: {}" +msgstr "Remise n°{:d} : {}" + +#: apps/treasury/models.py:164 apps/treasury/tables.py:64 +#: apps/treasury/tables.py:72 +msgid "Remittance" +msgstr "Remise" + +#: apps/treasury/tables.py:16 msgid "Invoice #{:d}" msgstr "Facture n°{:d}" -#: apps/treasury/tables.py:17 +#: apps/treasury/tables.py:19 msgid "Invoice" msgstr "Facture" +#: apps/treasury/tables.py:38 +msgid "Transaction count" +msgstr "Nombre de transactions" + +#: apps/treasury/tables.py:43 apps/treasury/tables.py:45 +msgid "View" +msgstr "Voir" + +#: apps/treasury/tables.py:66 +msgid "Add" +msgstr "Ajouter" + +#: apps/treasury/tables.py:74 +msgid "Remove" +msgstr "supprimer" + #: note_kfet/settings/__init__.py:63 msgid "" "The Central Authentication Service grants you access to most of our websites " @@ -672,11 +717,6 @@ msgstr "" msgid "Field filters" msgstr "" -#: templates/django_filters/rest_framework/form.html:5 -#: templates/member/club_form.html:10 templates/treasury/invoice_form.html:47 -msgid "Submit" -msgstr "Envoyer" - #: templates/member/club_detail.html:10 msgid "Membership starts on" msgstr "L'adhésion commence le" @@ -802,24 +842,12 @@ msgstr "Paiement externe" msgid "Transfer type" msgstr "Type de transfert" -#: templates/note/transaction_form.html:92 -msgid "First name" -msgstr "Prénom" - -#: templates/note/transaction_form.html:98 -msgid "Bank" -msgstr "Banque" - #: templates/note/transaction_form.html:111 #: templates/note/transaction_form.html:169 #: templates/note/transaction_form.html:176 msgid "Select receivers" msgstr "Sélection des destinataires" -#: templates/note/transaction_form.html:128 -msgid "Amount" -msgstr "Montant" - #: templates/note/transaction_form.html:138 msgid "Reason" msgstr "Raison" @@ -930,8 +958,55 @@ msgstr "Retirer produit" msgid "New invoice" msgstr "Nouvelle facture" -#~ msgid "Source and destination must be different." -#~ msgstr "La source et la destination doivent être différentes." +#: templates/treasury/remittance_form.html:7 +msgid "Remittance #" +msgstr "Remise n°" -#~ msgid "Transfer money from your account to one or others" -#~ msgstr "Transfert d'argent de ton compte vers un ou plusieurs autres" +#: templates/treasury/remittance_form.html:9 +#: templates/treasury/specialtransactionproxy_form.html:7 +msgid "Remittances list" +msgstr "Liste des remises" + +#: templates/treasury/remittance_form.html:12 +msgid "Count" +msgstr "Nombre" + +#: templates/treasury/remittance_form.html:29 +msgid "Linked transactions" +msgstr "Transactions liées" + +#: templates/treasury/remittance_form.html:34 +msgid "There is no transaction linked with this remittance." +msgstr "Il n'y a pas de transaction liée à cette remise." + +#: templates/treasury/remittance_list.html:6 +msgid "Opened remittances" +msgstr "Remises ouvertes" + +#: templates/treasury/remittance_list.html:11 +msgid "There is no opened remittance." +msgstr "Il n'y a pas de remise ouverte." + +#: templates/treasury/remittance_list.html:15 +msgid "New remittance" +msgstr "Nouvelle remise" + +#: templates/treasury/remittance_list.html:19 +msgid "Transfers without remittances" +msgstr "Transactions sans remise associée" + +#: templates/treasury/remittance_list.html:24 +msgid "There is no transaction without any linked remittance." +msgstr "Il n'y a pas de transactions sans remise associée." + +#: templates/treasury/remittance_list.html:30 +msgid "Transfers with opened remittances" +msgstr "Transactions avec remise associée ouverte" + +#: templates/treasury/remittance_list.html:35 +msgid "There is no transaction without an opened linked remittance." +msgstr "Il n'y a pas de transaction sans remise ouverte associée." + +#: templates/treasury/remittance_list.html:41 +msgid "Closed remittances" +msgstr "Remises fermées" From a52741b87f880d037cec424888b18c1a07cccd52 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 01:12:22 +0100 Subject: [PATCH 30/37] Add tabs (can be improved) --- templates/treasury/invoice_list.html | 13 +++++++++++++ templates/treasury/remittance_list.html | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/templates/treasury/invoice_list.html b/templates/treasury/invoice_list.html index f80e2342..f14d278d 100644 --- a/templates/treasury/invoice_list.html +++ b/templates/treasury/invoice_list.html @@ -3,6 +3,19 @@ {% load i18n %} {% block content %} + + {% render_table table %} {% trans "New invoice" %} diff --git a/templates/treasury/remittance_list.html b/templates/treasury/remittance_list.html index cb06bbd0..f56c0c79 100644 --- a/templates/treasury/remittance_list.html +++ b/templates/treasury/remittance_list.html @@ -3,6 +3,19 @@ {% load i18n %} {% block content %} + +

{% trans "Opened remittances" %}

{% if opened_remittances.data %} {% render_table opened_remittances %} From 1b03eac95b552c90edb8c5d35845d89ce00910bc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 01:34:47 +0100 Subject: [PATCH 31/37] Last and first name didn't auto-complete because of a bug --- static/js/base.js | 10 +++++++--- static/js/transfer.js | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/static/js/base.js b/static/js/base.js index bace7a9f..b22df077 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -67,6 +67,7 @@ function displayNote(note, alias, user_note_field=null, profile_pic_field=null) note.display_image = new_note.display_image.replace("http:", "https:"); note.name = new_note.name; note.balance = new_note.balance; + note.user = new_note.user; displayNote(note, alias, user_note_field, profile_pic_field); }); @@ -151,10 +152,13 @@ function autoCompleteNote(field_id, alias_matched_id, note_list_id, notes, notes let old_pattern = null; - // When the user type "Enter", the first alias is clicked + // When the user type "Enter", the first alias is clicked, and the informations are displayed field.keypress(function(event) { - if (event.originalEvent.charCode === 13) - $("#" + alias_matched_id + " li").first().trigger("click"); + if (event.originalEvent.charCode === 13) { + let li_obj = $("#" + alias_matched_id + " 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 diff --git a/static/js/transfer.js b/static/js/transfer.js index c615f932..a4171910 100644 --- a/static/js/transfer.js +++ b/static/js/transfer.js @@ -39,10 +39,21 @@ $(document).ready(function() { last.quantity = 1; - $.getJSON("/api/user/" + last.note.user + "/", function(user) { - $("#last_name").val(user.last_name); - $("#first_name").val(user.first_name); - }); + 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); + }); + } } return true; From 4cf1047faae95dadbbd85b7f052c6de79bd45f26 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 17:06:50 +0100 Subject: [PATCH 32/37] Manage remittance types --- apps/treasury/admin.py | 27 ++++++++++++++------ apps/treasury/api/serializers.py | 30 ++++++++++++++++++++++- apps/treasury/api/urls.py | 4 ++- apps/treasury/api/views.py | 24 ++++++++++++++++-- apps/treasury/fixtures/initial.json | 9 +++++++ apps/treasury/forms.py | 10 ++++---- apps/treasury/models.py | 26 +++++++++++++++----- apps/treasury/tables.py | 2 +- apps/treasury/views.py | 11 ++++----- locale/de/LC_MESSAGES/django.po | 38 +++++++++++++++-------------- locale/fr/LC_MESSAGES/django.po | 38 +++++++++++++++-------------- 11 files changed, 153 insertions(+), 66 deletions(-) create mode 100644 apps/treasury/fixtures/initial.json diff --git a/apps/treasury/admin.py b/apps/treasury/admin.py index cef9e37d..abeec3e3 100644 --- a/apps/treasury/admin.py +++ b/apps/treasury/admin.py @@ -1,16 +1,27 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: GPL-3.0-or-lateré from django.contrib import admin -from .models import Invoice, Product +from .models import RemittanceType, Remittance -@admin.register(Invoice) -class InvoiceAdmin(admin.ModelAdmin): - list_display = ('id', 'name', 'object', 'acquitted', ) +@admin.register(RemittanceType) +class RemittanceTypeAdmin(admin.ModelAdmin): + """ + Admin customisation for RemiitanceType + """ + list_display = ('note', ) -@admin.register(Product) -class ProductAdmin(admin.ModelAdmin): - list_display = ('designation', 'quantity', 'amount', ) +@admin.register(Remittance) +class RemittanceAdmin(admin.ModelAdmin): + """ + Admin customisation for Remittance + """ + list_display = ('remittance_type', 'date', 'comment', 'count', 'amount', 'closed', ) + + def has_change_permission(self, request, obj=None): + if not obj: + return True + return not obj.closed and super().has_change_permission(request, obj) diff --git a/apps/treasury/api/serializers.py b/apps/treasury/api/serializers.py index c835fd80..f1bbef75 100644 --- a/apps/treasury/api/serializers.py +++ b/apps/treasury/api/serializers.py @@ -2,8 +2,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later from rest_framework import serializers +from note.api.serializers import SpecialTransactionSerializer -from ..models import Invoice, Product +from ..models import Invoice, Product, RemittanceType, Remittance class ProductSerializer(serializers.ModelSerializer): @@ -32,3 +33,30 @@ class InvoiceSerializer(serializers.ModelSerializer): def get_products(self, obj): return serializers.ListSerializer(child=ProductSerializer())\ .to_representation(Product.objects.filter(invoice=obj).all()) + + +class RemittanceTypeSerializer(serializers.ModelSerializer): + """ + REST API Serializer for RemittanceType types. + The djangorestframework plugin will analyse the model `RemittanceType` and parse all fields in the API. + """ + + class Meta: + model = RemittanceType + fields = '__all__' + + +class RemittanceSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Remittance types. + The djangorestframework plugin will analyse the model `Remittance` and parse all fields in the API. + """ + + transactions = serializers.SerializerMethodField() + + class Meta: + model = Remittance + fields = '__all__' + + def get_transactions(self, obj): + return serializers.ListSerializer(child=SpecialTransactionSerializer()).to_representation(obj.transactions) diff --git a/apps/treasury/api/urls.py b/apps/treasury/api/urls.py index ff3a606f..30ac00e1 100644 --- a/apps/treasury/api/urls.py +++ b/apps/treasury/api/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import InvoiceViewSet, ProductViewSet +from .views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet def register_treasury_urls(router, path): @@ -10,3 +10,5 @@ def register_treasury_urls(router, path): """ router.register(path + '/invoice', InvoiceViewSet) router.register(path + '/product', ProductViewSet) + router.register(path + '/remittance_type', RemittanceTypeViewSet) + router.register(path + '/remittance', RemittanceViewSet) diff --git a/apps/treasury/api/views.py b/apps/treasury/api/views.py index 2f35b92c..7a70fd24 100644 --- a/apps/treasury/api/views.py +++ b/apps/treasury/api/views.py @@ -5,8 +5,8 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import SearchFilter from api.viewsets import ReadProtectedModelViewSet -from .serializers import InvoiceSerializer, ProductSerializer -from ..models import Invoice, Product +from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer +from ..models import Invoice, Product, RemittanceType, Remittance class InvoiceViewSet(ReadProtectedModelViewSet): @@ -31,3 +31,23 @@ class ProductViewSet(ReadProtectedModelViewSet): serializer_class = ProductSerializer filter_backends = [SearchFilter] search_fields = ['$designation', ] + + +class RemittanceTypeViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer + then render it on /api/treasury/remittance_type/ + """ + queryset = RemittanceType.objects.all() + serializer_class = RemittanceTypeSerializer + + +class RemittanceViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer, + then render it on /api/treasury/remittance/ + """ + queryset = Remittance.objects.all() + serializer_class = RemittanceSerializer diff --git a/apps/treasury/fixtures/initial.json b/apps/treasury/fixtures/initial.json new file mode 100644 index 00000000..143d2101 --- /dev/null +++ b/apps/treasury/fixtures/initial.json @@ -0,0 +1,9 @@ +[ + { + "model": "treasury.remittancetype", + "pk": 1, + "fields": { + "note": 3 + } + } +] diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index 65fae3a4..6269138c 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -49,8 +49,8 @@ class RemittanceForm(forms.ModelForm): self.helper = FormHelper() if self.instance.pk: - self.fields["type"].disabled = True - self.fields["type"].required = False + self.fields["remittance_type"].disabled = True + self.fields["remittance_type"].required = False if not self.instance.closed: self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) @@ -66,8 +66,8 @@ class RemittanceForm(forms.ModelForm): cleaned_data = super().clean() - if "type" in self.changed_data: - self.add_error("type", _("You can't change the type of the remittance.")) + if self.instance.pk and cleaned_data.get("remittance_type") != self.instance.remittance_type: + self.add_error("remittance_type", _("You can't change the type of the remittance.")) if "close" in self.data: self.instance.closed = True @@ -77,7 +77,7 @@ class RemittanceForm(forms.ModelForm): class Meta: model = Remittance - fields = ('type', 'comment', ) + fields = ('remittance_type', 'comment',) class LinkTransactionToRemittanceForm(forms.ModelForm): diff --git a/apps/treasury/models.py b/apps/treasury/models.py index e2be5af7..4180d065 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -96,6 +96,20 @@ class Product(models.Model): return self.total / 100 +class RemittanceType(models.Model): + """ + Store what kind of remittances can be stored. + """ + + note = models.OneToOneField( + NoteSpecial, + on_delete=models.CASCADE, + ) + + def __str__(self): + return str(self.note) + + class Remittance(models.Model): """ Treasurers want to regroup checks or bank transfers in bank remittances. @@ -106,8 +120,8 @@ class Remittance(models.Model): verbose_name=_("Date"), ) - type = models.ForeignKey( - NoteSpecial, + remittance_type = models.ForeignKey( + RemittanceType, on_delete=models.PROTECT, verbose_name=_("Type"), ) @@ -133,13 +147,13 @@ class Remittance(models.Model): def amount(self): return sum(transaction.total for transaction in self.transactions.all()) - def full_clean(self, exclude=None, validate_unique=True): - ret = super().full_clean(exclude, validate_unique) + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): - if self.transactions.filter(~Q(source=self.type)).exists(): + if self.transactions.filter(~Q(source=self.remittance_type.note)).exists(): raise ValidationError("All transactions in a remittance must have the same type") - return ret + return super().save(force_insert, force_update, using, update_fields) def __str__(self): return _("Remittance #{:d}: {}").format(self.id, self.comment, ) diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index f5d1a472..7c78a8a4 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -56,7 +56,7 @@ class RemittanceTable(tables.Table): } model = Remittance template_name = 'django_tables2/bootstrap4.html' - fields = ('id', 'date', 'type', 'comment', 'count', 'amount', 'view',) + fields = ('id', 'date', 'remittance_type', 'comment', 'count', 'amount', 'view',) class SpecialTransactionTable(tables.Table): diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 71b07b0b..981dbc76 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -17,11 +17,11 @@ from django.urls import reverse_lazy from django.views.generic import CreateView, UpdateView from django.views.generic.base import View, TemplateView from django_tables2 import SingleTableView -from note.models import SpecialTransaction +from note.models import SpecialTransaction, NoteSpecial from note_kfet.settings.base import BASE_DIR from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm -from .models import Invoice, Product, Remittance, SpecialTransactionProxy +from .models import Invoice, Product, Remittance, SpecialTransactionProxy, RemittanceType from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable @@ -212,11 +212,11 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all()) ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all()) ctx["special_transactions_no_remittance"] = SpecialTransactionTable( - data=SpecialTransaction.objects.filter(source__polymorphic_ctype__model="notespecial", + data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), specialtransactionproxy__remittance=None).all(), exclude=('remittance_remove', )) ctx["special_transactions_with_remittance"] = SpecialTransactionTable( - data=SpecialTransaction.objects.filter(source__polymorphic_ctype__model="notespecial", + data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), specialtransactionproxy__remittance__closed=False).all(), exclude=('remittance_add', )) @@ -236,7 +236,6 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView): def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) - form = ctx["form"] ctx["table"] = RemittanceTable(data=Remittance.objects.all()) data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all() ctx["special_transactions"] = SpecialTransactionTable( @@ -262,7 +261,7 @@ class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView): form.fields["bank"].initial = self.object.transaction.bank form.fields["amount"].initial = self.object.transaction.amount form.fields["remittance"].queryset = form.fields["remittance"] \ - .queryset.filter(type=self.object.transaction.source) + .queryset.filter(remittance_type__note=self.object.transaction.source) return ctx diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 5b7047ae..1dc8ab2a 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-24 01:00+0100\n" +"POT-Creation-Date: 2020-03-24 15:49+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -576,28 +576,29 @@ msgstr "" msgid "Unit price" msgstr "" -#: apps/treasury/models.py:106 +#: apps/treasury/models.py:120 msgid "Date" msgstr "" -#: apps/treasury/models.py:112 +#: apps/treasury/models.py:126 msgid "Type" msgstr "" -#: apps/treasury/models.py:117 +#: apps/treasury/models.py:131 msgid "Comment" msgstr "" -#: apps/treasury/models.py:122 +#: apps/treasury/models.py:136 msgid "Closed" msgstr "" -#: apps/treasury/models.py:145 +#: apps/treasury/models.py:159 msgid "Remittance #{:d}: {}" msgstr "" -#: apps/treasury/models.py:164 apps/treasury/tables.py:64 -#: apps/treasury/tables.py:72 +#: apps/treasury/models.py:178 apps/treasury/tables.py:64 +#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13 +#: templates/treasury/remittance_list.html:13 msgid "Remittance" msgstr "" @@ -605,7 +606,8 @@ msgstr "" msgid "Invoice #{:d}" msgstr "" -#: apps/treasury/tables.py:19 +#: apps/treasury/tables.py:19 templates/treasury/invoice_list.html:10 +#: templates/treasury/remittance_list.html:10 msgid "Invoice" msgstr "" @@ -952,7 +954,7 @@ msgstr "" msgid "Remove product" msgstr "" -#: templates/treasury/invoice_list.html:8 +#: templates/treasury/invoice_list.html:21 msgid "New invoice" msgstr "" @@ -977,34 +979,34 @@ msgstr "" msgid "There is no transaction linked with this remittance." msgstr "" -#: templates/treasury/remittance_list.html:6 +#: templates/treasury/remittance_list.html:19 msgid "Opened remittances" msgstr "" -#: templates/treasury/remittance_list.html:11 +#: templates/treasury/remittance_list.html:24 msgid "There is no opened remittance." msgstr "" -#: templates/treasury/remittance_list.html:15 +#: templates/treasury/remittance_list.html:28 msgid "New remittance" msgstr "" -#: templates/treasury/remittance_list.html:19 +#: templates/treasury/remittance_list.html:32 msgid "Transfers without remittances" msgstr "" -#: templates/treasury/remittance_list.html:24 +#: templates/treasury/remittance_list.html:37 msgid "There is no transaction without any linked remittance." msgstr "" -#: templates/treasury/remittance_list.html:30 +#: templates/treasury/remittance_list.html:43 msgid "Transfers with opened remittances" msgstr "" -#: templates/treasury/remittance_list.html:35 +#: templates/treasury/remittance_list.html:48 msgid "There is no transaction without an opened linked remittance." msgstr "" -#: templates/treasury/remittance_list.html:41 +#: templates/treasury/remittance_list.html:54 msgid "Closed remittances" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index b96f3f44..e2d41826 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-24 01:00+0100\n" +"POT-Creation-Date: 2020-03-24 15:49+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -576,28 +576,29 @@ msgstr "Quantité" msgid "Unit price" msgstr "Prix unitaire" -#: apps/treasury/models.py:106 +#: apps/treasury/models.py:120 msgid "Date" msgstr "Date" -#: apps/treasury/models.py:112 +#: apps/treasury/models.py:126 msgid "Type" msgstr "Type" -#: apps/treasury/models.py:117 +#: apps/treasury/models.py:131 msgid "Comment" msgstr "Commentaire" -#: apps/treasury/models.py:122 +#: apps/treasury/models.py:136 msgid "Closed" msgstr "Fermée" -#: apps/treasury/models.py:145 +#: apps/treasury/models.py:159 msgid "Remittance #{:d}: {}" msgstr "Remise n°{:d} : {}" -#: apps/treasury/models.py:164 apps/treasury/tables.py:64 -#: apps/treasury/tables.py:72 +#: apps/treasury/models.py:178 apps/treasury/tables.py:64 +#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13 +#: templates/treasury/remittance_list.html:13 msgid "Remittance" msgstr "Remise" @@ -605,7 +606,8 @@ msgstr "Remise" msgid "Invoice #{:d}" msgstr "Facture n°{:d}" -#: apps/treasury/tables.py:19 +#: apps/treasury/tables.py:19 templates/treasury/invoice_list.html:10 +#: templates/treasury/remittance_list.html:10 msgid "Invoice" msgstr "Facture" @@ -954,7 +956,7 @@ msgstr "Ajouter produit" msgid "Remove product" msgstr "Retirer produit" -#: templates/treasury/invoice_list.html:8 +#: templates/treasury/invoice_list.html:21 msgid "New invoice" msgstr "Nouvelle facture" @@ -979,34 +981,34 @@ msgstr "Transactions liées" msgid "There is no transaction linked with this remittance." msgstr "Il n'y a pas de transaction liée à cette remise." -#: templates/treasury/remittance_list.html:6 +#: templates/treasury/remittance_list.html:19 msgid "Opened remittances" msgstr "Remises ouvertes" -#: templates/treasury/remittance_list.html:11 +#: templates/treasury/remittance_list.html:24 msgid "There is no opened remittance." msgstr "Il n'y a pas de remise ouverte." -#: templates/treasury/remittance_list.html:15 +#: templates/treasury/remittance_list.html:28 msgid "New remittance" msgstr "Nouvelle remise" -#: templates/treasury/remittance_list.html:19 +#: templates/treasury/remittance_list.html:32 msgid "Transfers without remittances" msgstr "Transactions sans remise associée" -#: templates/treasury/remittance_list.html:24 +#: templates/treasury/remittance_list.html:37 msgid "There is no transaction without any linked remittance." msgstr "Il n'y a pas de transactions sans remise associée." -#: templates/treasury/remittance_list.html:30 +#: templates/treasury/remittance_list.html:43 msgid "Transfers with opened remittances" msgstr "Transactions avec remise associée ouverte" -#: templates/treasury/remittance_list.html:35 +#: templates/treasury/remittance_list.html:48 msgid "There is no transaction without an opened linked remittance." msgstr "Il n'y a pas de transaction sans remise ouverte associée." -#: templates/treasury/remittance_list.html:41 +#: templates/treasury/remittance_list.html:54 msgid "Closed remittances" msgstr "Remises fermées" From 093e585b795f05b82149320035b7b7d3540053ef Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 17:14:29 +0100 Subject: [PATCH 33/37] We don't need to create proxies for special transactions that don't require a remittance --- apps/treasury/apps.py | 10 ++++++++-- apps/treasury/signals.py | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/treasury/apps.py b/apps/treasury/apps.py index 14ca36e7..6e07a5f5 100644 --- a/apps/treasury/apps.py +++ b/apps/treasury/apps.py @@ -2,8 +2,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django.apps import AppConfig +from django.db.models import Q from django.db.models.signals import post_save, post_migrate from django.utils.translation import gettext_lazy as _ +from note.models import NoteSpecial class TreasuryConfig(AppConfig): @@ -21,8 +23,12 @@ class TreasuryConfig(AppConfig): post_save.connect(signals.save_special_transaction, sender=SpecialTransaction) def setup_specialtransactions_proxies(**kwargs): - # If the treasury app was disabled, we ensure that each special transaction is linked to a proxy - for transaction in SpecialTransaction.objects.filter(specialtransactionproxy=None): + # If the treasury app was disabled for any reason during a certain amount of time, + # we ensure that each special transaction is linked to a proxy + for transaction in SpecialTransaction.objects.filter( + source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), + specialtransactionproxy=None, + ): SpecialTransactionProxy.objects.create(transaction=transaction, remittance=None) post_migrate.connect(setup_specialtransactions_proxies, sender=SpecialTransactionProxy) diff --git a/apps/treasury/signals.py b/apps/treasury/signals.py index e1183d6b..54c19c09 100644 --- a/apps/treasury/signals.py +++ b/apps/treasury/signals.py @@ -1,12 +1,12 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from treasury.models import SpecialTransactionProxy +from treasury.models import SpecialTransactionProxy, RemittanceType def save_special_transaction(instance, created, **kwargs): """ When a special transaction is created, we create its linked proxy """ - if created: + if created and RemittanceType.objects.filter(note=instance.source).exists(): SpecialTransactionProxy.objects.create(transaction=instance, remittance=None).save() From a33d373f6e1213b3bf1b0556e0cd4274a480816e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 24 Mar 2020 20:22:15 +0100 Subject: [PATCH 34/37] Comment code --- apps/treasury/apps.py | 3 +- apps/treasury/forms.py | 39 ++++++++++++++ apps/treasury/models.py | 20 +++++-- apps/treasury/tables.py | 12 +++++ apps/treasury/urls.py | 2 + apps/treasury/views.py | 40 +++++++++++++- locale/de/LC_MESSAGES/django.po | 2 +- locale/fr/LC_MESSAGES/django.po | 6 +-- templates/treasury/invoice_form.html | 70 ++++++++++++++----------- templates/treasury/remittance_list.html | 2 +- 10 files changed, 152 insertions(+), 44 deletions(-) diff --git a/apps/treasury/apps.py b/apps/treasury/apps.py index 6e07a5f5..e2873ea2 100644 --- a/apps/treasury/apps.py +++ b/apps/treasury/apps.py @@ -5,7 +5,6 @@ from django.apps import AppConfig from django.db.models import Q from django.db.models.signals import post_save, post_migrate from django.utils.translation import gettext_lazy as _ -from note.models import NoteSpecial class TreasuryConfig(AppConfig): @@ -18,7 +17,7 @@ class TreasuryConfig(AppConfig): """ from . import signals - from note.models import SpecialTransaction + from note.models import SpecialTransaction, NoteSpecial from treasury.models import SpecialTransactionProxy post_save.connect(signals.save_special_transaction, sender=SpecialTransaction) diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index 6269138c..8692791c 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -12,6 +12,11 @@ from .models import Invoice, Product, Remittance, SpecialTransactionProxy class InvoiceForm(forms.ModelForm): + """ + Create and generate invoices. + """ + + # Django forms don't support date fields. We have to add it manually date = forms.DateField( initial=datetime.date.today, widget=forms.TextInput(attrs={'type': 'date'}) @@ -25,6 +30,8 @@ class InvoiceForm(forms.ModelForm): exclude = ('bde', ) +# Add a subform per product in the invoice form, and manage correctly the link between the invoice and +# its products. The FormSet will search automatically the ForeignKey in the Product model. ProductFormSet = forms.inlineformset_factory( Invoice, Product, @@ -34,6 +41,10 @@ ProductFormSet = forms.inlineformset_factory( class ProductFormSetHelper(FormHelper): + """ + Specify some template informations for the product form. + """ + def __init__(self, form=None): super().__init__(form) self.form_tag = False @@ -43,24 +54,33 @@ class ProductFormSetHelper(FormHelper): class RemittanceForm(forms.ModelForm): + """ + Create remittances. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() + # We can't update the type of the remittance once created. if self.instance.pk: self.fields["remittance_type"].disabled = True self.fields["remittance_type"].required = False + # We display the submit button iff the remittance is open, + # the close button iff it is open and has a linked transaction if not self.instance.closed: self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) if self.instance.transactions: self.helper.add_input(Submit("close", _("Close"), css_class='btn btn-success')) else: + # If the remittance is closed, we can't change anything self.fields["comment"].disabled = True self.fields["comment"].required = False def clean(self): + # We can't update anything if the remittance is already closed. if self.instance.closed: self.add_error("comment", _("Remittance is already closed.")) @@ -69,6 +89,7 @@ class RemittanceForm(forms.ModelForm): if self.instance.pk and cleaned_data.get("remittance_type") != self.instance.remittance_type: self.add_error("remittance_type", _("You can't change the type of the remittance.")) + # The close button is manually handled if "close" in self.data: self.instance.closed = True self.cleaned_data["closed"] = True @@ -81,6 +102,11 @@ class RemittanceForm(forms.ModelForm): class LinkTransactionToRemittanceForm(forms.ModelForm): + """ + Attach a special transaction to a remittance. + """ + + # Since we use a proxy model for special transactions, we add manually the fields related to the transaction last_name = forms.CharField(label=_("Last name")) first_name = forms.Field(label=_("First name")) @@ -92,21 +118,34 @@ class LinkTransactionToRemittanceForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() + # Add submit button self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) def clean_last_name(self): + """ + Replace the first name in the information of the transaction. + """ self.instance.transaction.last_name = self.data.get("last_name") self.instance.transaction.clean() def clean_first_name(self): + """ + Replace the last name in the information of the transaction. + """ self.instance.transaction.first_name = self.data.get("first_name") self.instance.transaction.clean() def clean_bank(self): + """ + Replace the bank in the information of the transaction. + """ self.instance.transaction.bank = self.data.get("bank") self.instance.transaction.clean() def clean_amount(self): + """ + Replace the amount of the transaction. + """ self.instance.transaction.amount = self.data.get("amount") self.instance.transaction.clean() diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 4180d065..a342eeb1 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -10,7 +10,7 @@ from note.models import NoteSpecial, SpecialTransaction class Invoice(models.Model): """ - An invoice model that can generate a true invoice + An invoice model that can generates a true invoice. """ id = models.PositiveIntegerField( @@ -62,7 +62,7 @@ class Invoice(models.Model): class Product(models.Model): """ - Product that appear on an invoice. + Product that appears on an invoice. """ invoice = models.ForeignKey( @@ -138,18 +138,28 @@ class Remittance(models.Model): @property def transactions(self): + """ + :return: Transactions linked to this remittance. + """ + if not self.pk: + return SpecialTransaction.objects.none() return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self) def count(self): + """ + Linked transactions count. + """ return self.transactions.count() @property def amount(self): + """ + Total amount of the remittance. + """ return sum(transaction.total for transaction in self.transactions.all()) - def save(self, force_insert=False, force_update=False, using=None, - update_fields=None): - + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + # Check if all transactions have the right type. if self.transactions.filter(~Q(source=self.remittance_type.note)).exists(): raise ValidationError("All transactions in a remittance must have the same type") diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index 7c78a8a4..1ecc04db 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -11,6 +11,9 @@ from .models import Invoice, Remittance class InvoiceTable(tables.Table): + """ + List all invoices. + """ id = tables.LinkColumn("treasury:invoice_update", args=[A("pk")], text=lambda record: _("Invoice #{:d}").format(record.id), ) @@ -35,6 +38,10 @@ class InvoiceTable(tables.Table): class RemittanceTable(tables.Table): + """ + List all remittances. + """ + count = tables.Column(verbose_name=_("Transaction count")) amount = tables.Column(verbose_name=_("Amount")) @@ -60,6 +67,11 @@ class RemittanceTable(tables.Table): class SpecialTransactionTable(tables.Table): + """ + List special credit transactions that are (or not, following the queryset) attached to a remittance. + """ + + # Display add and remove buttons. Use the `exclude` field to select what is needed. remittance_add = tables.LinkColumn("treasury:link_transaction", verbose_name=_("Remittance"), args=[A("specialtransactionproxy.pk")], diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py index fa5ef0e4..d44cc414 100644 --- a/apps/treasury/urls.py +++ b/apps/treasury/urls.py @@ -8,11 +8,13 @@ from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, Invoic app_name = 'treasury' urlpatterns = [ + # Invoice app paths path('invoice/', InvoiceListView.as_view(), name='invoice_list'), path('invoice/create/', InvoiceCreateView.as_view(), name='invoice_create'), path('invoice//', InvoiceUpdateView.as_view(), name='invoice_update'), path('invoice/render//', InvoiceRenderView.as_view(), name='invoice_render'), + # Remittance app paths path('remittance/', RemittanceListView.as_view(), name='remittance_list'), path('remittance/create/', RemittanceCreateView.as_view(), name='remittance_create'), path('remittance//', RemittanceUpdateView.as_view(), name='remittance_update'), diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 981dbc76..90440566 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -21,7 +21,7 @@ from note.models import SpecialTransaction, NoteSpecial from note_kfet.settings.base import BASE_DIR from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm -from .models import Invoice, Product, Remittance, SpecialTransactionProxy, RemittanceType +from .models import Invoice, Product, Remittance, SpecialTransactionProxy from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable @@ -34,9 +34,12 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + form = context['form'] form.helper = FormHelper() + # Remove form tag on the generation of the form in the template (already present on the template) form.helper.form_tag = False + # The formset handles the set of the products form_set = ProductFormSet(instance=form.instance) context['formset'] = form_set context['helper'] = ProductFormSetHelper() @@ -48,6 +51,8 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView): ret = super().form_valid(form) kwargs = {} + + # The user type amounts in cents. We convert it in euros. for key in self.request.POST: value = self.request.POST[key] if key.endswith("amount") and value: @@ -55,9 +60,11 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView): elif value: kwargs[key] = value + # For each product, we save it formset = ProductFormSet(kwargs, instance=form.instance) if formset.is_valid(): for f in formset: + # We don't save the product if the designation is not entered, ie. if the line is empty if f.is_valid() and f.instance.designation: f.save() f.instance.save() @@ -87,10 +94,14 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + form = context['form'] form.helper = FormHelper() + # Remove form tag on the generation of the form in the template (already present on the template) form.helper.form_tag = False + # Fill the intial value for the date field, with the initial date of the model instance form.fields['date'].initial = form.instance.date + # The formset handles the set of the products form_set = ProductFormSet(instance=form.instance) context['formset'] = form_set context['helper'] = ProductFormSetHelper() @@ -102,6 +113,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): ret = super().form_valid(form) kwargs = {} + # The user type amounts in cents. We convert it in euros. for key in self.request.POST: value = self.request.POST[key] if key.endswith("amount") and value: @@ -111,14 +123,17 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): formset = ProductFormSet(kwargs, instance=form.instance) saved = [] + # For each product, we save it if formset.is_valid(): for f in formset: + # We don't save the product if the designation is not entered, ie. if the line is empty if f.is_valid() and f.instance.designation: f.save() f.instance.save() saved.append(f.instance.pk) else: f.instance = None + # Remove old products that weren't given in the form Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete() return ret @@ -129,7 +144,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView): class InvoiceRenderView(LoginRequiredMixin, View): """ - Render Invoice as generated PDF + Render Invoice as a generated PDF with the given information and a LaTeX template """ def get(self, request, **kwargs): @@ -137,6 +152,7 @@ class InvoiceRenderView(LoginRequiredMixin, View): invoice = Invoice.objects.get(pk=pk) products = Product.objects.filter(invoice=invoice).all() + # Informations of the BDE. Should be updated when the school will move. invoice.place = "Cachan" invoice.my_name = "BDE ENS Cachan" invoice.my_address_street = "61 avenue du Président Wilson" @@ -147,13 +163,17 @@ class InvoiceRenderView(LoginRequiredMixin, View): invoice.rib_key = 14 invoice.bic = "SOGEFRPP" + # Replace line breaks with the LaTeX equivalent invoice.description = invoice.description.replace("\r", "").replace("\n", "\\\\ ") invoice.address = invoice.address.replace("\r", "").replace("\n", "\\\\ ") + # Fill the template with the information tex = render_to_string("treasury/invoice_sample.tex", dict(obj=invoice, products=products)) + try: os.mkdir(BASE_DIR + "/tmp") except FileExistsError: pass + # We render the file in a temporary directory tmp_dir = mkdtemp(prefix=BASE_DIR + "/tmp/") try: @@ -161,21 +181,27 @@ class InvoiceRenderView(LoginRequiredMixin, View): f.write(tex.encode("UTF-8")) del tex + # The file has to be rendered twice for _ in range(2): error = subprocess.Popen( ["pdflatex", "invoice-{}.tex".format(pk)], cwd=tmp_dir, + stdin=open(os.devnull, "r"), + stderr=open(os.devnull, "wb"), + stdout=open(os.devnull, "wb"), ).wait() if error: raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")") + # Display the generated pdf as a HTTP Response pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read() response = HttpResponse(pdf, content_type="application/pdf") response['Content-Disposition'] = "inline;filename=invoice-{:d}.pdf".format(pk) except IOError as e: raise e finally: + # Delete all temporary files shutil.rmtree(tmp_dir) return response @@ -211,6 +237,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all()) ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all()) + ctx["special_transactions_no_remittance"] = SpecialTransactionTable( data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), specialtransactionproxy__remittance=None).all(), @@ -246,6 +273,10 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView): class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView): + """ + Attach a special transaction to a remittance + """ + model = SpecialTransactionProxy form_class = LinkTransactionToRemittanceForm @@ -267,10 +298,15 @@ class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView): class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View): + """ + Unlink a special transaction and its remittance + """ + def get(self, *args, **kwargs): pk = kwargs["pk"] transaction = SpecialTransactionProxy.objects.get(pk=pk) + # The remittance must be open (or inexistant) if transaction.remittance and transaction.remittance.closed: raise ValidationError("Remittance is already closed.") diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 1dc8ab2a..b6a8c120 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -1004,7 +1004,7 @@ msgid "Transfers with opened remittances" msgstr "" #: templates/treasury/remittance_list.html:48 -msgid "There is no transaction without an opened linked remittance." +msgid "There is no transaction with an opened linked remittance." msgstr "" #: templates/treasury/remittance_list.html:54 diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index e2d41826..67af6bee 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -1003,11 +1003,11 @@ msgstr "Il n'y a pas de transactions sans remise associée." #: templates/treasury/remittance_list.html:43 msgid "Transfers with opened remittances" -msgstr "Transactions avec remise associée ouverte" +msgstr "Transactions associées à une remise ouverte" #: templates/treasury/remittance_list.html:48 -msgid "There is no transaction without an opened linked remittance." -msgstr "Il n'y a pas de transaction sans remise ouverte associée." +msgid "There is no transaction with an opened linked remittance." +msgstr "Il n'y a pas de transaction associée à une remise ouverte." #: templates/treasury/remittance_list.html:54 msgid "Closed remittances" diff --git a/templates/treasury/invoice_form.html b/templates/treasury/invoice_form.html index f6e2a106..de43af22 100644 --- a/templates/treasury/invoice_form.html +++ b/templates/treasury/invoice_form.html @@ -6,22 +6,29 @@

{% trans "Invoices list" %}

{% csrf_token %} + {# Render the invoice form #} {% crispy form %} + {# The next part concerns the product formset #} + {# Generate some hidden fields that manage the number of products, and make easier the parsing #} {{ formset.management_form }} - {% for form in formset %} - {% if forloop.first %} - - - - - - - {% endif %} - + {# Fill initial data #} + {% for form in formset %} + {% if forloop.first %} + + + + + + + + + {% endif %} + + {# These fields are hidden but handled by the formset to link the id and the invoice id #} {{ form.invoice }} {{ form.id }} - - {% endfor %} - + + {% endfor %} +
{{ form.designation.label }}*{{ form.quantity.label }}*{{ form.amount.label }}*
{{ form.designation.label }}*{{ form.quantity.label }}*{{ form.amount.label }}*
{{ form.designation }} {{ form.quantity }} + {# Use custom input for amount, with the € symbol #}
+ {# Display buttons to add and remove products #}
@@ -49,43 +58,44 @@ {% endblock %} {% block extrajavascript %} -