Skip to content

Commit

Permalink
Merge pull request #3686 from unicef/develop
Browse files Browse the repository at this point in the history
dev
  • Loading branch information
robertavram authored Jun 7, 2024
2 parents e481341 + caf3c23 commit 82d3942
Show file tree
Hide file tree
Showing 32 changed files with 964 additions and 645 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ class Command(BaseCommand):
'audit.engagement.status',
'audit.engagement.status_date',

'audit.spotcheck.face_form_start_date',
'audit.spotcheck.face_form_end_date',

'purchase_order.purchaseorder.*',
'purchase_order.auditorfirm.*',
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.19 on 2024-05-07 10:59

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('audit', '0030_engagement_send_back_comment'),
]

operations = [
migrations.AlterField(
model_name='engagement',
name='end_date',
field=models.DateField(blank=True, null=True, verbose_name='Start date of first reporting FACE'),
),
migrations.AlterField(
model_name='engagement',
name='start_date',
field=models.DateField(blank=True, null=True, verbose_name='End date of last reporting FACE'),
),
]
4 changes: 2 additions & 2 deletions src/etools/applications/audit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ class Engagement(InheritedModelMixin, TimeStampedModel, models.Model):
)
partner_contacted_at = models.DateField(verbose_name=_('Date IP was contacted'), blank=True, null=True)
engagement_type = models.CharField(verbose_name=_('Engagement Type'), max_length=10, choices=TYPES)
start_date = models.DateField(verbose_name=_('Period Start Date'), blank=True, null=True)
end_date = models.DateField(verbose_name=_('Period End Date'), blank=True, null=True)
start_date = models.DateField(verbose_name=_('Start date of first reporting FACE'), blank=True, null=True)
end_date = models.DateField(verbose_name=_('End date of last reporting FACE'), blank=True, null=True)
total_value = models.DecimalField(
verbose_name=_('Total value of selected FACE form(s)'), default=0, decimal_places=2, max_digits=20
)
Expand Down
24 changes: 18 additions & 6 deletions src/etools/applications/audit/serializers/engagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ class EngagementSerializer(
WritableNestedParentSerializerMixin,
EngagementListSerializer
):
face_form_start_date = serializers.DateField(label='FACE Form(s) Start Date', read_only=True, source='start_date')
face_form_end_date = serializers.DateField(label='FACE Form(s) End Date', read_only=True, source='end_date')

