diff --git a/hypha/apply/activity/adapters/activity_feed.py b/hypha/apply/activity/adapters/activity_feed.py index d9db2b8513..b088485f4b 100644 --- a/hypha/apply/activity/adapters/activity_feed.py +++ b/hypha/apply/activity/adapters/activity_feed.py @@ -46,6 +46,7 @@ class ActivityAdapter(AdapterBase): MESSAGES.SKIPPED_REPORT: 'handle_skipped_report', MESSAGES.REPORT_FREQUENCY_CHANGED: 'handle_report_frequency', MESSAGES.BATCH_DELETE_SUBMISSION: 'handle_batch_delete_submission', + MESSAGES.BATCH_ARCHIVE_SUBMISSION: 'handle_batch_archive_submission', } def recipients(self, message_type, **kwargs): @@ -111,6 +112,13 @@ def handle_batch_delete_submission(self, sources, **kwargs): title=submissions_text ) + def handle_batch_archive_submission(self, sources, **kwargs): + submissions = sources + submissions_text = ', '.join([submission.title for submission in submissions]) + return _('Successfully archived submissions: {title}').format( + title=submissions_text + ) + def handle_transition(self, old_phase, source, **kwargs): submission = source base_message = _('Progressed from {old_display} to {new_display}') diff --git a/hypha/apply/activity/adapters/slack.py b/hypha/apply/activity/adapters/slack.py index c35d13a900..5882634878 100644 --- a/hypha/apply/activity/adapters/slack.py +++ b/hypha/apply/activity/adapters/slack.py @@ -69,6 +69,7 @@ class SlackAdapter(AdapterBase): MESSAGES.BATCH_DELETE_SUBMISSION: 'handle_batch_delete_submission', MESSAGES.STAFF_ACCOUNT_CREATED: _('{user} has created a new account for <{link}|{source}>'), MESSAGES.STAFF_ACCOUNT_EDITED: _('{user} has edited account for <{link}|{source}> that now has following roles: {roles}'), + MESSAGES.BATCH_ARCHIVE_SUBMISSION: 'handle_batch_archive_submission', } def __init__(self): @@ -276,6 +277,13 @@ def handle_batch_delete_submission(self, sources, links, user, **kwargs): user=user, title=submissions_text ) + def handle_batch_archive_submission(self, sources, links, user, **kwargs): + submissions = sources + submissions_text = ', '.join([submission.title for submission in submissions]) + return _('{user} has archived submissions: {title}').format( + user=user, title=submissions_text + ) + def notify_reviewers(self, source, link, **kwargs): submission = source reviewers_to_notify = [] diff --git a/hypha/apply/activity/migrations/0066_alter_event_type.py b/hypha/apply/activity/migrations/0066_alter_event_type.py new file mode 100644 index 0000000000..30c955eaf7 --- /dev/null +++ b/hypha/apply/activity/migrations/0066_alter_event_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2022-10-19 04:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('activity', '0065_auto_20221006_1115'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='type', + field=models.CharField(choices=[('UPDATE_LEAD', 'updated lead'), ('BATCH_UPDATE_LEAD', 'batch updated lead'), ('EDIT_SUBMISSION', 'edited submission'), ('APPLICANT_EDIT', 'edited applicant'), ('NEW_SUBMISSION', 'submitted new submission'), ('SCREENING', 'screened'), ('TRANSITION', 'transitioned'), ('BATCH_TRANSITION', 'batch transitioned'), ('DETERMINATION_OUTCOME', 'sent determination outcome'), ('BATCH_DETERMINATION_OUTCOME', 'sent batch determination outcome'), ('INVITED_TO_PROPOSAL', 'invited to proposal'), ('REVIEWERS_UPDATED', 'updated reviewers'), ('BATCH_REVIEWERS_UPDATED', 'batch updated reviewers'), ('PARTNERS_UPDATED', 'updated partners'), ('PARTNERS_UPDATED_PARTNER', 'partners updated partner'), ('READY_FOR_REVIEW', 'marked ready for review'), ('BATCH_READY_FOR_REVIEW', 'marked batch ready for review'), ('NEW_REVIEW', 'added new review'), ('COMMENT', 'added comment'), ('PROPOSAL_SUBMITTED', 'submitted proposal'), ('OPENED_SEALED', 'opened sealed submission'), ('REVIEW_OPINION', 'reviewed opinion'), ('DELETE_SUBMISSION', 'deleted submission'), ('DELETE_REVIEW', 'deleted review'), ('CREATED_PROJECT', 'created project'), ('UPDATED_VENDOR', 'updated contracting information'), ('UPDATE_PROJECT_LEAD', 'updated project lead'), ('EDIT_REVIEW', 'edited review'), ('SEND_FOR_APPROVAL', 'sent for approval'), ('APPROVE_PROJECT', 'approved project'), ('PROJECT_FINAL_APPROVAL', 'sent project for final approval'), ('PROJECT_TRANSITION', 'transitioned project'), ('REQUEST_PROJECT_CHANGE', 'requested project change'), ('UPLOAD_DOCUMENT', 'uploaded document to project'), ('REMOVE_DOCUMENT', 'removed document from project'), ('UPLOAD_CONTRACT', 'uploaded contract to project'), ('APPROVE_CONTRACT', 'approved contract'), ('CREATE_INVOICE', 'created invoice for project'), ('UPDATE_INVOICE_STATUS', 'updated invoice status'), ('DELETE_INVOICE', 'deleted invoice'), ('SENT_TO_COMPLIANCE', 'sent project to compliance'), ('UPDATE_INVOICE', 'updated invoice'), ('SUBMIT_REPORT', 'submitted report'), ('SKIPPED_REPORT', 'skipped report'), ('REPORT_FREQUENCY_CHANGED', 'changed report frequency'), ('REPORT_NOTIFY', 'notified report'), ('CREATE_REMINDER', 'created reminder'), ('DELETE_REMINDER', 'deleted reminder'), ('REVIEW_REMINDER', 'reminder to review'), ('BATCH_DELETE_SUBMISSION', 'batch deleted submissions'), ('BATCH_ARCHIVE_SUBMISSION', 'batch archive submissions'), ('STAFF_ACCOUNT_CREATED', 'created new account'), ('STAFF_ACCOUNT_EDITED', 'edited account')], max_length=50, verbose_name='verb'), + ), + ] diff --git a/hypha/apply/activity/options.py b/hypha/apply/activity/options.py index 8cbfdee53b..c03197e6e9 100644 --- a/hypha/apply/activity/options.py +++ b/hypha/apply/activity/options.py @@ -54,5 +54,6 @@ class MESSAGES(TextChoices): DELETE_REMINDER = 'DELETE_REMINDER', _('deleted reminder') REVIEW_REMINDER = 'REVIEW_REMINDER', _('reminder to review') BATCH_DELETE_SUBMISSION = 'BATCH_DELETE_SUBMISSION', _('batch deleted submissions') + BATCH_ARCHIVE_SUBMISSION = 'BATCH_ARCHIVE_SUBMISSION', _('batch archive submissions') STAFF_ACCOUNT_CREATED = 'STAFF_ACCOUNT_CREATED', _('created new account') STAFF_ACCOUNT_EDITED = 'STAFF_ACCOUNT_EDITED', _('edited account') diff --git a/hypha/apply/funds/forms.py b/hypha/apply/funds/forms.py index 99d6b18049..d48f3d3743 100644 --- a/hypha/apply/funds/forms.py +++ b/hypha/apply/funds/forms.py @@ -193,6 +193,24 @@ def save(self): return None +class BatchArchiveSubmissionForm(forms.Form): + submissions = forms.CharField(widget=forms.HiddenInput(attrs={'class': 'js-submissions-id'})) + + def __init__(self, *args, round=None, **kwargs): + self.user = kwargs.pop('user') + super().__init__(*args, **kwargs) + + def clean_submissions(self): + value = self.cleaned_data['submissions'] + submission_ids = [int(submission) for submission in value.split(',')] + return ApplicationSubmission.objects.filter(id__in=submission_ids) + + def save(self): + submissions = self.cleaned_data['submissions'] + submissions.update(is_archive=True) + return None + + class UpdateReviewersForm(ApplicationSubmissionModelForm): reviewer_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.reviewers().only('pk', 'full_name'), diff --git a/hypha/apply/funds/migrations/0106_applicationsubmission_is_archive.py b/hypha/apply/funds/migrations/0106_applicationsubmission_is_archive.py new file mode 100644 index 0000000000..bc9d299bae --- /dev/null +++ b/hypha/apply/funds/migrations/0106_applicationsubmission_is_archive.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2022-11-26 11:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0105_applicationbase_activity_digest_recipient_emails'), + ] + + operations = [ + migrations.AddField( + model_name='applicationsubmission', + name='is_archive', + field=models.BooleanField(default=False), + ), + ] diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py index 59412657d8..e8ac4616c2 100644 --- a/hypha/apply/funds/models/submissions.py +++ b/hypha/apply/funds/models/submissions.py @@ -109,10 +109,10 @@ class ApplicationSubmissionQueryset(JSONOrderable): json_field = 'form_data' def active(self): - return self.filter(status__in=active_statuses) + return self.filter(status__in=active_statuses, is_archive=False) def inactive(self): - return self.exclude(status__in=active_statuses) + return self.exclude(Q(status__in=active_statuses) | Q(is_archive=True)) def in_community_review(self, user): qs = self.filter( @@ -179,7 +179,14 @@ def undetermined(self): return self.exclude(pk__in=determined_submissions) def current(self): - # Applications which have the current stage active (have not been progressed) + # Applications which have the current stage active (have not been progressed) or not archived yet. + return self.exclude(Q(next__isnull=False) | Q(is_archive=True)) + + def archived(self): + return self.filter(is_archive=True) + + def include_archive(self): + # Show all submissions( current submissions + archived submissions) return self.exclude(next__isnull=False) def current_accepted(self): @@ -504,6 +511,9 @@ class ApplicationSubmission( # Meta: used for migration purposes only drupal_id = models.IntegerField(null=True, blank=True, editable=False) + # archive old submissions + is_archive = models.BooleanField(default=False) + objects = ApplicationSubmissionQueryset.as_manager() @property diff --git a/hypha/apply/funds/permissions.py b/hypha/apply/funds/permissions.py index 816e1cf0b1..7d0781fb3e 100644 --- a/hypha/apply/funds/permissions.py +++ b/hypha/apply/funds/permissions.py @@ -1,3 +1,6 @@ +from django.conf import settings + + def is_user_has_access_to_view_submission(user, submission): has_access = False @@ -14,3 +17,13 @@ def is_user_has_access_to_view_submission(user, submission): has_access = True return has_access + + +def is_user_has_access_to_view_archived_submissions(user): + if not user.is_authenticated: + pass + elif user.is_apply_staff and settings.SUBMISSIONS_ARCHIVED_ACCESS_STAFF: + return True + elif user.is_apply_staff_admin and settings.SUBMISSIONS_ARCHIVED_ACCESS_STAFF_ADMIN: + return True + return False diff --git a/hypha/apply/funds/tables.py b/hypha/apply/funds/tables.py index 12ac3aad25..5442512e33 100644 --- a/hypha/apply/funds/tables.py +++ b/hypha/apply/funds/tables.py @@ -84,6 +84,7 @@ class Meta: row_attrs = { 'class': make_row_class, 'data-record-id': lambda record: record.id, + 'data-archived': lambda record: record.is_archive } attrs = {'class': 'all-submissions-table'} empty_text = _('No submissions available') @@ -186,31 +187,60 @@ def get_used_rounds(request): return Round.objects.filter(submissions__isnull=False).distinct() +def get_used_rounds_from_dataset(dataset): + return Round.objects.filter(id__in=dataset.values('round')).distinct() + + def get_used_funds(request): # Use page to pick up on both Labs and Funds return Page.objects.filter(applicationsubmission__isnull=False).distinct() +def get_used_funds_from_dataset(dataset): + return Page.objects.filter(id__in=dataset.values('page')).distinct() + + def get_round_leads(request): return User.objects.filter(submission_lead__isnull=False).distinct() +def get_round_leads_from_dataset(dataset): + return User.objects.filter(id__in=dataset.values('lead')).distinct() + + def get_reviewers(request): """ All assigned reviewers, staff or admin """ return User.objects.filter(Q(submissions_reviewer__isnull=False) | Q(groups__name=STAFF_GROUP_NAME) | Q(is_superuser=True)).distinct() +def get_reviewers_from_dataset(dataset): + """ All assigned reviewers, not including Staff and Admin because we want a list of reviewers only""" + return User.objects.filter(id__in=dataset.values('reviewers')).distinct() + + def get_screening_statuses(request): return ScreeningStatus.objects.filter( id__in=ApplicationSubmission.objects.all().values('screening_statuses__id').distinct('screening_statuses__id')) +def get_screening_statuses_from_dataset(dataset): + return ScreeningStatus.objects.filter( + id__in=dataset.values('screening_statuses__id') + ).distinct() + + def get_meta_terms(request): return MetaTerm.objects.filter( filter_on_dashboard=True, id__in=ApplicationSubmission.objects.all().values('meta_terms__id').distinct('meta_terms__id')) +def get_meta_terms_from_dataset(dataset): + return MetaTerm.objects.filter( + filter_on_dashboard=True, + id__in=dataset.values('meta_terms__id')).distinct() + + class Select2CheckboxWidgetMixin(filters.Filter): def __init__(self, *args, **kwargs): label = kwargs.get('label') @@ -269,8 +299,26 @@ class Meta: fields = ('status', 'fund', 'round') def __init__(self, *args, exclude=[], limit_statuses=None, **kwargs): + qs = kwargs.get('queryset') + + archived = kwargs.pop('archived') if 'archived' in kwargs.keys() else None + if archived is not None: + archived = int(archived) if archived else None + super().__init__(*args, **kwargs) + reviewers_qs = get_reviewers_from_dataset(dataset=qs.exclude(reviewers__isnull=True)) + if archived is not None and archived == 0: + reviewers_qs = get_reviewers_from_dataset(dataset=qs.filter(is_archive=archived).exclude(reviewers__isnull=True)) + qs = qs.filter(is_archive=archived) + + self.filters['fund'].queryset = get_used_funds_from_dataset(dataset=qs) + self.filters['round'].queryset = get_used_rounds_from_dataset(dataset=qs) + self.filters['lead'].queryset = get_round_leads_from_dataset(dataset=qs) + self.filters['screening_statuses'].queryset = get_screening_statuses_from_dataset(dataset=qs) + self.filters['reviewers'].queryset = reviewers_qs + self.filters['meta_terms'].queryset = get_meta_terms_from_dataset(dataset=qs) + self.filters['status'] = StatusMultipleChoiceFilter(limit_to=limit_statuses) self.filters['category_options'].extra['choices'] = [ (option.id, option.value) @@ -315,6 +363,13 @@ def filter_category_options(self, queryset, name, value): class SubmissionFilterAndSearch(SubmissionFilter): query = filters.CharFilter(field_name='search_data', lookup_expr="icontains", widget=forms.HiddenInput) + archived = filters.BooleanFilter(field_name='is_archive', widget=forms.HiddenInput, method='filter_archived') + + def filter_archived(self, queryset, name, value): + if not value: + # if value is 0 or None + queryset = queryset.exclude(is_archive=True) + return queryset class SubmissionDashboardFilter(filters.FilterSet): diff --git a/hypha/apply/funds/templates/funds/includes/batch_archive_submission_form.html b/hypha/apply/funds/templates/funds/includes/batch_archive_submission_form.html new file mode 100644 index 0000000000..e7b41dd823 --- /dev/null +++ b/hypha/apply/funds/templates/funds/includes/batch_archive_submission_form.html @@ -0,0 +1,16 @@ +{% load i18n %} +
{% trans "Are you sure you want to archive the selected submissions?" %}
+