From 544ff630a5b00d627abbee1a05d46c6bc9218d55 Mon Sep 17 00:00:00 2001 From: Krophil Date: Mon, 12 Jun 2017 08:49:03 +0200 Subject: [PATCH] Format accounting --- accounting/admin.py | 2 - accounting/models.py | 44 +++++---- accounting/tests.py | 178 ++++++++++++++++++------------------ accounting/urls.py | 4 +- accounting/views.py | 213 +++++++++++++++++++++++++------------------ 5 files changed, 236 insertions(+), 205 deletions(-) diff --git a/accounting/admin.py b/accounting/admin.py index 67409ea7..93960568 100644 --- a/accounting/admin.py +++ b/accounting/admin.py @@ -35,5 +35,3 @@ admin.site.register(SimplifiedAccountingType) admin.site.register(Operation) admin.site.register(Label) admin.site.register(Company) - - diff --git a/accounting/models.py b/accounting/models.py index c6b65551..97258270 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -25,7 +25,6 @@ from django.core.urlresolvers import reverse from django.core.exceptions import ValidationError from django.core import validators -from django.db.models import Count from django.db import models from django.conf import settings from django.utils.translation import ugettext_lazy as _ @@ -37,6 +36,7 @@ from decimal import Decimal from core.models import User, SithFile from club.models import Club + class CurrencyField(models.DecimalField): """ This is a custom database field used for currency @@ -50,12 +50,13 @@ class CurrencyField(models.DecimalField): def to_python(self, value): try: - return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01")) + return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01")) except AttributeError: - return None + return None # Accounting classes + class Company(models.Model): name = models.CharField(_('name'), max_length=60) street = models.CharField(_('street'), max_length=60, blank=True) @@ -104,6 +105,7 @@ class Company(models.Model): def __str__(self): return self.name + class BankAccount(models.Model): name = models.CharField(_('name'), max_length=30) iban = models.CharField(_('iban'), max_length=255, blank=True) @@ -131,6 +133,7 @@ class BankAccount(models.Model): def __str__(self): return self.name + class ClubAccount(models.Model): name = models.CharField(_('name'), max_length=30) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) @@ -244,6 +247,7 @@ class GeneralJournal(models.Model): self.amount -= o.amount self.save() + class Operation(models.Model): """ An operation is a line in the journal, a debit or a credit @@ -258,17 +262,17 @@ class Operation(models.Model): 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) + verbose_name=_("simple type"), null=True, blank=True) accounting_type = models.ForeignKey('AccountingType', related_name="operations", - verbose_name=_("accounting type"), null=True, blank=True) + 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) + 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'))]) + 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) + null=True, blank=True, default=None) class Meta: unique_together = ('number', 'journal') @@ -349,8 +353,9 @@ class Operation(models.Model): 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, + ) + class AccountingType(models.Model): """ @@ -359,13 +364,13 @@ 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')), - ], - ) + 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) + ('NEUTRAL', _('Neutral'))], max_length=12) class Meta: verbose_name = _("accounting type") @@ -383,7 +388,8 @@ class AccountingType(models.Model): return reverse('accounting:type_list') def __str__(self): - return self.code+" - "+self.get_movement_type_display()+" - "+self.label + return self.code + " - " + self.get_movement_type_display() + " - " + self.label + class SimplifiedAccountingType(models.Model): """ @@ -391,7 +397,7 @@ class SimplifiedAccountingType(models.Model): """ label = models.CharField(_('label'), max_length=128) accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", - verbose_name=_("simplified accounting types")) + verbose_name=_("simplified accounting types")) class Meta: verbose_name = _("simplified type") @@ -408,7 +414,8 @@ class SimplifiedAccountingType(models.Model): 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""" @@ -432,4 +439,3 @@ class Label(models.Model): def can_be_viewed_by(self, user): return self.club_account.can_be_viewed_by(user) - diff --git a/accounting/tests.py b/accounting/tests.py index d54efe34..a2c3ab38 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -22,15 +22,12 @@ # # -from django.test import Client, TestCase +from django.test import TestCase from django.core.urlresolvers import reverse -from django.contrib.auth.models import Group from django.core.management import call_command -from django.conf import settings -from datetime import date, datetime +from datetime import date from core.models import User -from counter.models import Counter from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType @@ -72,10 +69,11 @@ class RefoundAccountTest(TestCase): self.assertFalse(response_post.status_code == 403) self.assertTrue(self.skia.customer.amount == 0) + class JournalTest(TestCase): def setUp(self): call_command("populate") - self.journal = GeneralJournal.objects.filter(id = 1).first() + self.journal = GeneralJournal.objects.filter(id=1).first() def test_permission_granted(self): self.client.login(username='comptable', password='plop') @@ -91,115 +89,116 @@ class JournalTest(TestCase): self.assertTrue(response_get.status_code == 403) self.assertFalse('M\xc3\xa9thode de paiement' in str(response_get.content)) + class OperationTest(TestCase): def setUp(self): call_command("populate") - self.journal = GeneralJournal.objects.filter(id = 1).first() + self.journal = GeneralJournal.objects.filter(id=1).first() self.skia = User.objects.filter(username='skia').first() at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT') at.save() - l = Label(club_account= self.journal.club_account, name='bob') + l = Label(club_account=self.journal.club_account, name='bob') l.save() self.client.login(username='comptable', password='plop') - self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, + remark="Test bilan", mode='CASH', done=True, label=l, + accounting_type=at, target_type='USER', target_id=self.skia.id) self.op1.save() - self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, + remark="Test bilan", mode='CASH', done=True, label=l, + accounting_type=at, target_type='USER', target_id=self.skia.id) self.op2.save() def test_new_operation(self): - self.client.login(username='comptable', password='plop') - at = AccountingType.objects.filter(code = '604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome de la nuit", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : '', - 'accounting_type': at.id, - 'label' : '', - 'done' : False, - }) + self.client.login(username='comptable', password='plop') + at = AccountingType.objects.filter(code='604').first() + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 30, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome de la nuit", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': '', + 'accounting_type': at.id, + 'label': '', + 'done': False, + }) self.assertFalse(response.status_code == 403) - self.assertTrue(self.journal.operations.filter(target_label = "Le fantome de la nuit").exists()) + self.assertTrue(self.journal.operations.filter(target_label="Le fantome de la nuit").exists()) response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) self.assertTrue('Le fantome de la nuit' in str(response_get.content)) def test_bad_new_operation(self): self.client.login(username='comptable', password='plop') - at = AccountingType.objects.filter(code = '604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome de la nuit", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : '', - 'accounting_type': '', - 'label' : '', - 'done' : False, - }) + AccountingType.objects.filter(code='604').first() + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 30, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome de la nuit", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': '', + 'accounting_type': '', + 'label': '', + 'done': False, + }) self.assertTrue('Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard' in str(response.content)) def test_new_operation_not_authorized(self): self.client.login(username='skia', password='plop') - at = AccountingType.objects.filter(code = '604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome du jour", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : '', - 'accounting_type': at.id, - 'label' : '', - 'done' : False, - }) + at = AccountingType.objects.filter(code='604').first() + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 30, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome du jour", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': '', + 'accounting_type': at.id, + 'label': '', + 'done': False, + }) self.assertTrue(response.status_code == 403) - self.assertFalse(self.journal.operations.filter(target_label = "Le fantome du jour").exists()) + self.assertFalse(self.journal.operations.filter(target_label="Le fantome du jour").exists()) def test__operation_simple_accounting(self): self.client.login(username='comptable', password='plop') sat = SimplifiedAccountingType.objects.all().first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 23, - 'remark' : "Un gros test", - 'journal' : self.journal.id, - 'target_type' : 'OTHER', - 'target_id' : '', - 'target_label' : "Le fantome de l'aurore", - 'date' : '04/12/2020', - 'mode' : 'CASH', - 'cheque_number' : '', - 'invoice' : '', - 'simpleaccounting_type' : sat.id, - 'accounting_type': '', - 'label' : '', - 'done' : False, - }) + response = self.client.post(reverse('accounting:op_new', + args=[self.journal.id]), + {'amount': 23, + 'remark': "Un gros test", + 'journal': self.journal.id, + 'target_type': 'OTHER', + 'target_id': '', + 'target_label': "Le fantome de l'aurore", + 'date': '04/12/2020', + 'mode': 'CASH', + 'cheque_number': '', + 'invoice': '', + 'simpleaccounting_type': sat.id, + 'accounting_type': '', + 'label': '', + 'done': False, + }) self.assertFalse(response.status_code == 403) self.assertTrue(self.journal.operations.filter(amount=23).exists()) response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) @@ -216,8 +215,7 @@ class OperationTest(TestCase): response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id])) self.assertTrue("S' Kia\\n \\n 3.00" in str(response_get.content)) - def test_accounting_statement(self): self.client.login(username='comptable', password='plop') response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id])) - self.assertTrue("443 - Cr\\xc3\\xa9dit - Ce code n'existe pas\\n 3.00" in str(response_get.content)) \ No newline at end of file + self.assertTrue("443 - Cr\\xc3\\xa9dit - Ce code n'existe pas\\n 3.00" in str(response_get.content)) diff --git a/accounting/urls.py b/accounting/urls.py index 7085fc30..70e6a3ea 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -22,7 +22,7 @@ # # -from django.conf.urls import url, include +from django.conf.urls import url from accounting.views import * @@ -71,5 +71,3 @@ urlpatterns = [ # User account url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'), ] - - diff --git a/accounting/views.py b/accounting/views.py index b15ea4e5..9ce9af47 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -22,25 +22,21 @@ # # -from django.views.generic import ListView, DetailView, RedirectView +from django.views.generic import ListView, DetailView from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView -from django.shortcuts import render from django.core.urlresolvers import reverse_lazy, reverse from django.utils.translation import ugettext_lazy as _ from django.forms.models import modelform_factory from django.core.exceptions import PermissionDenied, ValidationError -from django.forms import HiddenInput, TextInput +from django.forms import HiddenInput from django.db import transaction from django.db.models import Sum from django.conf import settings from django import forms -from django.http import HttpResponseRedirect, HttpResponse -from django.utils.translation import ugettext as _ -from django.conf import settings - +from django.http import HttpResponse import collections -from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField +from ajax_select.fields import AutoCompleteSelectField from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin from core.views.forms import SelectFile, SelectDate @@ -49,6 +45,7 @@ from counter.models import Counter, Selling, Product # Main accounting view + class BankAccountListView(CanViewMixin, ListView): """ A list view for the admins @@ -57,6 +54,7 @@ class BankAccountListView(CanViewMixin, ListView): template_name = 'accounting/bank_account_list.jinja' ordering = ['name'] + # Simplified accounting types class SimplifiedAccountingTypeListView(CanViewMixin, ListView): @@ -66,6 +64,7 @@ class SimplifiedAccountingTypeListView(CanViewMixin, ListView): model = SimplifiedAccountingType template_name = 'accounting/simplifiedaccountingtype_list.jinja' + class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins @@ -75,6 +74,7 @@ class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): fields = ['label', 'accounting_type'] template_name = 'core/edit.jinja' + class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) @@ -83,6 +83,7 @@ class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): fields = ['label', 'accounting_type'] template_name = 'core/create.jinja' + # Accounting types class AccountingTypeListView(CanViewMixin, ListView): @@ -92,6 +93,7 @@ class AccountingTypeListView(CanViewMixin, ListView): model = AccountingType template_name = 'accounting/accountingtype_list.jinja' + class AccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins @@ -101,6 +103,7 @@ class AccountingTypeEditView(CanViewMixin, UpdateView): fields = ['code', 'label', 'movement_type'] template_name = 'core/edit.jinja' + class AccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) @@ -109,6 +112,7 @@ class AccountingTypeCreateView(CanCreateMixin, CreateView): fields = ['code', 'label', 'movement_type'] template_name = 'core/create.jinja' + # BankAccount views class BankAccountEditView(CanViewMixin, UpdateView): @@ -120,6 +124,7 @@ class BankAccountEditView(CanViewMixin, UpdateView): fields = ['name', 'iban', 'number', 'club'] template_name = 'core/edit.jinja' + class BankAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every club account @@ -128,6 +133,7 @@ class BankAccountDetailView(CanViewMixin, DetailView): pk_url_kwarg = "b_account_id" template_name = 'accounting/bank_account_details.jinja' + class BankAccountCreateView(CanCreateMixin, CreateView): """ Create a bank account (for the admins) @@ -136,7 +142,8 @@ class BankAccountCreateView(CanCreateMixin, CreateView): fields = ['name', 'club', 'iban', 'number'] template_name = 'core/create.jinja' -class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close + +class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close """ Delete a bank account (for the admins) """ @@ -145,6 +152,7 @@ class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete template_name = 'core/delete_confirm.jinja' success_url = reverse_lazy('accounting:bank_list') + # ClubAccount views class ClubAccountEditView(CanViewMixin, UpdateView): @@ -156,6 +164,7 @@ class ClubAccountEditView(CanViewMixin, UpdateView): fields = ['name', 'club', 'bank_account'] template_name = 'core/edit.jinja' + class ClubAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every journal @@ -164,6 +173,7 @@ class ClubAccountDetailView(CanViewMixin, DetailView): pk_url_kwarg = "c_account_id" template_name = 'accounting/club_account_details.jinja' + class ClubAccountCreateView(CanCreateMixin, CreateView): """ Create a club account (for the admins) @@ -180,7 +190,8 @@ class ClubAccountCreateView(CanCreateMixin, CreateView): ret['bank_account'] = obj.id return ret -class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close + +class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close """ Delete a club account (for the admins) """ @@ -189,6 +200,7 @@ class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete template_name = 'core/delete_confirm.jinja' success_url = reverse_lazy('accounting:bank_list') + # Journal views class JournalTabsMixin(TabedViewMixin): @@ -198,34 +210,35 @@ class JournalTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] tab_list.append({ - 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), - 'slug': 'journal', + 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), + 'slug': 'journal', 'name': _("Journal"), - }) + }) tab_list.append({ - 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), - 'slug': 'nature_statement', + 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), + 'slug': 'nature_statement', 'name': _("Statement by nature"), - }) + }) tab_list.append({ - 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), - 'slug': 'person_statement', + 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), + 'slug': 'person_statement', 'name': _("Statement by person"), - }) + }) tab_list.append({ - 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), - 'slug': 'accounting_statement', + 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), + 'slug': 'accounting_statement', 'name': _("Accounting statement"), - }) + }) return tab_list + class JournalCreateView(CanCreateMixin, CreateView): """ Create a general journal """ model = GeneralJournal form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'], - widgets={ 'start_date': SelectDate, }) + widgets={'start_date': SelectDate, }) template_name = 'core/create.jinja' def get_initial(self): @@ -236,6 +249,7 @@ class JournalCreateView(CanCreateMixin, CreateView): ret['club_account'] = obj.id return ret + class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): """ A detail view, listing every operation @@ -245,6 +259,7 @@ class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): template_name = 'accounting/journal_details.jinja' current_tab = 'journal' + class JournalEditView(CanEditMixin, UpdateView): """ Update a general journal @@ -254,6 +269,7 @@ class JournalEditView(CanEditMixin, UpdateView): fields = ['name', 'start_date', 'end_date', 'club_account', 'closed'] template_name = 'core/edit.jinja' + class JournalDeleteView(CanEditPropMixin, DeleteView): """ Delete a club account (for the admins) @@ -264,11 +280,12 @@ class JournalDeleteView(CanEditPropMixin, DeleteView): success_url = reverse_lazy('accounting:club_details') def dispatch(self, request, *args, **kwargs): - self.object = self.get_object() - if self.object.operations.count() == 0: - return super(JournalDeleteView, self).dispatch(request, *args, **kwargs) - else: - raise PermissionDenied + self.object = self.get_object() + if self.object.operations.count() == 0: + return super(JournalDeleteView, self).dispatch(request, *args, **kwargs) + else: + raise PermissionDenied + # Operation views @@ -276,13 +293,13 @@ class OperationForm(forms.ModelForm): class Meta: model = Operation fields = ['amount', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode', - 'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done' ] + 'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done'] widgets = { - 'journal': HiddenInput, - 'target_id': HiddenInput, - 'date': SelectDate, - 'invoice': SelectFile, - } + 'journal': HiddenInput, + 'target_id': HiddenInput, + 'date': SelectDate, + 'invoice': SelectFile, + } user = AutoCompleteSelectField('users', help_text=None, required=False) club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False) club = AutoCompleteSelectField('clubs', help_text=None, required=False) @@ -328,28 +345,29 @@ class OperationForm(forms.ModelForm): inst = self.instance club_account = inst.target acc_type = AccountingType.objects.exclude(movement_type="NEUTRAL").exclude( - movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type + movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type op = Operation( - journal=club_account.get_open_journal(), - amount=inst.amount, - date=inst.date, - remark=inst.remark, - mode=inst.mode, - cheque_number=inst.cheque_number, - invoice=inst.invoice, - done=False, # Has to be checked by hand - simpleaccounting_type=None, - accounting_type=acc_type, - target_type="ACCOUNT", - target_id=inst.journal.club_account.id, - target_label="", - linked_operation=inst, - ) + journal=club_account.get_open_journal(), + amount=inst.amount, + date=inst.date, + remark=inst.remark, + mode=inst.mode, + cheque_number=inst.cheque_number, + invoice=inst.invoice, + done=False, # Has to be checked by hand + simpleaccounting_type=None, + accounting_type=acc_type, + target_type="ACCOUNT", + target_id=inst.journal.club_account.id, + target_label="", + linked_operation=inst, + ) op.save() self.instance.linked_operation = op self.save() return ret + class OperationCreateView(CanCreateMixin, CreateView): """ Create an operation @@ -376,6 +394,7 @@ class OperationCreateView(CanCreateMixin, CreateView): kwargs['object'] = self.journal return kwargs + class OperationEditView(CanEditMixin, UpdateView): """ An edit view, working as detail for the moment @@ -391,6 +410,7 @@ class OperationEditView(CanEditMixin, UpdateView): kwargs['object'] = self.object.journal return kwargs + class OperationPDFView(CanViewMixin, DetailView): """ Display the PDF of a given operation @@ -402,11 +422,10 @@ class OperationPDFView(CanViewMixin, DetailView): def get(self, request, *args, **kwargs): from reportlab.pdfgen import canvas from reportlab.lib.units import cm - from reportlab.platypus import SimpleDocTemplate, Table, TableStyle + from reportlab.platypus import Table, TableStyle from reportlab.lib import colors from reportlab.lib.pagesizes import letter from reportlab.lib.utils import ImageReader - from reportlab.graphics.shapes import Drawing from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase import pdfmetrics @@ -419,7 +438,6 @@ class OperationPDFView(CanViewMixin, DetailView): num = self.object.number date = self.object.date mode = self.object.mode - cheque_number = self.object.cheque_number club_name = self.object.journal.club_account.name ti = self.object.journal.name op_label = self.object.label @@ -432,7 +450,7 @@ class OperationPDFView(CanViewMixin, DetailView): target = self.object.target.get_display_name() response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' %(num, ti, club_name) + response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' % (num, ti, club_name) p = canvas.Canvas(response) p.setFont('DejaVu', 12) @@ -441,21 +459,21 @@ class OperationPDFView(CanViewMixin, DetailView): width, height = letter im = ImageReader("core/static/core/img/logo.jpg") iw, ih = im.getSize() - p.drawImage(im, 40, height - 50, width=iw/2, height=ih/2) + p.drawImage(im, 40, height - 50, width=iw / 2, height=ih / 2) labelStr = [["%s %s - %s %s" % (_("Journal"), ti, _("Operation"), num)]] label = Table(labelStr, colWidths=[150], rowHeights=[20]) label.setStyle(TableStyle([ - ('ALIGN',(0,0),(-1,-1),'RIGHT'), - ])) + ('ALIGN', (0, 0), (-1, -1), 'RIGHT'), + ])) w, h = label.wrapOn(label, 0, 0) - label.drawOn(p, width-180, height) + label.drawOn(p, width - 180, height) - p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) #Justificatif du libellé + p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) # Justificatif du libellé p.drawString(90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name})) - p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label != None else ""}) + p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label is not None else ""}) p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date}) data = [] @@ -470,7 +488,7 @@ class OperationPDFView(CanViewMixin, DetailView): payment_mode += "[\u00D7]" else: payment_mode += "[ ]" - payment_mode += " %s\n" %(m[1]) + payment_mode += " %s\n" % (m[1]) data += [[payment_mode]] @@ -478,29 +496,29 @@ class OperationPDFView(CanViewMixin, DetailView): data += [["%s \n%s" % (_("Comment:"), remark)]] - t = Table(data, colWidths=[(width-90*2)/2]*2, rowHeights=[20, 20, 70, 20, 80]) + t = Table(data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80]) t.setStyle(TableStyle([ - ('ALIGN',(0,0),(-1,-1),'CENTER'), - ('VALIGN',(-2,-1),(-1,-1),'TOP'), - ('VALIGN',(0,0),(-1,-2),'MIDDLE'), - ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), - ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT - ('SPAN', (0, 1), (1, 1)), # line amount - ('SPAN',(-2, -1), (-1,-1)), # line comment - ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor - ('SPAN', (0, 2), (1, 2)), # line payment_mode - ('ALIGN',(0, 2), (1, 2),'LEFT'), # line payment_mode - ('ALIGN', (-2, -1), (-1, -1), 'LEFT'), - ('BOX', (0,0), (-1,-1), 0.25, colors.black), - ])) + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('VALIGN', (-2, -1), (-1, -1), 'TOP'), + ('VALIGN', (0, 0), (-1, -2), 'MIDDLE'), + ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black), + ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT + ('SPAN', (0, 1), (1, 1)), # line amount + ('SPAN', (-2, -1), (-1, -1)), # line comment + ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor + ('SPAN', (0, 2), (1, 2)), # line payment_mode + ('ALIGN', (0, 2), (1, 2), 'LEFT'), # line payment_mode + ('ALIGN', (-2, -1), (-1, -1), 'LEFT'), + ('BOX', (0, 0), (-1, -1), 0.25, colors.black), + ])) signature = [] signature += [[_("Signature:")]] - tSig = Table(signature, colWidths=[(width-90*2)], rowHeights=[80]) + tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80]) tSig.setStyle(TableStyle([ - ('VALIGN',(0,0),(-1,-1),'TOP'), - ('BOX', (0,0), (-1,-1), 0.25, colors.black)])) + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('BOX', (0, 0), (-1, -1), 0.25, colors.black)])) w, h = tSig.wrapOn(p, 0, 0) tSig.drawOn(p, 90, 200) @@ -516,14 +534,15 @@ class OperationPDFView(CanViewMixin, DetailView): p.save() return response + class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Display a statement sorted by labels """ model = GeneralJournal pk_url_kwarg = "j_id" - template_name='accounting/journal_statement_nature.jinja' - current_tab='nature_statement' + template_name = 'accounting/journal_statement_nature.jinja' + current_tab = 'nature_statement' def statement(self, queryset, movement_type): ret = collections.OrderedDict() @@ -531,14 +550,16 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): total_sum = 0 for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()): sum = queryset.filter(accounting_type__movement_type=movement_type, - simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] - if sat: sat = sat.label - else: sat = "" + simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] + if sat: + sat = sat.label + else: + sat = "" if sum: total_sum += sum statement[sat] = sum ret[movement_type] = statement - ret[movement_type+"_sum"] = total_sum + ret[movement_type + "_sum"] = total_sum return ret def big_statement(self): @@ -566,23 +587,24 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): kwargs['statement'] = self.big_statement() return kwargs + class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Calculate a dictionary with operation target and sum of operations """ model = GeneralJournal pk_url_kwarg = "j_id" - template_name='accounting/journal_statement_person.jinja' - current_tab='person_statement' + template_name = 'accounting/journal_statement_person.jinja' + current_tab = 'person_statement' def sum_by_target(self, target_id, target_type, movement_type): return self.object.operations.filter(accounting_type__movement_type=movement_type, - target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] + target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] def statement(self, movement_type): statement = collections.OrderedDict() for op in self.object.operations.filter(accounting_type__movement_type=movement_type).order_by('target_type', - 'target_id').distinct(): + 'target_id').distinct(): statement[op.target] = self.sum_by_target(op.target_id, op.target_type, movement_type) return statement @@ -598,13 +620,14 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): kwargs['total_debit'] = self.total("DEBIT") return kwargs + class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Calculate a dictionary with operation type and sum of operations """ model = GeneralJournal pk_url_kwarg = "j_id" - template_name='accounting/journal_statement_accounting.jinja' + template_name = 'accounting/journal_statement_accounting.jinja' current_tab = "accounting_statement" def statement(self): @@ -624,10 +647,12 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) # Company views + class CompanyListView(CanViewMixin, ListView): model = Company template_name = 'accounting/co_list.jinja' + class CompanyCreateView(CanCreateMixin, CreateView): """ Create a company @@ -648,6 +673,7 @@ class CompanyEditView(CanCreateMixin, UpdateView): template_name = 'core/edit.jinja' success_url = reverse_lazy('accounting:co_list') + # Label views class LabelListView(CanViewMixin, DetailView): @@ -655,11 +681,12 @@ class LabelListView(CanViewMixin, DetailView): pk_url_kwarg = "clubaccount_id" template_name = 'accounting/label_list.jinja' -class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object + +class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object model = Label form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={ 'club_account': HiddenInput, - }) + }) template_name = 'core/create.jinja' def get_initial(self): @@ -670,12 +697,14 @@ class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the ret['club_account'] = obj.id return ret + class LabelEditView(CanEditMixin, UpdateView): model = Label pk_url_kwarg = "label_id" fields = ['name'] template_name = 'core/edit.jinja' + class LabelDeleteView(CanEditMixin, DeleteView): model = Label pk_url_kwarg = "label_id" @@ -684,9 +713,11 @@ class LabelDeleteView(CanEditMixin, DeleteView): def get_success_url(self): return self.object.get_absolute_url() + class CloseCustomerAccountForm(forms.Form): user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True) + class RefoundAccountView(FormView): """ Create a selling with the same amount than the current user money