\documentclass[10pt,xcolor={table,dvipsnames},t, aspectratio=169]{beamer} \usepackage{minted} \usetheme{rmrf} \title{Re2o et les cotisations} \subtitle{Rézo Rennes et Metz Fédérés} \author{Klafyvel} \date{10/07/2021} \begin{document} \begin{frame} \titlepage \end{frame} \begin{frame}{Sommaire} \tableofcontents \end{frame} \begin{frame} \vspace{2cm} \centering{\Huge\textbf{Contrainte principale :}\\Ne pas faire de la merde avec les sous des adhérents.} \end{frame} \section{Les principales structures de données} \begin{frame}[fragile]{L'objet Facture} \begin{columns} \begin{column}{0.5\textwidth} \begin{itemize} \item<1-> Représente une facture; \item<2-> Visible par les adhérents; \item<3-> Est censé être controlée par les trésoriers. \end{itemize} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} class Facture(BaseInvoice): user = models.ForeignKey("users.User",...) paiement = models.ForeignKey("Paiement", ...) banque = models.ForeignKey("Banque", ...) cheque = models.CharField(...) valid = models.BooleanField(...) control = models.BooleanField(...) \end{minted} \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{L'objet Vente} \begin{columns} \begin{column}{0.5\textwidth} \begin{itemize} \item Représente une vente de \verb|number| articles(s) au prix unitaire \verb|prix|; \item Stocke une information sur la durée d'adhésion / cotisation. \end{itemize} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} class Vente(RevMixin, AclMixin, models.Model): facture = models.ForeignKey("BaseInvoice", ...) number = models.IntegerField(...) name = models.CharField(...) prix = models.DecimalField(...) duration_connection = models.PositiveIntegerField(...) duration_days_connection = models.PositiveIntegerField(...) duration_membership = models.PositiveIntegerField(...) duration_days_membership = models.PositiveIntegerField(...) \end{minted} \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{L'objet Article} \begin{columns} \begin{column}{0.5\textwidth} \begin{itemize} \item Une recette pour créer des \verb|Vente|s; \item On ne lie pas directement les ventes aux articles, on peut supprimer les articles sans contrainte. \end{itemize} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} class Article(RevMixin, AclMixin, models.Model): name = models.CharField(...) prix = models.DecimalField(...) duration_membership = models.PositiveIntegerField(...) duration_days_membership = models.PositiveIntegerField(...) duration_connection = models.PositiveIntegerField(...) duration_days_connection = models.PositiveIntegerField(...) need_membership = models.BooleanField(...) type_user = models.CharField(...) available_for_everyone = models.BooleanField(...) \end{minted} \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{L'objet Cotisation} \begin{columns} \begin{column}{0.5\textwidth} \begin{itemize} \item Information sur les adhésions / cotisations des utilisateurs; \item C’est ce qui est utilisé par l’objet \verb|User| pour savoir si un utilisateur est adhérent. \end{itemize} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} class Cotisation(RevMixin, AclMixin, models.Model): vente = models.OneToOneField("Vente",...) date_start_con = models.DateTimeField(...) date_end_con = models.DateTimeField(...) date_start_memb = models.DateTimeField(...) date_end_memb = models.DateTimeField(...) \end{minted} \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{L'objet Paiement} \begin{columns} \begin{column}{0.5\textwidth} \begin{itemize} \item Enregistre un moyen de paiement; \item On peut y brancher des méthodes de paiement personnalisées. \end{itemize} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} class Paiement(RevMixin, AclMixin, models.Model): moyen = models.CharField(...) available_for_everyone = models.BooleanField(...) is_balance = models.BooleanField(...) \end{minted} \end{column} \end{columns} \end{frame} \section{La vue new\_facture} \begin{frame}[fragile] \begin{columns} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} @login_required @can_create(Facture) @can_edit(User) def new_facture(request, user, userid): """De la doc...""" invoice = Facture(user=user) article_list = Article.objects.filter(...) invoice_form = FactureForm(...) article_formset = formset_factory(SelectArticleForm)(...) if invoice_form.is_valid() and article_formset.is_valid(): # On verra après # Gestion du solde p = Paiement.objects.filter(is_balance=True) if len(p) and p[0].can_use_payment(request.user): balance = user.solde else: balance = None return form(...) \end{minted} \end{column} \begin{column}{0.5\textwidth} Vue assez classique de formulaire Django. \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{À l'intérieur de ce if} \begin{minted}[fontsize=\footnotesize,]{python} new_invoice_instance = invoice_form.save(commit=False) articles = article_formset # Check if at least one article has been selected if any(art.cleaned_data for art in articles): # Encore un peu de patience, slide suivante else : messages.error(request, _("You need to choose at least one article.")) \end{minted} \end{frame} \begin{frame} \vspace{2cm} \centering{\Huge\textbf{Roulement de tambours...}} \end{frame} \begin{frame}[fragile] \begin{columns} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} # Building a purchase for each article sold purchases = [] total_price = 0 for art_item in articles: if art_item.cleaned_data: article = art_item.cleaned_data["article"] quantity = art_item.cleaned_data["quantity"] total_price += article.prix * quantity new_purchase = Vente(...) purchases.append(new_purchase) p = find_payment_method(new_invoice_instance.paiement) if hasattr(p, "check_price"): price_ok, msg = p.check_price(total_price, user) invoice_form.add_error(None, msg) else: price_ok = True \end{minted} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} if price_ok: new_invoice_instance.save() # Saving purchases so the invoice can find them. Invoice # will modify them after being validated to put the right dates. for p in purchases: p.facture = new_invoice_instance p.save() return new_invoice_instance.paiement.end_payment( new_invoice_instance, request ) \end{minted} \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{Le retour du modèle Paiement} \begin{columns} \begin{column}{0.5\textwidth} Pour finir un paiement, \begin{itemize} \item Si une méthode de paiement personnalisée existe et que la transisition n'est pas explicitement interdite, on branche; \item Sinon on valide la facture, on affiche un message et on redirige. \end{itemize} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} def end_payment(self, invoice, request, use_payment_method=True): payment_method = find_payment_method(self) if payment_method is not None \ and use_payment_method: return payment_method.end_payment(invoice, request) # So make this invoice valid, trigger send mail invoice.valid = True invoice.save(request=request) # Du code pas intéressant return redirect(...) \end{minted} \end{column} \end{columns} \end{frame} \section{Un exemple simple : FreePayment} \begin{frame}[fragile] \begin{columns} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} class FreePayment(PaymentMethodMixin, models.Model): """...""" class Meta: verbose_name = _("Free payment") payment = models.OneToOneField( Paiement, on_delete=models.CASCADE, related_name="payment_method_free", editable=False, ) \end{minted} \end{column} \begin{column}{0.5\textwidth} \begin{minted}[fontsize=\footnotesize,]{python} def end_payment(self, invoice, request): """Ends the payment normally. """ return invoice.paiement.end_payment(invoice, request, use_payment_method=False ) def check_price(self, price, user, *args, **kwargs): """Checks that the price meets the requirement to be paid with user balance. """ return ( price == 0, _("You can't pay this invoice for free.") ) \end{minted} \end{column} \end{columns} \end{frame} \section{Un exemple intéressant : ComnPay} \begin{frame}{Déroulé} \begin{itemize} \item<2-> Le end\_payment construit une requête spéciale pour que l'utilisateur l'envoie vers comnpay. La facture est invalide; \item<3-> L'utilisateur fait son affaire avec ComnPay; \item<4-> ComnPay notifie re2o que le paiement a été effectué. La facture est validée; \item<5-> L'utilisateur est redirigé vers re2o. \end{itemize} \end{frame} \begin{frame} \vspace{2cm} \centering{\Huge\textbf{Un petit tour dans le code.}} \end{frame} \section{Conclusion} \begin{frame}{Conclusion} \begin{itemize} \item Le système de paiement est modulable; \item Mais du coup il est complexe; \item Pour intégrer HelloAsso il faudrait s'inspirer du système ComnPay. \end{itemize} \end{frame} \end{document}