367 lines
11 KiB
Python
367 lines
11 KiB
Python
|
"""Définition des modèles associés à la comptabilité : transaction bancaire, `calcul` des comptes
|
||
|
annuels (demandé par le SPF Finance), …
|
||
|
|
||
|
"""
|
||
|
|
||
|
import markdown
|
||
|
|
||
|
from django.core.exceptions import ValidationError
|
||
|
from django.db import models
|
||
|
from django.utils import timezone
|
||
|
|
||
|
from eventCompta.models import Event
|
||
|
|
||
|
from .utils import zero_or_value
|
||
|
|
||
|
|
||
|
class RuleOfEvaluation(models.Model):
|
||
|
"""Définit les règles d'évaluation (comptabilité simplifié pour le SPF Finance)
|
||
|
"""
|
||
|
|
||
|
label = models.CharField(max_length=255)
|
||
|
explanation = models.TextField()
|
||
|
start_date = models.DateField()
|
||
|
stop_date = models.DateField(blank=True, null=True)
|
||
|
|
||
|
def __str__(self):
|
||
|
return "%s : %s - %s" % (self.label, self.start_date, self.stop_date)
|
||
|
|
||
|
|
||
|
class Annuality(models.Model):
|
||
|
"""Définit un année par un nombre (2016, 2017, …), une balance d'ouverture (combien il
|
||
|
y a sur le compte en début d'année) et une balance de fermeture (combien il y a sur le
|
||
|
compte en fin d'année).
|
||
|
|
||
|
La balance d'ouverture d'une année doit être égale à la balance de fermeture de l'année
|
||
|
précédente.
|
||
|
|
||
|
A répercuter dans les vues !!!
|
||
|
"""
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "0. Annualité"
|
||
|
verbose_name_plural = "0. Annualités"
|
||
|
|
||
|
year = models.DateField(default=timezone.now, verbose_name="Année comptabile")
|
||
|
opening_balance = models.DecimalField(
|
||
|
max_digits=9,
|
||
|
decimal_places=2,
|
||
|
blank=True,
|
||
|
verbose_name="Balance d'ouverture",
|
||
|
default=0,
|
||
|
)
|
||
|
closing_balance = models.DecimalField(
|
||
|
max_digits=9,
|
||
|
decimal_places=2,
|
||
|
blank=True,
|
||
|
verbose_name="Balance de fermeture",
|
||
|
default=0,
|
||
|
)
|
||
|
|
||
|
def __str__(self):
|
||
|
"""Retourne l'année, le montant d'entrée et le montant de fin."""
|
||
|
return "%s (%s - %s)" % (self.year, self.opening_balance, self.closing_balance)
|
||
|
|
||
|
|
||
|
class ComptabilityManager(models.Manager):
|
||
|
"""Manager permettant de récupérer toutes les transactions par année.
|
||
|
"""
|
||
|
|
||
|
def by_year(self, year):
|
||
|
return super().get_queryset().filter(registrationDate__year=year)
|
||
|
|
||
|
|
||
|
class Comptability(models.Model):
|
||
|
"""Classe représentant les champs communs aux lignes de comptabilité, aux droits en
|
||
|
engagements, … etc.
|
||
|
"""
|
||
|
|
||
|
objects = ComptabilityManager()
|
||
|
|
||
|
class Meta:
|
||
|
abstract = True
|
||
|
verbose_name = "Comptabilité"
|
||
|
verbose_name_plural = "Comptabilités"
|
||
|
|
||
|
registrationDate = models.DateTimeField(
|
||
|
default=timezone.now, verbose_name="Date d'enregistrement"
|
||
|
)
|
||
|
totalAmount = models.DecimalField(
|
||
|
max_digits=9,
|
||
|
decimal_places=2,
|
||
|
blank=True,
|
||
|
verbose_name="Montant total",
|
||
|
default=0,
|
||
|
)
|
||
|
description = models.TextField(verbose_name="Description")
|
||
|
is_done = models.BooleanField(default=True, blank=True, verbose_name="Effectué ?")
|
||
|
is_simulated = models.BooleanField(
|
||
|
default=False, blank=True, verbose_name="Simulé ?"
|
||
|
)
|
||
|
notes = models.TextField(
|
||
|
blank=True, null=True, help_text="Seul le MarkDown simple est accepté"
|
||
|
)
|
||
|
counterpart = models.CharField(
|
||
|
max_length=255, blank=True, null=True, verbose_name="Contre partie"
|
||
|
)
|
||
|
|
||
|
def to_markdown(self):
|
||
|
"""Convertit le commentaire associé à une `Transaction` en (Github-flavored) Markdown. """
|
||
|
html = ""
|
||
|
if self.notes:
|
||
|
html = markdown.markdown(self.notes)
|
||
|
return html
|
||
|
|
||
|
|
||
|
# Classe représentant les règles d'évaluation
|
||
|
class EvaluationRules(RuleOfEvaluation):
|
||
|
"""Règles d'évaluation
|
||
|
"""
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "1. Règle d'évaluation"
|
||
|
verbose_name_plural = "1. Règles d'évaluation"
|
||
|
|
||
|
|
||
|
# Classe représentant les règles d'évaluation
|
||
|
class EvaluationRulesAdaptation(RuleOfEvaluation):
|
||
|
"""Règles d'évaluation adaptées
|
||
|
"""
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "2. Adaptation des règles d'évaluation"
|
||
|
verbose_name_plural = "2. Adaptations des règles d'évaluation"
|
||
|
|
||
|
|
||
|
# Classe représentant les règles d'évaluation
|
||
|
class ComplementaryInformations(models.Model):
|
||
|
"""Informations complémentaire pour la comptabilité simplifiée.
|
||
|
"""
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "3. Information complémentaire"
|
||
|
verbose_name_plural = "3. Informations complémentaires"
|
||
|
|
||
|
annuality = models.ForeignKey(
|
||
|
Annuality, null=True, blank=True, on_delete=models.CASCADE
|
||
|
)
|
||
|
label = models.CharField(max_length=255)
|
||
|
information = models.TextField()
|
||
|
|
||
|
def __str__(self):
|
||
|
return "%s : (%s)" % (self.label, self.annuality)
|
||
|
|
||
|
|
||
|
class TransactionType(models.Model):
|
||
|
"""Représente les types de transaction possible. """
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "Type de transaction"
|
||
|
verbose_name_plural = "Types de transactions"
|
||
|
ordering = [
|
||
|
"label",
|
||
|
]
|
||
|
|
||
|
TRANSACTION_TYPE_CHOICE = (
|
||
|
(0, "Dépense"),
|
||
|
(1, "Recette"),
|
||
|
)
|
||
|
|
||
|
TRANSACTION_SUBTYPE_CHOICE = (
|
||
|
(0, "Autre"), # pour les deux : dépense et recette
|
||
|
(1, "Marchandise & Service"), # dépense
|
||
|
(2, "Rémunérations"), # dépense
|
||
|
(3, "Service et biens divers"), # dépense
|
||
|
(4, "Cotisations"), # recette
|
||
|
(5, "Dons & legs"), # recette
|
||
|
(6, "Subsides"), # recette
|
||
|
)
|
||
|
|
||
|
order = models.IntegerField()
|
||
|
label = models.CharField(max_length=255)
|
||
|
category = models.IntegerField(choices=TRANSACTION_SUBTYPE_CHOICE)
|
||
|
transaction_type = models.IntegerField(choices=TRANSACTION_TYPE_CHOICE)
|
||
|
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL)
|
||
|
|
||
|
def __str__(self):
|
||
|
return "%s (%s)" % (self.label, self.get_transaction_type_display())
|
||
|
|
||
|
|
||
|
class TransactionManager(ComptabilityManager):
|
||
|
"""Manager permettant de récupérer des transactions par type
|
||
|
"""
|
||
|
|
||
|
def by_type(self, transaction_type):
|
||
|
return super().get_queryset().filter(transaction_type=transaction_type)
|
||
|
|
||
|
|
||
|
class Transaction(Comptability):
|
||
|
"""Classe représentant un mouvement d'argent. """
|
||
|
|
||
|
objects = TransactionManager()
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "0. Transaction"
|
||
|
verbose_name_plural = "0. Transactions"
|
||
|
unique_together = (
|
||
|
"registrationDate",
|
||
|
"description",
|
||
|
"bkAmount",
|
||
|
"account_number",
|
||
|
)
|
||
|
|
||
|
bkExtractNumber = models.IntegerField(
|
||
|
blank=True, null=True, verbose_name="N° Extrait de compte"
|
||
|
)
|
||
|
bkAmount = models.DecimalField(
|
||
|
max_digits=7, decimal_places=2, default=0, verbose_name="Montant (banque)"
|
||
|
)
|
||
|
bxExtractNumber = models.IntegerField(blank=True, null=True)
|
||
|
bxAmount = models.DecimalField(
|
||
|
max_digits=7, decimal_places=2, default=0, verbose_name="Montant (cash)"
|
||
|
)
|
||
|
transaction_type = models.ForeignKey(
|
||
|
TransactionType,
|
||
|
related_name="to_compta_line",
|
||
|
verbose_name="Type",
|
||
|
on_delete=models.CASCADE,
|
||
|
)
|
||
|
amount = models.DecimalField(
|
||
|
max_digits=7, decimal_places=2, blank=True, null=True, verbose_name="Montant"
|
||
|
)
|
||
|
otherDescription = models.TextField(
|
||
|
max_length=255, blank=True, null=True, verbose_name="Description (type `Autre`)"
|
||
|
)
|
||
|
event = models.ForeignKey(
|
||
|
Event,
|
||
|
null=True,
|
||
|
blank=True,
|
||
|
related_name="transactions",
|
||
|
on_delete=models.CASCADE,
|
||
|
)
|
||
|
account_number = models.CharField(max_length=33, blank=True, default="")
|
||
|
|
||
|
def __str__(self):
|
||
|
"""Retourne la description de la ligne de comptabilité. """
|
||
|
return "%s" % (self.description)
|
||
|
|
||
|
def __compute_total_amount(self):
|
||
|
"""Calcule le montant total de la transaction. """
|
||
|
self.totalAmount = zero_or_value(self.bkAmount) + zero_or_value(self.bxAmount)
|
||
|
|
||
|
def __compute_amount(self):
|
||
|
"""Calcule le montant de la transaction s'il n'est pas déjà fourni. """
|
||
|
if self.amount is None:
|
||
|
self.amount = self.totalAmount
|
||
|
|
||
|
def clean(self):
|
||
|
"""Valide les champs `bkamount` et `bxamount`. """
|
||
|
msg = 'Merci de remplir au moins un des montants : "montant banque" ou "montant caisse".'
|
||
|
|
||
|
if self.bkAmount is None and self.bxAmount is None:
|
||
|
raise ValidationError(msg)
|
||
|
|
||
|
def save(self, *args, **kwargs):
|
||
|
"""Enregistre l'objet en DB après avoir calculé le montant total. """
|
||
|
self.clean()
|
||
|
self.__compute_total_amount()
|
||
|
self.__compute_amount()
|
||
|
super().save(*args, **kwargs)
|
||
|
|
||
|
|
||
|
class PatrimonyType(models.Model):
|
||
|
"""Classe représentant les types de droits et devoirs. """
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "Type de patrimoine"
|
||
|
verbose_name_plural = "Types de patrimoine"
|
||
|
|
||
|
PATRIMONY_CHOICE = (
|
||
|
(0, "Avoirs"),
|
||
|
(1, "Dettes"),
|
||
|
)
|
||
|
|
||
|
PATRIMONY_TYPE_CHOICE = (
|
||
|
(0, "Immeubles (terrain, …)"),
|
||
|
(1, "Machines"),
|
||
|
(2, "Mobilier et matériel roulant"),
|
||
|
(3, "Stocks"),
|
||
|
(4, "Créance"),
|
||
|
(5, "Placemet de trésorerie"),
|
||
|
(6, "Liquidité"),
|
||
|
(7, "Autres avoirs"),
|
||
|
(8, "Dettes financières"),
|
||
|
(9, "Dettes à l'égard des fournisseurs"),
|
||
|
(10, "Dettes à l'égard des membres"),
|
||
|
(11, "Dettes fiscales, salariales et sociales"),
|
||
|
(12, "Autres dettes"),
|
||
|
)
|
||
|
|
||
|
order = models.IntegerField(verbose_name="Ordre")
|
||
|
label = models.CharField(max_length=255)
|
||
|
category = models.IntegerField(choices=PATRIMONY_CHOICE, verbose_name="Catégorie")
|
||
|
ptype = models.IntegerField(choices=PATRIMONY_TYPE_CHOICE, verbose_name="Type")
|
||
|
|
||
|
def __str__(self):
|
||
|
return "%s" % (self.label,)
|
||
|
|
||
|
|
||
|
class Patrimony(Comptability):
|
||
|
"""Classe représentant un avoir ou un dette. """
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "4. Patrimoine"
|
||
|
verbose_name_plural = "4. Patrimoines"
|
||
|
|
||
|
patrimony_type = models.ForeignKey(
|
||
|
"PatrimonyType", verbose_name="Type", default=0, on_delete=models.SET_DEFAULT
|
||
|
) # effacer le default
|
||
|
|
||
|
|
||
|
class RightEngagementType(models.Model):
|
||
|
"""Classe représentant les Droits et Engagements d'une ASBL. """
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "Type de Droits et engagements"
|
||
|
verbose_name_plural = "Types de Droits et engagements"
|
||
|
ordering = [
|
||
|
"label",
|
||
|
]
|
||
|
|
||
|
CATEGORY_CHOICE = (
|
||
|
(0, "Droit"),
|
||
|
(1, "Engagement"),
|
||
|
)
|
||
|
|
||
|
TYPE_CHOICE = (
|
||
|
(0, "Subsides promis"), # Droits
|
||
|
(1, "Don promis"), # Droits
|
||
|
(2, "Autres droits"), # Droits
|
||
|
(3, "Hypothèque et promesses d'hyp."), # Engagements
|
||
|
(4, "Garanties données"), # Engagements
|
||
|
(5, "Autres engagements"), # Engagements
|
||
|
)
|
||
|
|
||
|
order = models.IntegerField()
|
||
|
label = models.CharField(max_length=255)
|
||
|
category = models.IntegerField(choices=CATEGORY_CHOICE)
|
||
|
RightEngagementType_type = models.IntegerField(choices=TYPE_CHOICE)
|
||
|
|
||
|
def __str__(self):
|
||
|
return "%s (%s)" % (self.label, self.get_RightEngagementType_type_display())
|
||
|
|
||
|
|
||
|
class RightEngagement(Comptability):
|
||
|
"""Classe représentant une ligne de droits et devoirs. """
|
||
|
|
||
|
class Meta:
|
||
|
verbose_name = "5. Droits et engagements"
|
||
|
verbose_name_plural = "5. Droits et engagements"
|
||
|
|
||
|
r_e_type = models.ForeignKey(
|
||
|
RightEngagementType, verbose_name="Type", on_delete=models.CASCADE
|
||
|
)
|
||
|
|
||
|
def __str__(self):
|
||
|
return "%s" % self.counterpart
|