diff --git a/accounting/admin.py b/accounting/admin.py deleted file mode 100644 index c3386eb8..00000000 --- a/accounting/admin.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# 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) -# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE -# OR WITHIN THE LOCAL FILE "LICENSE" -# -# - -from django.contrib import admin - -from accounting.models import ( - AccountingType, - BankAccount, - ClubAccount, - Company, - GeneralJournal, - Label, - Operation, - SimplifiedAccountingType, -) - -admin.site.register(BankAccount) -admin.site.register(ClubAccount) -admin.site.register(GeneralJournal) -admin.site.register(AccountingType) -admin.site.register(SimplifiedAccountingType) -admin.site.register(Operation) -admin.site.register(Label) -admin.site.register(Company) diff --git a/accounting/api.py b/accounting/api.py deleted file mode 100644 index 5ba6c12d..00000000 --- a/accounting/api.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Annotated - -from annotated_types import MinLen -from ninja_extra import ControllerBase, api_controller, paginate, route -from ninja_extra.pagination import PageNumberPaginationExtra -from ninja_extra.schemas import PaginatedResponseSchema - -from accounting.models import ClubAccount, Company -from accounting.schemas import ClubAccountSchema, CompanySchema -from core.auth.api_permissions import CanAccessLookup - - -@api_controller("/lookup", permissions=[CanAccessLookup]) -class AccountingController(ControllerBase): - @route.get("/club-account", response=PaginatedResponseSchema[ClubAccountSchema]) - @paginate(PageNumberPaginationExtra, page_size=50) - def search_club_account(self, search: Annotated[str, MinLen(1)]): - return ClubAccount.objects.filter(name__icontains=search).values() - - @route.get("/company", response=PaginatedResponseSchema[CompanySchema]) - @paginate(PageNumberPaginationExtra, page_size=50) - def search_company(self, search: Annotated[str, MinLen(1)]): - return Company.objects.filter(name__icontains=search).values() diff --git a/accounting/models.py b/accounting/models.py index 6fb1a6c8..ba1024cf 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -17,15 +17,12 @@ from decimal import Decimal from django.conf import settings from django.core import validators -from django.core.exceptions import ValidationError from django.db import models -from django.template import defaultfilters -from django.urls import reverse from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from club.models import Club -from core.models import SithFile, User +from core.models import SithFile class CurrencyField(models.DecimalField): @@ -74,28 +71,6 @@ class Company(models.Model): def __str__(self): return self.name - def get_absolute_url(self): - return reverse("accounting:co_edit", kwargs={"co_id": self.id}) - - def get_display_name(self): - return self.name - - def is_owned_by(self, user): - """Check if that object can be edited by the given user.""" - return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - - def can_be_edited_by(self, user): - """Check if that object can be edited by the given user.""" - return user.memberships.filter( - end_date=None, club__role=settings.SITH_CLUB_ROLES_ID["Treasurer"] - ).exists() - - def can_be_viewed_by(self, user): - """Check if that object can be viewed by the given user.""" - return user.memberships.filter( - end_date=None, club__role_gte=settings.SITH_CLUB_ROLES_ID["Treasurer"] - ).exists() - class BankAccount(models.Model): name = models.CharField(_("name"), max_length=30) @@ -115,18 +90,6 @@ class BankAccount(models.Model): def __str__(self): return self.name - def get_absolute_url(self): - return reverse("accounting:bank_details", kwargs={"b_account_id": self.id}) - - def is_owned_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_anonymous: - return False - if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): - return True - m = self.club.get_membership_for(user) - return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"] - class ClubAccount(models.Model): name = models.CharField(_("name"), max_length=30) @@ -150,37 +113,6 @@ class ClubAccount(models.Model): def __str__(self): return self.name - def get_absolute_url(self): - return reverse("accounting:club_details", kwargs={"c_account_id": self.id}) - - def is_owned_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_anonymous: - return False - return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - - def can_be_edited_by(self, user): - """Check if that object can be edited by the given user.""" - m = self.club.get_membership_for(user) - return m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"] - - def can_be_viewed_by(self, user): - """Check if that object can be viewed by the given user.""" - m = self.club.get_membership_for(user) - return m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"] - - def has_open_journal(self): - return self.journals.filter(closed=False).exists() - - def get_open_journal(self): - return self.journals.filter(closed=False).first() - - def get_display_name(self): - return _("%(club_account)s on %(bank_account)s") % { - "club_account": self.name, - "bank_account": self.bank_account, - } - class GeneralJournal(models.Model): """Class storing all the operations for a period of time.""" @@ -206,40 +138,6 @@ class GeneralJournal(models.Model): def __str__(self): return self.name - def get_absolute_url(self): - return reverse("accounting:journal_details", kwargs={"j_id": self.id}) - - def is_owned_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_anonymous: - return False - if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): - return True - return self.club_account.can_be_edited_by(user) - - def can_be_edited_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): - return True - return self.club_account.can_be_edited_by(user) - - def can_be_viewed_by(self, user): - return self.club_account.can_be_viewed_by(user) - - def update_amounts(self): - self.amount = 0 - self.effective_amount = 0 - for o in self.operations.all(): - if o.accounting_type.movement_type == "CREDIT": - if o.done: - self.effective_amount += o.amount - self.amount += o.amount - else: - if o.done: - self.effective_amount -= o.amount - self.amount -= o.amount - self.save() - class Operation(models.Model): """An operation is a line in the journal, a debit or a credit.""" @@ -328,88 +226,6 @@ class Operation(models.Model): def __str__(self): return f"{self.amount} € | {self.date} | {self.accounting_type} | {self.done}" - def __getattribute__(self, attr): - if attr == "target": - return self.get_target() - else: - return object.__getattribute__(self, attr) - - def save(self, *args, **kwargs): - if self.number is None: - self.number = self.journal.operations.count() + 1 - super().save(*args, **kwargs) - self.journal.update_amounts() - - def get_absolute_url(self): - return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) - - def clean(self): - super().clean() - if self.date is None: - raise ValidationError(_("The date must be set.")) - elif self.date < self.journal.start_date: - raise ValidationError( - _( - """The date can not be before the start date of the journal, which is -%(start_date)s.""" - ) - % { - "start_date": defaultfilters.date( - self.journal.start_date, settings.DATE_FORMAT - ) - } - ) - if self.target_type != "OTHER" and self.get_target() is None: - raise ValidationError(_("Target does not exists")) - if self.target_type == "OTHER" and self.target_label == "": - raise ValidationError( - _("Please add a target label if you set no existing target") - ) - if not self.accounting_type and not self.simpleaccounting_type: - raise ValidationError( - _( - "You need to provide ether a simplified accounting type or a standard accounting type" - ) - ) - if self.simpleaccounting_type: - self.accounting_type = self.simpleaccounting_type.accounting_type - - @property - def target(self): - return self.get_target() - - def get_target(self): - tar = None - if self.target_type == "USER": - tar = User.objects.filter(id=self.target_id).first() - elif self.target_type == "CLUB": - tar = Club.objects.filter(id=self.target_id).first() - elif self.target_type == "ACCOUNT": - tar = ClubAccount.objects.filter(id=self.target_id).first() - elif self.target_type == "COMPANY": - tar = Company.objects.filter(id=self.target_id).first() - return tar - - def is_owned_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_anonymous: - return False - if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): - return True - if self.journal.closed: - return False - m = self.journal.club_account.club.get_membership_for(user) - return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"] - - def can_be_edited_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): - return True - if self.journal.closed: - return False - m = self.journal.club_account.club.get_membership_for(user) - return m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"] - class AccountingType(models.Model): """Accounting types. @@ -444,15 +260,6 @@ class AccountingType(models.Model): def __str__(self): return self.code + " - " + self.get_movement_type_display() + " - " + self.label - def get_absolute_url(self): - return reverse("accounting:type_list") - - def is_owned_by(self, user): - """Check if that object can be edited by the given user.""" - if user.is_anonymous: - return False - return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - class SimplifiedAccountingType(models.Model): """Simplified version of `AccountingType`.""" @@ -475,9 +282,6 @@ class SimplifiedAccountingType(models.Model): f"- {self.accounting_type.code} - {self.label}" ) - def get_absolute_url(self): - return reverse("accounting:simple_type_list") - @property def movement_type(self): return self.accounting_type.movement_type @@ -502,19 +306,3 @@ class Label(models.Model): def __str__(self): return "%s (%s)" % (self.name, self.club_account.name) - - def get_absolute_url(self): - return reverse( - "accounting:label_list", kwargs={"clubaccount_id": self.club_account.id} - ) - - def is_owned_by(self, user): - if user.is_anonymous: - return False - return self.club_account.is_owned_by(user) - - def can_be_edited_by(self, user): - return self.club_account.can_be_edited_by(user) - - def can_be_viewed_by(self, user): - return self.club_account.can_be_viewed_by(user) diff --git a/accounting/schemas.py b/accounting/schemas.py deleted file mode 100644 index 3d9edbcc..00000000 --- a/accounting/schemas.py +++ /dev/null @@ -1,15 +0,0 @@ -from ninja import ModelSchema - -from accounting.models import ClubAccount, Company - - -class ClubAccountSchema(ModelSchema): - class Meta: - model = ClubAccount - fields = ["id", "name"] - - -class CompanySchema(ModelSchema): - class Meta: - model = Company - fields = ["id", "name"] diff --git a/accounting/static/bundled/accounting/components/ajax-select-index.ts b/accounting/static/bundled/accounting/components/ajax-select-index.ts deleted file mode 100644 index 3fc93cf3..00000000 --- a/accounting/static/bundled/accounting/components/ajax-select-index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { AjaxSelect } from "#core:core/components/ajax-select-base"; -import { registerComponent } from "#core:utils/web-components"; -import type { TomOption } from "tom-select/dist/types/types"; -import type { escape_html } from "tom-select/dist/types/utils"; -import { - type ClubAccountSchema, - type CompanySchema, - accountingSearchClubAccount, - accountingSearchCompany, -} from "#openapi"; - -@registerComponent("club-account-ajax-select") -export class ClubAccountAjaxSelect extends AjaxSelect { - protected valueField = "id"; - protected labelField = "name"; - protected searchField = ["code", "name"]; - - protected async search(query: string): Promise { - const resp = await accountingSearchClubAccount({ query: { search: query } }); - if (resp.data) { - return resp.data.results; - } - return []; - } - - protected renderOption(item: ClubAccountSchema, sanitize: typeof escape_html) { - return `
- ${sanitize(item.name)} -
`; - } - - protected renderItem(item: ClubAccountSchema, sanitize: typeof escape_html) { - return `${sanitize(item.name)}`; - } -} - -@registerComponent("company-ajax-select") -export class CompanyAjaxSelect extends AjaxSelect { - protected valueField = "id"; - protected labelField = "name"; - protected searchField = ["code", "name"]; - - protected async search(query: string): Promise { - const resp = await accountingSearchCompany({ query: { search: query } }); - if (resp.data) { - return resp.data.results; - } - return []; - } - - protected renderOption(item: CompanySchema, sanitize: typeof escape_html) { - return `
- ${sanitize(item.name)} -
`; - } - - protected renderItem(item: CompanySchema, sanitize: typeof escape_html) { - return `${sanitize(item.name)}`; - } -} diff --git a/accounting/templates/accounting/accountingtype_list.jinja b/accounting/templates/accounting/accountingtype_list.jinja deleted file mode 100644 index 7ae54014..00000000 --- a/accounting/templates/accounting/accountingtype_list.jinja +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Accounting type list{% endtrans %} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {% trans %}Accounting types{% endtrans %} -

-
-

{% trans %}New accounting type{% endtrans %}

- {% if accountingtype_list %} -

{% trans %}Accounting type list{% endtrans %}

-
    - {% for a in accountingtype_list %} -
  • {{ a }}
  • - {% endfor %} -
- {% else %} - {% trans %}There is no types in this website.{% endtrans %} - {% endif %} -
-{% endblock %} - diff --git a/accounting/templates/accounting/bank_account_details.jinja b/accounting/templates/accounting/bank_account_details.jinja deleted file mode 100644 index f1b1e056..00000000 --- a/accounting/templates/accounting/bank_account_details.jinja +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Bank account: {% endtrans %}{{ object.name }} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {{ object.name }} -

-
-

{% trans %}Bank account: {% endtrans %}{{ object.name }}

- {% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and not object.club_accounts.exists() %} - {% trans %}Delete{% endtrans %} - {% endif %} -

{% trans %}Infos{% endtrans %}

-
    -
  • {% trans %}IBAN: {% endtrans %}{{ object.iban }}
  • -
  • {% trans %}Number: {% endtrans %}{{ object.number }}
  • -
-

{% trans %}New club account{% endtrans %}

- -
-{% endblock %} - - - diff --git a/accounting/templates/accounting/bank_account_list.jinja b/accounting/templates/accounting/bank_account_list.jinja deleted file mode 100644 index bb47cfca..00000000 --- a/accounting/templates/accounting/bank_account_list.jinja +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Bank account list{% endtrans %} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} -

- {% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} -

{% trans %}Manage simplified types{% endtrans %}

-

{% trans %}Manage accounting types{% endtrans %}

-

{% trans %}New bank account{% endtrans %}

- {% endif %} - {% if bankaccount_list %} -

{% trans %}Bank account list{% endtrans %}

- - {% else %} - {% trans %}There is no accounts in this website.{% endtrans %} - {% endif %} -
-{% endblock %} - - - diff --git a/accounting/templates/accounting/club_account_details.jinja b/accounting/templates/accounting/club_account_details.jinja deleted file mode 100644 index b6df130e..00000000 --- a/accounting/templates/accounting/club_account_details.jinja +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Club account:{% endtrans %} {{ object.name }} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {{object.bank_account }} > - {{ object }} -

-
-

{% trans %}Club account:{% endtrans %} {{ object.name }}

- {% if user.is_root and not object.journals.exists() %} - {% trans %}Delete{% endtrans %} - {% endif %} - {% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} -

{% trans %}New label{% endtrans %}

- {% endif %} -

{% trans %}Label list{% endtrans %}

- {% if not object.has_open_journal() %} -

{% trans %}New journal{% endtrans %}

- {% else %} -

{% trans %}You can not create new journal while you still have one opened{% endtrans %}

- {% endif %} - - - - - - - - - - - - - - {% for j in object.journals.all() %} - - - - {% if j.end_date %} - - {% else %} - - {% endif %} - - - {% if j.closed %} - - {% else %} - - {% endif %} - - - {% endfor %} - -
{% trans %}Name{% endtrans %}{% trans %}Start{% endtrans %}{% trans %}End{% endtrans %}{% trans %}Amount{% endtrans %}{% trans %}Effective amount{% endtrans %}{% trans %}Closed{% endtrans %}{% trans %}Actions{% endtrans %}
{{ j.name }}{{ j.start_date }}{{ j.end_date }} - {{ j.amount }} €{{ j.effective_amount }} €{% trans %}Yes{% endtrans %}{% trans %}No{% endtrans %} {% trans %}View{% endtrans %} - {% trans %}Edit{% endtrans %} - {% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) and j.operations.count() == 0 %} - {% trans %}Delete{% endtrans %} - {% endif %} -
-
-{% endblock %} diff --git a/accounting/templates/accounting/co_list.jinja b/accounting/templates/accounting/co_list.jinja deleted file mode 100644 index 1d357820..00000000 --- a/accounting/templates/accounting/co_list.jinja +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Company list{% endtrans %} -{% endblock %} - -{% block content %} -
- {% if user.is_root - or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - %} -

{% trans %}Create new company{% endtrans %}

-{% endif %} -
- - - - - - - - {% for o in object_list %} - - - - {% endfor %} - -
{% trans %}Companies{% endtrans %}
{{ o.get_display_name() }}
-
-{% endblock %} diff --git a/accounting/templates/accounting/journal_details.jinja b/accounting/templates/accounting/journal_details.jinja deleted file mode 100644 index 8393ca8b..00000000 --- a/accounting/templates/accounting/journal_details.jinja +++ /dev/null @@ -1,103 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}General journal:{% endtrans %} {{ object.name }} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {{object.club_account.bank_account }} > - {{ object.club_account }} > - {{ object.name }} -

-
-

{% trans %}General journal:{% endtrans %} {{ object.name }}

-

{% trans %}New label{% endtrans %}

-

{% trans %}Label list{% endtrans %}

-

{% trans %}Company list{% endtrans %}

-

{% trans %}Amount: {% endtrans %}{{ object.amount }} € - - {% trans %}Effective amount: {% endtrans %}{{ object.effective_amount }} €

- {% if object.closed %} -

{% trans %}Journal is closed, you can not create operation{% endtrans %}

- {% else %} -

{% trans %}New operation{% endtrans %}

-
- {% endif %} -
- - - - - - - - - - - - - - - - - - - - {% for o in object.operations.all() %} - - - - - {% if o.accounting_type.movement_type == "DEBIT" %} - - {% else %} - - {% endif %} - - {% if o.target_type == "OTHER" %} - - {% else %} - - {% endif %} - - - {% if o.done %} - - {% else %} - - {% endif %} - - {% if o.invoice %} - - {% else %} - - {% endif %} - - - -{% endfor %} - -
{% trans %}Nb{% endtrans %}{% trans %}Date{% endtrans %}{% trans %}Label{% endtrans %}{% trans %}Amount{% endtrans %}{% trans %}Payment mode{% endtrans %}{% trans %}Target{% endtrans %}{% trans %}Code{% endtrans %}{% trans %}Nature{% endtrans %}{% trans %}Done{% endtrans %}{% trans %}Comment{% endtrans %}{% trans %}File{% endtrans %}{% trans %}Actions{% endtrans %}{% trans %}PDF{% endtrans %}
{{ o.number }}{{ o.date }}{{ o.label or "" }} {{ o.amount }} € {{ o.amount }} €{{ o.get_mode_display() }}{{ o.target_label }}{{ o.target.get_display_name() }}{{ o.accounting_type.code }}{{ o.accounting_type.label }}{% trans %}Yes{% endtrans %}{% trans %}No{% endtrans %}{{ o.remark }} - {% if not o.linked_operation and o.target_type == "ACCOUNT" and not o.target.has_open_journal() %} -

- {% trans %}Warning: this operation has no linked operation because the targeted club account has no opened journal.{% endtrans %} -

-

- {% trans url=o.target.get_absolute_url() %}Open a journal in this club account, then save this operation again to make the linked operation.{% endtrans %} -

- {% endif %} -
{{ o.invoice.name }}- - {% - if o.journal.club_account.bank_account.name not in ["AE TI", "TI"] - or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - %} - {% if not o.journal.closed %} - {% trans %}Edit{% endtrans %} - {% endif %} - {% endif %} - {% trans %}Generate{% endtrans %}
-
-
-{% endblock %} diff --git a/accounting/templates/accounting/journal_statement_accounting.jinja b/accounting/templates/accounting/journal_statement_accounting.jinja deleted file mode 100644 index 5641e78b..00000000 --- a/accounting/templates/accounting/journal_statement_accounting.jinja +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}General journal:{% endtrans %} {{ object.name }} -{% endblock %} - - -{% block content %} -
-

{% trans %}Accounting statement: {% endtrans %} {{ object.name }}

- - - - - - - - - - {% for k,v in statement.items() %} - - - - - {% endfor %} - - -
{% trans %}Operation type{% endtrans %}{% trans %}Sum{% endtrans %}
{{ k }}{{ "%.2f" % v }}
- -

{% trans %}Amount: {% endtrans %}{{ "%.2f" % object.amount }} €

-

{% trans %}Effective amount: {% endtrans %}{{ "%.2f" %object.effective_amount }} €

-
-{% endblock %} diff --git a/accounting/templates/accounting/journal_statement_nature.jinja b/accounting/templates/accounting/journal_statement_nature.jinja deleted file mode 100644 index 0a149326..00000000 --- a/accounting/templates/accounting/journal_statement_nature.jinja +++ /dev/null @@ -1,57 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}General journal:{% endtrans %} {{ object.name }} -{% endblock %} - -{% macro display_tables(dict) %} -
-
{% trans %}Credit{% endtrans %}
- - - - - - - - - {% for k,v in dict['CREDIT'].items() %} - - - - - {% endfor %} - -
{% trans %}Nature of operation{% endtrans %}{% trans %}Sum{% endtrans %}
{{ k }}{{ "%.2f" % v }}
- {% trans %}Total: {% endtrans %}{{ "%.2f" % dict['CREDIT_sum'] }} - -
{% trans %}Debit{% endtrans %}
- - - - - - - - - {% for k,v in dict['DEBIT'].items() %} - - - - - {% endfor %} - -
{% trans %}Nature of operation{% endtrans %}{% trans %}Sum{% endtrans %}
{{ k }}{{ "%.2f" % v }}
- {% trans %}Total: {% endtrans %}{{ "%.2f" % dict['DEBIT_sum'] }} -{% endmacro %} - -{% block content %} -

{% trans %}Statement by nature: {% endtrans %} {{ object.name }}

- - {% for k,v in statement.items() %} -

{{ k }} : {{ "%.2f" % (v['CREDIT_sum'] - v['DEBIT_sum']) }}

- {{ display_tables(v) }} -
- {% endfor %} -
-{% endblock %} diff --git a/accounting/templates/accounting/journal_statement_person.jinja b/accounting/templates/accounting/journal_statement_person.jinja deleted file mode 100644 index 76482647..00000000 --- a/accounting/templates/accounting/journal_statement_person.jinja +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}General journal:{% endtrans %} {{ object.name }} -{% endblock %} - - -{% block content %} -
-

{% trans %}Statement by person: {% endtrans %} {{ object.name }}

- -

{% trans %}Credit{% endtrans %}

- - - - - - - - - - {% for key in credit_statement.keys() %} - - {% if key.target_type == "OTHER" %} - - {% elif key %} - - {% else %} - - {% endif %} - - - {% endfor %} - - -
{% trans %}Target of the operation{% endtrans %}{% trans %}Sum{% endtrans %}
{{ o.target_label }}{{ key.get_display_name() }}{{ "%.2f" % credit_statement[key] }}
- -

Total : {{ "%.2f" % total_credit }}

- -

{% trans %}Debit{% endtrans %}

- - - - - - - - - - {% for key in debit_statement.keys() %} - - {% if key.target_type == "OTHER" %} - - {% elif key %} - - {% else %} - - {% endif %} - - - {% endfor %} - - -
{% trans %}Target of the operation{% endtrans %}{% trans %}Sum{% endtrans %}
{{ o.target_label }}{{ key.get_display_name() }}{{ "%.2f" % debit_statement[key] }}
- -

Total : {{ "%.2f" % total_debit }}

-
-{% endblock %} diff --git a/accounting/templates/accounting/label_list.jinja b/accounting/templates/accounting/label_list.jinja deleted file mode 100644 index 6b5a9b8b..00000000 --- a/accounting/templates/accounting/label_list.jinja +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Label list{% endtrans %} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {{object.bank_account }} > - {{ object }} -

-
-

{% trans %}Back to club account{% endtrans %}

- {% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} -

{% trans %}New label{% endtrans %}

- {% endif %} - {% if object.labels.all() %} -

{% trans %}Label list{% endtrans %}

-
    - {% for l in object.labels.all() %} -
  • {{ l }} - {% if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} - - - {% trans %}Delete{% endtrans %} - {% endif %} -
  • - {% endfor %} -
- {% else %} - {% trans %}There is no label in this club account.{% endtrans %} - {% endif %} -
-{% endblock %} - diff --git a/accounting/templates/accounting/operation_edit.jinja b/accounting/templates/accounting/operation_edit.jinja deleted file mode 100644 index 4a75cb83..00000000 --- a/accounting/templates/accounting/operation_edit.jinja +++ /dev/null @@ -1,123 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Edit operation{% endtrans %} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {{object.club_account.bank_account }} > - {{ object.club_account }} > - {{ object.name }} > - {% trans %}Edit operation{% endtrans %} -

-
-

{% trans %}Edit operation{% endtrans %}

-
- {% csrf_token %} - {{ form.non_field_errors() }} - {{ form.journal }} - {{ form.target_id }} -

{{ form.amount.errors }} {{ form.amount }}

-

{{ form.remark.errors }} {{ form.remark }}

-
- {% trans %}Warning: if you select Account, the opposite operation will be created in the target account. If you don't want that, select Club instead of Account.{% endtrans %} -

{{ form.target_type.errors }} {{ form.target_type }}

- {{ form.user }} - {{ form.club }} - {{ form.club_account }} - {{ form.company }} - {{ form.target_label }} - {{ form.need_link }} -

{{ form.date.errors }} {{ form.date }}

-

{{ form.mode.errors }} {{ form.mode }}

-

{{ form.cheque_number.errors }} {{ - form.cheque_number }}

-

{{ form.invoice.errors }} {{ form.invoice }}

-

{{ form.simpleaccounting_type.errors }} {{ form.simpleaccounting_type }}

-

{{ form.accounting_type.errors }} {{ - form.accounting_type }}

-

{{ form.label.errors }} {{ form.label }}

-

{{ form.done.errors }} {{ form.done }}

- {% if form.instance.linked_operation %} - {% set obj = form.instance.linked_operation %} -

{% trans %}Linked operation:{% endtrans %}
- - {{obj.journal.club_account.bank_account }} > - {{ obj.journal.club_account }} > - {{ obj.journal }} > - n°{{ obj.number }} -

- {% endif %} -

-
-{% endblock %} - -{% block script %} - {{ super() }} - -
-{% endblock %} - - diff --git a/accounting/templates/accounting/simplifiedaccountingtype_list.jinja b/accounting/templates/accounting/simplifiedaccountingtype_list.jinja deleted file mode 100644 index da72a370..00000000 --- a/accounting/templates/accounting/simplifiedaccountingtype_list.jinja +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "core/base.jinja" %} - -{% block title %} - {% trans %}Simplified type list{% endtrans %} -{% endblock %} - -{% block content %} -
-

- {% trans %}Accounting{% endtrans %} > - {% trans %}Simplified types{% endtrans %} -

-
-

{% trans %}New simplified type{% endtrans %}

- {% if simplifiedaccountingtype_list %} -

{% trans %}Simplified type list{% endtrans %}

-
    - {% for a in simplifiedaccountingtype_list %} -
  • {{ a }}
  • - {% endfor %} -
- {% else %} - {% trans %}There is no types in this website.{% endtrans %} - {% endif %} -
-{% endblock %} - diff --git a/accounting/tests.py b/accounting/tests.py index 30d1586b..34cf7cfb 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -13,18 +13,10 @@ # # -from datetime import date, timedelta from django.test import TestCase from django.urls import reverse -from accounting.models import ( - AccountingType, - GeneralJournal, - Label, - Operation, - SimplifiedAccountingType, -) from core.models import User @@ -65,228 +57,3 @@ class TestRefoundAccount(TestCase): assert response.status_code == 200 assert '
' in str(response.content) assert self.skia.customer.amount == 0 - - -class TestJournal(TestCase): - @classmethod - def setUpTestData(cls): - cls.journal = GeneralJournal.objects.get(id=1) - - def test_permission_granted(self): - self.client.force_login(User.objects.get(username="comptable")) - response_get = self.client.get( - reverse("accounting:journal_details", args=[self.journal.id]) - ) - - assert response_get.status_code == 200 - assert "M\\xc3\\xa9thode de paiement" in str(response_get.content) - - def test_permission_not_granted(self): - self.client.force_login(User.objects.get(username="skia")) - response_get = self.client.get( - reverse("accounting:journal_details", args=[self.journal.id]) - ) - - assert response_get.status_code == 403 - assert "M\xc3\xa9thode de paiement" not in str(response_get.content) - - -class TestOperation(TestCase): - def setUp(self): - self.tomorrow_formatted = (date.today() + timedelta(days=1)).strftime( - "%d/%m/%Y" - ) - 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() - label = Label.objects.create(club_account=self.journal.club_account, name="bob") - self.client.force_login(User.objects.get(username="comptable")) - self.op1 = Operation( - journal=self.journal, - date=date.today(), - amount=1, - remark="Test bilan", - mode="CASH", - done=True, - label=label, - 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=label, - accounting_type=at, - target_type="USER", - target_id=self.skia.id, - ) - self.op2.save() - - def test_new_operation(self): - at = AccountingType.objects.get(code="604") - 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": self.tomorrow_formatted, - "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() - ) - 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): - AccountingType.objects.get(code="604") - 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": self.tomorrow_formatted, - "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.force_login(self.skia) - 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": self.tomorrow_formatted, - "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() - ) - - def test_operation_simple_accounting(self): - 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": self.tomorrow_formatted, - "mode": "CASH", - "cheque_number": "", - "invoice": "", - "simpleaccounting_type": sat.id, - "accounting_type": "", - "label": "", - "done": False, - }, - ) - assert response.status_code != 403 - assert self.journal.operations.filter(amount=23).exists() - response_get = self.client.get( - reverse("accounting:journal_details", args=[self.journal.id]) - ) - assert "Le fantome de l'aurore" in str(response_get.content) - - assert ( - self.journal.operations.filter(amount=23) - .values("accounting_type") - .first()["accounting_type"] - == AccountingType.objects.filter(code=6).values("id").first()["id"] - ) - - def test_nature_statement(self): - response = self.client.get( - reverse("accounting:journal_nature_statement", args=[self.journal.id]) - ) - self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200) - - def test_person_statement(self): - response = self.client.get( - reverse("accounting:journal_person_statement", args=[self.journal.id]) - ) - self.assertContains(response, "Total : 5575.72", status_code=200) - self.assertContains(response, "Total : 71.42") - content = response.content.decode() - self.assertInHTML( - """S' Kia3.00""", content - ) - self.assertInHTML( - """S' Kia823.00""", content - ) - - def test_accounting_statement(self): - response = self.client.get( - reverse("accounting:journal_accounting_statement", args=[self.journal.id]) - ) - assert response.status_code == 200 - self.assertInHTML( - """ - - 443 - Crédit - Ce code n'existe pas - 3.00 - """, - response.content.decode(), - ) - self.assertContains( - response, - """ -

Montant : -5504.30 €

-

Montant effectif: -5504.30 €

""", - ) diff --git a/accounting/urls.py b/accounting/urls.py index f1917462..3bebe3bb 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -15,159 +15,9 @@ from django.urls import path -from accounting.views import ( - AccountingTypeCreateView, - AccountingTypeEditView, - AccountingTypeListView, - BankAccountCreateView, - BankAccountDeleteView, - BankAccountDetailView, - BankAccountEditView, - BankAccountListView, - ClubAccountCreateView, - ClubAccountDeleteView, - ClubAccountDetailView, - ClubAccountEditView, - CompanyCreateView, - CompanyEditView, - CompanyListView, - JournalAccountingStatementView, - JournalCreateView, - JournalDeleteView, - JournalDetailView, - JournalEditView, - JournalNatureStatementView, - JournalPersonStatementView, - LabelCreateView, - LabelDeleteView, - LabelEditView, - LabelListView, - OperationCreateView, - OperationEditView, - OperationPDFView, - RefoundAccountView, - SimplifiedAccountingTypeCreateView, - SimplifiedAccountingTypeEditView, - SimplifiedAccountingTypeListView, -) +from accounting.views import RefoundAccountView urlpatterns = [ - # Accounting types - path( - "simple_type/", - SimplifiedAccountingTypeListView.as_view(), - name="simple_type_list", - ), - path( - "simple_type/create/", - SimplifiedAccountingTypeCreateView.as_view(), - name="simple_type_new", - ), - path( - "simple_type//edit/", - SimplifiedAccountingTypeEditView.as_view(), - name="simple_type_edit", - ), - # Accounting types - path("type/", AccountingTypeListView.as_view(), name="type_list"), - path("type/create/", AccountingTypeCreateView.as_view(), name="type_new"), - path( - "type//edit/", - AccountingTypeEditView.as_view(), - name="type_edit", - ), - # Bank accounts - path("", BankAccountListView.as_view(), name="bank_list"), - path("bank/create", BankAccountCreateView.as_view(), name="bank_new"), - path( - "bank//", - BankAccountDetailView.as_view(), - name="bank_details", - ), - path( - "bank//edit/", - BankAccountEditView.as_view(), - name="bank_edit", - ), - path( - "bank//delete/", - BankAccountDeleteView.as_view(), - name="bank_delete", - ), - # Club accounts - path("club/create/", ClubAccountCreateView.as_view(), name="club_new"), - path( - "club//", - ClubAccountDetailView.as_view(), - name="club_details", - ), - path( - "club//edit/", - ClubAccountEditView.as_view(), - name="club_edit", - ), - path( - "club//delete/", - ClubAccountDeleteView.as_view(), - name="club_delete", - ), - # Journals - path("journal/create/", JournalCreateView.as_view(), name="journal_new"), - path( - "journal//", - JournalDetailView.as_view(), - name="journal_details", - ), - path( - "journal//edit/", - JournalEditView.as_view(), - name="journal_edit", - ), - path( - "journal//delete/", - JournalDeleteView.as_view(), - name="journal_delete", - ), - path( - "journal//statement/nature/", - JournalNatureStatementView.as_view(), - name="journal_nature_statement", - ), - path( - "journal//statement/person/", - JournalPersonStatementView.as_view(), - name="journal_person_statement", - ), - path( - "journal//statement/accounting/", - JournalAccountingStatementView.as_view(), - name="journal_accounting_statement", - ), - # Operations - path( - "operation/create//", - OperationCreateView.as_view(), - name="op_new", - ), - path("operation//", OperationEditView.as_view(), name="op_edit"), - path("operation//pdf/", OperationPDFView.as_view(), name="op_pdf"), - # Companies - path("company/list/", CompanyListView.as_view(), name="co_list"), - path("company/create/", CompanyCreateView.as_view(), name="co_new"), - path("company//", CompanyEditView.as_view(), name="co_edit"), - # Labels - path("label/new/", LabelCreateView.as_view(), name="label_new"), - path( - "label//", - LabelListView.as_view(), - name="label_list", - ), - path("label//edit/", LabelEditView.as_view(), name="label_edit"), - path( - "label//delete/", - LabelDeleteView.as_view(), - name="label_delete", - ), # User account path("refound/account/", RefoundAccountView.as_view(), name="refound_account"), ] diff --git a/accounting/views.py b/accounting/views.py index 5f8640fd..3331d38e 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -13,829 +13,22 @@ # # -import collections from django import forms from django.conf import settings -from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin -from django.core.exceptions import PermissionDenied, ValidationError +from django.contrib.auth.mixins import UserPassesTestMixin from django.db import transaction -from django.db.models import Sum -from django.forms import HiddenInput -from django.forms.models import modelform_factory -from django.http import HttpResponse -from django.urls import reverse, reverse_lazy +from django.urls import reverse 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 +from django.views.generic.edit import FormView -from accounting.models import ( - AccountingType, - BankAccount, - ClubAccount, - Company, - GeneralJournal, - Label, - Operation, - SimplifiedAccountingType, -) -from accounting.widgets.ajax_select import ( - AutoCompleteSelectClubAccount, - AutoCompleteSelectCompany, -) -from club.models import Club -from club.widgets.ajax_select import AutoCompleteSelectClub -from core.auth.mixins import ( - CanCreateMixin, - CanEditMixin, - CanEditPropMixin, - CanViewMixin, -) from core.models import User -from core.views.forms import SelectDate, SelectFile -from core.views.mixins import TabedViewMixin from core.views.widgets.ajax_select import AutoCompleteSelectUser from counter.models import Counter, Product, Selling # Main accounting view -class BankAccountListView(CanViewMixin, ListView): - """A list view for the admins.""" - - model = BankAccount - template_name = "accounting/bank_account_list.jinja" - ordering = ["name"] - - -# Simplified accounting types - - -class SimplifiedAccountingTypeListView(CanViewMixin, ListView): - """A list view for the admins.""" - - model = SimplifiedAccountingType - template_name = "accounting/simplifiedaccountingtype_list.jinja" - - -class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): - """An edit view for the admins.""" - - model = SimplifiedAccountingType - pk_url_kwarg = "type_id" - fields = ["label", "accounting_type"] - template_name = "core/edit.jinja" - - -class SimplifiedAccountingTypeCreateView(PermissionRequiredMixin, CreateView): - """Create an accounting type (for the admins).""" - - model = SimplifiedAccountingType - fields = ["label", "accounting_type"] - template_name = "core/create.jinja" - permission_required = "accounting.add_simplifiedaccountingtype" - - -# Accounting types - - -class AccountingTypeListView(CanViewMixin, ListView): - """A list view for the admins.""" - - model = AccountingType - template_name = "accounting/accountingtype_list.jinja" - - -class AccountingTypeEditView(CanViewMixin, UpdateView): - """An edit view for the admins.""" - - model = AccountingType - pk_url_kwarg = "type_id" - fields = ["code", "label", "movement_type"] - template_name = "core/edit.jinja" - - -class AccountingTypeCreateView(PermissionRequiredMixin, CreateView): - """Create an accounting type (for the admins).""" - - model = AccountingType - fields = ["code", "label", "movement_type"] - template_name = "core/create.jinja" - permission_required = "accounting.add_accountingtype" - - -# BankAccount views - - -class BankAccountEditView(CanViewMixin, UpdateView): - """An edit view for the admins.""" - - model = BankAccount - pk_url_kwarg = "b_account_id" - fields = ["name", "iban", "number", "club"] - template_name = "core/edit.jinja" - - -class BankAccountDetailView(CanViewMixin, DetailView): - """A detail view, listing every club account.""" - - model = BankAccount - pk_url_kwarg = "b_account_id" - template_name = "accounting/bank_account_details.jinja" - - -class BankAccountCreateView(CanCreateMixin, CreateView): - """Create a bank account (for the admins).""" - - model = BankAccount - fields = ["name", "club", "iban", "number"] - template_name = "core/create.jinja" - - -class BankAccountDeleteView( - CanEditPropMixin, DeleteView -): # TODO change Delete to Close - """Delete a bank account (for the admins).""" - - model = BankAccount - pk_url_kwarg = "b_account_id" - template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy("accounting:bank_list") - - -# ClubAccount views - - -class ClubAccountEditView(CanViewMixin, UpdateView): - """An edit view for the admins.""" - - model = ClubAccount - pk_url_kwarg = "c_account_id" - fields = ["name", "club", "bank_account"] - template_name = "core/edit.jinja" - - -class ClubAccountDetailView(CanViewMixin, DetailView): - """A detail view, listing every journal.""" - - model = ClubAccount - pk_url_kwarg = "c_account_id" - template_name = "accounting/club_account_details.jinja" - - -class ClubAccountCreateView(CanCreateMixin, CreateView): - """Create a club account (for the admins).""" - - model = ClubAccount - fields = ["name", "club", "bank_account"] - template_name = "core/create.jinja" - - def get_initial(self): - ret = super().get_initial() - if "parent" in self.request.GET: - obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first() - if obj is not None: - ret["bank_account"] = obj.id - return ret - - -class ClubAccountDeleteView( - CanEditPropMixin, DeleteView -): # TODO change Delete to Close - """Delete a club account (for the admins).""" - - model = ClubAccount - pk_url_kwarg = "c_account_id" - template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy("accounting:bank_list") - - -# Journal views - - -class JournalTabsMixin(TabedViewMixin): - def get_tabs_title(self): - return _("Journal") - - def get_list_of_tabs(self): - return [ - { - "url": reverse( - "accounting:journal_details", kwargs={"j_id": self.object.id} - ), - "slug": "journal", - "name": _("Journal"), - }, - { - "url": reverse( - "accounting:journal_nature_statement", - kwargs={"j_id": self.object.id}, - ), - "slug": "nature_statement", - "name": _("Statement by nature"), - }, - { - "url": reverse( - "accounting:journal_person_statement", - kwargs={"j_id": self.object.id}, - ), - "slug": "person_statement", - "name": _("Statement by person"), - }, - { - "url": reverse( - "accounting:journal_accounting_statement", - kwargs={"j_id": self.object.id}, - ), - "slug": "accounting_statement", - "name": _("Accounting statement"), - }, - ] - - -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}, - ) - template_name = "core/create.jinja" - - def get_initial(self): - ret = super().get_initial() - if "parent" in self.request.GET: - obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() - if obj is not None: - ret["club_account"] = obj.id - return ret - - -class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): - """A detail view, listing every operation.""" - - model = GeneralJournal - pk_url_kwarg = "j_id" - template_name = "accounting/journal_details.jinja" - current_tab = "journal" - - -class JournalEditView(CanEditMixin, UpdateView): - """Update a general journal.""" - - model = GeneralJournal - pk_url_kwarg = "j_id" - 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).""" - - model = GeneralJournal - pk_url_kwarg = "j_id" - template_name = "core/delete_confirm.jinja" - 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().dispatch(request, *args, **kwargs) - else: - raise PermissionDenied - - -# Operation views - - -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", - ] - widgets = { - "journal": HiddenInput, - "target_id": HiddenInput, - "date": SelectDate, - "invoice": SelectFile, - } - - user = forms.ModelChoiceField( - help_text=None, - required=False, - widget=AutoCompleteSelectUser, - queryset=User.objects.all(), - ) - club_account = forms.ModelChoiceField( - help_text=None, - required=False, - widget=AutoCompleteSelectClubAccount, - queryset=ClubAccount.objects.all(), - ) - club = forms.ModelChoiceField( - help_text=None, - required=False, - widget=AutoCompleteSelectClub, - queryset=Club.objects.all(), - ) - company = forms.ModelChoiceField( - help_text=None, - required=False, - widget=AutoCompleteSelectCompany, - queryset=Company.objects.all(), - ) - need_link = forms.BooleanField( - label=_("Link this operation to the target account"), - required=False, - initial=False, - ) - - def __init__(self, *args, **kwargs): - club_account = kwargs.pop("club_account", None) - super().__init__(*args, **kwargs) - if club_account: - self.fields["label"].queryset = club_account.labels.order_by("name").all() - if self.instance.target_type == "USER": - self.fields["user"].initial = self.instance.target_id - elif self.instance.target_type == "ACCOUNT": - self.fields["club_account"].initial = self.instance.target_id - elif self.instance.target_type == "CLUB": - self.fields["club"].initial = self.instance.target_id - elif self.instance.target_type == "COMPANY": - self.fields["company"].initial = self.instance.target_id - - def clean(self): - self.cleaned_data = super().clean() - if "target_type" in self.cleaned_data: - 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") == "" - ): - self.add_error( - "target_type", ValidationError(_("The target must be set.")) - ) - else: - 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: - self.add_error("amount", ValidationError(_("The amount must be set."))) - - return self.cleaned_data - - def save(self): - ret = super().save() - 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"] - ): - 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 - 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, - ) - op.save() - self.instance.linked_operation = op - self.save() - return ret - - -class OperationCreateView(CanCreateMixin, CreateView): - """Create an operation.""" - - model = Operation - form_class = OperationForm - template_name = "accounting/operation_edit.jinja" - - def get_form(self, form_class=None): - self.journal = GeneralJournal.objects.filter(id=self.kwargs["j_id"]).first() - ca = self.journal.club_account if self.journal else None - return self.form_class(club_account=ca, **self.get_form_kwargs()) - - def get_initial(self): - ret = super().get_initial() - if self.journal is not None: - ret["journal"] = self.journal.id - return ret - - def get_context_data(self, **kwargs): - """Add journal to the context.""" - kwargs = super().get_context_data(**kwargs) - if self.journal: - kwargs["object"] = self.journal - return kwargs - - -class OperationEditView(CanEditMixin, UpdateView): - """An edit view, working as detail for the moment.""" - - model = Operation - pk_url_kwarg = "op_id" - form_class = OperationForm - template_name = "accounting/operation_edit.jinja" - - def get_context_data(self, **kwargs): - """Add journal to the context.""" - kwargs = super().get_context_data(**kwargs) - kwargs["object"] = self.object.journal - return kwargs - - -class OperationPDFView(CanViewMixin, DetailView): - """Display the PDF of a given operation.""" - - model = Operation - pk_url_kwarg = "op_id" - - def get(self, request, *args, **kwargs): - from reportlab.lib import colors - from reportlab.lib.pagesizes import letter - from reportlab.lib.units import cm - from reportlab.lib.utils import ImageReader - from reportlab.pdfbase import pdfmetrics - from reportlab.pdfbase.ttfonts import TTFont - from reportlab.pdfgen import canvas - from reportlab.platypus import Table, TableStyle - - pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf")) - - 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() - - response = HttpResponse(content_type="application/pdf") - response["Content-Disposition"] = 'filename="op-%d(%s_on_%s).pdf"' % ( - num, - ti, - club_name, - ) - p = canvas.Canvas(response) - - p.setFont("DejaVu", 12) - - p.setTitle("%s %d" % (_("Operation"), num)) - 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) - - 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")])) - w, h = label.wrapOn(label, 0, 0) - label.drawOn(p, width - 180, height) - - 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 ""}, - ) - p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date}) - - data = [] - - data += [ - ["%s" % (_("Credit").upper() if nature == "CREDIT" else _("Debit").upper())] - ] - - data += [[_("Amount: %(amount).2f €") % {"amount": amount}]] - - payment_mode = "" - for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD: - if m[0] == mode: - payment_mode += "[\u00d7]" - else: - payment_mode += "[ ]" - payment_mode += " %s\n" % (m[1]) - - data += [[payment_mode]] - - data += [ - [ - "%s : %s" - % (_("Debtor") if nature == "CREDIT" else _("Creditor"), target), - "", - ] - ] - - data += [["%s \n%s" % (_("Comment:"), remark)]] - - 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), - ] - ) - ) - - signature = [] - signature += [[_("Signature:")]] - - 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), - ] - ) - ) - - w, h = tSig.wrapOn(p, 0, 0) - tSig.drawOn(p, 90, 200) - - 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 - - -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" - - 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")), - ]: - amount = queryset.filter( - 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 "" - if amount: - total_sum += amount - statement[label] = amount - ret[movement_type] = statement - ret[movement_type + "_sum"] = total_sum - return ret - - def big_statement(self): - label_list = ( - self.object.operations.order_by("label").values_list("label").distinct() - ) - 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 - 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") - ) - statement[_("No label operations")] = no_label_statement - for label in labels: - 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 - return statement - - def get_context_data(self, **kwargs): - """Add infos to the context.""" - kwargs = super().get_context_data(**kwargs) - 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" - - 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"] - - 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() - ): - statement[op.target] = self.sum_by_target( - op.target_id, op.target_type, movement_type - ) - return statement - - def total(self, movement_type): - return sum(self.statement(movement_type).values()) - - def get_context_data(self, **kwargs): - """Add journal to the context.""" - kwargs = super().get_context_data(**kwargs) - kwargs["credit_statement"] = self.statement("CREDIT") - kwargs["debit_statement"] = self.statement("DEBIT") - kwargs["total_credit"] = self.total("CREDIT") - 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" - current_tab = "accounting_statement" - - def statement(self): - statement = collections.OrderedDict() - for at in AccountingType.objects.order_by("code").all(): - sum_by_type = self.object.operations.filter( - accounting_type__code__startswith=at.code - ).aggregate(amount_sum=Sum("amount"))["amount_sum"] - if sum_by_type: - statement[at] = sum_by_type - return statement - - def get_context_data(self, **kwargs): - """Add journal to the context.""" - kwargs = super().get_context_data(**kwargs) - kwargs["statement"] = self.statement() - return kwargs - - -# Company views - - -class CompanyListView(CanViewMixin, ListView): - model = Company - template_name = "accounting/co_list.jinja" - - -class CompanyCreateView(CanCreateMixin, CreateView): - """Create a company.""" - - model = Company - fields = ["name"] - template_name = "core/create.jinja" - success_url = reverse_lazy("accounting:co_list") - - -class CompanyEditView(CanCreateMixin, UpdateView): - """Edit a company.""" - - model = Company - pk_url_kwarg = "co_id" - fields = ["name"] - template_name = "core/edit.jinja" - success_url = reverse_lazy("accounting:co_list") - - -# Label views - - -class LabelListView(CanViewMixin, DetailView): - model = ClubAccount - 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 - model = Label - form_class = modelform_factory( - Label, fields=["name", "club_account"], widgets={"club_account": HiddenInput} - ) - template_name = "core/create.jinja" - - def get_initial(self): - ret = super().get_initial() - if "parent" in self.request.GET: - obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() - if obj is not None: - 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" - template_name = "core/delete_confirm.jinja" - - def get_success_url(self): - return self.object.get_absolute_url() - - class CloseCustomerAccountForm(forms.Form): user = forms.ModelChoiceField( label=_("Refound this account"), diff --git a/accounting/widgets/ajax_select.py b/accounting/widgets/ajax_select.py deleted file mode 100644 index 76bd8382..00000000 --- a/accounting/widgets/ajax_select.py +++ /dev/null @@ -1,42 +0,0 @@ -from pydantic import TypeAdapter - -from accounting.models import ClubAccount, Company -from accounting.schemas import ClubAccountSchema, CompanySchema -from core.views.widgets.ajax_select import ( - AutoCompleteSelect, - AutoCompleteSelectMultiple, -) - -_js = ["bundled/accounting/components/ajax-select-index.ts"] - - -class AutoCompleteSelectClubAccount(AutoCompleteSelect): - component_name = "club-account-ajax-select" - model = ClubAccount - adapter = TypeAdapter(list[ClubAccountSchema]) - - js = _js - - -class AutoCompleteSelectMultipleClubAccount(AutoCompleteSelectMultiple): - component_name = "club-account-ajax-select" - model = ClubAccount - adapter = TypeAdapter(list[ClubAccountSchema]) - - js = _js - - -class AutoCompleteSelectCompany(AutoCompleteSelect): - component_name = "company-ajax-select" - model = Company - adapter = TypeAdapter(list[CompanySchema]) - - js = _js - - -class AutoCompleteSelectMultipleCompany(AutoCompleteSelectMultiple): - component_name = "company-ajax-select" - model = Company - adapter = TypeAdapter(list[CompanySchema]) - - js = _js diff --git a/club/templates/club/club_tools.jinja b/club/templates/club/club_tools.jinja index fa9584fa..d9245b34 100644 --- a/club/templates/club/club_tools.jinja +++ b/club/templates/club/club_tools.jinja @@ -29,14 +29,6 @@ {% endfor %} {% endif %} - {% if object.club_account.exists() %} -

{% trans %}Accounting: {% endtrans %}

- - {% endif %} {% if object.unix_name == settings.SITH_LAUNDERETTE_MANAGER['unix_name'] %}
  • {% trans %}Manage launderettes{% endtrans %}
  • {% endif %} diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja index 4c9b9462..29b276a0 100644 --- a/core/templates/core/user_tools.jinja +++ b/core/templates/core/user_tools.jinja @@ -110,27 +110,7 @@ {% endif %}