Skip to content

Commit

Permalink
Merge pull request #1799 from GSA-TTS/main
Browse files Browse the repository at this point in the history
  • Loading branch information
jadudm authored Aug 12, 2023
2 parents 15ffa54 + cfb4a29 commit e045329
Show file tree
Hide file tree
Showing 73 changed files with 1,802 additions and 399 deletions.
4 changes: 4 additions & 0 deletions backend/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.http import Http404, HttpResponse, JsonResponse
from django.urls import reverse
from django.views import View, generic
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from rest_framework import viewsets
from rest_framework.authentication import BaseAuthentication
Expand All @@ -23,6 +24,8 @@
UEISerializer,
)

UserModel = get_user_model()

AUDITEE_INFO_PREVIOUS_STEP_DATA_WE_NEED = [
"user_provided_organization_type",
"met_spending_threshold",
Expand Down Expand Up @@ -128,6 +131,7 @@ def access_and_submission_check(user, data):
role="certifying_auditor_contact",
email=serializer.data.get("certifying_auditor_contact"),
)

for contact in serializer.data.get("auditee_contacts"):
Access.objects.create(sac=sac, role="editor", email=contact)
for contact in serializer.data.get("auditor_contacts"):
Expand Down
4 changes: 3 additions & 1 deletion backend/audit/cross_validation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@
]
"""
from .auditee_ueis_match import auditee_ueis_match
from .additional_ueis import additional_ueis
from .auditee_ueis_match import auditee_ueis_match
from .audit_findings import audit_findings
from .sac_validation_shape import sac_validation_shape # noqa: F401

functions = [
audit_findings,
auditee_ueis_match,
additional_ueis,
]
28 changes: 28 additions & 0 deletions backend/audit/cross_validation/audit_findings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
def audit_findings(sac_dict):
"""
Checks that the number of audit findings in the Federal Awards and Audit Findings
sections are consistent.
"""
all_sections = sac_dict["sf_sac_sections"]
awards_outer = all_sections.get("federal_awards", {})
awards = awards_outer.get("federal_awards", []) if awards_outer else []
finds_outer = all_sections.get("findings_text", {})
findings = finds_outer.get("findings_text_entries", []) if finds_outer else []
# If both empty, that's consistent:
if not awards and not findings:
return []
if awards:
# How to determine if findings is required? Loop through the awards?
num_expected = sum(_.get("number_of_audit_findings", 0) for _ in findings)
# For now, just check for non-zero
if num_expected and not awards:
return [
{
"error": "There are findings listed in Federal Awards "
" but none in Findings Text"
}
]

if False > 1:
return [{"error": "error message"}]
return []
4 changes: 4 additions & 0 deletions backend/audit/cross_validation/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def err_auditee_ueis_match():
return "Not all auditee UEIs matched."


def err_awards_findings_but_no_findings_text():
return "There are findings indicated in Federal Awards but" "none in Findings Text."


def err_missing_tribal_data_sharing_consent():
return (
"As a tribal organization, you must complete the data "
Expand Down
19 changes: 14 additions & 5 deletions backend/audit/etl.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ def load_findings(self):

def load_federal_award(self):
federal_awards = self.single_audit_checklist.federal_awards
report_id = self.single_audit_checklist.report_id
try:
general = General.objects.get(report_id=report_id)
except General.DoesNotExist:
logger.error(
f"General must be loaded before FederalAward. report_id = {report_id}"
)
return
general.total_amount_expended = federal_awards["FederalAwards"].get(
"total_amount_expended"
)
general.save()

for entry in federal_awards["FederalAwards"]["federal_awards"]:
program = entry["program"]
loan = entry["loan_or_loan_guarantee"]
Expand Down Expand Up @@ -113,12 +126,10 @@ def load_federal_award(self):
is_loan=loan["is_guaranteed"] == "Y",
loan_balance=loan["loan_balance_at_audit_period_end"],
is_direct=is_direct,
is_major=program["is_major"] == "Y",
mp_audit_report_type=program["audit_report_type"],
findings_count=program["number_of_audit_findings"],
is_passthrough_award=is_passthrough,
passthrough_amount=subrecipient_amount,
type_requirement=None, # TODO: What is this?
)
federal_award.save()

Expand Down Expand Up @@ -274,9 +285,7 @@ def load_general(self):
entity_type=general_information["user_provided_organization_type"],
number_months=general_information["audit_period_other_months"],
audit_period_covered=general_information["audit_period_covered"],
is_report_required=None, # TODO: Notes say this hasn't been used since 2008.
total_fed_expenditures=None, # TODO: Where does this come from?
type_report_major_program=None, # TODO: Where does this come from?
total_amount_expended=None, # loaded from FederalAward
type_audit_code="UG",
is_public=self.single_audit_checklist.is_public,
data_source="G-FAC",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"FederalAwards": {
"auditee_uei": "TEST0001TEST",
"total_amount_expended": 12345,
"federal_awards": [
{
"program": {
"federal_agency_prefix": "42",
"three_digit_extension": "RD",
"additional_award_identification": 1234,
"program_name": "FLOWER DREAMING",
"is_major": "N",
"audit_report_type": "",
"number_of_audit_findings": 0,
"amount_expended": 500,
"federal_program_total": 500
},
"loan_or_loan_guarantee": {
"is_guaranteed": "N",
"loan_balance_at_audit_period_end": 0
},
"direct_or_indirect_award": {
"is_direct": "N",
"entities": [
{
"passthrough_name": "Lothlórien",
"passthrough_identifying_number": "54321"
}
]
},
"cluster": {
"cluster_name": "N/A",
"cluster_total": 0
},
"subrecipients": {
"is_passed": "N"
},
"award_reference": "AWARD-0001"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"ein": "123456789",
"audit_type": "single-audit",
"auditee_uei": "TEST0001TEST",
"auditee_zip": "94109",
"auditor_ein": "987654321",
"auditor_zip": "94109",
"auditee_city": "HAL",
"auditee_name": "IBM",
"auditor_city": "Rivendell",
"is_usa_based": true,
"auditee_email": "[email protected]",
"auditee_phone": "5558675309",
"auditee_state": "CA",
"auditor_email": "[email protected]",
"auditor_phone": "5558675309",
"auditor_state": "CA",
"auditor_country": "United States",
"auditor_firm_name": "Fellowship",
"audit_period_covered": "annual",
"auditee_contact_name": "Frodo Baggins",
"auditor_contact_name": "Sauron",
"auditee_contact_title": "Ringbearer",
"auditor_contact_title": "Dark Lord",
"multiple_eins_covered": false,
"multiple_ueis_covered": false,
"auditee_address_line_1": "HAL",
"auditor_address_line_1": "Main Hall",
"met_spending_threshold": true,
"auditee_fiscal_period_end": "2023-01-01",
"ein_not_an_ssn_attestation": true,
"auditee_fiscal_period_start": "2022-01-01",
"user_provided_organization_type": "non-profit",
"auditor_ein_not_an_ssn_attestation": true
}
41 changes: 41 additions & 0 deletions backend/audit/fixtures/single_audit_checklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,47 @@ def _fake_general_information(auditee_name=None):
return general_information


def fake_auditor_certification():
"""Create fake auditor confirmation form data."""
fake = Faker()
data_step_1 = {
"is_OMB_limited": True,
"is_auditee_responsible": True,
"has_used_auditors_report": True,
"has_no_auditee_procedures": True,
"is_FAC_releasable": True,
}
data_step_2 = {
"auditor_name": fake.name(),
"auditor_title": fake.job(),
"auditor_certification_date_signed": fake.date(),
}

return data_step_1, data_step_2


def fake_auditee_certification():
"""Create fake auditor confirmation form data."""
fake = Faker()
data_step_1 = {
"has_no_PII": True,
"has_no_BII": True,
"meets_2CFR_specifications": True,
"is_2CFR_compliant": True,
"is_complete_and_accurate": True,
"has_engaged_auditor": True,
"is_issued_and_signed": True,
"is_FAC_releasable": True,
}
data_step_2 = {
"auditee_name": fake.name(),
"auditee_title": fake.job(),
"auditee_certification_date_signed": fake.date(),
}

return data_step_1, data_step_2


def _create_sac(user, auditee_name):
"""Create a single example SAC."""
SingleAuditChecklist = apps.get_model("audit.SingleAuditChecklist")
Expand Down
31 changes: 31 additions & 0 deletions backend/audit/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,34 @@ def clean_booleans(self):
dollar_threshold = forms.IntegerField(min_value=1)
is_low_risk_auditee = forms.MultipleChoiceField(choices=choices_YoN)
agencies = forms.MultipleChoiceField(choices=choices_agencies)


class AuditorCertificationStep1Form(forms.Form):
is_OMB_limited = forms.BooleanField()
is_auditee_responsible = forms.BooleanField()
has_used_auditors_report = forms.BooleanField()
has_no_auditee_procedures = forms.BooleanField()
is_FAC_releasable = forms.BooleanField()


class AuditorCertificationStep2Form(forms.Form):
auditor_name = forms.CharField()
auditor_title = forms.CharField()
auditor_certification_date_signed = forms.DateField()


class AuditeeCertificationStep1Form(forms.Form):
has_no_PII = forms.BooleanField()
has_no_BII = forms.BooleanField()
meets_2CFR_specifications = forms.BooleanField()
is_2CFR_compliant = forms.BooleanField()
is_complete_and_accurate = forms.BooleanField()
has_engaged_auditor = forms.BooleanField()
is_issued_and_signed = forms.BooleanField()
is_FAC_releasable = forms.BooleanField()


class AuditeeCertificationStep2Form(forms.Form):
auditee_name = forms.CharField()
auditee_title = forms.CharField()
auditee_certification_date_signed = forms.DateField()
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.2.3 on 2023-08-07 18:21

import audit.validators
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("audit", "0031_singleauditreportfile_component_page_numbers"),
]

operations = [
migrations.AddField(
model_name="singleauditchecklist",
name="auditee_certification",
field=models.JSONField(
blank=True,
null=True,
validators=[audit.validators.validate_auditee_certification_json],
),
),
migrations.AddField(
model_name="singleauditchecklist",
name="auditor_certification",
field=models.JSONField(
blank=True,
null=True,
validators=[audit.validators.validate_auditor_certification_json],
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.3 on 2023-08-10 19:49

import audit.validators
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("audit", "0032_singleauditchecklist_auditee_certification_and_more"),
]

operations = [
migrations.AddField(
model_name="singleauditchecklist",
name="tribal_data_consent",
field=models.JSONField(
blank=True,
null=True,
validators=[audit.validators.validate_tribal_data_consent_json],
),
),
]
15 changes: 15 additions & 0 deletions backend/audit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
validate_secondary_auditors_json,
validate_notes_to_sefa_json,
validate_single_audit_report_file,
validate_auditor_certification_json,
validate_auditee_certification_json,
validate_tribal_data_consent_json,
validate_audit_information_json,
validate_component_page_numbers,
)
Expand Down Expand Up @@ -273,6 +276,18 @@ class STATUS:
blank=True, null=True, validators=[validate_notes_to_sefa_json]
)

auditor_certification = models.JSONField(
blank=True, null=True, validators=[validate_auditor_certification_json]
)

auditee_certification = models.JSONField(
blank=True, null=True, validators=[validate_auditee_certification_json]
)

tribal_data_consent = models.JSONField(
blank=True, null=True, validators=[validate_tribal_data_consent_json]
)

def validate_full(self):
"""
Full validation, intended for use when the user indicates that the
Expand Down
Loading

0 comments on commit e045329

Please sign in to comment.