feat(contracts): add column sorting by code and dates to contracts list

This commit is contained in:
kdeterme 2026-10-10 14:47:28 +02:00
parent 0732f89274
commit a6378eee90
4 changed files with 97 additions and 8 deletions

View file

@ -60,6 +60,7 @@ document.addEventListener('DOMContentLoaded', function() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.searchParams.set('page_size', this.value); url.searchParams.set('page_size', this.value);
url.searchParams.delete('page'); url.searchParams.delete('page');
url.searchParams.delete('contracts_page');
window.location.href = url.toString(); window.location.href = url.toString();
}); });
} }

View file

@ -394,12 +394,37 @@
<thead> <thead>
<tr> <tr>
<th style="width: 48px;"></th> <th style="width: 48px;"></th>
<th>{% translate "Numéro" %}</th> <th class="sortable">
<th>{% translate "Description" %}</th> <a href="{{ contracts_sort_urls.code }}" class="sort-link">
<span>{% translate "Numéro" %}</span>
<i class="bi {% if contracts_sort_field == 'code' %}{% if contracts_sort_dir == 'asc' %}bi-sort-up text-primary{% else %}bi-sort-down text-primary{% endif %}{% else %}bi-arrow-down-up text-muted opacity-50{% endif %}"></i>
</a>
</th>
<th class="sortable">
<a href="{{ contracts_sort_urls.description }}" class="sort-link">
<span>{% translate "Description" %}</span>
<i class="bi {% if contracts_sort_field == 'description' %}{% if contracts_sort_dir == 'asc' %}bi-sort-up text-primary{% else %}bi-sort-down text-primary{% endif %}{% else %}bi-arrow-down-up text-muted opacity-50{% endif %}"></i>
</a>
</th>
<th>{% translate "Thématiques" %}</th> <th>{% translate "Thématiques" %}</th>
<th>{% translate "Prestataire" %}</th> <th class="sortable">
<th>{% translate "Début" %}</th> <a href="{{ contracts_sort_urls.company }}" class="sort-link">
<th>{% translate "Fin" %}</th> <span>{% translate "Prestataire" %}</span>
<i class="bi {% if contracts_sort_field == 'company' %}{% if contracts_sort_dir == 'asc' %}bi-sort-up text-primary{% else %}bi-sort-down text-primary{% endif %}{% else %}bi-arrow-down-up text-muted opacity-50{% endif %}"></i>
</a>
</th>
<th class="sortable">
<a href="{{ contracts_sort_urls.start_date }}" class="sort-link">
<span>{% translate "Début" %}</span>
<i class="bi {% if contracts_sort_field == 'start_date' or contracts_sort_field == 'date' %}{% if contracts_sort_dir == 'asc' %}bi-sort-up text-primary{% else %}bi-sort-down text-primary{% endif %}{% else %}bi-arrow-down-up text-muted opacity-50{% endif %}"></i>
</a>
</th>
<th class="sortable">
<a href="{{ contracts_sort_urls.end_date }}" class="sort-link">
<span>{% translate "Fin" %}</span>
<i class="bi {% if contracts_sort_field == 'end_date' %}{% if contracts_sort_dir == 'asc' %}bi-sort-up text-primary{% else %}bi-sort-down text-primary{% endif %}{% else %}bi-arrow-down-up text-muted opacity-50{% endif %}"></i>
</a>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -915,6 +915,22 @@ class ContractsIndexViewTests(TestCase):
# Premier élément doit être CMD-002 car CMD-001 est cancelled # Premier élément doit être CMD-002 car CMD-001 est cancelled
self.assertEqual(resp.context['orders_page'][0].order_code, 'CMD-002') self.assertEqual(resp.context['orders_page'][0].order_code, 'CMD-002')
def test_contracts_index_contracts_sorting_code_asc(self):
url = reverse('contracts:contracts_index')
resp = self.client.get(url, {'tab': 'contracts', 'contracts_sort': 'code', 'contracts_dir': 'asc'})
self.assertEqual(resp.status_code, 200)
contracts_page = resp.context['contracts_page']
self.assertEqual(contracts_page[0].contract_number, self.contract_green.contract_number)
self.assertEqual(contracts_page[1].contract_number, self.contract_roads.contract_number)
def test_contracts_index_contracts_sorting_code_desc(self):
url = reverse('contracts:contracts_index')
resp = self.client.get(url, {'tab': 'contracts', 'contracts_sort': 'code', 'contracts_dir': 'desc'})
self.assertEqual(resp.status_code, 200)
contracts_page = resp.context['contracts_page']
self.assertEqual(contracts_page[0].contract_number, self.contract_roads.contract_number)
self.assertEqual(contracts_page[1].contract_number, self.contract_green.contract_number)

View file

