Skip to content

Commit

Permalink
Merge pull request #3162 from GSA-TTS/main
Browse files Browse the repository at this point in the history
2024-01-08 main -> prod
  • Loading branch information
danswick authored Jan 8, 2024
2 parents d2b40ff + a2c41b3 commit 95f3fff
Show file tree
Hide file tree
Showing 20 changed files with 293 additions and 230 deletions.
62 changes: 14 additions & 48 deletions backend/audit/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from django import forms
from config.settings import AGENCY_NAMES
from config.settings import (
AGENCY_NAMES,
GAAP_RESULTS,
SP_FRAMEWORK_BASIS,
SP_FRAMEWORK_OPINIONS,
)


class UploadReportForm(forms.Form):
Expand All @@ -17,6 +22,10 @@ class UploadReportForm(forms.Form):
upload_report = forms.FileField()


def _kvpair(info):
return (info["key"], info["value"])


class AuditInfoForm(forms.Form):
def clean_booleans(self):
data = self.cleaned_data
Expand All @@ -29,53 +38,10 @@ def clean_booleans(self):
return data

choices_YoN = (("True", "Yes"), ("False", "No"))
# These should probably have the lowercase values from Jsonnet:
choices_GAAP = (
("unmodified_opinion", "Unmodified opinion"),
("qualified_opinion", "Qualified opinion"),
("adverse_opinion", "Adverse opinion"),
("disclaimer_of_opinion", "Disclaimer of opinion"),
(
"not_gaap",
"Financial statements were not prepared in accordance with GAAP but were prepared in accordance with a special purpose framework.",
),
)
choices_SP_FRAMEWORK_BASIS = (
(
"cash_basis",
"Cash basis",
),
(
"tax_basis",
"Tax basis",
),
(
"contractual_basis",
"Contractual basis",
),
(
"other_basis",
"Other basis",
),
)
choices_SP_FRAMEWORK_OPINIONS = (
(
"unmodified_opinion",
"Unmodified opinion",
),
(
"qualified_opinion",
"Qualified opinion",
),
(
"adverse_opinion",
"Adverse opinion",
),
(
"disclaimer_of_opinion",
"Disclaimer of opinion",
),
)

choices_GAAP = [_kvpair(_) for _ in GAAP_RESULTS]
choices_SP_FRAMEWORK_BASIS = [_kvpair(_) for _ in SP_FRAMEWORK_BASIS]
choices_SP_FRAMEWORK_OPINIONS = [_kvpair(_) for _ in SP_FRAMEWORK_OPINIONS]

choices_agencies = list((i, i) for i in AGENCY_NAMES)

Expand Down
57 changes: 38 additions & 19 deletions backend/audit/get_agency_names.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,61 @@
from collections import OrderedDict
from pathlib import Path
import csv
import glob
import json
import os


# Pull in agency names from the most recent cfda-agencies-YYYYMMDD.csv
# Scraps rows with non-int agency nums and rows with empty agency names.
def get_agency_names():
# Lets build a dictionary of names, mapping the agency number
# to the name.
_agency_names = dict()
agency_names = OrderedDict()
"""
From a CSV of agency data, return a dictionary with the agency codes as
keys and the names as values, in order:
{
"00": "None",
"01": "African Development Foundation",
"02": "Agency for International Development",
"97": "Department of Homeland Security",
"98": "Agency for International Development",
"99": "Miscellaneous",
}
Pull in agency names from the most recent cfda-agencies-YYYYMMDD.csv
Scrap rows with non-numeric agency nums and rows with empty agency names.
"""
# Grab all the files, but we'll then sort and grab the latest one.
# MCJ: We need to figure out how to keep ALNs up-to-date...
# https://github.com/GSA-TTS/FAC/issues/1555
list_of_files = glob.glob("./schemas/source/data/cfda-agencies*.csv")
latest_file = max(list_of_files, key=os.path.getctime)

