class Fournisseur(LocationMixin, TimeStampedModel): """Ce modèle permet d'enregistrer des fournisseurs récurrents. Un fournisseur peut posséder une adresse, un IBAN pour réaliser des virements, et des informations de contact. """ nom = models.CharField(verbose_name="Nom du fournisseur", blank=False, max_length=100) description = models.TextField(verbose_name="Description", blank=True) iban = IBANField(verbose_name="IBAN du fournisseur", blank=True) contact_phone = PhoneNumberField(verbose_name="Numéro de téléphone", blank=True) contact_email = models.EmailField(verbose_name="Adresse email", blank=True) def __str__(self): return self.nom
class IBANTestModel(Model): iban = IBANField("IBAN")
class SpendingRequest(HistoryMixin, TimeStampedModel): DIFFED_FIELDS = [ "title", "event", "category", "category_precisions", "explanation", "amount", "spending_date", "provider", "iban", ] STATUS_DRAFT = "D" STATUS_AWAITING_GROUP_VALIDATION = "G" STATUS_AWAITING_REVIEW = "R" STATUS_AWAITING_SUPPLEMENTARY_INFORMATION = "I" STATUS_VALIDATED = "V" STATUS_TO_PAY = "T" STATUS_PAID = "P" STATUS_REFUSED = "B" STATUS_CHOICES = ( (STATUS_DRAFT, _("Brouillon à compléter")), ( STATUS_AWAITING_GROUP_VALIDATION, _("En attente de validation par un autre animateur"), ), ( STATUS_AWAITING_REVIEW, _("En attente de vérification par l'équipe de suivi des questions financières" ), ), ( STATUS_AWAITING_SUPPLEMENTARY_INFORMATION, _("Informations supplémentaires requises"), ), (STATUS_VALIDATED, _("Validée, en attente des fonds")), (STATUS_TO_PAY, _("Décomptée de l'allocation du groupe, à payer")), (STATUS_PAID, _("Payée")), (STATUS_REFUSED, _("Cette demande a été refusée")), ) STATUS_NEED_ACTION = { STATUS_DRAFT, STATUS_AWAITING_GROUP_VALIDATION, STATUS_AWAITING_SUPPLEMENTARY_INFORMATION, STATUS_VALIDATED, } STATUS_ADMINISTRATOR_ACTION = {STATUS_AWAITING_REVIEW, STATUS_TO_PAY} STATUS_EDITION_MESSAGES = { STATUS_AWAITING_REVIEW: "Votre requête a déjà été transmise ! Si vous l'éditez, il vous faudra la retransmettre à nouveau.", STATUS_VALIDATED: "Votre requête a déjà été validée par l'équipe de suivi des questions financières. Si vous l'éditez, il vous faudra recommencer le processus de validation.", } CATEGORY_HARDWARE = "H" CATEGORY_VENUE = "V" CATEGORY_SERVICE = "S" CATEGORY_OTHER = "O" CATEGORY_CHOICES = ( (CATEGORY_HARDWARE, _("Matériel militant")), (CATEGORY_VENUE, _("Location d'une salle")), (CATEGORY_SERVICE, _("Prestation de service")), (CATEGORY_SERVICE, _("Autres")), ) HISTORY_MESSAGES = { STATUS_DRAFT: "Création de la demande", STATUS_AWAITING_GROUP_VALIDATION: "Validé par l'auteur d'origine", STATUS_AWAITING_REVIEW: "Renvoyé pour validation à l'équipe de suivi des questions financières", STATUS_AWAITING_SUPPLEMENTARY_INFORMATION: "Informations supplémentaires requises", STATUS_VALIDATED: "Demande validée par l'équipe de suivi des questions financières", STATUS_TO_PAY: "Demande en attente de réglement", STATUS_PAID: "Demande réglée", STATUS_REFUSED: "Demande rejetée par l'équipe de suivi des questions financières", ( STATUS_AWAITING_GROUP_VALIDATION, STATUS_AWAITING_REVIEW, ): "Validé par un⋅e second⋅e animateur⋅rice", } for status, label in STATUS_CHOICES: HISTORY_MESSAGES[(status, status)] = "Modification de la demande" id = models.UUIDField(_("Identifiant"), primary_key=True, default=uuid.uuid4) title = models.CharField(_("Titre"), max_length=200) status = models.CharField(_("Statut"), max_length=1, default=STATUS_DRAFT, choices=STATUS_CHOICES) operation = models.ForeignKey(Operation, on_delete=models.PROTECT, related_name="spending_request", null=True) group = models.ForeignKey( "groups.SupportGroup", on_delete=models.PROTECT, related_name="spending_requests", related_query_name="spending_request", blank=False, null=False, ) event = models.ForeignKey( "events.Event", verbose_name=_("Événement lié à la dépense"), on_delete=models.SET_NULL, related_name="spending_requests", related_query_name="spending_request", blank=True, null=True, help_text=_( "Si c'est pertinent, l'événement concerné par la dépense. Il doit être organisé par le groupe pour" " pouvoir être sélectionné."), ) category = models.CharField( _("Catégorie de demande"), max_length=1, blank=False, null=False, choices=CATEGORY_CHOICES, ) category_precisions = models.CharField( _("Précisions sur le type de demande"), max_length=260, blank=False, null=False) explanation = models.TextField( _("Justification de la demande"), max_length=1500, help_text= _("Merci de justifier votre demande. Longueur conseillée : 500 signes." ), ) amount = AmountField( _("Montant de la dépense"), null=False, blank=False, help_text= _("Pour que cette demande soit payée, la somme allouée à votre groupe doit être suffisante." ), ) spending_date = models.DateField( _("Date de la dépense"), blank=False, null=False, help_text= _("Si la dépense n'a pas encore été effectuée, merci d'indiquer la date probable à laquelle elle surviendra." ), ) provider = models.CharField(_("Raison sociale du prestataire"), blank=False, null=False, max_length=200) iban = IBANField( _("RIB (format IBAN)"), blank=False, null=False, help_text=_( "Indiquez le RIB du prestataire s'il s'agit d'un réglement, ou le RIB de la personne qui a payé s'il s'agit" " d'un remboursement."), allowed_countries=["FR"], ) payer_name = models.CharField( _("Nom de la personne qui a payé"), blank=True, max_length=200, help_text= "S'il s'agit du remboursement d'une dépense déjà faite, indiquez le nom de la personne qui a payé" " et à qui l'IBAN correspond. Sinon, laissez vide.", ) class Meta: permissions = (("review_spendingrequest", _("Peut traiter les demandes de dépenses")), ) verbose_name = "Demande de dépense ou remboursement" verbose_name_plural = "Demandes de dépense ou remboursement" # noinspection PyMethodOverriding @classmethod def get_history_step(cls, old, new, *, admin=False, **kwargs): old_fields = old.field_dict if old else {} new_fields = new.field_dict old_status, new_status = old_fields.get("status"), new_fields["status"] revision = new.revision person = revision.user.person if revision and revision.user else None res = { "modified": new_fields["modified"], "comment": revision.get_comment(), "diff": cls.get_diff(old_fields, new_fields) if old_fields else [], } if person and admin: res["user"] = format_html( '<a href="{url}">{text}</a>', url=reverse("admin:people_person_change", args=[person.pk]), text=person.get_short_name(), ) elif person: res["user"] = person.get_short_name() else: res["user"] = "******" # cas spécifique : si on revient à "attente d'informations supplémentaires suite à une modification par un non admin # c'est forcément une modification if (new_status == cls.STATUS_AWAITING_SUPPLEMENTARY_INFORMATION and person is not None): res["title"] = "Modification de la demande" # some couples (old_status, new_status) elif (old_status, new_status) in cls.HISTORY_MESSAGES: res["title"] = cls.HISTORY_MESSAGES[(old_status, new_status)] else: res["title"] = cls.HISTORY_MESSAGES.get( new_status, "[Modification non identifiée]") return res
class Reglement(TimeStampedModel): class Statut(models.TextChoices): ATTENTE = "C", "En cours" REGLE = "R", "Réglé" RAPPROCHE = "P", "Rapproché" class Mode(models.TextChoices): VIREMENT = "V", "Par virement" PRELEV = "P", "Par prélèvement" CHEQUE = "C", "Par chèque" CARTE = "A", "Par carte bancaire" CASH = "S", "En espèces" depense = models.ForeignKey( to="Depense", verbose_name="Dépense concernée", related_name="reglements", related_query_name="reglement", on_delete=models.PROTECT, ) intitule = models.CharField(verbose_name="Intitulé du réglement", max_length=200, blank=False) mode = models.CharField( verbose_name="Mode de réglement", max_length=1, choices=Mode.choices, blank=False, ) montant = models.DecimalField( verbose_name="Montant du règlement", decimal_places=2, null=False, max_digits=10, ) date = models.DateField( verbose_name="Date du règlement", blank=False, null=False, default=timezone.now, ) preuve = models.ForeignKey( to="Document", verbose_name="Preuve de paiement", null=True, blank=True, on_delete=models.PROTECT, ) statut = models.CharField(max_length=1, blank=False, choices=Statut.choices, default=Statut.ATTENTE) # lien vers le fournisseur fournisseur = models.ForeignKey( to="Fournisseur", null=True, blank=True, on_delete=models.SET_NULL, ) # informations fournisseurs nom_fournisseur = models.CharField(verbose_name="Nom du fournisseur", blank=False, max_length=100) iban_fournisseur = IBANField(verbose_name="IBAN du fournisseur", blank=True) contact_phone_fournisseur = PhoneNumberField( verbose_name="Numéro de téléphone", blank=True) contact_email_fournisseur = models.EmailField(verbose_name="Adresse email", blank=True) location_address1_fournisseur = models.CharField("adresse (1ère ligne)", max_length=100, blank=True) location_address2_fournisseur = models.CharField("adresse (2ère ligne)", max_length=100, blank=True) location_city_fournisseur = models.CharField("ville", max_length=100, blank=False) location_zip_fournisseur = models.CharField("code postal", max_length=20, blank=False) location_country_fournisseur = CountryField( "pays", blank_label="(sélectionner un pays)", default="FR", blank=False) search_config = ( ("numero", "B"), ("titre", "A"), ("description", "B"), ) class Meta: verbose_name = "règlement" ordering = ("date", )
class SpendingRequest(TimeStampedModel): STATUS_DRAFT = "D" STATUS_AWAITING_GROUP_VALIDATION = "G" STATUS_AWAITING_REVIEW = "R" STATUS_AWAITING_SUPPLEMENTARY_INFORMATION = "I" STATUS_VALIDATED = "V" STATUS_TO_PAY = "T" STATUS_PAID = "P" STATUS_REFUSED = "B" STATUS_CHOICES = ( (STATUS_DRAFT, _("Brouillon à compléter")), ( STATUS_AWAITING_GROUP_VALIDATION, _("En attente de validation par un autre animateur"), ), ( STATUS_AWAITING_REVIEW, _( "En attente de vérification par l'équipe de suivi des questions financières" ), ), ( STATUS_AWAITING_SUPPLEMENTARY_INFORMATION, _("Informations supplémentaires requises"), ), (STATUS_VALIDATED, _("Validée, en attente des fonds")), (STATUS_TO_PAY, _("Décomptée de l'allocation du groupe, à payer")), (STATUS_PAID, _("Payée")), (STATUS_REFUSED, _("Cette demande a été refusée")), ) STATUS_NEED_ACTION = { STATUS_DRAFT, STATUS_AWAITING_GROUP_VALIDATION, STATUS_AWAITING_SUPPLEMENTARY_INFORMATION, STATUS_VALIDATED, } STATUS_ADMINISTRATOR_ACTION = {STATUS_AWAITING_REVIEW, STATUS_TO_PAY} STATUS_EDITION_MESSAGES = { STATUS_AWAITING_REVIEW: "Votre requête a déjà été transmise ! Si vous l'éditez, il vous faudra la retransmettre à nouveau.", STATUS_VALIDATED: "Votre requête a déjà été validée par l'équipe de suivi des questions financières. Si vous l'éditez, il vous faudra recommencer le processus de validation.", } CATEGORY_HARDWARE = "H" CATEGORY_VENUE = "V" CATEGORY_SERVICE = "S" CATEGORY_OTHER = "O" CATEGORY_CHOICES = ( (CATEGORY_HARDWARE, _("Matériel militant")), (CATEGORY_VENUE, _("Location d'une salle")), (CATEGORY_SERVICE, _("Prestation de service")), (CATEGORY_SERVICE, _("Autres")), ) id = models.UUIDField(_("Identifiant"), primary_key=True, default=uuid.uuid4) title = models.CharField(_("Titre"), max_length=200) status = models.CharField( _("Statut"), max_length=1, default=STATUS_DRAFT, choices=STATUS_CHOICES ) operation = models.ForeignKey( Operation, on_delete=models.PROTECT, related_name="spending_request", null=True ) group = models.ForeignKey( "groups.SupportGroup", on_delete=models.PROTECT, related_name="spending_requests", related_query_name="spending_request", blank=False, null=False, ) event = models.ForeignKey( "events.Event", verbose_name=_("Événement lié à la dépense"), on_delete=models.SET_NULL, related_name="spending_requests", related_query_name="spending_request", blank=True, null=True, help_text=_( "Si c'est pertinent, l'événement concerné par la dépense. Il doit être organisé par le groupe pour" " pouvoir être sélectionné." ), ) category = models.CharField( _("Catégorie de demande"), max_length=1, blank=False, null=False, choices=CATEGORY_CHOICES, ) category_precisions = models.CharField( _("Précisions sur le type de demande"), max_length=260, blank=False, null=False ) explanation = models.TextField( _("Justification de la demande"), max_length=1500, help_text=_( "Merci de justifier votre demande. Longueur conseillée : 500 signes." ), ) amount = AmountField( _("Montant de la dépense"), null=False, blank=False, help_text=_( "Pour que cette demande soit payée, la somme allouée à votre groupe doit être suffisante." ), ) spending_date = models.DateField( _("Date de la dépense"), blank=False, null=False, help_text=_( "Si la dépense n'a pas encore été effectuée, merci d'indiquer la date probable à laquelle elle surviendra." ), ) provider = models.CharField( _("Raison sociale du prestataire"), blank=False, null=False, max_length=200 ) iban = IBANField( _("RIB (format IBAN)"), blank=False, null=False, help_text=_( "Indiquez le RIB du prestataire s'il s'agit d'un réglement, ou le RIB de la personne concernée s'il s'agit" " d'un remboursement." ), allowed_countries=["FR"], ) class Meta: permissions = ( ("review_spendingrequest", _("Peut traiter les demandes de dépenses")), )