@ -146,6 +146,30 @@ def contracts_index(request):
orders_page = orders_paginator.get_page(orders_page_num) orders_page = orders_paginator.get_page(orders_page_num)
orders_elided_range = orders_paginator.get_elided_page_range(orders_page.number, on_each_side=2, on_ends=1) orders_elided_range = orders_paginator.get_elided_page_range(orders_page.number, on_each_side=2, on_ends=1)
# Tri des marchés
contracts_sort_field = request.GET.get('contracts_sort', '')
if not contracts_sort_field and active_tab == 'contracts':
contracts_sort_field = request.GET.get('sort', '')
if not contracts_sort_field:
contracts_sort_field = 'start_date'
contracts_sort_dir = request.GET.get('contracts_dir', '')
if not contracts_sort_dir and active_tab == 'contracts':
contracts_sort_dir = request.GET.get('dir', '')
if contracts_sort_dir not in ('asc', 'desc'):
contracts_sort_dir = 'desc' if contracts_sort_field in ('start_date', 'date', 'end_date') else 'asc'
contracts_sort_mapping = {
'code': 'contract_number',
'description': 'description',
'company': 'company__name',
'start_date': 'start_date',
'date': 'start_date',
'end_date': 'end_date',
}
db_contracts_sort_col = contracts_sort_mapping.get(contracts_sort_field, 'start_date')
contracts_order_by_arg = db_contracts_sort_col if contracts_sort_dir == 'asc' else f"-{db_contracts_sort_col}"
# Construction de la requête marchés/contrats # Construction de la requête marchés/contrats
contracts_qs = contracts contracts_qs = contracts
if search_query: if search_query:
@ -159,9 +183,9 @@ def contracts_index(request):
Q(thematics__id__in=selected_thematics) | Q(thematics__id__in=selected_thematics) |
Q(thematics__code__in=selected_thematics) Q(thematics__code__in=selected_thematics)
) )
contracts_qs = contracts_qs.distinct().order_by('-start_date') contracts_qs = contracts_qs.distinct().order_by(contracts_order_by_arg, '-id')
contracts_paginator = Paginator(contracts_qs, 25) contracts_paginator = Paginator(contracts_qs, page_size)
contracts_page_num = request.GET.get('contracts_page', 1) contracts_page_num = request.GET.get('contracts_page', 1)
contracts_page = contracts_paginator.get_page(contracts_page_num) contracts_page = contracts_paginator.get_page(contracts_page_num)
contracts_elided_range = contracts_paginator.get_elided_page_range(contracts_page.number, on_each_side=2, on_ends=1) contracts_elided_range = contracts_paginator.get_elided_page_range(contracts_page.number, on_each_side=2, on_ends=1)
@ -229,11 +253,12 @@ def contracts_index(request):
orders_querystring = p_orders.urlencode() orders_querystring = p_orders.urlencode()
p_contracts = request.GET.copy() p_contracts = request.GET.copy()
p_contracts['tab'] = 'contracts'
if 'contracts_page' in p_contracts: if 'contracts_page' in p_contracts:
del p_contracts['contracts_page'] del p_contracts['contracts_page']
contracts_querystring = p_contracts.urlencode() contracts_querystring = p_contracts.urlencode()
# Générateur d'URLs de tri # Générateur d'URLs de tri des commandes
def build_sort_url(col): def build_sort_url(col):
next_dir = 'asc' if (sort_field == col and sort_dir == 'desc') else 'desc' next_dir = 'asc' if (sort_field == col and sort_dir == 'desc') else 'desc'
p = request.GET.copy() p = request.GET.copy()
@ -245,6 +270,25 @@ def contracts_index(request):
sort_urls = {col: build_sort_url(col) for col in ('code', 'description', 'date', 'status', 'contract', 'amount')} sort_urls = {col: build_sort_url(col) for col in ('code', 'description', 'date', 'status', 'contract', 'amount')}
# Générateur d'URLs de tri des marchés
def build_contracts_sort_url(col):
if contracts_sort_field == col:
next_dir = 'desc' if contracts_sort_dir == 'asc' else 'asc'
else:
next_dir = 'desc' if col in ('start_date', 'end_date', 'date') else 'asc'
p = request.GET.copy()
p['tab'] = 'contracts'
p['contracts_sort'] = col
p['contracts_dir'] = next_dir
if 'contracts_page' in p:
del p['contracts_page']
return f"?{p.urlencode()}"
contracts_sort_urls = {
col: build_contracts_sort_url(col)
for col in ('code', 'description', 'company', 'start_date', 'end_date')
}
# Vérification des droits pour voir les prix # Vérification des droits pour voir les prix
can_view_prices = False can_view_prices = False
if request.user.is_superuser: if request.user.is_superuser:
@ -279,6 +323,9 @@ def contracts_index(request):
'sort_field': sort_field, 'sort_field': sort_field,
'sort_dir': sort_dir, 'sort_dir': sort_dir,
'sort_urls': sort_urls, 'sort_urls': sort_urls,
'contracts_sort_field': contracts_sort_field,
'contracts_sort_dir': contracts_sort_dir,
'contracts_sort_urls': contracts_sort_urls,
'active_tab': active_tab, 'active_tab': active_tab,
'active_filters_list': active_filters_list, 'active_filters_list': active_filters_list,
'active_filters_count': active_filters_count, 'active_filters_count': active_filters_count,