commit f3df8d70e1789f0966fb6993c41f2df540dde20d Author: Hugo Levy-Falk Date: Sat Jul 10 21:42:45 2021 +0200 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e06903f --- /dev/null +++ b/.gitignore @@ -0,0 +1,302 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/latex +# Edit at https://www.toptal.com/developers/gitignore?templates=latex + +### LaTeX ### +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +## Intermediate documents: +*.dvi +*.xdv +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Generated if empty string is given at "Please type another file name for output:" +.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex(busy) +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync + +## Build tool directories for auxiliary files +# latexrun +latex.out/ + +## Auxiliary and intermediate files from other packages: +# algorithms +*.alg +*.loa + +# achemso +acs-*.bib + +# amsthm +*.thm + +# beamer +*.nav +*.pre +*.snm +*.vrb + +# changes +*.soc + +# comment +*.cut + +# cprotect +*.cpt + +# elsarticle (documentclass of Elsevier journals) +*.spl + +# endnotes +*.ent + +# fixme +*.lox + +# feynmf/feynmp +*.mf +*.mp +*.t[1-9] +*.t[1-9][0-9] +*.tfm + +#(r)(e)ledmac/(r)(e)ledpar +*.end +*.?end +*.[1-9] +*.[1-9][0-9] +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# glossaries +*.acn +*.acr +*.glg +*.glo +*.gls +*.glsdefs +*.lzo +*.lzs + +# uncomment this for glossaries-extra (will ignore makeindex's style files!) +# *.ist + +# gnuplottex +*-gnuplottex-* + +# gregoriotex +*.gaux +*.glog +*.gtex + +# htlatex +*.4ct +*.4tc +*.idv +*.lg +*.trc +*.xref + +# hyperref +*.brf + +# knitr +*-concordance.tex +# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files +# *.tikz +*-tikzDictionary + +# listings +*.lol + +# luatexja-ruby +*.ltjruby + +# makeidx +*.idx +*.ilg +*.ind + +# minitoc +*.maf +*.mlf +*.mlt +*.mtc[0-9]* +*.slf[0-9]* +*.slt[0-9]* +*.stc[0-9]* + +# minted +_minted* +*.pyg + +# morewrites +*.mw + +# newpax +*.newpax + +# nomencl +*.nlg +*.nlo +*.nls + +# pax +*.pax + +# pdfpcnotes +*.pdfpc + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# scrwfile +*.wrt + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +# pdfcomment +*.upa +*.upb + +# pythontex +*.pytxcode +pythontex-files-*/ + +# tcolorbox +*.listing + +# thmtools +*.loe + +# TikZ & PGF +*.dpth +*.md5 +*.auxlock + +# todonotes +*.tdo + +# vhistory +*.hst +*.ver + +# easy-todo +*.lod + +# xcolor +*.xcp + +# xmpincl +*.xmpi + +# xindy +*.xdy + +# xypic precompiled matrices and outlines +*.xyc +*.xyd + +# endfloat +*.ttt +*.fff + +# Latexian +TSWLatexianTemp* + +## Editors: +# WinEdt +*.bak +*.sav + +# Texpad +.texpadtmp + +# LyX +*.lyx~ + +# Kile +*.backup + +# gummi +.*.swp + +# KBibTeX +*~[0-9]* + +# TeXnicCenter +*.tps + +# auto folder when using emacs and auctex +./auto/* +*.el + +# expex forward references with \gathertags +*-tags.tex + +# standalone packages +*.sta + +# Makeindex log files +*.lpz + +# xwatermark package +*.xwm + +# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib +# option is specified. Footnotes are the stored in a file with suffix Notes.bib. +# Uncomment the next line to have this generated file ignored. +#*Notes.bib + +### LaTeX Patch ### +# LIPIcs / OASIcs +*.vtc + +# glossaries +*.glstex + +# End of https://www.toptal.com/developers/gitignore/api/latex diff --git a/.latexmkrc b/.latexmkrc new file mode 100644 index 0000000..4690487 --- /dev/null +++ b/.latexmkrc @@ -0,0 +1,24 @@ +# LaTeXmk configuration file + +# Usage example +# latexmk file.tex + +# Main command line options +# -pdf : generate pdf using pdflatex +# -pv : run file previewer +# -pvc : run file previewer and continually recompile on change +# -C : clean up by removing all regeneratable files + +# Generate pdf using pdflatex (-pdf) +$pdf_mode = 1; + +# Define command to compile with pdfsync support and nonstopmode +$pdflatex = 'lualatex -shell-escape'; + +# Use default pdf viewer (Skim) +$pdf_previewer = 'evince'; + +# Also remove pdfsync files on clean +$clean_ext = 'pdfsync synctex.gz'; + +$ENV{'TEXINPUTS'}='/home/klafyvel/Documents/asso/rezo/formations/SystèmePaiementRe2o/rmrf-latex:' . $ENV{'TEXINPUTS'}; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..89d6569 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +# Makefile for presentation + +.PHONY: presentation.pdf all clean + +all: presentation.pdf + +presentation.pdf: presentation.tex + latexmk -use-make $< + +cleanall: + latexmk -C + +clean: + latexmk -c \ No newline at end of file diff --git a/presentation.pdf b/presentation.pdf new file mode 100644 index 0000000..8339e2e Binary files /dev/null and b/presentation.pdf differ diff --git a/presentation.tex b/presentation.tex new file mode 100644 index 0000000..d13d75d --- /dev/null +++ b/presentation.tex @@ -0,0 +1,326 @@ +\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}