with open(latest_file, "r") as file:
csvreader = csv.reader(file)
csvreader = sorted(csvreader, key=lambda x: x[0])
for row in csvreader:
if row[0].isnumeric() and row[1] != "":
_agency_names[row[0]] = row[1]
# Now, create an OrderedDict of all the values
agency_names = OrderedDict(sorted(_agency_names.items(), key=lambda tupl: tupl[0]))
with open(latest_file, "r", newline="", encoding="UTF-8") as file:
agencies = csv.reader(file)
sorted_agencies = sorted(agencies, key=lambda x: x[0])

return agency_names
valid_agencies = filter(lambda r: r[0].isnumeric() and r[1] != "", sorted_agencies)
return {row[0]: row[1] for row in valid_agencies}


def get_audit_info_lists(name):
"""
Get lists of internal values and friendly strings for the responses to the
Audit Information form section.
Filter out anything with historical_only set to true.
get_audit_info_lists("gaap_results")
=>
[
{
"value": "Unmodified opinion",
"key": "unmodified_opinion",
"property": "UNMODIFIED_OPINION"
},
]
"""
jsonfile = Path("./schemas/output/audit/audit-info-values.json")
jsonfile = Path("./schemas/source/audit/audit-info-values.json")
jobj = json.loads(jsonfile.read_text(encoding="UTF-8"))
# Returns a list of dictionaries with the keys 'tag' and 'readable'
return jobj[name]

