facturation: gestion des bordereaux de remise de règlements (#88698) #177
|
@ -39,6 +39,7 @@ from lingo.invoicing.models import (
|
|||
Payer,
|
||||
Payment,
|
||||
PaymentCancellationReason,
|
||||
PaymentDocket,
|
||||
PaymentType,
|
||||
Refund,
|
||||
Regie,
|
||||
|
@ -845,6 +846,100 @@ class RegiePaymentCancelForm(forms.ModelForm):
|
|||
return self.instance
|
||||
|
||||
|
||||
class RegieDocketPaymentFilterSet(django_filters.FilterSet):
|
||||
payment_type = django_filters.MultipleChoiceFilter(
|
||||
label=_('Payment type'),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=True,
|
||||
)
|
||||
date_end = django_filters.DateFilter(
|
||||
label=_('Date end'),
|
||||
widget=forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
required=True,
|
||||
field_name='created_at',
|
||||
lookup_expr='lt',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Payment
|
||||
fields = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.regie = kwargs.pop('regie')
|
||||
if kwargs.get('data') is None:
|
||||
# set initial through data, so form is valid on page load
|
||||
pmarillonnet marked this conversation as resolved
Outdated
|
||||
kwargs['data'] = {
|
||||
'payment_type': [p.pk for p in self.regie.paymenttype_set.all()],
|
||||
'date_end': now().date(),
|
||||
}
|
||||
super().__init__(*args, **kwargs)
|
||||
self.filters['payment_type'].field.choices = [(t.pk, t) for t in self.regie.paymenttype_set.all()]
|
||||
|
||||
|
||||
class PaymentDocketForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = PaymentDocket
|
||||
fields = ['payment_types', 'date_end']
|
||||
widgets = {
|
||||
'payment_types': forms.CheckboxSelectMultiple,
|
||||
'date_end': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.regie = kwargs.pop('regie')
|
||||
if kwargs.get('data') is None:
|
||||
# set initial through data, so form is valid on page load
|
||||
kwargs['data'] = {
|
||||
'payment_types': [p.pk for p in self.regie.paymenttype_set.all()],
|
||||
'date_end': now().date(),
|
||||
}
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['payment_types'].queryset = self.regie.paymenttype_set.all()
|
||||
|
||||
def save(self):
|
||||
self.instance = super().save()
|
||||
filterset = RegieDocketPaymentFilterSet(
|
||||
regie=self.regie,
|
||||
queryset=Payment.objects.filter(regie=self.regie, docket__isnull=True, cancelled_at__isnull=True),
|
||||
data={
|
||||
'payment_type': [p.pk for p in self.instance.payment_types.all()],
|
||||
'date_end': self.instance.date_end,
|
||||
},
|
||||
)
|
||||
if filterset.form.is_valid():
|
||||
payment_queryset = filterset.qs
|
||||
payment_queryset.update(docket=self.instance)
|
||||
return self.instance
|
||||
|
||||
|
||||
class PaymentDocketPaymentTypeForm(forms.ModelForm):
|
||||
additionnal_information = forms.CharField(
|
||||
label=_('Additionnal information'),
|
||||
widget=forms.Textarea,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PaymentDocket
|
||||
fields = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.payment_type = kwargs.pop('payment_type')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.initial['additionnal_information'] = (
|
||||
self.instance.payment_types_info.get(self.payment_type.slug) or ''
|
||||
)
|
||||
|
||||
def save(self):
|
||||
super().save(commit=False)
|
||||
|
||||
self.instance.payment_types_info[self.payment_type.slug] = self.cleaned_data[
|
||||
'additionnal_information'
|
||||
]
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
||||
|
||||
class RegieCreditFilterSet(AgendaFieldsFilterSetMixin, django_filters.FilterSet):
|
||||
number = django_filters.CharFilter(
|
||||
label=_('Credit number'),
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0084_payment_cancellation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='regie',
|
||||
name='docket_number_format',
|
||||
field=models.CharField(
|
||||
default='B{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
max_length=100,
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Pas compris l’intérêt de limiter autant ce format sur le modèle de régie, lequel est a priori instancié en un nombre d’objets très limité. N’est-ce pas uniquement la longueur de l’identifiant généré à partir de ce format qu’il convient de contrôler ? Pas compris l’intérêt de limiter autant ce format sur le modèle de régie, lequel est a priori instancié en un nombre d’objets très limité. N’est-ce pas uniquement la longueur de l’identifiant généré à partir de ce format qu’il convient de contrôler ?
lguerin
commented
pas réfléchi plus que ça, les autres compteurs aussi ont un max_length à 100, et les formatted_number ont un max_length à 200, ce qui devrait suffire pas réfléchi plus que ça, les autres compteurs aussi ont un max_length à 100, et les formatted_number ont un max_length à 200, ce qui devrait suffire
pmarillonnet
commented
Ok. Ok.
|
||||
verbose_name='Payment docket number format',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='counter',
|
||||
name='kind',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('invoice', 'Invoice'),
|
||||
('payment', 'Payment'),
|
||||
('credit', 'Credit'),
|
||||
('refund', 'Refund'),
|
||||
('docket', 'Payment Docket'),
|
||||
],
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentDocket',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('number', models.PositiveIntegerField(default=0)),
|
||||
('formatted_number', models.CharField(max_length=200)),
|
||||
('date_end', models.DateField(verbose_name='End date')),
|
||||
('draft', models.BooleanField()),
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Peut-être Peut-être `is_draft` (pour ne pas croire à tort que ce champ est un accesseur vers les brouillons antérieurs du bordereau) ?
lguerin
commented
on a draft aussi sur les campagnes, et tu ne l'as pas relevé :) on a draft aussi sur les campagnes, et tu ne l'as pas relevé :)
je peux changer ici si tu insistes
pmarillonnet
commented
Non, je n’ai pas vu que c’était ainsi par ailleurs, et je relève mais n’insiste pas :) Non, je n’ai pas vu que c’était ainsi par ailleurs, et je relève mais n’insiste pas :)
Laissons ainsi.
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('payment_types', models.ManyToManyField(to='invoicing.PaymentType')),
|
||||
('payment_types_info', models.JSONField(blank=True, default=dict)),
|
||||
(
|
||||
'regie',
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.regie'),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='payment',
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Je vais voir si je capte mieux dans la suite de la relecture, mais je ne comprends pas le choix de la FK dans ce sens et pas l’inverse. L’intuition me dit que c’est le paiement l’objet principal, vers lequel le bordereau, en tant qu’objet secondaire, pointe. Edit: Ok, je comprends mieux à la lecture de la suite de la PR. Je vais voir si je capte mieux dans la suite de la relecture, mais je ne comprends pas le choix de la FK dans ce sens et pas l’inverse. L’intuition me dit que c’est le paiement l’objet principal, vers lequel le bordereau, en tant qu’objet secondaire, pointe.
Edit: Ok, je comprends mieux à la lecture de la suite de la PR.
lguerin
commented
un payment ne peut être inclus que dans un seul bordereau :) un payment ne peut être inclus que dans un seul bordereau :)
pmarillonnet
commented
Oui j’ai capté ça en cours de route, c’est bon pour moi. Oui j’ai capté ça en cours de route, c’est bon pour moi.
|
||||
name='docket',
|
||||
field=models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.PROTECT, to='invoicing.paymentdocket'
|
||||
),
|
||||
),
|
||||
]
|
|
@ -52,10 +52,6 @@ from lingo.utils.wcs import (
|
|||
)
|
||||
|
||||
|
||||
class RegieImportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PoolPromotionError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
@ -329,6 +325,11 @@ class Regie(WithApplicationMixin, models.Model):
|
|||
default='R{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
max_length=100,
|
||||
)
|
||||
docket_number_format = models.CharField(
|
||||
_('Payment docket number format'),
|
||||
default='B{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
max_length=100,
|
||||
)
|
||||
credit_number_format = models.CharField(
|
||||
_('Credit number format'),
|
||||
default='A{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
|
@ -385,6 +386,17 @@ class Regie(WithApplicationMixin, models.Model):
|
|||
'cashier': self.cashier_role.name if self.cashier_role else None,
|
||||
},
|
||||
'payer': self.payer.slug if self.payer else None,
|
||||
'counter_name': self.counter_name,
|
||||
'invoice_number_format': self.invoice_number_format,
|
||||
'payment_number_format': self.payment_number_format,
|
||||
'docket_number_format': self.docket_number_format,
|
||||
'credit_number_format': self.credit_number_format,
|
||||
'refund_number_format': self.refund_number_format,
|
||||
'invoice_model': self.invoice_model,
|
||||
'invoice_custom_text': self.invoice_custom_text,
|
||||
'invoice_main_colour': self.invoice_main_colour,
|
||||
'cashier_name': self.cashier_name,
|
||||
'city_name': self.city_name,
|
||||
'payment_types': [p.export_json() for p in self.paymenttype_set.all()],
|
||||
}
|
||||
|
||||
|
@ -399,10 +411,8 @@ class Regie(WithApplicationMixin, models.Model):
|
|||
if role_name:
|
||||
try:
|
||||
data['cashier_role'] = Group.objects.get(name=role_name)
|
||||
except Group.DoesNotExists:
|
||||
raise RegieImportError('Missing role: %s' % role_name)
|
||||
except Group.MultipleObjectsReturned:
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Ok, je ne savais pas que la définition du Ok, je ne savais pas que la définition du `Group` de `django.contrib.auth` impose l’unicité sur le nom, top.
|
||||
raise RegieImportError('Multiple role exist with the name: %s' % role_name)
|
||||
except Group.DoesNotExist:
|
||||
raise LingoImportError('Missing role: %s' % role_name)
|
||||
if data['payer']:
|
||||
try:
|
||||
data['payer'] = Payer.objects.get(slug=data['payer'])
|
||||
|
@ -852,6 +862,7 @@ class Counter(models.Model):
|
|||
('payment', _('Payment')),
|
||||
('credit', _('Credit')),
|
||||
('refund', _('Refund')),
|
||||
('docket', _('Payment Docket')),
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -1335,6 +1346,53 @@ class PaymentCancellationReason(models.Model):
|
|||
return slugify(self.label)
|
||||
|
||||
|
||||
class PaymentDocket(models.Model):
|
||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||
regie = models.ForeignKey(Regie, on_delete=models.PROTECT)
|
||||
number = models.PositiveIntegerField(default=0)
|
||||
formatted_number = models.CharField(max_length=200)
|
||||
|
||||
date_end = models.DateField(_('End date'))
|
||||
draft = models.BooleanField()
|
||||
payment_types = models.ManyToManyField(PaymentType)
|
||||
payment_types_info = models.JSONField(blank=True, default=dict)
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Note pour moi-même, aller voir à quoi ça correspond dans le code ajouté par les commits successifs. Note pour moi-même, aller voir à quoi ça correspond dans le code ajouté par les commits successifs.
pmarillonnet
commented
Ok, davantage capté, à la lecture des commits successifs, à quoi sert ce JSONField. Ok, davantage capté, à la lecture des commits successifs, à quoi sert ce JSONField.
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
if self.draft:
|
||||
return '%s-%s' % (_('TEMPORARY'), self.pk)
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Est-ce qu’on a pas intérêt à gérer (dans un autre ticket) aussi un format de brouillon paramétrable ? Est-ce qu’on est certain que le format fixé à 'TEMPORARY-' va convenir à coup sûr ? Est-ce qu’on a pas intérêt à gérer (dans un autre ticket) aussi un format de brouillon paramétrable ? Est-ce qu’on est certain que le format fixé à 'TEMPORARY-<pk>' va convenir à coup sûr ?
lguerin
commented
on numérote les factures temporaires aussi comme ça, et on ne veut pas consommer un compteur pour un draft on numérote les factures temporaires aussi comme ça, et on ne veut pas consommer un compteur pour un draft
pmarillonnet
commented
Ah oui il y a la gestion des compteurs aussi, c’est vrai. Ok. Ah oui il y a la gestion des compteurs aussi, c’est vrai. Ok.
|
||||
return self.formatted_number
|
||||
|
||||
def set_number(self):
|
||||
self.number = Counter.get_count(
|
||||
regie=self.regie,
|
||||
name=self.regie.get_counter_name(self.created_at),
|
||||
kind='docket',
|
||||
)
|
||||
self.formatted_number = self.regie.format_number(self.created_at, self.number, 'docket')
|
||||
|
||||
def get_active_payments(self):
|
||||
result = []
|
||||
for payment_type in self.payment_types.all():
|
||||
qs = self.payment_set.filter(payment_type=payment_type, cancelled_at__isnull=True).select_related(
|
||||
'payment_type'
|
||||
)
|
||||
result.append(
|
||||
{
|
||||
'payment_type': payment_type,
|
||||
'list': qs.order_by('-created_at'),
|
||||
'amount': qs.aggregate(amount=models.Sum('amount')),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def get_cancelled_payments(self):
|
||||
qs = self.payment_set.filter(cancelled_at__isnull=False).select_related('payment_type')
|
||||
return {'list': qs.order_by('-created_at'), 'amount': qs.aggregate(amount=models.Sum('amount'))}
|
||||
|
||||
|
||||
PAYMENT_INFO = [
|
||||
('check_issuer', _('Issuer')),
|
||||
('check_bank', _('Bank/Organism')),
|
||||
|
@ -1366,6 +1424,8 @@ class Payment(models.Model):
|
|||
)
|
||||
cancellation_description = models.TextField(_('Description'), blank=True)
|
||||
|
||||
docket = models.ForeignKey(PaymentDocket, on_delete=models.PROTECT, null=True)
|
||||
|
||||
order_date = models.DateTimeField(null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
{% extends "lingo/invoicing/manager_docket_list.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-detail' regie.pk object.pk %}">{{ object }}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{{ object }}</h2>
|
||||
{% if object.draft %}
|
||||
<span class="actions">
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a href="{% url 'lingo-manager-invoicing-regie-docket-delete' regie_pk=regie.pk pk=object.pk %}" rel="popup">{% trans "Delete" %}</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'lingo-manager-invoicing-regie-payment-list' regie_pk=regie.pk as regie_payment_list_url %}
|
||||
{% for value in object.get_active_payments %}
|
||||
{% if value.list %}
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% if object.draft %}
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-payment-type-edit' regie_pk=regie.pk pk=object.pk payment_type_pk=value.payment_type.pk %}">{{ value.payment_type }}</a>
|
||||
{% else %}
|
||||
{{ value.payment_type }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
<div>
|
||||
<p>
|
||||
{% trans "Number of payments:" %} {{ value.list|length }}
|
||||
<br />
|
||||
{% trans "Total amount:" %} {% blocktrans with amount=value.amount.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}
|
||||
<br />
|
||||
{% trans "Additionnal information:" %}
|
||||
<br />
|
||||
{{ object.payment_types_info|get:value.payment_type.slug|default:""|linebreaksbr }}
|
||||
</p>
|
||||
<table class="main pk-compact-table invoicing-element-list">
|
||||
{% for payment in value.list %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
{% blocktrans with payment_number=payment.formatted_number cdate=payment.created_at|date:'d/m/Y' payer_id=payment.payer_external_id payer_name=payment.payer_name amount=payment.amount payment_type=payment.payment_type %}Payment <a href="{{ regie_payment_list_url }}?number={{ payment_number }}">{{ payment_number }}</a> dated {{ cdate }} from {{ payer_name }}, amount {{ amount }}€ ({{ payment_type }}){% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% with object.get_cancelled_payments as cancelled %}
|
||||
{% if cancelled.list %}
|
||||
<div class="section">
|
||||
<h3>{% trans "Cancelled payments" %}</h3>
|
||||
<div>
|
||||
<p>
|
||||
{% trans "Number of payments:" %} {{ cancelled.list|length }}
|
||||
<br />
|
||||
{% trans "Total amount:" %} {% blocktrans with amount=cancelled.amount.amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}
|
||||
</p>
|
||||
<table class="main pk-compact-table invoicing-element-list">
|
||||
{% for payment in cancelled.list %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
{% blocktrans with payment_number=payment.formatted_number cdate=payment.created_at|date:'d/m/Y' payer_id=payment.payer_external_id payer_name=payment.payer_name amount=payment.amount payment_type=payment.payment_type %}Payment <a href="{{ regie_payment_list_url }}?number={{ payment_number }}">{{ payment_number }}</a> dated {{ cdate }} from {{ payer_name }}, amount {{ amount }}€ ({{ payment_type }}){% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "lingo/invoicing/manager_docket_payment_list.html" %}
|
||||
{% load gadjo i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
{% if form.instance.pk %}
|
||||
<a href="{# url 'lingo-manager-invoicing-regie-docket-edit' regie_pk=regie.pk pk=object.pk #}">{% trans "Edit" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-add' regie_pk=regie.pk %}">{% trans "New docket" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
{% if object.pk %}
|
||||
<h2>{% trans "Edit docket" %}</h2>
|
||||
{% else %}
|
||||
<h2>{% trans "New docket" %}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-regie-docket-payment-list' regie.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,55 @@
|
|||
{% extends "lingo/invoicing/manager_docket_payment_list.html" %}
|
||||
{% load gadjo i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-list' regie_pk=regie.pk %}">{% trans "Dockets" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Dockets" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if object_list %}
|
||||
<div>
|
||||
<table class="main pk-compact-table invoicing-element-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Number" %}</th>
|
||||
<th>{% trans "Payment types" %}</th>
|
||||
<th>{% trans "Number of payments" %}</th>
|
||||
<th>{% trans "End date" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for docket in object_list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-detail' regie_pk=regie.pk pk=docket.pk %}">{{ docket }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% for payment_type in docket.payment_types.all %}{{ payment_type }}{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% if docket.active_count %}<span class="meta meta-success">{{ docket.active_count }} ({% blocktrans with amount=docket.active_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %})</span>{% endif %}
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Ici juste ça va afficher un chiffre suivi d’un montant en euros entre parenthèses. Niveau UI c’est les classes Ici juste ça va afficher un chiffre suivi d’un montant en euros entre parenthèses. Niveau UI c’est les classes `meta-success` et `meta-warning` qui permettent de distinguer si on parle de paiements actifs ou de paiements annulés ?
Est-ce qu’on aurait pas intérêt à préciser à chaque fois, en toutes lettres, dans lequel des deux cas on se situe, genre `2 actifs (34.34 €)` ou `7 annulés (187.87 €)` ?
lguerin
commented
ça a été vu avec stef, il a validé en l'état; sur les campagnes aussi on a juste des nombres et une différence de couleur ça a été vu avec stef, il a validé en l'état; sur les campagnes aussi on a juste des nombres et une différence de couleur
on pourra toujours revenir dessus plus tard si besoin :)
pmarillonnet
commented
Ok, très bien alors, laissons comme ça. Ok, très bien alors, laissons comme ça.
|
||||
{% if docket.cancelled_count %}<span class="meta meta-warning">{{ docket.cancelled_count }} ({% blocktrans with amount=docket.cancelled_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %})</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ docket.date_end|date:"d/m/Y" }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% include "gadjo/pagination.html" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% trans "This site doesn't have any docket yet." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,60 @@
|
|||
{% extends "lingo/invoicing/manager_regie_detail.html" %}
|
||||
{% load gadjo i18n %}
|
||||
|
||||
{% block page-title-extra-label %}{% trans "Dockets" %} | {{ block.super }}{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-payment-list' regie_pk=regie.pk %}">{% trans "Payments outside dockets" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Payments outside dockets" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<div>
|
||||
<form class="invoicing-element-filters">
|
||||
<fieldset id="filters">
|
||||
<legend>{% trans "Payments outside dockets search" %}</legend>
|
||||
<div>
|
||||
{{ filterset.form|with_template }}
|
||||
<button class="submit-button">{% trans "Search" context 'form filtering action' %}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table class="main pk-compact-table invoicing-element-list">
|
||||
{% url 'lingo-manager-invoicing-regie-payment-list' regie_pk=regie.pk as regie_payment_list_url %}
|
||||
{% for payment in object_list %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
{% blocktrans with payment_number=payment.formatted_number cdate=payment.created_at|date:'d/m/Y' payer_id=payment.payer_external_id payer_name=payment.payer_name amount=payment.amount payment_type=payment.payment_type %}Payment <a href="{{ regie_payment_list_url }}?number={{ payment_number }}">{{ payment_number }}</a> dated {{ cdate }} from {{ payer_name }}, amount {{ amount }}€ ({{ payment_type }}){% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% include "gadjo/pagination.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<aside id="sidebar">
|
||||
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Peut-être ici un {% else %} avec un message indiquant que l’action de création d’un nouveau bordereau n’est pas disponible car il y a déjà des bordereaux à l’état de brouillons ? (Je vois déjà arriver les tickets clients “L’action « Nouveau bordereau » a disparu”). Peut-être ici un {% else %} avec un message indiquant que l’action de création d’un nouveau bordereau n’est pas disponible car il y a déjà des bordereaux à l’état de brouillons ? (Je vois déjà arriver les tickets clients “L’action « Nouveau bordereau » a disparu”).
lguerin
commented
je fais ça dans un fixup (avec une copie d'écran attaché à la PR) je fais ça dans un fixup (avec une copie d'écran attaché à la PR)
pmarillonnet
commented
Top, je te remercie. Top, je te remercie.
|
||||
{% if not has_draft %}
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-docket-add' regie_pk=regie.pk %}?{{ request.GET.urlencode }}">{% trans "New docket" %}</a>
|
||||
{% else %}
|
||||
<div class="paragraph">
|
||||
{% trans "The action to create a new docket is not available because a draft docket already exists." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-docket-list' regie_pk=regie.pk %}">{% trans "Dockets" %}</a>
|
||||
|
||||
</aside>
|
||||
{% endblock %}
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "lingo/invoicing/manager_docket_detail.html" %}
|
||||
{% load gadjo i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-docket-payment-type-edit' regie_pk=regie.pk pk=object.pk payment_type_pk=form.payment_type.pk %}">{{ form.payment_type }}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{{ form.payment_type }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-regie-docket-detail' regie.pk object.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -74,6 +74,14 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if payment.docket %}
|
||||
<tr class="line" data-related-invoicing-element-id="{{ payment.pk }}" style="display: none;">
|
||||
<td colspan="2"></td>
|
||||
<td class="payment-details" colspan="3">
|
||||
<i>{% trans "Docket:" %} <a href="{% url 'lingo-manager-invoicing-regie-docket-detail' regie_pk=regie.pk pk=payment.docket.pk %}">{{ payment.docket }}</a></i>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if not payment.cancelled_at %}
|
||||
<tr class="line last-line" data-related-invoicing-element-id="{{ payment.pk }}" style="display: none;">
|
||||
<td colspan="2"></td>
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-invoice-list' regie_pk=regie.pk %}">{% trans 'Invoices' %}</a>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-payment-list' regie_pk=regie.pk %}">{% trans 'Payments' %}</a>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-docket-payment-list' regie_pk=regie.pk %}">{% trans 'Dockets' %}</a>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-credit-list' regie_pk=regie.pk %}">{% trans 'Credits' %}</a>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-refund-list' regie_pk=regie.pk %}">{% trans 'Refunds' %}</a>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' regie_pk=regie.pk %}">{% trans 'Non invoiced lines' %}</a>
|
||||
|
|
|
@ -53,6 +53,8 @@
|
|||
<dd><code>{{ regie.invoice_number_format }}</code></dd>
|
||||
<dt><b>{% trans "Payment number format:" %}</b></dt>
|
||||
<dd><code>{{ regie.payment_number_format }}</code></dd>
|
||||
<dt><b>{% trans "Payment docket number format:" %}</b></dt>
|
||||
<dd><code>{{ regie.docket_number_format }}</code></dd>
|
||||
<dt><b>{% trans "Credit number format:" %}</b></dt>
|
||||
<dd><code>{{ regie.credit_number_format }}</code></dd>
|
||||
<dt><b>{% trans "Refund number format:" %}</b></dt>
|
||||
|
|
|
@ -208,6 +208,36 @@ urlpatterns = [
|
|||
regie_views.regie_payment_cancel,
|
||||
name='lingo-manager-invoicing-regie-payment-cancel',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/dockets/payments/',
|
||||
regie_views.regie_docket_payment_list,
|
||||
name='lingo-manager-invoicing-regie-docket-payment-list',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/dockets/',
|
||||
regie_views.regie_docket_list,
|
||||
name='lingo-manager-invoicing-regie-docket-list',
|
||||
),
|
||||
path(
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Je loupe peut-être un truc mais j’ai l’impression qu’il manquerait une vue pour éditer un bordereau existant (je vois bien des vues du genre DocketAdd, DocketDelete, DocketList et DocketDetails pas mais de vue DocketEdit). C’est volontaire genre c’est un objet qu’on n’est pas censé éditer directement ainsi (seulement le type de paiement via Je loupe peut-être un truc mais j’ai l’impression qu’il manquerait une vue pour éditer un bordereau existant (je vois bien des vues du genre DocketAdd, DocketDelete, DocketList et DocketDetails pas mais de vue DocketEdit). C’est volontaire genre c’est un objet qu’on n’est pas censé éditer directement ainsi (seulement le type de paiement via `RegieDocketPaymentTypeEditView`), ou bien c’est un oubli ?
pmarillonnet
commented
Ah, c’est la PR suivante, #88699, top. Ah, c’est la PR suivante, #88699, top.
|
||||
'regie/<int:regie_pk>/docket/add/',
|
||||
regie_views.regie_docket_add,
|
||||
name='lingo-manager-invoicing-regie-docket-add',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/docket/<int:pk>/',
|
||||
regie_views.regie_docket_detail,
|
||||
name='lingo-manager-invoicing-regie-docket-detail',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/docket/<int:pk>/payment-type/<int:payment_type_pk>/',
|
||||
regie_views.regie_docket_payment_type_edit,
|
||||
name='lingo-manager-invoicing-regie-docket-payment-type-edit',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/docket/<int:pk>/delete/',
|
||||
regie_views.regie_docket_delete,
|
||||
name='lingo-manager-invoicing-regie-docket-delete',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/credits/',
|
||||
regie_views.regie_credit_list,
|
||||
|
|
|
@ -19,8 +19,21 @@ import csv
|
|||
import datetime
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import CharField, IntegerField, JSONField, Prefetch, Q, Value
|
||||
from django.http import HttpResponse
|
||||
from django.db.models import (
|
||||
CharField,
|
||||
Count,
|
||||
DecimalField,
|
||||
IntegerField,
|
||||
JSONField,
|
||||
OuterRef,
|
||||
Prefetch,
|
||||
Q,
|
||||
Subquery,
|
||||
Sum,
|
||||
Value,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.defaultfilters import yesno
|
||||
from django.urls import reverse
|
||||
|
@ -30,8 +43,11 @@ from django.views.generic import CreateView, DeleteView, DetailView, ListView, U
|
|||
from lingo.agendas.models import Agenda
|
||||
from lingo.export_import.views import WithApplicationsMixin
|
||||
from lingo.invoicing.forms import (
|
||||
PaymentDocketForm,
|
||||
PaymentDocketPaymentTypeForm,
|
||||
PaymentTypeForm,
|
||||
RegieCreditFilterSet,
|
||||
RegieDocketPaymentFilterSet,
|
||||
RegieForm,
|
||||
RegieInvoiceFilterSet,
|
||||
RegiePaymentCancelForm,
|
||||
|
@ -49,6 +65,7 @@ from lingo.invoicing.models import (
|
|||
InvoiceLinePayment,
|
||||
JournalLine,
|
||||
Payment,
|
||||
PaymentDocket,
|
||||
PaymentType,
|
||||
Pool,
|
||||
Refund,
|
||||
|
@ -159,6 +176,7 @@ class RegieCountersEditView(UpdateView):
|
|||
'counter_name',
|
||||
'invoice_number_format',
|
||||
'payment_number_format',
|
||||
'docket_number_format',
|
||||
'credit_number_format',
|
||||
'refund_number_format',
|
||||
]
|
||||
|
@ -598,6 +616,7 @@ class RegiePaymentListView(ListView):
|
|||
queryset=invoice_line_payment_queryset,
|
||||
to_attr='prefetched_invoicelinepayments',
|
||||
),
|
||||
'docket',
|
||||
)
|
||||
.order_by('-created_at'),
|
||||
regie=self.regie,
|
||||
|
@ -733,6 +752,204 @@ class RegiePaymentCancelView(UpdateView):
|
|||
regie_payment_cancel = RegiePaymentCancelView.as_view()
|
||||
|
||||
|
||||
class RegieDocketPaymentListView(ListView):
|
||||
template_name = 'lingo/invoicing/manager_docket_payment_list.html'
|
||||
paginate_by = 100
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.filterset = RegieDocketPaymentFilterSet(
|
||||
data=self.request.GET or None,
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
J’ai du mal à comprendre le besoin fonctionnel d’exposer une liste des “paiements hors bordereaux” (“payments outside dockets”). Ça correspond à quoi en réalité ? Edit: ok, pigé dans la suite de la relecture des commits successifs de cette PR. J’ai du mal à comprendre le besoin fonctionnel d’exposer une liste des “paiements hors bordereaux” (“payments outside dockets”). Ça correspond à quoi en réalité ?
Edit: ok, pigé dans la suite de la relecture des commits successifs de cette PR.
|
||||
queryset=Payment.objects.filter(regie=self.regie, docket__isnull=True, cancelled_at__isnull=True)
|
||||
.select_related('payment_type')
|
||||
.order_by('-created_at'),
|
||||
regie=self.regie,
|
||||
)
|
||||
if not self.filterset.form.is_valid():
|
||||
return Payment.objects.none()
|
||||
return self.filterset.qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['regie'] = self.regie
|
||||
kwargs['filterset'] = self.filterset
|
||||
kwargs['has_draft'] = self.regie.paymentdocket_set.filter(draft=True).exists()
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
regie_docket_payment_list = RegieDocketPaymentListView.as_view()
|
||||
|
||||
|
||||
class RegieDocketListView(ListView):
|
||||
template_name = 'lingo/invoicing/manager_docket_list.html'
|
||||
model = PaymentDocket
|
||||
paginate_by = 100
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
payments = Payment.objects.filter(docket=OuterRef('pk')).order_by().values('docket')
|
||||
active_count = (
|
||||
payments.filter(cancelled_at__isnull=True).annotate(count=Count('docket')).values('count')
|
||||
)
|
||||
active_amount = (
|
||||
payments.filter(cancelled_at__isnull=True).annotate(total=Sum('amount')).values('total')
|
||||
)
|
||||
cancelled_count = (
|
||||
payments.filter(cancelled_at__isnull=False).annotate(count=Count('docket')).values('count')
|
||||
)
|
||||
cancelled_amount = (
|
||||
payments.filter(cancelled_at__isnull=False).annotate(total=Sum('amount')).values('total')
|
||||
)
|
||||
return (
|
||||
self.regie.paymentdocket_set.all()
|
||||
.prefetch_related('payment_types')
|
||||
.annotate(
|
||||
active_count=Coalesce(Subquery(active_count, output_field=IntegerField()), Value(0)),
|
||||
cancelled_count=Coalesce(Subquery(cancelled_count, output_field=IntegerField()), Value(0)),
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
On écrase les centimes ici en envoyant ça dans un On écrase les centimes ici en envoyant ça dans un `IntegerField` au lieu d’un flottant, non ? (pareil pour la ligne du dessous)
lguerin
commented
bien vu, merci bien vu, merci
lguerin
commented
fixup fixup
|
||||
active_amount=Coalesce(
|
||||
Subquery(active_amount, output_field=DecimalField(max_digits=9, decimal_places=2)),
|
||||
Value(0),
|
||||
output_field=DecimalField(max_digits=9, decimal_places=2),
|
||||
),
|
||||
cancelled_amount=Coalesce(
|
||||
Subquery(cancelled_amount, output_field=DecimalField(max_digits=9, decimal_places=2)),
|
||||
Value(0),
|
||||
output_field=DecimalField(max_digits=9, decimal_places=2),
|
||||
),
|
||||
)
|
||||
.order_by('-created_at')
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['regie'] = self.regie
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
regie_docket_list = RegieDocketListView.as_view()
|
||||
|
||||
|
||||
class RegieDocketAddView(CreateView):
|
||||
template_name = 'lingo/invoicing/manager_docket_form.html'
|
||||
model = PaymentDocket
|
||||
form_class = PaymentDocketForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
|
||||
if self.regie.paymentdocket_set.filter(draft=True).exists():
|
||||
raise Http404
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['regie'] = self.regie
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
if not kwargs.get('instance'):
|
||||
kwargs['instance'] = self.model()
|
||||
kwargs['instance'].regie = self.regie
|
||||
kwargs['instance'].draft = True
|
||||
kwargs['regie'] = self.regie
|
||||
pmarillonnet marked this conversation as resolved
Outdated
pmarillonnet
commented
Ça correspond à quelle disjonction, ce test Ça correspond à quelle disjonction, ce test `request.GET` évalué à vrai ou non, sans chercher davantage à creuser ce que ce dictionnaire contiendrait ? Ça correspond à quels cas ?
À vue de nez on dirait qu’on cherche à faire dans `def get(…):` ce qu’une CreateView opérerait normalement dans `def post(…):`.
lguerin
commented
effectivement, je voulais qu'au click sur le bouton "créer", s'il y a des trucs dans request.GET, qui viennent du listing des paiements non remis et du filtrage qui a été fait, et qu'il n'y a pas d'erreur, faire ce qui est fait normalement en POST. En gros, zapper la page de présentation du formulaire qu'on doit submit, pour éviter de remontrer à l'usager un formulaire qu'il a déjà renseigné sur la page de listing effectivement, je voulais qu'au click sur le bouton "créer", s'il y a des trucs dans request.GET, qui viennent du listing des paiements non remis et du filtrage qui a été fait, et qu'il n'y a pas d'erreur, faire ce qui est fait normalement en POST. En gros, zapper la page de présentation du formulaire qu'on doit submit, pour éviter de remontrer à l'usager un formulaire qu'il a déjà renseigné sur la page de listing
pmarillonnet
commented
Ah oui ok, je comprends mieux, merci. Ah oui ok, je comprends mieux, merci.
|
||||
return kwargs
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
form = self.get_form_class()(
|
||||
data={
|
||||
'payment_types': request.GET.getlist('payment_type'),
|
||||
'date_end': request.GET.get('date_end'),
|
||||
}
|
||||
if request.GET
|
||||
else None,
|
||||
**self.get_form_kwargs(),
|
||||
)
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-regie-docket-detail', args=[self.regie.pk, self.object.pk])
|
||||
|
||||
|
||||
regie_docket_add = RegieDocketAddView.as_view()
|
||||
|
||||
|
||||
class RegieDocketDetailView(DetailView):
|
||||
template_name = 'lingo/invoicing/manager_docket_detail.html'
|
||||
model = PaymentDocket
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.regie.paymentdocket_set.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['regie'] = self.regie
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
regie_docket_detail = RegieDocketDetailView.as_view()
|
||||
|
||||
|
||||
class RegieDocketPaymentTypeEditView(UpdateView):
|
||||
template_name = 'lingo/invoicing/manager_docket_payment_type_form.html'
|
||||
model = PaymentDocket
|
||||
form_class = PaymentDocketPaymentTypeForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.regie.paymentdocket_set.filter(draft=True)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['regie'] = self.regie
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['payment_type'] = get_object_or_404(
|
||||
self.object.payment_types, pk=self.kwargs['payment_type_pk']
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-regie-docket-detail', args=[self.regie.pk, self.object.pk])
|
||||
|
||||
|
||||
regie_docket_payment_type_edit = RegieDocketPaymentTypeEditView.as_view()
|
||||
|
||||
|
||||
class RegieDocketDeleteView(DeleteView):
|
||||
template_name = 'lingo/manager_confirm_delete.html'
|
||||
model = PaymentDocket
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.regie.paymentdocket_set.filter(draft=True)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
self.object.payment_set.update(docket=None)
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-regie-docket-list', args=[self.regie.pk])
|
||||
|
||||
|
||||
regie_docket_delete = RegieDocketDeleteView.as_view()
|
||||
|
||||
|
||||
class RegieCreditListView(ListView):
|
||||
template_name = 'lingo/invoicing/manager_credit_list.html'
|
||||
paginate_by = 100
|
||||
|
|
|
@ -0,0 +1,727 @@
|
|||
import datetime
|
||||
import decimal
|
||||
|
||||
import pytest
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import localtime, now
|
||||
from pyquery import PyQuery
|
||||
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.models import (
|
||||
AppearanceSettings,
|
||||
Credit,
|
||||
CreditAssignment,
|
||||
CreditLine,
|
||||
Invoice,
|
||||
Payment,
|
||||
PaymentType,
|
||||
Refund,
|
||||
Regie,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_regie_credits(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
Agenda.objects.create(label='Agenda A', regie=regie)
|
||||
Agenda.objects.create(label='Agenda B', regie=regie)
|
||||
PaymentType.create_defaults(regie)
|
||||
invoice = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
regie=regie,
|
||||
)
|
||||
credit1 = Credit.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit1.set_number()
|
||||
credit1.save()
|
||||
credit2 = Credit.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:2',
|
||||
payer_first_name='First2',
|
||||
payer_last_name='Name2',
|
||||
payer_address='42 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit2.set_number()
|
||||
credit2.save()
|
||||
credit3 = Credit.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
payer_address='43 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit3.set_number()
|
||||
credit3.save()
|
||||
|
||||
CreditLine.objects.create(
|
||||
slug='event-a-foo-bar',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit1,
|
||||
quantity=1.2,
|
||||
unit_amount=1,
|
||||
label='Event A',
|
||||
description='A description',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
event_slug='agenda-a@event-a',
|
||||
agenda_slug='agenda-a',
|
||||
activity_label='Agenda A',
|
||||
)
|
||||
CreditLine.objects.create(
|
||||
slug='event-b-foo-bar',
|
||||
event_date=datetime.date(2022, 9, 2),
|
||||
credit=credit1,
|
||||
quantity=1,
|
||||
unit_amount=2,
|
||||
label='Event B',
|
||||
user_external_id='user:2',
|
||||
user_first_name='User2',
|
||||
user_last_name='Name2',
|
||||
event_slug='agenda-b@event-b',
|
||||
agenda_slug='agenda-b',
|
||||
activity_label='Agenda B',
|
||||
)
|
||||
CreditLine.objects.create(
|
||||
slug='event-a-foo-bar',
|
||||
event_date=datetime.date(2022, 9, 3),
|
||||
credit=credit1,
|
||||
quantity=1,
|
||||
unit_amount=3,
|
||||
label='Event A',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
event_slug='agenda-a@event-a',
|
||||
agenda_slug='agenda-a',
|
||||
activity_label='Agenda A',
|
||||
)
|
||||
payment1 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=1,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='credit'),
|
||||
)
|
||||
payment1.set_number()
|
||||
payment1.save()
|
||||
credit_assignment1 = CreditAssignment.objects.create(
|
||||
invoice=invoice,
|
||||
payment=payment1,
|
||||
credit=credit1,
|
||||
amount=1,
|
||||
)
|
||||
refund = Refund.objects.create(
|
||||
regie=regie,
|
||||
amount=5.2,
|
||||
)
|
||||
refund.set_number()
|
||||
refund.save()
|
||||
credit_assignment2 = CreditAssignment.objects.create(
|
||||
refund=refund,
|
||||
credit=credit1,
|
||||
amount=5.2,
|
||||
)
|
||||
credit1.refresh_from_db()
|
||||
assert credit1.remaining_amount == 0
|
||||
assert credit1.assigned_amount == decimal.Decimal('6.2')
|
||||
|
||||
CreditLine.objects.create(
|
||||
slug='agenda-a@event-aa',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit2,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
label='Event AA',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
event_slug='agenda-a@event-aa',
|
||||
agenda_slug='agenda-a',
|
||||
activity_label='Agenda A',
|
||||
)
|
||||
CreditAssignment.objects.create(
|
||||
invoice=invoice,
|
||||
credit=credit2,
|
||||
amount=0.5,
|
||||
)
|
||||
credit2.refresh_from_db()
|
||||
assert credit2.remaining_amount == 0.5
|
||||
assert credit2.assigned_amount == 0.5
|
||||
|
||||
CreditLine.objects.create(
|
||||
slug='injected',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit3,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
label='Event A',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
credit3.refresh_from_db()
|
||||
assert credit3.remaining_amount == 1
|
||||
assert credit3.assigned_amount == 0
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/credits/' % regie.pk)
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % credit1.pk
|
||||
).text() == 'Assigned Credit A%02d-%s-0000001 dated %s for First1 Name1, amount 6.20€ - download' % (
|
||||
regie.pk,
|
||||
credit1.created_at.strftime('%y-%m'),
|
||||
credit1.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert len(resp.pyquery('tr[data-invoicing-element-id="%s"] a' % credit1.pk)) == 2
|
||||
lines_url = resp.pyquery('tr[data-invoicing-element-id="%s"]' % credit1.pk).attr(
|
||||
'data-invoicing-element-lines-url'
|
||||
)
|
||||
assert lines_url == '/manage/invoicing/ajax/regie/%s/credit/%s/lines/' % (
|
||||
regie.pk,
|
||||
credit1.pk,
|
||||
)
|
||||
lines_resp = app.get(lines_url)
|
||||
assert len(lines_resp.pyquery('tr')) == 14
|
||||
assert [PyQuery(tr).text() for tr in lines_resp.pyquery('tr')] == [
|
||||
'User1 Name1',
|
||||
'Description\nAmount\nQuantity\nSubtotal',
|
||||
'Agenda A',
|
||||
'Event A\nA description\n1.00€\n1.2\n1.20€',
|
||||
'Event A\n3.00€\n1\n3.00€',
|
||||
'User2 Name2',
|
||||
'Description\nAmount\nQuantity\nSubtotal',
|
||||
'Agenda B',
|
||||
'Event B\n2.00€\n1\n2.00€',
|
||||
'Assignments',
|
||||
'Payment\nDate\nAmount',
|
||||
'R%02d-%s-0000001\n%s\n1.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
payment1.created_at.strftime('%y-%m'),
|
||||
date_format(localtime(credit_assignment1.created_at), 'DATETIME_FORMAT'),
|
||||
),
|
||||
'V%02d-%s-0000001 (Refund)\n%s\n5.20€'
|
||||
% (
|
||||
regie.pk,
|
||||
refund.created_at.strftime('%y-%m'),
|
||||
date_format(localtime(credit_assignment2.created_at), 'DATETIME_FORMAT'),
|
||||
),
|
||||
'Assigned amount: 6.20€',
|
||||
]
|
||||
assert [PyQuery(a).attr('href') for a in lines_resp.pyquery('tr a')] == [
|
||||
'/manage/invoicing/regie/%s/payments/?number=R%s-%s-0000001'
|
||||
% (
|
||||
regie.pk,
|
||||
regie.pk,
|
||||
payment1.created_at.strftime('%y-%m'),
|
||||
),
|
||||
'/manage/invoicing/regie/%s/refunds/?number=V%s-%s-0000001'
|
||||
% (
|
||||
regie.pk,
|
||||
regie.pk,
|
||||
refund.created_at.strftime('%y-%m'),
|
||||
),
|
||||
]
|
||||
assert len(resp.pyquery('tr[data-invoicing-element-id="%s"] a' % credit2.pk)) == 2
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % credit2.pk
|
||||
).text() == 'Partially assigned Credit A%02d-%s-0000002 dated %s for First2 Name2, amount 1.00€ - download' % (
|
||||
regie.pk,
|
||||
credit2.created_at.strftime('%y-%m'),
|
||||
credit2.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
lines_url = resp.pyquery('tr[data-invoicing-element-id="%s"]' % credit2.pk).attr(
|
||||
'data-invoicing-element-lines-url'
|
||||
)
|
||||
assert lines_url == '/manage/invoicing/ajax/regie/%s/credit/%s/lines/' % (
|
||||
regie.pk,
|
||||
credit2.pk,
|
||||
)
|
||||
lines_resp = app.get(lines_url)
|
||||
assert len(lines_resp.pyquery('tr')) == 9
|
||||
assert [PyQuery(tr).text() for tr in lines_resp.pyquery('tr')] == [
|
||||
'User1 Name1',
|
||||
'Description\nAmount\nQuantity\nSubtotal',
|
||||
'Agenda A',
|
||||
'Event AA\n1.00€\n1\n1.00€',
|
||||
'Assignments',
|
||||
'Payment\nDate\nAmount',
|
||||
'Pending...\n0.50€',
|
||||
'Assigned amount: 0.50€',
|
||||
'Remaining amount to assign: 0.50€',
|
||||
]
|
||||
assert [PyQuery(a).attr('href') for a in lines_resp.pyquery('tr a')] == []
|
||||
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % credit3.pk
|
||||
).text() == 'Credit A%02d-%s-0000003 dated %s for First3 Name3, amount 1.00€ - download' % (
|
||||
regie.pk,
|
||||
credit3.created_at.strftime('%y-%m'),
|
||||
credit3.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert len(resp.pyquery('tr[data-invoicing-element-id="%s"] a' % credit3.pk)) == 2
|
||||
lines_url = resp.pyquery('tr[data-invoicing-element-id="%s"]' % credit3.pk).attr(
|
||||
'data-invoicing-element-lines-url'
|
||||
)
|
||||
assert lines_url == '/manage/invoicing/ajax/regie/%s/credit/%s/lines/' % (
|
||||
regie.pk,
|
||||
credit3.pk,
|
||||
)
|
||||
lines_resp = app.get(lines_url)
|
||||
assert len(lines_resp.pyquery('tr')) == 7
|
||||
assert [PyQuery(tr).text() for tr in lines_resp.pyquery('tr')] == [
|
||||
'User1 Name1',
|
||||
'Description\nAmount\nQuantity\nSubtotal',
|
||||
'Event A\n1.00€\n1\n1.00€',
|
||||
'Assignments',
|
||||
'Payment\nDate\nAmount',
|
||||
'No assignments for this credit',
|
||||
'Remaining amount to assign: 1.00€',
|
||||
]
|
||||
|
||||
# test filters
|
||||
today = now().date()
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
yesterday = today - datetime.timedelta(days=1)
|
||||
params = [
|
||||
({'number': credit1.formatted_number}, 1),
|
||||
({'number': credit1.created_at.strftime('%y-%m')}, 3),
|
||||
({'created_at_after': today.strftime('%Y-%m-%d')}, 3),
|
||||
({'created_at_after': tomorrow.strftime('%Y-%m-%d')}, 0),
|
||||
({'created_at_before': yesterday.strftime('%Y-%m-%d')}, 0),
|
||||
({'created_at_before': today.strftime('%Y-%m-%d')}, 3),
|
||||
({'payment_number': payment1.formatted_number}, 1),
|
||||
({'payment_number': payment1.created_at.strftime('%y-%m')}, 1),
|
||||
({'payer_external_id': 'payer:1'}, 1),
|
||||
({'payer_external_id': 'payer:2'}, 1),
|
||||
({'payer_first_name': 'first'}, 3),
|
||||
({'payer_first_name': 'first1'}, 1),
|
||||
({'payer_last_name': 'name'}, 3),
|
||||
({'payer_last_name': 'name1'}, 1),
|
||||
({'user_external_id': 'user:1'}, 3),
|
||||
({'user_external_id': 'user:2'}, 1),
|
||||
({'user_first_name': 'user'}, 3),
|
||||
({'user_first_name': 'user2'}, 1),
|
||||
({'user_last_name': 'name'}, 3),
|
||||
({'user_last_name': 'name1'}, 3),
|
||||
(
|
||||
{
|
||||
'total_amount_min': '1',
|
||||
'total_amount_min_lookup': 'gt',
|
||||
},
|
||||
1,
|
||||
),
|
||||
(
|
||||
{
|
||||
'total_amount_min': '1',
|
||||
'total_amount_min_lookup': 'gte',
|
||||
},
|
||||
3,
|
||||
),
|
||||
(
|
||||
{
|
||||
'total_amount_max': '6.2',
|
||||
'total_amount_max_lookup': 'lt',
|
||||
},
|
||||
2,
|
||||
),
|
||||
(
|
||||
{
|
||||
'total_amount_max': '6.2',
|
||||
'total_amount_max_lookup': 'lte',
|
||||
},
|
||||
3,
|
||||
),
|
||||
({'assigned': 'yes'}, 1),
|
||||
({'assigned': 'partially'}, 1),
|
||||
({'assigned': 'no'}, 1),
|
||||
({'agenda': 'agenda-a'}, 2),
|
||||
({'agenda': 'agenda-b'}, 1),
|
||||
({'event': 'agenda-a@event-a'}, 1),
|
||||
({'event': 'agenda-a@event-aa'}, 1),
|
||||
({'event': 'agenda-b@event-b'}, 1),
|
||||
]
|
||||
for param, result in params:
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/credits/' % regie.pk,
|
||||
params=param,
|
||||
)
|
||||
assert len(resp.pyquery('tr.credit')) == result
|
||||
|
||||
|
||||
def test_regie_credit_pdf(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo', invoice_main_colour='#9141ac')
|
||||
credit = Credit.objects.create(
|
||||
label='Credit from 01/09/2022',
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit.set_number()
|
||||
credit.save()
|
||||
|
||||
CreditLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit,
|
||||
quantity=1.2,
|
||||
unit_amount=1,
|
||||
label='Label 11',
|
||||
description='A description',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
event_slug='agenda-a@event-a',
|
||||
agenda_slug='agenda-a',
|
||||
activity_label='Agenda A',
|
||||
)
|
||||
CreditLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 2),
|
||||
credit=credit,
|
||||
quantity=1,
|
||||
unit_amount=2,
|
||||
label='Label 12',
|
||||
user_external_id='user:2',
|
||||
user_first_name='User2',
|
||||
user_last_name='Name2',
|
||||
event_slug='agenda-a@event-a',
|
||||
agenda_slug='agenda-a',
|
||||
activity_label='Agenda A',
|
||||
)
|
||||
CreditLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 3),
|
||||
credit=credit,
|
||||
quantity=1,
|
||||
unit_amount=3,
|
||||
label='Label 13',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
event_slug='agenda-b@event-b',
|
||||
agenda_slug='agenda-b',
|
||||
activity_label='Agenda B',
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/credit/%s/pdf/?html' % (regie.pk, credit.pk))
|
||||
assert 'color: #9141ac;' in resp
|
||||
assert resp.pyquery('#document-label').text() == 'Credit from 01/09/2022'
|
||||
assert resp.pyquery('#regie-label').text() == 'Foo'
|
||||
assert resp.pyquery('address#to').text() == 'First1 Name1\n41 rue des kangourous\n99999 Kangourou Ville'
|
||||
assert resp.pyquery('dl#informations').text() == 'Credit number:\nA%02d-%s-0000001\nDate:\n%s' % (
|
||||
regie.pk,
|
||||
credit.created_at.strftime('%y-%m'),
|
||||
date_format(localtime(credit.created_at), 'DATE_FORMAT'),
|
||||
)
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery('table#lines tr')] == [
|
||||
'User1 Name1',
|
||||
'Services\nDetails\nUA\nQTY\nTA',
|
||||
'Label 11\nAgenda A\n\nA description\n1.00€\n1.2\n1.20€',
|
||||
'Label 13\nAgenda B\n\n3.00€\n1\n3.00€',
|
||||
'User2 Name2',
|
||||
'Services\nDetails\nUA\nQTY\nTA',
|
||||
'Label 12\nAgenda A\n\n2.00€\n1\n2.00€',
|
||||
'Total amount:\n6.20€',
|
||||
]
|
||||
|
||||
app.get('/manage/invoicing/regie/%s/credit/%s/pdf/?html' % (0, credit.pk), status=404)
|
||||
app.get('/manage/invoicing/regie/%s/credit/%s/pdf/?html' % (regie.pk, 0), status=404)
|
||||
other_regie = Regie.objects.create(label='Foo')
|
||||
app.get('/manage/invoicing/regie/%s/credit/%s/pdf/?html' % (other_regie.pk, credit.pk), status=404)
|
||||
|
||||
appearance_settings = AppearanceSettings.singleton()
|
||||
appearance_settings.address = '<p>Foo bar<br>Streetname</p>'
|
||||
appearance_settings.extra_info = '<p>Opening hours...</p>'
|
||||
appearance_settings.save()
|
||||
resp = app.get('/manage/invoicing/regie/%s/credit/%s/pdf/?html' % (regie.pk, credit.pk))
|
||||
assert appearance_settings.address in resp.text
|
||||
assert appearance_settings.extra_info in resp.text
|
||||
|
||||
|
||||
def test_regie_refunds(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
Agenda.objects.create(label='Agenda A', regie=regie)
|
||||
Agenda.objects.create(label='Agenda B', regie=regie)
|
||||
PaymentType.create_defaults(regie)
|
||||
invoice = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
regie=regie,
|
||||
)
|
||||
credit1 = Credit.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit1.set_number()
|
||||
credit1.save()
|
||||
credit2 = Credit.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:2',
|
||||
payer_first_name='First2',
|
||||
payer_last_name='Name2',
|
||||
payer_address='42 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit2.set_number()
|
||||
credit2.save()
|
||||
credit3 = Credit.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
payer_address='43 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
credit3.set_number()
|
||||
credit3.save()
|
||||
|
||||
CreditLine.objects.create(
|
||||
slug='event-a-foo-bar',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit1,
|
||||
quantity=1.2,
|
||||
unit_amount=1,
|
||||
label='Event A',
|
||||
description='A description',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
CreditLine.objects.create(
|
||||
slug='event-b-foo-bar',
|
||||
event_date=datetime.date(2022, 9, 2),
|
||||
credit=credit1,
|
||||
quantity=1,
|
||||
unit_amount=2,
|
||||
label='Event B',
|
||||
user_external_id='user:2',
|
||||
user_first_name='User2',
|
||||
user_last_name='Name2',
|
||||
)
|
||||
CreditLine.objects.create(
|
||||
slug='event-a-foo-bar',
|
||||
event_date=datetime.date(2022, 9, 3),
|
||||
credit=credit1,
|
||||
quantity=1,
|
||||
unit_amount=3,
|
||||
label='Event A',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
refund1 = Refund.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
amount=6.2,
|
||||
)
|
||||
refund1.set_number()
|
||||
refund1.save()
|
||||
CreditAssignment.objects.create(
|
||||
refund=refund1,
|
||||
credit=credit1,
|
||||
amount=6.2,
|
||||
)
|
||||
credit1.refresh_from_db()
|
||||
assert credit1.remaining_amount == 0
|
||||
assert credit1.assigned_amount == decimal.Decimal('6.2')
|
||||
|
||||
CreditLine.objects.create(
|
||||
slug='agenda-a@event-aa',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit2,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
label='Event AA',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
payment2 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=0.5,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='credit'),
|
||||
)
|
||||
payment2.set_number()
|
||||
payment2.save()
|
||||
CreditAssignment.objects.create(
|
||||
invoice=invoice,
|
||||
payment=payment2,
|
||||
credit=credit2,
|
||||
amount=0.5,
|
||||
)
|
||||
refund2 = Refund.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:2',
|
||||
payer_first_name='First2',
|
||||
payer_last_name='Name2',
|
||||
payer_address='42 rue des kangourous\n99999 Kangourou Ville',
|
||||
amount=0.5,
|
||||
)
|
||||
refund2.set_number()
|
||||
refund2.save()
|
||||
CreditAssignment.objects.create(
|
||||
refund=refund2,
|
||||
credit=credit2,
|
||||
amount=0.5,
|
||||
)
|
||||
credit2.refresh_from_db()
|
||||
assert credit2.remaining_amount == 0
|
||||
assert credit2.assigned_amount == 1
|
||||
|
||||
CreditLine.objects.create(
|
||||
slug='injected',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
credit=credit3,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
label='Event A',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
refund3 = Refund.objects.create(
|
||||
regie=regie,
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
payer_address='43 rue des kangourous\n99999 Kangourou Ville',
|
||||
amount=1,
|
||||
)
|
||||
refund3.set_number()
|
||||
refund3.save()
|
||||
CreditAssignment.objects.create(
|
||||
refund=refund3,
|
||||
credit=credit3,
|
||||
amount=1,
|
||||
)
|
||||
credit3.refresh_from_db()
|
||||
assert credit3.remaining_amount == 0
|
||||
assert credit3.assigned_amount == 1
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/refunds/' % regie.pk)
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % refund1.pk
|
||||
).text() == 'Refund V%02d-%s-0000001 dated %s for First1 Name1, amount 6.20€' % (
|
||||
regie.pk,
|
||||
refund1.created_at.strftime('%y-%m'),
|
||||
refund1.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % refund1.pk)
|
||||
] == [
|
||||
'Credit\nDate\nCredit amount\nRefund amount',
|
||||
'A%02d-%s-0000001\n%s\n6.20€\n6.20€'
|
||||
% (
|
||||
regie.pk,
|
||||
credit1.created_at.strftime('%y-%m'),
|
||||
credit1.created_at.strftime('%d/%m/%Y'),
|
||||
),
|
||||
]
|
||||
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % refund2.pk
|
||||
).text() == 'Refund V%02d-%s-0000002 dated %s for First2 Name2, amount 0.50€' % (
|
||||
regie.pk,
|
||||
refund2.created_at.strftime('%y-%m'),
|
||||
refund2.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % refund2.pk)
|
||||
] == [
|
||||
'Credit\nDate\nCredit amount\nRefund amount',
|
||||
'A%02d-%s-0000002\n%s\n1.00€\n0.50€'
|
||||
% (
|
||||
regie.pk,
|
||||
credit2.created_at.strftime('%y-%m'),
|
||||
credit2.created_at.strftime('%d/%m/%Y'),
|
||||
),
|
||||
]
|
||||
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % refund3.pk
|
||||
).text() == 'Refund V%02d-%s-0000003 dated %s for First3 Name3, amount 1.00€' % (
|
||||
regie.pk,
|
||||
refund3.created_at.strftime('%y-%m'),
|
||||
refund3.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % refund3.pk)
|
||||
] == [
|
||||
'Credit\nDate\nCredit amount\nRefund amount',
|
||||
'A%02d-%s-0000003\n%s\n1.00€\n1.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
credit3.created_at.strftime('%y-%m'),
|
||||
credit3.created_at.strftime('%d/%m/%Y'),
|
||||
),
|
||||
]
|
||||
|
||||
# test filters
|
||||
today = now().date()
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
yesterday = today - datetime.timedelta(days=1)
|
||||
params = [
|
||||
({'number': refund1.formatted_number}, 1),
|
||||
({'number': refund1.created_at.strftime('%y-%m')}, 3),
|
||||
({'created_at_after': today.strftime('%Y-%m-%d')}, 3),
|
||||
({'created_at_after': tomorrow.strftime('%Y-%m-%d')}, 0),
|
||||
({'created_at_before': yesterday.strftime('%Y-%m-%d')}, 0),
|
||||
({'created_at_before': today.strftime('%Y-%m-%d')}, 3),
|
||||
({'credit_number': credit1.formatted_number}, 1),
|
||||
({'credit_number': credit1.created_at.strftime('%y-%m')}, 3),
|
||||
({'payer_external_id': 'payer:1'}, 1),
|
||||
({'payer_external_id': 'payer:2'}, 1),
|
||||
({'payer_first_name': 'first'}, 3),
|
||||
({'payer_first_name': 'first1'}, 1),
|
||||
({'payer_last_name': 'name'}, 3),
|
||||
({'payer_last_name': 'name1'}, 1),
|
||||
(
|
||||
{
|
||||
'amount_min': '1',
|
||||
'amount_min_lookup': 'gt',
|
||||
},
|
||||
1,
|
||||
),
|
||||
(
|
||||
{
|
||||
'amount_min': '1',
|
||||
'amount_min_lookup': 'gte',
|
||||
},
|
||||
2,
|
||||
),
|
||||
(
|
||||
{
|
||||
'amount_max': '6.2',
|
||||
'amount_max_lookup': 'lt',
|
||||
},
|
||||
2,
|
||||
),
|
||||
(
|
||||
{
|
||||
'amount_max': '6.2',
|
||||
'amount_max_lookup': 'lte',
|
||||
},
|
||||
3,
|
||||
),
|
||||
]
|
||||
for param, result in params:
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/refunds/' % regie.pk,
|
||||
params=param,
|
||||
)
|
||||
assert len(resp.pyquery('tr.refund')) == result
|
|
@ -0,0 +1,544 @@
|
|||
import datetime
|
||||
import decimal
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
from pyquery import PyQuery
|
||||
|
||||
from lingo.invoicing.models import Payment, PaymentDocket, PaymentType, Regie
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_regie_payments_outside_dockets(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
PaymentType.create_defaults(regie)
|
||||
docket = PaymentDocket.objects.create(regie=regie, date_end=now().date(), draft=True)
|
||||
|
||||
payment1 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=35,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
)
|
||||
payment1.created_at = now() - datetime.timedelta(days=2)
|
||||
payment1.set_number()
|
||||
payment1.save()
|
||||
|
||||
payment2 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=55,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
)
|
||||
payment2.created_at = now() - datetime.timedelta(days=1)
|
||||
payment2.set_number()
|
||||
payment2.save()
|
||||
|
||||
payment3 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='creditcard'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
)
|
||||
payment3.created_at = now() - datetime.timedelta(days=1)
|
||||
payment3.set_number()
|
||||
payment3.save()
|
||||
|
||||
payment4 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
)
|
||||
payment4.set_number()
|
||||
payment4.save()
|
||||
|
||||
payment5 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
cancelled_at=now(),
|
||||
)
|
||||
payment5.set_number()
|
||||
payment5.save()
|
||||
|
||||
payment6 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
docket=docket,
|
||||
)
|
||||
payment6.set_number()
|
||||
payment6.save()
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/' % regie.pk)
|
||||
resp = resp.click('Dockets')
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery('tr')] == [
|
||||
'Payment R%02d-%s-0000003 dated %s from First3 Name3, amount 2.00€ (Credit card)'
|
||||
% (regie.pk, payment3.created_at.strftime('%y-%m'), payment3.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000002 dated %s from First1 Name1, amount 55.00€ (Check)'
|
||||
% (regie.pk, payment2.created_at.strftime('%y-%m'), payment2.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000001 dated %s from First1 Name1, amount 35.00€ (Cash)'
|
||||
% (regie.pk, payment1.created_at.strftime('%y-%m'), payment1.created_at.strftime('%d/%m/%Y')),
|
||||
]
|
||||
|
||||
resp.form['date_end'] = now().date() + datetime.timedelta(days=1)
|
||||
resp = resp.form.submit()
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery('tr')] == [
|
||||
'Payment R%02d-%s-0000004 dated %s from First3 Name3, amount 2.00€ (Check)'
|
||||
% (regie.pk, payment4.created_at.strftime('%y-%m'), payment4.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000003 dated %s from First3 Name3, amount 2.00€ (Credit card)'
|
||||
% (regie.pk, payment3.created_at.strftime('%y-%m'), payment3.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000002 dated %s from First1 Name1, amount 55.00€ (Check)'
|
||||
% (regie.pk, payment2.created_at.strftime('%y-%m'), payment2.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000001 dated %s from First1 Name1, amount 35.00€ (Cash)'
|
||||
% (regie.pk, payment1.created_at.strftime('%y-%m'), payment1.created_at.strftime('%d/%m/%Y')),
|
||||
]
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
resp.form['payment_type'] = [
|
||||
PaymentType.objects.get(slug='cash').pk,
|
||||
PaymentType.objects.get(slug='check').pk,
|
||||
]
|
||||
resp.form['date_end'] = now().date() + datetime.timedelta(days=1)
|
||||
resp = resp.form.submit()
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery('tr')] == [
|
||||
'Payment R%02d-%s-0000004 dated %s from First3 Name3, amount 2.00€ (Check)'
|
||||
% (regie.pk, payment4.created_at.strftime('%y-%m'), payment4.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000002 dated %s from First1 Name1, amount 55.00€ (Check)'
|
||||
% (regie.pk, payment2.created_at.strftime('%y-%m'), payment2.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000001 dated %s from First1 Name1, amount 35.00€ (Cash)'
|
||||
% (regie.pk, payment1.created_at.strftime('%y-%m'), payment1.created_at.strftime('%d/%m/%Y')),
|
||||
]
|
||||
|
||||
|
||||
def test_regie_docket_list(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
PaymentType.create_defaults(regie)
|
||||
docket1 = PaymentDocket.objects.create(
|
||||
regie=regie, date_end=now().date() + datetime.timedelta(days=1), draft=False
|
||||
)
|
||||
docket1.set_number()
|
||||
docket1.save()
|
||||
docket2 = PaymentDocket.objects.create(
|
||||
regie=regie, date_end=now().date() + datetime.timedelta(days=1), draft=True
|
||||
)
|
||||
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=decimal.Decimal('35.5'),
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
docket=docket1,
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=42,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
docket=docket1,
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=43,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
docket=docket2,
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=decimal.Decimal('44.5'),
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='creditcard'),
|
||||
docket=docket2,
|
||||
cancelled_at=now(),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/' % regie.pk)
|
||||
resp = resp.click('Dockets')
|
||||
resp = resp.click('Dockets')
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery('tr')] == [
|
||||
'Number\nPayment types\nNumber of payments\nEnd date',
|
||||
'TEMPORARY-%s\n1 (43.00€) 1 (44.50€)\n%s' % (docket2.pk, docket2.date_end.strftime('%d/%m/%Y')),
|
||||
'B%02d-%s-0000001\n2 (77.50€)\n%s'
|
||||
% (regie.pk, docket1.created_at.strftime('%y-%m'), docket1.date_end.strftime('%d/%m/%Y')),
|
||||
]
|
||||
|
||||
|
||||
def test_regie_docket_add(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
PaymentType.create_defaults(regie)
|
||||
docket = PaymentDocket.objects.create(regie=regie, date_end=now().date(), draft=False)
|
||||
|
||||
payment1 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=35,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
)
|
||||
payment1.created_at = now() - datetime.timedelta(days=2)
|
||||
payment1.set_number()
|
||||
payment1.save()
|
||||
|
||||
payment2 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=55,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
)
|
||||
payment2.created_at = now() - datetime.timedelta(days=1)
|
||||
payment2.set_number()
|
||||
payment2.save()
|
||||
|
||||
payment3 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='creditcard'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
)
|
||||
payment3.created_at = now() - datetime.timedelta(days=1)
|
||||
payment3.set_number()
|
||||
payment3.save()
|
||||
|
||||
payment4 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
)
|
||||
payment4.set_number()
|
||||
payment4.save()
|
||||
|
||||
payment5 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
cancelled_at=now(),
|
||||
)
|
||||
payment5.set_number()
|
||||
payment5.save()
|
||||
|
||||
payment6 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
docket=docket,
|
||||
)
|
||||
payment6.set_number()
|
||||
payment6.save()
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
resp = resp.click('New docket')
|
||||
docket = PaymentDocket.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
assert docket.regie == regie
|
||||
assert docket.draft is True
|
||||
assert docket.date_end == now().date()
|
||||
assert list(docket.payment_types.all()) == list(PaymentType.objects.all())
|
||||
assert list(docket.payment_set.all().order_by('-pk')) == [payment3, payment2, payment1]
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
assert 'New docket' not in resp
|
||||
app.get('/manage/invoicing/regie/%s/docket/add/' % regie.pk, status=404)
|
||||
Payment.objects.filter(docket=docket).update(docket=None)
|
||||
docket.delete()
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
resp = resp.click('New docket')
|
||||
docket = PaymentDocket.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
assert docket.regie == regie
|
||||
assert docket.draft is True
|
||||
assert docket.date_end == now().date()
|
||||
assert list(docket.payment_types.all()) == list(PaymentType.objects.all())
|
||||
assert list(docket.payment_set.all().order_by('-pk')) == [payment3, payment2, payment1]
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
assert 'New docket' not in resp
|
||||
Payment.objects.filter(docket=docket).update(docket=None)
|
||||
docket.delete()
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
resp.form['payment_type'] = [p.pk for p in PaymentType.objects.all()]
|
||||
resp.form['date_end'] = now().date() + datetime.timedelta(days=1)
|
||||
resp = resp.form.submit()
|
||||
resp = resp.click('New docket')
|
||||
docket = PaymentDocket.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
assert docket.regie == regie
|
||||
assert docket.draft is True
|
||||
assert docket.date_end == now().date() + datetime.timedelta(days=1)
|
||||
assert list(docket.payment_types.all()) == list(PaymentType.objects.all())
|
||||
assert list(docket.payment_set.all().order_by('-pk')) == [payment4, payment3, payment2, payment1]
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
assert 'New docket' not in resp
|
||||
Payment.objects.filter(docket=docket).update(docket=None)
|
||||
docket.delete()
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
resp.form['payment_type'] = [
|
||||
PaymentType.objects.get(slug='cash').pk,
|
||||
PaymentType.objects.get(slug='check').pk,
|
||||
]
|
||||
resp.form['date_end'] = now().date() + datetime.timedelta(days=1)
|
||||
resp = resp.form.submit()
|
||||
resp = resp.click('New docket')
|
||||
docket = PaymentDocket.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
assert docket.regie == regie
|
||||
assert docket.draft is True
|
||||
assert docket.date_end == now().date() + datetime.timedelta(days=1)
|
||||
assert list(docket.payment_types.all()) == list(PaymentType.objects.filter(slug__in=['cash', 'check']))
|
||||
assert list(docket.payment_set.all().order_by('-pk')) == [payment4, payment2, payment1]
|
||||
|
||||
docket.draft = False
|
||||
docket.save()
|
||||
resp = app.get('/manage/invoicing/regie/%s/dockets/payments/' % regie.pk)
|
||||
assert 'New docket' in resp
|
||||
app.get('/manage/invoicing/regie/%s/docket/add/' % regie.pk, status=302)
|
||||
|
||||
|
||||
def test_regie_docket_detail(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
PaymentType.create_defaults(regie)
|
||||
docket = PaymentDocket.objects.create(
|
||||
regie=regie,
|
||||
date_end=now().date() + datetime.timedelta(days=1),
|
||||
draft=False,
|
||||
payment_types_info={
|
||||
'cash': 'foo bar\nblah',
|
||||
'check': 'foo bar',
|
||||
},
|
||||
)
|
||||
docket.payment_types.set(PaymentType.objects.all())
|
||||
docket.set_number()
|
||||
docket.save()
|
||||
|
||||
payment1 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=35,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
docket=docket,
|
||||
)
|
||||
payment1.set_number()
|
||||
payment1.save()
|
||||
payment2 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=42,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:2',
|
||||
payer_first_name='First2',
|
||||
payer_last_name='Name2',
|
||||
docket=docket,
|
||||
)
|
||||
payment2.set_number()
|
||||
payment2.save()
|
||||
payment3 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=43,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
docket=docket,
|
||||
cancelled_at=now(),
|
||||
)
|
||||
payment3.set_number()
|
||||
payment3.save()
|
||||
payment4 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=23,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
docket=docket,
|
||||
)
|
||||
payment4.set_number()
|
||||
payment4.save()
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=44,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='creditcard'),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/' % regie.pk)
|
||||
resp = resp.click('Dockets')
|
||||
resp = resp.click('Dockets')
|
||||
resp = resp.click(docket.formatted_number)
|
||||
assert [PyQuery(h3).text() for h3 in resp.pyquery('h3')] == [
|
||||
'Cash',
|
||||
'Check',
|
||||
'Cancelled payments',
|
||||
]
|
||||
assert len(resp.pyquery('table')) == 3
|
||||
assert [PyQuery(tr).text() for tr in PyQuery(resp.pyquery('table')[0]).find('tr')] == [
|
||||
'Payment R%02d-%s-0000001 dated %s from First1 Name1, amount 35.00€ (Cash)'
|
||||
% (regie.pk, payment1.created_at.strftime('%y-%m'), payment1.created_at.strftime('%d/%m/%Y')),
|
||||
]
|
||||
assert [PyQuery(tr).text() for tr in PyQuery(resp.pyquery('table')[1]).find('tr')] == [
|
||||
'Payment R%02d-%s-0000004 dated %s from First3 Name3, amount 23.00€ (Check)'
|
||||
% (regie.pk, payment4.created_at.strftime('%y-%m'), payment4.created_at.strftime('%d/%m/%Y')),
|
||||
'Payment R%02d-%s-0000002 dated %s from First2 Name2, amount 42.00€ (Check)'
|
||||
% (regie.pk, payment2.created_at.strftime('%y-%m'), payment2.created_at.strftime('%d/%m/%Y')),
|
||||
]
|
||||
assert [PyQuery(tr).text() for tr in PyQuery(resp.pyquery('table')[2]).find('tr')] == [
|
||||
'Payment R%02d-%s-0000003 dated %s from First3 Name3, amount 43.00€ (Check)'
|
||||
% (regie.pk, payment3.created_at.strftime('%y-%m'), payment3.created_at.strftime('%d/%m/%Y')),
|
||||
]
|
||||
assert len(resp.pyquery('p')) == 3
|
||||
assert (
|
||||
PyQuery(resp.pyquery('p')[0]).text()
|
||||
== 'Number of payments: 1\nTotal amount: 35.00€\nAdditionnal information:\nfoo bar\nblah'
|
||||
)
|
||||
assert (
|
||||
PyQuery(resp.pyquery('p')[1]).text()
|
||||
== 'Number of payments: 2\nTotal amount: 65.00€\nAdditionnal information:\nfoo bar'
|
||||
)
|
||||
assert PyQuery(resp.pyquery('p')[2]).text() == 'Number of payments: 1\nTotal amount: 43.00€'
|
||||
|
||||
|
||||
def test_regie_docket_payment_type_edit(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
PaymentType.create_defaults(regie)
|
||||
docket = PaymentDocket.objects.create(
|
||||
regie=regie,
|
||||
date_end=now().date() + datetime.timedelta(days=1),
|
||||
draft=False,
|
||||
payment_types_info={
|
||||
'cash': 'foo bar\nblah',
|
||||
'check': 'foo bar',
|
||||
},
|
||||
)
|
||||
docket.payment_types.set(PaymentType.objects.all())
|
||||
docket.set_number()
|
||||
docket.save()
|
||||
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=35,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
docket=docket,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
assert (
|
||||
'/manage/invoicing/regie/%s/docket/%s/payment-type/%s/'
|
||||
% (regie.pk, docket.pk, PaymentType.objects.get(regie=regie, slug='cash').pk)
|
||||
not in resp
|
||||
)
|
||||
app.get(
|
||||
'/manage/invoicing/regie/%s/docket/%s/payment-type/%s/'
|
||||
% (regie.pk, docket.pk, PaymentType.objects.get(regie=regie, slug='cash').pk),
|
||||
status=404,
|
||||
)
|
||||
|
||||
docket.draft = True
|
||||
docket.save()
|
||||
resp = app.get('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
resp = resp.click(
|
||||
href='/manage/invoicing/regie/%s/docket/%s/payment-type/%s/'
|
||||
% (regie.pk, docket.pk, PaymentType.objects.get(regie=regie, slug='cash').pk)
|
||||
)
|
||||
resp.form['additionnal_information'].value == 'foo bar\nblah'
|
||||
resp.form['additionnal_information'] = 'baz'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
docket.refresh_from_db()
|
||||
assert docket.payment_types_info == {
|
||||
'cash': 'baz',
|
||||
'check': 'foo bar',
|
||||
}
|
||||
|
||||
|
||||
def test_regie_docket_delete(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
PaymentType.create_defaults(regie)
|
||||
docket = PaymentDocket.objects.create(
|
||||
regie=regie, date_end=now().date() + datetime.timedelta(days=1), draft=False
|
||||
)
|
||||
docket.set_number()
|
||||
docket.save()
|
||||
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=35,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
docket=docket,
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=42,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
docket=docket,
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=43,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
docket=docket,
|
||||
cancelled_at=now(),
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=23,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
docket=docket,
|
||||
)
|
||||
Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=44,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='creditcard'),
|
||||
)
|
||||
assert Payment.objects.filter(docket__isnull=False).count() == 4
|
||||
assert Payment.objects.count() == 5
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
assert 'Delete' not in resp
|
||||
app.get('/manage/invoicing/regie/%s/docket/%s/delete/' % (regie.pk, docket.pk), status=404)
|
||||
|
||||
docket.draft = True
|
||||
docket.save()
|
||||
resp = app.get('/manage/invoicing/regie/%s/docket/%s/' % (regie.pk, docket.pk))
|
||||
resp = resp.click('Delete')
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/regie/%s/dockets/' % regie.pk)
|
||||
assert PaymentDocket.objects.filter(pk=docket.pk).exists() is False
|
||||
assert Payment.objects.filter(docket__isnull=False).count() == 0
|
||||
assert Payment.objects.count() == 5
|
|
@ -0,0 +1,650 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
from pyquery import PyQuery
|
||||
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.models import (
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
InvoiceLinePayment,
|
||||
Payment,
|
||||
PaymentCancellationReason,
|
||||
PaymentDocket,
|
||||
PaymentType,
|
||||
Regie,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_regie_payments(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
Agenda.objects.create(label='Agenda A', regie=regie)
|
||||
Agenda.objects.create(label='Agenda B', regie=regie)
|
||||
PaymentType.create_defaults(regie)
|
||||
invoice1 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=True,
|
||||
payer_direct_debit=False,
|
||||
)
|
||||
invoice1.set_number()
|
||||
invoice1.save()
|
||||
invoice2 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=False,
|
||||
payer_direct_debit=True,
|
||||
)
|
||||
invoice2.set_number()
|
||||
invoice2.save()
|
||||
invoice3 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
regie=regie,
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
payer_address='43 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=False,
|
||||
payer_direct_debit=True,
|
||||
)
|
||||
invoice3.set_number()
|
||||
invoice3.save()
|
||||
|
||||
invoice_line1 = InvoiceLine.objects.create(
|
||||
slug='event-a-foo-bar',
|
||||
label='Event A',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
unit_amount=40,
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
details={
|
||||
'agenda': 'agenda-a',
|
||||
'primary_event': 'event-a',
|
||||
},
|
||||
event_slug='agenda-a@event-a',
|
||||
agenda_slug='agenda-a',
|
||||
activity_label='Agenda A',
|
||||
)
|
||||
invoice_line2 = InvoiceLine.objects.create(
|
||||
slug='agenda-b@event-b', # non recurring event
|
||||
label='Event B',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice2,
|
||||
quantity=1,
|
||||
unit_amount=50,
|
||||
event_slug='agenda-b@event-b',
|
||||
agenda_slug='agenda-b',
|
||||
activity_label='Agenda B',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
invoice_line3 = InvoiceLine.objects.create(
|
||||
slug='injected',
|
||||
label='Event A',
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice3,
|
||||
quantity=1,
|
||||
unit_amount=60,
|
||||
event_slug='injected',
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
|
||||
payment1 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=35,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payment_info={
|
||||
'check_number': '123456',
|
||||
},
|
||||
)
|
||||
payment1.set_number()
|
||||
payment1.save()
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment1,
|
||||
line=invoice_line1,
|
||||
amount=35,
|
||||
)
|
||||
|
||||
payment2 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=55,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payment_info={
|
||||
'check_number': '123456',
|
||||
'check_issuer': 'Foo',
|
||||
'check_bank': 'Bar',
|
||||
'payment_reference': 'Ref',
|
||||
},
|
||||
)
|
||||
payment2.set_number()
|
||||
payment2.save()
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment2,
|
||||
line=invoice_line1,
|
||||
amount=5,
|
||||
)
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment2,
|
||||
line=invoice_line2,
|
||||
amount=50,
|
||||
)
|
||||
|
||||
docket = PaymentDocket.objects.create(
|
||||
regie=regie, date_end=now().date() + datetime.timedelta(days=1), draft=False
|
||||
)
|
||||
docket.set_number()
|
||||
docket.save()
|
||||
payment3 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='creditcard'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
payer_address='43 rue des kangourous\n99999 Kangourou Ville',
|
||||
docket=docket,
|
||||
)
|
||||
payment3.set_number()
|
||||
payment3.save()
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment3,
|
||||
line=invoice_line3,
|
||||
amount=2,
|
||||
)
|
||||
|
||||
payment4 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=2,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:3',
|
||||
payer_first_name='First3',
|
||||
payer_last_name='Name3',
|
||||
payer_address='43 rue des kangourous\n99999 Kangourou Ville',
|
||||
cancelled_at=now(),
|
||||
cancelled_by=admin_user,
|
||||
cancellation_reason=PaymentCancellationReason.objects.create(label='Uncovered check'),
|
||||
cancellation_description='foo bar\nblah',
|
||||
)
|
||||
payment4.set_number()
|
||||
payment4.save()
|
||||
|
||||
invoice1.refresh_from_db()
|
||||
assert invoice1.remaining_amount == 0
|
||||
assert invoice1.paid_amount == 40
|
||||
invoice2.refresh_from_db()
|
||||
assert invoice2.remaining_amount == 0
|
||||
assert invoice2.paid_amount == 50
|
||||
invoice3.refresh_from_db()
|
||||
assert invoice3.remaining_amount == 58
|
||||
assert invoice3.paid_amount == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/payments/' % regie.pk)
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % payment1.pk
|
||||
).text() == 'Payment R%02d-%s-0000001 dated %s from First1 Name1, amount 35.00€ (Cash) - download' % (
|
||||
regie.pk,
|
||||
payment1.created_at.strftime('%y-%m'),
|
||||
payment1.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % payment1.pk)
|
||||
] == [
|
||||
'Invoice\nAmount charged\nAmount assigned',
|
||||
'F%02d-%s-0000001\n40.00€\n35.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
invoice1.created_at.strftime('%y-%m'),
|
||||
),
|
||||
'Number: 123456',
|
||||
'Cancel payment',
|
||||
]
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % payment2.pk
|
||||
).text() == 'Payment R%02d-%s-0000002 dated %s from First1 Name1, amount 55.00€ (Check) - download' % (
|
||||
regie.pk,
|
||||
payment2.created_at.strftime('%y-%m'),
|
||||
payment2.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % payment2.pk)
|
||||
] == [
|
||||
'Invoice\nAmount charged\nAmount assigned',
|
||||
'F%02d-%s-0000001\n40.00€\n5.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
invoice1.created_at.strftime('%y-%m'),
|
||||
),
|
||||
'F%02d-%s-0000002\n50.00€\n50.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
invoice2.created_at.strftime('%y-%m'),
|
||||
),
|
||||
'Issuer: Foo',
|
||||
'Bank/Organism: Bar',
|
||||
'Number: 123456',
|
||||
'Reference: Ref',
|
||||
'Cancel payment',
|
||||
]
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % payment3.pk
|
||||
).text() == 'Payment R%02d-%s-0000003 dated %s from First3 Name3, amount 2.00€ (Credit card) - download' % (
|
||||
regie.pk,
|
||||
payment3.created_at.strftime('%y-%m'),
|
||||
payment3.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % payment3.pk)
|
||||
] == [
|
||||
'Invoice\nAmount charged\nAmount assigned',
|
||||
'F%02d-%s-0000003\n60.00€\n2.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
invoice3.created_at.strftime('%y-%m'),
|
||||
),
|
||||
'Docket: B%02d-%s-0000001' % (regie.pk, docket.created_at.strftime('%y-%m')),
|
||||
'Cancel payment',
|
||||
]
|
||||
assert resp.pyquery(
|
||||
'tr[data-invoicing-element-id="%s"]' % payment4.pk
|
||||
).text() == 'Cancelled Payment R%02d-%s-0000004 dated %s from First3 Name3, amount 2.00€ (Check) - download' % (
|
||||
regie.pk,
|
||||
payment4.created_at.strftime('%y-%m'),
|
||||
payment4.created_at.strftime('%d/%m/%Y'),
|
||||
)
|
||||
assert [
|
||||
PyQuery(tr).text() for tr in resp.pyquery('tr[data-related-invoicing-element-id="%s"]' % payment4.pk)
|
||||
] == [
|
||||
'Invoice\nAmount charged\nAmount assigned',
|
||||
'No assignments for this payment',
|
||||
'Cancelled on: %s' % payment4.cancelled_at.strftime('%d/%m/%Y %H:%M'),
|
||||
'Cancelled by: admin',
|
||||
'Reason: Uncovered check',
|
||||
'Description: foo bar\nblah',
|
||||
]
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s/payments/?csv' % regie.pk)
|
||||
assert resp.headers['Content-Type'] == 'text/csv'
|
||||
assert resp.headers['Content-Disposition'] == 'attachment; filename="payments.csv"'
|
||||
assert len([a for a in resp.text.split('\r\n') if a]) == 1 + 5
|
||||
assert resp.text == (
|
||||
'Number,Invoice number,Date,Payer ID,Payer first name,Payer last name,Payment type,Amount assigned,'
|
||||
'Total amount,Issuer,Bank/Organism,Number,Reference,Cancelled on,Cancellation reason\r\n'
|
||||
'R%02d-%s-0000004,,%s,payer:3,First3,Name3,Check,,2.00,,,,,%s,Uncovered check\r\n'
|
||||
'R%02d-%s-0000003,F%02d-%s-0000003,%s,payer:3,First3,Name3,Credit card,2.00,2.00,,,,,,\r\n'
|
||||
'R%02d-%s-0000002,F%02d-%s-0000001,%s,payer:1,First1,Name1,Check,5.00,55.00,Foo,Bar,123456,Ref,,\r\n'
|
||||
'R%02d-%s-0000002,F%02d-%s-0000002,%s,payer:1,First1,Name1,Check,50.00,55.00,Foo,Bar,123456,Ref,,\r\n'
|
||||
'R%02d-%s-0000001,F%02d-%s-0000001,%s,payer:1,First1,Name1,Cash,35.00,35.00,,,123456,,,\r\n'
|
||||
) % (
|
||||
regie.pk,
|
||||
payment4.created_at.strftime('%y-%m'),
|
||||
payment4.created_at.strftime('%Y-%m-%d'),
|
||||
payment4.cancelled_at.isoformat(),
|
||||
regie.pk,
|
||||
payment3.created_at.strftime('%y-%m'),
|
||||
regie.pk,
|
||||
invoice3.created_at.strftime('%y-%m'),
|
||||
payment3.created_at.strftime('%Y-%m-%d'),
|
||||
regie.pk,
|
||||
payment2.created_at.strftime('%y-%m'),
|
||||
regie.pk,
|
||||
invoice1.created_at.strftime('%y-%m'),
|
||||
payment2.created_at.strftime('%Y-%m-%d'),
|
||||
regie.pk,
|
||||
payment2.created_at.strftime('%y-%m'),
|
||||
regie.pk,
|
||||
invoice2.created_at.strftime('%y-%m'),
|
||||
payment2.created_at.strftime('%Y-%m-%d'),
|
||||
regie.pk,
|
||||
payment1.created_at.strftime('%y-%m'),
|
||||
regie.pk,
|
||||
invoice1.created_at.strftime('%y-%m'),
|
||||
payment1.created_at.strftime('%Y-%m-%d'),
|
||||
)
|
||||
|
||||
# test filters
|
||||
today = datetime.date.today()
|
||||
params = [
|
||||
({'number': payment1.formatted_number}, 1, 1),
|
||||
({'number': payment1.created_at.strftime('%y-%m')}, 4, 5),
|
||||
({'created_at_after': today.strftime('%Y-%m-%d')}, 4, 5),
|
||||
({'created_at_after': (today + datetime.timedelta(days=1)).strftime('%Y-%m-%d')}, 0, 0),
|
||||
({'created_at_before': (today - datetime.timedelta(days=1)).strftime('%Y-%m-%d')}, 0, 0),
|
||||
({'created_at_before': today.strftime('%Y-%m-%d')}, 4, 5),
|
||||
({'invoice_number': invoice1.formatted_number}, 2, 3),
|
||||
({'invoice_number': invoice1.created_at.strftime('%y-%m')}, 3, 4),
|
||||
({'payer_external_id': 'payer:1'}, 2, 3),
|
||||
({'payer_external_id': 'payer:3'}, 2, 2),
|
||||
({'payer_first_name': 'first'}, 4, 5),
|
||||
({'payer_first_name': 'first1'}, 2, 3),
|
||||
({'payer_last_name': 'name'}, 4, 5),
|
||||
({'payer_last_name': 'name1'}, 2, 3),
|
||||
({'payment_type': PaymentType.objects.get(slug='cash').pk}, 1, 1),
|
||||
({'payment_type': PaymentType.objects.get(slug='check').pk}, 2, 3),
|
||||
(
|
||||
{
|
||||
'amount_min': '2',
|
||||
'amount_min_lookup': 'gt',
|
||||
},
|
||||
2,
|
||||
3,
|
||||
),
|
||||
(
|
||||
{
|
||||
'amount_min': '2',
|
||||
'amount_min_lookup': 'gte',
|
||||
},
|
||||
4,
|
||||
5,
|
||||
),
|
||||
(
|
||||
{
|
||||
'amount_max': '55',
|
||||
'amount_max_lookup': 'lt',
|
||||
},
|
||||
3,
|
||||
3,
|
||||
),
|
||||
(
|
||||
{
|
||||
'amount_max': '55',
|
||||
'amount_max_lookup': 'lte',
|
||||
},
|
||||
4,
|
||||
5,
|
||||
),
|
||||
({'agenda': 'agenda-a'}, 2, 3),
|
||||
({'agenda': 'agenda-b'}, 1, 2),
|
||||
({'event': 'agenda-a@event-a'}, 2, 3),
|
||||
({'event': 'agenda-b@event-b'}, 1, 2),
|
||||
({'cancelled': 'yes'}, 1, 1),
|
||||
({'cancelled': 'no'}, 3, 4),
|
||||
]
|
||||
for param, result, csv_result in params:
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/payments/' % regie.pk,
|
||||
params=param,
|
||||
)
|
||||
assert len(resp.pyquery('tr.payment')) == result
|
||||
param['csv'] = True
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/payments/' % regie.pk,
|
||||
params=param,
|
||||
)
|
||||
assert len([a for a in resp.text.split('\r\n') if a]) == 1 + csv_result
|
||||
|
||||
|
||||
def test_regie_payment_pdf(app, admin_user):
|
||||
regie = Regie.objects.create(
|
||||
label='Foo',
|
||||
cashier_name='Le régisseur principal',
|
||||
city_name='Kangourou Ville',
|
||||
invoice_main_colour='#9141ac',
|
||||
)
|
||||
PaymentType.create_defaults(regie)
|
||||
invoice1 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=True,
|
||||
payer_direct_debit=False,
|
||||
)
|
||||
invoice1.set_number()
|
||||
invoice1.save()
|
||||
invoice2 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=False,
|
||||
payer_direct_debit=True,
|
||||
)
|
||||
invoice2.set_number()
|
||||
invoice2.save()
|
||||
|
||||
invoice_line1 = InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
unit_amount=40,
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
invoice_line2 = InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice2,
|
||||
quantity=1,
|
||||
unit_amount=50,
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
|
||||
payment = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=55,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
payment.set_number()
|
||||
payment.save()
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
line=invoice_line1,
|
||||
amount=5,
|
||||
)
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
line=invoice_line2,
|
||||
amount=50,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/payment/%s/pdf/?html' % (regie.pk, payment.pk))
|
||||
assert 'color: #9141ac;' in resp
|
||||
assert resp.pyquery('#document-label').text() == 'Payment receipt'
|
||||
assert resp.pyquery('.address-to-container').text() == (
|
||||
'Invoiced account:\nFirst1 Name1 (1)\nInvoicing address:\n41 rue des kangourous\n99999 Kangourou Ville'
|
||||
)
|
||||
assert resp.pyquery('p#informations').text() == (
|
||||
'Hereby certifies that I have received a payment of type CHECK in the amount of 55.00€ (number R%02d-%s-0000001), for account 1 First1 Name1:'
|
||||
) % (
|
||||
regie.pk,
|
||||
payment.created_at.strftime('%y-%m'),
|
||||
)
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery.find('thead tr')] == [
|
||||
'Invoice number\nInvoice object\nAmount charged\nAmount assigned'
|
||||
]
|
||||
assert [PyQuery(tr).text() for tr in resp.pyquery.find('tbody tr')] == [
|
||||
'F%02d-%s-0000001\n40.00€\n5.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
invoice1.created_at.strftime('%y-%m'),
|
||||
),
|
||||
'F%02d-%s-0000002\n50.00€\n50.00€'
|
||||
% (
|
||||
regie.pk,
|
||||
invoice2.created_at.strftime('%y-%m'),
|
||||
),
|
||||
]
|
||||
assert resp.pyquery(
|
||||
'#regie-signature'
|
||||
).text() == 'Le régisseur principal\nKangourou Ville, on %s' % payment.created_at.strftime('%d/%m/%Y')
|
||||
|
||||
resp = app.get('/manage/invoicing/regie/%s//payment/%s/pdf/?html' % (0, payment.pk), status=404)
|
||||
resp = app.get('/manage/invoicing/regie/%s//payment/%s/pdf/?html' % (regie.pk, 0), status=404)
|
||||
other_regie = Regie.objects.create(label='Foo')
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/payment/%s/pdf/?html' % (other_regie.pk, payment.pk), status=404
|
||||
)
|
||||
|
||||
|
||||
def test_regie_payment_cancel(app, admin_user):
|
||||
regie = Regie.objects.create(
|
||||
label='Foo',
|
||||
)
|
||||
PaymentType.create_defaults(regie)
|
||||
invoice1 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=True,
|
||||
payer_direct_debit=False,
|
||||
)
|
||||
invoice1.set_number()
|
||||
invoice1.save()
|
||||
invoice2 = Invoice.objects.create(
|
||||
date_publication=datetime.date(2022, 10, 1),
|
||||
date_payment_deadline=datetime.date(2022, 10, 31),
|
||||
date_due=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
regie=regie,
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
payer_demat=False,
|
||||
payer_direct_debit=True,
|
||||
)
|
||||
invoice2.set_number()
|
||||
invoice2.save()
|
||||
|
||||
invoice_line1 = InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
unit_amount=40,
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
invoice_line2 = InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice2,
|
||||
quantity=1,
|
||||
unit_amount=50,
|
||||
user_external_id='user:1',
|
||||
user_first_name='User1',
|
||||
user_last_name='Name1',
|
||||
)
|
||||
|
||||
payment = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=55,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
payment.set_number()
|
||||
payment.save()
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
line=invoice_line1,
|
||||
amount=5,
|
||||
)
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment,
|
||||
line=invoice_line2,
|
||||
amount=50,
|
||||
)
|
||||
cancellation_reason = PaymentCancellationReason.objects.create(label='Uncovered check')
|
||||
PaymentCancellationReason.objects.create(label='Disabled', disabled=True)
|
||||
payment2 = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=5,
|
||||
payment_type=PaymentType.objects.get(regie=regie, slug='check'),
|
||||
payer_external_id='payer:1',
|
||||
payer_first_name='First1',
|
||||
payer_last_name='Name1',
|
||||
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
|
||||
)
|
||||
payment2.set_number()
|
||||
payment2.save()
|
||||
InvoiceLinePayment.objects.create(
|
||||
payment=payment2,
|
||||
line=invoice_line1,
|
||||
amount=5,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regie/%s/payments/' % regie.pk)
|
||||
resp = resp.click(href='/manage/invoicing/regie/%s/payment/%s/cancel/' % (regie.pk, payment.pk))
|
||||
assert resp.form['cancellation_reason'].options == [
|
||||
('', True, '---------'),
|
||||
(str(cancellation_reason.pk), False, 'Uncovered check'),
|
||||
]
|
||||
resp.form['cancellation_reason'] = cancellation_reason.pk
|
||||
resp.form['cancellation_description'] = 'foo bar blah'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(
|
||||
'/manage/invoicing/regie/%s/payments/?number=%s' % (regie.pk, payment.formatted_number)
|
||||
)
|
||||
payment.refresh_from_db()
|
||||
assert payment.cancelled_at is not None
|
||||
assert payment.cancelled_by == admin_user
|
||||
assert payment.cancellation_reason == cancellation_reason
|
||||
assert payment.cancellation_description == 'foo bar blah'
|
||||
assert payment.invoicelinepayment_set.count() == 0
|
||||
assert payment2.invoicelinepayment_set.count() == 1
|
||||
|
||||
# already cancelled
|
||||
app.get('/manage/invoicing/regie/%s/payment/%s/cancel/' % (regie.pk, payment.pk), status=404)
|
||||
payment.cancelled_at = None
|
||||
payment.save()
|
||||
other_regie = Regie.objects.create(label='Foo')
|
||||
app.get('/manage/invoicing/regie/%s/payment/%s/cancel/' % (other_regie.pk, payment.pk), status=404)
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from lingo.invoicing.models import Payer, PaymentType, Regie
|
||||
from lingo.invoicing.utils import export_site, import_site
|
||||
|
@ -25,19 +26,54 @@ def test_import_export_regies(app):
|
|||
payload = export_site()
|
||||
assert len(payload['regies']) == 0
|
||||
|
||||
regie = Regie.objects.create(label='Foo bar')
|
||||
group = Group.objects.create(name='role-foo')
|
||||
regie = Regie.objects.create(
|
||||
label='Foo bar',
|
||||
description='blah',
|
||||
cashier_role=group,
|
||||
counter_name='{yyyy}',
|
||||
invoice_number_format='Fblah{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
payment_number_format='Rblah{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
docket_number_format='Bblah{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
credit_number_format='Ablah{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
refund_number_format='Vblah{regie_id:02d}-{yy}-{mm}-{number:07d}',
|
||||
invoice_model='full',
|
||||
invoice_custom_text='foo bar',
|
||||
invoice_main_colour='#DF5A14',
|
||||
cashier_name='Foo',
|
||||
city_name='Bar',
|
||||
)
|
||||
|
||||
payload = export_site()
|
||||
assert len(payload['regies']) == 1
|
||||
|
||||
regie.delete()
|
||||
group.delete()
|
||||
assert not Regie.objects.exists()
|
||||
|
||||
Group.objects.create(name='role')
|
||||
with pytest.raises(LingoImportError) as excinfo:
|
||||
import_site(copy.deepcopy(payload))
|
||||
assert str(excinfo.value) == 'Missing role: role-foo'
|
||||
|
||||
group = Group.objects.create(name='role-foo')
|
||||
import_site(copy.deepcopy(payload))
|
||||
assert Regie.objects.count() == 1
|
||||
regie = Regie.objects.first()
|
||||
assert regie.label == 'Foo bar'
|
||||
assert regie.slug == 'foo-bar'
|
||||
assert regie.cashier_role == group
|
||||
assert regie.counter_name == '{yyyy}'
|
||||
assert regie.invoice_number_format == 'Fblah{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.payment_number_format == 'Rblah{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.docket_number_format == 'Bblah{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.credit_number_format == 'Ablah{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.refund_number_format == 'Vblah{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.invoice_model == 'full'
|
||||
assert regie.invoice_custom_text == 'foo bar'
|
||||
assert regie.invoice_main_colour == '#DF5A14'
|
||||
assert regie.cashier_name == 'Foo'
|
||||
assert regie.city_name == 'Bar'
|
||||
|
||||
# update
|
||||
update_payload = copy.deepcopy(payload)
|
||||
|
|
|
@ -867,13 +867,18 @@ def test_counter():
|
|||
counter1_bis = Counter.objects.get(regie=regie1, name='foo', kind='payment')
|
||||
assert counter1_bis.value == 1
|
||||
|
||||
assert Counter.get_count(regie=regie1, name='foo', kind='credit') == 1
|
||||
assert Counter.get_count(regie=regie1, name='foo', kind='docket') == 1
|
||||
assert Counter.objects.count() == 3
|
||||
counter1_bis = Counter.objects.get(regie=regie1, name='foo', kind='docket')
|
||||
assert counter1_bis.value == 1
|
||||
|
||||
assert Counter.get_count(regie=regie1, name='foo', kind='credit') == 1
|
||||
assert Counter.objects.count() == 4
|
||||
counter1_ter = Counter.objects.get(regie=regie1, name='foo', kind='credit')
|
||||
assert counter1_ter.value == 1
|
||||
|
||||
assert Counter.get_count(regie=regie1, name='foo', kind='refund') == 1
|
||||
assert Counter.objects.count() == 4
|
||||
assert Counter.objects.count() == 5
|
||||
counter1_ter = Counter.objects.get(regie=regie1, name='foo', kind='refund')
|
||||
assert counter1_ter.value == 1
|
||||
|
||||
|
@ -890,14 +895,14 @@ def test_counter():
|
|||
assert counter1.value == 3
|
||||
|
||||
assert Counter.get_count(regie=regie2, name='foo', kind='invoice') == 1
|
||||
assert Counter.objects.count() == 5
|
||||
assert Counter.objects.count() == 6
|
||||
counter1.refresh_from_db()
|
||||
assert counter1.value == 3
|
||||
counter2 = Counter.objects.get(regie=regie2, name='foo', kind='invoice')
|
||||
assert counter2.value == 1
|
||||
|
||||
assert Counter.get_count(regie=regie2, name='bar', kind='invoice') == 1
|
||||
assert Counter.objects.count() == 6
|
||||
assert Counter.objects.count() == 7
|
||||
counter1.refresh_from_db()
|
||||
assert counter1.value == 3
|
||||
counter2.refresh_from_db()
|
||||
|
@ -929,6 +934,7 @@ def test_regie_format_number():
|
|||
regie = Regie.objects.create()
|
||||
assert regie.invoice_number_format == 'F{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.payment_number_format == 'R{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.docket_number_format == 'B{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.credit_number_format == 'A{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
assert regie.refund_number_format == 'V{regie_id:02d}-{yy}-{mm}-{number:07d}'
|
||||
|
||||
|
@ -954,6 +960,17 @@ def test_regie_format_number():
|
|||
assert regie.format_number(datetime.date(2023, 2, 15), 42, 'payment') == 'Rfoobar-2023-00000042'
|
||||
assert regie.format_number(datetime.date(2024, 12, 15), 42000000, 'payment') == 'Rfoobar-2024-42000000'
|
||||
|
||||
assert regie.format_number(datetime.date(2023, 2, 15), 42, 'docket') == 'B%02d-23-02-0000042' % regie.pk
|
||||
assert (
|
||||
regie.format_number(datetime.date(2024, 12, 15), 42000000, 'docket')
|
||||
== 'B%02d-24-12-42000000' % regie.pk
|
||||
)
|
||||
|
||||
regie.docket_number_format = 'Bfoobar-{yyyy}-{number:08d}'
|
||||
regie.save()
|
||||
assert regie.format_number(datetime.date(2023, 2, 15), 42, 'docket') == 'Bfoobar-2023-00000042'
|
||||
assert regie.format_number(datetime.date(2024, 12, 15), 42000000, 'docket') == 'Bfoobar-2024-42000000'
|
||||
|
||||
assert regie.format_number(datetime.date(2023, 2, 15), 42, 'credit') == 'A%02d-23-02-0000042' % regie.pk
|
||||
assert (
|
||||
regie.format_number(datetime.date(2024, 12, 15), 42000000, 'credit')
|
||||
|
|
Je ne connaissais pas l’astuce, ok :)
Oui, je ne suis pas super fière de moi, c'est un peu moche; mais en faisant avec initial ça ne valide pas le formulaire et donc pas de résultat du filtrage.
L'idée étant d'arriver sur la page avec déjà tous les paiements non remis listés
Oui je vois, rien à redire et de toute façon je n’ai pas mieux à proposer, ok pour moi. Je marque comme résolue cette conversation.