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