manager: configure invoicing options for partial bookings (#80842)

This commit is contained in:
Lauréline Guérin 2023-09-05 10:36:14 +02:00
parent 0fe3933ed1
commit df0223abf2
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
7 changed files with 128 additions and 0 deletions

View File

@ -0,0 +1,33 @@
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agendas', '0158_partial_booking_check_fields'),
]
operations = [
migrations.AddField(
model_name='agenda',
name='invoicing_tolerance',
field=models.PositiveSmallIntegerField(
default=0, validators=[django.core.validators.MaxValueValidator(59)], verbose_name='Tolerance'
),
),
migrations.AddField(
model_name='agenda',
name='invoicing_unit',
field=models.CharField(
choices=[
('hour', 'Per hour'),
('half_hour', 'Per half hour'),
('quarter', 'Per quarter-hour'),
('minute', 'Per minute'),
],
default='hour',
max_length=10,
verbose_name='Invoicing',
),
),
]

View File

@ -291,6 +291,22 @@ class Agenda(models.Model):
blank=True,
)
partial_bookings = models.BooleanField(default=False)
invoicing_unit = models.CharField(
verbose_name=_('Invoicing'),
max_length=10,
choices=[
('hour', _('Per hour')),
('half_hour', _('Per half hour')),
('quarter', _('Per quarter-hour')),
('minute', _('Per minute')),
],
default='hour',
)
invoicing_tolerance = models.PositiveSmallIntegerField(
verbose_name=_('Tolerance'),
default=0,
validators=[MaxValueValidator(59)],
)
class Meta:
ordering = ['label']

View File

@ -1538,6 +1538,15 @@ class AgendaBookingCheckSettingsForm(forms.ModelForm):
widgets = {'booking_extra_user_block_template': forms.Textarea(attrs={'rows': 3})}
class AgendaInvoicingSettingsForm(forms.ModelForm):
class Meta:
model = Agenda
fields = [
'invoicing_unit',
'invoicing_tolerance',
]
class AgendaNotificationsForm(forms.ModelForm):
class Meta:
model = AgendaNotificationsSettings

View File

@ -19,6 +19,9 @@
{% endif %}
<button aria-controls="panel-display-options" aria-selected="false" id="tab-display-options" role="tab" tabindex="-1">{% trans "Display options" %}</button>
<button aria-controls="panel-booking-check-options" aria-selected="false" id="tab-booking-check-options" role="tab" tabindex="-1">{% trans "Booking check options" %}</button>
{% if agenda.partial_bookings %}
<button aria-controls="panel-invoicing-options" aria-selected="false" id="tab-invoicing-options" role="tab" tabindex="-1">{% trans "Invoicing options" %}</button>
{% endif %}
<button aria-controls="panel-notifications" aria-selected="false" id="tab-notifications" role="tab" tabindex="-1">{% trans "Management notifications" %}</button>
{% endblock %}
@ -124,6 +127,18 @@
</div>
</div>
{% if agenda.partial_bookings %}
<div aria-labelledby="tab-invoicing-options" hidden="" id="panel-invoicing-options" role="tabpanel" tabindex="0">
<ul>
<li>{% trans "Invoicing:" %} {{ agenda.get_invoicing_unit_display }}</li>
<li>{% trans "Tolerance:" %} {% blocktrans count num=agenda.invoicing_tolerance %}{{ num }} minute{% plural %}{{ num }} minutes{% endblocktrans %}</li>
</ul>
<div class="panel--buttons">
<a rel="popup" class="button" href="{% url 'chrono-manager-agenda-invoicing-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
</div>
</div>
{% endif %}
<div aria-labelledby="tab-notifications" hidden="" id="panel-notifications" role="tabpanel" tabindex="0">
{% for notification_type in object.notifications_settings.get_notification_types %}
{% if forloop.first %}<ul>{% endif %}

View File

@ -170,6 +170,11 @@ urlpatterns = [
views.agenda_booking_check_settings,
name='chrono-manager-agenda-booking-check-settings',
),
path(
'agendas/<int:pk>/invoicing-options',
views.agenda_invoicing_settings,
name='chrono-manager-agenda-invoicing-settings',
),
path('agendas/<int:pk>/delete', views.agenda_delete, name='chrono-manager-agenda-delete'),
path('agendas/<int:pk>/export', views.agenda_export, name='chrono-manager-agenda-export'),
path('agendas/<int:pk>/add-event', views.agenda_add_event, name='chrono-manager-agenda-add-event'),

View File

@ -98,6 +98,7 @@ from .forms import (
AgendaDisplaySettingsForm,
AgendaDuplicateForm,
AgendaEditForm,
AgendaInvoicingSettingsForm,
AgendaNotificationsForm,
AgendaReminderForm,
AgendaReminderTestForm,
@ -1163,6 +1164,18 @@ class AgendaBookingCheckSettingsView(AgendaEditView):
agenda_booking_check_settings = AgendaBookingCheckSettingsView.as_view()
class AgendaInvoicingSettingsView(AgendaEditView):
form_class = AgendaInvoicingSettingsForm
title = _('Configure invoicing options')
tab_anchor = 'invoicing-options'
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events', partial_bookings=True)
agenda_invoicing_settings = AgendaInvoicingSettingsView.as_view()
class AgendaDeleteView(DeleteView):
template_name = 'chrono/manager_confirm_delete.html'
model = Agenda

View File

@ -36,6 +36,43 @@ def test_manager_partial_bookings_add_agenda(app, admin_user, settings):
]
def test_options_partial_bookings_invoicing_settings(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
assert agenda.invoicing_unit == 'hour'
assert agenda.invoicing_tolerance == 0
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click(href='/manage/agendas/%s/invoicing-options' % agenda.pk)
assert resp.form['invoicing_unit'].options == [
('hour', True, 'Per hour'),
('half_hour', False, 'Per half hour'),
('quarter', False, 'Per quarter-hour'),
('minute', False, 'Per minute'),
]
resp.form['invoicing_unit'] = 'half_hour'
resp.form['invoicing_tolerance'] = 10
resp = resp.form.submit().follow()
agenda.refresh_from_db()
assert agenda.invoicing_unit == 'half_hour'
assert agenda.invoicing_tolerance == 10
# check kind
agenda.partial_bookings = False
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
def test_manager_partial_bookings_add_event(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)