Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/etools/applications/audit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from etools.applications.audit.transitions.serializers import EngagementCancelSerializer, EngagementSendBackSerializer
from etools.applications.audit.utils import generate_final_report
from etools.applications.core.urlresolvers import build_frontend_url
from etools.applications.core.util_scripts import currency_format
from etools.applications.environment.notifications import send_notification_with_template
from etools.applications.partners.models import PartnerOrganization
from etools.applications.reports.models import Office, Section
Expand Down Expand Up @@ -225,6 +226,10 @@ class Engagement(InheritedModelMixin, TimeStampedModel, models.Model):
)
conducted_by_sai = models.BooleanField(verbose_name=_('Conducted by SAI'), blank=True, null=True)

follow_up_tracker = FieldTracker(
fields=['amount_refunded', 'additional_supporting_documentation_provided',
'justification_provided_and_accepted', 'write_off_required'])

objects = InheritanceManager()

class Meta:
Expand Down Expand Up @@ -336,6 +341,26 @@ def _notify_focal_points(self, template_name, context=None):
context=context,
)

def notify_focal_points_on_follow_up(self, template_name):
for focal_point in self.users_notified.all():
engagement = self.get_subclass()
context = {
'full_name': focal_point.full_name,
'reference_number': self.reference_number,
'object_url': self.get_object_url(user=focal_point),
'engagement_type': self.engagement_type,
'financial_findings': currency_format(engagement.financial_findings) if self.engagement_type == self.TYPES.audit else 0,
'amount_refunded': currency_format(self.amount_refunded),
'additional_supporting_documentation_provided': currency_format(self.additional_supporting_documentation_provided),
'justification_provided_and_accepted': currency_format(self.justification_provided_and_accepted),
'write_off_required': currency_format(self.write_off_required),
}
send_notification_with_template(
recipients=[focal_point.email],
template_name=template_name,
context=context,
)

@transition(status, source=STATUSES.partner_contacted, target=STATUSES.report_submitted,
permission=has_action_permission(action='submit'))
def submit(self):
Expand Down Expand Up @@ -773,6 +798,10 @@ class Audit(Engagement):
final_report = CodedGenericRelation(Attachment, verbose_name=_('Audit Final Report'),
code='audit_final_report', blank=True, )

follow_up_tracker = FieldTracker(
fields=['amount_refunded', 'additional_supporting_documentation_provided',
'justification_provided_and_accepted', 'write_off_required', 'financial_findings'])

objects = models.Manager()

class Meta:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from unicef_notification.utils import strip_text

name = 'audit/engagement/follow-up-changed'
defaults = {
'description': 'Email sent when the amounts in the follow-up are changed',
'subject': 'Verification of Change in Financial Findings Record',

'content': strip_text("""
Dear {{ full_name }},

This is to inform you that changes have been made in the Financial Findings section of the Engagement {{ object_url }} {{ reference_number }}.

The updated Figures now is:

Financial Findings: {{ financial_findings }} USD
Refunded Amount: {{ amount_refunded }} USD
Additional Supporting Documentation Provided: {{ additional_supporting_documentation_provided }} USD
Justification Provided and Accepted: {{ justification_provided_and_accepted }} USD
Pending Unsupported Amount: {{ write_off_required }} USD

Kindly review and verify these updates at your earliest convenience to ensure accuracy and alignment with our records.

Thank you.
"""),

'html_content': """
{% extends "email-templates/base" %}

{% block content %}
Dear {{ full_name }},<br/><br/>

This is to inform you that changes have been made in the Financial Findings section of the Engagement <a href="{{ object_url }}">{{ reference_number }}</a>. <br/><br/>

The updated Figures now is: <br/><br/>
{% if engagement_type == 'audit' %}Financial Findings: {{ financial_findings }} USD <br/>{% endif %}
Refunded Amount: {{ amount_refunded }} USD <br/>
Additional Supporting Documentation Provided: {{ additional_supporting_documentation_provided }} USD <br/>
Justification Provided and Accepted: {{ justification_provided_and_accepted }} USD <br/>
Pending Unsupported Amount: {{ write_off_required }} USD <br/><br/>

Kindly review and verify these updates at your earliest convenience to ensure accuracy and alignment with our records.<br/><br/>

Thank you.

{% endblock %}
"""
}
27 changes: 26 additions & 1 deletion src/etools/applications/audit/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from django.db.models.signals import m2m_changed, post_save
from django.dispatch import receiver

from etools.applications.audit.models import Auditor, Engagement, EngagementActionPoint
from etools.applications.audit.models import Auditor, Engagement, EngagementActionPoint, UNICEFAuditFocalPoint, Audit, \
SpotCheck
from etools.applications.audit.purchase_order.models import send_user_appointed_email
from etools.applications.users.models import Country, Realm

