Skip to content

Commit

Permalink
#4434 Added logic to allow users to remove in progress reports from s…
Browse files Browse the repository at this point in the history
…ubmission list
  • Loading branch information
sambodeme committed Dec 17, 2024
1 parent 73dd827 commit f247ffb
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Generated by Django 5.1.2 on 2024-12-17 16:56

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("audit", "0014_alter_sacvalidationwaiver_waiver_types"),
]

operations = [
migrations.AlterField(
model_name="singleauditchecklist",
name="submission_status",
field=models.CharField(
choices=[
("in_progress", "In Progress"),
("flagged_for_removal", "Flagged for Removal"),
("ready_for_certification", "Ready for Certification"),
("auditor_certified", "Auditor Certified"),
("auditee_certified", "Auditee Certified"),
("certified", "Certified"),
("submitted", "Submitted"),
("disseminated", "Disseminated"),
],
default="in_progress",
),
),
migrations.AlterField(
model_name="singleauditchecklist",
name="transition_name",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(
choices=[
("in_progress", "In Progress"),
("flagged_for_removal", "Flagged for Removal"),
("ready_for_certification", "Ready for Certification"),
("auditor_certified", "Auditor Certified"),
("auditee_certified", "Auditee Certified"),
("certified", "Certified"),
("submitted", "Submitted"),
("disseminated", "Disseminated"),
],
max_length=40,
),
blank=True,
default=list,
null=True,
size=None,
),
),
migrations.AlterField(
model_name="submissionevent",
name="event",
field=models.CharField(
choices=[
("access-granted", "Access granted"),
("additional-eins-updated", "Additional EINs updated"),
("additional-eins-deleted", "Additional EINs deleted"),
("additional-ueis-updated", "Additional UEIs updated"),
("additional-ueis-deleted", "Additional UEIs deleted"),
("audit-information-updated", "Audit information updated"),
("audit-report-pdf-updated", "Audit report PDF updated"),
(
"auditee-certification-completed",
"Auditee certification completed",
),
(
"auditor-certification-completed",
"Auditor certification completed",
),
(
"corrective-action-plan-updated",
"Corrective action plan updated",
),
(
"corrective-action-plan-deleted",
"Corrective action plan deleted",
),
("created", "Created"),
("federal-awards-updated", "Federal awards updated"),
(
"federal-awards-audit-findings-updated",
"Federal awards audit findings updated",
),
(
"federal-awards-audit-findings-deleted",
"Federal awards audit findings deleted",
),
(
"federal-awards-audit-findings-text-updated",
"Federal awards audit findings text updated",
),
(
"federal-awards-audit-findings-text-deleted",
"Federal awards audit findings text deleted",
),
(
"findings-uniform-guidance-updated",
"Findings uniform guidance updated",
),
(
"findings-uniform-guidance-deleted",
"Findings uniform guidance deleted",
),
("general-information-updated", "General information updated"),
("locked-for-certification", "Locked for certification"),
("unlocked-after-certification", "Unlocked after certification"),
("notes-to-sefa-updated", "Notes to SEFA updated"),
("secondary-auditors-updated", "Secondary auditors updated"),
("secondary-auditors-deleted", "Secondary auditors deleted"),
("submitted", "Submitted to the FAC for processing"),
("disseminated", "Copied to dissemination tables"),
("tribal-consent-updated", "Tribal audit consent updated"),
(
"flagged-submission-for-removal",
"Flagged submission for removal",
),
("cancel-removal-flag", "Cancel removal flag"),
]
),
),
]
2 changes: 2 additions & 0 deletions backend/audit/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class STATUS:
CERTIFIED = "certified"
SUBMITTED = "submitted"
DISSEMINATED = "disseminated"
FLAGGED_FOR_REMOVAL = "flagged_for_removal"


