mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-25 22:22:26 +00:00
Add discount for custom invoices.
This commit is contained in:
parent
81b7a7c4af
commit
e7a7e81a2c
4 changed files with 172 additions and 98 deletions
|
@ -46,7 +46,7 @@ from django.shortcuts import get_object_or_404
|
|||
|
||||
from re2o.field_permissions import FieldPermissionFormMixin
|
||||
from re2o.mixins import FormRevMixin
|
||||
from .models import Article, Paiement, Facture, Banque, CustomInvoice
|
||||
from .models import Article, Paiement, Facture, Banque, CustomInvoice, Vente
|
||||
from .payment_methods import balance
|
||||
|
||||
|
||||
|
@ -104,7 +104,44 @@ class SelectArticleForm(FormRevMixin, Form):
|
|||
user = kwargs.pop('user')
|
||||
target_user = kwargs.pop('target_user', None)
|
||||
super(SelectArticleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(user, target_user)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(
|
||||
user, target_user)
|
||||
|
||||
|
||||
class DiscountForm(Form):
|
||||
"""
|
||||
Form used in oder to create a discount on an invoice.
|
||||
"""
|
||||
is_relative = forms.BooleanField(
|
||||
label=_("Discount is on percentage"),
|
||||
required=False,
|
||||
)
|
||||
discount = forms.DecimalField(
|
||||
label=_("Discount"),
|
||||
max_value=100,
|
||||
min_value=0,
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
required=False,
|
||||
)
|
||||
|
||||
def apply_to_invoice(self, invoice):
|
||||
invoice_price = invoice.prix_total()
|
||||
discount = self.cleaned_data['discount']
|
||||
is_relative = self.cleaned_data['is_relative']
|
||||
if is_relative:
|
||||
amount = discount/100 * invoice_price
|
||||
else:
|
||||
amount = discount
|
||||
if amount > 0:
|
||||
name = _("{}% discount") if is_relative else _("{}€ discount")
|
||||
name = name.format(discount)
|
||||
Vente.objects.create(
|
||||
facture=invoice,
|
||||
name=name,
|
||||
prix=-amount,
|
||||
number=1
|
||||
)
|
||||
|
||||
|
||||
class CustomInvoiceForm(FormRevMixin, ModelForm):
|
||||
|
@ -248,7 +285,8 @@ class RechargeForm(FormRevMixin, Form):
|
|||
super(RechargeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['payment'].empty_label = \
|
||||
_("Select a payment method")
|
||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(user_source).exclude(is_balance=True)
|
||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(
|
||||
user_source).exclude(is_balance=True)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
|
@ -266,4 +304,3 @@ class RechargeForm(FormRevMixin, Form):
|
|||
}
|
||||
)
|
||||
return self.cleaned_data
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% blocktrans %}Current balance: {{ balance }} €{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% bootstrap_form_errors factureform %}
|
||||
{% bootstrap_form_errors discount_form %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -68,8 +70,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</div>
|
||||
<input class="btn btn-primary btn-block" role="button" value="{% trans "Add an extra article"%}" id="add_one">
|
||||
<h3>{% trans "Discount" %}</h3>
|
||||
{% if discount_form %}
|
||||
{% bootstrap_form discount_form %}
|
||||
{% endif %}
|
||||
<p>
|
||||
{% blocktrans %}Total price: <span id="total_price">0,00</span> €{% endblocktrans %}
|
||||
{% blocktrans %}Total price: <span id="total_price">0,00</span> €{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% bootstrap_button action_name button_type='submit' icon='ok' button_class='btn-success' %}
|
||||
|
@ -78,105 +84,117 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if articlesformset or payment_method%}
|
||||
<script type="text/javascript">
|
||||
{% if articlesformset %}
|
||||
var prices = {};
|
||||
{% for article in articlelist %}
|
||||
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
||||
{% endfor %}
|
||||
var prices = {};
|
||||
{% for article in articlelist %}
|
||||
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
||||
{% endfor %}
|
||||
|
||||
var template = `Article :
|
||||
{% bootstrap_form articlesformset.empty_form label_class='sr-only' %}
|
||||
|
||||
<button class="btn btn-danger btn-sm"
|
||||
id="id_form-__prefix__-article-remove" type="button">
|
||||
<span class="fa fa-times"></span>
|
||||
</button>`
|
||||
var template = `Article :
|
||||
{% bootstrap_form articlesformset.empty_form label_class='sr-only' %}
|
||||
|
||||
<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 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(
|
||||
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(
|
||||
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('.', ',');
|
||||
price += article_price * quantity;
|
||||
}
|
||||
|
||||
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();
|
||||
})
|
||||
{% if discount_form %}
|
||||
var relative_discount = document.getElementById('id_is_relative').checked;
|
||||
var discount = document.getElementById('id_discount').value;
|
||||
if(relative_discount) {
|
||||
discount = discount/100 * price;
|
||||
}
|
||||
price -= discount;
|
||||
{% endif %}
|
||||
document.getElementById('total_price').innerHTML =
|
||||
price.toFixed(2).toString().replace('.', ',');
|
||||
}
|
||||
|
||||
// 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();
|
||||
});
|
||||
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);
|
||||
}
|
||||
document.getElementById('id_discount')
|
||||
.addEventListener('change', update_price, true);
|
||||
document.getElementById('id_is_relative')
|
||||
.addEventListener('click', update_price, true);
|
||||
update_price();
|
||||
});
|
||||
{% endif %}
|
||||
{% if payment_method.templates %}
|
||||
var TEMPLATES = [
|
||||
"",
|
||||
{% for t in payment_method.templates %}
|
||||
{% if t %}
|
||||
`{% bootstrap_form t %}`,
|
||||
{% else %}
|
||||
"",
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
function update_payment_method_form(){
|
||||
var method = document.getElementById('paymentMethodSelect').value;
|
||||
if(method==""){
|
||||
method=0;
|
||||
}
|
||||
else{
|
||||
method = Number(method);
|
||||
method += 1;
|
||||
}
|
||||
console.log(method);
|
||||
var html = TEMPLATES[method];
|
||||
|
||||
document.getElementById('paymentMethod').innerHTML = html;
|
||||
var TEMPLATES = [
|
||||
"",
|
||||
{% for t in payment_method.templates %}
|
||||
{% if t %}
|
||||
`{% bootstrap_form t %}`,
|
||||
{% else %}
|
||||
"",
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
function update_payment_method_form(){
|
||||
var method = document.getElementById('paymentMethodSelect').value;
|
||||
if(method==""){
|
||||
method=0;
|
||||
}
|
||||
document.getElementById("paymentMethodSelect").addEventListener("change", update_payment_method_form);
|
||||
else{
|
||||
method = Number(method);
|
||||
method += 1;
|
||||
}
|
||||
console.log(method);
|
||||
var html = TEMPLATES[method];
|
||||
|
||||
document.getElementById('paymentMethod').innerHTML = html;
|
||||
}
|
||||
document.getElementById("paymentMethodSelect").addEventListener("change", update_payment_method_form);
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
|
|
@ -36,6 +36,7 @@ from django.template import Context
|
|||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.utils.text import slugify
|
||||
import logging
|
||||
|
||||
|
||||
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
|
||||
|
@ -93,6 +94,20 @@ def create_pdf(template, ctx={}):
|
|||
return pdf
|
||||
|
||||
|
||||
def escape_chars(string):
|
||||
"""Escape the '%' and the '€' signs to avoid messing with LaTeX"""
|
||||
if not isinstance(string, str):
|
||||
return string
|
||||
mapping = (
|
||||
('€', r'\euro'),
|
||||
('%', r'\%'),
|
||||
)
|
||||
r = str(string)
|
||||
for k, v in mapping:
|
||||
r = r.replace(k, v)
|
||||
return r
|
||||
|
||||
|
||||
def render_tex(_request, template, ctx={}):
|
||||
"""Creates a PDF from a LaTex templates using pdflatex.
|
||||
|
||||
|
|
|
@ -80,9 +80,10 @@ from .forms import (
|
|||
DelBanqueForm,
|
||||
SelectArticleForm,
|
||||
RechargeForm,
|
||||
CustomInvoiceForm
|
||||
CustomInvoiceForm,
|
||||
DiscountForm
|
||||
)
|
||||
from .tex import render_invoice
|
||||
from .tex import render_invoice, escape_chars
|
||||
from .payment_methods.forms import payment_method_factory
|
||||
from .utils import find_payment_method
|
||||
|
||||
|
@ -198,8 +199,9 @@ def new_custom_invoice(request):
|
|||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
)
|
||||
discount_form = DiscountForm(request.POST or None)
|
||||
|
||||
if invoice_form.is_valid() and articles_formset.is_valid():
|
||||
if invoice_form.is_valid() and articles_formset.is_valid() and discount_form.is_valid():
|
||||
new_invoice_instance = invoice_form.save()
|
||||
for art_item in articles_formset:
|
||||
if art_item.cleaned_data:
|
||||
|
@ -213,6 +215,7 @@ def new_custom_invoice(request):
|
|||
duration=article.duration,
|
||||
number=quantity
|
||||
)
|
||||
discount_form.apply_to_invoice(new_invoice_instance)
|
||||
messages.success(
|
||||
request,
|
||||
_("The custom invoice was created.")
|
||||
|
@ -223,7 +226,8 @@ def new_custom_invoice(request):
|
|||
'factureform': invoice_form,
|
||||
'action_name': _("Confirm"),
|
||||
'articlesformset': articles_formset,
|
||||
'articlelist': articles
|
||||
'articlelist': articles,
|
||||
'discount_form': discount_form
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
|
@ -382,7 +386,7 @@ def custom_invoice_pdf(request, invoice, **_kwargs):
|
|||
purchases_info = []
|
||||
for purchase in purchases_objects:
|
||||
purchases_info.append({
|
||||
'name': purchase.name,
|
||||
'name': escape_chars(purchase.name),
|
||||
'price': purchase.prix,
|
||||
'quantity': purchase.number,
|
||||
'total_price': purchase.prix_total
|
||||
|
|
Loading…
Reference in a new issue