diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 64839da31..c4b6b7ad8 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -271,6 +271,8 @@ class DomainApplicationAdmin(ListHeaderAdmin): original_obj.approve(updated_domain_application=obj) elif obj.status == models.DomainApplication.WITHDRAWN: original_obj.withdraw() + elif obj.status == models.DomainApplication.REJECTED: + original_obj.reject() else: logger.warning("Unknown status selected in django admin") diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 76e227447..b869173c9 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -25,6 +25,7 @@ class DomainApplication(TimeStampedModel): ACTION_NEEDED = "action needed" APPROVED = "approved" WITHDRAWN = "withdrawn" + REJECTED = "rejected" STATUS_CHOICES = [ (STARTED, STARTED), (SUBMITTED, SUBMITTED), @@ -32,6 +33,7 @@ class DomainApplication(TimeStampedModel): (ACTION_NEEDED, ACTION_NEEDED), (APPROVED, APPROVED), (WITHDRAWN, WITHDRAWN), + (REJECTED, REJECTED), ] class StateTerritoryChoices(models.TextChoices): @@ -557,7 +559,7 @@ class DomainApplication(TimeStampedModel): "emails/status_change_in_review_subject.txt", ) - @transition(field="status", source=[INVESTIGATING], target=ACTION_NEEDED) + @transition(field="status", source=[INVESTIGATING, REJECTED], target=ACTION_NEEDED) def action_needed(self, updated_domain_application): """Send back an application that is under investigation or rejected. @@ -569,7 +571,7 @@ class DomainApplication(TimeStampedModel): "emails/status_change_action_needed_subject.txt", ) - @transition(field="status", source=[SUBMITTED, INVESTIGATING], target=APPROVED) + @transition(field="status", source=[SUBMITTED, INVESTIGATING, REJECTED], target=APPROVED) def approve(self, updated_domain_application=None): """Approve an application that has been submitted. @@ -618,6 +620,10 @@ class DomainApplication(TimeStampedModel): @transition(field="status", source=[SUBMITTED, INVESTIGATING], target=WITHDRAWN) def withdraw(self): """Withdraw an application that has been submitted.""" + + @transition(field="status", source=[INVESTIGATING, APPROVED], target=REJECTED) + def reject(self): + """Reject an application that has been submitted.""" # ## Form policies ### # diff --git a/src/registrar/templates/application_status.html b/src/registrar/templates/application_status.html index c0904373f..6d635a05a 100644 --- a/src/registrar/templates/application_status.html +++ b/src/registrar/templates/application_status.html @@ -21,7 +21,7 @@ {% if domainapplication.status == 'approved' %} Approved {% elif domainapplication.status == 'investigating' %} In Review - {% elif domainapplication.status == 'action needed' %} Action Needed + {% elif domainapplication.status == 'rejected' %} Rejected {% elif domainapplication.status == 'submitted' %} Received {% else %}ERROR Please contact technical support/dev {% endif %} diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt new file mode 100644 index 000000000..772050a7b --- /dev/null +++ b/src/registrar/templates/emails/status_change_rejected.txt @@ -0,0 +1,32 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +Hi {{ application.submitter.first_name }}. + +Your .gov domain request has been rejected. + +DOMAIN REQUESTED: {{ application.requested_domain.name }} +REQUEST RECEIVED ON: {{ application.updated_at|date }} +REQUEST #: {{ application.id }} +STATUS: Rejected + + +YOU CAN SUBMIT A NEW REQUEST + +The details of your request are included below. If your organization is eligible for a .gov +domain and you meet our other requirements, you can submit a new request. Learn +more about .gov domains . + + +THANK YOU + +.Gov helps the public identify official, trusted information. Thank you for +requesting a .gov domain. + +---------------------------------------------------------------- + +{% include 'emails/includes/application_summary.txt' %} +---------------------------------------------------------------- + +The .gov team +Contact us: +Visit +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/status_change_rejected_subject.txt b/src/registrar/templates/emails/status_change_rejected_subject.txt new file mode 100644 index 000000000..a15cc1575 --- /dev/null +++ b/src/registrar/templates/emails/status_change_rejected_subject.txt @@ -0,0 +1 @@ +Your .gov domain request has been rejected \ No newline at end of file diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 59a1d9ff7..0819bb72b 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -161,6 +161,15 @@ class TestDomainApplication(TestCase): with self.assertRaises(TransitionNotAllowed): application.submit() + + def test_transition_not_allowed_rejected_submitted(self): + """Create an application with status rejected and call submit + against transition rules""" + + application = completed_application(status=DomainApplication.REJECTED) + + with self.assertRaises(TransitionNotAllowed): + application.submit() def test_transition_not_allowed_started_investigating(self): """Create an application with status started and call in_review @@ -188,6 +197,24 @@ class TestDomainApplication(TestCase): with self.assertRaises(TransitionNotAllowed): application.in_review() + + def test_transition_not_allowed_action_needed_investigating(self): + """Create an application with status action needed and call in_review + against transition rules""" + + application = completed_application(status=DomainApplication.ACTION_NEEDED) + + with self.assertRaises(TransitionNotAllowed): + application.in_review() + + def test_transition_not_allowed_rejected_investigating(self): + """Create an application with status rejected and call in_review + against transition rules""" + + application = completed_application(status=DomainApplication.REJECTED) + + with self.assertRaises(TransitionNotAllowed): + application.in_review() def test_transition_not_allowed_withdrawn_investigating(self): """Create an application with status withdrawn and call in_review @@ -260,6 +287,15 @@ class TestDomainApplication(TestCase): with self.assertRaises(TransitionNotAllowed): application.approve() + + def test_transition_not_allowed_action_needed_approved(self): + """Create an application with status action needed and call approve + against transition rules""" + + application = completed_application(status=DomainApplication.ACTION_NEEDED) + + with self.assertRaises(TransitionNotAllowed): + application.approve() def test_transition_not_allowed_withdrawn_approved(self): """Create an application with status withdrawn and call approve @@ -287,7 +323,25 @@ class TestDomainApplication(TestCase): with self.assertRaises(TransitionNotAllowed): application.withdraw() + + def test_transition_not_allowed_action_needed_withdrawn(self): + """Create an application with status action needed and call withdraw + against transition rules""" + application = completed_application(status=DomainApplication.ACTION_NEEDED) + + with self.assertRaises(TransitionNotAllowed): + application.withdraw() + + def test_transition_not_allowed_rejected_withdrawn(self): + """Create an application with status rejected and call withdraw + against transition rules""" + + application = completed_application(status=DomainApplication.REJECTED) + + with self.assertRaises(TransitionNotAllowed): + application.withdraw() + def test_transition_not_allowed_withdrawn_withdrawn(self): """Create an application with status withdrawn and call withdraw against transition rules""" @@ -296,6 +350,51 @@ class TestDomainApplication(TestCase): with self.assertRaises(TransitionNotAllowed): application.withdraw() + + def test_transition_not_allowed_started_rejected(self): + """Create an application with status started and call reject + against transition rules""" + + application = completed_application(status=DomainApplication.STARTED) + + with self.assertRaises(TransitionNotAllowed): + application.reject() + + def test_transition_not_allowed_submitted_rejected(self): + """Create an application with status submitted and call reject + against transition rules""" + + application = completed_application(status=DomainApplication.SUBMITTED) + + with self.assertRaises(TransitionNotAllowed): + application.reject() + + def test_transition_not_allowed_action_needed_rejected(self): + """Create an application with status action needed and call reject + against transition rules""" + + application = completed_application(status=DomainApplication.ACTION_NEEDED) + + with self.assertRaises(TransitionNotAllowed): + application.reject() + + def test_transition_not_allowed_withdrawn_rejected(self): + """Create an application with status withdrawn and call reject + against transition rules""" + + application = completed_application(status=DomainApplication.WITHDRAWN) + + with self.assertRaises(TransitionNotAllowed): + application.reject() + + def test_transition_not_allowed_rejected_rejected(self): + """Create an application with status rejected and call reject + against transition rules""" + + application = completed_application(status=DomainApplication.REJECTED) + + with self.assertRaises(TransitionNotAllowed): + application.reject() class TestPermissions(TestCase):