From 8f54a9cf40d2e6d464ed2ff50743717b81f4906f Mon Sep 17 00:00:00 2001 From: Tim Ballard <1425377+timoballard@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:10:47 -0600 Subject: [PATCH 1/5] first pass (#3136) --- backend/audit/models/models.py | 19 ++++++++++++++----- .../audit/templates/audit/my_submissions.html | 2 +- .../audit/unlock-after-certification.html | 6 +++--- backend/audit/test_models.py | 2 ++ .../audit/views/unlock_after_certification.py | 8 ++++++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/backend/audit/models/models.py b/backend/audit/models/models.py index afa40f92aa..a48935752d 100644 --- a/backend/audit/models/models.py +++ b/backend/audit/models/models.py @@ -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)) @@ -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)) @@ -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)) @@ -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) diff --git a/backend/audit/templates/audit/my_submissions.html b/backend/audit/templates/audit/my_submissions.html index 6bc3f86605..23424494cb 100644 --- a/backend/audit/templates/audit/my_submissions.html +++ b/backend/audit/templates/audit/my_submissions.html @@ -34,7 +34,7 @@

Audits in progress

{{ item.submission_status }} - {% if item.submission_status == "Ready for Certification" %} + {% if item.submission_status in "Ready for Certification,Auditor Certified,Auditee Certified" %}

- {% if submission_status == target_status %} + {% if submission_status in target_statuses %}
{% csrf_token %}
- Unlock submission + Unlock submission

{% comment %} ‑ 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‑validated before certification. @@ -34,7 +34,7 @@

{% else %} -

This form is only accessible for submissions with a “Ready for Certification” status. This submission is not locked for certification.

+

This form is only accessible for submissions that have been locked for certification. This submission is not locked for certification.

{% endif %}
diff --git a/backend/audit/test_models.py b/backend/audit/test_models.py index afca1b9b79..f662ebe112 100644 --- a/backend/audit/test_models.py +++ b/backend/audit/test_models.py @@ -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", diff --git a/backend/audit/views/unlock_after_certification.py b/backend/audit/views/unlock_after_certification.py index d2971685a2..319b3107e8 100644 --- a/backend/audit/views/unlock_after_certification.py +++ b/backend/audit/views/unlock_after_certification.py @@ -29,13 +29,17 @@ def get(self, request, *args, **kwargs): try: sac = SingleAuditChecklist.objects.get(report_id=report_id) - target_status = SingleAuditChecklist.STATUS.READY_FOR_CERTIFICATION + target_statuses = [ + SingleAuditChecklist.STATUS.READY_FOR_CERTIFICATION, + SingleAuditChecklist.STATUS.AUDITOR_CERTIFIED, + SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED, + ] context = { "auditee_uei": sac.auditee_uei, "auditee_name": sac.auditee_name, "report_id": report_id, "submission_status": sac.submission_status, - "target_status": target_status, + "target_statuses": target_statuses, } return render( From 0c76af746ef311c1eb0ecb234922f36aa148f1e2 Mon Sep 17 00:00:00 2001 From: Tim Ballard <1425377+timoballard@users.noreply.github.com> Date: Fri, 5 Jan 2024 13:27:38 -0600 Subject: [PATCH 2/5] BUG: Pre-submission validation should remain checked after certifications (#3144) * first pass * lint --- .../audit/views/submission_progress_view.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/audit/views/submission_progress_view.py b/backend/audit/views/submission_progress_view.py index 2e12613a39..c05c24684e 100644 --- a/backend/audit/views/submission_progress_view.py +++ b/backend/audit/views/submission_progress_view.py @@ -137,7 +137,12 @@ def get(self, request, *args, **kwargs): "completed_by": None, }, "pre_submission_validation": { - "completed": sac.submission_status == "ready_for_certification", + "completed": sac.submission_status + in [ + SingleAuditChecklist.STATUS.READY_FOR_CERTIFICATION, + SingleAuditChecklist.STATUS.AUDITOR_CERTIFIED, + SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED, + ], "completed_date": None, "completed_by": None, # We want the user to always be able to run this check: @@ -146,15 +151,18 @@ def get(self, request, *args, **kwargs): "certification": { "auditor_certified": bool(sac.auditor_certification), "auditor_enabled": sac.submission_status - == "ready_for_certification", + == SingleAuditChecklist.STATUS.READY_FOR_CERTIFICATION, "auditee_certified": bool(sac.auditee_certification), - "auditee_enabled": sac.submission_status == "auditor_certified", + "auditee_enabled": sac.submission_status + == SingleAuditChecklist.STATUS.AUDITOR_CERTIFIED, }, "submission": { - "completed": sac.submission_status == "submitted", + "completed": sac.submission_status + == SingleAuditChecklist.STATUS.SUBMITTED, "completed_date": None, "completed_by": None, - "enabled": sac.submission_status == "auditee_certified", + "enabled": sac.submission_status + == SingleAuditChecklist.STATUS.AUDITEE_CERTIFIED, }, "report_id": report_id, "auditee_name": sac.auditee_name, From a5bd210d6b4eed8829aac0c69a5fbe9727325832 Mon Sep 17 00:00:00 2001 From: Tadhg O'Higgins <2626258+tadhg-ohiggins@users.noreply.github.com> Date: Fri, 5 Jan 2024 13:48:44 -0800 Subject: [PATCH 3/5] Add missing auditor certification fields to dissemination (#3085) * Add auditor certification fields to intake. * Properly handle Django two-step of adding default value, then removing it. --- backend/audit/intake_to_dissemination.py | 15 ++++++++-- backend/audit/test_intake_to_dissemination.py | 22 ++++++++++++++ backend/dissemination/docs.py | 2 ++ ...7_general_auditor_certify_name_and_more.py | 30 +++++++++++++++++++ ...r_general_auditor_certify_name_and_more.py | 28 +++++++++++++++++ backend/dissemination/models.py | 8 +++++ 6 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py create mode 100644 backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py diff --git a/backend/audit/intake_to_dissemination.py b/backend/audit/intake_to_dissemination.py index 51ce528e85..070d486e92 100644 --- a/backend/audit/intake_to_dissemination.py +++ b/backend/audit/intake_to_dissemination.py @@ -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 @@ -295,6 +294,12 @@ 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: @@ -302,8 +307,10 @@ def load_general(self): 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" @@ -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, diff --git a/backend/audit/test_intake_to_dissemination.py b/backend/audit/test_intake_to_dissemination.py index 32925faa3b..a7b58d280e 100644 --- a/backend/audit/test_intake_to_dissemination.py +++ b/backend/audit/test_intake_to_dissemination.py @@ -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, @@ -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 { diff --git a/backend/dissemination/docs.py b/backend/dissemination/docs.py index ebee925ad7..c32c28c987 100644 --- a/backend/dissemination/docs.py +++ b/backend/dissemination/docs.py @@ -152,6 +152,8 @@ significant_deficiency = "Data sources: SF-SAC 2013-2015: III/7/i; SF-SAC 2016-2018: III/4/j; SF-SAC 2019-2021: III/4/j; SF-SAC 2022: III/4/j Census mapping: FINDINGS, SIGNIFICANTDEFICIENCY" auditee_certify_name = "Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: GENERAL, AUDITEECERTIFYNAME" auditee_certify_title = "Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: GENERAL, AUDITEECERTIFYTITLE" +auditor_certify_name = "Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: UNKNOWN" +auditor_certify_title = "Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: UNKNOWN" auditee_contact = "Data sources: SF-SAC 1997-2000: I/6/c; SF-SAC 2001-2003: I/6/c; SF-SAC 2004-2007: I/6/c; SF-SAC 2008-2009: I/5/c; SF-SAC 2010-2012: I/5/c; SF-SAC 2013-2015: I/5/c; SF-SAC 2016-2018: I/5/c; SF-SAC 2019-2021: I/5/c; SF-SAC 2022: I/5/c Census mapping: GENERAL, AUDITEECONTACT" auditee_date_signed = "Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: GENERAL, AUDITEEDATESIGNED" auditee_email = "Data sources: SF-SAC 1997-2000: I/6/f; SF-SAC 2001-2003: I/6/f; SF-SAC 2004-2007: I/6/f; SF-SAC 2008-2009: I/5/f; SF-SAC 2010-2012: I/5/f; SF-SAC 2013-2015: I/5/f; SF-SAC 2016-2018: I/5/e; SF-SAC 2019-2021: I/5/e; SF-SAC 2022: I/5/e Census mapping: GENERAL, AUDITEEEMAIL" diff --git a/backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py b/backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py new file mode 100644 index 0000000000..b202bb1610 --- /dev/null +++ b/backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.6 on 2023-12-26 19:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("dissemination", "0006_migrationchangerecord"), + ] + + operations = [ + migrations.AddField( + model_name="general", + name="auditor_certify_name", + field=models.TextField( + default="DEFAULT_AUDITOR_CERTIFY_NAME", + help_text="Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: UNKNOWN", + verbose_name="Name of Auditor Certifying Official", + ), + ), + migrations.AddField( + model_name="general", + name="auditor_certify_title", + field=models.TextField( + default="DEFAULT_AUDITOR_CERTIFY_TITLE", + help_text="Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: UNKNOWN", + verbose_name="Title of Auditor Certifying Official", + ), + ), + ] diff --git a/backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py b/backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py new file mode 100644 index 0000000000..77da7506f4 --- /dev/null +++ b/backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.6 on 2023-12-26 20:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("dissemination", "0007_general_auditor_certify_name_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="general", + name="auditor_certify_name", + field=models.TextField( + help_text="Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: UNKNOWN", + verbose_name="Name of Auditor Certifying Official", + ), + ), + migrations.AlterField( + model_name="general", + name="auditor_certify_title", + field=models.TextField( + help_text="Data sources: SF-SAC 1997-2000: I/6/g; SF-SAC 2001-2003: I/6/g; SF-SAC 2004-2007: I/6/g; SF-SAC 2008-2009: I/5/g; SF-SAC 2010-2012: I/5/g; SF-SAC 2013-2015: certifications; SF-SAC 2016-2018: certifications; SF-SAC 2019-2021: certifications; SF-SAC 2022: certifications Census mapping: UNKNOWN", + verbose_name="Title of Auditor Certifying Official", + ), + ), + ] diff --git a/backend/dissemination/models.py b/backend/dissemination/models.py index ad603b80be..8469752aa9 100644 --- a/backend/dissemination/models.py +++ b/backend/dissemination/models.py @@ -248,6 +248,14 @@ class General(models.Model): "Title of Auditee Certifying Official", help_text=docs.auditee_certify_title, ) + auditor_certify_name = models.TextField( + "Name of Auditor Certifying Official", + help_text=docs.auditor_certify_name, + ) + auditor_certify_title = models.TextField( + "Title of Auditor Certifying Official", + help_text=docs.auditor_certify_title, + ) auditee_contact_name = models.TextField( "Name of Auditee Contact", help_text=docs.auditee_contact, From 42477595ed30d59eef9fe67afc5e88689b74c299 Mon Sep 17 00:00:00 2001 From: Tadhg O'Higgins <2626258+tadhg-ohiggins@users.noreply.github.com> Date: Fri, 5 Jan 2024 13:58:19 -0800 Subject: [PATCH 4/5] Do not populate Audit Information form with historical-only values (#3148) * WIP checkin. * Filter out historical_only=True values in audit.get_agency_names.get_audit_info_lists. --- backend/audit/forms.py | 62 ++++----------- backend/audit/get_agency_names.py | 57 ++++++++----- .../templates/audit/audit-info-form.html | 24 +++--- .../output/audit/audit-info-values.json | 60 -------------- .../source/audit/audit-info-values.json | 79 +++++++++++++++++++ .../source/audit/audit-info-values.jsonnet | 3 - backend/schemas/source/base/Base.libsonnet | 6 +- backend/schemas/source/base/GAAP.libsonnet | 72 ++--------------- 8 files changed, 152 insertions(+), 211 deletions(-) delete mode 100644 backend/schemas/output/audit/audit-info-values.json create mode 100644 backend/schemas/source/audit/audit-info-values.json delete mode 100644 backend/schemas/source/audit/audit-info-values.jsonnet diff --git a/backend/audit/forms.py b/backend/audit/forms.py index e558b6e8f9..31cadb46a0 100644 --- a/backend/audit/forms.py +++ b/backend/audit/forms.py @@ -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): @@ -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 @@ -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) diff --git a/backend/audit/get_agency_names.py b/backend/audit/get_agency_names.py index 55f236d96c..e1f20e52ae 100644 --- a/backend/audit/get_agency_names.py +++ b/backend/audit/get_agency_names.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from pathlib import Path import csv import glob @@ -6,37 +5,57 @@ 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] diff --git a/backend/audit/templates/audit/audit-info-form.html b/backend/audit/templates/audit/audit-info-form.html index cde3614af5..6883b38d86 100644 --- a/backend/audit/templates/audit/audit-info-form.html +++ b/backend/audit/templates/audit/audit-info-form.html @@ -29,14 +29,14 @@

Financial statements

Select any of the following that apply.

{% for pair in gaap_results %}
- - + value="{{ pair.key }}" + {% if pair.key in form.cleaned_data.gaap_results %}checked{% endif %} /> +
{% endfor %} {{ form.errors.gaap_results|striptags }} @@ -46,13 +46,13 @@

Financial statements

What was the special purpose framework? Select only one. {% for pair in sp_framework_basis %}
- - + value="{{ pair.key }}" + {% if pair.key in form.cleaned_data.sp_framework_basis %}checked{% endif %} /> +
{% endfor %} {{ form.errors.gaap_results|striptags }} @@ -88,13 +88,13 @@

Financial statements

Select any of the following that apply.

{% for pair in sp_framework_opinions %}
- - + value="{{ pair.key }}" + {% if pair.key in form.cleaned_data.sp_framework_opinions %}checked{% endif %} /> +
{% endfor %} {{ form.errors.gaap_results|striptags }} diff --git a/backend/schemas/output/audit/audit-info-values.json b/backend/schemas/output/audit/audit-info-values.json deleted file mode 100644 index 968dc75559..0000000000 --- a/backend/schemas/output/audit/audit-info-values.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "gaap_results": [ - { - "readable": "Unmodified opinion", - "tag": "unmodified_opinion" - }, - { - "readable": "Qualified opinion", - "tag": "qualified_opinion" - }, - { - "readable": "Adverse opinion", - "tag": "adverse_opinion" - }, - { - "readable": "Disclaimer of opinion", - "tag": "disclaimer_of_opinion" - }, - { - "readable": "Financial statements were not prepared in accordance with GAAP but were prepared in accordance with a special purpose framework.", - "tag": "not_gaap" - } - ], - "sp_framework_basis": [ - { - "readable": "Cash basis", - "tag": "cash_basis" - }, - { - "readable": "Tax basis", - "tag": "tax_basis" - }, - { - "readable": "Contractual basis", - "tag": "contractual_basis" - }, - { - "readable": "Other basis", - "tag": "other_basis" - } - ], - "sp_framework_opinions": [ - { - "readable": "Unmodified opinion", - "tag": "unmodified_opinion" - }, - { - "readable": "Qualified opinion", - "tag": "qualified_opinion" - }, - { - "readable": "Adverse opinion", - "tag": "adverse_opinion" - }, - { - "readable": "Disclaimer of opinion", - "tag": "disclaimer_of_opinion" - } - ] -} diff --git a/backend/schemas/source/audit/audit-info-values.json b/backend/schemas/source/audit/audit-info-values.json new file mode 100644 index 0000000000..f19a7c6214 --- /dev/null +++ b/backend/schemas/source/audit/audit-info-values.json @@ -0,0 +1,79 @@ +{ + "gaap_results": [ + { + "value": "Unmodified opinion", + "key": "unmodified_opinion", + "property": "UNMODIFIED_OPINION" + }, + { + "value": "Qualified opinion", + "key": "qualified_opinion", + "property": "QUALIFIED_OPINION" + }, + { + "value": "Adverse opinion", + "key": "adverse_opinion", + "property": "ADVERSE_OPINION" + }, + { + "value": "Disclaimer of opinion", + "key": "disclaimer_of_opinion", + "property": "DISCLAIMER_OF_OPINION" + }, + { + "value": "Financial statements were not prepared in accordance with GAAP but were prepared in accordance with a special purpose framework.", + "key": "not_gaap", + "property": "NOT_GAAP" + } + ], + "sp_framework_basis": [ + { + "value": "Cash basis", + "key": "cash_basis", + "property": "CASH_BASIS" + }, + { + "value": "Tax basis", + "key": "tax_basis", + "property": "TAX_BASIS" + }, + { + "value": "Contractual basis", + "key": "contractual_basis", + "property": "CONTRACTUAL_BASIS" + }, + { + "value": "Regulatory basis", + "key": "regulatory_basis", + "property": "REGULATORY_BASIS", + "historical_only": true + }, + { + "value": "Other basis", + "key": "other_basis", + "property": "OTHER_BASIS" + } + ], + "sp_framework_opinions": [ + { + "value": "Unmodified opinion", + "key": "unmodified_opinion", + "property": "UNMODIFIED_OPINION" + }, + { + "value": "Qualified opinion", + "key": "qualified_opinion", + "property": "QUALIFIED_OPINION" + }, + { + "value": "Adverse opinion", + "key": "adverse_opinion", + "property": "ADVERSE_OPINION" + }, + { + "value": "Disclaimer of opinion", + "key": "disclaimer_of_opinion", + "property": "DISCLAIMER_OF_OPINION" + } + ] +} diff --git a/backend/schemas/source/audit/audit-info-values.jsonnet b/backend/schemas/source/audit/audit-info-values.jsonnet deleted file mode 100644 index 903acbb27b..0000000000 --- a/backend/schemas/source/audit/audit-info-values.jsonnet +++ /dev/null @@ -1,3 +0,0 @@ -local GAAP = import '../base/GAAP.libsonnet'; - -GAAP diff --git a/backend/schemas/source/base/Base.libsonnet b/backend/schemas/source/base/Base.libsonnet index eb7709ba34..27a1168142 100644 --- a/backend/schemas/source/base/Base.libsonnet +++ b/backend/schemas/source/base/Base.libsonnet @@ -222,15 +222,15 @@ local Enum = { }, GAAPResults: Types.string { description: 'GAAP Results (Audit Information)', - enum: std.map(function(pair) pair.tag, GAAP.gaap_results), + enum: std.map(function(pair) pair.key, GAAP.gaap_results), }, SP_Framework_Basis: Types.string { description: 'SP Framework Basis (Audit Information)', - enum: std.map(function(pair) pair.tag, GAAP.sp_framework_basis), + enum: std.map(function(pair) pair.key, GAAP.sp_framework_basis), }, SP_Framework_Opinions: Types.string { description: 'SP Framework Opinions (Audit Information)', - enum: std.map(function(pair) pair.tag, GAAP.sp_framework_opinions), + enum: std.map(function(pair) pair.key, GAAP.sp_framework_opinions), }, UnitedStatesStateAbbr: { description: 'US States 2-letter abbreviations', diff --git a/backend/schemas/source/base/GAAP.libsonnet b/backend/schemas/source/base/GAAP.libsonnet index 2b0232e20f..811baada3f 100644 --- a/backend/schemas/source/base/GAAP.libsonnet +++ b/backend/schemas/source/base/GAAP.libsonnet @@ -1,67 +1,7 @@ -local gaap_results = [ - { - readable: 'Unmodified opinion', - tag: 'unmodified_opinion', - }, - { - readable: 'Qualified opinion', - tag: 'qualified_opinion', - }, - { - readable: 'Adverse opinion', - tag: 'adverse_opinion', - }, - { - readable: 'Disclaimer of opinion', - tag: 'disclaimer_of_opinion', - }, - { - readable: 'Financial statements were not prepared in accordance with GAAP but were prepared in accordance with a special purpose framework.', - tag: 'not_gaap', - }, -]; -local sp_framework_basis = [ - { - readable: 'Cash basis', - tag: 'cash_basis', - }, - { - readable: 'Tax basis', - tag: 'tax_basis', - }, - { - readable: 'Contractual basis', - tag: 'contractual_basis', - }, - { - readable: 'Regulatory basis', - tag: 'regulatory_basis', - }, - { - readable: 'Other basis', - tag: 'other_basis', - }, -]; -local sp_framework_opinions = [ - { - readable: 'Unmodified opinion', - tag: 'unmodified_opinion', - }, - { - readable: 'Qualified opinion', - tag: 'qualified_opinion', - }, - { - readable: 'Adverse opinion', - tag: 'adverse_opinion', - }, - { - readable: 'Disclaimer of opinion', - tag: 'disclaimer_of_opinion', - }, -]; +local audit_info_data = import '../audit/audit-info-values.json'; -{ gaap_results: gaap_results, - sp_framework_basis: sp_framework_basis, - sp_framework_opinions: sp_framework_opinions, -} \ No newline at end of file +{ + gaap_results: audit_info_data.gaap_results, + sp_framework_basis: audit_info_data.sp_framework_basis, + sp_framework_opinions: audit_info_data.sp_framework_opinions, +} From a2c41b3096cb3dcac101efc5edda4c75ae62a616 Mon Sep 17 00:00:00 2001 From: Tadhg O'Higgins <2626258+tadhg-ohiggins@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:36:51 -0800 Subject: [PATCH 5/5] Fix dissemination migration chain. (#3164) --- ...nd_more.py => 0008_general_auditor_certify_name_and_more.py} | 2 +- ...e.py => 0009_alter_general_auditor_certify_name_and_more.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename backend/dissemination/migrations/{0007_general_auditor_certify_name_and_more.py => 0008_general_auditor_certify_name_and_more.py} (94%) rename backend/dissemination/migrations/{0008_alter_general_auditor_certify_name_and_more.py => 0009_alter_general_auditor_certify_name_and_more.py} (95%) diff --git a/backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py b/backend/dissemination/migrations/0008_general_auditor_certify_name_and_more.py similarity index 94% rename from backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py rename to backend/dissemination/migrations/0008_general_auditor_certify_name_and_more.py index b202bb1610..ffbbd67ecd 100644 --- a/backend/dissemination/migrations/0007_general_auditor_certify_name_and_more.py +++ b/backend/dissemination/migrations/0008_general_auditor_certify_name_and_more.py @@ -5,7 +5,7 @@ class Migration(migrations.Migration): dependencies = [ - ("dissemination", "0006_migrationchangerecord"), + ("dissemination", "0007_remove_migrationchangerecord_census_data_and_more"), ] operations = [ diff --git a/backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py b/backend/dissemination/migrations/0009_alter_general_auditor_certify_name_and_more.py similarity index 95% rename from backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py rename to backend/dissemination/migrations/0009_alter_general_auditor_certify_name_and_more.py index 77da7506f4..89e2e34b2c 100644 --- a/backend/dissemination/migrations/0008_alter_general_auditor_certify_name_and_more.py +++ b/backend/dissemination/migrations/0009_alter_general_auditor_certify_name_and_more.py @@ -5,7 +5,7 @@ class Migration(migrations.Migration): dependencies = [ - ("dissemination", "0007_general_auditor_certify_name_and_more"), + ("dissemination", "0008_general_auditor_certify_name_and_more"), ] operations = [