Skip to content

Commit 80f5641

Browse files
authored
Merge pull request #3682 from unicef/staging
Staging
2 parents c4fd264 + dfa1bed commit 80f5641

File tree

47 files changed

+1138
-677
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1138
-677
lines changed

src/etools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
VERSION = __version__ = '11.2.3'
1+
VERSION = __version__ = '11.4'
22
NAME = 'eTools'

src/etools/applications/audit/management/commands/update_audit_permissions.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ class Command(BaseCommand):
6262
'audit.engagement.status',
6363
'audit.engagement.status_date',
6464

65-
'audit.spotcheck.face_form_start_date',
66-
'audit.spotcheck.face_form_end_date',
67-
6865
'purchase_order.purchaseorder.*',
6966
'purchase_order.auditorfirm.*',
7067
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 3.2.19 on 2024-05-07 10:59
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('audit', '0030_engagement_send_back_comment'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='engagement',
15+
name='end_date',
16+
field=models.DateField(blank=True, null=True, verbose_name='Start date of first reporting FACE'),
17+
),
18+
migrations.AlterField(
19+
model_name='engagement',
20+
name='start_date',
21+
field=models.DateField(blank=True, null=True, verbose_name='End date of last reporting FACE'),
22+
),
23+
]

src/etools/applications/audit/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ class Engagement(InheritedModelMixin, TimeStampedModel, models.Model):
111111
)
112112
partner_contacted_at = models.DateField(verbose_name=_('Date IP was contacted'), blank=True, null=True)
113113
engagement_type = models.CharField(verbose_name=_('Engagement Type'), max_length=10, choices=TYPES)
114-
start_date = models.DateField(verbose_name=_('Period Start Date'), blank=True, null=True)
115-
end_date = models.DateField(verbose_name=_('Period End Date'), blank=True, null=True)
114+
start_date = models.DateField(verbose_name=_('Start date of first reporting FACE'), blank=True, null=True)
115+
end_date = models.DateField(verbose_name=_('End date of last reporting FACE'), blank=True, null=True)
116116
total_value = models.DecimalField(
117117
verbose_name=_('Total value of selected FACE form(s)'), default=0, decimal_places=2, max_digits=20
118118
)

