mirror of
https://gitlab.crans.org/bde/nk20
synced 2024-11-26 18:37:12 +00:00
Comment code
This commit is contained in:
parent
093e585b79
commit
a33d373f6e
@ -5,7 +5,6 @@ from django.apps import AppConfig
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_save, post_migrate
|
from django.db.models.signals import post_save, post_migrate
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note.models import NoteSpecial
|
|
||||||
|
|
||||||
|
|
||||||
class TreasuryConfig(AppConfig):
|
class TreasuryConfig(AppConfig):
|
||||||
@ -18,7 +17,7 @@ class TreasuryConfig(AppConfig):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from . import signals
|
from . import signals
|
||||||
from note.models import SpecialTransaction
|
from note.models import SpecialTransaction, NoteSpecial
|
||||||
from treasury.models import SpecialTransactionProxy
|
from treasury.models import SpecialTransactionProxy
|
||||||
post_save.connect(signals.save_special_transaction, sender=SpecialTransaction)
|
post_save.connect(signals.save_special_transaction, sender=SpecialTransaction)
|
||||||
|
|
||||||
|
@ -12,6 +12,11 @@ from .models import Invoice, Product, Remittance, SpecialTransactionProxy
|
|||||||
|
|
||||||
|
|
||||||
class InvoiceForm(forms.ModelForm):
|
class InvoiceForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Create and generate invoices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Django forms don't support date fields. We have to add it manually
|
||||||
date = forms.DateField(
|
date = forms.DateField(
|
||||||
initial=datetime.date.today,
|
initial=datetime.date.today,
|
||||||
widget=forms.TextInput(attrs={'type': 'date'})
|
widget=forms.TextInput(attrs={'type': 'date'})
|
||||||
@ -25,6 +30,8 @@ class InvoiceForm(forms.ModelForm):
|
|||||||
exclude = ('bde', )
|
exclude = ('bde', )
|
||||||
|
|
||||||
|
|
||||||
|
# Add a subform per product in the invoice form, and manage correctly the link between the invoice and
|
||||||
|
# its products. The FormSet will search automatically the ForeignKey in the Product model.
|
||||||
ProductFormSet = forms.inlineformset_factory(
|
ProductFormSet = forms.inlineformset_factory(
|
||||||
Invoice,
|
Invoice,
|
||||||
Product,
|
Product,
|
||||||
@ -34,6 +41,10 @@ ProductFormSet = forms.inlineformset_factory(
|
|||||||
|
|
||||||
|
|
||||||
class ProductFormSetHelper(FormHelper):
|
class ProductFormSetHelper(FormHelper):
|
||||||
|
"""
|
||||||
|
Specify some template informations for the product form.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, form=None):
|
def __init__(self, form=None):
|
||||||
super().__init__(form)
|
super().__init__(form)
|
||||||
self.form_tag = False
|
self.form_tag = False
|
||||||
@ -43,24 +54,33 @@ class ProductFormSetHelper(FormHelper):
|
|||||||
|
|
||||||
|
|
||||||
class RemittanceForm(forms.ModelForm):
|
class RemittanceForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Create remittances.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
|
|
||||||
|
# We can't update the type of the remittance once created.
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
self.fields["remittance_type"].disabled = True
|
self.fields["remittance_type"].disabled = True
|
||||||
self.fields["remittance_type"].required = False
|
self.fields["remittance_type"].required = False
|
||||||
|
|
||||||
|
# We display the submit button iff the remittance is open,
|
||||||
|
# the close button iff it is open and has a linked transaction
|
||||||
if not self.instance.closed:
|
if not self.instance.closed:
|
||||||
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
|
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
|
||||||
if self.instance.transactions:
|
if self.instance.transactions:
|
||||||
self.helper.add_input(Submit("close", _("Close"), css_class='btn btn-success'))
|
self.helper.add_input(Submit("close", _("Close"), css_class='btn btn-success'))
|
||||||
else:
|
else:
|
||||||
|
# If the remittance is closed, we can't change anything
|
||||||
self.fields["comment"].disabled = True
|
self.fields["comment"].disabled = True
|
||||||
self.fields["comment"].required = False
|
self.fields["comment"].required = False
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
# We can't update anything if the remittance is already closed.
|
||||||
if self.instance.closed:
|
if self.instance.closed:
|
||||||
self.add_error("comment", _("Remittance is already closed."))
|
self.add_error("comment", _("Remittance is already closed."))
|
||||||
|
|
||||||
@ -69,6 +89,7 @@ class RemittanceForm(forms.ModelForm):
|
|||||||
if self.instance.pk and cleaned_data.get("remittance_type") != self.instance.remittance_type:
|
if self.instance.pk and cleaned_data.get("remittance_type") != self.instance.remittance_type:
|
||||||
self.add_error("remittance_type", _("You can't change the type of the remittance."))
|
self.add_error("remittance_type", _("You can't change the type of the remittance."))
|
||||||
|
|
||||||
|
# The close button is manually handled
|
||||||
if "close" in self.data:
|
if "close" in self.data:
|
||||||
self.instance.closed = True
|
self.instance.closed = True
|
||||||
self.cleaned_data["closed"] = True
|
self.cleaned_data["closed"] = True
|
||||||
@ -81,6 +102,11 @@ class RemittanceForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class LinkTransactionToRemittanceForm(forms.ModelForm):
|
class LinkTransactionToRemittanceForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Attach a special transaction to a remittance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Since we use a proxy model for special transactions, we add manually the fields related to the transaction
|
||||||
last_name = forms.CharField(label=_("Last name"))
|
last_name = forms.CharField(label=_("Last name"))
|
||||||
|
|
||||||
first_name = forms.Field(label=_("First name"))
|
first_name = forms.Field(label=_("First name"))
|
||||||
@ -92,21 +118,34 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
|
# Add submit button
|
||||||
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
|
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
|
||||||
|
|
||||||
def clean_last_name(self):
|
def clean_last_name(self):
|
||||||
|
"""
|
||||||
|
Replace the first name in the information of the transaction.
|
||||||
|
"""
|
||||||
self.instance.transaction.last_name = self.data.get("last_name")
|
self.instance.transaction.last_name = self.data.get("last_name")
|
||||||
self.instance.transaction.clean()
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
def clean_first_name(self):
|
def clean_first_name(self):
|
||||||
|
"""
|
||||||
|
Replace the last name in the information of the transaction.
|
||||||
|
"""
|
||||||
self.instance.transaction.first_name = self.data.get("first_name")
|
self.instance.transaction.first_name = self.data.get("first_name")
|
||||||
self.instance.transaction.clean()
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
def clean_bank(self):
|
def clean_bank(self):
|
||||||
|
"""
|
||||||
|
Replace the bank in the information of the transaction.
|
||||||
|
"""
|
||||||
self.instance.transaction.bank = self.data.get("bank")
|
self.instance.transaction.bank = self.data.get("bank")
|
||||||
self.instance.transaction.clean()
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
def clean_amount(self):
|
def clean_amount(self):
|
||||||
|
"""
|
||||||
|
Replace the amount of the transaction.
|
||||||
|
"""
|
||||||
self.instance.transaction.amount = self.data.get("amount")
|
self.instance.transaction.amount = self.data.get("amount")
|
||||||
self.instance.transaction.clean()
|
self.instance.transaction.clean()
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from note.models import NoteSpecial, SpecialTransaction
|
|||||||
|
|
||||||
class Invoice(models.Model):
|
class Invoice(models.Model):
|
||||||
"""
|
"""
|
||||||
An invoice model that can generate a true invoice
|
An invoice model that can generates a true invoice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = models.PositiveIntegerField(
|
id = models.PositiveIntegerField(
|
||||||
@ -62,7 +62,7 @@ class Invoice(models.Model):
|
|||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
"""
|
"""
|
||||||
Product that appear on an invoice.
|
Product that appears on an invoice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
invoice = models.ForeignKey(
|
invoice = models.ForeignKey(
|
||||||
@ -138,18 +138,28 @@ class Remittance(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def transactions(self):
|
def transactions(self):
|
||||||
|
"""
|
||||||
|
:return: Transactions linked to this remittance.
|
||||||
|
"""
|
||||||
|
if not self.pk:
|
||||||
|
return SpecialTransaction.objects.none()
|
||||||
return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self)
|
return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self)
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
|
"""
|
||||||
|
Linked transactions count.
|
||||||
|
"""
|
||||||
return self.transactions.count()
|
return self.transactions.count()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def amount(self):
|
def amount(self):
|
||||||
|
"""
|
||||||
|
Total amount of the remittance.
|
||||||
|
"""
|
||||||
return sum(transaction.total for transaction in self.transactions.all())
|
return sum(transaction.total for transaction in self.transactions.all())
|
||||||
|
|
||||||
def save(self, force_insert=False, force_update=False, using=None,
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
update_fields=None):
|
# Check if all transactions have the right type.
|
||||||
|
|
||||||
if self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
if self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||||
raise ValidationError("All transactions in a remittance must have the same type")
|
raise ValidationError("All transactions in a remittance must have the same type")
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@ from .models import Invoice, Remittance
|
|||||||
|
|
||||||
|
|
||||||
class InvoiceTable(tables.Table):
|
class InvoiceTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List all invoices.
|
||||||
|
"""
|
||||||
id = tables.LinkColumn("treasury:invoice_update",
|
id = tables.LinkColumn("treasury:invoice_update",
|
||||||
args=[A("pk")],
|
args=[A("pk")],
|
||||||
text=lambda record: _("Invoice #{:d}").format(record.id), )
|
text=lambda record: _("Invoice #{:d}").format(record.id), )
|
||||||
@ -35,6 +38,10 @@ class InvoiceTable(tables.Table):
|
|||||||
|
|
||||||
|
|
||||||
class RemittanceTable(tables.Table):
|
class RemittanceTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List all remittances.
|
||||||
|
"""
|
||||||
|
|
||||||
count = tables.Column(verbose_name=_("Transaction count"))
|
count = tables.Column(verbose_name=_("Transaction count"))
|
||||||
|
|
||||||
amount = tables.Column(verbose_name=_("Amount"))
|
amount = tables.Column(verbose_name=_("Amount"))
|
||||||
@ -60,6 +67,11 @@ class RemittanceTable(tables.Table):
|
|||||||
|
|
||||||
|
|
||||||
class SpecialTransactionTable(tables.Table):
|
class SpecialTransactionTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List special credit transactions that are (or not, following the queryset) attached to a remittance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Display add and remove buttons. Use the `exclude` field to select what is needed.
|
||||||
remittance_add = tables.LinkColumn("treasury:link_transaction",
|
remittance_add = tables.LinkColumn("treasury:link_transaction",
|
||||||
verbose_name=_("Remittance"),
|
verbose_name=_("Remittance"),
|
||||||
args=[A("specialtransactionproxy.pk")],
|
args=[A("specialtransactionproxy.pk")],
|
||||||
|
@ -8,11 +8,13 @@ from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, Invoic
|
|||||||
|
|
||||||
app_name = 'treasury'
|
app_name = 'treasury'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
# Invoice app paths
|
||||||
path('invoice/', InvoiceListView.as_view(), name='invoice_list'),
|
path('invoice/', InvoiceListView.as_view(), name='invoice_list'),
|
||||||
path('invoice/create/', InvoiceCreateView.as_view(), name='invoice_create'),
|
path('invoice/create/', InvoiceCreateView.as_view(), name='invoice_create'),
|
||||||
path('invoice/<int:pk>/', InvoiceUpdateView.as_view(), name='invoice_update'),
|
path('invoice/<int:pk>/', InvoiceUpdateView.as_view(), name='invoice_update'),
|
||||||
path('invoice/render/<int:pk>/', InvoiceRenderView.as_view(), name='invoice_render'),
|
path('invoice/render/<int:pk>/', InvoiceRenderView.as_view(), name='invoice_render'),
|
||||||
|
|
||||||
|
# Remittance app paths
|
||||||
path('remittance/', RemittanceListView.as_view(), name='remittance_list'),
|
path('remittance/', RemittanceListView.as_view(), name='remittance_list'),
|
||||||
path('remittance/create/', RemittanceCreateView.as_view(), name='remittance_create'),
|
path('remittance/create/', RemittanceCreateView.as_view(), name='remittance_create'),
|
||||||
path('remittance/<int:pk>/', RemittanceUpdateView.as_view(), name='remittance_update'),
|
path('remittance/<int:pk>/', RemittanceUpdateView.as_view(), name='remittance_update'),
|
||||||
|
@ -21,7 +21,7 @@ from note.models import SpecialTransaction, NoteSpecial
|
|||||||
from note_kfet.settings.base import BASE_DIR
|
from note_kfet.settings.base import BASE_DIR
|
||||||
|
|
||||||
from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
|
from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
|
||||||
from .models import Invoice, Product, Remittance, SpecialTransactionProxy, RemittanceType
|
from .models import Invoice, Product, Remittance, SpecialTransactionProxy
|
||||||
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable
|
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable
|
||||||
|
|
||||||
|
|
||||||
@ -34,9 +34,12 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
form = context['form']
|
form = context['form']
|
||||||
form.helper = FormHelper()
|
form.helper = FormHelper()
|
||||||
|
# Remove form tag on the generation of the form in the template (already present on the template)
|
||||||
form.helper.form_tag = False
|
form.helper.form_tag = False
|
||||||
|
# The formset handles the set of the products
|
||||||
form_set = ProductFormSet(instance=form.instance)
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
context['formset'] = form_set
|
context['formset'] = form_set
|
||||||
context['helper'] = ProductFormSetHelper()
|
context['helper'] = ProductFormSetHelper()
|
||||||
@ -48,6 +51,8 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
|
|||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
|
# The user type amounts in cents. We convert it in euros.
|
||||||
for key in self.request.POST:
|
for key in self.request.POST:
|
||||||
value = self.request.POST[key]
|
value = self.request.POST[key]
|
||||||
if key.endswith("amount") and value:
|
if key.endswith("amount") and value:
|
||||||
@ -55,9 +60,11 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
|
|||||||
elif value:
|
elif value:
|
||||||
kwargs[key] = value
|
kwargs[key] = value
|
||||||
|
|
||||||
|
# For each product, we save it
|
||||||
formset = ProductFormSet(kwargs, instance=form.instance)
|
formset = ProductFormSet(kwargs, instance=form.instance)
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
for f in formset:
|
for f in formset:
|
||||||
|
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||||
if f.is_valid() and f.instance.designation:
|
if f.is_valid() and f.instance.designation:
|
||||||
f.save()
|
f.save()
|
||||||
f.instance.save()
|
f.instance.save()
|
||||||
@ -87,10 +94,14 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
form = context['form']
|
form = context['form']
|
||||||
form.helper = FormHelper()
|
form.helper = FormHelper()
|
||||||
|
# Remove form tag on the generation of the form in the template (already present on the template)
|
||||||
form.helper.form_tag = False
|
form.helper.form_tag = False
|
||||||
|
# Fill the intial value for the date field, with the initial date of the model instance
|
||||||
form.fields['date'].initial = form.instance.date
|
form.fields['date'].initial = form.instance.date
|
||||||
|
# The formset handles the set of the products
|
||||||
form_set = ProductFormSet(instance=form.instance)
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
context['formset'] = form_set
|
context['formset'] = form_set
|
||||||
context['helper'] = ProductFormSetHelper()
|
context['helper'] = ProductFormSetHelper()
|
||||||
@ -102,6 +113,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
# The user type amounts in cents. We convert it in euros.
|
||||||
for key in self.request.POST:
|
for key in self.request.POST:
|
||||||
value = self.request.POST[key]
|
value = self.request.POST[key]
|
||||||
if key.endswith("amount") and value:
|
if key.endswith("amount") and value:
|
||||||
@ -111,14 +123,17 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
formset = ProductFormSet(kwargs, instance=form.instance)
|
formset = ProductFormSet(kwargs, instance=form.instance)
|
||||||
saved = []
|
saved = []
|
||||||
|
# For each product, we save it
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
for f in formset:
|
for f in formset:
|
||||||
|
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||||
if f.is_valid() and f.instance.designation:
|
if f.is_valid() and f.instance.designation:
|
||||||
f.save()
|
f.save()
|
||||||
f.instance.save()
|
f.instance.save()
|
||||||
saved.append(f.instance.pk)
|
saved.append(f.instance.pk)
|
||||||
else:
|
else:
|
||||||
f.instance = None
|
f.instance = None
|
||||||
|
# Remove old products that weren't given in the form
|
||||||
Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete()
|
Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete()
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@ -129,7 +144,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
class InvoiceRenderView(LoginRequiredMixin, View):
|
class InvoiceRenderView(LoginRequiredMixin, View):
|
||||||
"""
|
"""
|
||||||
Render Invoice as generated PDF
|
Render Invoice as a generated PDF with the given information and a LaTeX template
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
@ -137,6 +152,7 @@ class InvoiceRenderView(LoginRequiredMixin, View):
|
|||||||
invoice = Invoice.objects.get(pk=pk)
|
invoice = Invoice.objects.get(pk=pk)
|
||||||
products = Product.objects.filter(invoice=invoice).all()
|
products = Product.objects.filter(invoice=invoice).all()
|
||||||
|
|
||||||
|
# Informations of the BDE. Should be updated when the school will move.
|
||||||
invoice.place = "Cachan"
|
invoice.place = "Cachan"
|
||||||
invoice.my_name = "BDE ENS Cachan"
|
invoice.my_name = "BDE ENS Cachan"
|
||||||
invoice.my_address_street = "61 avenue du Président Wilson"
|
invoice.my_address_street = "61 avenue du Président Wilson"
|
||||||
@ -147,13 +163,17 @@ class InvoiceRenderView(LoginRequiredMixin, View):
|
|||||||
invoice.rib_key = 14
|
invoice.rib_key = 14
|
||||||
invoice.bic = "SOGEFRPP"
|
invoice.bic = "SOGEFRPP"
|
||||||
|
|
||||||
|
# Replace line breaks with the LaTeX equivalent
|
||||||
invoice.description = invoice.description.replace("\r", "").replace("\n", "\\\\ ")
|
invoice.description = invoice.description.replace("\r", "").replace("\n", "\\\\ ")
|
||||||
invoice.address = invoice.address.replace("\r", "").replace("\n", "\\\\ ")
|
invoice.address = invoice.address.replace("\r", "").replace("\n", "\\\\ ")
|
||||||
|
# Fill the template with the information
|
||||||
tex = render_to_string("treasury/invoice_sample.tex", dict(obj=invoice, products=products))
|
tex = render_to_string("treasury/invoice_sample.tex", dict(obj=invoice, products=products))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.mkdir(BASE_DIR + "/tmp")
|
os.mkdir(BASE_DIR + "/tmp")
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
pass
|
pass
|
||||||
|
# We render the file in a temporary directory
|
||||||
tmp_dir = mkdtemp(prefix=BASE_DIR + "/tmp/")
|
tmp_dir = mkdtemp(prefix=BASE_DIR + "/tmp/")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -161,21 +181,27 @@ class InvoiceRenderView(LoginRequiredMixin, View):
|
|||||||
f.write(tex.encode("UTF-8"))
|
f.write(tex.encode("UTF-8"))
|
||||||
del tex
|
del tex
|
||||||
|
|
||||||
|
# The file has to be rendered twice
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
error = subprocess.Popen(
|
error = subprocess.Popen(
|
||||||
["pdflatex", "invoice-{}.tex".format(pk)],
|
["pdflatex", "invoice-{}.tex".format(pk)],
|
||||||
cwd=tmp_dir,
|
cwd=tmp_dir,
|
||||||
|
stdin=open(os.devnull, "r"),
|
||||||
|
stderr=open(os.devnull, "wb"),
|
||||||
|
stdout=open(os.devnull, "wb"),
|
||||||
).wait()
|
).wait()
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")")
|
raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")")
|
||||||
|
|
||||||
|
# Display the generated pdf as a HTTP Response
|
||||||
pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
|
pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
|
||||||
response = HttpResponse(pdf, content_type="application/pdf")
|
response = HttpResponse(pdf, content_type="application/pdf")
|
||||||
response['Content-Disposition'] = "inline;filename=invoice-{:d}.pdf".format(pk)
|
response['Content-Disposition'] = "inline;filename=invoice-{:d}.pdf".format(pk)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
|
# Delete all temporary files
|
||||||
shutil.rmtree(tmp_dir)
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -211,6 +237,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all())
|
ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all())
|
||||||
ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all())
|
ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all())
|
||||||
|
|
||||||
ctx["special_transactions_no_remittance"] = SpecialTransactionTable(
|
ctx["special_transactions_no_remittance"] = SpecialTransactionTable(
|
||||||
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
|
||||||
specialtransactionproxy__remittance=None).all(),
|
specialtransactionproxy__remittance=None).all(),
|
||||||
@ -246,6 +273,10 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
|
|
||||||
class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView):
|
class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView):
|
||||||
|
"""
|
||||||
|
Attach a special transaction to a remittance
|
||||||
|
"""
|
||||||
|
|
||||||
model = SpecialTransactionProxy
|
model = SpecialTransactionProxy
|
||||||
form_class = LinkTransactionToRemittanceForm
|
form_class = LinkTransactionToRemittanceForm
|
||||||
|
|
||||||
@ -267,10 +298,15 @@ class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
|
|
||||||
class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View):
|
class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Unlink a special transaction and its remittance
|
||||||
|
"""
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
pk = kwargs["pk"]
|
pk = kwargs["pk"]
|
||||||
transaction = SpecialTransactionProxy.objects.get(pk=pk)
|
transaction = SpecialTransactionProxy.objects.get(pk=pk)
|
||||||
|
|
||||||
|
# The remittance must be open (or inexistant)
|
||||||
if transaction.remittance and transaction.remittance.closed:
|
if transaction.remittance and transaction.remittance.closed:
|
||||||
raise ValidationError("Remittance is already closed.")
|
raise ValidationError("Remittance is already closed.")
|
||||||
|
|
||||||
|
@ -1004,7 +1004,7 @@ msgid "Transfers with opened remittances"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/treasury/remittance_list.html:48
|
#: templates/treasury/remittance_list.html:48
|
||||||
msgid "There is no transaction without an opened linked remittance."
|
msgid "There is no transaction with an opened linked remittance."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/treasury/remittance_list.html:54
|
#: templates/treasury/remittance_list.html:54
|
||||||
|
@ -1003,11 +1003,11 @@ msgstr "Il n'y a pas de transactions sans remise associée."
|
|||||||
|
|
||||||
#: templates/treasury/remittance_list.html:43
|
#: templates/treasury/remittance_list.html:43
|
||||||
msgid "Transfers with opened remittances"
|
msgid "Transfers with opened remittances"
|
||||||
msgstr "Transactions avec remise associée ouverte"
|
msgstr "Transactions associées à une remise ouverte"
|
||||||
|
|
||||||
#: templates/treasury/remittance_list.html:48
|
#: templates/treasury/remittance_list.html:48
|
||||||
msgid "There is no transaction without an opened linked remittance."
|
msgid "There is no transaction with an opened linked remittance."
|
||||||
msgstr "Il n'y a pas de transaction sans remise ouverte associée."
|
msgstr "Il n'y a pas de transaction associée à une remise ouverte."
|
||||||
|
|
||||||
#: templates/treasury/remittance_list.html:54
|
#: templates/treasury/remittance_list.html:54
|
||||||
msgid "Closed remittances"
|
msgid "Closed remittances"
|
||||||
|
@ -6,22 +6,29 @@
|
|||||||
<p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p>
|
<p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p>
|
||||||
<form method="post" action="">
|
<form method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{# Render the invoice form #}
|
||||||
{% crispy form %}
|
{% crispy form %}
|
||||||
|
{# The next part concerns the product formset #}
|
||||||
|
{# Generate some hidden fields that manage the number of products, and make easier the parsing #}
|
||||||
{{ formset.management_form }}
|
{{ formset.management_form }}
|
||||||
<table class="table table-condensed table-striped">
|
<table class="table table-condensed table-striped">
|
||||||
{% for form in formset %}
|
{# Fill initial data #}
|
||||||
{% if forloop.first %}
|
{% for form in formset %}
|
||||||
<thead><tr>
|
{% if forloop.first %}
|
||||||
<th>{{ form.designation.label }}<span class="asteriskField">*</span></th>
|
<thead>
|
||||||
<th>{{ form.quantity.label }}<span class="asteriskField">*</span></th>
|
<tr>
|
||||||
<th>{{ form.amount.label }}<span class="asteriskField">*</span></th>
|
<th>{{ form.designation.label }}<span class="asteriskField">*</span></th>
|
||||||
</tr></thead>
|
<th>{{ form.quantity.label }}<span class="asteriskField">*</span></th>
|
||||||
<tbody id="form_body" >
|
<th>{{ form.amount.label }}<span class="asteriskField">*</span></th>
|
||||||
{% endif %}
|
</tr>
|
||||||
<tr class="row-formset">
|
</thead>
|
||||||
|
<tbody id="form_body">
|
||||||
|
{% endif %}
|
||||||
|
<tr class="row-formset">
|
||||||
<td>{{ form.designation }}</td>
|
<td>{{ form.designation }}</td>
|
||||||
<td>{{ form.quantity }} </td>
|
<td>{{ form.quantity }} </td>
|
||||||
<td>
|
<td>
|
||||||
|
{# Use custom input for amount, with the € symbol #}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="number" name="product_set-{{ forloop.counter0 }}-amount" min="0" step="0.01"
|
<input type="number" name="product_set-{{ forloop.counter0 }}-amount" min="0" step="0.01"
|
||||||
id="id_product_set-{{ forloop.counter0 }}-amount"
|
id="id_product_set-{{ forloop.counter0 }}-amount"
|
||||||
@ -31,13 +38,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
|
||||||
{{ form.invoice }}
|
{{ form.invoice }}
|
||||||
{{ form.id }}
|
{{ form.id }}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{# Display buttons to add and remove products #}
|
||||||
<div class="btn-group btn-block" role="group">
|
<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="add_more" class="btn btn-primary">{% trans "Add product" %}</button>
|
||||||
<button type="button" id="remove_one" class="btn btn-danger">{% trans "Remove product" %}</button>
|
<button type="button" id="remove_one" class="btn btn-danger">{% trans "Remove product" %}</button>
|
||||||
@ -49,43 +58,44 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="empty_form" style="display: none;">
|
<div id="empty_form" style="display: none;">
|
||||||
|
{# Hidden div that store an empty product form, to be copied into new forms #}
|
||||||
<table class='no_error'>
|
<table class='no_error'>
|
||||||
<tbody id="for_real">
|
<tbody id="for_real">
|
||||||
<tr class="row-formset">
|
<tr class="row-formset">
|
||||||
<td>{{ formset.empty_form.designation }}</td>
|
<td>{{ formset.empty_form.designation }}</td>
|
||||||
<td>{{ formset.empty_form.quantity }} </td>
|
<td>{{ formset.empty_form.quantity }} </td>
|
||||||
<td>
|
<td>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="number" name="product_set-__prefix__-amount" min="0" step="0.01"
|
<input type="number" name="product_set-__prefix__-amount" min="0" step="0.01"
|
||||||
id="id_product_set-__prefix__-amount">
|
id="id_product_set-__prefix__-amount">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<span class="input-group-text">€</span>
|
<span class="input-group-text">€</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
{{ formset.empty_form.invoice }}
|
</td>
|
||||||
{{ formset.empty_form.id }}
|
{{ formset.empty_form.invoice }}
|
||||||
</tr>
|
{{ formset.empty_form.id }}
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
<script src="{% static 'js/dynamic-formset.js' %}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
|
{# Script that handles add and remove lines #}
|
||||||
IDS = {};
|
IDS = {};
|
||||||
|
|
||||||
$("#id_product_set-TOTAL_FORMS").val($(".row-formset").length - 1);
|
$("#id_product_set-TOTAL_FORMS").val($(".row-formset").length - 1);
|
||||||
|
|
||||||
$('#add_more').click(function() {
|
$('#add_more').click(function () {
|
||||||
var form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
var form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
||||||
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
|
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
|
||||||
$('#id_product_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
$('#id_product_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
||||||
$('#id_product_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
|
$('#id_product_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#remove_one').click(function() {
|
$('#remove_one').click(function () {
|
||||||
let form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
let form_idx = $('#id_product_set-TOTAL_FORMS').val();
|
||||||
if (form_idx > 0) {
|
if (form_idx > 0) {
|
||||||
IDS[parseInt(form_idx) - 1] = $('#id_product_set-' + (parseInt(form_idx) - 1) + '-id').val();
|
IDS[parseInt(form_idx) - 1] = $('#id_product_set-' + (parseInt(form_idx) - 1) + '-id').val();
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
{% render_table special_transactions_with_remittance %}
|
{% render_table special_transactions_with_remittance %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
{% trans "There is no transaction without an opened linked remittance." %}
|
{% trans "There is no transaction with an opened linked remittance." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user