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
Show file tree
Hide file tree
Changes from all commits
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
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
Loading