Sith/accounting/views.py

873 lines
28 KiB
Python
Raw Permalink Normal View History

#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
2024-09-23 08:25:27 +00:00
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
2024-06-24 11:07:36 +00:00
import collections
from ajax_select.fields import AutoCompleteSelectField
from django import forms
from django.conf import settings
from django.core.exceptions import PermissionDenied, ValidationError
2016-11-30 01:41:25 +00:00
from django.db import transaction
2016-12-21 03:58:52 +00:00
from django.db.models import Sum
2024-06-24 11:07:36 +00:00
from django.forms import HiddenInput
from django.forms.models import modelform_factory
2017-06-12 06:49:03 +00:00
from django.http import HttpResponse
2024-06-24 11:07:36 +00:00
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
2016-04-20 01:01:14 +00:00
2018-10-04 19:29:19 +00:00
from accounting.models import (
2024-06-24 11:07:36 +00:00
AccountingType,
2018-10-04 19:29:19 +00:00
BankAccount,
ClubAccount,
2024-06-24 11:07:36 +00:00
Company,
2018-10-04 19:29:19 +00:00
GeneralJournal,
2024-06-24 11:07:36 +00:00
Label,
2018-10-04 19:29:19 +00:00
Operation,
SimplifiedAccountingType,
)
2024-06-24 11:07:36 +00:00
from core.views import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
TabedViewMixin,
)
from core.views.forms import SelectDate, SelectFile
from counter.models import Counter, Product, Selling
2016-08-24 17:50:22 +00:00
# Main accounting view
2017-06-12 06:49:03 +00:00
2016-08-24 17:50:22 +00:00
class BankAccountListView(CanViewMixin, ListView):
2024-07-12 07:34:16 +00:00
"""A list view for the admins."""
2018-10-04 19:29:19 +00:00
2016-08-24 17:50:22 +00:00
model = BankAccount
2018-10-04 19:29:19 +00:00
template_name = "accounting/bank_account_list.jinja"
ordering = ["name"]
2016-08-24 17:50:22 +00:00
2017-06-12 06:49:03 +00:00
2016-08-24 17:50:22 +00:00
# Simplified accounting types
2018-10-04 19:29:19 +00:00
2016-08-24 17:50:22 +00:00
class SimplifiedAccountingTypeListView(CanViewMixin, ListView):
2024-07-12 07:34:16 +00:00
"""A list view for the admins."""
2018-10-04 19:29:19 +00:00
2016-08-24 17:50:22 +00:00
model = SimplifiedAccountingType
2018-10-04 19:29:19 +00:00
template_name = "accounting/simplifiedaccountingtype_list.jinja"
2016-08-24 17:50:22 +00:00
2017-06-12 06:49:03 +00:00
2016-08-24 17:50:22 +00:00
class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""An edit view for the admins."""
2018-10-04 19:29:19 +00:00
2016-08-24 17:50:22 +00:00
model = SimplifiedAccountingType
pk_url_kwarg = "type_id"
2018-10-04 19:29:19 +00:00
fields = ["label", "accounting_type"]
template_name = "core/edit.jinja"
2016-08-24 17:50:22 +00:00
2017-06-12 06:49:03 +00:00
2016-08-24 17:50:22 +00:00
class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create an accounting type (for the admins)."""
2018-10-04 19:29:19 +00:00
2016-08-24 17:50:22 +00:00
model = SimplifiedAccountingType
2018-10-04 19:29:19 +00:00
fields = ["label", "accounting_type"]
template_name = "core/create.jinja"
2016-05-03 06:50:54 +00:00
2017-06-12 06:49:03 +00:00
2016-05-03 06:50:54 +00:00
# Accounting types
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
class AccountingTypeListView(CanViewMixin, ListView):
2024-07-12 07:34:16 +00:00
"""A list view for the admins."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = AccountingType
2018-10-04 19:29:19 +00:00
template_name = "accounting/accountingtype_list.jinja"
2016-05-03 06:50:54 +00:00
2017-06-12 06:49:03 +00:00
2016-05-03 06:50:54 +00:00
class AccountingTypeEditView(CanViewMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""An edit view for the admins."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = AccountingType
pk_url_kwarg = "type_id"
2018-10-04 19:29:19 +00:00
fields = ["code", "label", "movement_type"]
template_name = "core/edit.jinja"
2016-05-03 06:50:54 +00:00
2017-06-12 06:49:03 +00:00
class AccountingTypeCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create an accounting type (for the admins)."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = AccountingType
2018-10-04 19:29:19 +00:00
fields = ["code", "label", "movement_type"]
template_name = "core/create.jinja"
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2016-04-20 01:01:14 +00:00
# BankAccount views
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
class BankAccountEditView(CanViewMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""An edit view for the admins."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = BankAccount
pk_url_kwarg = "b_account_id"
2018-10-04 19:29:19 +00:00
fields = ["name", "iban", "number", "club"]
template_name = "core/edit.jinja"
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2016-04-20 01:01:14 +00:00
class BankAccountDetailView(CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""A detail view, listing every club account."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = BankAccount
pk_url_kwarg = "b_account_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/bank_account_details.jinja"
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
class BankAccountCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create a bank account (for the admins)."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = BankAccount
2018-10-04 19:29:19 +00:00
fields = ["name", "club", "iban", "number"]
template_name = "core/create.jinja"
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2018-10-04 19:29:19 +00:00
class BankAccountDeleteView(
CanEditPropMixin, DeleteView
): # TODO change Delete to Close
2024-07-12 07:34:16 +00:00
"""Delete a bank account (for the admins)."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = BankAccount
pk_url_kwarg = "b_account_id"
2018-10-04 19:29:19 +00:00
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("accounting:bank_list")
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2016-04-20 01:01:14 +00:00
# ClubAccount views
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
class ClubAccountEditView(CanViewMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""An edit view for the admins."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = ClubAccount
pk_url_kwarg = "c_account_id"
2018-10-04 19:29:19 +00:00
fields = ["name", "club", "bank_account"]
template_name = "core/edit.jinja"
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2016-04-20 01:01:14 +00:00
class ClubAccountDetailView(CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""A detail view, listing every journal."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = ClubAccount
pk_url_kwarg = "c_account_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/club_account_details.jinja"
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
class ClubAccountCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create a club account (for the admins)."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = ClubAccount
2018-10-04 19:29:19 +00:00
fields = ["name", "club", "bank_account"]
template_name = "core/create.jinja"
2016-06-24 17:43:11 +00:00
def get_initial(self):
2024-06-27 12:46:43 +00:00
ret = super().get_initial()
if "parent" in self.request.GET:
2018-10-04 19:29:19 +00:00
obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
2016-06-24 17:43:11 +00:00
if obj is not None:
2018-10-04 19:29:19 +00:00
ret["bank_account"] = obj.id
2016-06-24 17:43:11 +00:00
return ret
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2018-10-04 19:29:19 +00:00
class ClubAccountDeleteView(
CanEditPropMixin, DeleteView
): # TODO change Delete to Close
2024-07-12 07:34:16 +00:00
"""Delete a club account (for the admins)."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = ClubAccount
pk_url_kwarg = "c_account_id"
2018-10-04 19:29:19 +00:00
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("accounting:bank_list")
2016-04-20 01:01:14 +00:00
2017-06-12 06:49:03 +00:00
2016-04-20 01:01:14 +00:00
# Journal views
2018-10-04 19:29:19 +00:00
2016-12-21 02:52:33 +00:00
class JournalTabsMixin(TabedViewMixin):
def get_tabs_title(self):
return _("Journal")
def get_list_of_tabs(self):
tab_list = []
2018-10-04 19:29:19 +00:00
tab_list.append(
{
"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",
"name": _("Statement by nature"),
}
)
tab_list.append(
{
"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",
"name": _("Accounting statement"),
}
)
2016-12-21 02:52:33 +00:00
return tab_list
2017-06-12 06:49:03 +00:00
2016-06-22 11:40:30 +00:00
class JournalCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create a general journal."""
2018-10-04 19:29:19 +00:00
2016-04-20 01:01:14 +00:00
model = GeneralJournal
2018-10-04 19:29:19 +00:00
form_class = modelform_factory(
GeneralJournal,
fields=["name", "start_date", "club_account"],
widgets={"start_date": SelectDate},
)
template_name = "core/create.jinja"
2016-06-24 17:43:11 +00:00
def get_initial(self):
2024-06-27 12:46:43 +00:00
ret = super().get_initial()
if "parent" in self.request.GET:
2018-10-04 19:29:19 +00:00
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
2016-06-24 17:43:11 +00:00
if obj is not None:
2018-10-04 19:29:19 +00:00
ret["club_account"] = obj.id
2016-06-24 17:43:11 +00:00
return ret
2016-05-03 06:50:54 +00:00
2017-06-12 06:49:03 +00:00
2016-12-21 02:52:33 +00:00
class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""A detail view, listing every operation."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = GeneralJournal
pk_url_kwarg = "j_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/journal_details.jinja"
current_tab = "journal"
2016-05-03 06:50:54 +00:00
2017-06-12 06:49:03 +00:00
2016-05-03 06:50:54 +00:00
class JournalEditView(CanEditMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""Update a general journal."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = GeneralJournal
pk_url_kwarg = "j_id"
2018-10-04 19:29:19 +00:00
fields = ["name", "start_date", "end_date", "club_account", "closed"]
template_name = "core/edit.jinja"
2016-05-03 06:50:54 +00:00
2017-06-12 06:49:03 +00:00
2017-02-05 16:26:04 +00:00
class JournalDeleteView(CanEditPropMixin, DeleteView):
2024-07-12 07:34:16 +00:00
"""Delete a club account (for the admins)."""
2018-10-04 19:29:19 +00:00
2017-02-05 16:26:04 +00:00
model = GeneralJournal
pk_url_kwarg = "j_id"
2018-10-04 19:29:19 +00:00
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("accounting:club_details")
2017-02-05 16:26:04 +00:00
2017-03-12 19:33:17 +00:00
def dispatch(self, request, *args, **kwargs):
2017-06-12 06:49:03 +00:00
self.object = self.get_object()
if self.object.operations.count() == 0:
2024-06-27 12:46:43 +00:00
return super().dispatch(request, *args, **kwargs)
2017-06-12 06:49:03 +00:00
else:
raise PermissionDenied
2016-11-07 00:51:02 +00:00
2016-05-03 06:50:54 +00:00
# Operation views
2018-10-04 19:29:19 +00:00
2016-08-24 17:50:22 +00:00
class OperationForm(forms.ModelForm):
class Meta:
model = Operation
2018-10-04 19:29:19 +00:00
fields = [
"amount",
"remark",
"journal",
"target_type",
"target_id",
"target_label",
"date",
"mode",
"cheque_number",
"invoice",
"simpleaccounting_type",
"accounting_type",
"label",
"done",
]
2016-08-24 17:50:22 +00:00
widgets = {
2018-10-04 19:29:19 +00:00
"journal": HiddenInput,
"target_id": HiddenInput,
"date": SelectDate,
"invoice": SelectFile,
2017-06-12 06:49:03 +00:00
}
2018-10-04 19:29:19 +00:00
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)
company = AutoCompleteSelectField("companies", help_text=None, required=False)
need_link = forms.BooleanField(
label=_("Link this operation to the target account"),
required=False,
initial=False,
)
2016-08-24 17:50:22 +00:00
def __init__(self, *args, **kwargs):
2018-10-04 19:29:19 +00:00
club_account = kwargs.pop("club_account", None)
2024-06-27 12:46:43 +00:00
super().__init__(*args, **kwargs)
2016-10-05 18:17:37 +00:00
if club_account:
2018-10-04 19:29:19 +00:00
self.fields["label"].queryset = club_account.labels.order_by("name").all()
2016-08-24 17:50:22 +00:00
if self.instance.target_type == "USER":
2018-10-04 19:29:19 +00:00
self.fields["user"].initial = self.instance.target_id
2016-08-24 17:50:22 +00:00
elif self.instance.target_type == "ACCOUNT":
2018-10-04 19:29:19 +00:00
self.fields["club_account"].initial = self.instance.target_id
2016-08-24 17:50:22 +00:00
elif self.instance.target_type == "CLUB":
2018-10-04 19:29:19 +00:00
self.fields["club"].initial = self.instance.target_id
2016-08-24 17:50:22 +00:00
elif self.instance.target_type == "COMPANY":
2018-10-04 19:29:19 +00:00
self.fields["company"].initial = self.instance.target_id
2016-08-24 17:50:22 +00:00
def clean(self):
2024-06-27 12:46:43 +00:00
self.cleaned_data = super().clean()
if "target_type" in self.cleaned_data:
2018-10-04 19:29:19 +00:00
if (
self.cleaned_data.get("user") is None
and self.cleaned_data.get("club") is None
and self.cleaned_data.get("club_account") is None
and self.cleaned_data.get("company") is None
and self.cleaned_data.get("target_label") == ""
2018-10-04 19:29:19 +00:00
):
self.add_error(
"target_type", ValidationError(_("The target must be set."))
)
else:
2018-10-04 19:29:19 +00:00
if self.cleaned_data["target_type"] == "USER":
self.cleaned_data["target_id"] = self.cleaned_data["user"].id
elif self.cleaned_data["target_type"] == "ACCOUNT":
self.cleaned_data["target_id"] = self.cleaned_data[
"club_account"
].id
elif self.cleaned_data["target_type"] == "CLUB":
self.cleaned_data["target_id"] = self.cleaned_data["club"].id
elif self.cleaned_data["target_type"] == "COMPANY":
self.cleaned_data["target_id"] = self.cleaned_data["company"].id
if self.cleaned_data.get("amount") is None:
2018-10-04 19:29:19 +00:00
self.add_error("amount", ValidationError(_("The amount must be set.")))
2016-08-24 17:50:22 +00:00
return self.cleaned_data
def save(self):
2024-06-27 12:46:43 +00:00
ret = super().save()
2018-10-04 19:29:19 +00:00
if (
self.instance.target_type == "ACCOUNT"
and not self.instance.linked_operation
and self.instance.target.has_open_journal()
and self.cleaned_data["need_link"]
):
2016-08-24 17:50:22 +00:00
inst = self.instance
club_account = inst.target
2018-10-04 19:29:19 +00:00
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
2016-08-24 17:50:22 +00:00
op = Operation(
2017-06-12 06:49:03 +00:00
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,
)
2016-08-24 17:50:22 +00:00
op.save()
self.instance.linked_operation = op
self.save()
return ret
2017-06-12 06:49:03 +00:00
class OperationCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create an operation."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = Operation
2016-08-24 17:50:22 +00:00
form_class = OperationForm
2018-10-04 19:29:19 +00:00
template_name = "accounting/operation_edit.jinja"
2016-06-24 17:43:11 +00:00
2016-10-05 18:17:37 +00:00
def get_form(self, form_class=None):
2018-10-04 19:29:19 +00:00
self.journal = GeneralJournal.objects.filter(id=self.kwargs["j_id"]).first()
2016-10-05 18:17:37 +00:00
ca = self.journal.club_account if self.journal else None
return self.form_class(club_account=ca, **self.get_form_kwargs())
2016-06-24 17:43:11 +00:00
def get_initial(self):
2024-06-27 12:46:43 +00:00
ret = super().get_initial()
2016-10-05 18:17:37 +00:00
if self.journal is not None:
2018-10-04 19:29:19 +00:00
ret["journal"] = self.journal.id
2016-06-24 17:43:11 +00:00
return ret
2016-05-03 06:50:54 +00:00
2016-08-24 19:49:46 +00:00
def get_context_data(self, **kwargs):
2024-07-12 07:34:16 +00:00
"""Add journal to the context."""
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2016-08-24 19:49:46 +00:00
if self.journal:
2018-10-04 19:29:19 +00:00
kwargs["object"] = self.journal
2016-08-24 19:49:46 +00:00
return kwargs
2017-06-12 06:49:03 +00:00
2016-06-24 19:55:52 +00:00
class OperationEditView(CanEditMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""An edit view, working as detail for the moment."""
2018-10-04 19:29:19 +00:00
2016-05-03 06:50:54 +00:00
model = Operation
pk_url_kwarg = "op_id"
2016-08-24 17:50:22 +00:00
form_class = OperationForm
2018-10-04 19:29:19 +00:00
template_name = "accounting/operation_edit.jinja"
2016-08-07 18:10:50 +00:00
2016-08-24 19:49:46 +00:00
def get_context_data(self, **kwargs):
2024-07-12 07:34:16 +00:00
"""Add journal to the context."""
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2018-10-04 19:29:19 +00:00
kwargs["object"] = self.object.journal
2016-08-24 19:49:46 +00:00
return kwargs
2017-06-12 06:49:03 +00:00
2016-11-07 00:51:02 +00:00
class OperationPDFView(CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""Display the PDF of a given operation."""
2016-11-07 00:51:02 +00:00
model = Operation
pk_url_kwarg = "op_id"
def get(self, request, *args, **kwargs):
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
2024-06-24 11:07:36 +00:00
from reportlab.lib.units import cm
2016-11-07 00:51:02 +00:00
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
2024-06-24 11:07:36 +00:00
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
from reportlab.platypus import Table, TableStyle
2016-11-07 00:51:02 +00:00
2018-10-04 19:29:19 +00:00
pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf"))
2016-11-07 00:51:02 +00:00
self.object = self.get_object()
amount = self.object.amount
remark = self.object.remark
nature = self.object.accounting_type.movement_type
num = self.object.number
date = self.object.date
mode = self.object.mode
club_name = self.object.journal.club_account.name
ti = self.object.journal.name
op_label = self.object.label
club_address = self.object.journal.club_account.club.address
id_op = self.object.id
if self.object.target_type == "OTHER":
target = self.object.target_label
else:
target = self.object.target.get_display_name()
2018-10-04 19:29:19 +00:00
response = HttpResponse(content_type="application/pdf")
response["Content-Disposition"] = 'filename="op-%d(%s_on_%s).pdf"' % (
num,
ti,
club_name,
)
2016-11-07 00:51:02 +00:00
p = canvas.Canvas(response)
2018-10-04 19:29:19 +00:00
p.setFont("DejaVu", 12)
2016-11-07 00:51:02 +00:00
p.setTitle("%s %d" % (_("Operation"), num))
width, height = letter
im = ImageReader("core/static/core/img/logo.jpg")
iw, ih = im.getSize()
2017-06-12 06:49:03 +00:00
p.drawImage(im, 40, height - 50, width=iw / 2, height=ih / 2)
2016-12-21 03:58:52 +00:00
2016-11-07 00:51:02 +00:00
labelStr = [["%s %s - %s %s" % (_("Journal"), ti, _("Operation"), num)]]
label = Table(labelStr, colWidths=[150], rowHeights=[20])
2016-12-21 03:58:52 +00:00
2018-10-04 19:29:19 +00:00
label.setStyle(TableStyle([("ALIGN", (0, 0), (-1, -1), "RIGHT")]))
2016-11-07 00:51:02 +00:00
w, h = label.wrapOn(label, 0, 0)
2017-06-12 06:49:03 +00:00
label.drawOn(p, width - 180, height)
2016-11-07 00:51:02 +00:00
2018-10-04 19:29:19 +00:00
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 is not None else ""},
)
2016-12-18 22:11:23 +00:00
p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date})
2016-11-07 00:51:02 +00:00
data = []
2016-12-21 03:58:52 +00:00
2018-10-04 19:29:19 +00:00
data += [
["%s" % (_("Credit").upper() if nature == "CREDIT" else _("Debit").upper())]
]
2016-11-07 00:51:02 +00:00
data += [[_("Amount: %(amount).2f") % {"amount": amount}]]
2016-12-21 03:58:52 +00:00
2016-11-07 00:51:02 +00:00
payment_mode = ""
for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD:
if m[0] == mode:
payment_mode += "[\u00d7]"
2016-11-07 00:51:02 +00:00
else:
payment_mode += "[ ]"
2017-06-12 06:49:03 +00:00
payment_mode += " %s\n" % (m[1])
2016-11-07 00:51:02 +00:00
data += [[payment_mode]]
2016-12-21 03:58:52 +00:00
2018-10-04 19:29:19 +00:00
data += [
[
"%s : %s"
% (_("Debtor") if nature == "CREDIT" else _("Creditor"), target),
"",
]
]
2016-11-07 00:51:02 +00:00
data += [["%s \n%s" % (_("Comment:"), remark)]]
2016-12-21 03:58:52 +00:00
2018-10-04 19:29:19 +00:00
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),
]
)
)
2016-11-07 00:51:02 +00:00
2016-12-18 22:11:23 +00:00
signature = []
signature += [[_("Signature:")]]
2017-06-12 06:49:03 +00:00
tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80])
2018-10-04 19:29:19 +00:00
tSig.setStyle(
TableStyle(
[
("VALIGN", (0, 0), (-1, -1), "TOP"),
("BOX", (0, 0), (-1, -1), 0.25, colors.black),
]
)
)
2016-12-18 22:11:23 +00:00
w, h = tSig.wrapOn(p, 0, 0)
tSig.drawOn(p, 90, 200)
2016-11-07 00:51:02 +00:00
w, h = t.wrapOn(p, 0, 0)
t.drawOn(p, 90, 350)
p.drawCentredString(10.5 * cm, 2 * cm, club_name)
p.drawCentredString(10.5 * cm, 1 * cm, club_address)
p.showPage()
p.save()
return response
2017-06-12 06:49:03 +00:00
2016-12-21 04:18:02 +00:00
class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""Display a statement sorted by labels."""
2018-10-04 19:29:19 +00:00
model = GeneralJournal
pk_url_kwarg = "j_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/journal_statement_nature.jinja"
current_tab = "nature_statement"
2016-12-21 03:58:52 +00:00
def statement(self, queryset, movement_type):
ret = collections.OrderedDict()
statement = collections.OrderedDict()
total_sum = 0
for sat in [
None,
*list(SimplifiedAccountingType.objects.order_by("label")),
]:
2024-06-27 12:30:58 +00:00
amount = queryset.filter(
2018-10-04 19:29:19 +00:00
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
label = sat.label if sat is not None else ""
2024-06-27 12:30:58 +00:00
if amount:
total_sum += amount
statement[label] = amount
2016-12-21 03:58:52 +00:00
ret[movement_type] = statement
2017-06-12 06:49:03 +00:00
ret[movement_type + "_sum"] = total_sum
2016-12-21 03:58:52 +00:00
return ret
2016-12-14 17:04:57 +00:00
2016-12-21 03:58:52 +00:00
def big_statement(self):
2018-10-04 19:29:19 +00:00
label_list = (
self.object.operations.order_by("label").values_list("label").distinct()
)
2016-12-21 03:58:52 +00:00
labels = Label.objects.filter(id__in=label_list).all()
statement = collections.OrderedDict()
gen_statement = collections.OrderedDict()
no_label_statement = collections.OrderedDict()
gen_statement.update(self.statement(self.object.operations.all(), "CREDIT"))
gen_statement.update(self.statement(self.object.operations.all(), "DEBIT"))
statement[_("General statement")] = gen_statement
2018-10-04 19:29:19 +00:00
no_label_statement.update(
self.statement(self.object.operations.filter(label=None).all(), "CREDIT")
)
no_label_statement.update(
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
)
2016-12-21 03:58:52 +00:00
statement[_("No label operations")] = no_label_statement
for label in labels:
2016-12-21 03:58:52 +00:00
l_stmt = collections.OrderedDict()
journals = self.object.operations.filter(label=label).all()
l_stmt.update(self.statement(journals, "CREDIT"))
l_stmt.update(self.statement(journals, "DEBIT"))
statement[label] = l_stmt
2016-12-21 03:58:52 +00:00
return statement
2016-12-14 17:04:57 +00:00
def get_context_data(self, **kwargs):
2024-07-12 07:34:16 +00:00
"""Add infos to the context."""
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2018-10-04 19:29:19 +00:00
kwargs["statement"] = self.big_statement()
return kwargs
2017-06-12 06:49:03 +00:00
2016-12-21 04:18:02 +00:00
class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""Calculate a dictionary with operation target and sum of operations."""
2018-10-04 19:29:19 +00:00
2016-12-14 17:04:57 +00:00
model = GeneralJournal
pk_url_kwarg = "j_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/journal_statement_person.jinja"
current_tab = "person_statement"
2016-12-14 17:04:57 +00:00
def sum_by_target(self, target_id, target_type, movement_type):
2018-10-04 19:29:19 +00:00
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"]
2016-12-14 17:04:57 +00:00
def statement(self, movement_type):
2016-12-21 12:09:40 +00:00
statement = collections.OrderedDict()
2018-10-04 19:29:19 +00:00
for op in (
self.object.operations.filter(accounting_type__movement_type=movement_type)
.order_by("target_type", "target_id")
.distinct()
):
statement[op.target] = self.sum_by_target(
op.target_id, op.target_type, movement_type
)
2016-12-21 03:58:52 +00:00
return statement
2016-12-14 17:04:57 +00:00
def total(self, movement_type):
return sum(self.statement(movement_type).values())
2016-12-14 17:04:57 +00:00
def get_context_data(self, **kwargs):
2024-07-12 07:34:16 +00:00
"""Add journal to the context."""
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2018-10-04 19:29:19 +00:00
kwargs["credit_statement"] = self.statement("CREDIT")
kwargs["debit_statement"] = self.statement("DEBIT")
kwargs["total_credit"] = self.total("CREDIT")
kwargs["total_debit"] = self.total("DEBIT")
2016-12-14 17:04:57 +00:00
return kwargs
2017-06-12 06:49:03 +00:00
2016-12-21 04:18:02 +00:00
class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView):
2024-07-12 07:34:16 +00:00
"""Calculate a dictionary with operation type and sum of operations."""
2018-10-04 19:29:19 +00:00
2016-12-14 17:04:57 +00:00
model = GeneralJournal
pk_url_kwarg = "j_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/journal_statement_accounting.jinja"
2016-12-21 04:18:02 +00:00
current_tab = "accounting_statement"
2016-12-14 17:04:57 +00:00
2016-12-21 03:58:52 +00:00
def statement(self):
statement = collections.OrderedDict()
2018-10-04 19:29:19 +00:00
for at in AccountingType.objects.order_by("code").all():
2016-12-21 03:58:52 +00:00
sum_by_type = self.object.operations.filter(
2018-10-04 19:29:19 +00:00
accounting_type__code__startswith=at.code
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
2016-12-21 12:09:40 +00:00
if sum_by_type:
2016-12-21 03:58:52 +00:00
statement[at] = sum_by_type
return statement
2016-12-14 17:04:57 +00:00
def get_context_data(self, **kwargs):
2024-07-12 07:34:16 +00:00
"""Add journal to the context."""
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2018-10-04 19:29:19 +00:00
kwargs["statement"] = self.statement()
2016-12-14 17:04:57 +00:00
return kwargs
2018-10-04 19:29:19 +00:00
2016-08-07 18:10:50 +00:00
# Company views
2017-06-12 06:49:03 +00:00
2016-12-19 20:58:40 +00:00
class CompanyListView(CanViewMixin, ListView):
model = Company
2018-10-04 19:29:19 +00:00
template_name = "accounting/co_list.jinja"
2016-12-19 20:58:40 +00:00
2017-06-12 06:49:03 +00:00
2016-08-07 18:10:50 +00:00
class CompanyCreateView(CanCreateMixin, CreateView):
2024-07-12 07:34:16 +00:00
"""Create a company."""
2018-10-04 19:29:19 +00:00
2016-08-07 18:10:50 +00:00
model = Company
2018-10-04 19:29:19 +00:00
fields = ["name"]
template_name = "core/create.jinja"
success_url = reverse_lazy("accounting:co_list")
2016-12-19 20:58:40 +00:00
2016-08-07 18:10:50 +00:00
class CompanyEditView(CanCreateMixin, UpdateView):
2024-07-12 07:34:16 +00:00
"""Edit a company."""
2018-10-04 19:29:19 +00:00
2016-08-07 18:10:50 +00:00
model = Company
pk_url_kwarg = "co_id"
2018-10-04 19:29:19 +00:00
fields = ["name"]
template_name = "core/edit.jinja"
success_url = reverse_lazy("accounting:co_list")
2016-01-28 15:53:37 +00:00
2017-06-12 06:49:03 +00:00
2016-10-05 13:54:00 +00:00
# Label views
2018-10-04 19:29:19 +00:00
2016-10-05 13:54:00 +00:00
class LabelListView(CanViewMixin, DetailView):
model = ClubAccount
pk_url_kwarg = "clubaccount_id"
2018-10-04 19:29:19 +00:00
template_name = "accounting/label_list.jinja"
2016-10-05 13:54:00 +00:00
2017-06-12 06:49:03 +00:00
2018-10-04 19:29:19 +00:00
class LabelCreateView(
CanCreateMixin, CreateView
): # FIXME we need to check the rights before creating the object
2016-10-05 13:54:00 +00:00
model = Label
2018-10-04 19:29:19 +00:00
form_class = modelform_factory(
Label, fields=["name", "club_account"], widgets={"club_account": HiddenInput}
)
template_name = "core/create.jinja"
2016-10-05 13:54:00 +00:00
def get_initial(self):
2024-06-27 12:46:43 +00:00
ret = super().get_initial()
if "parent" in self.request.GET:
2018-10-04 19:29:19 +00:00
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
2016-10-05 13:54:00 +00:00
if obj is not None:
2018-10-04 19:29:19 +00:00
ret["club_account"] = obj.id
2016-10-05 13:54:00 +00:00
return ret
2017-06-12 06:49:03 +00:00
2016-10-05 13:54:00 +00:00
class LabelEditView(CanEditMixin, UpdateView):
model = Label
pk_url_kwarg = "label_id"
2018-10-04 19:29:19 +00:00
fields = ["name"]
template_name = "core/edit.jinja"
2016-10-05 13:54:00 +00:00
2017-06-12 06:49:03 +00:00
2016-10-05 13:54:00 +00:00
class LabelDeleteView(CanEditMixin, DeleteView):
model = Label
pk_url_kwarg = "label_id"
2018-10-04 19:29:19 +00:00
template_name = "core/delete_confirm.jinja"
2016-10-05 13:54:00 +00:00
def get_success_url(self):
return self.object.get_absolute_url()
2016-11-30 01:41:25 +00:00
2017-06-12 06:49:03 +00:00
2016-11-30 01:41:25 +00:00
class CloseCustomerAccountForm(forms.Form):
2018-10-04 19:29:19 +00:00
user = AutoCompleteSelectField(
"users", label=_("Refound this account"), help_text=None, required=True
)
2016-11-30 01:41:25 +00:00
2017-06-12 06:49:03 +00:00
2016-11-30 01:41:25 +00:00
class RefoundAccountView(FormView):
2024-07-12 07:34:16 +00:00
"""Create a selling with the same amount than the current user money."""
2018-10-04 19:29:19 +00:00
2016-11-30 01:41:25 +00:00
template_name = "accounting/refound_account.jinja"
form_class = CloseCustomerAccountForm
def permission(self, user):
if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
2016-11-30 01:41:25 +00:00
return True
else:
raise PermissionDenied
def dispatch(self, request, *arg, **kwargs):
2024-06-27 12:46:43 +00:00
res = super().dispatch(request, *arg, **kwargs)
2016-11-30 01:41:25 +00:00
if self.permission(request.user):
return res
def post(self, request, *arg, **kwargs):
self.operator = request.user
if self.permission(request.user):
2024-06-27 12:46:43 +00:00
return super().post(self, request, *arg, **kwargs)
2016-11-30 01:41:25 +00:00
def form_valid(self, form):
2018-10-04 19:29:19 +00:00
self.customer = form.cleaned_data["user"]
2016-11-30 01:41:25 +00:00
self.create_selling()
2024-06-27 12:46:43 +00:00
return super().form_valid(form)
2016-11-30 01:41:25 +00:00
def get_success_url(self):
2018-10-04 19:29:19 +00:00
return reverse("accounting:refound_account")
2016-11-30 01:41:25 +00:00
def create_selling(self):
with transaction.atomic():
uprice = self.customer.customer.amount
2018-10-04 19:29:19 +00:00
refound_club_counter = Counter.objects.get(
id=settings.SITH_COUNTER_REFOUND_ID
)
2016-12-15 11:17:19 +00:00
refound_club = refound_club_counter.club
2018-10-04 19:29:19 +00:00
s = Selling(
label=_("Refound account"),
unit_price=uprice,
quantity=1,
seller=self.operator,
customer=self.customer.customer,
club=refound_club,
counter=refound_club_counter,
product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID),
)
2016-11-30 01:41:25 +00:00
s.save()