Skip to content

Commit

Permalink
Merge pull request #3298 from unicef/ch29625-develop-fix-intervention…
Browse files Browse the repository at this point in the history
…-auto-transition

[Develop] Auto-transitions to active (via task) for PDs in the future do not work as expected.
  • Loading branch information
robertavram authored May 23, 2022
2 parents ecc7211 + 1748a0a commit be2ffa1
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 45 deletions.
18 changes: 6 additions & 12 deletions src/etools/applications/partners/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import connection, transaction
from django.db.models import F, Sum

from celery.utils.log import get_task_logger
from django_tenants.utils import schema_context
Expand Down Expand Up @@ -119,17 +118,12 @@ def _make_intervention_status_automatic_transitions(country_name):

admin_user = get_user_model().objects.get(username=settings.TASK_ADMIN_USER)
bad_interventions = []
active_ended = Intervention.objects.filter(status=Intervention.ACTIVE,
end__lt=datetime.date.today())
qs = Intervention.objects\
.prefetch_related('frs')\
.filter(status=Intervention.ENDED)\
.annotate(frs_total_outstanding=Sum('frs__outstanding_amt_local'),
frs_total_actual_amt=Sum('frs__total_amt_local'),
frs_intervention_amt=Sum('frs__actual_amt_local'))\
.filter(frs_total_outstanding=0, frs_total_actual_amt=F('frs_intervention_amt'))
# we should try all interventions except the ones in terminal statuses
possible_interventions = Intervention.objects.exclude(status__in=[
Intervention.DRAFT, Intervention.TERMINATED, Intervention.CLOSED, Intervention.SUSPENDED
]).order_by('id')
processed = 0
for intervention in itertools.chain(active_ended, qs):
for intervention in possible_interventions:
old_status = intervention.status
with transaction.atomic():
validator = InterventionValid(intervention, user=admin_user, disable_rigid_check=True)
Expand All @@ -142,7 +136,7 @@ def _make_intervention_status_automatic_transitions(country_name):

logger.error('Bad interventions {}'.format(len(bad_interventions)))
logger.error('Bad interventions ids: ' + ' '.join(str(a.id) for a in bad_interventions))
logger.info('Total interventions {}'.format(active_ended.count() + qs.count()))
logger.info('Total interventions {}'.format(possible_interventions.count()))
logger.info("Transitioned interventions {} ".format(processed))


Expand Down
116 changes: 83 additions & 33 deletions src/etools/applications/partners/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@
from django.utils import timezone

from unicef_attachments.models import Attachment
from unicef_locations.tests.factories import LocationFactory

import etools.applications.partners.tasks
from etools.applications.attachments.tests.factories import AttachmentFactory, AttachmentFileTypeFactory
from etools.applications.core.tests.cases import BaseTenantTestCase
from etools.applications.funds.tests.factories import FundsReservationHeaderFactory
from etools.applications.partners.models import Agreement, Intervention
from etools.applications.partners.models import Agreement, Intervention, InterventionBudget
from etools.applications.partners.tasks import _make_intervention_status_automatic_transitions
from etools.applications.partners.tests.factories import (
AgreementFactory,
CoreValuesAssessmentFactory,
InterventionFactory,
InterventionResultLinkFactory,
PartnerFactory,
)
from etools.applications.reports.tests.factories import CountryProgrammeFactory
from etools.applications.reports.models import ResultType
from etools.applications.reports.tests.factories import (
CountryProgrammeFactory,
LowerResultFactory,
OfficeFactory,
ReportingRequirementFactory,
SectionFactory,
)
from etools.applications.users.tests.factories import CountryFactory, UserFactory


Expand Down Expand Up @@ -549,23 +559,11 @@ def test_make_intervention_status_automatic_transitions_with_valid_interventions
interventions.append(intervention)

# Create a few items that should be ignored. If they're not ignored, this test will fail.
# Ignored because of end date
InterventionFactory(status=Intervention.ACTIVE, end=datetime.date.today() + datetime.timedelta(days=2))
# Ignored because of status
InterventionFactory(status=Intervention.IMPLEMENTED, end=end_date)
# Ignored because funds total outstanding != 0
intervention = InterventionFactory(status=Intervention.ENDED, end=end_date)
for i in range(3):
FundsReservationHeaderFactory(intervention=intervention, outstanding_amt=Decimal(i),
intervention_amt=_make_decimal(i),
actual_amt=_make_decimal(i), total_amt=_make_decimal(i))

# Ignored because funds totals don't match
intervention = InterventionFactory(status=Intervention.ENDED, end=end_date)
for i in range(3):
FundsReservationHeaderFactory(intervention=intervention, outstanding_amt=Decimal(0.00),
intervention_amt=_make_decimal(i),
actual_amt=_make_decimal(i + 1), total_amt=_make_decimal(i))
InterventionFactory(status=Intervention.TERMINATED)
InterventionFactory(status=Intervention.CLOSED)
InterventionFactory(status=Intervention.SUSPENDED)
InterventionFactory(status=Intervention.DRAFT)

