diff --git a/backend/audit/etl.py b/backend/audit/etl.py index 2a9e55f898..67fb02f87f 100644 --- a/backend/audit/etl.py +++ b/backend/audit/etl.py @@ -35,7 +35,7 @@ def load_all(self): self.load_passthrough, self.load_finding_texts, self.load_captext, - # self.load_audit_info() # TODO: Uncomment when SingleAuditChecklist adds audit_information + self.load_audit_info, ) for load_method in load_methods: try: @@ -47,6 +47,11 @@ def load_all(self): def load_finding_texts(self): findings_text = self.single_audit_checklist.findings_text + + if not findings_text: + logger.warning("No finding texts found to load") + return + findings_text_entries = findings_text["FindingsText"]["findings_text_entries"] for entry in findings_text_entries: finding_text_ = FindingText( @@ -61,6 +66,10 @@ def load_findings(self): findings_uniform_guidance = ( self.single_audit_checklist.findings_uniform_guidance ) + if not findings_uniform_guidance: + logger.warning("No findings found to load") + return + findings_uniform_guidance_entries = findings_uniform_guidance[ "FindingsUniformGuidance" ]["findings_uniform_guidance_entries"] @@ -135,6 +144,10 @@ def load_federal_award(self): def load_captext(self): corrective_action_plan = self.single_audit_checklist.corrective_action_plan + if not corrective_action_plan: + logger.warning("No corrective action plans found to load") + return + corrective_action_plan_entries = corrective_action_plan["CorrectiveActionPlan"][ "corrective_action_plan_entries" ] @@ -149,6 +162,10 @@ def load_captext(self): def load_note(self): notes_to_sefa = self.single_audit_checklist.notes_to_sefa["NotesToSefa"] + if not notes_to_sefa: + logger.warning("No notes to sefa found to load") + return + accounting_policies = notes_to_sefa["accounting_policies"] is_minimis_rate_used = notes_to_sefa["is_minimis_rate_used"] == "Y" rate_explained = notes_to_sefa["rate_explained"] @@ -201,6 +218,13 @@ def load_revision(self): def load_passthrough(self): federal_awards = self.single_audit_checklist.federal_awards for entry in federal_awards["FederalAwards"]["federal_awards"]: + entities = ( + entry["direct_or_indirect_award"] + and entry["direct_or_indirect_award"]["entities"] + ) + if not entities: + logger.warning("No passthrough to load") + return for entity in entry["direct_or_indirect_award"]["entities"]: passthrough = Passthrough( award_reference=entry["award_reference"], @@ -294,6 +318,9 @@ def load_general(self): def load_secondary_auditor(self): secondary_auditors = self.single_audit_checklist.secondary_auditors + if not secondary_auditors: + logger.warning("No secondary_auditors found to load") + return for secondary_auditor in secondary_auditors["SecondaryAuditors"][ "secondary_auditors_entries" @@ -315,17 +342,26 @@ def load_secondary_auditor(self): sec_auditor.save() def load_audit_info(self): - general = General.objects.get(report_id=self.single_audit_checklist.report_id) + 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 AuditInfo. report_id = {report_id}" + ) + return audit_information = self.single_audit_checklist.audit_information - + if not audit_information: + logger.warning("No audit info found to load") + return general.gaap_results = audit_information["gaap_results"] - """ - TODO: - Missing in schema - general.sp_framework = audit_information[] - general.is_sp_framework_required = audit_information[] - general.sp_framework_auditor_opinion = audit_information[] - """ + general.sp_framework = audit_information["sp_framework_basis"] + general.is_sp_framework_required = ( + audit_information["is_sp_framework_required"] == "Y" + ) + general.sp_framework_auditor_opinion = audit_information[ + "sp_framework_opinions" + ] general.is_going_concern = audit_information["is_going_concern_included"] == "Y" general.is_significant_deficiency = ( audit_information["is_internal_control_deficiency_disclosed"] == "Y" diff --git a/backend/audit/forms.py b/backend/audit/forms.py index 87ffd6f384..0232d4c743 100644 --- a/backend/audit/forms.py +++ b/backend/audit/forms.py @@ -40,9 +40,57 @@ def clean_booleans(self): "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_agencies = list((i, i) for i in AGENCY_NAMES) gaap_results = forms.MultipleChoiceField(choices=choices_GAAP) + sp_framework_basis = forms.MultipleChoiceField( + choices=choices_SP_FRAMEWORK_BASIS, + required=False, + ) + is_sp_framework_required = forms.MultipleChoiceField( + choices=choices_YoN, + ) + sp_framework_opinions = forms.MultipleChoiceField( + choices=choices_SP_FRAMEWORK_OPINIONS, + required=False, + ) is_going_concern_included = forms.MultipleChoiceField(choices=choices_YoN) is_internal_control_deficiency_disclosed = forms.MultipleChoiceField( choices=choices_YoN diff --git a/backend/audit/get_agency_names.py b/backend/audit/get_agency_names.py index 2b8fce61fc..741eae8482 100644 --- a/backend/audit/get_agency_names.py +++ b/backend/audit/get_agency_names.py @@ -31,9 +31,9 @@ def get_agency_names(): return agency_names -def get_gaap_results(): +def get_audit_info_lists(name): sonnet = "./schemas/source/base/GAAP.libsonnet" json_str = _jsonnet.evaluate_file(sonnet) jobj = json.loads(json_str) # Returns a list of dictionaries with the keys 'tag' and 'readable' - return jobj["gaap_results"] + return jobj[name] diff --git a/backend/audit/templates/audit/audit-info-form.html b/backend/audit/templates/audit/audit-info-form.html index c69be2af21..ee9abbd56e 100644 --- a/backend/audit/templates/audit/audit-info-form.html +++ b/backend/audit/templates/audit/audit-info-form.html @@ -36,7 +36,7 @@