class SingleAuditChecklist(models.Model, GeneralInformationMixin): # type: ignore
Expand Down Expand Up @@ -303,6 +304,7 @@ def get_statuses(self) -> type[STATUS]:
# Constants:
STATUS_CHOICES = (
(STATUS.IN_PROGRESS, "In Progress"),
(STATUS.FLAGGED_FOR_REMOVAL, "Flagged for Removal"),
(STATUS.READY_FOR_CERTIFICATION, "Ready for Certification"),
(STATUS.AUDITOR_CERTIFIED, "Auditor Certified"),
(STATUS.AUDITEE_CERTIFIED, "Auditee Certified"),
Expand Down
7 changes: 7 additions & 0 deletions backend/audit/models/submission_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class EventType:
SUBMITTED = "submitted"
DISSEMINATED = "disseminated"
TRIBAL_CONSENT_UPDATED = "tribal-consent-updated"
FLAGGED_SUBMISSION_FOR_REMOVAL = "flagged-submission-for-removal"
CANCEL_REMOVAL_FLAG = "cancel-removal-flag"

EVENT_TYPES = (
(EventType.ACCESS_GRANTED, _("Access granted")),
Expand Down Expand Up @@ -96,6 +98,11 @@ class EventType:
(EventType.SUBMITTED, _("Submitted to the FAC for processing")),
(EventType.DISSEMINATED, _("Copied to dissemination tables")),
(EventType.TRIBAL_CONSENT_UPDATED, _("Tribal audit consent updated")),
(
EventType.FLAGGED_SUBMISSION_FOR_REMOVAL,
_("Flagged submission for removal"),
),
(EventType.CANCEL_REMOVAL_FLAG, _("Cancel removal flag")),
)

sac = models.ForeignKey("audit.SingleAuditChecklist", on_delete=models.CASCADE)
Expand Down
56 changes: 56 additions & 0 deletions backend/audit/models/viewflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,41 @@ def sac_revert_from_submitted(sac):
return False


def sac_revert_from_flagged_for_removal(sac, user):
"""
Transitions the submission_state for a SingleAuditChecklist back
to "in_progress" so the user can continue working on it.
This should be accessible to django admin.
"""
if sac.submission_status == STATUS.FLAGGED_FOR_REMOVAL:
flow = SingleAuditChecklistFlow(sac)

flow.transition_to_in_progress_again()

with CurationTracking():
sac.save(
event_user=user,
event_type=SubmissionEvent.EventType.CANCEL_REMOVAL_FLAG,
)


def sac_flag_for_removal(sac, user):
"""
Transitions the submission_state for a SingleAuditChecklist to "flagged_for_removal".
This should be accessible to django admin.
"""
if sac.submission_status == STATUS.IN_PROGRESS:
flow = SingleAuditChecklistFlow(sac)

flow.transition_to_flagged_for_removal()

with CurationTracking():
sac.save(
event_user=user,
event_type=SubmissionEvent.EventType.FLAGGED_SUBMISSION_FOR_REMOVAL,
)


def sac_transition(request, sac, **kwargs):
"""
Transitions the submission_state for a SingleAuditChecklist (sac).
Expand All @@ -54,6 +89,14 @@ def sac_transition(request, sac, **kwargs):
)
return True

elif target == STATUS.FLAGGED_FOR_REMOVAL:
flow.transition_to_flagged_for_removal()
sac.save(
event_user=user,
event_type=SubmissionEvent.EventType.FLAGGED_SUBMISSION_FOR_REMOVAL,
)
return True

elif target == STATUS.READY_FOR_CERTIFICATION:
flow.transition_to_ready_for_certification()
sac.save(
Expand Down Expand Up @@ -127,11 +170,24 @@ def transition_to_ready_for_certification(self):
self.sac.transition_name.append(STATUS.READY_FOR_CERTIFICATION)
self.sac.transition_date.append(datetime.datetime.now(datetime.timezone.utc))

@state.transition(
source=STATUS.IN_PROGRESS,
target=STATUS.FLAGGED_FOR_REMOVAL,
)
def transition_to_flagged_for_removal(self):
"""
The permission checks verifying that the user attempting to do this has
the appropriate privileges will be done at the view level.
"""
self.sac.transition_name.append(STATUS.FLAGGED_FOR_REMOVAL)
self.sac.transition_date.append(datetime.datetime.now(datetime.timezone.utc))

@state.transition(
source=[
STATUS.READY_FOR_CERTIFICATION,
STATUS.AUDITOR_CERTIFIED,
STATUS.AUDITEE_CERTIFIED,
STATUS.FLAGGED_FOR_REMOVAL,
],
target=STATUS.IN_PROGRESS,
)
Expand Down
16 changes: 16 additions & 0 deletions backend/audit/templates/audit/my_submissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ <h1 class="font-sans-xl">Audits in progress</h1>
data-open-modal>UEI</button>
</th>
<th data-sortable scope="col" role="columnheader">Fiscal period end date</th>
<th scope="col" role="columnheader">User Access</th>
<th scope="col" role="columnheader">Delete</th>
</tr>
</thead>
<tbody>
Expand All @@ -46,6 +48,20 @@ <h1 class="font-sans-xl">Audits in progress</h1>
<td>{{ item.report_id }}</td>
<td>{{ item.auditee_uei }}</td>
<td>{{ item.fiscal_year_end_date }}</td>
<td class="text-center">
<a href="{% url 'audit:ManageSubmission' report_id=item.report_id %}" title="Manage report access">
<svg class="usa-icon usa-icon--size-3 text-blue" aria-hidden="true" focusable="false" role="img">
{% uswds_sprite "person_add" %}
</svg>
</a>
</td>
<td class="text-center">
<a href="{% url 'audit:RemoveSubmissionInProgress' report_id=item.report_id %}" title="Delete audit">
<svg class="usa-icon usa-icon--size-3 text-red" aria-hidden="true" focusable="false" role="img">
{% uswds_sprite "delete" %}
</svg>
</a>
</td>
</tr>
{% endfor %}
</tbody>
Expand Down
29 changes: 29 additions & 0 deletions backend/audit/templates/audit/remove-submission-in-progress.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% load static %}
{% load sprite_helper %}

{% block content %}
<div class="grid-container margin-y-8">
<h1 class="font-sans-xl">Confirm Report Removal</h1>
<p class="text-base-darker margin-bottom-2">Are you sure you want to remove the following report?</p>

<div class="usa-card">
<div class="usa-card__body">
<ul class="usa-list usa-list--unstyled">
<li><strong>Auditee UEI:</strong> {{ auditee_uei }}</li>
<li><strong>Auditee Name:</strong> {{ auditee_name }}</li>
<li><strong>Report ID:</strong> {{ report_id }}</li>
<li><strong>Fiscal Period End Date:</strong> {{ fiscal_year_end_date }}</li>
</ul>
</div>
</div>

<form method="post" class="margin-top-6">
{% csrf_token %}
<div class="usa-button-group">
<button type="submit" class="usa-button usa-button--danger">Remove Report</button>
<a href="{% url 'audit:MySubmissions' %}" class="usa-button usa-button--unstyled text-bold">Return to Submissions</a>
</div>
</form>
</div>
{% endblock content %}
5 changes: 5 additions & 0 deletions backend/audit/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def camel_to_hyphen(raw: str) -> str:
views.RemoveEditorView.as_view(),
name="RemoveEditorView",
),
path(
"manage-submission/remove-report/<str:report_id>",
views.RemoveSubmissionView.as_view(),
name="RemoveSubmissionInProgress",
),
path(
"workbook/xlsx/<str:file_type>/<str:report_id>",
views.PredisseminationXlsxDownloadView.as_view(),
Expand Down
2 changes: 2 additions & 0 deletions backend/audit/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PredisseminationPdfDownloadView,
PredisseminationSummaryReportDownloadView,
)
from .remove_submission_in_progress import RemoveSubmissionView
from .submission_progress_view import ( # noqa
SubmissionProgressView,
submission_progress_check,
Expand Down Expand Up @@ -67,4 +68,5 @@
TribalDataConsent,
UnlockAfterCertificationView,
UploadReportView,
RemoveSubmissionView,
]
64 changes: 64 additions & 0 deletions backend/audit/views/remove_submission_in_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging
from django.shortcuts import redirect, render, reverse
from django.views import generic
from django.core.exceptions import PermissionDenied

from audit.mixins import (
SingleAuditChecklistAccessRequiredMixin,
)
from audit.models import (
Access,
SingleAuditChecklist,
)
from audit.views.views import verify_status
from audit.models.models import STATUS
from audit.models.viewflow import SingleAuditChecklistFlow

logger = logging.getLogger(__name__)


class RemoveSubmissionView(SingleAuditChecklistAccessRequiredMixin, generic.View):
"""
View for removing an audit.
"""

template = "audit/remove-submission-in-progress.html"

@verify_status(STATUS.IN_PROGRESS)
def get(self, request, *args, **kwargs):
"""
Show the audit to be removed and confirmation form.
"""
report_id = kwargs["report_id"]
sac = SingleAuditChecklist.objects.get(report_id=report_id)

if not Access.objects.filter(
email=request.user.email, sac=sac, role="editor"
).exists():
raise PermissionDenied("Only authorized auditors can remove audits.")

context = {
"auditee_uei": sac.general_information["auditee_uei"],
"auditee_name": sac.general_information.get("auditee_name"),
"report_id": sac.report_id,
"fiscal_year_end_date": sac.general_information.get(
"auditee_fiscal_period_end"
),
}

return render(request, self.template, context)

@verify_status(STATUS.IN_PROGRESS)
def post(self, request, *args, **kwargs):
"""
Remove the audit and redirect to the audits list.
"""
report_id = kwargs["report_id"]
sac = SingleAuditChecklist.objects.get(report_id=report_id)

flow = SingleAuditChecklistFlow(sac)
flow.transition_to_flagged_for_removal()
sac.save()
url = reverse("audit:MySubmissions")

return redirect(url)

0 comments on commit f247ffb

Please sign in to comment.