staff_members = SeparatedReadWriteField(
read_field=serializers.SerializerMethodField(),
label=_('Audit Staff Team Members')
Expand Down Expand Up @@ -287,6 +290,7 @@ class EngagementSerializer(

class Meta(EngagementListSerializer.Meta):
fields = EngagementListSerializer.Meta.fields + [
'face_form_start_date', 'face_form_end_date',
'total_value', 'staff_members', 'active_pd', 'authorized_officers', 'users_notified',
'joint_audit', 'year_of_audit', 'shared_ip_with', 'exchange_rate', 'currency_of_report',
'start_date', 'end_date', 'partner_contacted_at', 'date_of_field_visit', 'date_of_draft_report_to_ip',
Expand Down Expand Up @@ -399,16 +403,13 @@ class Meta(WritableNestedSerializerMixin.Meta):
class SpotCheckSerializer(ActivePDValidationMixin, EngagementSerializer):
findings = FindingSerializer(many=True, required=False)

face_form_start_date = serializers.DateField(label='FACE Form(s) Start Date', read_only=True, source='start_date')
face_form_end_date = serializers.DateField(label='FACE Form(s) End Date', read_only=True, source='end_date')

pending_unsupported_amount = serializers.DecimalField(20, 2, label=_('Pending Unsupported Amount'), read_only=True)

class Meta(EngagementSerializer.Meta):
model = SpotCheck
fields = EngagementSerializer.Meta.fields + [
'total_amount_tested', 'total_amount_of_ineligible_expenditure',
'internal_controls', 'findings', 'face_form_start_date', 'face_form_end_date',
'internal_controls', 'findings',
'amount_refunded', 'additional_supporting_documentation_provided',
'justification_provided_and_accepted', 'write_off_required', 'pending_unsupported_amount',
'explanation_for_additional_information'
Expand Down Expand Up @@ -561,18 +562,29 @@ def get_number_of_financial_findings(self, obj):
def _validate_financial_findings(self, validated_data):
financial_findings = validated_data.get('financial_findings')
audited_expenditure = validated_data.get('audited_expenditure')
if not (financial_findings or audited_expenditure):
financial_findings_local = validated_data.get('financial_findings_local')
audited_expenditure_local = validated_data.get('audited_expenditure_local')
if not (financial_findings or audited_expenditure) and not (financial_findings_local or audited_expenditure_local):
return

if not financial_findings:
financial_findings = self.instance.financial_findings if self.instance else None
if not audited_expenditure:
audited_expenditure = self.instance.audited_expenditure if self.instance else None

if audited_expenditure and financial_findings and financial_findings > audited_expenditure:
if audited_expenditure and financial_findings and financial_findings >= audited_expenditure:
raise serializers.ValidationError({'financial_findings': _('Cannot exceed Audited Expenditure')})

if not financial_findings_local:
financial_findings_local = self.instance.financial_findings_local if self.instance else None
if not audited_expenditure_local:
audited_expenditure_local = self.instance.audited_expenditure_local if self.instance else None

if audited_expenditure_local and financial_findings_local and financial_findings_local >= audited_expenditure_local:
raise serializers.ValidationError({'financial_findings_local': _('Cannot exceed Audited Expenditure Local')})

def validate(self, validated_data):
validated_data = super().validate(validated_data)
self._validate_financial_findings(validated_data)
return validated_data

Expand Down
27 changes: 27 additions & 0 deletions src/etools/applications/audit/serializers/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,33 @@ def _validate_dates(self, validated_data):
if value and key in date_fields and timezone.now().date() < value:
errors[key] = _('Date should be in past.')

start_date = validated_data.get('start_date', self.instance.start_date if self.instance else None)
end_date = validated_data.get('end_date', self.instance.end_date if self.instance else None)
partner_contacted_at = validated_data.get('partner_contacted_at', self.instance.partner_contacted_at if self.instance else None)
date_of_field_visit = validated_data.get('date_of_field_visit', self.instance.date_of_field_visit if self.instance else None)
date_of_draft_report_to_ip = validated_data.get('date_of_draft_report_to_ip', self.instance.date_of_draft_report_to_ip if self.instance else None)
date_of_comments_by_ip = validated_data.get('date_of_comments_by_ip', self.instance.date_of_comments_by_ip if self.instance else None)
date_of_draft_report_to_unicef = validated_data.get('date_of_draft_report_to_unicef', self.instance.date_of_draft_report_to_unicef if self.instance else None)
date_of_comments_by_unicef = validated_data.get('date_of_comments_by_unicef', self.instance.date_of_comments_by_unicef if self.instance else None)

if start_date and end_date and end_date < start_date:
errors['end_date'] = _('This date should be after Period Start Date.')
if end_date and partner_contacted_at and partner_contacted_at < end_date:
errors['partner_contacted_at'] = _('This date should be after Period End Date.')

if partner_contacted_at and date_of_field_visit and date_of_field_visit < partner_contacted_at:
errors['date_of_field_visit'] = _('This date should be after Date IP was contacted.')

if date_of_field_visit and date_of_draft_report_to_ip and date_of_draft_report_to_ip < date_of_field_visit:
# date of field visit is editable even if date of draft report is readonly, map error to field visit date
errors['date_of_field_visit'] = _('This date should be before Date Draft Report Issued to IP.')
if date_of_draft_report_to_ip and date_of_comments_by_ip and date_of_comments_by_ip < date_of_draft_report_to_ip:
errors['date_of_comments_by_ip'] = _('This date should be after Date Draft Report Issued to UNICEF.')
if date_of_comments_by_ip and date_of_draft_report_to_unicef and date_of_draft_report_to_unicef < date_of_comments_by_ip:
errors['date_of_draft_report_to_unicef'] = _('This date should be after Date Comments Received from IP.')
if date_of_draft_report_to_unicef and date_of_comments_by_unicef and date_of_comments_by_unicef < date_of_draft_report_to_unicef:
errors['date_of_comments_by_unicef'] = _('This date should be after Date Draft Report Issued to UNICEF.')

if errors:
raise serializers.ValidationError(errors)
return validated_data
11 changes: 11 additions & 0 deletions src/etools/applications/audit/tests/factories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import random
from datetime import timedelta

from django.db.models import signals
from django.utils import timezone

import factory
from factory import fuzzy
Expand Down Expand Up @@ -133,16 +135,25 @@ class Meta:


class AuditFactory(EngagementFactory):
start_date = timezone.now().date() - timedelta(days=30)
end_date = timezone.now().date() - timedelta(days=10)

class Meta:
model = Audit


class SpecialAuditFactory(EngagementFactory):
start_date = timezone.now().date() - timedelta(days=30)
end_date = timezone.now().date() - timedelta(days=10)

class Meta:
model = SpecialAudit


class SpotCheckFactory(EngagementFactory):
start_date = timezone.now().date() - timedelta(days=30)
end_date = timezone.now().date() - timedelta(days=10)

class Meta:
model = SpotCheck

Expand Down
Loading

0 comments on commit 82d3942

Please sign in to comment.