Expand Down Expand Up @@ -39,3 +40,27 @@ def action_point_updated_receiver(instance, created, **kwargs):
elif not instance.tracker.has_changed('reference_number'):
if instance.tracker.has_changed('assigned_to'):
instance.send_email(instance.assigned_to, 'audit/engagement/action_point_assigned')


@receiver(post_save, sender=Audit)
def follow_up_field_update(instance, created, **kwargs):
if not created and instance.follow_up_tracker.changed():
# country = Country.objects.get(schema_name=connection.schema_name)
# audit_focal_points = get_user_model().objects.filter(
# realms__country=country,
# realms__group=UNICEFAuditFocalPoint.as_group(),
# realms__is_active=True
# )
instance.notify_focal_points_on_follow_up('audit/engagement/follow-up-changed')


@receiver(post_save, sender=Engagement)
def follow_up_field_update(instance, created, **kwargs):
if not created and instance.follow_up_tracker.changed():
# country = Country.objects.get(schema_name=connection.schema_name)
# audit_focal_points = get_user_model().objects.filter(
# realms__country=country,
# realms__group=UNICEFAuditFocalPoint.as_group(),
# realms__is_active=True
# )
instance.notify_focal_points_on_follow_up('audit/engagement/follow-up-changed')
1 change: 1 addition & 0 deletions src/etools/applications/audit/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def setUp(self):
EmailTemplate.objects.get_or_create(name='audit/staff_member/invite')
EmailTemplate.objects.get_or_create(name='audit/engagement/submit_to_auditor')
EmailTemplate.objects.get_or_create(name='audit/engagement/reported_by_auditor')
EmailTemplate.objects.get_or_create(name='audit/engagement/follow-up-changed')

GroupWrapper.invalidate_instances()

Expand Down
3 changes: 2 additions & 1 deletion src/etools/applications/audit/tests/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def test_expected_email_templates_exist(self):
"""Ensure the email templates for this app exist and have content"""
for name in ('audit/engagement/submit_to_auditor',
'audit/engagement/reported_by_auditor',
'audit/engagement/action_point_assigned', ):
'audit/engagement/action_point_assigned',
'audit/engagement/follow-up-changed'):
q = EmailTemplate.objects.filter(name=name)
# There's a migration that creates these EmailTemplate objects, but with empty content. The empty
# content versions are pretty useless, so I want to ensure the fixture versions (with non-null content)
Expand Down
49 changes: 39 additions & 10 deletions src/etools/applications/audit/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,16 +1095,20 @@ def test_update_financial_finding_set(self):
self.assertEqual(self.engagement.financial_finding_set.count(), 0)
self.assertEqual(self.engagement.financial_findings, 0)
self.assertEqual(self.engagement.financial_findings_local, 0)
response = self._do_update(self.auditor, {"financial_finding_set": [
{
"title": "vat-incorrectly-claimed",
"local_amount": "96533.00",
"amount": "1253.67",
"description": "During the audit process..",
"recommendation": "We recommend proper control procedures",
"ip_comments": "payments accordingly"
}
]})
mock_send = Mock()
with patch("etools.applications.audit.models.send_notification_with_template", mock_send):
response = self._do_update(self.auditor, {"financial_finding_set": [
{
"title": "vat-incorrectly-claimed",
"local_amount": "96533.00",
"amount": "1253.67",
"description": "During the audit process..",
"recommendation": "We recommend proper control procedures",
"ip_comments": "payments accordingly"
}
]})
self.assertEqual(mock_send.call_count, 1)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.engagement.refresh_from_db()
self.assertEqual(self.engagement.financial_findings, Decimal('1253.67'))
Expand Down Expand Up @@ -1289,6 +1293,31 @@ def test_update_financial_finding_set(self):
self.assertEqual(response.data['total_amount_of_ineligible_expenditure_local'], '96533.00')


def test_update_follow_up(self):
self.engagement.exchange_rate = 2
self.engagement.save(update_fields=['exchange_rate'])

self.assertEqual(self.engagement.amount_refunded, 0)
self.assertEqual(self.engagement.amount_refunded_local, 0)
data = {
'amount_refunded_local': 333
}
mock_send = Mock()
with patch("etools.applications.audit.models.send_notification_with_template", mock_send):
response = self.forced_auth_req(
'patch',
'/api/audit/spot-checks/{}/'.format(self.engagement.id),
user=self.auditor, data=data
)
self.assertEqual(mock_send.call_count, 1)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.engagement.refresh_from_db()
self.assertEqual(self.engagement.total_amount_of_ineligible_expenditure, Decimal('1253.67'))
self.assertEqual(self.engagement.total_amount_of_ineligible_expenditure_local, Decimal('96533.00'))
self.assertEqual(response.data['total_amount_of_ineligible_expenditure'], '1253.67')
self.assertEqual(response.data['total_amount_of_ineligible_expenditure_local'], '96533.00')

class TestStaffSpotCheck(AuditTestCaseMixin, BaseTenantTestCase):
fixtures = ('audit_staff_organization',)

Expand Down