Skip to content

Commit

Permalink
4434 implementing audit deletion workflow (#4554)
Browse files Browse the repository at this point in the history
* #4434 Added logic to allow users to remove in progress reports from submission list

* Added django admin logic to allow staff members to revert reports back to in progress once flagged for removal

* #4434 Added link to return submission list from access management UI

* Fixed linting

* #4434 and #3346  Bug fix
  • Loading branch information
sambodeme authored Dec 20, 2024
1 parent 4e3fc9e commit dfd3a10
Show file tree
Hide file tree
Showing 14 changed files with 463 additions and 4 deletions.
78 changes: 77 additions & 1 deletion backend/audit/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
UeiValidationWaiver,
)
from audit.models.models import STATUS
from audit.models.viewflow import sac_transition
from audit.models.viewflow import (
sac_flag_for_removal,
sac_revert_from_flagged_for_removal,
sac_transition,
)
from audit.validators import (
validate_auditee_certification_json,
validate_auditor_certification_json,
Expand All @@ -24,6 +28,76 @@
logger = logging.getLogger(__name__)


@admin.action(description="Revert selected report(s) to In Progress")
def revert_to_in_progress(modeladmin, request, queryset):
successful_reverts = []
errors = []

for sac in queryset:
if sac.submission_status == STATUS.FLAGGED_FOR_REMOVAL:
try:
sac_revert_from_flagged_for_removal(sac, request.user)
sac.save()
successful_reverts.append(sac.report_id)
except Exception as e:
modeladmin.message_user(
request,
f"Error reverting {sac.report_id}: {str(e)}",
level=messages.ERROR,
)
errors.append(sac.report_id)
else:
modeladmin.message_user(
request,
f"Report {sac.report_id} is not flagged for removal.",
level=messages.WARNING,
)
errors.append(sac.report_id)

if successful_reverts:
modeladmin.message_user(
request,
f"Successfully reverted report(s) ({', '.join(successful_reverts)}) back to In Progress.",
level=messages.SUCCESS,
)

if errors:
modeladmin.message_user(
request,
f"Unable to revert report(s) ({', '.join(errors)}) back to In Progress.",
level=messages.ERROR,
)


@admin.action(description="Flag selected report(s) for removal")
def flag_for_removal(modeladmin, request, queryset):

flagged = []
already_flagged = []

for sac in queryset:
if sac.submission_status != STATUS.FLAGGED_FOR_REMOVAL:
sac_flag_for_removal(sac, request.user)
sac.save()
flagged.append(sac.report_id)
else:
already_flagged.append(sac.report_id)

if flagged:
modeladmin.message_user(
request,
f"Successfully flagged report(s) ({', '.join(flagged)}) for removal.",
level=messages.SUCCESS,
)

if already_flagged:
modeladmin.message_user(
request,
f"Report(s) ({', '.join(already_flagged)}) were already flagged.",
level=messages.WARNING,
)


class SACAdmin(admin.ModelAdmin):
"""
Support for read-only staff access, and control of what fields are present and
Expand All @@ -41,6 +115,7 @@ def has_view_permission(self, request, obj=None):
"report_id",
"cognizant_agency",
"oversight_agency",
"submission_status",
)
list_filter = [
"cognizant_agency",
Expand All @@ -50,6 +125,7 @@ def has_view_permission(self, request, obj=None):
]
readonly_fields = ("submitted_by",)
search_fields = ("general_information__auditee_uei", "report_id")
actions = [revert_to_in_progress, flag_for_removal]


class AccessAdmin(admin.ModelAdmin):
Expand Down
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
2 changes: 2 additions & 0 deletions backend/audit/templates/audit/manage-submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ <h1 class="usa-legend usa-legend--large font-sans-2xl">Manage user roles</h1>
href="{{ add_editor_url }}">Add editor</a>
<a class="usa-button usa-button--unstyled margin-left-2"
href="{{ progress_url }}">Return to checklist</a>
<a class="usa-button usa-button--unstyled margin-left-2"
href="{% url 'audit:MySubmissions' %}">Return to Submissions</a>
</div>
</div>
{% include "audit-metadata.html" %}
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
Loading

0 comments on commit dfd3a10

Please sign in to comment.