From e2a731ee4e84ae54b233894c65a2030fe7e18f7e Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 6 Nov 2020 11:00:00 +0800 Subject: [PATCH 01/23] WildlifeCompliance: ADD reset character ID check function. --- .../components/applications/api.py | 20 ++++++++++++++++ .../components/applications/models.py | 24 +++++++++++++++++++ .../internal/applications/application.vue | 23 ++++++++++++++++-- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/wildlifecompliance/components/applications/api.py b/wildlifecompliance/components/applications/api.py index 7b49f761a..2727d12b6 100644 --- a/wildlifecompliance/components/applications/api.py +++ b/wildlifecompliance/components/applications/api.py @@ -1119,6 +1119,26 @@ def accept_character_check(self, request, *args, **kwargs): print(traceback.print_exc()) raise serializers.ValidationError(str(e)) + @detail_route(methods=['POST', ]) + def reset_character_check(self, request, *args, **kwargs): + try: + instance = self.get_object() + instance.reset_character_check(request) + + return Response( + {'character_check_status': instance.character_check_status}, + status=status.HTTP_200_OK + ) + 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 accept_return_check(self, request, *args, **kwargs): try: diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index 193e328da..d8049a267 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -1374,6 +1374,30 @@ def accept_character_check(self, request): ApplicationUserAction.ACTION_ACCEPT_CHARACTER.format( self.id), request) + def reset_character_check(self, request): + self.character_check_status = \ + Application.CHARACTER_CHECK_STATUS_NOT_CHECKED + + self.save() + # Create a log entry for the application + self.log_user_action( + ApplicationUserAction.ACTION_RESET_CHARACTER.format( + self.id), request) + # Create a log entry for the applicant (submitter, organisation or + # proxy) + if self.org_applicant: + self.org_applicant.log_user_action( + ApplicationUserAction.ACTION_RESET_CHARACTER.format( + self.id), request) + elif self.proxy_applicant: + self.proxy_applicant.log_user_action( + ApplicationUserAction.ACTION_RESET_CHARACTER.format( + self.id), request) + else: + self.submitter.log_user_action( + ApplicationUserAction.ACTION_RESET_CHARACTER.format( + self.id), request) + def accept_return_check(self, request): self.return_check_status = Application.RETURN_CHECK_STATUS_ACCEPTED self.save() diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue index 90c4c6c01..fe73754e8 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue @@ -483,7 +483,7 @@
- +
@@ -1057,6 +1057,26 @@ export default { },(error) => { }); }, + resetCharacterRequest: async function() { + let vm = this; + swal({ + title: "Reset Character Check", + text: "Are you sure you want to reset this Character Check?", + type: "question", + showCancelButton: true, + confirmButtonText: 'Accept' + }).then(async (result) => { + if (result.value) { + await vm.$http.post(helpers.add_endpoint_json(api_endpoints.applications,(vm.application.id+'/reset_character_check'))) + .then((response) => { + vm.setCharacterCheckStatus(response.body.character_check_status); + }, (error) => { + console.log(error); + }); + } + },(error) => { + }); + }, acceptReturnRequest: async function() { let vm = this; swal({ @@ -1402,7 +1422,6 @@ export default { }); }, initialiseAssignedOfficerSelect: function(reinit=false){ - console.log('initialiseAssigned') let vm = this; if (reinit){ $(vm.$refs.assigned_officer).data('select2') ? $(vm.$refs.assigned_officer).select2('destroy'): ''; From b701ef83ab0e84d15df8269d5b38a40caa33f9fc Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 6 Nov 2020 11:32:06 +0800 Subject: [PATCH 02/23] WildlifeCompliance: FIX assessment conditions page when assigned to another officer. --- .../applications/application_conditions.vue | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_conditions.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_conditions.vue index 6ffd32d88..8838cca08 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_conditions.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_conditions.vue @@ -194,6 +194,14 @@ export default { return false; } + // check activity is not assigned to another officer. + var selectedActivity = this.application.activities.find(activity => { + return activity.licence_activity === this.selected_activity_tab_id; + }); + if (selectedActivity.assigned_officer != null && selectedActivity.assigned_officer !== this.current_user.id) { + return false; + }; + let required_role = false; if (this.activity.processing_status.id === 'with_assessor') { let assessment = this.canEditAssessmentFor(this.selected_activity_tab_id) @@ -224,6 +232,14 @@ export default { return false; } + // check activity is not assigned to another officer. + var selectedActivity = this.application.activities.find(activity => { + return activity.licence_activity === this.selected_activity_tab_id; + }); + if (selectedActivity.assigned_officer != null && selectedActivity.assigned_officer !== this.current_user.id) { + return false; + }; + let required_role = false; switch(this.activity.processing_status.id) { case 'with_assessor': From 3dbee9ff5ca153e504ebac82eec5463579658b88 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 6 Nov 2020 13:32:35 +0800 Subject: [PATCH 03/23] WildlifeCompliance: FIX assessment page when assigned to someone else Recall/Resend unavailable. --- .../applications/application_assessments.vue | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_assessments.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_assessments.vue index 6c7a664c7..1d73de367 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_assessments.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application_assessments.vue @@ -229,6 +229,14 @@ export default { // officer has no permissions for licence activity. return null; } + // check activity is not assigned to another officer. + var selectedActivity = this.application.activities.find(activity => { + return activity.licence_activity === this.selected_activity_tab_id; + }); + if (selectedActivity.assigned_officer != null && selectedActivity.assigned_officer !== this.current_user.id) { + return false; + }; + return this.sendToAssessorActivities.filter(visible_activity => { if(visible_activity.id != this.selected_activity_tab_id) { return false; @@ -317,6 +325,15 @@ export default { return assessment.assigned_assessor && assessment.assigned_assessor.id===this.current_user.id }, userHasRole: function(role, activity_id) { + + // check activity is not assigned to another officer. + var selectedActivity = this.application.activities.find(activity => { + return activity.licence_activity === this.selected_activity_tab_id; + }); + if (selectedActivity.assigned_officer != null && selectedActivity.assigned_officer !== this.current_user.id) { + return false; + }; + return this.hasRole(role, activity_id); }, getVisibleConditionsFor: function(for_role, processing_status, tab_id) { From 384373fd2be23e4c0830d766475c4c3fad0820c2 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 6 Nov 2020 14:32:59 +0800 Subject: [PATCH 04/23] WildlifeCompliance: FIX spinners when requesting an amendment. --- .../internal/applications/application.vue | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue index fe73754e8..e0281b552 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue @@ -105,7 +105,9 @@
-
+ + +
@@ -117,7 +119,8 @@
- + +
@@ -522,8 +525,9 @@ Updated application fee: {{adjusted_application_fee | toCurrency}} licence fee: {{application.licence_fee | toCurrency}} - - + + +

@@ -634,6 +638,8 @@ export default { contacts_table_id: vm._uid+'contacts-table', application_assessor_datatable:vm._uid+'assessment-table', spinner: false, + request_spinner: false, + condition_spinner: false, contacts_options:{ language: { processing: "" @@ -887,6 +893,12 @@ export default { showSpinner: function() { return this.spinner }, + showRequestSpinner: function() { + return this.request_spinner + }, + showConditionSpinner: function() { + return this.condition_spinner + }, showReturnCheckButton: function() { // return this.application.is_return_check_accept ? false : true return this.return_check_status !== 'accepted'; @@ -1118,9 +1130,10 @@ export default { }); }, amendmentRequest: async function(){ + this.request_spinner = true let vm = this; let is_saved = await vm.save_wo(); - + this.request_spinner = false if (is_saved){ vm.$refs.amendment_request.amendment.text = ''; vm.$refs.amendment_request.isModalOpen = true; @@ -1128,9 +1141,9 @@ export default { }, togglesendtoAssessor: async function(){ - this.spinner = true + this.condition_spinner = true await this.assessmentData({ url: `/api/application/${this.application.id}/assessment_data.json` }).then( async response => { - this.spinner = false; + this.condition_spinner = false; $('#tabs-main li').removeClass('active'); this.isSendingToAssessor = !this.isSendingToAssessor; this.showingApplication = false; From d44ea10918851f3a2f0b87e8518aed044c93733b Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 6 Nov 2020 15:23:27 +0800 Subject: [PATCH 05/23] WildlifeCompliance: FIX default zero adding new return running sheet row. --- .../external/returns/enter_return_sheet.vue | 1 + .../external/returns/enter_return_sheet_entry.vue | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet.vue index fea00fac7..72b78802e 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet.vue @@ -232,6 +232,7 @@ export default { self.$refs.sheet_entry.entryTotal = self.sheet_total; self.$refs.sheet_entry.currentStock = self.sheet_total; self.$refs.sheet_entry.initialQty = '0'; + self.$refs.sheet_entry.entryQty = '0'; // for editing purposes. self.$refs.sheet_entry.entryComment = ''; self.$refs.sheet_entry.entryLicence = ''; self.$refs.sheet_entry.entryDateTime = ''; diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet_entry.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet_entry.vue index 1c2fc925b..05a2de145 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet_entry.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/external/returns/enter_return_sheet_entry.vue @@ -48,7 +48,12 @@
- + +
+
+
+
+
@@ -57,7 +62,11 @@
- +
+
+
+
+
From 4a6b65eaac21e4f5361d72ca64dc6f590f49d1d3 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 6 Nov 2020 16:44:09 +0800 Subject: [PATCH 06/23] WildlifeCompliance: FIX application editable when user is not the assigned officer. --- .../wildlifecompliance/src/store/modules/renderer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js index 392a85f74..450d4ed51 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js +++ b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/renderer.js @@ -65,8 +65,12 @@ export const rendererStore = { return state.form_data[component_name] ? state.form_data[component_name].value : null; }, isComponentEditableForOfficer: (state, getters, rootState, rootGetters) => { + let activity = rootGetters.application.activities.filter(activity => { + return activity.licence_activity === rootGetters.selected_activity_tab_id; + })[0]; + let is_assigned = activity.assigned_officer == null ? false : activity.assigned_officer === getters.current_user.id ? false : true // function to enforce editable rendered components for officer. - return rootGetters.canAssignOfficerFor(rootGetters.selected_activity_tab_id); // check permissions. + return !is_assigned && rootGetters.canAssignOfficerFor(rootGetters.selected_activity_tab_id); // check permissions. }, allCurrentActivitiesWithAssessor: (state, getters, rootState, rootGetters) => { // list of activities with assessment sent for the current user. From ff2c3664f0403a33eb40714b16ec51553bf30d0e Mon Sep 17 00:00:00 2001 From: sharpeez Date: Tue, 10 Nov 2020 15:06:53 +0800 Subject: [PATCH 07/23] WildlifeCompliance: UPDATE application load to exclude estimate calc. --- .../components/applications/models.py | 25 +++++++++++++++++++ .../components/applications/payments.py | 15 ++++++----- .../components/applications/serializers.py | 25 +++---------------- .../components/applications/services.py | 1 + .../src/store/modules/application.js | 8 ++++-- 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index d8049a267..8c62b2a58 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -809,6 +809,31 @@ def licence_category(self): except AttributeError: return '' + def set_property_cache_licence_fee(self, licence_fee): + ''' + Setter for licence fee on the property cache. + + NOTE: only used for presentation purposes. + ''' + if self.id: + self.property_cache['licence_fee'] = licence_fee + + def get_property_cache_licence_fee(self): + ''' + Getter for licence fee on the property cache. + + NOTE: only used for presentation purposes. + ''' + fee = 0 + try: + + fee = self.property_cache['licence_fee'] + + except KeyError: + pass + + return fee + def set_activity_processing_status(self, activity_id, processing_status): if not activity_id: logger.error("Application: %s cannot update processing status (%s) for an empty activity_id!" % diff --git a/wildlifecompliance/components/applications/payments.py b/wildlifecompliance/components/applications/payments.py index 318df290a..d3b8e0fa5 100644 --- a/wildlifecompliance/components/applications/payments.py +++ b/wildlifecompliance/components/applications/payments.py @@ -1375,17 +1375,20 @@ def set_application_fee_from_attributes(self, attributes): paid_lic_tot = 0 paid_app_tot = 0 for p in activity.proposed_purposes.all(): - fees_lic += p.licence_fee if p.is_payable \ - else 0 - fees_app += p.application_fee if p.is_payable \ - else 0 + # fees_lic += p.licence_fee if p.is_payable \ + # else 0 + # fees_app += p.application_fee if p.is_payable \ + # else 0 paid_lic_tot += p.total_paid_adjusted_licence_fee \ if p.is_payable else 0 paid_app_tot += p.total_paid_adjusted_application_fee \ if p.is_payable else 0 - fees_app_new = fees_app_adj - paid_app_tot - fees_lic_new = fees_lic_adj - paid_lic_tot + fees_app = fees_app_adj - paid_app_tot + fees_lic = fees_lic_adj - paid_lic_tot + + fees_app_new += fees_app + fees_lic_new += fees_lic else: fees_app_new += fees_app_adj diff --git a/wildlifecompliance/components/applications/serializers.py b/wildlifecompliance/components/applications/serializers.py index 010a7fe69..5907d3931 100644 --- a/wildlifecompliance/components/applications/serializers.py +++ b/wildlifecompliance/components/applications/serializers.py @@ -1195,28 +1195,11 @@ def get_adjusted_paid_amount(self, obj): """ Total paid amount adjusted for presentation purposes. Only applicable for internal officers to enforce refundable payments. - - TODO: REDUNDANT """ - # adjusted = None - # # Include previously paid amounts for amendments. - # adjusted = obj.total_paid_amount + obj.previous_paid_amount - - # if obj.processing_status == \ - # Application.PROCESSING_STATUS_UNDER_REVIEW: - # # when Under Review, fee for amendment is paid and included in - # # previous paid amount as well as total paid amount. Need to - # # exclude this previous amount. - # adjusted = adjusted - obj.previous_paid_amount - - # # licence fee is paid with the application fee. Licence fee needs - # # to be excluded from total paid for application. - # licence_fee_paid = 0 - # for activity in obj.activities: - # licence_fee_paid += activity.licence_fee - # adjusted = adjusted - licence_fee_paid - - adjusted = 0 + adjusted = { + 'application_fee': obj.application_fee, + 'licence_fee': obj.get_property_cache_licence_fee() + } return adjusted diff --git a/wildlifecompliance/components/applications/services.py b/wildlifecompliance/components/applications/services.py index f57daf230..a0693ce88 100644 --- a/wildlifecompliance/components/applications/services.py +++ b/wildlifecompliance/components/applications/services.py @@ -1218,6 +1218,7 @@ def do_update_dynamic_attributes(application, fee_exemption=False): # Update application and licence fees fees = dynamic_attributes['fees'] application.application_fee = fees['application'] + application.set_property_cache_licence_fee(fees['licence']) application.save() diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js index d42106787..3ac9b2544 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js +++ b/wildlifecompliance/frontend/wildlifecompliance/src/store/modules/application.js @@ -166,12 +166,16 @@ export const applicationStore = { commit(UPDATE_PROXY_APPLICANT, {key: 'address', value: state.proxy_address}); }; }, - loadApplication({ dispatch, commit }, { url }) { + loadApplication({ dispatch, state, commit }, { url }) { return new Promise((resolve, reject) => { Vue.http.get(url).then(res => { dispatch('setOriginalApplication', res.body); dispatch('setApplication', res.body); - dispatch('refreshApplicationFees'); + dispatch('setApplication', { + ...state.application, + application_fee: res.body.adjusted_paid_amount.application_fee, + licence_fee: res.body.adjusted_paid_amount.licence_fee + }); for(let form_data_record of res.body.data) { dispatch('setFormValue', { key: form_data_record.field_name, From e2025e598c2a1c061f94323e56f9f08f0bce0235 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Wed, 11 Nov 2020 09:48:21 +0800 Subject: [PATCH 08/23] WildlifeCompliance: FIX external application estimate fee calculation. --- wildlifecompliance/components/applications/models.py | 3 ++- .../wildlifecompliance/src/components/external/application.vue | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index 8c62b2a58..e0ceadc3c 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -816,7 +816,8 @@ def set_property_cache_licence_fee(self, licence_fee): NOTE: only used for presentation purposes. ''' if self.id: - self.property_cache['licence_fee'] = licence_fee + data = str(licence_fee) + self.property_cache['licence_fee'] = data def get_property_cache_licence_fee(self): ''' diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/external/application.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/external/application.vue index 6460753a9..862651058 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/external/application.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/external/application.vue @@ -136,6 +136,7 @@ export default { 'setApplication', 'setActivityTab', 'saveFormData', + 'refreshApplicationFees', ]), eventListeners: function(){ if(!this.tabSelected) { @@ -438,6 +439,7 @@ export default { beforeRouteEnter: function(to, from, next) { next(vm => { vm.reloadApplication(to.params.application_id); + vm.refreshApplicationFees(); }); }, updated: function(){ From 4c405164d393c71442231a038a29e824e758b68e Mon Sep 17 00:00:00 2001 From: sharpeez Date: Thu, 12 Nov 2020 11:17:13 +0800 Subject: [PATCH 09/23] WildlifeCompliance: ADD components for system maintenance. --- cron | 1 + wildlifecompliance/components/main/models.py | 25 +++++++++ .../commands/system_maintenance_check.py | 37 ++++++++++++ .../migrations/0524_systemmaintenance.py | 28 ++++++++++ wildlifecompliance/settings.py | 6 +- .../templates/wildlifecompliance/base.html | 8 +++ wildlifecompliance/templatetags/users.py | 56 +++++++++++++++++++ 7 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 wildlifecompliance/management/commands/system_maintenance_check.py create mode 100644 wildlifecompliance/migrations/0524_systemmaintenance.py diff --git a/cron b/cron index dce593eba..697656474 100644 --- a/cron +++ b/cron @@ -1,2 +1,3 @@ +* * * * * root eval $(grep -v '^#' /etc/.cronenv | xargs -d "\n" -I {} echo export \"{}\" ) && cd /app && python manage_wc.py system_maintenance_check > /dev/null 2>&1 0 2 * * * root eval $(grep -v '^#' /etc/.cronenv | xargs -d "\n" -I {} echo export \"{}\" ) && cd /app && python manage_wc.py cron_tasks > logs/cron_tasks.log 2>&1 10 2 * * * root eval $(grep -v '^#' /etc/.cronenv | xargs -d "\n" -I {} echo export \"{}\" ) && cd /app && python manage_wc.py runcrons > logs/runcrons.log 2>&1 diff --git a/wildlifecompliance/components/main/models.py b/wildlifecompliance/components/main/models.py index 8fbfdb8d2..1f40253fb 100644 --- a/wildlifecompliance/components/main/models.py +++ b/wildlifecompliance/components/main/models.py @@ -10,6 +10,31 @@ logger = logging.getLogger(__name__) +@python_2_unicode_compatible +class SystemMaintenance(models.Model): + name = models.CharField(max_length=100) + description = models.TextField() + start_date = models.DateTimeField() + end_date = models.DateTimeField() + + def duration(self): + """ Duration of system maintenance (in mins) """ + return int( + (self.end_date - self.start_date).total_seconds()/60. + ) if self.end_date and self.start_date else '' + + duration.short_description = 'Duration (mins)' + + class Meta: + app_label = 'wildlifecompliance' + verbose_name_plural = "System maintenance" + + def __str__(self): + return 'System Maintenance: {} ({}) - starting {}, ending {}'.format( + self.name, self.description, self.start_date, self.end_date + ) + + @python_2_unicode_compatible class Sequence(models.Model): diff --git a/wildlifecompliance/management/commands/system_maintenance_check.py b/wildlifecompliance/management/commands/system_maintenance_check.py new file mode 100644 index 000000000..eed1fc08d --- /dev/null +++ b/wildlifecompliance/management/commands/system_maintenance_check.py @@ -0,0 +1,37 @@ +from django.core.management.base import BaseCommand +from django.conf import settings +import subprocess +import os +from wildlifecompliance.templatetags.users import system_maintenance_can_start + +import logging +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Excecuted from cron, eg: + SHELL=/bin/bash + # Execute every minute. Polls the Disturbance Admin table + # SystemMaintenance, and checks if the application can be taken down at + # the time indicated in the Admin table. + + * * * * * root cd /var/www/disturbance-dev2 + && source venv/bin/activate + && python manage_ds.py system_maintenance_check >/dev/null 2>&1 + CMD's eg: + SUPERVISOR_STOP_CMD="supervisorctl stop disturbance-dev" + SUPERVISOR_STOP_CMD="pkill -f 8499" + """ + help = 'Check Maintenance is due, and terminate uwsgi/supervisor process' + log_file = os.getcwd() + '/logs/sys_maintenance.log' + + def handle(self, *args, **options): + if system_maintenance_can_start(): + logger.info('Running command {}'.format(__name__)) + subprocess.Popen( + 'date 2>&1 | tee -a {}'.format(self.log_file), shell=True) + subprocess.Popen( + settings.SUPERVISOR_STOP_CMD + ' 2>&1 | tee -a {}'.format( + self.log_file), shell=True + ) diff --git a/wildlifecompliance/migrations/0524_systemmaintenance.py b/wildlifecompliance/migrations/0524_systemmaintenance.py new file mode 100644 index 000000000..80bbcaac0 --- /dev/null +++ b/wildlifecompliance/migrations/0524_systemmaintenance.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2020-11-12 03:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wildlifecompliance', '0523_auto_20201016_1342'), + ] + + operations = [ + migrations.CreateModel( + name='SystemMaintenance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField()), + ('start_date', models.DateTimeField()), + ('end_date', models.DateTimeField()), + ], + options={ + 'verbose_name_plural': 'System maintenance', + }, + ), + ] diff --git a/wildlifecompliance/settings.py b/wildlifecompliance/settings.py index 94fc58a1b..756a9005a 100644 --- a/wildlifecompliance/settings.py +++ b/wildlifecompliance/settings.py @@ -1,10 +1,11 @@ +from django.core.exceptions import ImproperlyConfigured +from ledger.settings_base import * + import os import confy BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) confy.read_environment_file(BASE_DIR+"/.env") os.environ.setdefault("BASE_DIR", BASE_DIR) -from django.core.exceptions import ImproperlyConfigured -from ledger.settings_base import * os.environ['LEDGER_PRODUCT_CUSTOM_FIELDS'] = "('ledger_description','quantity','price_incl_tax','price_excl_tax','oracle_code')" os.environ['LEDGER_REFUND_TRANSACTION_CALLBACK_MODULE'] = 'wildlifecompliance:wildlifecompliance.components.applications.api.application_refund_callback' @@ -12,6 +13,7 @@ ROOT_URLCONF = 'wildlifecompliance.urls' SITE_ID = 1 +SYSTEM_MAINTENANCE_WARNING = env('SYSTEM_MAINTENANCE_WARNING', 24) # hours STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles_wc') SHOW_DEBUG_TOOLBAR = env('SHOW_DEBUG_TOOLBAR', False) diff --git a/wildlifecompliance/templates/wildlifecompliance/base.html b/wildlifecompliance/templates/wildlifecompliance/base.html index fd1fb807c..d67f4378f 100644 --- a/wildlifecompliance/templates/wildlifecompliance/base.html +++ b/wildlifecompliance/templates/wildlifecompliance/base.html @@ -11,6 +11,7 @@ {% is_compliance_internal_user as is_compliance_internal_login %} {% prefer_compliance_management as prefer_compliance_management %} {% is_compliance_management_readonly_user as is_compliance_management_readonly_user %} +{% system_maintenance_due as system_maintenance_due %} @@ -182,6 +183,13 @@
{% endblock %} + + {% if system_maintenance_due %} +
+ Notice! System will be down for maintenance between {{ system_maintenance_due }} +
+ {% endif %} + {% block content %} {% endblock %} {% block modals %} diff --git a/wildlifecompliance/templatetags/users.py b/wildlifecompliance/templatetags/users.py index 737beff01..d621c7c01 100644 --- a/wildlifecompliance/templatetags/users.py +++ b/wildlifecompliance/templatetags/users.py @@ -1,5 +1,9 @@ from django.template import Library +from django.utils import timezone +from datetime import timedelta + from wildlifecompliance import helpers as wildlifecompliance_helpers +from wildlifecompliance.components.main.models import SystemMaintenance register = Library() @@ -49,3 +53,55 @@ def is_compliance_management_readonly_user(context): request = context['request'] return wildlifecompliance_helpers.is_compliance_management_readonly_user(request) + +@register.simple_tag() +def system_maintenance_due(): + ''' + Returns True (actually a time str), if within of system + maintenance due datetime. + ''' + import pytz + from django.conf import settings + + tz = pytz.timezone(settings.TIME_ZONE) + now = timezone.now() # returns UTC time + qs = SystemMaintenance.objects.filter(start_date__gte=now - timedelta( + minutes=1) + ) + if qs: + obj = qs.earliest('start_date') + if now >= obj.start_date - timedelta( + hours=settings.SYSTEM_MAINTENANCE_WARNING + ) and now <= obj.start_date + timedelta(minutes=1): + # display time in local timezone + return '{0} - {1} (Duration: {2} mins)'.format( + obj.start_date.astimezone(tz=tz).ctime(), + obj.end_date.astimezone(tz=tz).ctime(), + obj.duration() + ) + + return False + + +@register.simple_tag() +def system_maintenance_can_start(): + ''' + Returns True if current datetime is within 1 minute past scheduled + start_date. + ''' + from django.utils import timezone + from datetime import timedelta + from wildlifecompliance.components.main.models import SystemMaintenance + + now = timezone.now() # returns UTC time + qs = SystemMaintenance.objects.filter( + start_date__gte=now - timedelta(minutes=1) + ) + if qs: + obj = qs.earliest('start_date') + if now >= obj.start_date \ + and now <= obj.start_date + timedelta(minutes=1): + return True + + return False + From 5c90d23e7a32c45bcf0ec3f992034a30c6cc8fe3 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Thu, 12 Nov 2020 12:14:48 +0800 Subject: [PATCH 10/23] WildlifeCompliance: FIX unable to configure handler application_checkout error. --- wildlifecompliance/settings.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wildlifecompliance/settings.py b/wildlifecompliance/settings.py index 756a9005a..72873316a 100644 --- a/wildlifecompliance/settings.py +++ b/wildlifecompliance/settings.py @@ -1,11 +1,10 @@ -from django.core.exceptions import ImproperlyConfigured -from ledger.settings_base import * - import os import confy BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) confy.read_environment_file(BASE_DIR+"/.env") os.environ.setdefault("BASE_DIR", BASE_DIR) +from django.core.exceptions import ImproperlyConfigured +from ledger.settings_base import * os.environ['LEDGER_PRODUCT_CUSTOM_FIELDS'] = "('ledger_description','quantity','price_incl_tax','price_excl_tax','oracle_code')" os.environ['LEDGER_REFUND_TRANSACTION_CALLBACK_MODULE'] = 'wildlifecompliance:wildlifecompliance.components.applications.api.application_refund_callback' From 4ef04181a06d51cd57d2202b8eccadd7dbc65a97 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Thu, 12 Nov 2020 16:42:47 +0800 Subject: [PATCH 11/23] WildlifeCompliance: FIX deletion of default conditions with multiple activities. --- wildlifecompliance/components/applications/services.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wildlifecompliance/components/applications/services.py b/wildlifecompliance/components/applications/services.py index a0693ce88..9fe8bc8ef 100644 --- a/wildlifecompliance/components/applications/services.py +++ b/wildlifecompliance/components/applications/services.py @@ -805,7 +805,8 @@ def reset(self, licence_activity): for condition in ApplicationCondition.objects.filter( is_rendered=True, standard=True, - application=self._application + application=self._application, + licence_activity_id=licence_activity.id ): condition.delete() From ba1d6eda66aa91ae021be199812a3c8a6166e5e0 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 13 Nov 2020 10:07:06 +0800 Subject: [PATCH 12/23] WildlifeCompliance: ADD SystemMaintenance admin forms. --- wildlifecompliance/components/main/admin.py | 10 +++- wildlifecompliance/components/main/forms.py | 62 +++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 wildlifecompliance/components/main/forms.py diff --git a/wildlifecompliance/components/main/admin.py b/wildlifecompliance/components/main/admin.py index de9ef41e7..786eaf972 100644 --- a/wildlifecompliance/components/main/admin.py +++ b/wildlifecompliance/components/main/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin #from ledger.accounts.models import EmailUser -from wildlifecompliance.components.main import models +from wildlifecompliance.components.main import models, forms #from reversion.admin import VersionAdmin @@ -8,3 +8,11 @@ class GlobalSettingsAdmin(admin.ModelAdmin): list_display = ['key', 'value'] ordering = ('key',) + + +@admin.register(models.SystemMaintenance) +class SystemMaintenanceAdmin(admin.ModelAdmin): + list_display = ['name', 'description', 'start_date', 'end_date', 'duration'] + ordering = ('start_date',) + readonly_fields = ('duration',) + form = forms.SystemMaintenanceAdminForm diff --git a/wildlifecompliance/components/main/forms.py b/wildlifecompliance/components/main/forms.py new file mode 100644 index 000000000..458d9897c --- /dev/null +++ b/wildlifecompliance/components/main/forms.py @@ -0,0 +1,62 @@ +import pytz + +from django import forms +from django.conf import settings +from datetime import datetime, timedelta + +from wildlifecompliance.components.main.models import SystemMaintenance + + +class SystemMaintenanceAdminForm(forms.ModelForm): + class Meta: + model = SystemMaintenance + fields = '__all__' + + MSG = 'Start date cannot be before an existing records latest end_date. Start Date must be after' + + def clean(self): + cleaned_data = self.cleaned_data + start_date = cleaned_data.get('start_date') + end_date = cleaned_data.get('end_date') + try: + latest_obj = SystemMaintenance.objects.exclude( + id=self.instance.id + ).latest('start_date') + + except Exception: + latest_obj = SystemMaintenance.objects.none() + tz_local = pytz.timezone(settings.TIME_ZONE) # start_date.tzinfo + # tz_utc = pytz.timezone('utc') # latest_obj.start_date.tzinfo + + if latest_obj: + latest_end_date = latest_obj.end_date.astimezone(tz=tz_local) + if self.instance.id: + if start_date < latest_end_date and start_date \ + < self.instance.start_date.astimezone(tz_local): + + raise forms.ValidationError('{0} {1}'.format( + self.MSG, + latest_end_date.ctime() + )) + else: + if start_date < latest_end_date: + raise forms.ValidationError('{0} {1}'.format( + self.MSG, + latest_end_date.ctime() + )) + + if self.instance.id: + if start_date < datetime.now(tz=tz_local) - timedelta(minutes=5) \ + and start_date < self.instance.start_date.astimezone(tz_local): + + raise forms.ValidationError('Start date cannot be edited to be further in the past') + else: + if start_date < datetime.now(tz=tz_local) - timedelta(minutes=5): + raise forms.ValidationError('Start date cannot be in the past') + + if end_date < start_date: + raise forms.ValidationError('End date cannot be before start date') + + super(SystemMaintenanceAdminForm, self).clean() + + return cleaned_data From 287d91891256fe7f1d2e86cabaee8ccfebd34500 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 13 Nov 2020 13:22:12 +0800 Subject: [PATCH 13/23] WildlifeCompliance: FIX dollar symbol for cached estimate licence. --- .../components/applications/serializers.py | 4 +++- .../internal/applications/application.vue | 16 ++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/wildlifecompliance/components/applications/serializers.py b/wildlifecompliance/components/applications/serializers.py index 5907d3931..f2dbaca27 100644 --- a/wildlifecompliance/components/applications/serializers.py +++ b/wildlifecompliance/components/applications/serializers.py @@ -1196,9 +1196,11 @@ def get_adjusted_paid_amount(self, obj): Total paid amount adjusted for presentation purposes. Only applicable for internal officers to enforce refundable payments. """ + import decimal + licence_fee = decimal.Decimal(obj.get_property_cache_licence_fee() * 1) adjusted = { 'application_fee': obj.application_fee, - 'licence_fee': obj.get_property_cache_licence_fee() + 'licence_fee': licence_fee } return adjusted diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue index e0281b552..497451721 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue @@ -523,7 +523,7 @@

- Updated application fee: {{adjusted_application_fee | toCurrency}} + Updated application fee: {{application.application_fee | toCurrency}} licence fee: {{application.licence_fee | toCurrency}} @@ -683,7 +683,6 @@ export default { comms_add_url: helpers.add_endpoint_json(api_endpoints.applications,vm.$route.params.application_id+'/add_comms_log'), logs_url: helpers.add_endpoint_json(api_endpoints.applications,vm.$route.params.application_id+'/action_log'), panelClickersInitialised: false, - adjusted_application_fee: 0, } }, components: { @@ -865,7 +864,7 @@ export default { } }, updatedFee: function() { - return (this.adjusted_application_fee !== 0 || this.application.licence_fee !== 0) ? true : false + return (this.application.application_fee !== 0 || this.application.licence_fee !== 0) ? true : false }, showNavBarBottom: function() { return this.canReturnToConditions || (!this.applicationIsDraft && this.canSaveApplication) @@ -1511,9 +1510,11 @@ export default { } }, mounted: function() { + console.log('mounted') // console.log(this.application) }, updated: function(){ + console.log('updated') let vm = this; if (!vm.panelClickersInitialised){ $('.panelClicker[data-toggle="collapse"]').on('click', function () { @@ -1530,15 +1531,6 @@ export default { vm.form = document.forms.new_application; vm.eventListeners(); }); - if ((this.application.application_type.id=='amend_activity') // licence activity amendments. - || (this.application.customer_status.id=='amendment_required' || this.application.customer_status.id=='under_review')) { // requested amendments. - // fees can be adjusted by officer from selected components for requested amendments. - // this.adjusted_application_fee = this.application.application_fee - this.application.adjusted_paid_amount - } else { - // no adjustments for new applications. - // this.adjusted_application_fee = this.application.application_fee - } - this.adjusted_application_fee = this.application.application_fee }, beforeRouteEnter: function(to, from, next) { next(vm => { From ccb06e8caf4a656dbb98aca9f40158f3162e7c5f Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 13 Nov 2020 14:32:51 +0800 Subject: [PATCH 14/23] WildlifeCompliance: UPDATE centering System maintenance message. --- wildlifecompliance/templates/wildlifecompliance/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wildlifecompliance/templates/wildlifecompliance/base.html b/wildlifecompliance/templates/wildlifecompliance/base.html index d67f4378f..da28e148a 100644 --- a/wildlifecompliance/templates/wildlifecompliance/base.html +++ b/wildlifecompliance/templates/wildlifecompliance/base.html @@ -186,7 +186,7 @@ {% if system_maintenance_due %}

- Notice! System will be down for maintenance between {{ system_maintenance_due }} +
Notice! System will be down for maintenance between {{ system_maintenance_due }}
{% endif %} From aa2dfba0500cd7163af059c3f2e6be54d6d29ce5 Mon Sep 17 00:00:00 2001 From: sharpeez Date: Fri, 13 Nov 2020 15:31:48 +0800 Subject: [PATCH 15/23] WLC #364: UPDATE with Save on Assessment & Conditions button. --- .../components/applications/api.py | 22 +++++------ .../components/applications/models.py | 6 ++- .../internal/applications/application.vue | 39 ++++++++++++------- .../0525_remove_application_is_resubmitted.py | 19 +++++++++ 4 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 wildlifecompliance/migrations/0525_remove_application_is_resubmitted.py diff --git a/wildlifecompliance/components/applications/api.py b/wildlifecompliance/components/applications/api.py index 2727d12b6..876e1acf4 100644 --- a/wildlifecompliance/components/applications/api.py +++ b/wildlifecompliance/components/applications/api.py @@ -1534,20 +1534,18 @@ def assessment_data(self, request, *args, **kwargs): instance = self.get_object() with transaction.atomic(): - if instance.is_resubmitted: - checkbox = CheckboxAndRadioButtonVisitor( - instance, request.data - ) - # Set StandardCondition Fields. - for_condition_fields = StandardConditionFieldElement() - for_condition_fields.accept(checkbox) + checkbox = CheckboxAndRadioButtonVisitor( + instance, request.data + ) + # Set StandardCondition Fields. + for_condition_fields = StandardConditionFieldElement() + for_condition_fields.accept(checkbox) - # Set PromptInspection Fields. - for_inspection_fields = PromptInspectionFieldElement() - for_inspection_fields.accept(checkbox) + # Set PromptInspection Fields. + for_inspection_fields = PromptInspectionFieldElement() + for_inspection_fields.accept(checkbox) - instance.is_resubmitted = False - instance.save() + instance.save() return Response({'success': True}) except MissingFieldsException as e: diff --git a/wildlifecompliance/components/applications/models.py b/wildlifecompliance/components/applications/models.py index e0ceadc3c..713d8f0dc 100644 --- a/wildlifecompliance/components/applications/models.py +++ b/wildlifecompliance/components/applications/models.py @@ -388,7 +388,8 @@ class Application(RevisionedMixin): choices=SUBMIT_TYPE_CHOICES, default=SUBMIT_TYPE_ONLINE) property_cache = JSONField(null=True, blank=True, default={}) - is_resubmitted = models.BooleanField(default=False) + # is_resubmitted is not used and can be removed. + # is_resubmitted = models.BooleanField(default=False) class Meta: app_label = 'wildlifecompliance' @@ -1151,7 +1152,8 @@ def submit(self, request): # set is_resubmitted to True everytime. # flag is only used for assessments and conditions and is set # to false once conditions are processed. - self.is_resubmitted = True + # NOTE: self.is_resubmitted not used. + # self.is_resubmitted = True # if amendment is submitted change the status of only particular activity # else if the new application is submitted change the status of # all the activities diff --git a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue index 497451721..9b14d9027 100644 --- a/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue +++ b/wildlifecompliance/frontend/wildlifecompliance/src/components/internal/applications/application.vue @@ -526,9 +526,9 @@ Updated application fee: {{application.application_fee | toCurrency}} licence fee: {{application.licence_fee | toCurrency}} - +

@@ -592,6 +592,13 @@
+
+







+
+
+
+
+