Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4434 implementing audit deletion workflow #4554

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
#4434 Added logic to allow users to remove in progress reports from s…
…ubmission list
sambodeme committed Dec 17, 2024
commit f247ffbdb15cd31a3e06efba731e0fe3576cd06c
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
@@ -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
@@ -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"),
7 changes: 7 additions & 0 deletions backend/audit/models/submission_event.py
Original file line number Diff line number Diff line change
@@ -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")),
@@ -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)
56 changes: 56 additions & 0 deletions backend/audit/models/viewflow.py
Original file line number Diff line number Diff line change
@@ -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).
@@ -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(
@@ -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,
)
16 changes: 16 additions & 0 deletions backend/audit/templates/audit/my_submissions.html
Original file line number Diff line number Diff line change
@@ -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>
@@ -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>
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
@@ -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(),
2 changes: 2 additions & 0 deletions backend/audit/views/__init__.py
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
PredisseminationPdfDownloadView,
PredisseminationSummaryReportDownloadView,
)
from .remove_submission_in_progress import RemoveSubmissionView
from .submission_progress_view import ( # noqa
SubmissionProgressView,
submission_progress_check,
@@ -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)