diff --git a/mooringlicensing/components/approvals/api.py b/mooringlicensing/components/approvals/api.py index c3997b923..1f1cbd3e0 100755 --- a/mooringlicensing/components/approvals/api.py +++ b/mooringlicensing/components/approvals/api.py @@ -1302,6 +1302,8 @@ def create_mooring_licence_application(self, request, *args, **kwargs): waiting_list_allocation.current_proposal.copy_vessel_details(new_proposal) logger.info(f'Vessel details have been copied from the proposal: [{waiting_list_allocation.current_proposal}] to the mooring site licence application: [{new_proposal}].') + waiting_list_allocation.log_user_action(f'Offer new Mooring Site Licence application: {new_proposal.lodgement_number}.', request) + if new_proposal: # send email send_create_mooring_licence_application_email_notification(request, waiting_list_allocation, new_proposal) diff --git a/mooringlicensing/components/approvals/models.py b/mooringlicensing/components/approvals/models.py index b98febb19..14dd133fb 100755 --- a/mooringlicensing/components/approvals/models.py +++ b/mooringlicensing/components/approvals/models.py @@ -635,17 +635,31 @@ def allowed_assessors(self): return self.current_proposal.allowed_assessors def allowed_assessors_user(self, request): - return self.current_proposal.allowed_assessors_user(request) + if self.current_proposal: + return self.current_proposal.allowed_assessors_user(request) + else: + logger.warning(f'Current proposal of the approval: [{self}] not found.') + return None - def is_assessor(self,user): + def is_assessor(self, user): if isinstance(user, EmailUserRO): user = user.id - return self.current_proposal.is_assessor(user) + + if self.current_proposal: + return self.current_proposal.is_assessor(user) + else: + logger.warning(f'Current proposal of the approval: [{self}] not found.') + return False def is_approver(self,user): if isinstance(user, EmailUserRO): user = user.id - return self.current_proposal.is_approver(user) + + if self.current_proposal: + return self.current_proposal.is_approver(user) + else: + logger.warning(f'Current proposal of the approval: [{self}] not found.') + return False @property def is_issued(self): @@ -654,7 +668,7 @@ def is_issued(self): @property def can_action(self): if not (self.set_to_cancel or self.set_to_suspend or self.set_to_surrender): - return True + return True else: return False @@ -848,8 +862,6 @@ def reinstate_approval(self,request): if type(self.child_obj) == WaitingListAllocation and previous_status in [Approval.APPROVAL_STATUS_CANCELLED, Approval.APPROVAL_STATUS_SURRENDERED]: wla = self.child_obj wla.internal_status = Approval.INTERNAL_STATUS_WAITING - current_datetime = datetime.datetime.now(pytz.timezone(TIME_ZONE)) - # wla.wla_queue_date = current_datetime # Comment out this line because we never want to lost the original queue_date. wla.save() wla.set_wla_order() send_approval_reinstate_email_notification(self, request) @@ -1139,7 +1151,7 @@ def process_after_discarded(self): logger.info(f'Set attributes as follows: [status=fulfilled, wla_order=None] of the WL Allocation: [{self}].') self.set_wla_order() - def reinstate_wla_order(self, request): + def reinstate_wla_order(self): """ This function makes this WL allocation back to the 'waiting' status """ @@ -1147,8 +1159,9 @@ def reinstate_wla_order(self, request): self.status = Approval.APPROVAL_STATUS_CURRENT self.internal_status = Approval.INTERNAL_STATUS_WAITING self.save() - logger.info(f'Set attributes as follows: [status=current, internal_status=waiting, wla_order=None] of the WL Allocation: [{self}].') + logger.info(f'Set attributes as follows: [status=current, internal_status=waiting, wla_order=None] of the WL Allocation: [{self}]. These changes make this WL allocation back to the waiting list queue.') self.set_wla_order() + return self class AnnualAdmissionPermit(Approval): diff --git a/mooringlicensing/components/approvals/serializers.py b/mooringlicensing/components/approvals/serializers.py index 0b0f5517e..5d01fc752 100755 --- a/mooringlicensing/components/approvals/serializers.py +++ b/mooringlicensing/components/approvals/serializers.py @@ -840,7 +840,11 @@ def get_allowed_assessors_user(self, obj): def get_current_proposal_approved(self, obj): from mooringlicensing.components.proposals.models import Proposal - return obj.current_proposal.processing_status == Proposal.PROCESSING_STATUS_APPROVED + if obj.current_proposal: + return obj.current_proposal.processing_status == Proposal.PROCESSING_STATUS_APPROVED + else: + logger.warning(f'Current proposal of the approval: [{obj}] not found.') + return '' def get_is_assessor(self, obj): request = self.context.get('request') @@ -972,10 +976,15 @@ def get_vessel_regos(self, obj): # regos += '{}\n'.format(obj.current_proposal.vessel_details.vessel.rego_no) if obj.current_proposal.vessel_details else '' # if obj.current_proposal.vessel_details: # regos.append(obj.current_proposal.vessel_details.vessel.rego_no) - if obj.current_proposal.vessel_ownership: - if obj.current_proposal.vessel_ownership.end_date is None or obj.current_proposal.vessel_ownership.end_date >= today: - # We don't want to include the sold vessel - regos.append(obj.current_proposal.vessel_ownership.vessel.rego_no) + if obj.current_proposal: + if obj.current_proposal.vessel_ownership: + if obj.current_proposal.vessel_ownership.end_date is None or obj.current_proposal.vessel_ownership.end_date >= today: + # We don't want to include the sold vessel + regos.append(obj.current_proposal.vessel_ownership.vessel.rego_no) + else: + logger.warning(f'Current proposal of the approval: [{obj}] not found.') + return '' + return regos diff --git a/mooringlicensing/components/payments_ml/views.py b/mooringlicensing/components/payments_ml/views.py index c629a2bc6..07f84d60d 100644 --- a/mooringlicensing/components/payments_ml/views.py +++ b/mooringlicensing/components/payments_ml/views.py @@ -777,7 +777,7 @@ def get(self, request, uuid, format=None): # When WLA / AAA if proposal.application_type.code in [WaitingListApplication.code, AnnualAdmissionApplication.code]: proposal.lodgement_date = datetime.datetime.now(pytz.timezone(TIME_ZONE)) - proposal.log_user_action(ProposalUserAction.ACTION_LODGE_APPLICATION.format(proposal.id), request) + proposal.log_user_action(ProposalUserAction.ACTION_LODGE_APPLICATION.format(proposal.lodgement_number), request) proposal.child_obj.send_emails_after_payment_success(request) proposal.save() @@ -834,8 +834,9 @@ def get(self, request, uuid, *args, **kwargs): except Exception as e: # Should not reach here - msg = 'Failed to process the payment. {}'.format(str(e)) + msg = f'Failed to process the payment. {str(e)}' logger.error(msg) + logger.error('Check if the preload_url is configured correctly.') raise Exception(msg) diff --git a/mooringlicensing/components/proposals/api.py b/mooringlicensing/components/proposals/api.py index 64968470f..76d09ae9f 100755 --- a/mooringlicensing/components/proposals/api.py +++ b/mooringlicensing/components/proposals/api.py @@ -617,8 +617,7 @@ def create(self, request, *args, **kwargs): proposal_type=proposal_type ) logger.info(f'Annual Admission Application: [{obj}] has been created by the user: [{request.user}].') - - # make_proposal_applicant_ready(obj, request) + obj.log_user_action(f'Annual Admission Application: {obj.lodgement_number} has been created.', request) serialized_obj = ProposalSerializer(obj.proposal) return Response(serialized_obj.data) @@ -647,9 +646,8 @@ def create(self, request, *args, **kwargs): submitter=request.user.id, proposal_type=proposal_type ) - logger.info(f'Authorised User Application: [{obj}] has been created by the user: [{request.user}].') - - # make_proposal_applicant_ready(obj, request) + logger.info(f'Authorised User Permit Application: [{obj}] has been created by the user: [{request.user}].') + obj.log_user_action(f'Authorised User Permit Application: {obj.lodgement_number} has been created.', request) serialized_obj = ProposalSerializer(obj.proposal) return Response(serialized_obj.data) @@ -684,8 +682,7 @@ def create(self, request, *args, **kwargs): allocated_mooring=mooring, ) logger.info(f'Mooring Licence Application: [{obj}] has been created by the user: [{request.user}].') - - # make_proposal_applicant_ready(obj, request) + obj.log_user_action(f'Mooring Licence Application: {obj.lodgement_number} has been created.', request) serialized_obj = ProposalSerializer(obj.proposal) return Response(serialized_obj.data) @@ -717,16 +714,12 @@ def create(self, request, *args, **kwargs): ) logger.info(f'Waiting List Application: [{obj}] has been created by the user: [{request.user}].') - - # make_proposal_applicant_ready(obj, request) - - # make_ownership_ready(obj, request) + obj.log_user_action(f'Waiting List Application: {obj.lodgement_number} has been created.', request) serialized_obj = ProposalSerializer(obj.proposal) return Response(serialized_obj.data) - class ProposalByUuidViewSet(viewsets.ModelViewSet): queryset = Proposal.objects.none() @@ -874,6 +867,21 @@ def internal_serializer_class(self): print(traceback.print_exc()) raise serializers.ValidationError(str(e)) + @detail_route(methods=['PUT'], detail=True) + @renderer_classes((JSONRenderer,)) + @basic_exception_handler + def reinstate_wl_allocation(self, request, *args, **kwargs): + instance = self.get_object() + if is_internal(request) and instance.child_obj.code == MooringLicenceApplication.code and instance.processing_status in [Proposal.PROCESSING_STATUS_DISCARDED,]: + # Internal user is accessing + # Proposal is ML application and the status of it is 'discarded' + wlallocation = instance.child_obj.reinstate_wl_allocation(request) + return Response({'lodgement_number': wlallocation.lodgement_number}) # TODO + else: + msg = f'This application: [{instance}] does not meet the conditions to put the original WLAllocation to the waiting list queue.' + logger.warn(msg) + raise serializers.ValidationError(msg) + @detail_route(methods=['POST'], detail=True) @renderer_classes((JSONRenderer,)) @basic_exception_handler @@ -1075,7 +1083,6 @@ def assign_request_user(self, request, *args, **kwargs): serializer_class = self.internal_serializer_class() serializer = serializer_class(instance,context={'request':request}) return Response(serializer.data) - raise serializers.ValidationError(str(e)) @detail_route(methods=['POST',], detail=True) @basic_exception_handler @@ -1388,14 +1395,6 @@ def destroy(self, request, *args, **kwargs): else: instance.destroy(request, *args, **kwargs) - ## ML - # if type(instance.child_obj) == MooringLicenceApplication and instance.waiting_list_allocation: - # pass - # instance.waiting_list_allocation.internal_status = 'waiting' - # current_datetime = datetime.now(pytz.timezone(TIME_ZONE)) - # instance.waiting_list_allocation.wla_queue_date = current_datetime - # instance.waiting_list_allocation.save() - # instance.waiting_list_allocation.set_wla_order() return Response() @detail_route(methods=['POST',], detail=True) diff --git a/mooringlicensing/components/proposals/models.py b/mooringlicensing/components/proposals/models.py index d513d48a2..8e048458d 100644 --- a/mooringlicensing/components/proposals/models.py +++ b/mooringlicensing/components/proposals/models.py @@ -415,6 +415,7 @@ def get_latest_vessel_ownership_by_vessel(self, vessel): if self.previous_application.vessel_ownership: if self.previous_application.vessel_ownership.vessel == vessel: # Same vessel is found. + # TODO: Check if vessel_ownership is valid return self.previous_application.vessel_ownership else: # vessel of the previous application is differenct vessel. Search further back. @@ -461,7 +462,7 @@ def withdraw(self, request, *args, **kwargs): self.processing_status = Proposal.PROCESSING_STATUS_DISCARDED self.save() logger.info(f'Status: [{self.processing_status}] has been set to the proposal: [{self}].') - self.log_user_action(ProposalUserAction.ACTION_WITHDRAW_PROPOSAL.format(self.lodgement_number, request)) + self.log_user_action(ProposalUserAction.ACTION_WITHDRAW_PROPOSAL.format(self.lodgement_number), request) # Perform post-processing for each application type after discarding. self.child_obj.process_after_withdrawn() @@ -470,7 +471,7 @@ def destroy(self, request, *args, **kwargs): self.processing_status = Proposal.PROCESSING_STATUS_DISCARDED self.save() logger.info(f'Status: [{self.processing_status}] has been set to the proposal: [{self}].') - self.log_user_action(ProposalUserAction.ACTION_DISCARD_PROPOSAL.format(self.lodgement_number, request)) + self.log_user_action(ProposalUserAction.ACTION_DISCARD_PROPOSAL.format(self.lodgement_number), request) # Perform post-processing for each application type after discarding. self.child_obj.process_after_discarded() @@ -1373,9 +1374,9 @@ def move_to_status(self, request, status, approver_comment): # Create a log entry for the proposal if self.processing_status == self.PROCESSING_STATUS_WITH_ASSESSOR: - self.log_user_action(ProposalUserAction.ACTION_BACK_TO_PROCESSING.format(self.id), request) + self.log_user_action(ProposalUserAction.ACTION_BACK_TO_PROCESSING.format(self.lodgement_number), request) elif self.processing_status == self.PROCESSING_STATUS_WITH_ASSESSOR_REQUIREMENTS: - self.log_user_action(ProposalUserAction.ACTION_ENTER_REQUIREMENTS.format(self.id), request) + self.log_user_action(ProposalUserAction.ACTION_ENTER_REQUIREMENTS.format(self.lodgement_number), request) def reissue_approval(self, request): with transaction.atomic(): @@ -1671,6 +1672,7 @@ def final_approval_for_WLA_AAA(self, request, details=None): ) if created: logger.info(f'New approval: [{approval}] has been created.') + approval.log_user_action(f'New approval: {approval} has been created.', request) self.approval = approval self.save() @@ -1708,11 +1710,11 @@ def final_approval_for_WLA_AAA(self, request, details=None): # Log proposal action if details: # When not auto-approve - self.log_user_action(ProposalUserAction.ACTION_APPROVED.format(self.id), request) + self.log_user_action(ProposalUserAction.ACTION_APPROVED.format(self.lodgement_number), request) # applicant_field.log_user_action(ProposalUserAction.ACTION_APPROVED.format(self.id), request) else: # When auto approve - self.log_user_action(ProposalUserAction.ACTION_AUTO_APPROVED.format(self.id),) + self.log_user_action(ProposalUserAction.ACTION_AUTO_APPROVED.format(self.lodgement_number),) # applicant_field.log_user_action(ProposalUserAction.ACTION_AUTO_APPROVED.format(self.id),) # set proposal status to approved - can change later after manage_stickers @@ -3261,7 +3263,7 @@ def get_mooring_authorisation_preference(self): def process_after_submit(self, request): self.lodgement_date = datetime.datetime.now(pytz.timezone(TIME_ZONE)) self.save() - self.log_user_action(ProposalUserAction.ACTION_LODGE_APPLICATION.format(self.id), request) + self.log_user_action(ProposalUserAction.ACTION_LODGE_APPLICATION.format(self.lodgement_number), request) mooring_preference = self.get_mooring_authorisation_preference() # if mooring_preference.lower() != 'ria' and self.proposal_type.code in [PROPOSAL_TYPE_NEW,]: @@ -3507,6 +3509,11 @@ class MooringLicenceApplication(Proposal): # This uuid is used to generate the URL for the ML document upload page uuid = models.UUIDField(default=uuid.uuid4, editable=False) + def reinstate_wl_allocation(self, request): + wlallocation = self.waiting_list_allocation.reinstate_wla_order() + self.log_user_action(f'Reinstate Waiting List Alocation: {wlallocation.lodgement_number} back to the waiting list queue.', request) + return wlallocation + def validate_against_existing_proposals_and_approvals(self): from mooringlicensing.components.approvals.models import Approval, ApprovalHistory, WaitingListAllocation, MooringLicence today = datetime.datetime.now(pytz.timezone(TIME_ZONE)).date() @@ -3764,7 +3771,7 @@ def send_emails_after_payment_success(self, request): def process_after_submit(self, request): self.lodgement_date = datetime.datetime.now(pytz.timezone(TIME_ZONE)) - self.log_user_action(ProposalUserAction.ACTION_LODGE_APPLICATION.format(self.id), request) + self.log_user_action(ProposalUserAction.ACTION_LODGE_APPLICATION.format(self.lodgement_number), request) if self.proposal_type in (ProposalType.objects.filter(code__in=[PROPOSAL_TYPE_RENEWAL, PROPOSAL_TYPE_AMENDMENT,])): # Renewal diff --git a/mooringlicensing/frontend/mooringlicensing/src/components/common/applicant.vue b/mooringlicensing/frontend/mooringlicensing/src/components/common/applicant.vue index caefeead5..2bc8b2e35 100755 --- a/mooringlicensing/frontend/mooringlicensing/src/components/common/applicant.vue +++ b/mooringlicensing/frontend/mooringlicensing/src/components/common/applicant.vue @@ -431,112 +431,112 @@ export default { // }, computed: { applicant_first_name: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.first_name } else { return this.email_user.first_name } }, applicant_last_name: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.last_name } else { return this.email_user.last_name } }, applicant_dob: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.dob } else { return this.email_user.dob } }, contact_mobile_number: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.mobile_number } else { return this.email_user.mobile_number } }, contact_phone_number: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.phone_number } else { return this.email_user.phone_number } }, residential_line1: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.residential_line1 } else { return this.email_user.residential_address.line1 } }, residential_locality: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.residential_locality } else { return this.email_user.residential_address.locality } }, residential_state: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.residential_state } else { return this.email_user.residential_address.state } }, residential_postcode: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.residential_postcode } else { return this.email_user.residential_address.postcode } }, residential_country: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.residential_country } else { return this.email_user.residential_address.country } }, postal_same_as_residential: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.postal_same_as_residential } else { return this.email_user.postal_same_as_residential } }, postal_line1: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.postal_line1 } else { return this.email_user.postal_address.line1 } }, postal_locality: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.postal_locality } else { return this.email_user.postal_address.locality } }, postal_state: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.postal_state } else { return this.email_user.postal_address.state } }, postal_postcode: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.postal_postcode } else { return this.email_user.postal_address.postcode } }, postal_country: function(){ - if (this.proposal){ + if (this.proposal && this.proposal.proposal_applicant){ return this.proposal.proposal_applicant.postal_country } else { return this.email_user.postal_address.country diff --git a/mooringlicensing/frontend/mooringlicensing/src/components/common/table_approvals.vue b/mooringlicensing/frontend/mooringlicensing/src/components/common/table_approvals.vue index 5358abcd4..bdb868eb4 100644 --- a/mooringlicensing/frontend/mooringlicensing/src/components/common/table_approvals.vue +++ b/mooringlicensing/frontend/mooringlicensing/src/components/common/table_approvals.vue @@ -945,8 +945,6 @@ export default { */ offerMooringLicence: function(id){ - console.log('offerMooringLicence') - console.log(id) this.selectedWaitingListAllocationId = parseInt(id); this.uuid++; this.$nextTick(() => { diff --git a/mooringlicensing/frontend/mooringlicensing/src/components/common/table_proposals.vue b/mooringlicensing/frontend/mooringlicensing/src/components/common/table_proposals.vue index 0b47534ea..6b8f7b42d 100644 --- a/mooringlicensing/frontend/mooringlicensing/src/components/common/table_proposals.vue +++ b/mooringlicensing/frontend/mooringlicensing/src/components/common/table_proposals.vue @@ -285,9 +285,13 @@ export default { } } if (full.application_type_dict.code === 'mla' && full.processing_status === 'Draft'){ - // Only mooring licensing draft application can be withdrawn + // Only ML draft application can be withdrawn links += `Withdraw
` } + if (full.application_type_dict.code === 'mla' && full.processing_status === 'Discarded'){ + // Only ML discarded application can make originated WL allocation reinstated + links += `Reinstate WL allocation
` + } } if (vm.is_external){ if (full.can_user_edit) { @@ -453,6 +457,33 @@ export default { name: 'apply_proposal' }) }, + reinstateWLAllocation: function(proposal_id){ + let vm = this; + swal({ + title: "Reinstate Waiting List Allocation", + text: "Are you sure you want to put the originated waiting list allocation back on the waiting list?", + type: "warning", + showCancelButton: true, + confirmButtonText: 'Reinstate WL Allocation', + confirmButtonColor:'#dc3545' + }).then(() => { + vm.$http.put('/api/proposal/' + proposal_id + '/reinstate_wl_allocation/') + .then((response) => { + console.log({response}) + swal( + 'Reinstated', + 'Originated waiting list allocation has been reinstated', + 'success' + ) + vm.$refs.application_datatable.vmDataTable.draw(); + }, (error) => { + helpers.processError(error) + }); + },(error) => { + + }); + + }, withdrawProposal: function(proposal_id){ let vm = this; swal({ @@ -574,6 +605,11 @@ export default { let id = $(this).attr('data-withdraw-proposal'); vm.withdrawProposal(id) }) + vm.$refs.application_datatable.vmDataTable.on('click', 'a[data-reinstate-wl-allocation]', function(e) { + e.preventDefault(); + let id = $(this).attr('data-reinstate-wl-allocation'); + vm.reinstateWLAllocation(id) + }) }, }, created: function(){