327 lines
9.8 KiB
TeX
327 lines
9.8 KiB
TeX
\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}
|