8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-08 19:06:25 +00:00

Merge branch 'Fix_81_Create_custom_invoice' into 'master'

Fix #81 and improve the selection of articles in custom invoices

Closes #81

See merge request federez/re2o!123
This commit is contained in:
chirac 2018-04-11 23:00:43 +02:00
commit aa8129a4ad
6 changed files with 225 additions and 92 deletions

View file

@ -145,25 +145,11 @@ class NewFactureFormPdf(Form):
""" """
Form used to create a custom PDF invoice. Form used to create a custom PDF invoice.
""" """
article = forms.ModelMultipleChoiceField(
queryset=Article.objects.all(),
label=_l("Article")
)
number = forms.IntegerField(
label=_l("Quantity"),
validators=[MinValueValidator(1)]
)
paid = forms.BooleanField(label=_l("Paid"), required=False) paid = forms.BooleanField(label=_l("Paid"), required=False)
# TODO : change dest field to recipient # TODO : change dest field to recipient
dest = forms.CharField(required=True, max_length=255, label=_l("Recipient")) dest = forms.CharField(required=True, max_length=255, label=_l("Recipient"))
# TODO : change chambre field to address # TODO : change chambre field to address
chambre = forms.CharField(required=False, max_length=10, label=_l("Address")) chambre = forms.CharField(required=False, max_length=10, label=_l("Address"))
# TODO : change fid field to invoice_id
fid = forms.CharField(
required=True,
max_length=10,
label=_l("Invoice number")
)
# TODO : change Facture to Invoice # TODO : change Facture to Invoice
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm): class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):

View file

@ -65,12 +65,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</th> </th>
<th> <th>
{% trans "Date" as tr_date %} {% trans "Date" as tr_date %}
{% include 'buttons/sort.html' with prefix='control' col='date' text=tr_date %}< {% include 'buttons/sort.html' with prefix='control' col='date' text=tr_date %}i
/th> </th>
<th> <th>
{% trans "Validated" as tr_validated %} {% trans "Validated" as tr_validated %}
{% include 'buttons/sort.html' with prefix='control' col='valid' text=tr_validated %}< {% include 'buttons/sort.html' with prefix='control' col='valid' text=tr_validated %}
/th> </th>
<th> <th>
{% trans "Controlled" as tr_controlled %} {% trans "Controlled" as tr_controlled %}
{% include 'buttons/sort.html' with prefix='control' col='control' text=tr_controlled %} {% include 'buttons/sort.html' with prefix='control' col='control' text=tr_controlled %}

View file

