diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index c34493015..c68c31e15 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -22,15 +22,17 @@ var SUCCESS = "success"; * */ const hideElement = (element) => { - element.classList.add('display-none'); + if (element && !element.classList.contains("display-none")) + element.classList.add('display-none'); }; /** - * Show element - * +* Show element +* */ const showElement = (element) => { - element.classList.remove('display-none'); + if (element && element.classList.contains("display-none")) + element.classList.remove('display-none'); }; /** @@ -2385,81 +2387,68 @@ document.addEventListener('DOMContentLoaded', function() { // This determines if we are on the requesting entity page or not. const fieldset = document.getElementById("requesting-entity-fieldset"); if (!fieldset) return; - console.log("past here") + // Get the is_suborganization radio buttons // Sadly, these ugly ids are the auto generated const formPrefix = "portfolio_requesting_entity" const isSuborgRadios = document.querySelectorAll(`input[name="${formPrefix}-is_suborganization"]`); - var selectedRequestingEntityValue = document.querySelector(`input[name="${formPrefix}-is_suborganization"]:checked`)?.value; const subOrgSelect = document.querySelector(`#id_${formPrefix}-sub_organization`); - const orgName = document.querySelector(`#id_${formPrefix}-organization_name`); - const city = document.querySelector(`#id_${formPrefix}-city`); - const stateTerritory = document.querySelector(`#id_${formPrefix}-state_territory`); - console.log(isSuborgRadios) - console.log(subOrgSelect) - console.log(orgName) - console.log(city) - console.log(stateTerritory) - console.log(selectedRequestingEntityValue) + // The suborganization section is its own div + const suborganizationFieldset = document.querySelector("#requesting-entity-fieldset__suborganization"); + + // Within the suborganization section, we also have a div that contains orgname, city, and stateterritory + const suborganizationDetailsFieldset = document.querySelector("#requesting-entity-fieldset__suborganization__details"); + + // Use a variable to determine which option has been selected on the yes/no form. + // Don't do anything if we are missing crucial page elements - if (!isSuborgRadios || !subOrgSelect || !orgName || !city || !stateTerritory) return; - console.log("past here x2") + if (!isSuborgRadios || !subOrgSelect || !suborganizationFieldset || !suborganizationDetailsFieldset) return; - // Add fake "other" option to sub_organization select - if (subOrgSelect && !Array.from(subOrgSelect.options).some(option => option.value === "other")) { - const fakeOption = document.createElement("option"); - fakeOption.value = "other"; - fakeOption.text = "Other (enter your organization manually)"; - subOrgSelect.add(fakeOption); - } - - // Hide organization_name, city, state_territory by default - hideElement(orgName.parentElement); - hideElement(city.parentElement); - hideElement(stateTerritory.parentElement); - - // Function to toggle forms based on is_suborganization selection - function toggleSubOrganizationFields () { - selectedRequestingEntityValue = document.querySelector(`input[name="${formPrefix}-is_suborganization"]:checked`)?.value; - if (selectedRequestingEntityValue === "True") { - showElement(subOrgSelect.parentElement); - toggleOrganizationDetails(); - } else { - hideElement(subOrgSelect.parentElement); - hideElement(orgName.parentElement); - hideElement(city.parentElement); - hideElement(stateTerritory.parentElement); - } + // Function to toggle suborganization based on is_suborganization selection + function toggleSuborganization(radio) { + if (radio && radio.checked && radio.value === "True") { + showElement(suborganizationFieldset); + toggleSuborganizationDetails(); + } else { + hideElement(suborganizationFieldset); + hideElement(suborganizationDetailsFieldset); + } }; // Function to toggle organization details based on sub_organization selection - function toggleOrganizationDetails () { - // We should hide the org name fields when we select the special other value - if (subOrgSelect.value === "other") { - showElement(orgName.parentElement); - showElement(city.parentElement); - showElement(stateTerritory.parentElement); - } else { - hideElement(orgName.parentElement); - hideElement(city.parentElement); - hideElement(stateTerritory.parentElement); - } + function toggleSuborganizationDetails () { + // We should hide the org name fields when we select the special other value + if (subOrgSelect.value === "other") { + showElement(suborganizationDetailsFieldset); + } else { + hideElement(suborganizationDetailsFieldset); + } }; - // Initialize visibility - toggleSubOrganizationFields(); + // Add fake "other" option to sub_organization select + if (subOrgSelect && !Array.from(subOrgSelect.options).some(option => option.value === "other")) { + const fakeOption = document.createElement("option"); + fakeOption.value = "other"; + fakeOption.text = "Other (enter your organization manually)"; + subOrgSelect.add(fakeOption); + } - // Add event listeners to is_suborganization radio buttons + // Add event listener to is_suborganization radio buttons isSuborgRadios.forEach(radio => { - radio.addEventListener("change", () => { - toggleSubOrganizationFields(); - }); + // Run this here for initial display. + // Since there are only two radio buttons and since this has (practically speaking) no performance impact, this is fine to do. + toggleSuborganization(radio); + + // Add an event listener to each to show/hide the relevant fields + radio.addEventListener("click", () => { + toggleSuborganization(radio); + }); }); + // Add event listener to the suborg dropdown to show/hide the suborg details section subOrgSelect.addEventListener("change", () => { - if (selectedRequestingEntityValue === "True") { - toggleOrganizationDetails(); - } + toggleSuborganizationDetails(); }); + })(); \ No newline at end of file diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index a7a971912..a348029a9 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -31,17 +31,20 @@ class RequestingEntityForm(RegistrarForm): queryset=Suborganization.objects.none(), empty_label="--Select--", ) - organization_name = forms.CharField( + + # We are using the current sub_organization naming convention here. + # We may want to refactor this to suborganization eventually. + requested_suborganization = forms.CharField( label="Requested suborganization", required=False, error_messages={"required": "Enter the name of your organization."}, ) - city = forms.CharField( + suborganization_city = forms.CharField( label="City", required=False, error_messages={"required": "Enter the city where your organization is located."}, ) - state_territory = forms.ChoiceField( + suborganization_state_territory = forms.ChoiceField( label="State, territory, or military post", required=False, choices=[("", "--Select--")] + DomainRequest.StateTerritoryChoices.choices, @@ -66,26 +69,52 @@ class RequestingEntityForm(RegistrarForm): def clean_sub_organization(self): """Require something to be selected when this is a federal agency.""" + is_suborganization = self.cleaned_data.get("is_suborganization", None) sub_organization = self.cleaned_data.get("sub_organization", None) - if self.cleaned_data.get("is_suborganization", None): - # TODO - logic for if other is selected, display other stuff - if not sub_organization: - # no answer was selected + print(f"sub org is: {sub_organization} vs is_suborg: {is_suborganization}") + if is_suborganization and not sub_organization: + raise forms.ValidationError( + "Select a suborganization.", + code="required", + ) + return sub_organization + + def clean_requested_suborganization(self): + field = self.cleaned_data.get("requested_suborganization") + if self.is_custom_suborg() and not field: + raise forms.ValidationError( + "Enter details for your organization name.", + code="required", + ) + return field + + def clean_suborganization_city(self): + field = self.cleaned_data.get("suborganization_city") + if self.is_custom_suborg(): + if not field: raise forms.ValidationError( - "Select a suborganization.", + "Enter details for your city.", code="required", ) - # Maybe we just represent this with none? - elif sub_organization == "other": - org_name = self.cleaned_data.get("organization_name", None) - city = self.cleaned_data.get("city", None) - state = self.cleaned_data.get("state_territory", None) - if not org_name or not city or not state: - raise forms.ValidationError( - "Enter details for your suborganization.", - code="required", - ) - return sub_organization + return field + + def clean_suborganization_state_territory(self): + field = self.cleaned_data.get("suborganization_state_territory") + if self.is_custom_suborg(): + if not field: + raise forms.ValidationError( + "Enter details for your state or territory.", + code="required", + ) + return field + + def is_custom_suborg(self): + """Checks that the select suborg is 'other', which is a custom field indicating + that we should create a new suborganization.""" + is_suborganization = self.cleaned_data.get("is_suborganization") + sub_organization = self.cleaned_data.get("sub_organization") + return is_suborganization and sub_organization == "other" + class RequestingEntityYesNoForm(BaseYesNoForm): """The yes/no field for the RequestingEntity form.""" @@ -107,10 +136,11 @@ class RequestingEntityYesNoForm(BaseYesNoForm): Determines the initial checked state of the form based on the domain_request's attributes. """ - if self.domain_request.portfolio and (self.domain_request.sub_organization or self.domain_request.organization_name): - return self.domain_request.organization_name != self.domain_request.portfolio.organization_name + if self.domain_request.portfolio and self.domain_request.organization_name == self.domain_request.portfolio.organization_name: + return False + elif self.domain_request.is_suborganization(): + return True else: - # No pre-selection for new domain requests return None class OrganizationTypeForm(RegistrarForm): diff --git a/src/registrar/migrations/0134_domainrequest_requested_suborganization_and_more.py b/src/registrar/migrations/0134_domainrequest_requested_suborganization_and_more.py new file mode 100644 index 000000000..bc7235a41 --- /dev/null +++ b/src/registrar/migrations/0134_domainrequest_requested_suborganization_and_more.py @@ -0,0 +1,95 @@ +# Generated by Django 4.2.10 on 2024-10-23 15:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0133_domainrequest_rejection_reason_email_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="domainrequest", + name="requested_suborganization", + field=models.CharField(blank=True, null=True), + ), + migrations.AddField( + model_name="domainrequest", + name="suborganization_city", + field=models.CharField(blank=True, null=True), + ), + migrations.AddField( + model_name="domainrequest", + name="suborganization_state_territory", + field=models.CharField( + blank=True, + choices=[ + ("AL", "Alabama (AL)"), + ("AK", "Alaska (AK)"), + ("AS", "American Samoa (AS)"), + ("AZ", "Arizona (AZ)"), + ("AR", "Arkansas (AR)"), + ("CA", "California (CA)"), + ("CO", "Colorado (CO)"), + ("CT", "Connecticut (CT)"), + ("DE", "Delaware (DE)"), + ("DC", "District of Columbia (DC)"), + ("FL", "Florida (FL)"), + ("GA", "Georgia (GA)"), + ("GU", "Guam (GU)"), + ("HI", "Hawaii (HI)"), + ("ID", "Idaho (ID)"), + ("IL", "Illinois (IL)"), + ("IN", "Indiana (IN)"), + ("IA", "Iowa (IA)"), + ("KS", "Kansas (KS)"), + ("KY", "Kentucky (KY)"), + ("LA", "Louisiana (LA)"), + ("ME", "Maine (ME)"), + ("MD", "Maryland (MD)"), + ("MA", "Massachusetts (MA)"), + ("MI", "Michigan (MI)"), + ("MN", "Minnesota (MN)"), + ("MS", "Mississippi (MS)"), + ("MO", "Missouri (MO)"), + ("MT", "Montana (MT)"), + ("NE", "Nebraska (NE)"), + ("NV", "Nevada (NV)"), + ("NH", "New Hampshire (NH)"), + ("NJ", "New Jersey (NJ)"), + ("NM", "New Mexico (NM)"), + ("NY", "New York (NY)"), + ("NC", "North Carolina (NC)"), + ("ND", "North Dakota (ND)"), + ("MP", "Northern Mariana Islands (MP)"), + ("OH", "Ohio (OH)"), + ("OK", "Oklahoma (OK)"), + ("OR", "Oregon (OR)"), + ("PA", "Pennsylvania (PA)"), + ("PR", "Puerto Rico (PR)"), + ("RI", "Rhode Island (RI)"), + ("SC", "South Carolina (SC)"), + ("SD", "South Dakota (SD)"), + ("TN", "Tennessee (TN)"), + ("TX", "Texas (TX)"), + ("UM", "United States Minor Outlying Islands (UM)"), + ("UT", "Utah (UT)"), + ("VT", "Vermont (VT)"), + ("VI", "Virgin Islands (VI)"), + ("VA", "Virginia (VA)"), + ("WA", "Washington (WA)"), + ("WV", "West Virginia (WV)"), + ("WI", "Wisconsin (WI)"), + ("WY", "Wyoming (WY)"), + ("AA", "Armed Forces Americas (AA)"), + ("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"), + ("AP", "Armed Forces Pacific (AP)"), + ], + max_length=2, + null=True, + verbose_name="state, territory, or military post", + ), + ), + ] diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index fb61e93e5..c19addd61 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -344,6 +344,24 @@ class DomainRequest(TimeStampedModel): verbose_name="Suborganization", ) + requested_suborganization = models.CharField( + null=True, + blank=True, + ) + + suborganization_city = models.CharField( + null=True, + blank=True, + ) + + suborganization_state_territory = models.CharField( + max_length=2, + choices=StateTerritoryChoices.choices, + null=True, + blank=True, + verbose_name="state, territory, or military post", + ) + # This is the domain request user who created this domain request. creator = models.ForeignKey( "registrar.User", @@ -1104,6 +1122,15 @@ class DomainRequest(TimeStampedModel): self.creator.restrict_user() + # Form unlocking steps + # These methods control the conditions in which we should unlock certain domain wizard steps. + def unlock_requesting_entity(self) -> bool: + """Unlocks the requesting entity step """ + if self.portfolio and self.organization_name == self.portfolio.organization_name: + return True + else: + return self.is_suborganization() + # ## Form policies ### # # These methods control what questions need to be answered by applicants @@ -1174,14 +1201,23 @@ class DomainRequest(TimeStampedModel): return False def is_suborganization(self) -> bool: + """Determines if this record is a suborganization or not""" if self.portfolio: if self.sub_organization: return True - if self.organization_name != self.portfolio.organization_name: + if self.has_information_required_to_make_suborganization(): return True - return False + + def has_information_required_to_make_suborganization(self): + """Checks if we have all the information we need to create a new suborganization object. + Checks for a the existence of requested_suborganization, suborganization_city, suborganization_state_territory""" + return ( + self.requested_domain and + self.suborganization_city and + self.suborganization_state_territory + ) def to_dict(self): """This is to process to_dict for Domain Information, making it friendly diff --git a/src/registrar/models/suborganization.py b/src/registrar/models/suborganization.py index 6ad80fdc0..0b1c6e0ac 100644 --- a/src/registrar/models/suborganization.py +++ b/src/registrar/models/suborganization.py @@ -1,4 +1,5 @@ from django.db import models +from registrar.models import DomainRequest from .utility.time_stamped_model import TimeStampedModel diff --git a/src/registrar/templates/domain_request_requesting_entity.html b/src/registrar/templates/domain_request_requesting_entity.html index ab7b2337b..144ab77bf 100644 --- a/src/registrar/templates/domain_request_requesting_entity.html +++ b/src/registrar/templates/domain_request_requesting_entity.html @@ -11,13 +11,38 @@ - {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} - {% input_with_errors forms.0.is_suborganization %} - {% endwith %} + {# forms.0 is a small yes/no form that toggles the visibility of "requesting entity" formset #} - {% input_with_errors forms.1.sub_organization %} - {% input_with_errors forms.1.organization_name %} - {% input_with_errors forms.1.city %} - {% input_with_errors forms.1.state_territory %} + {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} + {% with attr_required=True %} + {% input_with_errors forms.0.is_suborganization %} + {% endwith %} + {% endwith %} + +
+ This information will be published in .gov’s public data. If you don’t see your suborganization in the list, + select “other” and enter the name or your suborganization. +
+ {% with attr_required=True %} + {% input_with_errors forms.1.sub_organization %} + {% endwith %} + + {% comment %} This will be toggled if a special value, "other", is selected. + Otherwise this field is invisible. + {% endcomment %} +