Format accounting

This commit is contained in:
Pierre Brunet 2017-06-12 08:49:03 +02:00
parent 38026025af
commit 544ff630a5
5 changed files with 236 additions and 205 deletions

View File

@ -35,5 +35,3 @@ admin.site.register(SimplifiedAccountingType)
admin.site.register(Operation)
admin.site.register(Label)
admin.site.register(Company)

View File

@ -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)

View File

@ -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('<td>M\xc3\xa9thode de paiement</td>' 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)
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)
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()
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,
})
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('<td>Le fantome de la nuit</td>' 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()
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,
})
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()
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,
})
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,
})
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,7 +215,6 @@ class OperationTest(TestCase):
response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id]))
self.assertTrue("S&#39; Kia</a></td>\\n \\n <td>3.00</td>" 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]))

View File

@ -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'),
]

View File

@ -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