Audit information

Financial statements

- What were the results of the auditor’s determination of whether the financial statements of the auditee were prepared in accordance with generally accepted accounting principles (GAAP)? * + a. What were the results of the auditor’s determination of whether the financial statements of the auditee were prepared in accordance with generally accepted accounting principles (GAAP)? *

Select any of the following that apply.

{% for pair in gaap_results %} @@ -53,9 +53,74 @@

Financial statements

{% endfor %} {{ form.errors.gaap_results|striptags }}
+
+
+ + If the financial statements of the auditee were prepared in accordance with GAAP, proceed to question b. + +

i. What was the special purpose framework? Select only one.

+ {% for pair in sp_framework_basis %} +
+ + +
+ {% endfor %} + {{ form.errors.gaap_results|striptags }} +
+
+ + ii. Was the special purpose framework used as a basis of accounting required by state law? + +
+ + +
+
+ + +
+ {{ form.errors.is_going_concern_included|striptags }} +
+
+ + iii. What was the auditor’s opinion on the special purpose framework? * + +

Select any of the following that apply.

+ {% for pair in sp_framework_opinions %} +
+ + +
+ {% endfor %} + {{ form.errors.gaap_results|striptags }} +
+
- Is a "going concern" emphasis-of-matter paragraph included in the audit report? * + b. Is a "going concern" emphasis-of-matter paragraph included in the audit report? *
Financial statements
- Is a significant deficiency in internal control disclosed? * + c. Is a significant deficiency in internal control disclosed? *
Financial statements
- Is a material weakness in internal control disclosed? * + d. Is a material weakness in internal control disclosed? *
Financial statements
- Is a material noncompliance disclosed? * + e. Is a material noncompliance disclosed? *
tag, which is shared between choices. +HTML5 doesn't provide a native way to set multiple choice options as required, without setting ALL of them as required. +This solution sets them all as required, so a user is notified if they try to submit without selecting an answer. +If they select an answer, the options are set to no longer be required. +This function attaches handles by the name of the tag, which is shared between choices. */ function setCheckboxRequired(name) { let checkboxes = document.getElementsByName(name); - // First make sure the required tag is set properly on all elements - for (var i = 0; i < checkboxes.length; i++) { + for (let i = 0; i < checkboxes.length; i++) { let elements = document.getElementsByName(name); - var someChecked = false; - elements.forEach( - (element) => (someChecked = someChecked || element.checked) - ); + let someChecked = false; + elements.forEach((element) => { + someChecked = someChecked || element.checked; + if (element.id === not_gaap_id) toggle_sp_div(); + }); elements.forEach((element) => (element.required = !someChecked)); } // Attach the event handler that will change the required tag when a user selects (or unselects) an option - for (var j = 0; j < checkboxes.length; j++) { + for (let j = 0; j < checkboxes.length; j++) { checkboxes[j].addEventListener('input', () => { let elements = document.getElementsByName(name); - var someChecked = false; - elements.forEach( - (element) => (someChecked = someChecked || element.checked) - ); + let someChecked = false; + elements.forEach((element) => { + someChecked = someChecked || element.checked; + if (element.id === not_gaap_id) toggle_sp_div(); + }); elements.forEach((element) => (element.required = !someChecked)); }); } } +function attachEventHandlers() { + const CONTINUE_BUTTON = document.querySelector('#continue'); + CONTINUE_BUTTON.addEventListener('click', () => { + /* Uncheck sp_ fields if they are not applicable */ + sp_framework_div.removeAttribute('hidden'); + const not_gaap_cb = document.querySelector(`#${not_gaap_id}`); + if (!not_gaap_cb.checked) { + const basis = document.querySelectorAll( + 'input[name="sp_framework_basis"]' + ); + basis.forEach((basi) => { + basi.required = false; + basi.checked = false; + }); + const opinions = document.querySelectorAll( + 'input[name="sp_framework_opinions"]' + ); + opinions.forEach((opinion) => { + opinion.required = false; + opinion.checked = false; + }); + } + return; + }); +} + function init() { setCheckboxRequired('gaap_results'); + setCheckboxRequired('sp_framework_basis'); + setCheckboxRequired('is_sp_framework_required'); + setCheckboxRequired('sp_framework_opinions'); setCheckboxRequired('is_going_concern_included'); setCheckboxRequired('is_internal_control_deficiency_disclosed'); setCheckboxRequired('is_internal_control_material_weakness_disclosed'); setCheckboxRequired('is_material_noncompliance_disclosed'); setCheckboxRequired('is_aicpa_audit_guide_included'); setCheckboxRequired('is_low_risk_auditee'); + attachEventHandlers(); +} + +/* show or hide sp_f fields based on no_gaap being checked */ +function toggle_sp_div() { + const not_gaap_cb = document.querySelector(`#${not_gaap_id}`); + if (not_gaap_cb.checked) { + sp_framework_div.removeAttribute('hidden'); + } else { + sp_framework_div.setAttribute('hidden', true); + } } -init(); +window.onload = init;