mirror of https://gitlab.crans.org/bde/nk20
Add products on billings
This commit is contained in:
parent
02ac33d143
commit
a772cea760
|
@ -0,0 +1,4 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
default_app_config = 'treasury.apps.TreasuryConfig'
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from treasury.models import Billing, Product
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Billing)
|
||||||
|
class BillingAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'name', 'subject', 'acquitted', )
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Product)
|
||||||
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('designation', 'quantity', 'amount', )
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from .models import Billing, Product
|
||||||
|
|
||||||
|
|
||||||
|
class BillingForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Billing
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
ProductFormSet = forms.inlineformset_factory(
|
||||||
|
Billing,
|
||||||
|
Product,
|
||||||
|
fields='__all__',
|
||||||
|
extra=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProductFormSetHelper(FormHelper):
|
||||||
|
def __init__(self, form=None):
|
||||||
|
super().__init__(form)
|
||||||
|
self.form_tag = False
|
||||||
|
self.form_method = 'POST'
|
||||||
|
self.form_class = 'form-inline'
|
||||||
|
self.template = 'bootstrap4/table_inline_formset.html'
|
|
@ -2,6 +2,7 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +123,14 @@ class Product(models.Model):
|
||||||
verbose_name=_("Unit price")
|
verbose_name=_("Unit price")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount_euros(self):
|
||||||
|
return self.amount / 100
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
return self.quantity * self.amount
|
return self.quantity * self.amount
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_euros(self):
|
||||||
|
return self.total / 100
|
||||||
|
|
|
@ -14,7 +14,10 @@ class BillingTable(tables.Table):
|
||||||
args=[A("pk")],
|
args=[A("pk")],
|
||||||
accessor="pk",
|
accessor="pk",
|
||||||
text="",
|
text="",
|
||||||
attrs={'a': {'class': 'fa fa-file-pdf-o'}})
|
attrs={
|
||||||
|
'a': {'class': 'fa fa-file-pdf-o'},
|
||||||
|
'td': {'data-turbolinks': 'false'}
|
||||||
|
})
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
|
|
|
@ -6,15 +6,20 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic import CreateView, UpdateView
|
from django.views.generic import CreateView, UpdateView
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
from django_tables2 import SingleTableView
|
from django_tables2 import SingleTableView
|
||||||
|
|
||||||
from note_kfet.settings.base import BASE_DIR
|
from note_kfet.settings.base import BASE_DIR
|
||||||
|
|
||||||
from .models import Billing
|
from .forms import BillingForm, ProductFormSet, ProductFormSetHelper
|
||||||
|
from .models import Billing, Product
|
||||||
from .tables import BillingTable
|
from .tables import BillingTable
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,8 +28,36 @@ class BillingCreateView(LoginRequiredMixin, CreateView):
|
||||||
Create Billing
|
Create Billing
|
||||||
"""
|
"""
|
||||||
model = Billing
|
model = Billing
|
||||||
fields = '__all__'
|
form_class = BillingForm
|
||||||
# form_class = ClubForm
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
form = context['form']
|
||||||
|
form.helper = FormHelper()
|
||||||
|
form.helper.form_tag = False
|
||||||
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
|
context['formset'] = form_set
|
||||||
|
context['helper'] = ProductFormSetHelper()
|
||||||
|
context['no_cache'] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
|
formset = ProductFormSet(self.request.POST, instance=form.instance)
|
||||||
|
if formset.is_valid():
|
||||||
|
for f in formset:
|
||||||
|
if f.is_valid() and f.instance.designation:
|
||||||
|
f.save()
|
||||||
|
f.instance.save()
|
||||||
|
else:
|
||||||
|
f.instance = None
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:billing')
|
||||||
|
|
||||||
|
|
||||||
class BillingListView(LoginRequiredMixin, SingleTableView):
|
class BillingListView(LoginRequiredMixin, SingleTableView):
|
||||||
|
@ -40,8 +73,42 @@ class BillingUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
Create Billing
|
Create Billing
|
||||||
"""
|
"""
|
||||||
model = Billing
|
model = Billing
|
||||||
fields = '__all__'
|
form_class = BillingForm
|
||||||
# form_class = BillingForm
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
form = context['form']
|
||||||
|
form.helper = FormHelper()
|
||||||
|
form.helper.form_tag = False
|
||||||
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
|
context['formset'] = form_set
|
||||||
|
context['helper'] = ProductFormSetHelper()
|
||||||
|
context['no_cache'] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
|
formset = ProductFormSet(self.request.POST, instance=form.instance)
|
||||||
|
saved = []
|
||||||
|
if formset.is_valid():
|
||||||
|
for f in formset:
|
||||||
|
if f.is_valid() and f.instance.designation:
|
||||||
|
if type(f.instance.pk) == 'number' and f.instance.pk <= 0:
|
||||||
|
f.instance.pk = None
|
||||||
|
f.save()
|
||||||
|
f.instance.save()
|
||||||
|
saved.append(f.instance.pk)
|
||||||
|
else:
|
||||||
|
f.instance = None
|
||||||
|
|
||||||
|
Product.objects.filter(~Q(pk__in=saved), billing=form.instance).delete()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('treasury:billing')
|
||||||
|
|
||||||
|
|
||||||
class BillingRenderView(LoginRequiredMixin, View):
|
class BillingRenderView(LoginRequiredMixin, View):
|
||||||
|
@ -52,10 +119,11 @@ class BillingRenderView(LoginRequiredMixin, View):
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
pk = kwargs["pk"]
|
pk = kwargs["pk"]
|
||||||
billing = Billing.objects.get(pk=pk)
|
billing = Billing.objects.get(pk=pk)
|
||||||
|
products = Product.objects.filter(billing=billing).all()
|
||||||
|
|
||||||
billing.description = billing.description.replace("\n", "\\newline\n")
|
billing.description = billing.description.replace("\n", "\\newline\n")
|
||||||
billing.address = billing.address.replace("\n", "\\newline\n")
|
billing.address = billing.address.replace("\n", "\\newline\n")
|
||||||
tex = render_to_string("treasury/billing_sample.tex", dict(obj=billing))
|
tex = render_to_string("treasury/billing_sample.tex", dict(obj=billing, products=products))
|
||||||
try:
|
try:
|
||||||
os.mkdir(BASE_DIR + "/tmp")
|
os.mkdir(BASE_DIR + "/tmp")
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* jQuery Formset 1.3-pre
|
* jQuery Formset 1.5-pre
|
||||||
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
|
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
|
||||||
* @requires jQuery 1.2.6 or later
|
* @requires jQuery 1.2.6 or later
|
||||||
*
|
*
|
||||||
|
@ -55,19 +55,26 @@
|
||||||
insertDeleteLink = function(row) {
|
insertDeleteLink = function(row) {
|
||||||
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
||||||
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
||||||
if (row.is('TR')) {
|
|
||||||
|
var delButtonHTML = '<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>';
|
||||||
|
if (options.deleteContainerClass) {
|
||||||
|
// If we have a specific container for the remove button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML);
|
||||||
|
} else if (row.is('TR')) {
|
||||||
// If the forms are laid out in table rows, insert
|
// If the forms are laid out in table rows, insert
|
||||||
// the remove button into the last table cell:
|
// the remove button into the last table cell:
|
||||||
row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>');
|
row.children('td:last').append(delButtonHTML);
|
||||||
} else if (row.is('UL') || row.is('OL')) {
|
} else if (row.is('UL') || row.is('OL')) {
|
||||||
// If they're laid out as an ordered/unordered list,
|
// If they're laid out as an ordered/unordered list,
|
||||||
// insert an <li> after the last list item:
|
// insert an <li> after the last list item:
|
||||||
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>');
|
row.append('<li>' + delButtonHTML + '</li>');
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, just insert the remove button as the
|
// Otherwise, just insert the remove button as the
|
||||||
// last child element of the form's container:
|
// last child element of the form's container:
|
||||||
row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>');
|
row.append(delButtonHTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
||||||
if (!showDeleteLinks()){
|
if (!showDeleteLinks()){
|
||||||
row.find('a.' + delCssSelector).hide();
|
row.find('a.' + delCssSelector).hide();
|
||||||
|
@ -156,6 +163,7 @@
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, use the last form in the formset; this works much better if you've got
|
// Otherwise, use the last form in the formset; this works much better if you've got
|
||||||
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
||||||
|
if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide();
|
||||||
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
|
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
|
||||||
template.find('input:hidden[id $= "-DELETE"]').remove();
|
template.find('input:hidden[id $= "-DELETE"]').remove();
|
||||||
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
||||||
|
@ -173,21 +181,28 @@
|
||||||
// FIXME: Perhaps using $.data would be a better idea?
|
// FIXME: Perhaps using $.data would be a better idea?
|
||||||
options.formTemplate = template;
|
options.formTemplate = template;
|
||||||
|
|
||||||
if ($$.is('TR')) {
|
var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>';
|
||||||
|
if (options.addContainerClass) {
|
||||||
|
// If we have a specific container for the "add" button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
var addContainer = $('[class*="' + options.addContainerClass + '"');
|
||||||
|
addContainer.append(addButtonHTML);
|
||||||
|
addButton = addContainer.find('[class="' + options.addCssClass + '"]');
|
||||||
|
} else if ($$.is('TR')) {
|
||||||
// If forms are laid out as table rows, insert the
|
// If forms are laid out as table rows, insert the
|
||||||
// "add" button in a new table row:
|
// "add" button in a new table row:
|
||||||
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
|
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
|
||||||
buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>')
|
buttonRow = $('<tr><td colspan="' + numCols + '">' + addButtonHTML + '</tr>').addClass(options.formCssClass + '-add');
|
||||||
.addClass(options.formCssClass + '-add');
|
|
||||||
$$.parent().append(buttonRow);
|
$$.parent().append(buttonRow);
|
||||||
if (hideAddButton) buttonRow.hide();
|
|
||||||
addButton = buttonRow.find('a');
|
addButton = buttonRow.find('a');
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, insert it immediately after the last form:
|
// Otherwise, insert it immediately after the last form:
|
||||||
$$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');
|
$$.filter(':last').after(addButtonHTML);
|
||||||
addButton = $$.filter(':last').next();
|
addButton = $$.filter(':last').next();
|
||||||
if (hideAddButton) addButton.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hideAddButton) addButton.hide();
|
||||||
|
|
||||||
addButton.click(function() {
|
addButton.click(function() {
|
||||||
var formCount = parseInt(totalForms.val()),
|
var formCount = parseInt(totalForms.val()),
|
||||||
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
|
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
|
||||||
|
@ -220,12 +235,15 @@
|
||||||
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
||||||
addText: 'add another', // Text for the add link
|
addText: 'add another', // Text for the add link
|
||||||
deleteText: 'remove', // Text for the delete link
|
deleteText: 'remove', // Text for the delete link
|
||||||
addCssClass: '', // CSS class applied to the add link
|
addContainerClass: null, // Container CSS class for the add link
|
||||||
deleteCssClass: '', // CSS class applied to the delete link
|
deleteContainerClass: null, // Container CSS class for the delete link
|
||||||
|
addCssClass: 'add-row', // CSS class applied to the add link
|
||||||
|
deleteCssClass: 'delete-row', // CSS class applied to the delete link
|
||||||
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
||||||
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
||||||
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
||||||
added: null, // Function called each time a new form is added
|
added: null, // Function called each time a new form is added
|
||||||
removed: null // Function called each time a form is deleted
|
removed: null, // Function called each time a form is deleted
|
||||||
|
hideLastAddForm: false // When set to true, hide last empty add form (becomes visible when clicking on add button)
|
||||||
};
|
};
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
|
@ -3,10 +3,78 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p><a class="btn btn-default" href="{% url 'treasury:billing' %}">{% trans "Billings list" %}</a></p>
|
<p><a class="btn btn-default" href="{% url 'treasury:billing' %}">{% trans "Billings list" %}</a></p>
|
||||||
<form method="post">
|
<form method="post" action="" class="form-horizontal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form|crispy}}
|
{% crispy form %}
|
||||||
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
{{ formset.management_form }}
|
||||||
</form>
|
<table class="table table-condensed table-striped">
|
||||||
|
{% for form in formset %}
|
||||||
|
{% if forloop.first %}
|
||||||
|
<thead><tr>
|
||||||
|
<th>{{ form.designation.label }}<span class="asteriskField">*</span></th>
|
||||||
|
<th>{{ form.quantity.label }}<span class="asteriskField">*</span></th>
|
||||||
|
<th>{{ form.amount.label }}<span class="asteriskField">*</span></th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody id="form_body" >
|
||||||
|
{% endif %}
|
||||||
|
<tr class="row-formset">
|
||||||
|
<td>{{ form.designation }}</td>
|
||||||
|
<td>{{ form.quantity }} </td>
|
||||||
|
<td>{{ form.amount }}</td>
|
||||||
|
{{ form.billing }}
|
||||||
|
{{ form.id }}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="btn-group btn-block" role="group">
|
||||||
|
<button type="button" id="add_more" class="btn btn-primary">{% trans "Add product" %}</button>
|
||||||
|
<button type="button" id="remove_one" class="btn btn-danger">{% trans "Remove product" %}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-block">
|
||||||
|
<button type="submit" class="btn btn-block btn-primary">{% trans "Submit" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="empty_form" style="display: none;">
|
||||||
|
<table class='no_error'>
|
||||||
|
<tbody id="for_real">
|
||||||
|
<tr class="row-formset">
|
||||||
|
<td>{{ formset.empty_form.designation }}</td>
|
||||||
|
<td>{{ formset.empty_form.quantity }} </td>
|
||||||
|
<td>{{ formset.empty_form.amount }}</td>
|
||||||
|
{{ formset.empty_form.billing }}
|
||||||
|
{{ formset.empty_form.id }}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script src="{% static 'js/dynamic-formset.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
IDS = {};
|
||||||
|
|
||||||
|
$("#id_product_set-TOTAL_FORMS").val($(".row-formset").length - 1);
|
||||||
|
|
||||||
|
$('#add_more').click(function() {
|
||||||
|
var form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
||||||
|
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
|
||||||
|
$('#id_product_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
||||||
|
$('#id_product_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#remove_one').click(function() {
|
||||||
|
let form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
||||||
|
if (form_idx > 0) {
|
||||||
|
IDS[parseInt(form_idx) - 1] = $('#id_product_set-' + (parseInt(form_idx) - 1) + '-id').val();
|
||||||
|
$('#form_body tr:last-child').remove();
|
||||||
|
$('#id_product_set-TOTAL_FORMS').val(parseInt(form_idx) - 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -95,8 +95,8 @@
|
||||||
|
|
||||||
% Liste des produits facturés : Désignation, quantité, prix unitaire HT
|
% Liste des produits facturés : Désignation, quantité, prix unitaire HT
|
||||||
|
|
||||||
{% for product in obj.products %}
|
{% for product in products %}
|
||||||
\AjouterProduit{ {{product.designation|safe}} } { {{product.quantity|safe}} } { {{product.amount|safe}}} { {{product.total|safe}}}
|
\AjouterProduit{ {{product.designation|safe}}} { {{product.quantity|safe}}} { {{product.amount_euros|safe}}} { {{product.total_euros|safe}}}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
|
|
||||||
\renewcommand{\headrulewidth}{0pt}
|
\renewcommand{\headrulewidth}{0pt}
|
||||||
\cfoot{
|
\cfoot{
|
||||||
\small{\MonNom ~--~ \MonAdresseRue \MonAdresseVille ~--~ Telephone : +33(0)6 89 88 56 50\newline
|
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 89 88 56 50\newline
|
||||||
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
|
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue