From 58c00734f9712be26023b893a57f75a67a3375c2 Mon Sep 17 00:00:00 2001 From: Wilson Cheung Date: Thu, 20 Jun 2019 14:58:44 +0800 Subject: [PATCH 1/4] WildlifeCompliance: #4957, #4958 - reinstate activities/purposes and licence functionality --- .../components/applications/models.py | 15 ++- wildlifecompliance/components/licences/api.py | 32 +++++- .../components/licences/models.py | 103 ++++++++++++++++++ .../components/common/licences_dashboard.vue | 32 +++++- 4 files changed, 171 insertions(+), 11 deletions(-) diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index f5cc05891f..8a85fd6c48 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -2212,7 +2212,7 @@ def can_renew(self): def can_reactivate_renew(self): # TODO: clarify business logic for when an activity renew is allowed to be reactivate. return ApplicationSelectedActivity.get_current_activities_for_application_type( - Application.APPLICATION_TYPE_NEW_LICENCE, + Application.APPLICATION_TYPE_SYSTEM_GENERATED, activity_ids=[self.id] ).count() > 0 @@ -2220,7 +2220,7 @@ def can_reactivate_renew(self): def can_surrender(self): # TODO: clarify business logic for when an activity is allowed to be surrendered. return ApplicationSelectedActivity.get_current_activities_for_application_type( - Application.APPLICATION_TYPE_NEW_LICENCE, + Application.APPLICATION_TYPE_SYSTEM_GENERATED, activity_ids=[self.id] ).count() > 0 @@ -2228,17 +2228,17 @@ def can_surrender(self): def can_cancel(self): # TODO: clarify business logic for when an activity is allowed to be cancelled. return ApplicationSelectedActivity.get_current_activities_for_application_type( - Application.APPLICATION_TYPE_NEW_LICENCE, + Application.APPLICATION_TYPE_SYSTEM_GENERATED, activity_ids=[self.id] ).count() > 0 @property def can_suspend(self): - # TODO: clarify business logic for when an activity is allowed to be suspended. + # Returns true if the activity_status is CURRENT return ApplicationSelectedActivity.get_current_activities_for_application_type( - Application.APPLICATION_TYPE_NEW_LICENCE, + Application.APPLICATION_TYPE_SYSTEM_GENERATED, activity_ids=[self.id] - ).count() > 0 + ).exclude(activity_status=ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED).count() > 0 @property def can_reissue(self): @@ -2297,6 +2297,9 @@ def payment_status(self): @staticmethod def get_current_activities_for_application_type(application_type, **kwargs): + # Retrieves the current activities for an ApplicationSelectedActivity, filterable by LicenceActivity ID + # and Application.APPLICATION_TYPE in the case of the additional date_filter (use + # Application.APPLICATION_TYPE_SYSTEM_GENERATED for no APPLICATION_TYPE filters) applications = kwargs.get('applications', Application.objects.none()) activity_ids = kwargs.get('activity_ids', []) diff --git a/wildlifecompliance/components/licences/api.py b/wildlifecompliance/components/licences/api.py index 777f9a2903..5b0e0b694c 100644 --- a/wildlifecompliance/components/licences/api.py +++ b/wildlifecompliance/components/licences/api.py @@ -324,7 +324,7 @@ def surrender_licence(self, request, pk=None, *args, **kwargs): return Response(serializer.data) else: raise serializers.ValidationError( - 'Licence ID list must be specified') + 'Licence ID must be specified') except serializers.ValidationError: print(traceback.print_exc()) raise @@ -386,7 +386,7 @@ def cancel_licence(self, request, pk=None, *args, **kwargs): return Response(serializer.data) else: raise serializers.ValidationError( - 'Licence ID list must be specified') + 'Licence ID must be specified') except serializers.ValidationError: print(traceback.print_exc()) raise @@ -451,7 +451,7 @@ def suspend_licence(self, request, pk=None, *args, **kwargs): return Response(serializer.data) else: raise serializers.ValidationError( - 'Licence ID list must be specified') + 'Licence ID must be specified') except serializers.ValidationError: print(traceback.print_exc()) raise @@ -482,7 +482,7 @@ def suspend_purposes(self, request, pk=None, *args, **kwargs): first().licence_activity_id instance = self.get_object() can_suspend_purposes = instance.get_latest_purposes_for_licence_activity_and_action( - licence_activity_id, 'suspend') + licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND) can_suspend_purposes_ids_list = [purpose.id for purpose in can_suspend_purposes.order_by('id')] if not set(purpose_ids_list).issubset(can_suspend_purposes_ids_list): raise serializers.ValidationError( @@ -503,6 +503,30 @@ def suspend_purposes(self, request, pk=None, *args, **kwargs): print(traceback.print_exc()) raise serializers.ValidationError(str(e)) + @detail_route(methods=['POST', ]) + def reinstate_licence(self, request, pk=None, *args, **kwargs): + try: + if not request.user.has_perm('wildlifecompliance.issuing_officer'): + raise serializers.ValidationError( + 'You are not authorised to reinstate licences') + if pk: + instance = self.get_object() + instance.reinstate_licence(request) + serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) + return Response(serializer.data) + else: + raise serializers.ValidationError( + 'Licence ID must be specified') + except serializers.ValidationError: + print(traceback.print_exc()) + raise + except ValidationError as e: + print(traceback.print_exc()) + raise serializers.ValidationError(repr(e.error_dict)) + except Exception as e: + print(traceback.print_exc()) + raise serializers.ValidationError(str(e)) + @detail_route(methods=['POST', ]) def reinstate_purposes(self, request, pk=None, *args, **kwargs): try: diff --git a/wildlifecompliance/components/licences/models.py b/wildlifecompliance/components/licences/models.py index 14a91dece6..716241c7bd 100644 --- a/wildlifecompliance/components/licences/models.py +++ b/wildlifecompliance/components/licences/models.py @@ -617,6 +617,109 @@ def cancel_purposes(self, request): for activity in original_activities: activity.mark_as_replaced(request) + def reinstate_licence(self, request): + ''' + Reinstates all surrendered activities and purposes for a licence + ''' + with transaction.atomic(): + for activity_id in self.latest_activities.values_list('licence_activity_id', flat=True): + for activity in self.get_latest_activities_for_licence_activity_and_action( + activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE): + activity.reinstate(request) + + def reinstate_purposes(self, request): + ''' + Reinstates a licence's purposes for a selected licence_activity_id and purposes list + ''' + from wildlifecompliance.components.applications.models import ( + Application, ApplicationSelectedActivity + ) + purpose_ids_list = request.data.get('purpose_ids_list', None) + purpose_ids_list = list(set(purpose_ids_list)) + purpose_ids_list.sort() + licence_activity_id = LicencePurpose.objects.filter(id__in=purpose_ids_list). \ + first().licence_activity_id + can_reinstate_purposes = self.get_latest_purposes_for_licence_activity_and_action( + licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) + can_reinstate_purposes_ids_list = [purpose.id for purpose in can_reinstate_purposes.order_by('id')] + + # if all purposes were selected by the user for reinstate, + # reinstate all suspended ApplicationSelectedActivity records + if purpose_ids_list == can_reinstate_purposes_ids_list: + activities_to_reinstate = self.get_latest_activities_for_licence_activity_and_action( + licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) + with transaction.atomic(): + # reinstate target activities + for activity in activities_to_reinstate: + activity.reinstate(request) + + # else, if not all purposes were selected by the user for reinstate: + # - if any ApplicationSelectedActivity records can be reinstated completely (i.e. all purposes in the + # Application record are selected for reinstate), reinstate them + # - create new Application for the purposes to remain suspended, using the first application found + # to have a purpose_id to remain suspended + # - create new Application for the purposes to be reinstated, using the first application found + # to have a purpose_id to reinstate + # - add purposes from other relevant applications to either the new suspended or new reinstated application, + # copying data from their respective Applications + # - mark all previously suspended and not reinstated ApplicationSelectedActivity records as REPLACED + else: + new_suspended_application = None + new_reinstated_application = None + + licence_latest_activities = self.get_latest_activities_for_licence_activity_and_action( + licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) + original_application_ids = licence_latest_activities.filter( + application__licence_purposes__in=purpose_ids_list).values_list('application_id', flat=True) + original_applications = Application.objects.filter(id__in=original_application_ids) + with transaction.atomic(): + for application in original_applications: + # get purpose_ids linked with application + application_licence_purpose_ids_list = application.licence_purposes.filter( + licence_activity_id=licence_activity_id).values_list('id', flat=True) + + # if application's purpose_ids are all listed in the purpose_ids_list, + # completely reinstate ApplicationSelectedActivity + if not set(application_licence_purpose_ids_list) - set(purpose_ids_list): + activity = application.selected_activities.get(licence_activity_id=licence_activity_id) + activity.reinstate(request) + + # if application still has suspended purposes after reinstating selected purposes + if set(application_licence_purpose_ids_list) - set(purpose_ids_list): + common_reinstated_purpose_ids = set(application_licence_purpose_ids_list) & set(purpose_ids_list) + remaining_suspended_purpose_ids = set(application_licence_purpose_ids_list) - set(purpose_ids_list) + + # create new suspended application from this application if not exists + if not new_suspended_application: + new_suspended_application = application.copy_application_purposes_for_status( + remaining_suspended_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED) + # else, if new suspended application exists, link the target LicencePurpose IDs to it + else: + # Link the target LicencePurpose IDs to the application + for licence_purpose_id in remaining_suspended_purpose_ids: + application.copy_application_purpose_to_target_application( + new_suspended_application, + licence_purpose_id) + + # create new reinstated application from this application if not exists + if not new_reinstated_application: + new_reinstated_application = application.copy_application_purposes_for_status( + common_reinstated_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) + # else, if new reinstated application exists, link the target LicencePurpose IDs to it + else: + # Link the target LicencePurpose IDs to the application + for licence_purpose_id in common_reinstated_purpose_ids: + application.copy_application_purpose_to_target_application( + new_reinstated_application, + licence_purpose_id) + + # Set original activities to REPLACED except for any that were REINSTATED (CURRENT) completely + original_activities = ApplicationSelectedActivity.objects.\ + filter(application__id__in=original_application_ids).\ + exclude(activity_status=ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) + for activity in original_activities: + activity.mark_as_replaced(request) + def generate_doc(self): from wildlifecompliance.components.licences.pdf import create_licence_doc self.licence_document = create_licence_doc( diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/common/licences_dashboard.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/common/licences_dashboard.vue index 12a6ae412d..3ac99948a4 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/common/licences_dashboard.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/common/licences_dashboard.vue @@ -200,7 +200,7 @@ export default { links += `Suspend
` } if (!vm.is_external) { - links += `Reinstate
` + links += `Reinstate
` } return links; }, @@ -617,6 +617,36 @@ export default { },(error) => { }); }); + // Reinstate licence listener + vm.$refs.licence_datatable.vmDataTable.on('click', 'a[reinstate-licence]', function(e) { + e.preventDefault(); + swal({ + title: "Reinstate Licence", + text: "Are you sure you want to reinstate all suspended activities and purposes for this licence?", + type: "question", + showCancelButton: true, + confirmButtonText: 'Accept' + }).then((result) => { + if (result.value) { + var licence_id = $(this).attr('reinstate-licence'); + vm.$http.post(helpers.add_endpoint_json(api_endpoints.licences,licence_id+'/reinstate_licence')).then((response)=>{ + swal( + 'Reinstate Licence', + 'The selected licence\'s suspended activities and purposes have been Reinstated.', + 'success' + ) + vm.refreshFromResponse(response) + },(error)=>{ + swal( + 'Reinstate Licence Error', + helpers.apiVueResourceError(error), + 'error' + ) + }); + } + },(error) => { + }); + }); // Reinstate activity listener vm.$refs.licence_datatable.vmDataTable.on('click', 'a[reinstate-purposes]', function(e) { e.preventDefault(); From cb4cb1facd1e17201f92c87aa327ad07bfb631b2 Mon Sep 17 00:00:00 2001 From: Wilson Cheung Date: Mon, 24 Jun 2019 12:33:18 +0800 Subject: [PATCH 2/4] WildlifeCompliance: fix visible application tabs when application.can_user_edit true/false --- .../components/applications/models.py | 18 +++++++++--------- .../components/applications/serializers.py | 2 +- .../src/components/common/renderer.vue | 3 ++- .../src/store/modules/renderer.js | 8 +++++++- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index 8a85fd6c48..c5cef301ce 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -168,6 +168,15 @@ class Application(RevisionedMixin): CUSTOMER_STATUS_AMENDMENT_REQUIRED, ] + # List of statuses from above that allow a customer to view an application + # (read-only) + CUSTOMER_VIEWABLE_STATE = [ + CUSTOMER_STATUS_UNDER_REVIEW, + CUSTOMER_STATUS_ACCEPTED, + CUSTOMER_STATUS_PARTIALLY_APPROVED, + CUSTOMER_STATUS_DECLINED, + ] + PROCESSING_STATUS_DRAFT = 'draft' PROCESSING_STATUS_AWAITING_APPLICANT_RESPONSE = 'awaiting_applicant_response' PROCESSING_STATUS_APPROVED = 'approved' @@ -185,15 +194,6 @@ class Application(RevisionedMixin): (PROCESSING_STATUS_UNDER_REVIEW, 'Under Review'), ) - # List of statuses from above that allow a customer to view an application - # (read-only) - CUSTOMER_VIEWABLE_STATE = [ - PROCESSING_STATUS_UNDER_REVIEW, - PROCESSING_STATUS_APPROVED, - PROCESSING_STATUS_DECLINED, - PROCESSING_STATUS_PARTIALLY_APPROVED, - ] - ID_CHECK_STATUS_NOT_CHECKED = 'not_checked' ID_CHECK_STATUS_AWAITING_UPDATE = 'awaiting_update' ID_CHECK_STATUS_UPDATED = 'updated' diff --git a/wildlifecompliance/components/applications/serializers.py b/wildlifecompliance/components/applications/serializers.py index 6a59a6db6f..52958f10d2 100644 --- a/wildlifecompliance/components/applications/serializers.py +++ b/wildlifecompliance/components/applications/serializers.py @@ -638,7 +638,7 @@ class ApplicationSerializer(BaseApplicationSerializer): can_be_processed = serializers.SerializerMethodField(read_only=True) def get_readonly(self, obj): - return obj.can_user_view + return not obj.can_user_edit def get_amendment_requests(self, obj): return ExternalAmendmentRequestSerializer( diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/common/renderer.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/common/renderer.vue index edb17823d8..cc572902e7 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/common/renderer.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/common/renderer.vue @@ -98,10 +98,11 @@ export default { 'selected_activity_tab_id', 'renderer_tabs', 'unfinishedActivities', + 'allCurrentActivities', 'sectionsForTab', ]), listVisibleActivities: function() { - return this.unfinishedActivities; + return this.application.can_user_edit ? this.unfinishedActivities : this.allCurrentActivities; }, }, methods: { diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js index dc08361496..5f8e8c459b 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js +++ b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js @@ -42,13 +42,19 @@ export const rendererStore = { unfinishedActivities: (state, getters, rootState, rootGetters) => { return getters.visibleActivities( state.visibility.exclude_decisions, // Hide by decision - state.visibility.exclude_processing_status // Hide by processing_status + state.visibility.exclude_processing_status // Hide by processing_status (discarded) ).filter(activity => !rootGetters.application.has_amendment || rootGetters.application.amendment_requests.find( request => request.licence_activity.id == activity.id ) ); }, + allCurrentActivities: (state, getters, rootState, rootGetters) => { + return getters.visibleActivities( + [], // Do not hide by any decision + state.visibility.exclude_processing_status // Hide by processing_status (discarded) + ); + }, isComponentVisible: (state) => (key) => { return state.visible_components[key] ? true : false; }, From 19e4580b9f4a8a900a9a0eb5aa83ac958e18c232 Mon Sep 17 00:00:00 2001 From: Wilson Cheung Date: Mon, 24 Jun 2019 12:50:38 +0800 Subject: [PATCH 3/4] WildlifeCompliance: minor tidy up for readability --- .../wildlifecompliance/src/store/modules/application.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js index 0ea565f3b9..f8fb2540bc 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js +++ b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js @@ -60,9 +60,11 @@ export const applicationStore = { }, isApplicationLoaded: state => Object.keys(state.application).length && state.application.licence_type_data.activity.length, isApplicationActivityVisible: (state, getters, rootState, rootGetters) => - ({activity_id, exclude_statuses, - exclude_processing_statuses, - for_user_role, only_processing_statuses, + ({activity_id, + exclude_statuses, + exclude_processing_statuses, + for_user_role, + only_processing_statuses, }) => { if(!state.application.activities) { return 0; From e80d67d0a66fd00085ff01badc328fa0dc2970cb Mon Sep 17 00:00:00 2001 From: Wilson Cheung Date: Mon, 24 Jun 2019 18:42:57 +0800 Subject: [PATCH 4/4] WildlifeCompliance: refactor actions for licence and purposes functionality --- .../components/applications/models.py | 22 +- wildlifecompliance/components/licences/api.py | 18 +- .../components/licences/models.py | 508 +++++------------- 3 files changed, 161 insertions(+), 387 deletions(-) diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index c5cef301ce..fdcaef2e5a 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -2194,6 +2194,7 @@ def purposes(self): @property def can_amend(self): + # TODO: check for situation where a new licence of same purpose exists # Returns true if the activity can be included in a Amendment Application return ApplicationSelectedActivity.get_current_activities_for_application_type( Application.APPLICATION_TYPE_AMENDMENT, @@ -2202,6 +2203,7 @@ def can_amend(self): @property def can_renew(self): + # TODO: check for situation where a new licence of same purpose exists # Returns true if the activity can be included in a Renewal Application return ApplicationSelectedActivity.get_current_activities_for_application_type( Application.APPLICATION_TYPE_RENEWAL, @@ -2210,6 +2212,7 @@ def can_renew(self): @property def can_reactivate_renew(self): + # TODO: check for situation where a new licence of same purpose exists # TODO: clarify business logic for when an activity renew is allowed to be reactivate. return ApplicationSelectedActivity.get_current_activities_for_application_type( Application.APPLICATION_TYPE_SYSTEM_GENERATED, @@ -2242,6 +2245,7 @@ def can_suspend(self): @property def can_reissue(self): + # TODO: check for situation where a new licence of same purpose exists # Returns true if the activity has expired, excluding if it was surrendered or cancelled current_date = timezone.now().date() return ApplicationSelectedActivity.objects.filter( @@ -2258,11 +2262,15 @@ def can_reissue(self): @property def can_reinstate(self): - # Returns true if the activity has not yet expired and is currently suspended + # TODO: check for situation where a new licence of same purpose exists + # Returns true if the activity has not yet expired and is currently suspended or cancelled current_date = timezone.now().date() return self.expiry_date and\ self.expiry_date >= current_date and\ - self.activity_status == ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED + self.activity_status in [ + ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED, + ApplicationSelectedActivity.ACTIVITY_STATUS_CANCELLED + ] @property def base_fees(self): @@ -2365,11 +2373,11 @@ def process_licence_fee_payment(self, request, application): return self.licence_fee_paid and send_activity_invoice_email_notification(application, self, invoice_ref, request) def reactivate_renew(self, request): - pass - # with transaction.atomic(): - # self.activity_status = ApplicationSelectedActivity.ACTIVITY_STATUS_SURRENDERED - # self.updated_by = request.user - # self.save() + # TODO: this needs work, reactivate renew logic to be clarified and function adjusted + with transaction.atomic(): + self.activity_status = ApplicationSelectedActivity.ACTIVITY_STATUS_EXPIRED + self.updated_by = request.user + self.save() def surrender(self, request): with transaction.atomic(): diff --git a/wildlifecompliance/components/licences/api.py b/wildlifecompliance/components/licences/api.py index 5b0e0b694c..cafa68f4e0 100644 --- a/wildlifecompliance/components/licences/api.py +++ b/wildlifecompliance/components/licences/api.py @@ -298,7 +298,7 @@ def reactivate_renew_purposes(self, request, pk=None, *args, **kwargs): if not set(purpose_ids_list).issubset(can_reactivate_renew_purposes_ids_list): raise serializers.ValidationError( 'Renew for selected purposes cannot be reactivated') - instance.reactivate_renew_purposes(request) + instance.apply_action_to_purposes(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -319,7 +319,7 @@ def surrender_licence(self, request, pk=None, *args, **kwargs): try: if pk: instance = self.get_object() - instance.surrender_licence(request) + instance.apply_action_to_licence(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -357,7 +357,7 @@ def surrender_purposes(self, request, pk=None, *args, **kwargs): if not set(purpose_ids_list).issubset(can_surrender_purposes_ids_list): raise serializers.ValidationError( 'Selected purposes cannot be surrendered') - instance.surrender_purposes(request) + instance.apply_action_to_purposes(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -381,7 +381,7 @@ def cancel_licence(self, request, pk=None, *args, **kwargs): 'You are not authorised to cancel licences') if pk: instance = self.get_object() - instance.cancel_licence(request) + instance.apply_action_to_licence(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -422,7 +422,7 @@ def cancel_purposes(self, request, pk=None, *args, **kwargs): if not set(purpose_ids_list).issubset(can_cancel_purposes_ids_list): raise serializers.ValidationError( 'Selected purposes cannot be cancelled') - instance.cancel_purposes(request) + instance.apply_action_to_purposes(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -446,7 +446,7 @@ def suspend_licence(self, request, pk=None, *args, **kwargs): 'You are not authorised to suspend licences') if pk: instance = self.get_object() - instance.suspend_licence(request) + instance.apply_action_to_licence(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -487,7 +487,7 @@ def suspend_purposes(self, request, pk=None, *args, **kwargs): if not set(purpose_ids_list).issubset(can_suspend_purposes_ids_list): raise serializers.ValidationError( 'Selected purposes cannot be suspended') - instance.suspend_purposes(request) + instance.apply_action_to_purposes(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -511,7 +511,7 @@ def reinstate_licence(self, request, pk=None, *args, **kwargs): 'You are not authorised to reinstate licences') if pk: instance = self.get_object() - instance.reinstate_licence(request) + instance.apply_action_to_licence(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: @@ -552,7 +552,7 @@ def reinstate_purposes(self, request, pk=None, *args, **kwargs): if not set(purpose_ids_list).issubset(can_reinstate_purposes_ids_list): raise serializers.ValidationError( 'Selected purposes cannot be reinstated') - instance.reinstate_purposes(request) + instance.apply_action_to_purposes(request, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) serializer = DTExternalWildlifeLicenceSerializer(instance, context={'request': request}) return Response(serializer.data) else: diff --git a/wildlifecompliance/components/licences/models.py b/wildlifecompliance/components/licences/models.py index 716241c7bd..35936e3a36 100644 --- a/wildlifecompliance/components/licences/models.py +++ b/wildlifecompliance/components/licences/models.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from django.core.exceptions import ValidationError from django.db import models, transaction from django.dispatch import receiver from django.db.models.signals import pre_delete @@ -310,413 +311,178 @@ def reference(self): def is_issued(self): return self.licence_number is not None and len(self.licence_number) > 0 - def surrender_licence(self, request): + def apply_action_to_licence(self, request, action): ''' - Cancels all surrenderable activities and purposes for a licence + Applies a specified action to a all of a licence's activities and purposes for a licence ''' + if action not in [ + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE + ]: + raise ValidationError('Selected action is not valid') with transaction.atomic(): for activity_id in self.latest_activities.values_list('licence_activity_id', flat=True): for activity in self.get_latest_activities_for_licence_activity_and_action( - activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER): - activity.surrender(request) - - def surrender_purposes(self, request): - ''' - Surrenders a licence's purposes for a selected licence_activity_id and purposes list - ''' - from wildlifecompliance.components.applications.models import ( - Application, ApplicationSelectedActivity - ) - purpose_ids_list = request.data.get('purpose_ids_list', None) - purpose_ids_list = list(set(purpose_ids_list)) - purpose_ids_list.sort() - licence_activity_id = LicencePurpose.objects.filter(id__in=purpose_ids_list). \ - first().licence_activity_id - can_surrender_purposes = self.get_latest_purposes_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER) - can_surrender_purposes_ids_list = [purpose.id for purpose in can_surrender_purposes.order_by('id')] - - # if all purposes were selected by the user for surrender, - # surrender all current ApplicationSelectedActivity records - if purpose_ids_list == can_surrender_purposes_ids_list: - activities_to_surrender = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER) - with transaction.atomic(): - # surrender target activities - for activity in activities_to_surrender: - activity.surrender(request) - - # else, if not all purposes were selected by the user for surrender: - # - if any ApplicationSelectedActivity records can be surrendered completely (i.e. all purposes in the - # Application record are selected for surrender), surrender them - # - create new Application for the purposes to remain current, using the first application found - # to have a purpose_id to remain current - # - create new Application for the purposes to be surrendered, using the first application found - # to have a purpose_id to surrender - # - add purposes from other relevant applications to either the new current or new surrendered application, - # copying data from their respective Applications - # - mark all previously current and not surrendered ApplicationSelectedActivity records as REPLACED - else: - new_current_application = None - new_surrendered_application = None - - licence_latest_activities = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER) - original_application_ids = licence_latest_activities.filter( - application__licence_purposes__in=purpose_ids_list).values_list('application_id', flat=True) - original_applications = Application.objects.filter(id__in=original_application_ids) - with transaction.atomic(): - for application in original_applications: - # get purpose_ids linked with application - application_licence_purpose_ids_list = application.licence_purposes.filter( - licence_activity_id=licence_activity_id).values_list('id', flat=True) - - # if application's purpose_ids are all listed in the purpose_ids_list, - # completely surrender ApplicationSelectedActivity - if not set(application_licence_purpose_ids_list) - set(purpose_ids_list): - activity = application.selected_activities.get(licence_activity_id=licence_activity_id) + activity_id, action): + if action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW: + activity.reactivate_renew(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER: activity.surrender(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL: + activity.cancel(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND: + activity.suspend(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE: + activity.reinstate(request) - # if application still has current purposes after surrendering selected purposes - if set(application_licence_purpose_ids_list) - set(purpose_ids_list): - common_surrendered_purpose_ids = set(application_licence_purpose_ids_list) & set(purpose_ids_list) - remaining_current_purpose_ids = set(application_licence_purpose_ids_list) - set(purpose_ids_list) - - # create new current application from this application if not exists - if not new_current_application: - new_current_application = application.copy_application_purposes_for_status( - remaining_current_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) - # else, if new current application exists, link the target LicencePurpose IDs to it - else: - # Link the target LicencePurpose IDs to the application - for licence_purpose_id in remaining_current_purpose_ids: - application.copy_application_purpose_to_target_application( - new_current_application, - licence_purpose_id) - - # create new surrendered application from this application if not exists - if not new_surrendered_application: - new_surrendered_application = application.copy_application_purposes_for_status( - common_surrendered_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_SURRENDERED) - # else, if new surrendered application exists, link the target LicencePurpose IDs to it - else: - # Link the target LicencePurpose IDs to the application - for licence_purpose_id in common_surrendered_purpose_ids: - application.copy_application_purpose_to_target_application( - new_surrendered_application, - licence_purpose_id) - - # Set original activities to REPLACED except for any that were SURRENDERED completely - original_activities = ApplicationSelectedActivity.objects.\ - filter(application__id__in=original_application_ids).\ - exclude(activity_status=ApplicationSelectedActivity.ACTIVITY_STATUS_SURRENDERED) - for activity in original_activities: - activity.mark_as_replaced(request) - - def suspend_licence(self, request): - ''' - Cancels all suspendable activities and purposes for a licence - ''' - with transaction.atomic(): - for activity_id in self.latest_activities.values_list('licence_activity_id', flat=True): - for activity in self.get_latest_activities_for_licence_activity_and_action( - activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND): - activity.suspend(request) - - def suspend_purposes(self, request): + def apply_action_to_purposes(self, request, action): ''' - Suspends a licence's purposes for a selected licence_activity_id and purposes list + Applies a specified action to a licence's purposes for a single licence_activity_id and selected purposes list ''' from wildlifecompliance.components.applications.models import ( Application, ApplicationSelectedActivity ) - purpose_ids_list = request.data.get('purpose_ids_list', None) - purpose_ids_list = list(set(purpose_ids_list)) - purpose_ids_list.sort() - licence_activity_id = LicencePurpose.objects.filter(id__in=purpose_ids_list). \ - first().licence_activity_id - can_suspend_purposes = self.get_latest_purposes_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND) - can_suspend_purposes_ids_list = [purpose.id for purpose in can_suspend_purposes.order_by('id')] - - # if all purposes were selected by the user for suspend, suspend all current ApplicationSelectedActivity records - if purpose_ids_list == can_suspend_purposes_ids_list: - activities_to_suspend = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND) - with transaction.atomic(): - # suspend target activities - for activity in activities_to_suspend: - activity.suspend(request) - - # else, if not all purposes were selected by the user for suspend: - # - if any ApplicationSelectedActivity records can be suspended completely (i.e. all purposes in the - # Application record are selected for suspend), suspend them - # - create new Application for the purposes to remain current, using the first application found - # to have a purpose_id to remain current - # - create new Application for the purposes to be suspended, using the first application found - # to have a purpose_id to suspend - # - add purposes from other relevant applications to either the new current or new suspended application, - # copying data from their respective Applications - # - mark all previously current and not suspended ApplicationSelectedActivity records as REPLACED - else: - new_current_application = None - new_suspended_application = None - - licence_latest_activities = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND) - original_application_ids = licence_latest_activities.filter( - application__licence_purposes__in=purpose_ids_list).values_list('application_id', flat=True) - original_applications = Application.objects.filter(id__in=original_application_ids) - with transaction.atomic(): - for application in original_applications: - # get purpose_ids linked with application - application_licence_purpose_ids_list = application.licence_purposes.filter( - licence_activity_id=licence_activity_id).values_list('id', flat=True) - - # if application's purpose_ids are all listed in the purpose_ids_list, - # completely suspend ApplicationSelectedActivity - if not set(application_licence_purpose_ids_list) - set(purpose_ids_list): - activity = application.selected_activities.get(licence_activity_id=licence_activity_id) - activity.suspend(request) - - # if application still has current purposes after suspending selected purposes - if set(application_licence_purpose_ids_list) - set(purpose_ids_list): - common_suspended_purpose_ids = set(application_licence_purpose_ids_list) & set(purpose_ids_list) - remaining_current_purpose_ids = set(application_licence_purpose_ids_list) - set(purpose_ids_list) - - # create new current application from this application if not exists - if not new_current_application: - new_current_application = application.copy_application_purposes_for_status( - remaining_current_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) - # else, if new current application exists, link the target LicencePurpose IDs to it - else: - # Link the target LicencePurpose IDs to the application - for licence_purpose_id in remaining_current_purpose_ids: - application.copy_application_purpose_to_target_application( - new_current_application, - licence_purpose_id) - - # create new suspended application from this application if not exists - if not new_suspended_application: - new_suspended_application = application.copy_application_purposes_for_status( - common_suspended_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED) - # else, if new suspended application exists, link the target LicencePurpose IDs to it - else: - # Link the target LicencePurpose IDs to the application - for licence_purpose_id in common_suspended_purpose_ids: - application.copy_application_purpose_to_target_application( - new_suspended_application, - licence_purpose_id) + if action not in [ + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND, + WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE + ]: + raise ValidationError('Selected action is not valid') - # Set original activities to REPLACED except for any that were suspendED completely - original_activities = ApplicationSelectedActivity.objects.\ - filter(application__id__in=original_application_ids).\ - exclude(activity_status=ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED) - for activity in original_activities: - activity.mark_as_replaced(request) - - def cancel_licence(self, request): - ''' - Cancels all cancellable activities and purposes for a licence - ''' with transaction.atomic(): - for activity_id in self.latest_activities.values_list('licence_activity_id', flat=True): - for activity in self.get_latest_activities_for_licence_activity_and_action( - activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL): - activity.cancel(request) - - def cancel_purposes(self, request): - ''' - Cancels a licence's purposes for a selected licence_activity_id and purposes list - ''' - from wildlifecompliance.components.applications.models import ( - Application, ApplicationSelectedActivity - ) - purpose_ids_list = request.data.get('purpose_ids_list', None) - purpose_ids_list = list(set(purpose_ids_list)) - purpose_ids_list.sort() - licence_activity_id = LicencePurpose.objects.filter(id__in=purpose_ids_list). \ - first().licence_activity_id - can_cancel_purposes = self.get_latest_purposes_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL) - can_cancel_purposes_ids_list = [purpose.id for purpose in can_cancel_purposes.order_by('id')] - - # if all purposes were selected by the user for cancel, cancel all current ApplicationSelectedActivity records - if purpose_ids_list == can_cancel_purposes_ids_list: - activities_to_cancel = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL) - with transaction.atomic(): - # cancel target activities - for activity in activities_to_cancel: - activity.cancel(request) - - # else, if not all purposes were selected by the user for cancel: - # - if any ApplicationSelectedActivity records can be cancelled completely (i.e. all purposes in the - # Application record are selected for cancel), cancel them - # - create new Application for the purposes to remain current, using the first application found - # to have a purpose_id to remain current - # - create new Application for the purposes to be cancelled, using the first application found - # to have a purpose_id to cancel - # - add purposes from other relevant applications to either the new current or new cancelled application, - # copying data from their respective Applications - # - mark all previously current and not cancelled ApplicationSelectedActivity records as REPLACED - else: - new_current_application = None - new_cancelled_application = None - - licence_latest_activities = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL) - original_application_ids = licence_latest_activities.filter( - application__licence_purposes__in=purpose_ids_list).values_list('application_id', flat=True) - original_applications = Application.objects.filter(id__in=original_application_ids) - with transaction.atomic(): - for application in original_applications: - # get purpose_ids linked with application - application_licence_purpose_ids_list = application.licence_purposes.filter( - licence_activity_id=licence_activity_id).values_list('id', flat=True) - # if application's purpose_ids are all listed in the purpose_ids_list, - # completely cancel ApplicationSelectedActivity - if not set(application_licence_purpose_ids_list) - set(purpose_ids_list): - activity = application.selected_activities.get(licence_activity_id=licence_activity_id) + purpose_ids_list = request.data.get('purpose_ids_list', None) + purpose_ids_list = list(set(purpose_ids_list)) + purpose_ids_list.sort() + + if LicencePurpose.objects.filter(id__in=purpose_ids_list).\ + values_list('licence_activity_id', flat=True).\ + distinct().count() != 1: + raise ValidationError( + 'Selected purposes must all be of the same licence activity') + + licence_activity_id = LicencePurpose.objects.filter(id__in=purpose_ids_list). \ + first().licence_activity_id + + can_action_purposes = self.get_latest_purposes_for_licence_activity_and_action( + licence_activity_id, action) + can_action_purposes_ids_list = [purpose.id for purpose in can_action_purposes.order_by('id')] + + # if all purposes were selected by the user for action, + # action all previous status ApplicationSelectedActivity records + if purpose_ids_list == can_action_purposes_ids_list: + activities_to_action = self.get_latest_activities_for_licence_activity_and_action( + licence_activity_id, action) + # action target activities + for activity in activities_to_action: + if action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW: + activity.reactivate_renew(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER: + activity.surrender(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL: activity.cancel(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND: + activity.suspend(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE: + activity.reinstate(request) - # if application still has current purposes after cancelling selected purposes - if set(application_licence_purpose_ids_list) - set(purpose_ids_list): - common_cancelled_purpose_ids = set(application_licence_purpose_ids_list) & set(purpose_ids_list) - remaining_current_purpose_ids = set(application_licence_purpose_ids_list) - set(purpose_ids_list) - - # create new current application from this application if not exists - if not new_current_application: - new_current_application = application.copy_application_purposes_for_status( - remaining_current_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) - # else, if new current application exists, link the target LicencePurpose IDs to it - else: - # Link the target LicencePurpose IDs to the application - for licence_purpose_id in remaining_current_purpose_ids: - application.copy_application_purpose_to_target_application( - new_current_application, - licence_purpose_id) - - # create new cancelled application from this application if not exists - if not new_cancelled_application: - new_cancelled_application = application.copy_application_purposes_for_status( - common_cancelled_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_CANCELLED) - # else, if new cancelled application exists, link the target LicencePurpose IDs to it - else: - # Link the target LicencePurpose IDs to the application - for licence_purpose_id in common_cancelled_purpose_ids: - application.copy_application_purpose_to_target_application( - new_cancelled_application, - licence_purpose_id) - - # Set original activities to REPLACED except for any that were CANCELLED completely - original_activities = ApplicationSelectedActivity.objects.\ - filter(application__id__in=original_application_ids).\ - exclude(activity_status=ApplicationSelectedActivity.ACTIVITY_STATUS_CANCELLED) - for activity in original_activities: - activity.mark_as_replaced(request) - - def reinstate_licence(self, request): - ''' - Reinstates all surrendered activities and purposes for a licence - ''' - with transaction.atomic(): - for activity_id in self.latest_activities.values_list('licence_activity_id', flat=True): - for activity in self.get_latest_activities_for_licence_activity_and_action( - activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE): - activity.reinstate(request) + else: + # else, if not all purposes were selected by the user for action: + # - if any ApplicationSelectedActivity records can be actioned completely (i.e. all purposes in the + # Application record are selected for action), action them + # - create new Application for the purposes to remain in previous status, + # using the first application found to have a purpose_id to remain in previous status + # - create new Application for the purposes to be actioned, using the first application found + # to have a purpose_id to action + # - add purposes from other relevant applications to either the new previous status + # or new actioned application copying data from their respective Applications + # - mark all previous status and not actioned ApplicationSelectedActivity records as REPLACED + + # Use dict for new_previous_status_applications, new application per previous possible status + # e.g. REINSTATE can come from both CANCELLED and SURRENDERED activities/purposes + new_previous_status_applications = {} + new_actioned_application = None + + licence_latest_activities = self.get_latest_activities_for_licence_activity_and_action( + licence_activity_id, action) + previous_statuses = list(set(licence_latest_activities.values_list('activity_status', flat=True))) + for previous_status in previous_statuses: + new_previous_status_applications[previous_status] = None + original_application_ids = licence_latest_activities.filter( + application__licence_purposes__in=purpose_ids_list).values_list('application_id', flat=True) + original_applications = Application.objects.filter(id__in=original_application_ids) - def reinstate_purposes(self, request): - ''' - Reinstates a licence's purposes for a selected licence_activity_id and purposes list - ''' - from wildlifecompliance.components.applications.models import ( - Application, ApplicationSelectedActivity - ) - purpose_ids_list = request.data.get('purpose_ids_list', None) - purpose_ids_list = list(set(purpose_ids_list)) - purpose_ids_list.sort() - licence_activity_id = LicencePurpose.objects.filter(id__in=purpose_ids_list). \ - first().licence_activity_id - can_reinstate_purposes = self.get_latest_purposes_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) - can_reinstate_purposes_ids_list = [purpose.id for purpose in can_reinstate_purposes.order_by('id')] - - # if all purposes were selected by the user for reinstate, - # reinstate all suspended ApplicationSelectedActivity records - if purpose_ids_list == can_reinstate_purposes_ids_list: - activities_to_reinstate = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) - with transaction.atomic(): - # reinstate target activities - for activity in activities_to_reinstate: - activity.reinstate(request) - - # else, if not all purposes were selected by the user for reinstate: - # - if any ApplicationSelectedActivity records can be reinstated completely (i.e. all purposes in the - # Application record are selected for reinstate), reinstate them - # - create new Application for the purposes to remain suspended, using the first application found - # to have a purpose_id to remain suspended - # - create new Application for the purposes to be reinstated, using the first application found - # to have a purpose_id to reinstate - # - add purposes from other relevant applications to either the new suspended or new reinstated application, - # copying data from their respective Applications - # - mark all previously suspended and not reinstated ApplicationSelectedActivity records as REPLACED - else: - new_suspended_application = None - new_reinstated_application = None - - licence_latest_activities = self.get_latest_activities_for_licence_activity_and_action( - licence_activity_id, WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE) - original_application_ids = licence_latest_activities.filter( - application__licence_purposes__in=purpose_ids_list).values_list('application_id', flat=True) - original_applications = Application.objects.filter(id__in=original_application_ids) - with transaction.atomic(): for application in original_applications: # get purpose_ids linked with application application_licence_purpose_ids_list = application.licence_purposes.filter( licence_activity_id=licence_activity_id).values_list('id', flat=True) - # if application's purpose_ids are all listed in the purpose_ids_list, - # completely reinstate ApplicationSelectedActivity + activity = application.selected_activities.get(licence_activity_id=licence_activity_id) + # Get previous_status and target post_actioned_status + previous_status = activity.activity_status + if action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW: + post_actioned_status = ApplicationSelectedActivity.ACTIVITY_STATUS_EXPIRED + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER: + post_actioned_status = ApplicationSelectedActivity.ACTIVITY_STATUS_SURRENDERED + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL: + post_actioned_status = ApplicationSelectedActivity.ACTIVITY_STATUS_CANCELLED + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND: + post_actioned_status = ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE: + post_actioned_status = ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT + + # if an application's purpose_ids are all in the purpose_ids_list, + # completely action the ApplicationSelectedActivity if not set(application_licence_purpose_ids_list) - set(purpose_ids_list): - activity = application.selected_activities.get(licence_activity_id=licence_activity_id) - activity.reinstate(request) - - # if application still has suspended purposes after reinstating selected purposes - if set(application_licence_purpose_ids_list) - set(purpose_ids_list): - common_reinstated_purpose_ids = set(application_licence_purpose_ids_list) & set(purpose_ids_list) - remaining_suspended_purpose_ids = set(application_licence_purpose_ids_list) - set(purpose_ids_list) - - # create new suspended application from this application if not exists - if not new_suspended_application: - new_suspended_application = application.copy_application_purposes_for_status( - remaining_suspended_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_SUSPENDED) - # else, if new suspended application exists, link the target LicencePurpose IDs to it + if action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REACTIVATE_RENEW: + activity.reactivate_renew(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SURRENDER: + activity.surrender(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_CANCEL: + activity.cancel(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_SUSPEND: + activity.suspend(request) + elif action == WildlifeLicence.ACTIVITY_PURPOSE_ACTION_REINSTATE: + activity.reinstate(request) + + # if application still has previous_status purposes after actioning selected purposes + elif set(application_licence_purpose_ids_list) - set(purpose_ids_list): + common_actioned_purpose_ids = set(application_licence_purpose_ids_list) & set(purpose_ids_list) + remaining_previous_status_purpose_ids = set(application_licence_purpose_ids_list) - set(purpose_ids_list) + + # create new previous_status application from this application if not yet exists + if not new_previous_status_applications[previous_status]: + new_previous_status_applications[previous_status] = application.copy_application_purposes_for_status( + remaining_previous_status_purpose_ids, previous_status) + # else, if new previous_status application exists, link the target LicencePurpose IDs to it else: # Link the target LicencePurpose IDs to the application - for licence_purpose_id in remaining_suspended_purpose_ids: + for licence_purpose_id in remaining_previous_status_purpose_ids: application.copy_application_purpose_to_target_application( - new_suspended_application, + new_previous_status_applications[previous_status], licence_purpose_id) - # create new reinstated application from this application if not exists - if not new_reinstated_application: - new_reinstated_application = application.copy_application_purposes_for_status( - common_reinstated_purpose_ids, ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) - # else, if new reinstated application exists, link the target LicencePurpose IDs to it + # create new actioned application from this application if not yet exists + if not new_actioned_application: + new_actioned_application = application.copy_application_purposes_for_status( + common_actioned_purpose_ids, post_actioned_status) + # else, if new actioned application exists, link the target LicencePurpose IDs to it else: # Link the target LicencePurpose IDs to the application - for licence_purpose_id in common_reinstated_purpose_ids: + for licence_purpose_id in common_actioned_purpose_ids: application.copy_application_purpose_to_target_application( - new_reinstated_application, + new_actioned_application, licence_purpose_id) - # Set original activities to REPLACED except for any that were REINSTATED (CURRENT) completely + # Set original activities to REPLACED except for any that were ACTIONED completely original_activities = ApplicationSelectedActivity.objects.\ filter(application__id__in=original_application_ids).\ - exclude(activity_status=ApplicationSelectedActivity.ACTIVITY_STATUS_CURRENT) + exclude(activity_status=post_actioned_status) for activity in original_activities: activity.mark_as_replaced(request)