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