diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b5cb5ef..e94b7d00 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,11 +8,12 @@ setup: - apt-get update - apt-get install -y gettext - pip install -r requirements.txt + - pip install coverage + - pip install black test: stage: test script: - - pip install coverage - ./manage.py compilemessages - coverage run ./manage.py test - coverage html @@ -24,5 +25,4 @@ test: black: stage: test script: - - pip install black - black --check . diff --git a/README.md b/README.md index b82e3a49..e4e66955 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) [![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) ## Sith AE diff --git a/accounting/__init__.py b/accounting/__init__.py index 0a9419f8..0ace29c4 100644 --- a/accounting/__init__.py +++ b/accounting/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py index 19b967bc..9bd829a6 100644 --- a/accounting/migrations/0001_initial.py +++ b/accounting/migrations/0001_initial.py @@ -8,103 +8,271 @@ import accounting.models class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='AccountingType', + name="AccountingType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('code', models.CharField(max_length=16, verbose_name='code', validators=[django.core.validators.RegexValidator('^[0-9]*$', 'An accounting type code contains only numbers')])), - ('label', models.CharField(max_length=128, verbose_name='label')), - ('movement_type', models.CharField(choices=[('CREDIT', 'Credit'), ('DEBIT', 'Debit'), ('NEUTRAL', 'Neutral')], max_length=12, verbose_name='movement type')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "code", + models.CharField( + max_length=16, + verbose_name="code", + validators=[ + django.core.validators.RegexValidator( + "^[0-9]*$", + "An accounting type code contains only numbers", + ) + ], + ), + ), + ("label", models.CharField(max_length=128, verbose_name="label")), + ( + "movement_type", + models.CharField( + choices=[ + ("CREDIT", "Credit"), + ("DEBIT", "Debit"), + ("NEUTRAL", "Neutral"), + ], + max_length=12, + verbose_name="movement type", + ), + ), ], options={ - 'verbose_name': 'accounting type', - 'ordering': ['movement_type', 'code'], + "verbose_name": "accounting type", + "ordering": ["movement_type", "code"], }, ), migrations.CreateModel( - name='BankAccount', + name="BankAccount", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('iban', models.CharField(max_length=255, blank=True, verbose_name='iban')), - ('number', models.CharField(max_length=255, blank=True, verbose_name='account number')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "iban", + models.CharField(max_length=255, blank=True, verbose_name="iban"), + ), + ( + "number", + models.CharField( + max_length=255, blank=True, verbose_name="account number" + ), + ), + ], + options={"verbose_name": "Bank account", "ordering": ["club", "name"]}, + ), + migrations.CreateModel( + name="ClubAccount", + fields=[ + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), ], options={ - 'verbose_name': 'Bank account', - 'ordering': ['club', 'name'], + "verbose_name": "Club account", + "ordering": ["bank_account", "name"], }, ), migrations.CreateModel( - name='ClubAccount', + name="Company", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=60, verbose_name="name")), ], - options={ - 'verbose_name': 'Club account', - 'ordering': ['bank_account', 'name'], - }, + options={"verbose_name": "company"}, ), migrations.CreateModel( - name='Company', + name="GeneralJournal", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=60, verbose_name='name')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start_date", models.DateField(verbose_name="start date")), + ( + "end_date", + models.DateField( + null=True, verbose_name="end date", default=None, blank=True + ), + ), + ("name", models.CharField(max_length=40, verbose_name="name")), + ( + "closed", + models.BooleanField(verbose_name="is closed", default=False), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, + default=0, + verbose_name="amount", + max_digits=12, + ), + ), + ( + "effective_amount", + accounting.models.CurrencyField( + decimal_places=2, + default=0, + verbose_name="effective_amount", + max_digits=12, + ), + ), ], - options={ - 'verbose_name': 'company', - }, + options={"verbose_name": "General journal", "ordering": ["-start_date"]}, ), migrations.CreateModel( - name='GeneralJournal', + name="Operation", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateField(verbose_name='start date')), - ('end_date', models.DateField(null=True, verbose_name='end date', default=None, blank=True)), - ('name', models.CharField(max_length=40, verbose_name='name')), - ('closed', models.BooleanField(verbose_name='is closed', default=False)), - ('amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='amount', max_digits=12)), - ('effective_amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='effective_amount', max_digits=12)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("number", models.IntegerField(verbose_name="number")), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), + ("date", models.DateField(verbose_name="date")), + ("remark", models.CharField(max_length=128, verbose_name="comment")), + ( + "mode", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CASH", "Cash"), + ("TRANSFERT", "Transfert"), + ("CARD", "Credit card"), + ], + max_length=255, + verbose_name="payment method", + ), + ), + ( + "cheque_number", + models.CharField( + max_length=32, + null=True, + verbose_name="cheque number", + default="", + blank=True, + ), + ), + ("done", models.BooleanField(verbose_name="is done", default=False)), + ( + "target_type", + models.CharField( + choices=[ + ("USER", "User"), + ("CLUB", "Club"), + ("ACCOUNT", "Account"), + ("COMPANY", "Company"), + ("OTHER", "Other"), + ], + max_length=10, + verbose_name="target type", + ), + ), + ( + "target_id", + models.IntegerField( + null=True, verbose_name="target id", blank=True + ), + ), + ( + "target_label", + models.CharField( + max_length=32, + blank=True, + verbose_name="target label", + default="", + ), + ), + ( + "accounting_type", + models.ForeignKey( + null=True, + related_name="operations", + verbose_name="accounting type", + to="accounting.AccountingType", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'General journal', - 'ordering': ['-start_date'], - }, + options={"ordering": ["-number"]}, ), migrations.CreateModel( - name='Operation', + name="SimplifiedAccountingType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('number', models.IntegerField(verbose_name='number')), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), - ('date', models.DateField(verbose_name='date')), - ('remark', models.CharField(max_length=128, verbose_name='comment')), - ('mode', models.CharField(choices=[('CHECK', 'Check'), ('CASH', 'Cash'), ('TRANSFERT', 'Transfert'), ('CARD', 'Credit card')], max_length=255, verbose_name='payment method')), - ('cheque_number', models.CharField(max_length=32, null=True, verbose_name='cheque number', default='', blank=True)), - ('done', models.BooleanField(verbose_name='is done', default=False)), - ('target_type', models.CharField(choices=[('USER', 'User'), ('CLUB', 'Club'), ('ACCOUNT', 'Account'), ('COMPANY', 'Company'), ('OTHER', 'Other')], max_length=10, verbose_name='target type')), - ('target_id', models.IntegerField(null=True, verbose_name='target id', blank=True)), - ('target_label', models.CharField(max_length=32, blank=True, verbose_name='target label', default='')), - ('accounting_type', models.ForeignKey(null=True, related_name='operations', verbose_name='accounting type', to='accounting.AccountingType', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("label", models.CharField(max_length=128, verbose_name="label")), + ( + "accounting_type", + models.ForeignKey( + verbose_name="simplified accounting types", + to="accounting.AccountingType", + related_name="simplified_types", + ), + ), ], options={ - 'ordering': ['-number'], - }, - ), - migrations.CreateModel( - name='SimplifiedAccountingType', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('label', models.CharField(max_length=128, verbose_name='label')), - ('accounting_type', models.ForeignKey(verbose_name='simplified accounting types', to='accounting.AccountingType', related_name='simplified_types')), - ], - options={ - 'verbose_name': 'simplified type', - 'ordering': ['accounting_type__movement_type', 'accounting_type__code'], + "verbose_name": "simplified type", + "ordering": ["accounting_type__movement_type", "accounting_type__code"], }, ), ] diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py index b44310ff..e0322f08 100644 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ b/accounting/migrations/0002_auto_20160824_2152.py @@ -7,54 +7,88 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('club', '0001_initial'), - ('accounting', '0001_initial'), - ('core', '0001_initial'), + ("club", "0001_initial"), + ("accounting", "0001_initial"), + ("core", "0001_initial"), ] operations = [ migrations.AddField( - model_name='operation', - name='invoice', - field=models.ForeignKey(null=True, related_name='operations', verbose_name='invoice', to='core.SithFile', blank=True), + model_name="operation", + name="invoice", + field=models.ForeignKey( + null=True, + related_name="operations", + verbose_name="invoice", + to="core.SithFile", + blank=True, + ), ), migrations.AddField( - model_name='operation', - name='journal', - field=models.ForeignKey(verbose_name='journal', to='accounting.GeneralJournal', related_name='operations'), + model_name="operation", + name="journal", + field=models.ForeignKey( + verbose_name="journal", + to="accounting.GeneralJournal", + related_name="operations", + ), ), migrations.AddField( - model_name='operation', - name='linked_operation', - field=models.OneToOneField(blank=True, to='accounting.Operation', null=True, related_name='operation_linked_to', verbose_name='linked operation', default=None), + model_name="operation", + name="linked_operation", + field=models.OneToOneField( + blank=True, + to="accounting.Operation", + null=True, + related_name="operation_linked_to", + verbose_name="linked operation", + default=None, + ), ), migrations.AddField( - model_name='operation', - name='simpleaccounting_type', - field=models.ForeignKey(null=True, related_name='operations', verbose_name='simple type', to='accounting.SimplifiedAccountingType', blank=True), + model_name="operation", + name="simpleaccounting_type", + field=models.ForeignKey( + null=True, + related_name="operations", + verbose_name="simple type", + to="accounting.SimplifiedAccountingType", + blank=True, + ), ), migrations.AddField( - model_name='generaljournal', - name='club_account', - field=models.ForeignKey(verbose_name='club account', to='accounting.ClubAccount', related_name='journals'), + model_name="generaljournal", + name="club_account", + field=models.ForeignKey( + verbose_name="club account", + to="accounting.ClubAccount", + related_name="journals", + ), ), migrations.AddField( - model_name='clubaccount', - name='bank_account', - field=models.ForeignKey(verbose_name='bank account', to='accounting.BankAccount', related_name='club_accounts'), + model_name="clubaccount", + name="bank_account", + field=models.ForeignKey( + verbose_name="bank account", + to="accounting.BankAccount", + related_name="club_accounts", + ), ), migrations.AddField( - model_name='clubaccount', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='club_account'), + model_name="clubaccount", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="club_account" + ), ), migrations.AddField( - model_name='bankaccount', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='bank_accounts'), + model_name="bankaccount", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="bank_accounts" + ), ), migrations.AlterUniqueTogether( - name='operation', - unique_together=set([('number', 'journal')]), + name="operation", unique_together=set([("number", "journal")]) ), ] diff --git a/accounting/migrations/0003_auto_20160824_2203.py b/accounting/migrations/0003_auto_20160824_2203.py index 57bd9e22..47ae232f 100644 --- a/accounting/migrations/0003_auto_20160824_2203.py +++ b/accounting/migrations/0003_auto_20160824_2203.py @@ -7,44 +7,44 @@ import phonenumber_field.modelfields class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0002_auto_20160824_2152'), - ] + dependencies = [("accounting", "0002_auto_20160824_2152")] operations = [ migrations.AddField( - model_name='company', - name='city', - field=models.CharField(blank=True, verbose_name='city', max_length=60), + model_name="company", + name="city", + field=models.CharField(blank=True, verbose_name="city", max_length=60), ), migrations.AddField( - model_name='company', - name='country', - field=models.CharField(blank=True, verbose_name='country', max_length=32), + model_name="company", + name="country", + field=models.CharField(blank=True, verbose_name="country", max_length=32), ), migrations.AddField( - model_name='company', - name='email', - field=models.EmailField(blank=True, verbose_name='email', max_length=254), + model_name="company", + name="email", + field=models.EmailField(blank=True, verbose_name="email", max_length=254), ), migrations.AddField( - model_name='company', - name='phone', - field=phonenumber_field.modelfields.PhoneNumberField(blank=True, verbose_name='phone', max_length=128), + model_name="company", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, verbose_name="phone", max_length=128 + ), ), migrations.AddField( - model_name='company', - name='postcode', - field=models.CharField(blank=True, verbose_name='postcode', max_length=10), + model_name="company", + name="postcode", + field=models.CharField(blank=True, verbose_name="postcode", max_length=10), ), migrations.AddField( - model_name='company', - name='street', - field=models.CharField(blank=True, verbose_name='street', max_length=60), + model_name="company", + name="street", + field=models.CharField(blank=True, verbose_name="street", max_length=60), ), migrations.AddField( - model_name='company', - name='website', - field=models.CharField(blank=True, verbose_name='website', max_length=64), + model_name="company", + name="website", + field=models.CharField(blank=True, verbose_name="website", max_length=64), ), ] diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py index b1bfe2d8..c363cddb 100644 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ b/accounting/migrations/0004_auto_20161005_1505.py @@ -7,26 +7,45 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0003_auto_20160824_2203'), - ] + dependencies = [("accounting", "0003_auto_20160824_2203")] operations = [ migrations.CreateModel( - name='Label', + name="Label", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('name', models.CharField(max_length=64, verbose_name='label')), - ('club_account', models.ForeignKey(related_name='labels', verbose_name='club account', to='accounting.ClubAccount')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + auto_created=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="label")), + ( + "club_account", + models.ForeignKey( + related_name="labels", + verbose_name="club account", + to="accounting.ClubAccount", + ), + ), ], ), migrations.AddField( - model_name='operation', - name='label', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, related_name='operations', null=True, blank=True, verbose_name='label', to='accounting.Label'), + model_name="operation", + name="label", + field=models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + related_name="operations", + null=True, + blank=True, + verbose_name="label", + to="accounting.Label", + ), ), migrations.AlterUniqueTogether( - name='label', - unique_together=set([('name', 'club_account')]), + name="label", unique_together=set([("name", "club_account")]) ), ] diff --git a/accounting/migrations/0005_auto_20170324_0917.py b/accounting/migrations/0005_auto_20170324_0917.py index d97d82f0..c8258f8e 100644 --- a/accounting/migrations/0005_auto_20170324_0917.py +++ b/accounting/migrations/0005_auto_20170324_0917.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0004_auto_20161005_1505'), - ] + dependencies = [("accounting", "0004_auto_20161005_1505")] operations = [ migrations.AlterField( - model_name='operation', - name='remark', - field=models.CharField(null=True, max_length=128, blank=True, verbose_name='comment'), - ), + model_name="operation", + name="remark", + field=models.CharField( + null=True, max_length=128, blank=True, verbose_name="comment" + ), + ) ] diff --git a/accounting/models.py b/accounting/models.py index e5d73652..efcbdf2c 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -41,9 +41,10 @@ class CurrencyField(models.DecimalField): """ This is a custom database field used for currency """ + def __init__(self, *args, **kwargs): - kwargs['max_digits'] = 12 - kwargs['decimal_places'] = 2 + kwargs["max_digits"] = 12 + kwargs["decimal_places"] = 2 super(CurrencyField, self).__init__(*args, **kwargs) def to_python(self, value): @@ -52,18 +53,19 @@ class CurrencyField(models.DecimalField): except AttributeError: return None + # Accounting classes class Company(models.Model): - name = models.CharField(_('name'), max_length=60) - street = models.CharField(_('street'), max_length=60, blank=True) - city = models.CharField(_('city'), max_length=60, blank=True) - postcode = models.CharField(_('postcode'), max_length=10, blank=True) - country = models.CharField(_('country'), max_length=32, blank=True) - phone = PhoneNumberField(_('phone'), blank=True) - email = models.EmailField(_('email'), blank=True) - website = models.CharField(_('website'), max_length=64, blank=True) + name = models.CharField(_("name"), max_length=60) + street = models.CharField(_("street"), max_length=60, blank=True) + city = models.CharField(_("city"), max_length=60, blank=True) + postcode = models.CharField(_("postcode"), max_length=10, blank=True) + country = models.CharField(_("country"), max_length=32, blank=True) + phone = PhoneNumberField(_("phone"), blank=True) + email = models.EmailField(_("email"), blank=True) + website = models.CharField(_("website"), max_length=64, blank=True) class Meta: verbose_name = _("company") @@ -81,7 +83,7 @@ class Company(models.Model): Method to see if that object can be edited by the given user """ for club in user.memberships.filter(end_date=None).all(): - if club and club.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if club and club.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -90,12 +92,12 @@ class Company(models.Model): Method to see if that object can be viewed by the given user """ for club in user.memberships.filter(end_date=None).all(): - if club and club.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if club and club.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:co_edit', kwargs={'co_id': self.id}) + return reverse("accounting:co_edit", kwargs={"co_id": self.id}) def get_display_name(self): return self.name @@ -105,14 +107,14 @@ class Company(models.Model): class BankAccount(models.Model): - name = models.CharField(_('name'), max_length=30) - iban = models.CharField(_('iban'), max_length=255, blank=True) - number = models.CharField(_('account number'), max_length=255, blank=True) + name = models.CharField(_("name"), max_length=30) + iban = models.CharField(_("iban"), max_length=255, blank=True) + number = models.CharField(_("account number"), max_length=255, blank=True) club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club")) class Meta: verbose_name = _("Bank account") - ordering = ['club', 'name'] + ordering = ["club", "name"] def is_owned_by(self, user): """ @@ -121,25 +123,27 @@ class BankAccount(models.Model): if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return True m = self.club.get_membership_for(user) - if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:bank_details', kwargs={'b_account_id': self.id}) + return reverse("accounting:bank_details", kwargs={"b_account_id": self.id}) def __str__(self): return self.name class ClubAccount(models.Model): - name = models.CharField(_('name'), max_length=30) + name = models.CharField(_("name"), max_length=30) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) - bank_account = models.ForeignKey(BankAccount, related_name="club_accounts", verbose_name=_("bank account")) + bank_account = models.ForeignKey( + BankAccount, related_name="club_accounts", verbose_name=_("bank account") + ) class Meta: verbose_name = _("Club account") - ordering = ['bank_account', 'name'] + ordering = ["bank_account", "name"] def is_owned_by(self, user): """ @@ -154,7 +158,7 @@ class ClubAccount(models.Model): Method to see if that object can be edited by the given user """ m = self.club.get_membership_for(user) - if m and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -163,7 +167,7 @@ class ClubAccount(models.Model): Method to see if that object can be viewed by the given user """ m = self.club.get_membership_for(user) - if m and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -177,30 +181,36 @@ class ClubAccount(models.Model): return self.journals.filter(closed=False).first() def get_absolute_url(self): - return reverse('accounting:club_details', kwargs={'c_account_id': self.id}) + return reverse("accounting:club_details", kwargs={"c_account_id": self.id}) def __str__(self): return self.name def get_display_name(self): - return _("%(club_account)s on %(bank_account)s") % {"club_account": self.name, "bank_account": self.bank_account} + return _("%(club_account)s on %(bank_account)s") % { + "club_account": self.name, + "bank_account": self.bank_account, + } class GeneralJournal(models.Model): """ Class storing all the operations for a period of time """ - start_date = models.DateField(_('start date')) - end_date = models.DateField(_('end date'), null=True, blank=True, default=None) - name = models.CharField(_('name'), max_length=40) - closed = models.BooleanField(_('is closed'), default=False) - club_account = models.ForeignKey(ClubAccount, related_name="journals", null=False, verbose_name=_("club account")) - amount = CurrencyField(_('amount'), default=0) - effective_amount = CurrencyField(_('effective_amount'), default=0) + + start_date = models.DateField(_("start date")) + end_date = models.DateField(_("end date"), null=True, blank=True, default=None) + name = models.CharField(_("name"), max_length=40) + closed = models.BooleanField(_("is closed"), default=False) + club_account = models.ForeignKey( + ClubAccount, related_name="journals", null=False, verbose_name=_("club account") + ) + amount = CurrencyField(_("amount"), default=0) + effective_amount = CurrencyField(_("effective_amount"), default=0) class Meta: verbose_name = _("General journal") - ordering = ['-start_date'] + ordering = ["-start_date"] def is_owned_by(self, user): """ @@ -226,7 +236,7 @@ class GeneralJournal(models.Model): return self.club_account.can_be_viewed_by(user) def get_absolute_url(self): - return reverse('accounting:journal_details', kwargs={'j_id': self.id}) + return reverse("accounting:journal_details", kwargs={"j_id": self.id}) def __str__(self): return self.name @@ -250,31 +260,79 @@ class Operation(models.Model): """ An operation is a line in the journal, a debit or a credit """ - number = models.IntegerField(_('number')) - journal = models.ForeignKey(GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")) - amount = CurrencyField(_('amount')) - date = models.DateField(_('date')) - remark = models.CharField(_('comment'), max_length=128, null=True, blank=True) - mode = models.CharField(_('payment method'), max_length=255, choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD) - cheque_number = models.CharField(_('cheque number'), max_length=32, default="", null=True, blank=True) - invoice = models.ForeignKey(SithFile, related_name='operations', verbose_name=_("invoice"), null=True, blank=True) - done = models.BooleanField(_('is done'), default=False) - simpleaccounting_type = models.ForeignKey('SimplifiedAccountingType', related_name="operations", - verbose_name=_("simple type"), null=True, blank=True) - accounting_type = models.ForeignKey('AccountingType', related_name="operations", - verbose_name=_("accounting type"), null=True, blank=True) - label = models.ForeignKey('Label', related_name="operations", - verbose_name=_("label"), null=True, blank=True, on_delete=models.SET_NULL) - target_type = models.CharField(_('target type'), max_length=10, - choices=[('USER', _('User')), ('CLUB', _('Club')), ('ACCOUNT', _('Account')), ('COMPANY', _('Company')), ('OTHER', _('Other'))]) - target_id = models.IntegerField(_('target id'), null=True, blank=True) - target_label = models.CharField(_('target label'), max_length=32, default="", blank=True) - linked_operation = models.OneToOneField('self', related_name='operation_linked_to', verbose_name=_("linked operation"), - null=True, blank=True, default=None) + + number = models.IntegerField(_("number")) + journal = models.ForeignKey( + GeneralJournal, related_name="operations", null=False, verbose_name=_("journal") + ) + amount = CurrencyField(_("amount")) + date = models.DateField(_("date")) + remark = models.CharField(_("comment"), max_length=128, null=True, blank=True) + mode = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD, + ) + cheque_number = models.CharField( + _("cheque number"), max_length=32, default="", null=True, blank=True + ) + invoice = models.ForeignKey( + SithFile, + related_name="operations", + verbose_name=_("invoice"), + null=True, + blank=True, + ) + done = models.BooleanField(_("is done"), default=False) + simpleaccounting_type = models.ForeignKey( + "SimplifiedAccountingType", + related_name="operations", + verbose_name=_("simple type"), + null=True, + blank=True, + ) + accounting_type = models.ForeignKey( + "AccountingType", + related_name="operations", + verbose_name=_("accounting type"), + null=True, + blank=True, + ) + label = models.ForeignKey( + "Label", + related_name="operations", + verbose_name=_("label"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + target_type = models.CharField( + _("target type"), + max_length=10, + choices=[ + ("USER", _("User")), + ("CLUB", _("Club")), + ("ACCOUNT", _("Account")), + ("COMPANY", _("Company")), + ("OTHER", _("Other")), + ], + ) + target_id = models.IntegerField(_("target id"), null=True, blank=True) + target_label = models.CharField( + _("target label"), max_length=32, default="", blank=True + ) + linked_operation = models.OneToOneField( + "self", + related_name="operation_linked_to", + verbose_name=_("linked operation"), + null=True, + blank=True, + default=None, + ) class Meta: - unique_together = ('number', 'journal') - ordering = ['-number'] + unique_together = ("number", "journal") + ordering = ["-number"] def __getattribute__(self, attr): if attr == "target": @@ -287,14 +345,29 @@ class Operation(models.Model): if self.date is None: raise ValidationError(_("The date must be set.")) elif self.date < self.journal.start_date: - raise ValidationError(_("""The date can not be before the start date of the journal, which is -%(start_date)s.""") % {'start_date': defaultfilters.date(self.journal.start_date, settings.DATE_FORMAT)}) + raise ValidationError( + _( + """The date can not be before the start date of the journal, which is +%(start_date)s.""" + ) + % { + "start_date": defaultfilters.date( + self.journal.start_date, settings.DATE_FORMAT + ) + } + ) if self.target_type != "OTHER" and self.get_target() is None: raise ValidationError(_("Target does not exists")) if self.target_type == "OTHER" and self.target_label == "": - raise ValidationError(_("Please add a target label if you set no existing target")) + raise ValidationError( + _("Please add a target label if you set no existing target") + ) if not self.accounting_type and not self.simpleaccounting_type: - raise ValidationError(_("You need to provide ether a simplified accounting type or a standard accounting type")) + raise ValidationError( + _( + "You need to provide ether a simplified accounting type or a standard accounting type" + ) + ) if self.simpleaccounting_type: self.accounting_type = self.simpleaccounting_type.accounting_type @@ -329,7 +402,7 @@ class Operation(models.Model): if self.journal.closed: return False m = self.journal.club_account.club.get_membership_for(user) - if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -342,16 +415,19 @@ class Operation(models.Model): if self.journal.closed: return False m = self.journal.club_account.club.get_membership_for(user) - if m is not None and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:journal_details', kwargs={'j_id': self.journal.id}) + return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) def __str__(self): return "%d € | %s | %s | %s" % ( - self.amount, self.date, self.accounting_type, self.done, + self.amount, + self.date, + self.accounting_type, + self.done, ) @@ -361,18 +437,30 @@ class AccountingType(models.Model): Thoses are numbers used in accounting to classify operations """ - code = models.CharField(_('code'), max_length=16, - validators=[ - validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), - ], + + code = models.CharField( + _("code"), + max_length=16, + validators=[ + validators.RegexValidator( + r"^[0-9]*$", _("An accounting type code contains only numbers") + ) + ], + ) + label = models.CharField(_("label"), max_length=128) + movement_type = models.CharField( + _("movement type"), + choices=[ + ("CREDIT", _("Credit")), + ("DEBIT", _("Debit")), + ("NEUTRAL", _("Neutral")), + ], + max_length=12, ) - label = models.CharField(_('label'), max_length=128) - movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')), - ('NEUTRAL', _('Neutral'))], max_length=12) class Meta: verbose_name = _("accounting type") - ordering = ['movement_type', 'code'] + ordering = ["movement_type", "code"] def is_owned_by(self, user): """ @@ -383,7 +471,7 @@ class AccountingType(models.Model): return False def get_absolute_url(self): - return reverse('accounting:type_list') + return reverse("accounting:type_list") def __str__(self): return self.code + " - " + self.get_movement_type_display() + " - " + self.label @@ -393,13 +481,17 @@ class SimplifiedAccountingType(models.Model): """ Class describing the simplified accounting types. """ - label = models.CharField(_('label'), max_length=128) - accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", - verbose_name=_("simplified accounting types")) + + label = models.CharField(_("label"), max_length=128) + accounting_type = models.ForeignKey( + AccountingType, + related_name="simplified_types", + verbose_name=_("simplified accounting types"), + ) class Meta: verbose_name = _("simplified type") - ordering = ['accounting_type__movement_type', 'accounting_type__code'] + ordering = ["accounting_type__movement_type", "accounting_type__code"] @property def movement_type(self): @@ -409,25 +501,36 @@ class SimplifiedAccountingType(models.Model): return self.accounting_type.get_movement_type_display() def get_absolute_url(self): - return reverse('accounting:simple_type_list') + return reverse("accounting:simple_type_list") def __str__(self): - return self.get_movement_type_display() + " - " + self.accounting_type.code + " - " + self.label + return ( + self.get_movement_type_display() + + " - " + + self.accounting_type.code + + " - " + + self.label + ) class Label(models.Model): """Label allow a club to sort its operations""" - name = models.CharField(_('label'), max_length=64) - club_account = models.ForeignKey(ClubAccount, related_name="labels", verbose_name=_("club account")) + + name = models.CharField(_("label"), max_length=64) + club_account = models.ForeignKey( + ClubAccount, related_name="labels", verbose_name=_("club account") + ) class Meta: - unique_together = ('name', 'club_account') + unique_together = ("name", "club_account") def __str__(self): return "%s (%s)" % (self.name, self.club_account.name) def get_absolute_url(self): - return reverse('accounting:label_list', kwargs={'clubaccount_id': self.club_account.id}) + return reverse( + "accounting:label_list", kwargs={"clubaccount_id": self.club_account.id} + ) def is_owned_by(self, user): return self.club_account.is_owned_by(user) diff --git a/accounting/tests.py b/accounting/tests.py index ceb31c29..df4fd8d7 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -28,7 +28,13 @@ from django.core.management import call_command from datetime import date from core.models import User -from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType +from accounting.models import ( + GeneralJournal, + Operation, + Label, + AccountingType, + SimplifiedAccountingType, +) class RefoundAccountTest(TestCase): @@ -40,18 +46,20 @@ class RefoundAccountTest(TestCase): self.skia.customer.save() def test_permission_denied(self): - self.client.login(username='guy', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) + self.client.login(username="guy", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) response_get = self.client.get(reverse("accounting:refound_account")) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) def test_root_granteed(self): - self.client.login(username='root', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) - self.skia = User.objects.filter(username='skia').first() + self.client.login(username="root", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) + self.skia = User.objects.filter(username="skia").first() response_get = self.client.get(reverse("accounting:refound_account")) self.assertFalse(response_get.status_code == 403) self.assertTrue('