@ -34,8 +34,105 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}
<h3>{% trans "Invoice's articles" %}</h3>
<div id="form_set" class="form-group">
{{ articlesformset.management_form }}
{% for articlesform in articlesformset.forms %}
<div class='product_to_sell form-inline'>
{% trans "Article" %} : &nbsp;
{% bootstrap_form articlesform label_class='sr-only' %}
&nbsp;
<button class="btn btn-danger btn-sm" id="id_form-0-article-remove" type="button">
<span class="fa fa-times"></span>
</button>
</div>
{% endfor %}
</div>
<input class="btn btn-primary btn-sm" role="button" value="{% trans "Add an article"%}" id="add_one">
<p>
{% blocktrans %}
Total price : <span id="total_price">0,00</span>
{% endblocktrans %}
</p>
{% bootstrap_form factureform %} {% bootstrap_form factureform %}
{% bootstrap_button action_name button_type='submit' icon='star' %} {% bootstrap_button action_name button_type='submit' icon='star' %}
</form> </form>
<script type="text/javascript">
var prices = {};
{% for article in articles %}
prices[{{ article.id|escapejs }}] = {{ article.prix }};
{% endfor %}
var template = `Article : &nbsp;
{% bootstrap_form articlesformset.empty_form label_class='sr-only' %}
&nbsp;
<button class="btn btn-danger btn-sm"
id="id_form-__prefix__-article-remove" type="button">
<span class="fa fa-times"></span>
</button>`
function add_article(){
// Index start at 0 => new_index = number of items
var new_index =
document.getElementsByClassName('product_to_sell').length;
document.getElementById('id_form-TOTAL_FORMS').value ++;
var new_article = document.createElement('div');
new_article.className = 'product_to_sell form-inline';
new_article.innerHTML = template.replace(/__prefix__/g, new_index);
document.getElementById('form_set').appendChild(new_article);
add_listenner_for_id(new_index);
}
function update_price(){
var price = 0;
var product_count =
document.getElementsByClassName('product_to_sell').length;
var article, article_price, quantity;
for (i = 0; i < product_count; ++i){
article = document.getElementById(
'id_form-' + i.toString() + '-article').value;
if (article == '') {
continue;
}
article_price = prices[article];
quantity = document.getElementById(
'id_form-' + i.toString() + '-quantity').value;
price += article_price * quantity;
}
document.getElementById('total_price').innerHTML =
price.toFixed(2).toString().replace('.', ',');
}
function add_listenner_for_id(i){
document.getElementById('id_form-' + i.toString() + '-article')
.addEventListener("change", update_price, true);
document.getElementById('id_form-' + i.toString() + '-article')
.addEventListener("onkeypress", update_price, true);
document.getElementById('id_form-' + i.toString() + '-quantity')
.addEventListener("change", update_price, true);
document.getElementById('id_form-' + i.toString() + '-article-remove')
.addEventListener("click", function(event) {
var article = event.target.parentNode;
article.parentNode.removeChild(article);
document.getElementById('id_form-TOTAL_FORMS').value --;
update_price();
})
}
// Add events manager when DOM is fully loaded
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("add_one")
.addEventListener("click", add_article, true);
var product_count =
document.getElementsByClassName('product_to_sell').length;
for (i = 0; i < product_count; ++i){
add_listenner_for_id(i);
}
update_price();
});
</script>
{% endblock %} {% endblock %}

View file

