Skip to content

Commit

Permalink
command to delete duplicate visits
Browse files Browse the repository at this point in the history
  • Loading branch information
hemant10yadav committed Dec 5, 2024
1 parent 41b7557 commit 7dc1f53
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from django.core.management import BaseCommand
from django.db import transaction
from django.db.models import Count

from commcare_connect.opportunity.models import UserVisit


class Command(BaseCommand):
help = "Clean up duplicate visits."

def add_arguments(self, parser, *args, **kwargs):
parser.add_argument("--opp", type=int, help="Opportunity ID to clean up duplicate visits.")
parser.add_argument(
"--dry-run",
action="store_true",
help="If set, just print the visits that would be deleted without actually deleting them.",
)

def handle(self, *args, **options):
opportunity_id = options.get("opp")
dry_run = options.get("dry_run")

duplicates = (
UserVisit.objects.filter(opportunity_id=opportunity_id)
.values("opportunity", "entity_id", "deliver_unit", "xform_id")
.annotate(visit_count=Count("id"))
.filter(visit_count__gt=1)
)

if dry_run:
self.stdout.write("Running in dry-run mode. No records will be deleted.")
else:
self.stdout.write("Attention: Records will be deleted!!")

with transaction.atomic():
for duplicate in duplicates:
visits = UserVisit.objects.filter(
opportunity_id=opportunity_id,
entity_id=duplicate["entity_id"],
deliver_unit=duplicate["deliver_unit"],
xform_id=duplicate["xform_id"],
).order_by("id")

visits_to_delete = visits[1:]

for visit in visits_to_delete:
message = (
f"Identified duplicate visit: id={visit.id}, "
f"xform_id={visit.xform_id}, entity_id={visit.entity_id}, "
f"deliver_unit={visit.deliver_unit}, status={visit.status}"
)
self.stdout.write(message)

if not dry_run:
visit.delete()

if not dry_run:
self.stdout.write(
self.style.SUCCESS(f"Duplicate visits for opportunity {opportunity_id} deleted successfully.")
)
else:
self.stdout.write(f"Dry-run complete for opportunity {opportunity_id}")
66 changes: 66 additions & 0 deletions commcare_connect/opportunity/tests/test_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from io import StringIO
from uuid import uuid4

import pytest
from django.core.management import call_command

from commcare_connect.opportunity.models import UserVisit
from commcare_connect.opportunity.tests.factories import DeliverUnitFactory, UserVisitFactory


@pytest.fixture
def setup_opportunity_with_duplicates(db):
def _setup(opportunity, xform_id, entity_id, deliver_unit, num_duplicates):
first_visit = UserVisitFactory.create(
opportunity=opportunity, deliver_unit=deliver_unit, xform_id=xform_id, entity_id=entity_id
)
for _ in range(num_duplicates - 1):
UserVisitFactory.create(
opportunity=opportunity, deliver_unit=deliver_unit, xform_id=xform_id, entity_id=entity_id
)
return first_visit

return _setup


@pytest.mark.django_db
@pytest.mark.parametrize(
"num_duplicates,expected_remaining,dry_run",
[
(1, 1, True), # No duplicates, dry-run mode
(2, 2, True), # One duplicate, dry-run mode
(3, 3, True), # Two duplicates, dry-run mode
(2, 1, False), # One duplicate, actual deletion
(3, 1, False), # Two duplicates, actual deletion
],
)
def test_delete_duplicate_visits(
opportunity, setup_opportunity_with_duplicates, num_duplicates, expected_remaining, dry_run
):
xform_id = str(uuid4())
entity_id = str(uuid4())
deliver_unit = DeliverUnitFactory()

first_visit = setup_opportunity_with_duplicates(opportunity, xform_id, entity_id, deliver_unit, num_duplicates)

out = StringIO()

if dry_run:
call_command("delete_duplicate_visits", "--opp", str(opportunity.id), "--dry-run", stdout=out)
else:
call_command("delete_duplicate_visits", "--opp", str(opportunity.id), stdout=out)

remaining_visits = UserVisit.objects.filter(
opportunity=opportunity, entity_id=entity_id, deliver_unit=deliver_unit, xform_id=xform_id
)

# Verify the count of remaining visits matches the expectation
assert remaining_visits.count() == expected_remaining

if not dry_run:
# Ensure the first visit is still present after actual deletion
assert remaining_visits.filter(id=first_visit.id).exists()
assert f"Duplicate visits for opportunity {opportunity.id} deleted successfully." in out.getvalue()
else:
assert remaining_visits.count() == num_duplicates
assert f"Dry-run complete for opportunity {opportunity.id}" in out.getvalue()

0 comments on commit 7dc1f53

Please sign in to comment.