return [info for info in jobj[name] if not info.get("historical_only") is True]
15 changes: 12 additions & 3 deletions backend/audit/intake_to_dissemination.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,10 @@ def load_general(self):
dissemination. This structure is a list with one entry, a
dissemination.models.General instance.
"""

general_information = self.single_audit_checklist.general_information
audit_information = self.single_audit_checklist.audit_information
auditee_certification = self.single_audit_checklist.auditee_certification
# auditor_certification = self.single_audit_checklist.auditor_certification or {}
auditor_certification = self.single_audit_checklist.auditor_certification or {}
tribal_data_consent = self.single_audit_checklist.tribal_data_consent or {}
cognizant_agency = self.single_audit_checklist.cognizant_agency
oversight_agency = self.single_audit_checklist.oversight_agency
Expand All @@ -295,15 +294,23 @@ def load_general(self):
auditee_certify_title = auditee_certification["auditee_signature"][
"auditee_title"
]
auditor_certify_name = auditor_certification["auditor_signature"][
"auditor_name"
]
auditor_certify_title = auditor_certification["auditor_signature"][
"auditor_title"
]
auditor_certified_date = dates_by_status[status.AUDITOR_CERTIFIED]
auditee_certified_date = dates_by_status[status.AUDITEE_CERTIFIED]
elif self.mode == IntakeToDissemination.PRE_CERTIFICATION_REVIEW:
submitted_date = None
fac_accepted_date = submitted_date
auditee_certify_name = None
auditee_certify_title = None
auditor_certified_date = None
auditee_certified_date = None
auditor_certify_name = None
auditor_certify_title = None
auditor_certified_date = None

total_amount_expended = self.single_audit_checklist.federal_awards[
"FederalAwards"
Expand Down Expand Up @@ -381,6 +388,8 @@ def load_general(self):
report_id=self.report_id,
auditee_certify_name=auditee_certify_name,
auditee_certify_title=auditee_certify_title,
auditor_certify_name=auditor_certify_name,
auditor_certify_title=auditor_certify_title,
cognizant_agency=cognizant_agency,
oversight_agency=oversight_agency,
date_created=self.single_audit_checklist.date_created,
Expand Down
19 changes: 14 additions & 5 deletions backend/audit/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,14 +476,23 @@ def transition_to_ready_for_certification(self):

@transition(
field="submission_status",
source=STATUS.READY_FOR_CERTIFICATION,
source=[
STATUS.READY_FOR_CERTIFICATION,
STATUS.AUDITOR_CERTIFIED,
STATUS.AUDITEE_CERTIFIED,
],
target=STATUS.IN_PROGRESS,
)
def transition_to_in_progress_again(self):
"""
The permission checks verifying that the user attempting to do this has
the appropriate privileges will done at the view level.
the appropriate privileges will be done at the view level.
"""

# null out any existing certifications on this submission
self.auditor_certification = None
self.auditee_certification = None

self.transition_name.append(SingleAuditChecklist.STATUS.IN_PROGRESS)
self.transition_date.append(datetime.now(timezone.utc))

Expand All @@ -495,7 +504,7 @@ def transition_to_in_progress_again(self):
def transition_to_auditor_certified(self):
"""
The permission checks verifying that the user attempting to do this has
the appropriate privileges will done at the view level.
the appropriate privileges will be done at the view level.
"""
self.transition_name.append(SingleAuditChecklist.STATUS.AUDITOR_CERTIFIED)
self.transition_date.append(datetime.now(timezone.utc))
Expand All @@ -508,7 +517,7 @@ def transition_to_auditor_certified(self):
def transition_to_auditee_certified(self):
"""
The permission checks verifying that the user attempting to do this has
the appropriate privileges will done at the view level.
the appropriate privileges will be done at the view level.
"""
self.transition_name.append(SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED)
self.transition_date.append(datetime.now(timezone.utc))
Expand All @@ -521,7 +530,7 @@ def transition_to_auditee_certified(self):
def transition_to_submitted(self):
"""
The permission checks verifying that the user attempting to do this has
the appropriate privileges will done at the view level.
the appropriate privileges will be done at the view level.
"""

self.transition_name.append(SingleAuditChecklist.STATUS.SUBMITTED)
Expand Down
24 changes: 12 additions & 12 deletions backend/audit/templates/audit/audit-info-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ <h1>Financial statements</h1>
<p class="text-base">Select any of the following that apply.</p>
{% for pair in gaap_results %}
<div class="usa-checkbox">
<input id="gaap_results--{{ pair.tag }}"
<input id="gaap_results--{{ pair.key }}"
name="gaap_results"
class="usa-checkbox__input"
required
type="checkbox"
value="{{ pair.tag }}"
{% if pair.tag in form.cleaned_data.gaap_results %}checked{% endif %} />
<label class="usa-checkbox__label" for="gaap_results--{{ pair.tag }}">{{ pair.readable }}</label>
value="{{ pair.key }}"
{% if pair.key in form.cleaned_data.gaap_results %}checked{% endif %} />
<label class="usa-checkbox__label" for="gaap_results--{{ pair.key }}">{{ pair.value }}</label>
</div>
{% endfor %}
<span class="usa-error-message margin-top-2">{{ form.errors.gaap_results|striptags }}</span>
Expand All @@ -46,13 +46,13 @@ <h1>Financial statements</h1>
<legend>What was the special purpose framework? Select only one.</legend>
{% for pair in sp_framework_basis %}
<div class="usa-checkbox">
<input id="sp_framework_basis--{{ pair.tag }}"
<input id="sp_framework_basis--{{ pair.key }}"
name="sp_framework_basis"
class="usa-radio__input"
type="radio"
value="{{ pair.tag }}"
{% if pair.tag in form.cleaned_data.sp_framework_basis %}checked{% endif %} />
<label class="usa-radio__label" for="sp_framework_basis--{{ pair.tag }}">{{ pair.readable }}</label>
value="{{ pair.key }}"
{% if pair.key in form.cleaned_data.sp_framework_basis %}checked{% endif %} />
<label class="usa-radio__label" for="sp_framework_basis--{{ pair.key }}">{{ pair.value }}</label>
</div>
{% endfor %}
<span class="usa-error-message margin-top-2">{{ form.errors.gaap_results|striptags }}</span>
Expand Down Expand Up @@ -88,13 +88,13 @@ <h1>Financial statements</h1>
<p class="text-base">Select any of the following that apply.</p>
{% for pair in sp_framework_opinions %}
<div class="usa-checkbox">
<input id="sp_framework_opinions--{{ pair.tag }}"
<input id="sp_framework_opinions--{{ pair.key }}"
name="sp_framework_opinions"
class="usa-checkbox__input"
type="checkbox"
value="{{ pair.tag }}"
{% if pair.tag in form.cleaned_data.sp_framework_opinions %}checked{% endif %} />
<label class="usa-checkbox__label" for="sp_framework_opinions--{{ pair.tag }}">{{ pair.readable }}</label>
value="{{ pair.key }}"
{% if pair.key in form.cleaned_data.sp_framework_opinions %}checked{% endif %} />
<label class="usa-checkbox__label" for="sp_framework_opinions--{{ pair.key }}">{{ pair.value }}</label>
</div>
{% endfor %}
<span class="usa-error-message margin-top-2">{{ form.errors.gaap_results|striptags }}</span>
Expand Down
2 changes: 1 addition & 1 deletion backend/audit/templates/audit/my_submissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ <h1 class="font-sans-xl">Audits in progress</h1>
<tr>
<td>
<a class="usa-link" href="{% url 'audit:SubmissionProgress' item.report_id %}">{{ item.submission_status }}</a>
{% if item.submission_status == "Ready for Certification" %}
{% if item.submission_status in "Ready for Certification,Auditor Certified,Auditee Certified" %}
<p class="margin-0">
<a class="usa-link" href="{% url 'audit:UnlockAfterCertification' item.report_id %}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
Expand Down
6 changes: 3 additions & 3 deletions backend/audit/templates/audit/unlock-after-certification.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
{% block content %}
<div class="grid-container margin-top-6">
<div class="grid-row">
{% if submission_status == target_status %}
{% if submission_status in target_statuses %}
<form class="grid-col-8 grid-offset-2"
id="unlock-after-certification"
method="post">
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-legend usa-legend--large" id="auditor-certification">Unlock submission</legend>
<legend class="usa-legend usa-legend--large" id="unlock-after-certification">Unlock submission</legend>
<h3>
{% comment %} &#8209; is a non-breaking hyphen, to avoid splitting "re-validated" across two lines. {% endcomment %}
I understand that by unlocking this submission it will need to be re&#8209;validated before certification.
Expand All @@ -34,7 +34,7 @@ <h3>
</div>
</form>
{% else %}
<p class="margin-bottom-6">This form is only accessible for submissions with a “Ready for Certification” status. This submission is not locked for certification.</p>
<p class="margin-bottom-6">This form is only accessible for submissions that have been locked for certification. This submission is not locked for certification.</p>
{% endif %}
</div>
</div>
Expand Down
22 changes: 22 additions & 0 deletions backend/audit/test_intake_to_dissemination.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def _create_sac(
additional_eins=self._fake_additional_eins(),
audit_information=self._fake_audit_information(),
auditee_certification=self._fake_auditee_certification(),
auditor_certification=self._fake_auditor_certification(),
tribal_data_consent=self._fake_tribal_data_consent(privacy_flag),
cognizant_agency=cognizant_agency,
oversight_agency=oversight_agency,
Expand Down Expand Up @@ -185,6 +186,27 @@ def _fake_auditee_certification():
},
}

@staticmethod
def _fake_auditor_certification():
fake = Faker()
return {
"auditor_certification": {
"has_no_PII": fake.boolean(),
"has_no_BII": fake.boolean(),
"meets_2CFR_specifications": fake.boolean(),
"is_2CFR_compliant": fake.boolean(),
"is_complete_and_accurate": fake.boolean(),
"has_engaged_auditor": fake.boolean(),
"is_issued_and_signed": fake.boolean(),
"is_FAC_releasable": fake.boolean(),
},
"auditor_signature": {
"auditor_name": fake.name(),
"auditor_title": fake.job(),
"auditor_certification_date_signed": fake.date(),
},
}

@staticmethod
def _fake_federal_awards():
return {
Expand Down
2 changes: 2 additions & 0 deletions backend/audit/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def test_submission_status_transitions(self):
(
[
SingleAuditChecklist.STATUS.READY_FOR_CERTIFICATION,
SingleAuditChecklist.STATUS.AUDITOR_CERTIFIED,
SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED,
],
SingleAuditChecklist.STATUS.IN_PROGRESS,
"transition_to_in_progress_again",
Expand Down
Loading

0 comments on commit 95f3fff

Please sign in to comment.