src/etools/applications/audit/serializers/engagement.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ class EngagementSerializer(
257257
WritableNestedParentSerializerMixin,
258258
EngagementListSerializer
259259
):
260+
face_form_start_date = serializers.DateField(label='FACE Form(s) Start Date', read_only=True, source='start_date')
261+
face_form_end_date = serializers.DateField(label='FACE Form(s) End Date', read_only=True, source='end_date')
262+
260263
staff_members = SeparatedReadWriteField(
261264
read_field=serializers.SerializerMethodField(),
262265
label=_('Audit Staff Team Members')
@@ -287,6 +290,7 @@ class EngagementSerializer(
287290

288291
class Meta(EngagementListSerializer.Meta):
289292
fields = EngagementListSerializer.Meta.fields + [
293+
'face_form_start_date', 'face_form_end_date',
290294
'total_value', 'staff_members', 'active_pd', 'authorized_officers', 'users_notified',
291295
'joint_audit', 'year_of_audit', 'shared_ip_with', 'exchange_rate', 'currency_of_report',
292296
'start_date', 'end_date', 'partner_contacted_at', 'date_of_field_visit', 'date_of_draft_report_to_ip',
@@ -399,16 +403,13 @@ class Meta(WritableNestedSerializerMixin.Meta):
399403
class SpotCheckSerializer(ActivePDValidationMixin, EngagementSerializer):
400404
findings = FindingSerializer(many=True, required=False)
401405

402-
face_form_start_date = serializers.DateField(label='FACE Form(s) Start Date', read_only=True, source='start_date')
403-
face_form_end_date = serializers.DateField(label='FACE Form(s) End Date', read_only=True, source='end_date')
404-
405406
pending_unsupported_amount = serializers.DecimalField(20, 2, label=_('Pending Unsupported Amount'), read_only=True)
406407

407408
class Meta(EngagementSerializer.Meta):
408409
model = SpotCheck
409410
fields = EngagementSerializer.Meta.fields + [
410411
'total_amount_tested', 'total_amount_of_ineligible_expenditure',
411-
'internal_controls', 'findings', 'face_form_start_date', 'face_form_end_date',
412+
'internal_controls', 'findings',
412413
'amount_refunded', 'additional_supporting_documentation_provided',
413414
'justification_provided_and_accepted', 'write_off_required', 'pending_unsupported_amount',
414415
'explanation_for_additional_information'
@@ -561,18 +562,29 @@ def get_number_of_financial_findings(self, obj):
561562
def _validate_financial_findings(self, validated_data):
562563
financial_findings = validated_data.get('financial_findings')
563564
audited_expenditure = validated_data.get('audited_expenditure')
564-
if not (financial_findings or audited_expenditure):
565+
financial_findings_local = validated_data.get('financial_findings_local')
566+
audited_expenditure_local = validated_data.get('audited_expenditure_local')
567+
if not (financial_findings or audited_expenditure) and not (financial_findings_local or audited_expenditure_local):
565568
return
566569

567570
if not financial_findings:
568571
financial_findings = self.instance.financial_findings if self.instance else None
569572
if not audited_expenditure:
570573
audited_expenditure = self.instance.audited_expenditure if self.instance else None
571574

572-
if audited_expenditure and financial_findings and financial_findings > audited_expenditure:
575+
if audited_expenditure and financial_findings and financial_findings >= audited_expenditure:
573576
raise serializers.ValidationError({'financial_findings': _('Cannot exceed Audited Expenditure')})
574577

578+
if not financial_findings_local:
579+
financial_findings_local = self.instance.financial_findings_local if self.instance else None
580+
if not audited_expenditure_local:
581+
audited_expenditure_local = self.instance.audited_expenditure_local if self.instance else None
582+
583+
if audited_expenditure_local and financial_findings_local and financial_findings_local >= audited_expenditure_local:
584+
raise serializers.ValidationError({'financial_findings_local': _('Cannot exceed Audited Expenditure Local')})
585+
575586
def validate(self, validated_data):
587+
validated_data = super().validate(validated_data)
576588
self._validate_financial_findings(validated_data)
577589
return validated_data
578590

src/etools/applications/audit/serializers/mixins.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,33 @@ def _validate_dates(self, validated_data):
5050
if value and key in date_fields and timezone.now().date() < value:
5151
errors[key] = _('Date should be in past.')
5252

53+
start_date = validated_data.get('start_date', self.instance.start_date if self.instance else None)
54+
end_date = validated_data.get('end_date', self.instance.end_date if self.instance else None)
55+
partner_contacted_at = validated_data.get('partner_contacted_at', self.instance.partner_contacted_at if self.instance else None)
56+
date_of_field_visit = validated_data.get('date_of_field_visit', self.instance.date_of_field_visit if self.instance else None)
57+
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)
58+
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)
59+
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)
60+
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)
61+
62+
if start_date and end_date and end_date < start_date:
63+
errors['end_date'] = _('This date should be after Period Start Date.')
64+
if end_date and partner_contacted_at and partner_contacted_at < end_date:
65+
errors['partner_contacted_at'] = _('This date should be after Period End Date.')
66+
67+
if partner_contacted_at and date_of_field_visit and date_of_field_visit < partner_contacted_at:
68+
errors['date_of_field_visit'] = _('This date should be after Date IP was contacted.')
69+
70+
if date_of_field_visit and date_of_draft_report_to_ip and date_of_draft_report_to_ip < date_of_field_visit:
71+
# date of field visit is editable even if date of draft report is readonly, map error to field visit date
72+
errors['date_of_field_visit'] = _('This date should be before Date Draft Report Issued to IP.')
73+
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:
74+
errors['date_of_comments_by_ip'] = _('This date should be after Date Draft Report Issued to UNICEF.')
75+
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:
76+
errors['date_of_draft_report_to_unicef'] = _('This date should be after Date Comments Received from IP.')
77+
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:
78+
errors['date_of_comments_by_unicef'] = _('This date should be after Date Draft Report Issued to UNICEF.')
79+
5380
if errors:
5481
raise serializers.ValidationError(errors)
5582
return validated_data

src/etools/applications/audit/tests/factories.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import random
2+
from datetime import timedelta
23

34
from django.db.models import signals
5+
from django.utils import timezone
46

57
import factory
68
from factory import fuzzy
@@ -133,16 +135,25 @@ class Meta:
133135

134136

135137
class AuditFactory(EngagementFactory):
138+
start_date = timezone.now().date() - timedelta(days=30)
139+
end_date = timezone.now().date() - timedelta(days=10)
140+
136141
class Meta:
137142
model = Audit
138143

139144

140145
class SpecialAuditFactory(EngagementFactory):
146+
start_date = timezone.now().date() - timedelta(days=30)
147+
end_date = timezone.now().date() - timedelta(days=10)
148+
141149
class Meta:
142150
model = SpecialAudit
143151

144152

145153
class SpotCheckFactory(EngagementFactory):
154+
start_date = timezone.now().date() - timedelta(days=30)
155+
end_date = timezone.now().date() - timedelta(days=10)
156+
146157
class Meta:
147158
model = SpotCheck
148159

0 commit comments

Comments
 (0)