# Mock InterventionValid() to always return True.
mock_validator = mock.Mock(spec=['is_valid'])
Expand Down Expand Up @@ -619,22 +617,11 @@ def test_make_intervention_status_automatic_transitions_with_mixed_interventions
interventions.append(intervention)

# Create a few items that should be ignored. If they're not ignored, this test will fail.
# Ignored because of end date
InterventionFactory(status=Intervention.ACTIVE, end=datetime.date.today() + datetime.timedelta(days=2))
# Ignored because of status
InterventionFactory(status=Intervention.IMPLEMENTED, end=end_date)
# Ignored because funds total outstanding != 0
intervention = InterventionFactory(status=Intervention.ENDED, end=end_date)
for i in range(3):
FundsReservationHeaderFactory(intervention=intervention, outstanding_amt=Decimal(i),
intervention_amt=_make_decimal(i),
actual_amt=_make_decimal(i), total_amt=_make_decimal(i))
# Ignored because funds totals don't match
intervention = InterventionFactory(status=Intervention.ENDED, end=end_date)
for i in range(3):
FundsReservationHeaderFactory(intervention=intervention, outstanding_amt=Decimal(0.00),
intervention_amt=_make_decimal(i),
actual_amt=_make_decimal(i + 1), total_amt=_make_decimal(i))
InterventionFactory(status=Intervention.TERMINATED)
InterventionFactory(status=Intervention.CLOSED)
InterventionFactory(status=Intervention.SUSPENDED)
InterventionFactory(status=Intervention.DRAFT)

def mock_intervention_valid_class_side_effect(*args, **kwargs):
"""Side effect for my mock InterventionValid() that gets called each time my mock InterventionValid() class
Expand Down Expand Up @@ -680,6 +667,69 @@ def mock_intervention_valid_class_side_effect(*args, **kwargs):
]
self._assertCalls(mock_logger.error, expected_call_args)

def test_activate_intervention_with_task(self, _mock_db_connection, _mock_logger):
today = datetime.date.today()
unicef_staff = UserFactory(is_staff=True, groups__data=['UNICEF User'])

partner = PartnerFactory(name='Partner 2')
active_agreement = AgreementFactory(
partner=partner,
status=Agreement.SIGNED,
signed_by_unicef_date=today - datetime.timedelta(days=2),
signed_by_partner_date=today - datetime.timedelta(days=2),
start=today - datetime.timedelta(days=2),
)

active_intervention = InterventionFactory(
agreement=active_agreement,
title='Active Intervention',
document_type=Intervention.PD,
start=today - datetime.timedelta(days=1),
end=today + datetime.timedelta(days=365),
status=Intervention.SIGNED,
country_programme=active_agreement.country_programme,
# budget_owner=unicef_staff,
# date_sent_to_partner=today - datetime.timedelta(days=1),
signed_by_unicef_date=today - datetime.timedelta(days=1),
signed_by_partner_date=today - datetime.timedelta(days=1),
unicef_signatory=unicef_staff,
partner_authorized_officer_signatory=partner.staff_members.all().first(),
# cash_transfer_modalities=[Intervention.CASH_TRANSFER_DIRECT],
)
InterventionBudget.objects.get_or_create(
intervention=active_intervention,
defaults={
'unicef_cash': 100,
'unicef_cash_local': 10,
'partner_contribution': 200,
'partner_contribution_local': 20,
'in_kind_amount_local': 10,
}
)
active_intervention.flat_locations.add(LocationFactory())
active_intervention.partner_focal_points.add(partner.staff_members.all().first())
active_intervention.unicef_focal_points.add(unicef_staff)
active_intervention.offices.add(OfficeFactory())
active_intervention.sections.add(SectionFactory())
ReportingRequirementFactory(intervention=active_intervention)
AttachmentFactory(
code='partners_intervention_signed_pd',
content_object=active_intervention,
)
FundsReservationHeaderFactory(intervention=active_intervention)

result_link = InterventionResultLinkFactory(
intervention=active_intervention,
cp_output__result_type__name=ResultType.OUTPUT,
)
LowerResultFactory(result_link=result_link)
# activity = InterventionActivityFactory(result=pd_output) # epd related stuff
# activity.time_frames.add(active_intervention.quarters.first())

_make_intervention_status_automatic_transitions(self.country_name)
active_intervention.refresh_from_db()
self.assertEqual(active_intervention.status, Intervention.ACTIVE)


@mock.patch('etools.applications.partners.tasks.logger', spec=['info'])
@mock.patch('etools.applications.partners.tasks.connection', spec=['set_tenant'])
Expand Down

0 comments on commit be2ffa1

Please sign in to comment.