diff --git a/tally_ho/apps/tally/templates/super_admin/form_results_duplicates.html b/tally_ho/apps/tally/templates/super_admin/form_results_duplicates.html index 075ca12cb..a55991048 100644 --- a/tally_ho/apps/tally/templates/super_admin/form_results_duplicates.html +++ b/tally_ho/apps/tally/templates/super_admin/form_results_duplicates.html @@ -4,7 +4,7 @@ {% block content %} -

{% trans 'Results Duplicated List' %}

+

{% trans 'Duplicate Result Forms List' %}



diff --git a/tally_ho/apps/tally/templates/super_admin/home.html b/tally_ho/apps/tally/templates/super_admin/home.html index 819aa7b71..949a79cd3 100644 --- a/tally_ho/apps/tally/templates/super_admin/home.html +++ b/tally_ho/apps/tally/templates/super_admin/home.html @@ -62,7 +62,7 @@

{% trans 'Reports' %}

  • {% trans 'Form Duplicates' %}
  • {% trans 'Form Clearance' %}
  • {% trans 'Form Audit' %}
  • -
  • {% trans 'Form Results Duplicates' %}
  • +
  • {% trans 'Duplicate Result Forms List' %}
  • {% trans 'Download Results' %}
  • diff --git a/tally_ho/apps/tally/tests/views/test_super_admin.py b/tally_ho/apps/tally/tests/views/test_super_admin.py index d0fd9fa16..b862fdf7f 100644 --- a/tally_ho/apps/tally/tests/views/test_super_admin.py +++ b/tally_ho/apps/tally/tests/views/test_super_admin.py @@ -1160,10 +1160,10 @@ def test_get_result_form_with_duplicate_results(self): # create duplicate final results create_result(result_form_4, result.candidate, self.user, votes) ballot_1_duplicates = views.get_result_form_with_duplicate_results( - ballot=ballot_1.pk, + ballot=ballot_1.number, tally_id=tally.pk) ballot_2_duplicates = views.get_result_form_with_duplicate_results( - ballot=ballot_2.pk, + ballot=ballot_2.number, tally_id=tally.pk) all_duplicates = views.get_result_form_with_duplicate_results( tally_id=tally.pk) @@ -1229,11 +1229,11 @@ def test_duplicate_result_form_view_get(self): request, tally_id=tally.pk, barcode=barcode, - ballot_id=ballot.pk) + ballot_id=ballot.number) response.render() self.assertIn(("{}{}").format( "Duplicate result forms list for ballot: ", - ballot.pk), str(response.content)) + ballot.number), str(response.content)) self.assertIn(("{}{}").format("Result form barcode: ", barcode), str(response.content)) self.assertIn("Send to clearance", str(response.content)) @@ -1277,7 +1277,7 @@ def test_duplicate_result_form_view_duplicate_reviewed_post(self): request = self.factory.post('/', data=data) request.user = self.user configure_messages(request) - response = view(request, tally_id=tally.pk, ballot_id=ballot.pk) + response = view(request, tally_id=tally.pk, ballot_id=ballot.number) result_form_1.reload() result_form_2.reload() @@ -1310,7 +1310,7 @@ def test_duplicate_result_form_view_send_clearance_post(self): request.user = self.user configure_messages(request) request.session = {'result_form': result_form.pk} - response = view(request, tally_id=tally.pk, ballot_id=ballot.pk) + response = view(request, tally_id=tally.pk, ballot_id=ballot.number) result_form.reload() self.assertEqual(response.status_code, 302) @@ -1339,7 +1339,7 @@ def test_duplicate_result_form_view_send_clearance_post(self): request.user = self.user configure_messages(request) request.session = {'result_form': result_form_2.pk} - response = view(request, tally_id=tally.pk, ballot_id=ballot.pk) + response = view(request, tally_id=tally.pk, ballot_id=ballot.number) result_form_2.reload() self.assertNotEqual(result_form_2.form_state, FormState.CLEARANCE) @@ -1384,7 +1384,7 @@ def test_duplicate_result_form_view_send_all_clearance_post(self): request = self.factory.post('/', data=data) request.user = self.user configure_messages(request) - response = view(request, tally_id=tally.pk, ballot_id=ballot.pk) + response = view(request, tally_id=tally.pk, ballot_id=ballot.number) result_form_1.reload() result_form_2.reload() @@ -1432,7 +1432,7 @@ def test_duplicate_archived_result_forms_send_all_clearance_post(self): request = self.factory.post('/', data=data) request.user = self.user configure_messages(request) - response = view(request, tally_id=tally.pk, ballot_id=ballot.pk) + response = view(request, tally_id=tally.pk, ballot_id=ballot.number) result_form_1.reload() result_form_2.reload() diff --git a/tally_ho/apps/tally/views/super_admin.py b/tally_ho/apps/tally/views/super_admin.py index 794587c95..44bd7c102 100644 --- a/tally_ho/apps/tally/views/super_admin.py +++ b/tally_ho/apps/tally/views/super_admin.py @@ -4,7 +4,7 @@ from django.db.models.deletion import ProtectedError from django.core.exceptions import SuspiciousOperation from django.contrib.messages.views import SuccessMessageMixin -from django.db.models import Count, Func, Q, F +from django.db.models import Count, Q, F from django.shortcuts import get_object_or_404, redirect from django.views.generic.edit import UpdateView, DeleteView, CreateView from django.views.generic import FormView, TemplateView @@ -180,6 +180,12 @@ def get_results_duplicates(tally_id): return result_forms_founds +def all_candidates_have_duplicates(candidate_id_to_votes_array_map): + for votes in candidate_id_to_votes_array_map.values(): + # Check if all items in the list are the same + if len(set(votes)) != 1: + return False + return True def get_result_form_with_duplicate_results( ballot=None, @@ -192,24 +198,55 @@ def get_result_form_with_duplicate_results( :returns A list of result forms in the system with duplicate results. """ - qs = ResultForm.objects.filter(tally_id=tally_id) if not qs else qs - result_form_ids = qs.exclude(results=None).values( - 'ballot', - 'results__votes', - 'results__candidate') \ - .annotate(ids=ArrayAgg('id')) \ - .annotate(duplicate_count=Count('id')) \ - .annotate(ids=Func('ids', function='unnest')) \ - .filter( - duplicate_count__gt=1, - duplicate_reviewed=False - ).values_list('ids', flat=True).distinct() + qs =\ + ResultForm.objects.filter( + tally_id=tally_id, + duplicate_reviewed=False) if not qs else qs + qs = qs.exclude(results=None) + if ballot: + qs = qs.filter(ballot__number=ballot) + result_forms_barcodes_grouped_by_ballot =\ + qs.values('ballot').annotate(barcodes=ArrayAgg('barcode')) + result_forms_with_ballot =\ + [ + b for b in result_forms_barcodes_grouped_by_ballot\ + if len(b.get('barcodes')) > 1 + ] + result_form_barcodes_with_duplicate_results = [] + for ballot_result_form_dict in result_forms_with_ballot: + candidate_id_to_votes_array_map = {} + if ballot_result_form_dict.get('barcodes'): + for barcode in ballot_result_form_dict.get('barcodes'): + candidate_results_qs =\ + Result.objects.filter( + result_form__tally__id=tally_id, + active=True, + result_form__barcode=barcode, + ).values('candidate_id', 'votes') + for candidate_result in candidate_results_qs: + candidate_id = candidate_result.get('candidate_id') + candidate_votes = candidate_result.get('votes') + if candidate_id_to_votes_array_map.get(candidate_id): + candidate_id_to_votes_array_map.get( + candidate_id).append(candidate_votes) + continue + candidate_id_to_votes_array_map.setdefault( + candidate_id, []).append(candidate_votes) + + if len(candidate_id_to_votes_array_map.values()): + # Check for duplicate results + duplicates_found =\ + all_candidates_have_duplicates( + candidate_id_to_votes_array_map) + if duplicates_found: + result_form_barcodes_with_duplicate_results.extend( + ballot_result_form_dict.get('barcodes') + ) results_form_duplicates = \ - qs.filter(id__in=result_form_ids).order_by('ballot') - - if ballot: - results_form_duplicates = results_form_duplicates.filter(ballot=ballot) + qs.filter( + barcode__in=result_form_barcodes_with_duplicate_results + ).order_by('ballot') return results_form_duplicates @@ -494,7 +531,11 @@ def get(self, *args, **kwargs): ballot_id = kwargs['ballot_id'] result_form = ResultForm.objects.get( barcode=barcode, tally_id=tally_id) - results = Result.objects.filter(result_form=result_form.id) + results =\ + Result.objects.filter( + result_form=result_form, + result_form__tally__id=tally_id, + ) return self.render_to_response(self.get_context_data( results_form_duplicates=get_result_form_with_duplicate_results(