From ea18e1733c5426315f8d026dc8155cfacec375f0 Mon Sep 17 00:00:00 2001 From: "Hassan D. M. Sambo" Date: Fri, 29 Mar 2024 06:10:07 -0400 Subject: [PATCH] 2819 consolidate general information schema validation (#3473) * #2819 Initial attempt to consolidate GeneralInformation schemas in order to remove the unnecessary one * #2819 Initial step in translating GeneralInformation schema into python checks * #2819 Removed extra general information schema and consolidated existing one * #2819 Updated test cases and fixtures to meet changes in schema * #2819 Updated code to only use one schema for general information * #2819 Removed unused schema * Make addresses conditionally required by country * Re-require the address fields * Display foreign addresses in the summary view * Utilize two separate gen info validators. * Whitespace lint. * #2819 Improved error handling * Fix test case to reflect validation error handling * #2819 Increased international address schema size and rebuilt schemas * #2819 Ensure general information form allows 500 characters max for foreign address * More schema update. This is a temp update, we will revisit a better approach --------- Co-authored-by: James Person --- .../validate_general_information.py | 70 ++- ...nformation--test0001test--simple-pass.json | 2 +- backend/audit/test_schemas.py | 14 +- backend/audit/test_validators.py | 176 ++++++ backend/audit/validators.py | 124 ++++- .../audit/test_data_entries/simple-cases.json | 1 + backend/dissemination/templates/summary.html | 83 +-- .../dissemination/workbooklib/sac_creation.py | 1 + backend/report_submission/forms.py | 3 +- backend/report_submission/test_views.py | 11 +- backend/report_submission/views.py | 62 ++- .../sections/AdditionalEINs.schema.json | 2 +- .../sections/AdditionalUEIs.schema.json | 2 +- .../sections/AuditFindingsText.schema.json | 2 +- .../sections/CorrectiveActionPlan.schema.json | 2 +- .../output/sections/FederalAwards.schema.json | 2 +- .../FederalAwardsAuditFindings.schema.json | 2 +- .../sections/GeneralInformation.schema.json | 470 +++++++--------- .../GeneralInformationComplete.schema.json | 500 ------------------ .../GeneralInformationRequired.schema.json | 473 +++++++---------- .../output/sections/NotesToSefa.schema.json | 2 +- .../sections/SecondaryAuditors.schema.json | 2 +- backend/schemas/source/base/Base.libsonnet | 1 + .../GeneralInformation.schema.jsonnet | 118 ++--- .../GeneralInformationComplete.schema.jsonnet | 257 --------- .../GeneralInformationRequired.schema.jsonnet | 3 +- 26 files changed, 889 insertions(+), 1496 deletions(-) delete mode 100644 backend/schemas/output/sections/GeneralInformationComplete.schema.json delete mode 100644 backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet diff --git a/backend/audit/cross_validation/validate_general_information.py b/backend/audit/cross_validation/validate_general_information.py index e3ce1cdb9f..906d4aa4b2 100644 --- a/backend/audit/cross_validation/validate_general_information.py +++ b/backend/audit/cross_validation/validate_general_information.py @@ -2,8 +2,52 @@ from django.conf import settings from jsonschema import FormatChecker, validate from jsonschema.exceptions import ValidationError as JSONSchemaValidationError - +from django.core.exceptions import ValidationError from audit.cross_validation.naming import NC +from audit.validators import validate_general_information_schema_rules + +required_fields = { + # `key_in_json_schema: label_for_user` + "audit_type": "Type of audit", + "auditee_address_line_1": "Auditee street", + "auditee_city": "Auditee city", + "auditee_zip": "Auditee ZIP", + "auditee_contact_name": "Auditee contact name", + "auditee_contact_title": "Auditee contact title", + "auditee_email": "Auditee email", + "auditee_fiscal_period_end": "Fiscal period end date", + "auditee_fiscal_period_start": "Fiscal period start date", + "auditee_name": "Auditee name", + "auditee_phone": "Auditee phone", + "auditee_state": "Auditee state", + "auditee_uei": "Auditee UEI", + "auditor_contact_name": "Auditor contact name", + "auditor_contact_title": "Auditor contact title", + "auditor_ein": "Auditor EIN", + "auditor_ein_not_an_ssn_attestation": "Confirmation that auditor EIN is not an SSN", + "auditor_email": "Auditor email", + "auditor_firm_name": "Audit firm name", + "auditor_phone": "Auditor phone number", + "ein": "Auditee EIN", + "ein_not_an_ssn_attestation": "Confirmation that auditee EIN is not an SSN", + "is_usa_based": "Auditor is USA-based", + "met_spending_threshold": "Spending threshold", + "multiple_eins_covered": "Multiple EINs covered", + "multiple_ueis_covered": "Multiple UEIs covered", + "secondary_auditors_exist": "Confirmation that secondary auditors exist", + "user_provided_organization_type": "Organization type", + "audit_period_covered": "Audit period", +} + +# optionally_required_fields is handled separately from required_fields +optionally_required_fields = { + "auditor_state": "Auditor state", + "auditor_zip": "Auditor ZIP", + "auditor_address_line_1": "Auditor street", + "auditor_city": "Auditor city", + "auditor_country": "Auditor country", + "auditor_international_address": "Auditor international address", +} def validate_general_information(sac_dict, *_args, **_kwargs): @@ -20,11 +64,33 @@ def validate_general_information(sac_dict, *_args, **_kwargs): """ all_sections = sac_dict["sf_sac_sections"] general_information = all_sections[NC.GENERAL_INFORMATION] - schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformationComplete.schema.json" + schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformationRequired.schema.json" schema = json.loads(schema_path.read_text(encoding="utf-8")) + errors = _check_required_field(general_information) + if errors: + return errors try: + validate_general_information_schema_rules(general_information) validate(general_information, schema, format_checker=FormatChecker()) except JSONSchemaValidationError as err: return [{"error": f"General Information: {str(err)}"}] + except ValidationError as err: + return [{"error": f"General Information: {str(err.message)}"}] + return [] + + +def _check_required_field(general_information): + """ + Check that all required fields are present in the general information. + """ + # Check that all required fields are present or return a message pointing to the missing fields + missing_fields = [] + for key, label in required_fields.items(): + if key not in general_information or general_information[key] in [None, ""]: + missing_fields.append(label) + + if missing_fields: + return [{"error": f"Missing required fields: {', '.join(missing_fields)}"}] + return [] diff --git a/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json b/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json index 25edfbf69b..544ffd88b9 100644 --- a/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json +++ b/backend/audit/fixtures/json/general-information--test0001test--simple-pass.json @@ -28,7 +28,7 @@ "auditor_address_line_1": "Main Hall", "met_spending_threshold": true, "secondary_auditors_exist": false, - "audit_period_other_months": "null", + "audit_period_other_months": "", "auditee_fiscal_period_end": "2023-01-01", "ein_not_an_ssn_attestation": true, "auditee_fiscal_period_start": "2022-01-01", diff --git a/backend/audit/test_schemas.py b/backend/audit/test_schemas.py index 81e03516ca..8cd1bdfeb8 100644 --- a/backend/audit/test_schemas.py +++ b/backend/audit/test_schemas.py @@ -42,7 +42,7 @@ class GeneralInformationSchemaValidityTest(SimpleTestCase): """ GENERAL_INFO_SCHEMA = json.loads( - (SECTION_SCHEMA_DIR / "GeneralInformation.schema.json").read_text( + (SECTION_SCHEMA_DIR / "GeneralInformationRequired.schema.json").read_text( encoding="utf-8" ) ) @@ -72,7 +72,7 @@ def test_invalid_auditee_fiscal_period_start(self): self.assertRaisesRegex( exceptions.ValidationError, - "'' was expected", # Value also accepts a blank string, so this error comes back. + "'not a date' is not a 'date'", validate, simple_case, schema, @@ -91,7 +91,7 @@ def test_invalid_auditee_fiscal_period_end(self): self.assertRaisesRegex( exceptions.ValidationError, - "'' was expected", # Value also accepts a blank string, so this error comes back. + "'not a date' is not a 'date'", validate, simple_case, schema, @@ -129,7 +129,7 @@ def test_invalid_ein(self): with self.assertRaisesRegex( exceptions.ValidationError, - "is not valid", + "does not match", msg=f"ValidationError not raised with EIN = {bad_ein}", ): validate(instance, schema) @@ -257,7 +257,7 @@ def test_invalid_zip(self): with self.assertRaisesRegex( exceptions.ValidationError, - "is not valid", + "does not match", msg=f"ValidationError not raised with zip = {bad_zip}", ): validate(instance, schema) @@ -290,7 +290,7 @@ def test_invalid_zip_plus_4(self): with self.assertRaisesRegex( exceptions.ValidationError, - "is not valid", + "does not match", msg=f"ValidationError not raised with zip = {bad_zip}", ): validate(instance, schema) @@ -353,7 +353,7 @@ def test_invalid_phone(self): with self.assertRaisesRegex( exceptions.ValidationError, - "is not valid", + "does not match", msg=f"ValidationError not raised with phone = {bad_phone}", ): validate(instance, schema) diff --git a/backend/audit/test_validators.py b/backend/audit/test_validators.py index 153d0eaf3d..3a8f441b6e 100644 --- a/backend/audit/test_validators.py +++ b/backend/audit/test_validators.py @@ -31,6 +31,8 @@ MAX_EXCEL_FILE_SIZE_MB, validate_additional_ueis_json, validate_additional_eins_json, + validate_general_information_schema, + validate_general_information_schema_rules, validate_notes_to_sefa_json, validate_corrective_action_plan_json, validate_file_content_type, @@ -994,3 +996,177 @@ def test_error_is_not_gsa_migration_auditor_email(self): with self.assertRaises(ValidationError): validate_general_information_json(gen_info, False) + + +class TestValidateGeneralInformation(SimpleTestCase): + def setUp(self): + """Set up common test data""" + # Example general information that matches the schema + self.valid_general_information = { + "audit_period_covered": "annual", + "auditor_country": "USA", + "ein": "123456789", + "audit_type": "single-audit", + "auditee_uei": "AQDDHJH47DW7", + "auditee_zip": "12345", + "auditor_ein": "123456789", + "auditor_zip": "12345", + "auditee_city": "Washington", + "auditee_name": "Auditee Name", + "auditor_city": "Washington", + "is_usa_based": True, + "auditee_email": "auditee@email.com", + "auditee_phone": "1234567890", + "auditee_state": "DC", + "auditor_email": "auditor@email.com", + "auditor_phone": "1234567890", + "auditor_state": "DC", + "auditor_country": "USA", + "auditor_firm_name": "Auditor Firm Name", + "audit_period_covered": "annual", + "auditee_contact_name": "Auditee Contact Name", + "auditor_contact_name": "Auditor Contact Name", + "auditee_contact_title": "Auditee Contact Title", + "auditor_contact_title": "Auditor Contact Title", + "multiple_eins_covered": False, + "multiple_ueis_covered": False, + "auditee_address_line_1": "Auditee Address Line 1", + "auditor_address_line_1": "Auditor Address Line 1", + "met_spending_threshold": True, + "secondary_auditors_exist": False, + "audit_period_other_months": "", + "auditee_fiscal_period_end": "2023-12-31", + "ein_not_an_ssn_attestation": False, + "auditee_fiscal_period_start": "2023-01-01", + "auditor_international_address": "", + "user_provided_organization_type": "state", + "auditor_ein_not_an_ssn_attestation": False, + } + + self.invalid_fiscal_period_end = self.valid_general_information | { + "auditee_fiscal_period_end": "Not a date", + } + + self.wrong_fiscal_period_end_format = self.valid_general_information | { + "auditee_fiscal_period_end": "2023-31-12", + } + + self.invalid_auditee_uei = self.valid_general_information | { + "auditee_uei": "Invalid", + } + + self.invalid_audit_period_other_months = self.valid_general_information | { + "audit_period_other_months": "Invalid", + } + + self.unexpected_state_and_zip = self.valid_general_information | { + "auditor_state": "DC", + "auditor_zip": "12345", + "auditor_country": "", + } + + self.unexpected_audit_period_other_months = self.valid_general_information | { + "audit_period_covered": "annual", + "audit_period_other_months": "12", + } + + self.missing_audit_period_other_months = self.valid_general_information | { + "audit_period_covered": "other", + "audit_period_other_months": "", + } + + self.missing_state_and_zip = self.valid_general_information | { + "auditor_state": "", + "auditor_zip": "", + "auditee_country": "USA", + } + + self.invalid_state_and_zip = self.valid_general_information | { + "auditor_state": "Not a state", + "auditor_zip": "Not a zip", + } + + self.invalid_phone = self.valid_general_information | { + "auditor_phone": "123-456-789", + } + + self.invalid_email = self.valid_general_information | { + "auditor_email": "auditor.email.com", + } + + self.invalid_audit_period_covered = self.valid_general_information | { + "audit_period_covered": "Invalid", + } + + def test_validate_general_information_schema_with_valid_data(self): + """ + Test the validation method with valid general information data. + """ + try: + validate_general_information_schema(self.valid_general_information) + except ValidationError: + self.fail( + "validate_general_information_schema raised ValidationError unexpectedly!" + ) + + def test_validate_general_information_schema_with_invalid_data(self): + """ + Test the validation method with invalid general information data. + """ + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_fiscal_period_end) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.wrong_fiscal_period_end_format) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_auditee_uei) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_audit_period_other_months) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_state_and_zip) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_phone) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_email) + with self.assertRaises(ValidationError): + validate_general_information_schema(self.invalid_audit_period_covered) + + def test_validate_general_information_schema_rules_with_valid_data(self): + """ + Test the validation method with valid general information data. + """ + try: + validate_general_information_schema_rules(self.valid_general_information) + except ValidationError: + self.fail( + "validate_general_information_schema_rules raised ValidationError unexpectedly!" + ) + + def test_validate_general_information_schema_rules_with_invalid_data(self): + """ + Test the validation method with invalid general information data. + """ + with self.assertRaises(ValidationError): + validate_general_information_schema_rules(self.unexpected_state_and_zip) + with self.assertRaises(ValidationError): + validate_general_information_schema_rules( + self.unexpected_audit_period_other_months + ) + with self.assertRaises(ValidationError): + validate_general_information_schema_rules( + self.missing_audit_period_other_months + ) + with self.assertRaises(ValidationError): + validate_general_information_schema_rules(self.missing_state_and_zip) + + def tes_validate_general_information_json(self): + """ + Test the validation method with valid general information data. + """ + try: + validate_general_information_json( + self.valid_general_information, is_data_migration=False + ) + except ValidationError: + self.fail( + "validate_general_information_json raised ValidationError unexpectedly!" + ) diff --git a/backend/audit/validators.py b/backend/audit/validators.py index 317b5d7bad..cf971374f3 100644 --- a/backend/audit/validators.py +++ b/backend/audit/validators.py @@ -216,42 +216,124 @@ def validate_federal_award_json(value): raise ValidationError(message=_federal_awards_json_error(errors)) -def validate_general_information_json(value, is_data_migration=True): +def validate_general_information_schema(general_information): """ Apply JSON Schema for general information and report errors. """ - schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformation.schema.json" + schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformationRequired.schema.json" schema = json.loads(schema_path.read_text(encoding="utf-8")) - + validator = Draft7Validator(schema, format_checker=FormatChecker()) try: - if not is_data_migration and settings.GSA_MIGRATION in [ - value.get("auditee_email", ""), - value.get("auditor_email", ""), - ]: - raise JSONSchemaValidationError( - f"{settings.GSA_MIGRATION} not permitted outside of migrations" - ) - validate(value, schema, format_checker=FormatChecker()) + for key, val in general_information.items(): + if key in schema["properties"] and val not in [None, "", [], {}]: + field_schema = { + "type": "object", + "properties": {key: schema["properties"][key]}, + } + validator.validate({key: val}, field_schema) except JSONSchemaValidationError as err: + logger.error( + f"ValidationError in General Information: Invalid value: {val} for key: {key}" + ) raise ValidationError( _(err.message), ) from err + return general_information + + +def validate_use_of_gsa_migration_keyword(general_information, is_data_migration): + """Check if GSA_MIGRATION keyword is used and is allowed to be used in general information""" + + if not is_data_migration and settings.GSA_MIGRATION in [ + general_information.get("auditee_email", ""), + general_information.get("auditor_email", ""), + ]: + raise ValidationError( + _(f"{settings.GSA_MIGRATION} not permitted outside of migrations"), + ) + + return general_information + + +def validate_general_information_schema_rules(general_information): + """Check general information schema rules""" + + # Check for invalid 'audit_period_other_months' usage + if general_information.get("audit_period_covered") in [ + "annual", + "biennial", + ] and general_information.get("audit_period_other_months"): + if general_information.get("audit_period_other_months"): + raise ValidationError( + _( + "Invalid Audit Period - 'Audit period months' should not be set for 'annual' or 'biennial' Audit periods" + ) + ) + # Ensure 'audit_period_other_months' is provided for 'other' audit period + elif general_information.get( + "audit_period_covered" + ) == "other" and not general_information.get("audit_period_other_months"): + raise ValidationError( + _( + "Invalid Audit Period - 'Audit period months' must be set for 'other' Audit period" + ), + ) + + # Validate USA auditor information + if general_information.get("auditor_country") == "USA" and ( + not general_information.get("auditor_zip") + or not general_information.get("auditor_state") + or not general_information.get("auditor_address_line_1") + or not general_information.get("auditor_city") + ): + raise ValidationError(_("Missing Auditor Street or City or State or Zip Code")) + + # Validate non-USA auditor state or zip code should not be provided + elif general_information.get("auditor_country") != "USA" and ( + general_information.get("auditor_zip") + or general_information.get("auditor_state") + or general_information.get("auditor_city") + or general_information.get("auditor_address_line_1") + ): + raise ValidationError( + _( + "Invalid Auditor Street or City or State or Zip Code for non-USA countries" + ) + ) + # Validate non-USA auditor address is provided + elif general_information.get("auditor_country") != "USA" and not ( + general_information.get("auditor_international_address") + ): + raise ValidationError(_("Missing Auditor International Address")) + + return general_information + + +def validate_general_information_json(value, is_data_migration=True): + """ + Apply JSON Schema and Python checks to a general information record. + + Keyword arguments: + is_data_migration -- True if ignoring GSA_MIGRATION emails. (default True) + """ + validate_use_of_gsa_migration_keyword(value, is_data_migration) + validate_general_information_schema(value) + return value -def validate_general_information_complete_json(value): +def validate_general_information_complete_json(value, is_data_migration=True): """ - Apply JSON Schema for general information completeness and report errors. + Apply JSON Schema and Python checks to a general information record. + Performs additional checks to enforce completeness. + + Keyword arguments: + is_data_migration -- True if ignoring GSA_MIGRATION emails. (default True) """ - schema_path = settings.SECTION_SCHEMA_DIR / "GeneralInformationComplete.schema.json" - schema = json.loads(schema_path.read_text(encoding="utf-8")) + validate_use_of_gsa_migration_keyword(value, is_data_migration) + validate_general_information_schema(value) + validate_general_information_schema_rules(value) - try: - validate(value, schema, format_checker=FormatChecker()) - except JSONSchemaValidationError as err: - raise ValidationError( - _(err.message), - ) from err return value diff --git a/backend/data_fixtures/audit/test_data_entries/simple-cases.json b/backend/data_fixtures/audit/test_data_entries/simple-cases.json index 525ea8fdb9..267ecd3150 100644 --- a/backend/data_fixtures/audit/test_data_entries/simple-cases.json +++ b/backend/data_fixtures/audit/test_data_entries/simple-cases.json @@ -29,6 +29,7 @@ "auditor_city": "AnotherFakeCity", "auditor_state": "WY", "auditor_zip": "56789", + "auditor_international_address": "", "auditor_contact_name": "Jane", "auditor_contact_title": "Another Title", "auditor_phone": "999-999-9999", diff --git a/backend/dissemination/templates/summary.html b/backend/dissemination/templates/summary.html index 15f2330601..3d62c8f4d1 100644 --- a/backend/dissemination/templates/summary.html +++ b/backend/dissemination/templates/summary.html @@ -185,36 +185,59 @@

Auditor

- - -

- Address: {{ general.auditor_address_line_1 }} -

- - -

- City and state: {{ general.auditor_city }}, {{ general.auditor_state }} -

- - - - -

- Zip code: {{ general.auditor_zip }} -

- - -

- Secondary Auditors?  - {% if data|getkey:"Secondary Auditors" %} - Y - {% else %} - N - {% endif %} - -

- - + {% if general.auditor_address_line_1 %} + {% comment %} Domestic auditor {% endcomment %} + + +

+ Address: {{ general.auditor_address_line_1 }} +

+ + +

+ City and state: {{ general.auditor_city }}, {{ general.auditor_state }} +

+ + + + +

+ Zip code: {{ general.auditor_zip }} +

+ + +

+ Secondary Auditors?  + {% if data|getkey:"Secondary Auditors" %} + Y + {% else %} + N + {% endif %} + +

+ + + {% else %} + {% comment %} International auditor {% endcomment %} + + +

+ Address: {{ general.auditor_foreign_address }} +

+ + +

+ Secondary Auditors?  + {% if data|getkey:"Secondary Auditors" %} + Y + {% else %} + N + {% endif %} + +

+ + + {% endif %} diff --git a/backend/dissemination/workbooklib/sac_creation.py b/backend/dissemination/workbooklib/sac_creation.py index cf2e0f60e0..ec18ba64ef 100644 --- a/backend/dissemination/workbooklib/sac_creation.py +++ b/backend/dissemination/workbooklib/sac_creation.py @@ -146,6 +146,7 @@ def _fake_general_information(dbkey, auditee_name="DEFAULT AUDITEE"): "auditor_ein_not_an_ssn_attestation": True, "auditor_email": gobj.cpaemail if gobj.cpaemail else "noemailfound@noemail.com", "auditor_firm_name": gobj.cpafirmname, + "auditor_international_address": "", "auditor_phone": gobj.cpaphone, # TODO: when we include territories in our valid states, remove this restriction "auditor_state": gobj.cpastate, diff --git a/backend/report_submission/forms.py b/backend/report_submission/forms.py index cde028da5d..0394bb6339 100644 --- a/backend/report_submission/forms.py +++ b/backend/report_submission/forms.py @@ -60,6 +60,7 @@ def clean(self): class GeneralInformationForm(forms.Form): max_string_length = 100 + foreign_address_max_length = 500 choices_state_abbrevs = list((i, i) for i in STATE_ABBREVS) audit_type = forms.CharField(required=False) @@ -104,7 +105,7 @@ class GeneralInformationForm(forms.Form): auditor_ein_not_an_ssn_attestation = forms.BooleanField(required=False) auditor_country = forms.CharField(required=False) auditor_international_address = forms.CharField( - max_length=max_string_length, required=False + max_length=foreign_address_max_length, required=False ) auditor_address_line_1 = forms.CharField( max_length=max_string_length, required=False diff --git a/backend/report_submission/test_views.py b/backend/report_submission/test_views.py index 8a0c88f17d..710ceffc9d 100644 --- a/backend/report_submission/test_views.py +++ b/backend/report_submission/test_views.py @@ -679,6 +679,7 @@ def test_post_gsa_migration_error(self): "auditee_city": "Chicago", "auditee_state": "IL", "auditee_zip": "60640", + "auditor_international_address": "", "auditee_contact_name": "Updated Designated Representative", "auditee_contact_title": "Lord of Windows", "auditee_phone": "5558675310", @@ -694,7 +695,11 @@ def test_post_gsa_migration_error(self): response = self.client.post(url, data=data) - self.assertEqual(response.status_code, 400) + self.assertIn("errors", response.context) + self.assertIn( + "GSA_MIGRATION not permitted outside of migrations", + response.context["errors"], + ) def test_post_validates_general_information(self): """When the general information form is submitted, the data should be validated against the general information schema""" @@ -749,3 +754,7 @@ def test_post_validates_general_information(self): response = self.client.post(url, data=data) self.assertContains(response, "Dates should be in the format") + self.assertNotIn( + "GSA_MIGRATION not permitted outside of migrations", + response.context["errors"], + ) diff --git a/backend/report_submission/views.py b/backend/report_submission/views.py index 7779ac24a4..ad5990977f 100644 --- a/backend/report_submission/views.py +++ b/backend/report_submission/views.py @@ -215,8 +215,12 @@ def post(self, request, *args, **kwargs): except SingleAuditChecklist.DoesNotExist as err: raise PermissionDenied("You do not have access to this audit.") from err except ValidationError as err: - message = f"ValidationError for report ID {report_id}: {err.message}" - raise BadRequest(message) + context = form.cleaned_data | { + "errors": [err.message], + "report_id": report_id, + "state_abbrevs": STATE_ABBREVS, + } + return render(request, "report_submission/gen-form.html", context) except LateChangeError: return render(request, "audit/no-late-changes.html") except Exception as err: @@ -229,39 +233,41 @@ def _dates_to_slashes(self, data): Given a general_information object containging both auditee_fiscal_period_start and auditee_fiscal_period_start, convert YYYY-MM-DD to MM/DD/YYYY for display. """ - try: - datetime_object_start = datetime.strptime( - data.get("auditee_fiscal_period_start", ""), "%Y-%m-%d" - ) - datetime_object_end = datetime.strptime( - data.get("auditee_fiscal_period_end", ""), "%Y-%m-%d" - ) - data["auditee_fiscal_period_start"] = datetime_object_start.strftime( - "%m/%d/%Y" - ) - data["auditee_fiscal_period_end"] = datetime_object_end.strftime("%m/%d/%Y") - except Exception: - return data + + data["auditee_fiscal_period_start"] = self._parse_hyphened_date( + data.get("auditee_fiscal_period_start", "") + ) + data["auditee_fiscal_period_end"] = self._parse_hyphened_date( + data.get("auditee_fiscal_period_end", "") + ) + return data + def _parse_slashed_date(self, date_str): + try: + return datetime.strptime(date_str, "%m/%d/%Y").strftime("%Y-%m-%d") + except ValueError: + return date_str + + def _parse_hyphened_date(self, date_str): + try: + return datetime.strptime(date_str, "%Y-%m-%d").strftime("%m/%d/%Y") + except ValueError: + return date_str + def _dates_to_hyphens(self, data): """ Given a general_information object containging both auditee_fiscal_period_start and auditee_fiscal_period_start, convert MM/DD/YYYY to YYYY-MM-DD for storage. """ - try: - datetime_object_start = datetime.strptime( - data.get("auditee_fiscal_period_start", ""), "%m/%d/%Y" - ) - datetime_object_end = datetime.strptime( - data.get("auditee_fiscal_period_end", ""), "%m/%d/%Y" - ) - data["auditee_fiscal_period_start"] = datetime_object_start.strftime( - "%Y-%m-%d" - ) - data["auditee_fiscal_period_end"] = datetime_object_end.strftime("%Y-%m-%d") - except Exception: - return data + + data["auditee_fiscal_period_start"] = self._parse_slashed_date( + data.get("auditee_fiscal_period_start", "") + ) + data["auditee_fiscal_period_end"] = self._parse_slashed_date( + data.get("auditee_fiscal_period_end", "") + ) + return data def _wipe_auditor_address(self, form): diff --git a/backend/schemas/output/sections/AdditionalEINs.schema.json b/backend/schemas/output/sections/AdditionalEINs.schema.json index 255f4d631c..1e5288c51b 100644 --- a/backend/schemas/output/sections/AdditionalEINs.schema.json +++ b/backend/schemas/output/sections/AdditionalEINs.schema.json @@ -65,7 +65,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/AdditionalUEIs.schema.json b/backend/schemas/output/sections/AdditionalUEIs.schema.json index 25c82db86b..4fc8f2bd64 100644 --- a/backend/schemas/output/sections/AdditionalUEIs.schema.json +++ b/backend/schemas/output/sections/AdditionalUEIs.schema.json @@ -88,7 +88,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/AuditFindingsText.schema.json b/backend/schemas/output/sections/AuditFindingsText.schema.json index 7b81a7b700..06a0833cca 100644 --- a/backend/schemas/output/sections/AuditFindingsText.schema.json +++ b/backend/schemas/output/sections/AuditFindingsText.schema.json @@ -81,7 +81,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/CorrectiveActionPlan.schema.json b/backend/schemas/output/sections/CorrectiveActionPlan.schema.json index 5f82a4c80c..060048bc80 100644 --- a/backend/schemas/output/sections/CorrectiveActionPlan.schema.json +++ b/backend/schemas/output/sections/CorrectiveActionPlan.schema.json @@ -81,7 +81,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/FederalAwards.schema.json b/backend/schemas/output/sections/FederalAwards.schema.json index 09c769bdc6..acf81fe6a9 100644 --- a/backend/schemas/output/sections/FederalAwards.schema.json +++ b/backend/schemas/output/sections/FederalAwards.schema.json @@ -552,7 +552,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/FederalAwardsAuditFindings.schema.json b/backend/schemas/output/sections/FederalAwardsAuditFindings.schema.json index 59d0bcbec0..09172643f9 100644 --- a/backend/schemas/output/sections/FederalAwardsAuditFindings.schema.json +++ b/backend/schemas/output/sections/FederalAwardsAuditFindings.schema.json @@ -8646,7 +8646,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/GeneralInformation.schema.json b/backend/schemas/output/sections/GeneralInformation.schema.json index 9525fae7dc..d598c8a876 100644 --- a/backend/schemas/output/sections/GeneralInformation.schema.json +++ b/backend/schemas/output/sections/GeneralInformation.schema.json @@ -70,89 +70,87 @@ }, "then": { "properties": { + "auditor_address_line_1": { + "maxLength": 500, + "minLength": 1, + "type": "string" + }, + "auditor_city": { + "maxLength": 500, + "minLength": 1, + "type": "string" + }, + "auditor_international_address": { + "const": "", + "type": "string" + }, "auditor_state": { - "anyOf": [ - { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ] - }, - { - "const": "", - "type": "string" - } + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" ] }, "auditor_zip": { - "anyOf": [ - { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" } } } @@ -169,6 +167,19 @@ }, "then": { "properties": { + "auditor_address_line_1": { + "const": "", + "type": "string" + }, + "auditor_city": { + "const": "", + "type": "string" + }, + "auditor_international_address": { + "maxLength": 500, + "minLength": 1, + "type": "string" + }, "auditor_state": { "const": "", "type": "string" @@ -184,36 +195,19 @@ "metamodel_version": "1.7.0", "properties": { "audit_period_covered": { - "oneOf": [ - { - "description": "Period type of audit being submitted", - "enum": [ - "annual", - "biennial", - "other" - ], - "title": "AuditPeriod", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] - }, - "audit_period_other_months": { - "maxLength": 100, + "description": "Period type of audit being submitted", + "enum": [ + "annual", + "biennial", + "other" + ], + "title": "AuditPeriod", "type": "string" }, - "audit_type": { - "oneOf": [ + "audit_period_other_months": { + "anyOf": [ { - "description": "Type of audit being submitted", - "enum": [ - "program-specific", - "single-audit" - ], - "title": "AuditType", + "pattern": "^0[0-9]|1[0-8]$", "type": "string" }, { @@ -222,6 +216,15 @@ } ] }, + "audit_type": { + "description": "Type of audit being submitted", + "enum": [ + "program-specific", + "single-audit" + ], + "title": "AuditType", + "type": "string" + }, "auditee_address_line_1": { "maxLength": 100, "type": "string" @@ -248,125 +251,86 @@ { "const": "GSA_MIGRATION", "type": "string" - }, - { - "const": "", - "type": "string" } ], "type": "string" }, "auditee_fiscal_period_end": { - "oneOf": [ - { - "format": "date" - }, - { - "const": "", - "type": "string" - } - ], - "type": "string" + "format": "date" }, "auditee_fiscal_period_start": { - "oneOf": [ - { - "format": "date" - }, - { - "const": "", - "type": "string" - } - ], - "type": "string" + "format": "date" }, "auditee_name": { "maxLength": 100, "type": "string" }, "auditee_phone": { - "oneOf": [ - { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" }, "auditee_state": { - "oneOf": [ - { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" - }, - { - "const": "", - "type": "string" - } + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" ] }, "auditee_uei": { @@ -396,16 +360,8 @@ ] }, "auditee_zip": { - "anyOf": [ - { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" }, "auditor_address_line_1": { "maxLength": 100, @@ -433,16 +389,8 @@ "type": "string" }, "auditor_ein": { - "oneOf": [ - { - "pattern": "^[0-9]{9}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{9}$", + "type": "string" }, "auditor_ein_not_an_ssn_attestation": { "type": "boolean" @@ -457,10 +405,6 @@ { "const": "GSA_MIGRATION", "type": "string" - }, - { - "const": "", - "type": "string" } ] }, @@ -469,20 +413,12 @@ "type": "string" }, "auditor_international_address": { - "maxLength": 100, + "maxLength": 500, "type": "string" }, "auditor_phone": { - "oneOf": [ - { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" }, "auditor_state": { "anyOf": [ @@ -570,16 +506,8 @@ ] }, "ein": { - "oneOf": [ - { - "pattern": "^[0-9]{9}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{9}$", + "type": "string" }, "ein_not_an_ssn_attestation": { "type": "boolean" @@ -600,26 +528,18 @@ "type": "boolean" }, "user_provided_organization_type": { - "oneOf": [ - { - "description": "Org type", - "enum": [ - "state", - "local", - "tribal", - "higher-ed", - "non-profit", - "unknown", - "none" - ], - "title": "OrganizationType", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "description": "Org type", + "enum": [ + "state", + "local", + "tribal", + "higher-ed", + "non-profit", + "unknown", + "none" + ], + "title": "OrganizationType", + "type": "string" } }, "title": "GeneralInformation", diff --git a/backend/schemas/output/sections/GeneralInformationComplete.schema.json b/backend/schemas/output/sections/GeneralInformationComplete.schema.json deleted file mode 100644 index d22cf65b49..0000000000 --- a/backend/schemas/output/sections/GeneralInformationComplete.schema.json +++ /dev/null @@ -1,500 +0,0 @@ -{ - "$id": "http://example.org/generalinformation", - "$schema": "http://json-schema.org/draft/2019-09/schema#", - "additionalProperties": false, - "allOf": [ - { - "anyOf": [ - { - "if": { - "properties": { - "audit_period_covered": { - "const": "annual" - } - } - }, - "then": { - "audit_period_other_months": { - "description": "Empty string or null", - "enum": [ - "", - "null" - ], - "title": "EmptyString_Null" - } - } - }, - { - "if": { - "properties": { - "audit_period_covered": { - "const": "biennial" - } - } - }, - "then": { - "audit_period_other_months": { - "description": "Empty string or null", - "enum": [ - "", - "null" - ], - "title": "EmptyString_Null" - } - } - }, - { - "if": { - "properties": { - "audit_period_covered": { - "const": "other" - } - } - }, - "then": { - "audit_period_other_months": { - "pattern": "^0[0-9]|1[0-8]$", - "type": "string" - } - } - } - ] - }, - { - "if": { - "properties": { - "auditor_country": { - "const": "USA" - } - } - }, - "then": { - "properties": { - "auditor_zip": { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - } - } - } - }, - { - "if": { - "properties": { - "auditor_country": { - "not": { - "const": "USA" - } - } - } - }, - "then": { - "properties": { - "auditor_zip": { - "const": "", - "type": "string" - } - } - } - }, - { - "properties": { - "ein_not_an_ssn_attestation": { - "const": true - } - } - }, - { - "properties": { - "auditor_ein_not_an_ssn_attestation": { - "const": true - } - } - } - ], - "metamodel_version": "1.7.0", - "properties": { - "audit_period_covered": { - "description": "Period type of audit being submitted", - "enum": [ - "annual", - "biennial", - "other" - ], - "title": "AuditPeriod", - "type": "string" - }, - "audit_period_other_months": { - "type": "string" - }, - "audit_type": { - "description": "Type of audit being submitted", - "enum": [ - "program-specific", - "single-audit" - ], - "title": "AuditType", - "type": "string" - }, - "auditee_address_line_1": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditee_city": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditee_contact_name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditee_contact_title": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditee_email": { - "oneOf": [ - { - "format": "email", - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - { - "const": "GSA_MIGRATION", - "type": "string" - } - ] - }, - "auditee_fiscal_period_end": { - "format": "date" - }, - "auditee_fiscal_period_start": { - "format": "date" - }, - "auditee_name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditee_phone": { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" - }, - "auditee_state": { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" - }, - "auditee_uei": { - "oneOf": [ - { - "allOf": [ - { - "maxLength": 12, - "minLength": 12 - }, - { - "pattern": "^[A-HJ-NP-Z1-9][A-HJ-NP-Z0-9]+$" - }, - { - "pattern": "^(?![A-HJ-NP-Z1-9]+[A-HJ-NP-Z0-9]*?[0-9]{9})[A-HJ-NP-Z0-9]*$" - }, - { - "pattern": "^(?![0-9]{9})" - } - ], - "type": "string" - }, - { - "const": "GSA_MIGRATION", - "type": "string" - } - ] - }, - "auditee_zip": { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - }, - "auditor_address_line_1": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditor_city": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditor_contact_name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditor_contact_title": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditor_country": { - "description": "USA or International", - "enum": [ - "USA", - "non-USA" - ], - "title": "CountryType", - "type": "string" - }, - "auditor_ein": { - "pattern": "^[0-9]{9}$", - "type": "string" - }, - "auditor_ein_not_an_ssn_attestation": { - "type": "boolean" - }, - "auditor_email": { - "oneOf": [ - { - "format": "email", - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - { - "const": "GSA_MIGRATION", - "type": "string" - } - ] - }, - "auditor_firm_name": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "auditor_international_address": { - "maxLength": 100, - "type": "string" - }, - "auditor_phone": { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" - }, - "auditor_state": { - "oneOf": [ - { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ] - }, - { - "const": "", - "type": "string" - } - ] - }, - "auditor_zip": { - "oneOf": [ - { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] - }, - "ein": { - "pattern": "^[0-9]{9}$", - "type": "string" - }, - "ein_not_an_ssn_attestation": { - "type": "boolean" - }, - "is_usa_based": { - "type": "boolean" - }, - "met_spending_threshold": { - "type": "boolean" - }, - "multiple_eins_covered": { - "type": "boolean" - }, - "multiple_ueis_covered": { - "type": "boolean" - }, - "secondary_auditors_exist": { - "type": "boolean" - }, - "user_provided_organization_type": { - "description": "Org type", - "enum": [ - "state", - "local", - "tribal", - "higher-ed", - "non-profit", - "unknown", - "none" - ], - "title": "OrganizationType", - "type": "string" - } - }, - "required": [ - "audit_type", - "auditee_address_line_1", - "auditee_city", - "auditee_contact_name", - "auditee_contact_title", - "auditee_email", - "auditee_fiscal_period_end", - "auditee_fiscal_period_start", - "auditee_name", - "auditee_phone", - "auditee_state", - "auditee_uei", - "auditor_contact_name", - "auditor_contact_title", - "auditor_ein", - "auditor_ein_not_an_ssn_attestation", - "auditor_email", - "auditor_firm_name", - "auditor_phone", - "auditor_state", - "auditor_zip", - "ein", - "ein_not_an_ssn_attestation", - "is_usa_based", - "met_spending_threshold", - "multiple_eins_covered", - "multiple_ueis_covered", - "secondary_auditors_exist", - "user_provided_organization_type" - ], - "title": "GeneralInformation", - "type": "object", - "version": null -} diff --git a/backend/schemas/output/sections/GeneralInformationRequired.schema.json b/backend/schemas/output/sections/GeneralInformationRequired.schema.json index 3616cde650..500b769c1c 100644 --- a/backend/schemas/output/sections/GeneralInformationRequired.schema.json +++ b/backend/schemas/output/sections/GeneralInformationRequired.schema.json @@ -70,89 +70,87 @@ }, "then": { "properties": { + "auditor_address_line_1": { + "maxLength": 500, + "minLength": 1, + "type": "string" + }, + "auditor_city": { + "maxLength": 500, + "minLength": 1, + "type": "string" + }, + "auditor_international_address": { + "const": "", + "type": "string" + }, "auditor_state": { - "anyOf": [ - { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ] - }, - { - "const": "", - "type": "string" - } + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" ] }, "auditor_zip": { - "anyOf": [ - { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" } } } @@ -169,6 +167,19 @@ }, "then": { "properties": { + "auditor_address_line_1": { + "const": "", + "type": "string" + }, + "auditor_city": { + "const": "", + "type": "string" + }, + "auditor_international_address": { + "maxLength": 500, + "minLength": 1, + "type": "string" + }, "auditor_state": { "const": "", "type": "string" @@ -184,36 +195,19 @@ "metamodel_version": "1.7.0", "properties": { "audit_period_covered": { - "oneOf": [ - { - "description": "Period type of audit being submitted", - "enum": [ - "annual", - "biennial", - "other" - ], - "title": "AuditPeriod", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] - }, - "audit_period_other_months": { - "maxLength": 100, + "description": "Period type of audit being submitted", + "enum": [ + "annual", + "biennial", + "other" + ], + "title": "AuditPeriod", "type": "string" }, - "audit_type": { - "oneOf": [ + "audit_period_other_months": { + "anyOf": [ { - "description": "Type of audit being submitted", - "enum": [ - "program-specific", - "single-audit" - ], - "title": "AuditType", + "pattern": "^0[0-9]|1[0-8]$", "type": "string" }, { @@ -222,6 +216,15 @@ } ] }, + "audit_type": { + "description": "Type of audit being submitted", + "enum": [ + "program-specific", + "single-audit" + ], + "title": "AuditType", + "type": "string" + }, "auditee_address_line_1": { "maxLength": 100, "type": "string" @@ -248,125 +251,86 @@ { "const": "GSA_MIGRATION", "type": "string" - }, - { - "const": "", - "type": "string" } ], "type": "string" }, "auditee_fiscal_period_end": { - "oneOf": [ - { - "format": "date" - }, - { - "const": "", - "type": "string" - } - ], - "type": "string" + "format": "date" }, "auditee_fiscal_period_start": { - "oneOf": [ - { - "format": "date" - }, - { - "const": "", - "type": "string" - } - ], - "type": "string" + "format": "date" }, "auditee_name": { "maxLength": 100, "type": "string" }, "auditee_phone": { - "oneOf": [ - { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" }, "auditee_state": { - "oneOf": [ - { - "description": "US States 2-letter abbreviations", - "enum": [ - "AL", - "AK", - "AS", - "AZ", - "AR", - "CA", - "CO", - "CT", - "DE", - "DC", - "FM", - "FL", - "GA", - "GU", - "HI", - "ID", - "IL", - "IN", - "IA", - "KS", - "KY", - "LA", - "ME", - "MH", - "MD", - "MA", - "MI", - "MN", - "MS", - "MO", - "MT", - "NE", - "NV", - "NH", - "NJ", - "NM", - "NY", - "NC", - "ND", - "MP", - "OH", - "OK", - "OR", - "PW", - "PA", - "PR", - "RI", - "SC", - "SD", - "TN", - "TX", - "UT", - "VT", - "VI", - "VA", - "WA", - "WV", - "WI", - "WY" - ], - "title": "State" - }, - { - "const": "", - "type": "string" - } + "description": "US States 2-letter abbreviations", + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" ] }, "auditee_uei": { @@ -396,16 +360,8 @@ ] }, "auditee_zip": { - "anyOf": [ - { - "pattern": "^[0-9]{5}(?:[0-9]{4})?$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{5}(?:[0-9]{4})?$", + "type": "string" }, "auditor_address_line_1": { "maxLength": 100, @@ -433,16 +389,8 @@ "type": "string" }, "auditor_ein": { - "oneOf": [ - { - "pattern": "^[0-9]{9}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{9}$", + "type": "string" }, "auditor_ein_not_an_ssn_attestation": { "type": "boolean" @@ -457,10 +405,6 @@ { "const": "GSA_MIGRATION", "type": "string" - }, - { - "const": "", - "type": "string" } ] }, @@ -469,20 +413,12 @@ "type": "string" }, "auditor_international_address": { - "maxLength": 100, + "maxLength": 500, "type": "string" }, "auditor_phone": { - "oneOf": [ - { - "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^^(\\+0?1\\s)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$", + "type": "string" }, "auditor_state": { "anyOf": [ @@ -570,16 +506,8 @@ ] }, "ein": { - "oneOf": [ - { - "pattern": "^[0-9]{9}$", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "pattern": "^[0-9]{9}$", + "type": "string" }, "ein_not_an_ssn_attestation": { "type": "boolean" @@ -600,26 +528,18 @@ "type": "boolean" }, "user_provided_organization_type": { - "oneOf": [ - { - "description": "Org type", - "enum": [ - "state", - "local", - "tribal", - "higher-ed", - "non-profit", - "unknown", - "none" - ], - "title": "OrganizationType", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ] + "description": "Org type", + "enum": [ + "state", + "local", + "tribal", + "higher-ed", + "non-profit", + "unknown", + "none" + ], + "title": "OrganizationType", + "type": "string" } }, "required": [ @@ -656,7 +576,8 @@ "auditor_address_line_1", "auditor_city", "auditor_country", - "audit_period_covered" + "audit_period_covered", + "auditor_international_address" ], "title": "GeneralInformation", "type": "object", diff --git a/backend/schemas/output/sections/NotesToSefa.schema.json b/backend/schemas/output/sections/NotesToSefa.schema.json index d3d8d29b18..2617e99cde 100644 --- a/backend/schemas/output/sections/NotesToSefa.schema.json +++ b/backend/schemas/output/sections/NotesToSefa.schema.json @@ -13,7 +13,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/output/sections/SecondaryAuditors.schema.json b/backend/schemas/output/sections/SecondaryAuditors.schema.json index d40b187097..e47799e3eb 100644 --- a/backend/schemas/output/sections/SecondaryAuditors.schema.json +++ b/backend/schemas/output/sections/SecondaryAuditors.schema.json @@ -13,7 +13,7 @@ "type": "string" }, "version": { - "const": "1.0.5", + "const": "1.1.0", "type": "string" } }, diff --git a/backend/schemas/source/base/Base.libsonnet b/backend/schemas/source/base/Base.libsonnet index cbe189075b..98e8f44763 100644 --- a/backend/schemas/source/base/Base.libsonnet +++ b/backend/schemas/source/base/Base.libsonnet @@ -313,6 +313,7 @@ local Compound = { }, NonEmptyString: Types.string { minLength: 1, + maxLength: 500, }, EmployerIdentificationNumber: Types.string { pattern: '^[0-9]{9}$', diff --git a/backend/schemas/source/sections/GeneralInformation.schema.jsonnet b/backend/schemas/source/sections/GeneralInformation.schema.jsonnet index b94682806e..b62f39d8f6 100644 --- a/backend/schemas/source/sections/GeneralInformation.schema.jsonnet +++ b/backend/schemas/source/sections/GeneralInformation.schema.jsonnet @@ -3,7 +3,7 @@ local Func = import '../base/Functions.libsonnet'; local Types = Base.Types; /* -Typechecks fields, but allows for empty data as well. Contains conditional Checks. +Typechecks fields, but allows for empty data as well. Contains conditional checks. */ { '$id': 'http://example.org/generalinformation', @@ -12,46 +12,24 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check metamodel_version: '1.7.0', properties: { // Audit information - auditee_fiscal_period_start: Types.string { - oneOf: [ - { - format: 'date', - }, - Base.Compound.EmptyString, - ], + auditee_fiscal_period_start: { + format: 'date', }, - auditee_fiscal_period_end: Types.string { - oneOf: [ - { - format: 'date', - }, - Base.Compound.EmptyString, - ], + auditee_fiscal_period_end: { + format: 'date', }, - audit_type: { - oneOf: [ - Base.Enum.AuditType, - Base.Compound.EmptyString, - ], - }, - audit_period_covered: { - oneOf: [ - Base.Enum.AuditPeriod, + audit_type: Base.Enum.AuditType, + audit_period_covered: Base.Enum.AuditPeriod, + audit_period_other_months: { + anyOf: [ + Base.Compound.MonthsOther, Base.Compound.EmptyString, ], }, - audit_period_other_months: Types.string { - maxLength: 100, - }, // Auditee information auditee_uei: Base.Compound.UniqueEntityIdentifier, - ein: { - oneOf: [ - Base.Compound.EmployerIdentificationNumber, - Base.Compound.EmptyString, - ], - }, + ein: Base.Compound.EmployerIdentificationNumber, ein_not_an_ssn_attestation: Types.boolean, auditee_name: Types.string { maxLength: 100, @@ -62,20 +40,8 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check auditee_city: Types.string { maxLength: 100, }, - auditee_state: { - oneOf: [ - Base.Enum.UnitedStatesStateAbbr { - title: 'State', - }, - Base.Compound.EmptyString, - ], - }, - auditee_zip: { - anyOf: [ - Base.Compound.Zip, - Base.Compound.EmptyString, - ], - }, + auditee_state: Base.Enum.UnitedStatesStateAbbr, + auditee_zip: Base.Compound.Zip, auditee_contact_name: Types.string { maxLength: 100, @@ -83,12 +49,7 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check auditee_contact_title: Types.string { maxLength: 100, }, - auditee_phone: { - oneOf: [ - Base.Compound.UnitedStatesPhone, - Base.Compound.EmptyString, - ], - }, + auditee_phone: Base.Compound.UnitedStatesPhone, auditee_email: Types.string { oneOf: [ Types.string { @@ -98,24 +59,18 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check Types.string { const: Base.Const.GSA_MIGRATION, }, - Base.Compound.EmptyString, ], }, // Auditor information - auditor_ein: { - oneOf: [ - Base.Compound.EmployerIdentificationNumber, - Base.Compound.EmptyString, - ], - }, + auditor_ein: Base.Compound.EmployerIdentificationNumber, auditor_ein_not_an_ssn_attestation: Types.boolean, auditor_firm_name: Types.string { maxLength: 100, }, auditor_country: Base.Enum.CountryType, auditor_international_address: Types.string { - maxLength: 100, + maxLength: 500, }, auditor_address_line_1: Types.string { maxLength: 100, @@ -144,12 +99,7 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check auditor_contact_title: Types.string { maxLength: 100, }, - auditor_phone: { - oneOf: [ - Base.Compound.UnitedStatesPhone, - Base.Compound.EmptyString, - ], - }, + auditor_phone: Base.Compound.UnitedStatesPhone, auditor_email: { oneOf: [ Types.string { @@ -159,18 +109,12 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check Types.string { const: Base.Const.GSA_MIGRATION, }, - Base.Compound.EmptyString, ], }, // Others is_usa_based: Types.boolean, met_spending_threshold: Types.boolean, - user_provided_organization_type: { - oneOf: [ - Base.Enum.OrganizationType, - Base.Compound.EmptyString, - ], - }, + user_provided_organization_type: Base.Enum.OrganizationType, multiple_eins_covered: Types.boolean, multiple_ueis_covered: Types.boolean, secondary_auditors_exist: Types.boolean, @@ -216,6 +160,7 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check }, ], }, + // If auditor is from the USA, address info should be included. { 'if': { properties: { @@ -226,21 +171,16 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check }, 'then': { properties: { - auditor_zip: { - anyOf: [ - Base.Compound.Zip, - Base.Compound.EmptyString, - ], - }, - auditor_state: { - anyOf: [ - Base.Enum.UnitedStatesStateAbbr, - Base.Compound.EmptyString, - ], - }, + auditor_address_line_1: Base.Compound.NonEmptyString, + auditor_city: Base.Compound.NonEmptyString, + auditor_state: Base.Enum.UnitedStatesStateAbbr, + auditor_zip: Base.Compound.Zip, + + auditor_international_address: Base.Compound.EmptyString }, }, }, + // If auditor is NOT from the USA, international things should be filled in. { 'if': { properties: { @@ -253,8 +193,12 @@ Typechecks fields, but allows for empty data as well. Contains conditional Check }, 'then': { properties: { - auditor_zip: Base.Compound.EmptyString, + auditor_address_line_1: Base.Compound.EmptyString, + auditor_city: Base.Compound.EmptyString, auditor_state: Base.Compound.EmptyString, + auditor_zip: Base.Compound.EmptyString, + + auditor_international_address: Base.Compound.NonEmptyString }, }, }, diff --git a/backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet b/backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet deleted file mode 100644 index 9064f1f735..0000000000 --- a/backend/schemas/source/sections/GeneralInformationComplete.schema.jsonnet +++ /dev/null @@ -1,257 +0,0 @@ -local Base = import '../base/Base.libsonnet'; -local Func = import '../base/Functions.libsonnet'; -local Types = Base.Types; - -/* -Checks all the fields to answer the question, "Is the form complete?" -Requires most fields, has consitional checks for conditional fields. -*/ -{ - '$id': 'http://example.org/generalinformation', - '$schema': 'http://json-schema.org/draft/2019-09/schema#', - additionalProperties: false, - metamodel_version: '1.7.0', - properties: { - // Audit information - auditee_fiscal_period_start: { - format: 'date', - }, - auditee_fiscal_period_end: { - format: 'date', - }, - audit_type: Base.Enum.AuditType, - audit_period_covered: Base.Enum.AuditPeriod, - audit_period_other_months: Types.string, // Conditional, no min/max length - - // Auditee information - auditee_uei: Base.Compound.UniqueEntityIdentifier, - ein: Base.Compound.EmployerIdentificationNumber, - ein_not_an_ssn_attestation: Types.boolean, - auditee_name: Types.string { - maxLength: 100, - minLength: 1, - }, - auditee_address_line_1: Types.string { - maxLength: 100, - minLength: 1, - }, - auditee_city: Types.string { - maxLength: 100, - minLength: 1, - }, - auditee_state: Base.Enum.UnitedStatesStateAbbr { - title: 'State', - }, - auditee_zip: Base.Compound.Zip, - - auditee_contact_name: Types.string { - maxLength: 100, - minLength: 1, - }, - auditee_contact_title: Types.string { - maxLength: 100, - minLength: 1, - }, - auditee_phone: Base.Compound.UnitedStatesPhone, - auditee_email: { - oneOf: [ - Types.string { - format: 'email', - maxLength: 100, - minLength: 1, - }, - Types.string { - const: Base.Const.GSA_MIGRATION, - }, - ], - }, - - // Auditor information - // Auditor address information is conditional based on country - auditor_ein: Base.Compound.EmployerIdentificationNumber, - auditor_ein_not_an_ssn_attestation: Types.boolean, - auditor_firm_name: Types.string { - maxLength: 100, - minLength: 1, - }, - auditor_country: Base.Enum.CountryType, - auditor_international_address: Types.string { - maxLength: 100, - }, - auditor_address_line_1: Types.string { - maxLength: 100, - minLength: 1, - }, - auditor_city: Types.string { - maxLength: 100, - minLength: 1, - }, - auditor_state: { - oneOf: [ - Base.Enum.UnitedStatesStateAbbr, - Base.Compound.EmptyString, - ], - }, - auditor_zip: { - oneOf: [ - Base.Compound.Zip, - Base.Compound.EmptyString, - ], - }, - - auditor_contact_name: Types.string { - maxLength: 100, - minLength: 1, - }, - auditor_contact_title: Types.string { - maxLength: 100, - minLength: 1, - }, - auditor_phone: Base.Compound.UnitedStatesPhone, - auditor_email: { - oneOf: [ - Types.string { - format: 'email', - maxLength: 100, - minLength: 1, - }, - Types.string { - const: Base.Const.GSA_MIGRATION, - }, - ], - }, - // Others - is_usa_based: Types.boolean, - met_spending_threshold: Types.boolean, - user_provided_organization_type: Base.Enum.OrganizationType, - multiple_eins_covered: Types.boolean, - multiple_ueis_covered: Types.boolean, - secondary_auditors_exist: Types.boolean, - }, - allOf: [ - // If audit_period_covered is 'other', then audit_period_other_months should - // have a value. Otherwise, it should have no value. - { - anyOf: [ - { - 'if': { - properties: { - audit_period_covered: { - const: 'annual', - }, - }, - }, - 'then': { - audit_period_other_months: Base.Enum.EmptyString_Null, - }, - }, - { - 'if': { - properties: { - audit_period_covered: { - const: 'biennial', - }, - }, - }, - 'then': { - audit_period_other_months: Base.Enum.EmptyString_Null, - }, - }, - { - 'if': { - properties: { - audit_period_covered: { - const: 'other', - }, - }, - }, - 'then': { - audit_period_other_months: Base.Compound.MonthsOther, - }, - }, - ], - }, - // If auditor is from the USA, address info should be included. - { - 'if': { - properties: { - auditor_country: { - const: 'USA', - }, - }, - }, - 'then': { - properties: { - auditor_zip: Base.Compound.Zip, - }, - }, - }, - // If auditor is NOT from the USA, the zip should be empty. - { - 'if': { - properties: { - auditor_country: { - not: { - const: 'USA', - }, - }, - }, - }, - 'then': { - properties: { - auditor_zip: Base.Compound.EmptyString, - }, - }, - }, - // The auditee EIN attestation should always be true. - { - properties: { - ein_not_an_ssn_attestation: { - const: true, - }, - }, - }, - // The auditor EIN attestation should always be true. - { - properties: { - auditor_ein_not_an_ssn_attestation: { - const: true, - }, - }, - }, - ], - required: [ - 'audit_type', - 'auditee_address_line_1', - 'auditee_city', - 'auditee_contact_name', - 'auditee_contact_title', - 'auditee_email', - 'auditee_fiscal_period_end', - 'auditee_fiscal_period_start', - 'auditee_name', - 'auditee_phone', - 'auditee_state', - 'auditee_uei', - 'auditor_contact_name', - 'auditor_contact_title', - 'auditor_ein', - 'auditor_ein_not_an_ssn_attestation', - 'auditor_email', - 'auditor_firm_name', - 'auditor_phone', - 'auditor_state', - 'auditor_zip', - 'ein', - 'ein_not_an_ssn_attestation', - 'is_usa_based', - 'met_spending_threshold', - 'multiple_eins_covered', - 'multiple_ueis_covered', - 'secondary_auditors_exist', - 'user_provided_organization_type', - ], - title: 'GeneralInformation', - type: 'object', - version: null, -} diff --git a/backend/schemas/source/sections/GeneralInformationRequired.schema.jsonnet b/backend/schemas/source/sections/GeneralInformationRequired.schema.jsonnet index 509ce946f9..f80fee30a9 100644 --- a/backend/schemas/source/sections/GeneralInformationRequired.schema.jsonnet +++ b/backend/schemas/source/sections/GeneralInformationRequired.schema.jsonnet @@ -30,13 +30,12 @@ local RequiredField = [ 'multiple_ueis_covered', 'secondary_auditors_exist', 'user_provided_organization_type', - - //FIXME-MSHD: These fields were not enforced in GeneralInformationComplete.schema.jsonnet ??? 'auditee_zip', 'auditor_address_line_1', 'auditor_city', 'auditor_country', 'audit_period_covered', + 'auditor_international_address', ]; GeneralInformation {