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
示例#2
0
class IBANTestModel(Model):
    iban = IBANField("IBAN")
示例#3
0
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", )
示例#5
0
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")),
        )