@ -32,76 +32,97 @@
\usepackage{graphicx} \usepackage{graphicx}
\usepackage{tabularx} \usepackage{tabularx}
\usepackage{eurosym} \usepackage{eurosym}
\usepackage{multicol}
\pagestyle{empty} % No page numbers \pagestyle{empty} % No page numbers
\linespread{1.5} % Line spacing \linespread{1.5} % Line spacing
\setlength{\doublerulesep}{\arrayrulewidth} % Double rules look like one thick one \newcommand{\doublehline}{\noalign{\hrule height 1pt}}
\def \tab {\hspace*{3ex}} % Define \tab to create some horizontal white space
\setlength{\parindent}{0cm} \setlength{\parindent}{0cm}
\begin{document} \begin{document}
%\newcommand{\product}[5][0][0][0][0][0]{
%\setlength{ptotal}{#3*\real{#4}}
%\addtolength{total}{#3*\real{#4}}
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% HEADING SECTION % HEADING SECTION
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
%\includegraphics[width=3.5cm]{% templatetag openbrace %}{{ tpl_path }}} \begin{center}
\tab \tab \tab \tab \tab {\Huge\bf {{asso_name|safe}} }\hfil % Company providing the invoice {\Huge\bf {{asso_name|safe}} } % Company providing the invoice
\bigskip\break % Whitespace \end{center}
\hrule % Horizontal line \\
\vspace{0.5cm} \bigskip
{\bf Adresse :} \tab \parbox [t] {0.5\textwidth}{ {{line1|safe}} \\ {{line2|safe}} } \hfill \parbox [t] {0.3\textwidth}{ {\bf Téléphone :} {{phone}} \\ {\bf Mail :} {{email|safe}} } \\ % Your address and contact information \hrule
\\ \smallskip
{\bf Siret :} \tab{{siret}}
\\ \\ \\ {\setlength{\tabcolsep}{0pt} % Make table columns tighter, usefull for postionning
\\ \begin{tabular}{l l}
\parbox [t] {0.5\textwidth}{ {\bf Pour :} {{dest.name|safe}} {{dest.surname|safe}} \\ {\bf Adresse :} \tab {% if dest.room == None %} Aucune adresse renseignée {% else %}{{dest.room}}{% endif %} } \hfill \parbox [t] {0.3\textwidth}{ {\bf Date :} {{DATE}} } {\bf Adresse :}~ & {{line1|safe}} \\
\\ \\ & {{line2|safe}} \\
\end{tabular}
\hfill
\begin{tabular}{r}
{\bf Téléphone :} {{phone}} \\
{\bf Mail :} {{email|safe}} \\
\end{tabular}
}
\\
{\bf Siret :} {{siret|safe}}
\vspace{2cm}
\begin{tabular*}{\textwidth}{@{\extracolsep{\fill}} l r}
{\bf Pour :} {{recipient_name|safe}} & {\bf Date :} {{DATE}} \\
{\bf Adresse :} {% if address is None %}Aucune adresse renseignée{% else %}{{address}}{% endif %} & \\
{% if fid is not None %}
{\bf Facture n\textsuperscript{o} :} {{ fid }} & \\
{% endif %}
\end{tabular*}
\\
{\bf Facture \no :} \tab {{ fid }} \\\\
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
% TABLE OF EXPENSES % TABLE OF EXPENSES
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\begin{tabularx}{\textwidth}{|X|r|r|r|} \begin{tabularx}{\textwidth}{|X|r|r|r|}
\hline \hline
\textbf{Désignation} & \textbf{Prix Unit.} \euro & \textbf{Quantité} & \textbf{Prix total} \euro\\ \textbf{Désignation} & \textbf{Prix Unit.} \euro & \textbf{Quantité} & \textbf{Prix total} \euro\\
\hline \doublehline
{% for a in article %} {% for a in article %}
\hline {{a.name}} & {{a.price}} \euro & {{a.quantity}} & {{a.total_price}} \euro\\
{{a.0.name}} & {{a.0.prix}} \euro & {{a.1}} & {{a.2}} \euro\\
\hline \hline
{% endfor %} {% endfor %}
\hline
\end{tabularx} \end{tabularx}
\vspace{1cm} \vspace{1cm}
\hfill \hfill
\begin{tabular}{|l|r|} \begin{tabular}{|l|r|}
\hline \hline
\textbf{Total} & {{total|floatformat:2}} \euro \\ \textbf{Total} & {{total|floatformat:2}} \euro \\
\textbf{Votre règlement} & {% if paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro \\ \textbf{Votre règlement} & {% if paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro \\
\hline \doublehline
\textbf{À PAYER} & {% if not paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro\\ \textbf{À PAYER} & {% if not paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro\\
\hline \hline
\hline
\end{tabular} \end{tabular}
\vspace{1.5cm} % Whitespace \vfill
\hrule % Horizontal line
\vspace{0.25cm}
%----------------------------------------------------------------------------------------
% FOOTNOTE
%----------------------------------------------------------------------------------------
\hrule
\smallskip
\footnotesize{TVA non applicable, art. 293 B du CGI} \footnotesize{TVA non applicable, art. 293 B du CGI}
{% endlanguage %}
%---------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------
\end{document} \end{document}
{% endlanguage %}

View file

@ -48,7 +48,7 @@ def render_invoice(request, ctx={}):
filename = '_'.join([ filename = '_'.join([
'invoice', 'invoice',
slugify(ctx['asso_name']), slugify(ctx['asso_name']),
slugify(ctx['dest'].pseudo), slugify(ctx['recipient_name']),
str(ctx['DATE'].year), str(ctx['DATE'].year),
str(ctx['DATE'].month), str(ctx['DATE'].month),
str(ctx['DATE'].day), str(ctx['DATE'].day),

View file

@ -198,24 +198,40 @@ def new_facture_pdf(request):
get invoices that are not taken into account, for the administrative get invoices that are not taken into account, for the administrative
point of view. point of view.
""" """
# The template needs the list of articles (for the JS part)
articles = Article.objects.filter(
Q(type_user='All') | Q(type_user=request.user.class_name)
)
# Building the invocie form and the article formset
invoice_form = NewFactureFormPdf(request.POST or None) invoice_form = NewFactureFormPdf(request.POST or None)
if invoice_form.is_valid(): if request.user.is_class_club:
tbl = [] articles_formset = formset_factory(SelectClubArticleForm)(request.POST or None)
article = facture_form.cleaned_data['article'] else:
quantity = facture_form.cleaned_data['number'] articles_formset = formset_factory(SelectUserArticleForm)(request.POST or None)
paid = facture_form.cleaned_data['paid'] if invoice_form.is_valid() and articles_formset.is_valid():
recipient = facture_form.cleaned_data['dest'] # Get the article list and build an list out of it
room = facture_form.cleaned_data['chambre'] # contiaining (article_name, article_price, quantity, total_price)
invoice_id = facture_form.cleaned_data['fid'] articles_info = []
for art in article: for articles_form in articles_formset:
tbl.append([art, quantity, art.prix * quantity]) if articles_form.cleaned_data:
total_price = sum(a[2] for a in tbl) article = articles_form.cleaned_data['article']
user = {'name': recipient, 'room': room} quantity = articles_form.cleaned_data['quantity']
articles_info.append({
'name': article.name,
'price': article.prix,
'quantity': quantity,
'total_price': article.prix * quantity
})
paid = invoice_form.cleaned_data['paid']
recipient = invoice_form.cleaned_data['dest']
address = invoice_form.cleaned_data['chambre']
total_price = sum(a['total_price'] for a in articles_info)
return render_invoice(request, { return render_invoice(request, {
'DATE': timezone.now(), 'DATE': timezone.now(),
'dest': user, 'recipient_name': recipient,
'fid': invoice_id, 'address': address,
'article': tbl, 'article': articles_info,
'total': total_price, 'total': total_price,
'paid': paid, 'paid': paid,
'asso_name': AssoOption.get_cached_value('name'), 'asso_name': AssoOption.get_cached_value('name'),
@ -228,7 +244,9 @@ def new_facture_pdf(request):
}) })
return form({ return form({
'factureform': invoice_form, 'factureform': invoice_form,
'action_name': _("Edit") 'action_name': _("Create"),
'articlesformset': articles_formset,
'articles': articles
}, 'cotisations/facture.html', request) }, 'cotisations/facture.html', request)
@ -244,15 +262,26 @@ def facture_pdf(request, facture, factureid):
""" """
# TODO : change vente to purchase # TODO : change vente to purchase
purchases_objects = Vente.objects.all().filter(facture=facture) purchases_objects = Vente.objects.all().filter(facture=facture)
purchases = [] # Get the article list and build an list out of it
# contiaining (article_name, article_price, quantity, total_price)
purchases_info = []
for purchase in purchases_objects: for purchase in purchases_objects:
purchases.append([purchase, purchase.number, purchase.prix_total]) purchases_info.append({
'name': purchase.name,
'price': purchase.prix,
'quantity': purchase.number,
'total_price': purchase.prix_total
})
return render_invoice(request, { return render_invoice(request, {
'paid': True, 'paid': True,
'fid': facture.id, 'fid': facture.id,
'DATE': facture.date, 'DATE': facture.date,
'dest': facture.user, 'recipient_name': "{} {}".format(
'article': purchases, facture.user.name,
facture.user.surname
),
'address': facture.user.room,
'article': purchases_info,
'total': facture.prix_total(), 'total': facture.prix_total(),
'asso_name': AssoOption.get_cached_value('name'), 'asso_name': AssoOption.get_cached_value('name'),
'line1': AssoOption.get_cached_value('adresse1'), 'line1': AssoOption.get_cached_value('adresse1'),