ComptaClub/src/comptabilite/models.py

364 lines
11 KiB
Python

# coding=utf-8
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):
"""
"""
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 une chiffre (e.g. 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):
return "%s (%s - %s)" % (self.year, self.opening_balance, self.closing_balance)
class ComptabilityManager(models.Manager):
"""
"""
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):
""" """
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):
""" """
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):
""" """
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
(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):
"""
"""
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,)