facturation: attacher les paiements aux lignes de facturation (#81190) #99
|
@ -29,7 +29,7 @@ from lingo.invoicing.models import (
|
|||
DraftJournalLine,
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
InvoicePayment,
|
||||
InvoiceLinePayment,
|
||||
JournalLine,
|
||||
Payer,
|
||||
Payment,
|
||||
|
@ -356,9 +356,10 @@ class AbstractInvoiceFilterSet(AgendaFieldsFilterSetMixin, django_filters.Filter
|
|||
self._init_agenda_fields(line_model, self.queryset)
|
||||
|
||||
def filter_payment_number(self, queryset, name, value):
|
||||
return queryset.filter(
|
||||
pk__in=InvoicePayment.objects.filter(payment__formatted_number__contains=value).values('invoice')
|
||||
line_queryset = InvoiceLine.objects.filter(
|
||||
pk__in=InvoiceLinePayment.objects.filter(payment__formatted_number__contains=value).values('line')
|
||||
)
|
||||
return queryset.filter(pk__in=line_queryset.values('invoice'))
|
||||
|
||||
def filter_user_external_id(self, queryset, name, value):
|
||||
if not value:
|
||||
|
@ -666,9 +667,10 @@ class RegieInvoiceFilterSet(AgendaFieldsFilterSetMixin, django_filters.FilterSet
|
|||
self._init_agenda_fields(InvoiceLine, self.queryset)
|
||||
|
||||
def filter_payment_number(self, queryset, name, value):
|
||||
return queryset.filter(
|
||||
pk__in=InvoicePayment.objects.filter(payment__formatted_number__contains=value).values('invoice')
|
||||
line_queryset = InvoiceLine.objects.filter(
|
||||
pk__in=InvoiceLinePayment.objects.filter(payment__formatted_number__contains=value).values('line')
|
||||
)
|
||||
return queryset.filter(pk__in=line_queryset.values('invoice'))
|
||||
|
||||
def filter_user_external_id(self, queryset, name, value):
|
||||
if not value:
|
||||
|
@ -768,23 +770,24 @@ class RegiePaymentFilterSet(AgendaFieldsFilterSetMixin, django_filters.FilterSet
|
|||
super().__init__(*args, **kwargs)
|
||||
self.filters['payment_type'].field.choices = [(t.pk, t) for t in self.regie.paymenttype_set.all()]
|
||||
|
||||
invoice_queryset = Invoice.objects.filter(
|
||||
pk__in=InvoicePayment.objects.filter(payment__in=self.queryset).values('invoice')
|
||||
line_queryset = InvoiceLine.objects.filter(
|
||||
pk__in=InvoiceLinePayment.objects.filter(payment__in=self.queryset).values('line')
|
||||
)
|
||||
invoice_queryset = Invoice.objects.filter(pk__in=line_queryset.values('invoice'))
|
||||
self._init_agenda_fields(InvoiceLine, invoice_queryset)
|
||||
|
||||
def filter_invoice_number(self, queryset, name, value):
|
||||
return queryset.filter(
|
||||
pk__in=InvoicePayment.objects.filter(invoice__formatted_number__contains=value).values('payment')
|
||||
pk__in=InvoiceLinePayment.objects.filter(line__invoice__formatted_number__contains=value).values(
|
||||
'payment'
|
||||
)
|
||||
)
|
||||
|
||||
def filter_agenda(self, queryset, name, value):
|
||||
if not value:
|
||||
return queryset
|
||||
lines = InvoiceLine.objects.filter(
|
||||
Q(details__agenda=value) | Q(slug__startswith='%s@' % value)
|
||||
).values('invoice')
|
||||
return queryset.filter(pk__in=InvoicePayment.objects.filter(invoice__in=lines).values('payment'))
|
||||
lines = InvoiceLine.objects.filter(Q(details__agenda=value) | Q(slug__startswith='%s@' % value))
|
||||
return queryset.filter(pk__in=InvoiceLinePayment.objects.filter(line__in=lines).values('payment'))
|
||||
|
||||
def filter_event(self, queryset, name, value):
|
||||
if not value:
|
||||
|
@ -792,8 +795,8 @@ class RegiePaymentFilterSet(AgendaFieldsFilterSetMixin, django_filters.FilterSet
|
|||
agenda_slug, event_slug = value.split('@')
|
||||
lines = InvoiceLine.objects.filter(
|
||||
Q(details__agenda=agenda_slug, details__primary_event=event_slug) | Q(slug=value)
|
||||
).values('invoice')
|
||||
return queryset.filter(pk__in=InvoicePayment.objects.filter(invoice__in=lines).values('payment'))
|
||||
)
|
||||
return queryset.filter(pk__in=InvoiceLinePayment.objects.filter(line__in=lines).values('payment'))
|
||||
|
||||
|
||||
class NewPayerForm(forms.ModelForm):
|
||||
|
|
|
@ -1,26 +1,9 @@
|
|||
import os
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'..',
|
||||
'sql',
|
||||
'invoice_triggers_for_amount.sql',
|
||||
)
|
||||
) as sql_file:
|
||||
sql_triggers = sql_file.read()
|
||||
|
||||
|
||||
sql_forwards = sql_triggers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0025_payments'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(sql=sql_forwards, reverse_sql=migrations.RunSQL.noop),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,26 +1,9 @@
|
|||
import os
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'..',
|
||||
'sql',
|
||||
'invoice_triggers_for_amount.sql',
|
||||
)
|
||||
) as sql_file:
|
||||
sql_triggers = sql_file.read()
|
||||
|
||||
|
||||
sql_forwards = sql_triggers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0042_payer_external_id_from_nameid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(sql=sql_forwards, reverse_sql=migrations.RunSQL.noop),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,26 +1,9 @@
|
|||
import os
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'..',
|
||||
'sql',
|
||||
'invoice_triggers_for_amount.sql',
|
||||
)
|
||||
) as sql_file:
|
||||
sql_triggers = sql_file.read()
|
||||
|
||||
|
||||
sql_forwards = sql_triggers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0049_journal_lines'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(sql=sql_forwards, reverse_sql=migrations.RunSQL.noop),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
import os
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
sql_drop_triggers = """
|
||||
DROP TRIGGER IF EXISTS set_draftinvoice_line_amount_trg ON invoicing_draftinvoiceline;
|
||||
DROP TRIGGER IF EXISTS set_invoice_line_amount_trg ON invoicing_invoiceline;
|
||||
"""
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'..',
|
||||
'sql',
|
||||
'invoice_triggers_for_amount.sql',
|
||||
)
|
||||
) as sql_file:
|
||||
sql_triggers = sql_file.read()
|
||||
|
||||
|
||||
sql_forwards = sql_triggers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
|
@ -27,7 +7,6 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(sql=sql_drop_triggers, reverse_sql=migrations.RunSQL.noop),
|
||||
migrations.AlterField(
|
||||
model_name='draftinvoiceline',
|
||||
name='quantity',
|
||||
|
@ -38,5 +17,4 @@ class Migration(migrations.Migration):
|
|||
name='quantity',
|
||||
field=models.IntegerField(),
|
||||
),
|
||||
migrations.RunSQL(sql=sql_forwards, reverse_sql=migrations.RunSQL.noop),
|
||||
]
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
import os
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
sql_drop_triggers = """
|
||||
DROP TRIGGER IF EXISTS set_draftinvoice_line_amount_trg ON invoicing_draftinvoiceline;
|
||||
DROP TRIGGER IF EXISTS set_invoice_line_amount_trg ON invoicing_invoiceline;
|
||||
"""
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'..',
|
||||
'sql',
|
||||
'invoice_triggers_for_amount.sql',
|
||||
)
|
||||
) as sql_file:
|
||||
sql_triggers = sql_file.read()
|
||||
|
||||
|
||||
sql_forwards = sql_triggers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
|
@ -27,7 +7,6 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(sql=sql_drop_triggers, reverse_sql=migrations.RunSQL.noop),
|
||||
migrations.AddField(
|
||||
model_name='draftjournalline',
|
||||
name='quantity',
|
||||
|
@ -62,5 +41,4 @@ class Migration(migrations.Migration):
|
|||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=2, max_digits=9),
|
||||
),
|
||||
migrations.RunSQL(sql=sql_forwards, reverse_sql=migrations.RunSQL.noop),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'..',
|
||||
'sql',
|
||||
'invoice_triggers_for_amount.sql',
|
||||
)
|
||||
) as sql_file:
|
||||
sql_triggers = sql_file.read()
|
||||
|
||||
|
||||
sql_forwards = sql_triggers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0061_pdf_appearance'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InvoiceLinePayment',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
(
|
||||
'amount',
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
max_digits=9,
|
||||
validators=[django.core.validators.MinValueValidator(Decimal('0.01'))],
|
||||
),
|
||||
),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
'line',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to='invoicing.invoiceline'
|
||||
),
|
||||
),
|
||||
(
|
||||
'payment',
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.payment'),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='invoiceline',
|
||||
name='paid_amount',
|
||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=9),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='invoiceline',
|
||||
name='remaining_amount',
|
||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=9),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='invoiceline',
|
||||
constraint=models.CheckConstraint(
|
||||
check=models.Q(
|
||||
models.Q(
|
||||
('paid_amount__lte', django.db.models.expressions.F('total_amount')),
|
||||
('total_amount__gt', 0),
|
||||
),
|
||||
models.Q(
|
||||
('paid_amount__gte', django.db.models.expressions.F('total_amount')),
|
||||
('total_amount__lt', 0),
|
||||
),
|
||||
models.Q(('paid_amount', 0), ('total_amount', 0)),
|
||||
_connector='OR',
|
||||
),
|
||||
name='paid_amount_check',
|
||||
),
|
||||
),
|
||||
migrations.RunSQL(sql=sql_forwards, reverse_sql=migrations.RunSQL.noop),
|
||||
]
|
|
@ -0,0 +1,40 @@
|
|||
import decimal
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
InvoicePayment = apps.get_model('invoicing', 'InvoicePayment')
|
||||
InvoiceLinePayment = apps.get_model('invoicing', 'InvoiceLinePayment')
|
||||
InvoiceLine = apps.get_model('invoicing', 'InvoiceLine')
|
||||
|
||||
for invoice_payment in InvoicePayment.objects.all():
|
||||
amount_to_assign = invoice_payment.amount
|
||||
for line in InvoiceLine.objects.filter(invoice=invoice_payment.invoice).order_by('pk'):
|
||||
# trigger not played yet, remaining_amount is not up to date
|
||||
line.remaining_amount = line.total_amount - line.paid_amount
|
||||
if not line.remaining_amount:
|
||||
# nothing to pay for this line
|
||||
continue
|
||||
# paid_amount for this line: it can not be greater than line remaining_amount
|
||||
paid_amount = decimal.Decimal(min(line.remaining_amount, amount_to_assign))
|
||||
# create payment for the line
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=invoice_payment.payment,
|
||||
line=line,
|
||||
amount=paid_amount,
|
||||
)
|
||||
# new amount to assign
|
||||
amount_to_assign -= paid_amount
|
||||
if amount_to_assign <= 0:
|
||||
break
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0062_invoice_line_payment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse_code=migrations.RunPython.noop),
|
||||
]
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0063_invoice_line_payment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='InvoicePayment',
|
||||
),
|
||||
]
|
|
@ -15,7 +15,9 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import base64
|
||||
import collections
|
||||
import copy
|
||||
import dataclasses
|
||||
import datetime
|
||||
import decimal
|
||||
import sys
|
||||
|
@ -673,7 +675,7 @@ class AbstractInvoice(models.Model):
|
|||
context = {
|
||||
'regie': self.regie,
|
||||
'invoice': self,
|
||||
'payments': self.invoicepayment_set.all().order_by('created_at'),
|
||||
'payments': InvoiceLinePayment.objects.filter(line__invoice=self).order_by('created_at'),
|
||||
}
|
||||
return template.render(context)
|
||||
|
||||
|
@ -764,6 +766,20 @@ class Invoice(AbstractInvoice):
|
|||
'payment_deadline_date': self.date_payment_deadline,
|
||||
}
|
||||
|
||||
def get_invoice_payments(self):
|
||||
invoice_line_payments = (
|
||||
InvoiceLinePayment.objects.filter(line__invoice=self)
|
||||
.select_related('payment', 'payment__payment_type')
|
||||
.order_by('created_at')
|
||||
)
|
||||
invoice_payments = collections.defaultdict(InvoicePayment)
|
||||
for invoice_line_payment in invoice_line_payments:
|
||||
payment = invoice_line_payment.payment
|
||||
invoice_payments[payment].invoice = self
|
||||
invoice_payments[payment].payment = payment
|
||||
invoice_payments[payment].amount += invoice_line_payment.amount
|
||||
return sorted(invoice_payments.values(), key=lambda a: a.payment.created_at)
|
||||
|
||||
|
||||
class InjectedLine(models.Model):
|
||||
event_date = models.DateField()
|
||||
|
@ -979,6 +995,8 @@ class DraftInvoiceLine(AbstractInvoiceLine):
|
|||
final_line.pool = pool
|
||||
final_line.invoice = invoice
|
||||
final_line.error_status = ''
|
||||
final_line.paid_amount = 0
|
||||
final_line.remaining_amount = 0
|
||||
final_line.save()
|
||||
|
||||
for line in self.journal_lines.all().order_by('pk'):
|
||||
|
@ -987,6 +1005,27 @@ class DraftInvoiceLine(AbstractInvoiceLine):
|
|||
|
||||
class InvoiceLine(AbstractInvoiceLine):
|
||||
invoice = models.ForeignKey(Invoice, on_delete=models.PROTECT, null=True, related_name='lines')
|
||||
paid_amount = models.DecimalField(max_digits=9, decimal_places=2, default=0)
|
||||
remaining_amount = models.DecimalField(max_digits=9, decimal_places=2, default=0)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
check=models.Q(
|
||||
models.Q(
|
||||
('paid_amount__lte', models.F('total_amount')),
|
||||
('total_amount__gt', 0),
|
||||
),
|
||||
models.Q(
|
||||
('paid_amount__gte', models.F('total_amount')),
|
||||
('total_amount__lt', 0),
|
||||
),
|
||||
models.Q(('paid_amount', 0), ('total_amount', 0)),
|
||||
_connector='OR',
|
||||
),
|
||||
name='paid_amount_check',
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
DEFAULT_PAYMENT_TYPES = [
|
||||
|
@ -1078,18 +1117,27 @@ class Payment(models.Model):
|
|||
)
|
||||
payment.set_number()
|
||||
payment.save()
|
||||
remaining_amount = amount
|
||||
amount_to_assign = amount
|
||||
for invoice in invoices.order_by('date_publication', 'created_at'):
|
||||
if not invoice.remaining_amount:
|
||||
# nothing to pay for this invoice
|
||||
continue
|
||||
paid_amount = decimal.Decimal(min(invoice.remaining_amount, remaining_amount))
|
||||
InvoicePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
amount=paid_amount,
|
||||
)
|
||||
remaining_amount -= paid_amount
|
||||
if remaining_amount <= 0:
|
||||
for line in invoice.lines.order_by('pk'):
|
||||
if not line.remaining_amount:
|
||||
# nothing to pay for this line
|
||||
continue
|
||||
# paid_amount for this line: it can not be greater than line remaining_amount
|
||||
paid_amount = decimal.Decimal(min(line.remaining_amount, amount_to_assign))
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
line=line,
|
||||
amount=paid_amount,
|
||||
)
|
||||
# new amount to assign
|
||||
amount_to_assign -= paid_amount
|
||||
if amount_to_assign <= 0:
|
||||
break
|
||||
if amount_to_assign <= 0:
|
||||
break
|
||||
|
||||
return payment
|
||||
|
@ -1102,25 +1150,47 @@ class Payment(models.Model):
|
|||
)
|
||||
self.formatted_number = self.regie.format_number(self.created_at, self.number, 'payment')
|
||||
|
||||
def get_invoice_payments(self):
|
||||
if hasattr(self, 'prefetched_invoicelinepayments'):
|
||||
invoice_line_payments = self.prefetched_invoicelinepayments
|
||||
else:
|
||||
invoice_line_payments = self.invoicelinepayment_set.select_related('line__invoice').order_by(
|
||||
'created_at'
|
||||
)
|
||||
invoice_payments = collections.defaultdict(InvoicePayment)
|
||||
for invoice_line_payment in invoice_line_payments:
|
||||
invoice = invoice_line_payment.line.invoice
|
||||
invoice_payments[invoice].invoice = invoice
|
||||
invoice_payments[invoice].payment = self
|
||||
invoice_payments[invoice].amount += invoice_line_payment.amount
|
||||
return sorted(invoice_payments.values(), key=lambda a: a.invoice.created_at)
|
||||
|
||||
def html(self):
|
||||
template = get_template('lingo/invoicing/payment.html')
|
||||
context = {
|
||||
'regie': self.regie,
|
||||
'payment': self,
|
||||
'lines': self.invoicepayment_set.select_related('invoice').order_by('created_at'),
|
||||
'invoice_payments': self.get_invoice_payments(),
|
||||
}
|
||||
return template.render(context)
|
||||
|
||||
|
||||
class InvoicePayment(models.Model):
|
||||
class InvoiceLinePayment(models.Model):
|
||||
payment = models.ForeignKey(Payment, on_delete=models.PROTECT)
|
||||
invoice = models.ForeignKey(Invoice, on_delete=models.PROTECT)
|
||||
line = models.ForeignKey(InvoiceLine, on_delete=models.PROTECT)
|
||||
amount = models.DecimalField(
|
||||
max_digits=9, decimal_places=2, validators=[validators.MinValueValidator(decimal.Decimal('0.01'))]
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class InvoicePayment:
|
||||
payment: Payment = None
|
||||
invoice: Invoice = None
|
||||
amount: decimal.Decimal = 0
|
||||
|
||||
|
||||
class AppearanceSettings(models.Model):
|
||||
logo = models.ImageField(
|
||||
verbose_name=_('Logo'),
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
CREATE OR REPLACE FUNCTION set_invoice_line_amount() RETURNS TRIGGER AS $$
|
||||
-- update total_amount on quantity, unit_amount changes for draft invoice line & invoice line
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_invoice_line_total_amount() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.total_amount = NEW.quantity * NEW.unit_amount;
|
||||
RETURN NEW;
|
||||
|
@ -6,6 +9,87 @@ CREATE OR REPLACE FUNCTION set_invoice_line_amount() RETURNS TRIGGER AS $$
|
|||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_draftinvoice_line_amount_trg ON invoicing_draftinvoiceline;
|
||||
CREATE TRIGGER set_draftinvoice_line_amount_trg
|
||||
BEFORE INSERT OR UPDATE OF quantity, unit_amount ON invoicing_draftinvoiceline
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_line_total_amount();
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_invoice_line_amount_trg ON invoicing_invoiceline;
|
||||
CREATE TRIGGER set_invoice_line_amount_trg
|
||||
BEFORE INSERT OR UPDATE OF quantity, unit_amount ON invoicing_invoiceline
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_line_total_amount();
|
||||
|
||||
|
||||
-- update line paid_amount & remaining_amount on invoice line payment change
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_invoice_line_computed_amounts() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
line_ids integer[];
|
||||
error_ids integer[];
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
line_ids := ARRAY[NEW.line_id];
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
line_ids := ARRAY[OLD.line_id];
|
||||
ELSIF TG_OP = 'UPDATE' THEN
|
||||
line_ids := ARRAY[NEW.line_id, OLD.line_id];
|
||||
END IF;
|
||||
|
||||
EXECUTE 'UPDATE invoicing_invoiceline l
|
||||
SET paid_amount = COALESCE(
|
||||
(
|
||||
SELECT SUM(p.amount)
|
||||
FROM invoicing_invoicelinepayment p
|
||||
WHERE p.line_id = l.id
|
||||
), 0
|
||||
)
|
||||
WHERE id = ANY($1);' USING line_ids;
|
||||
|
||||
EXECUTE 'UPDATE invoicing_invoiceline
|
||||
SET remaining_amount = total_amount - paid_amount
|
||||
WHERE id = ANY($1);' USING line_ids;
|
||||
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
ELSE
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_invoice_invoicelinepayment_trg ON invoicing_invoicelinepayment;
|
||||
CREATE TRIGGER set_invoice_invoicelinepayment_trg
|
||||
AFTER INSERT OR UPDATE OF amount OR DELETE ON invoicing_invoicelinepayment
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_line_computed_amounts();
|
||||
|
||||
|
||||
-- update line remaining_amount on total_amout update
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_invoice_line_remaining_amount() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.remaining_amount = NEW.total_amount - NEW.paid_amount;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_invoice_line_remaining_amount_trg ON invoicing_invoiceline;
|
||||
CREATE TRIGGER set_invoice_line_remaining_amount_trg
|
||||
BEFORE INSERT OR UPDATE OF total_amount, paid_amount ON invoicing_invoiceline
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_line_remaining_amount();
|
||||
|
||||
|
||||
-- update invoice total_amount, paid_amount & remaining_amount on line amount changes
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_invoice_amounts() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
invoice_ids integer[];
|
||||
|
@ -19,37 +103,26 @@ CREATE OR REPLACE FUNCTION set_invoice_amounts() RETURNS TRIGGER AS $$
|
|||
invoice_ids := ARRAY[NEW.invoice_id, OLD.invoice_id];
|
||||
END IF;
|
||||
|
||||
IF TG_TABLE_NAME != 'invoicing_invoicepayment' THEN
|
||||
EXECUTE 'UPDATE ' || substring(TG_TABLE_NAME for length(TG_TABLE_NAME) - 4) || ' i
|
||||
SET total_amount = COALESCE(
|
||||
EXECUTE 'UPDATE ' || substring(TG_TABLE_NAME for length(TG_TABLE_NAME) - 4) || ' i
|
||||
SET total_amount = COALESCE(
|
||||
(
|
||||
SELECT SUM(l.total_amount)
|
||||
FROM ' || TG_TABLE_NAME || ' l
|
||||
WHERE l.invoice_id = i.id
|
||||
), 0
|
||||
)
|
||||
WHERE id = ANY($1);' USING invoice_ids;
|
||||
|
||||
IF TG_TABLE_NAME = 'invoicing_invoiceline' THEN
|
||||
EXECUTE 'UPDATE invoicing_invoice i
|
||||
SET paid_amount = COALESCE(
|
||||
(
|
||||
SELECT SUM(l.total_amount)
|
||||
FROM ' || TG_TABLE_NAME || ' l
|
||||
SELECT SUM(l.paid_amount)
|
||||
FROM invoicing_invoiceline l
|
||||
WHERE l.invoice_id = i.id
|
||||
), 0
|
||||
)
|
||||
WHERE id = ANY($1);' USING invoice_ids;
|
||||
END IF;
|
||||
|
||||
IF TG_TABLE_NAME != 'invoicing_draftinvoiceline' THEN
|
||||
EXECUTE 'UPDATE invoicing_invoice i
|
||||
SET paid_amount = COALESCE(
|
||||
(
|
||||
SELECT SUM(p.amount)
|
||||
FROM invoicing_invoicepayment p
|
||||
WHERE p.invoice_id = i.id
|
||||
), 0
|
||||
)
|
||||
WHERE id = ANY($1);' USING invoice_ids;
|
||||
|
||||
EXECUTE 'SELECT ARRAY_AGG(id) FROM invoicing_invoice
|
||||
WHERE id = ANY($1) AND paid_amount > total_amount AND NOT (paid_amount = 0 and total_amount < 0)'
|
||||
INTO error_ids
|
||||
USING invoice_ids;
|
||||
IF ARRAY_LENGTH(error_ids, 1) > 0 THEN
|
||||
RAISE EXCEPTION 'paid_amount is greater than total_amount for invoices %', error_ids;
|
||||
END IF;
|
||||
|
||||
EXECUTE 'UPDATE invoicing_invoice
|
||||
SET remaining_amount = total_amount - paid_amount
|
||||
WHERE id = ANY($1);' USING invoice_ids;
|
||||
|
@ -64,20 +137,6 @@ CREATE OR REPLACE FUNCTION set_invoice_amounts() RETURNS TRIGGER AS $$
|
|||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_draftinvoice_line_amount_trg ON invoicing_draftinvoiceline;
|
||||
CREATE TRIGGER set_draftinvoice_line_amount_trg
|
||||
BEFORE INSERT OR UPDATE OF quantity, unit_amount ON invoicing_draftinvoiceline
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_line_amount();
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_invoice_line_amount_trg ON invoicing_invoiceline;
|
||||
CREATE TRIGGER set_invoice_line_amount_trg
|
||||
BEFORE INSERT OR UPDATE OF quantity, unit_amount ON invoicing_invoiceline
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_line_amount();
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_draftinvoice_line_trg ON invoicing_draftinvoiceline;
|
||||
CREATE TRIGGER set_draftinvoice_line_trg
|
||||
AFTER INSERT OR UPDATE OF total_amount, invoice_id OR DELETE ON invoicing_draftinvoiceline
|
||||
|
@ -87,18 +146,11 @@ CREATE TRIGGER set_draftinvoice_line_trg
|
|||
|
||||
DROP TRIGGER IF EXISTS set_invoice_line_trg ON invoicing_invoiceline;
|
||||
CREATE TRIGGER set_invoice_line_trg
|
||||
AFTER INSERT OR UPDATE OF total_amount, invoice_id OR DELETE ON invoicing_invoiceline
|
||||
AFTER INSERT OR UPDATE OF total_amount, paid_amount, remaining_amount, invoice_id OR DELETE ON invoicing_invoiceline
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_amounts();
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS set_invoice_invoicepayment_trg ON invoicing_invoicepayment;
|
||||
CREATE TRIGGER set_invoice_invoicepayment_trg
|
||||
AFTER INSERT OR UPDATE OF amount OR DELETE ON invoicing_invoicepayment
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE set_invoice_amounts();
|
||||
|
||||
-- old triggers and function
|
||||
DROP TRIGGER IF EXISTS set_draftinvoice_total_amount_trg ON invoicing_draftinvoiceline;
|
||||
DROP TRIGGER IF EXISTS set_invoice_total_amount_trg ON invoicing_invoiceline;
|
||||
DROP FUNCTION IF EXISTS set_invoice_total_amount;
|
||||
DROP TRIGGER IF EXISTS set_invoice_invoicepayment_trg ON invoicing_invoicepayment;
|
||||
DROP FUNCTION IF EXISTS set_invoice_line_amount;
|
||||
|
|
|
@ -57,7 +57,8 @@
|
|||
<td colspan="2">{% trans "Description" %}</td>
|
||||
<td class="amount">{% trans "Amount" %}</td>
|
||||
<td class="quantity">{% trans "Quantity" %}</td>
|
||||
<td class="amount" colspan="2">{% trans "Subtotal" %}</td>
|
||||
<td class="amount">{% trans "Subtotal" %}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
{% ifchanged line.user_external_id line.agenda %}
|
||||
|
@ -67,7 +68,7 @@
|
|||
</tr>
|
||||
{% endif %}
|
||||
{% endifchanged %}
|
||||
<tr class="line {% if pool.draft and forloop.last %}last-line{% endif %}" data-related-invoicing-element-id="{{ invoice.pk }}">
|
||||
<tr class="line untoggled {% if pool.draft and forloop.last %}last-line{% endif %}" data-related-invoicing-element-id="{{ invoice.pk }}" data-invoicing-element-id="{{ invoice.pk }}-{{ line.pk }}">
|
||||
<td class="event">
|
||||
{% if line.pool %}<a href="{{ journal_url }}?invoice_line={{ line.pk }}">{% endif %}{% if not line.details %}{{ line.event_date|date:"d/m/Y" }} - {% endif %}{{ line.label }}{% if line.pool %}</a>{% endif %}
|
||||
</td>
|
||||
|
@ -91,10 +92,50 @@
|
|||
<td class="quantity">
|
||||
{{ line.quantity|floatformat }}
|
||||
</td>
|
||||
<td colspan="2" class="amount">
|
||||
<td class="amount">
|
||||
{% blocktrans with amount=line.total_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}
|
||||
</td>
|
||||
{% if not pool.draft and line.total_amount %}
|
||||
<td class="with-togglable">
|
||||
<span class="togglable"></span>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% if not pool.draft and line.total_amount %}
|
||||
<tr class="line" data-related-invoicing-element-id="{{ invoice.pk }}-{{ line.pk }}" style="display: none;">
|
||||
<th colspan="6" class="payments">
|
||||
{% trans "Payments" %}
|
||||
</th>
|
||||
</tr>
|
||||
<tr class="line" data-related-invoicing-element-id="{{ invoice.pk }}-{{ line.pk }}" style="display: none;">
|
||||
<td class="payment-num">{% trans "Payment" context 'payment' %}</td>
|
||||
<td>{% trans "Date" context 'payment' %}</td>
|
||||
<td colspan="2">{% trans "Type" context 'payment' %}</td>
|
||||
<td class="amount" colspan="2">{% trans "Amount" %}</td>
|
||||
</tr>
|
||||
{% for invoice_line_payment in line.invoicelinepayment_set.all %}
|
||||
<tr class="line payment {% if forloop.last %}last-line{% endif %}" data-related-invoicing-element-id="{{ invoice.pk }}-{{ line.pk }}" style="display: none;">
|
||||
<td>
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-payment-list' regie_pk=regie.pk %}?number={{ invoice_line_payment.payment.formatted_number }}">{{ invoice_line_payment.payment.formatted_number }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ invoice_line_payment.payment.created_at|date:"DATETIME_FORMAT" }}
|
||||
</td>
|
||||
<td colspan="2">
|
||||
{{ invoice_line_payment.payment.payment_type.label }}
|
||||
</td>
|
||||
<td class="amount" colspan="2">
|
||||
{% blocktrans with amount=invoice_line_payment.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="line payment" data-related-invoicing-element-id="{{ invoice.pk }}-{{ line.pk }}" style="display: none;">
|
||||
<td colspan="6" class="no-payments">
|
||||
{% trans "No payments for this line" %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not pool.draft %}
|
||||
<tr class="line" data-related-invoicing-element-id="{{ invoice.pk }}">
|
||||
|
@ -108,13 +149,13 @@
|
|||
<td colspan="2">{% trans "Type" context 'payment' %}</td>
|
||||
<td class="amount" colspan="2">{% trans "Amount" %}</td>
|
||||
</tr>
|
||||
{% for invoice_payment in invoice_payments %}
|
||||
{% for invoice_payment in invoice.get_invoice_payments %}
|
||||
<tr class="line" data-related-invoicing-element-id="{{ invoice.pk }}">
|
||||
<td>
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-payment-list' regie_pk=regie.pk %}?number={{ invoice_payment.payment.formatted_number }}">{{ invoice_payment.payment.formatted_number }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ invoice_payment.created_at|date:"DATETIME_FORMAT" }}
|
||||
{{ invoice_payment.payment.created_at|date:"DATETIME_FORMAT" }}
|
||||
</td>
|
||||
<td colspan="2">
|
||||
{{ invoice_payment.payment.payment_type.label }}
|
||||
|
|
|
@ -32,29 +32,31 @@
|
|||
<table class="main pk-compact-table invoicing-element-list">
|
||||
{% url 'lingo-manager-invoicing-regie-invoice-list' regie_pk=regie.pk as regie_invoice_list_url %}
|
||||
{% for payment in object_list %}
|
||||
<tr class="payment untoggled" data-invoicing-element-id="{{ payment.pk }}">
|
||||
<td colspan="3">
|
||||
{% blocktrans with payment_number=payment.formatted_number cdate=payment.created_at|date:'d/m/Y' payer_id=payment.invoicepayment_set.all.0.invoice.payer_external_id payer_name=payment.invoicepayment_set.all.0.invoice.payer_name amount=payment.amount payment_type=payment.payment_type %}Payment {{ payment_number }} dated {{ cdate }} from <a href="?payer_external_id={{ payer_id }}">{{ payer_name }}</a>, amount {{ amount }}€ ({{ payment_type }}){% endblocktrans %}
|
||||
- <a href="{% url 'lingo-manager-invoicing-regie-payment-pdf' regie_pk=regie.pk payment_pk=payment.pk %}">{% trans "download" %}</a>
|
||||
</td>
|
||||
<td class="with-togglable">
|
||||
<span class="togglable"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="line" data-related-invoicing-element-id="{{ payment.pk }}" style="display: none;">
|
||||
<td>{% trans "Invoice" %}</td>
|
||||
<td class="amount">{% trans "Amount charged" %}</td>
|
||||
<td class="amount" colspan="2">{% trans "Amount paid" %}</td>
|
||||
</tr>
|
||||
{% for line in payment.invoicepayment_set.all %}
|
||||
<tr class="line {% if forloop.last %}last-line{% endif %}" data-related-invoicing-element-id="{{ payment.pk }}" style="display: none;">
|
||||
<td class="invoice">
|
||||
<a href="{{ regie_invoice_list_url }}?number={{ line.invoice.formatted_number }}">{{ line.invoice.formatted_number }}</a>
|
||||
{% with invoice_payments=payment.get_invoice_payments %}
|
||||
<tr class="payment untoggled" data-invoicing-element-id="{{ payment.pk }}">
|
||||
<td colspan="3">
|
||||
{% blocktrans with payment_number=payment.formatted_number cdate=payment.created_at|date:'d/m/Y' payer_id=invoice_payments.0.invoice.payer_external_id payer_name=invoice_payments.0.invoice.payer_name amount=payment.amount payment_type=payment.payment_type %}Payment {{ payment_number }} dated {{ cdate }} from <a href="?payer_external_id={{ payer_id }}">{{ payer_name }}</a>, amount {{ amount }}€ ({{ payment_type }}){% endblocktrans %}
|
||||
- <a href="{% url 'lingo-manager-invoicing-regie-payment-pdf' regie_pk=regie.pk payment_pk=payment.pk %}">{% trans "download" %}</a>
|
||||
</td>
|
||||
<td class="with-togglable">
|
||||
<span class="togglable"></span>
|
||||
</td>
|
||||
<td class="amount">{% blocktrans with amount=line.invoice.total_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
<td class="amount" colspan="2">{% blocktrans with amount=line.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="line" data-related-invoicing-element-id="{{ payment.pk }}" style="display: none;">
|
||||
<td>{% trans "Invoice" %}</td>
|
||||
<td class="amount">{% trans "Amount charged" %}</td>
|
||||
<td class="amount" colspan="2">{% trans "Amount assigned" %}</td>
|
||||
</tr>
|
||||
{% for invoice_payment in invoice_payments %}
|
||||
<tr class="line {% if forloop.last %}last-line{% endif %}" data-related-invoicing-element-id="{{ payment.pk }}" style="display: none;">
|
||||
<td class="invoice">
|
||||
<a href="{{ regie_invoice_list_url }}?number={{ invoice_payment.invoice.formatted_number }}">{{ invoice_payment.invoice.formatted_number }}</a>
|
||||
</td>
|
||||
<td class="amount">{% blocktrans with amount=invoice_payment.invoice.total_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
<td class="amount" colspan="2">{% blocktrans with amount=invoice_payment.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% include "gadjo/pagination.html" %}
|
||||
|
|
|
@ -126,16 +126,16 @@
|
|||
<th class="invoice">{% trans "Invoice number" %}</th>
|
||||
<th class="object">{% trans "Invoice object" %}</th>
|
||||
<th class="amount">{% trans "Amount charged" %}</th>
|
||||
<th class="amount">{% trans "Amount payed" %}</th>
|
||||
<th class="amount">{% trans "Amount assigned" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in lines %}
|
||||
{% for invoice_payment in invoice_payments %}
|
||||
<tr>
|
||||
<td class="invoice">{{ line.invoice.formatted_number }}</td>
|
||||
<td class="object">{{ line.invoice.label }}</td>
|
||||
<td class="amount">{% blocktrans with amount=line.invoice.total_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
<td class="amount">{% blocktrans with amount=line.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
<td class="invoice">{{ invoice_payment.invoice.formatted_number }}</td>
|
||||
<td class="object">{{ invoice_payment.invoice.label }}</td>
|
||||
<td class="amount">{% blocktrans with amount=invoice_payment.invoice.total_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
<td class="amount">{% blocktrans with amount=invoice_payment.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -130,12 +130,12 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for invoice_payment in payments %}
|
||||
{% for invoice_line_payment in payments %}
|
||||
<tr>
|
||||
<td class="invoice">{{ invoice_payment.payment.formatted_number }}</td>
|
||||
<td class="date">{{ invoice_payment.created_at|date:"d/m/Y" }}</td>
|
||||
<td class="type">{{ invoice_payment.payment.payment_type }}</td>
|
||||
<td class="amount">{% blocktrans with amount=invoice_payment.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
<td class="invoice">{{ invoice_line_payment.payment.formatted_number }}</td>
|
||||
<td class="date">{{ invoice_line_payment.created_at|date:"d/m/Y" }}</td>
|
||||
<td class="type">{{ invoice_line_payment.payment.payment_type }}</td>
|
||||
<td class="amount">{% blocktrans with amount=invoice_line_payment.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -32,7 +32,6 @@ from lingo.invoicing.models import (
|
|||
DraftJournalLine,
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
InvoicePayment,
|
||||
JournalLine,
|
||||
Pool,
|
||||
Regie,
|
||||
|
@ -289,12 +288,6 @@ class InvoiceLineListView(ListView):
|
|||
kwargs['object'] = self.pool.campaign
|
||||
kwargs['pool'] = self.pool
|
||||
kwargs['invoice'] = self.invoice
|
||||
if not self.pool.draft:
|
||||
kwargs['invoice_payments'] = (
|
||||
InvoicePayment.objects.filter(invoice=self.invoice)
|
||||
.select_related('payment')
|
||||
.order_by('created_at')
|
||||
)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ from lingo.invoicing.models import (
|
|||
Counter,
|
||||
InjectedLine,
|
||||
Invoice,
|
||||
InvoicePayment,
|
||||
InvoiceLinePayment,
|
||||
JournalLine,
|
||||
Payment,
|
||||
PaymentType,
|
||||
|
@ -462,11 +462,6 @@ class RegieInvoiceLineListView(ListView):
|
|||
def get_context_data(self, **kwargs):
|
||||
kwargs['regie'] = self.regie
|
||||
kwargs['invoice'] = self.invoice
|
||||
kwargs['invoice_payments'] = (
|
||||
InvoicePayment.objects.filter(invoice=self.invoice)
|
||||
.select_related('payment', 'payment__payment_type')
|
||||
.order_by('created_at')
|
||||
)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
@ -482,12 +477,19 @@ class RegiePaymentListView(ListView):
|
|||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
invoice_payment_queryset = InvoicePayment.objects.select_related('invoice').order_by('created_at')
|
||||
invoice_line_payment_queryset = InvoiceLinePayment.objects.select_related('line__invoice').order_by(
|
||||
'created_at'
|
||||
)
|
||||
self.filterset = RegiePaymentFilterSet(
|
||||
data=self.request.GET or None,
|
||||
queryset=Payment.objects.filter(regie=self.regie)
|
||||
.prefetch_related(
|
||||
'payment_type', Prefetch('invoicepayment_set', queryset=invoice_payment_queryset)
|
||||
'payment_type',
|
||||
Prefetch(
|
||||
'invoicelinepayment_set',
|
||||
queryset=invoice_line_payment_queryset,
|
||||
to_attr='prefetched_invoicelinepayments',
|
||||
),
|
||||
)
|
||||
.order_by('-created_at'),
|
||||
regie=self.regie,
|
||||
|
@ -520,12 +522,12 @@ class RegiePaymentListView(ListView):
|
|||
_('Payer first name'),
|
||||
_('Payer last name'),
|
||||
_('Payment type'),
|
||||
_('Amount paid'),
|
||||
_('Amount assigned'),
|
||||
_('Total amount'),
|
||||
]
|
||||
)
|
||||
for payment in self.object_list:
|
||||
for invoice_payment in payment.invoicepayment_set.all():
|
||||
for invoice_payment in payment.get_invoice_payments():
|
||||
writer.writerow(
|
||||
[
|
||||
payment.formatted_number,
|
||||
|
|
|
@ -137,9 +137,12 @@ table.invoicing-element-list {
|
|||
padding-bottom: 2em;
|
||||
}
|
||||
td {
|
||||
&.quantity, &.amount, &.payment-num, &.with-togglable {
|
||||
&.quantity, &.amount, &.payment-num {
|
||||
width: 10em;
|
||||
}
|
||||
&.with-togglable {
|
||||
width: 2em;
|
||||
}
|
||||
&.quantity, &.amount, &.with-togglable {
|
||||
text-align: right;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ from lingo.invoicing.models import (
|
|||
InjectedLine,
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
InvoicePayment,
|
||||
InvoiceLinePayment,
|
||||
PayerError,
|
||||
Payment,
|
||||
PaymentType,
|
||||
|
@ -99,7 +99,7 @@ def test_list_invoices(mock_payer, app, user):
|
|||
assert mock_payer.call_args_list == [mock.call(regie, mock.ANY, 'foobar')]
|
||||
|
||||
# invoice with something to pay
|
||||
InvoiceLine.objects.create(
|
||||
line = InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
|
@ -155,9 +155,9 @@ def test_list_invoices(mock_payer, app, user):
|
|||
amount=1,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
invoice_line_payment = InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
line=line,
|
||||
amount=1,
|
||||
)
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
|
@ -186,15 +186,15 @@ def test_list_invoices(mock_payer, app, user):
|
|||
]
|
||||
|
||||
# invoice is paid
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 42
|
||||
invoice_line_payment.save()
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# no matching payer id
|
||||
invoice_payment.amount = 1
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 1
|
||||
invoice_line_payment.save()
|
||||
mock_payer.return_value = 'payer:unknown'
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
|
@ -268,7 +268,7 @@ def test_list_invoices_for_payer(app, user):
|
|||
assert resp.json['data'] == []
|
||||
|
||||
# invoice with something to pay
|
||||
InvoiceLine.objects.create(
|
||||
line = InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
|
@ -324,9 +324,9 @@ def test_list_invoices_for_payer(app, user):
|
|||
amount=1,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
invoice_line_payment = InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
line=line,
|
||||
amount=1,
|
||||
)
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'payer_external_id': 'payer:1'})
|
||||
|
@ -355,15 +355,15 @@ def test_list_invoices_for_payer(app, user):
|
|||
]
|
||||
|
||||
# invoice is paid
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 42
|
||||
invoice_line_payment.save()
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'payer_external_id': 'payer:1'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# campaign is not finalized
|
||||
invoice_payment.amount = 1
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 1
|
||||
invoice_line_payment.save()
|
||||
campaign = Campaign.objects.create(
|
||||
regie=regie,
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
|
@ -413,7 +413,7 @@ def test_list_history_invoices(mock_payer, app, user):
|
|||
)
|
||||
invoice.set_number()
|
||||
invoice.save()
|
||||
InvoiceLine.objects.create(
|
||||
line = InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
|
@ -425,9 +425,9 @@ def test_list_history_invoices(mock_payer, app, user):
|
|||
amount=42,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
invoice_line_payment = InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
line=line,
|
||||
amount=2,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
|
@ -441,8 +441,8 @@ def test_list_history_invoices(mock_payer, app, user):
|
|||
assert mock_payer.call_args_list == [mock.call(regie, mock.ANY, 'foobar')]
|
||||
|
||||
# invoice with nothing to pay
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 42
|
||||
invoice_line_payment.save()
|
||||
invoice.refresh_from_db()
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
|
@ -552,7 +552,7 @@ def test_list_history_invoices_for_payer(app, user):
|
|||
)
|
||||
invoice.set_number()
|
||||
invoice.save()
|
||||
InvoiceLine.objects.create(
|
||||
line = InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
|
@ -563,9 +563,9 @@ def test_list_history_invoices_for_payer(app, user):
|
|||
amount=42,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
invoice_line_payment = InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
line=line,
|
||||
amount=2,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
|
@ -577,8 +577,8 @@ def test_list_history_invoices_for_payer(app, user):
|
|||
assert resp.json['data'] == []
|
||||
|
||||
# invoice with nothing to pay
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 42
|
||||
invoice_line_payment.save()
|
||||
invoice.refresh_from_db()
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'payer_external_id': 'payer:1'})
|
||||
assert resp.json['err'] == 0
|
||||
|
@ -674,7 +674,7 @@ def test_detail_invoice(mock_payer, app, user):
|
|||
)
|
||||
invoice.set_number()
|
||||
invoice.save()
|
||||
InvoiceLine.objects.create(
|
||||
line = InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
|
@ -726,9 +726,9 @@ def test_detail_invoice(mock_payer, app, user):
|
|||
amount=1,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
invoice_line_payment = InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
line=line,
|
||||
amount=1,
|
||||
)
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'})
|
||||
|
@ -755,8 +755,8 @@ def test_detail_invoice(mock_payer, app, user):
|
|||
}
|
||||
|
||||
# invoice is paid
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 42
|
||||
invoice_line_payment.save()
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == {
|
||||
|
@ -781,8 +781,8 @@ def test_detail_invoice(mock_payer, app, user):
|
|||
}
|
||||
|
||||
# no matching payer id
|
||||
invoice_payment.amount = 1
|
||||
invoice_payment.save()
|
||||
invoice_line_payment.amount = 1
|
||||
invoice_line_payment.save()
|
||||
mock_payer.return_value = 'payer:unknown'
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'}, status=404)
|
||||
|
||||
|
@ -836,7 +836,7 @@ def test_detail_invoice_for_payer(app, user):
|
|||
)
|
||||
invoice.set_number()
|
||||
invoice.save()
|
||||
InvoiceLine.objects.create(
|
||||
line = InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|