diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml
index 611c6ae93..a5bb0c20b 100644
--- a/.github/workflows/build-release.yaml
+++ b/.github/workflows/build-release.yaml
@@ -1,18 +1,18 @@
## For each release, the value of workflow name, branches and PR_NUMBER need to be adjusted accordingly
-name: TFRS release-2.0.0
+name: TFRS release-2.1.0
on:
push:
- branches: [ release-2.0.0 ]
+ branches: [ release-2.1.0 ]
workflow_dispatch:
workflow_call:
env:
## The pull request number of the Tracking pull request to merge the release branch to main
## Also remember to update the version in .pipeline/lib/config.js
- PR_NUMBER: 1824
- RELEASE_NAME: release-2.0.0
+ PR_NUMBER: 1966
+ RELEASE_NAME: release-2.1.0
jobs:
diff --git a/.gitignore b/.gitignore
index 400ccc1dd..88ce92b61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,3 +108,4 @@ dev/
node_modules/
.sql.gz
+*.sql
\ No newline at end of file
diff --git a/.pipeline/lib/build.js b/.pipeline/lib/build.js
index ec574192b..d40086530 100755
--- a/.pipeline/lib/build.js
+++ b/.pipeline/lib/build.js
@@ -25,20 +25,6 @@ module.exports = settings => {
'GIT_REF': oc.git.ref
}
}))
-
- /** to be deleted once 2.0.0 is on prod
- //build frontend
- objects = objects.concat(oc.processDeploymentTemplate(`${templatesLocalBaseUrl}/templates/frontend/frontend-angular-app-bc.yaml`, {
- 'param':{
- 'NAME': phases[phase].name,
- 'SUFFIX': phases[phase].suffix,
- 'VERSION': phases[phase].tag,
- 'GIT_URL': oc.git.http_url,
- 'GIT_REF': oc.git.ref
- }
- }))
- */
-
objects = objects.concat(oc.processDeploymentTemplate(`${templatesLocalBaseUrl}/templates/frontend/frontend-bc-docker.yaml`, {
'param':{
diff --git a/.pipeline/lib/config.js b/.pipeline/lib/config.js
index 6a8b63a76..4fc0da821 100644
--- a/.pipeline/lib/config.js
+++ b/.pipeline/lib/config.js
@@ -1,7 +1,7 @@
'use strict';
const options= require('@bcgov/pipeline-cli').Util.parseArguments()
const changeId = options.pr //aka pull-request
-const version = '2.0.0'
+const version = '2.1.0'
const name = 'tfrs'
const ocpName = 'apps.silver.devops'
@@ -13,7 +13,7 @@ options.git.repository='tfrs'
const phases = {
build: { namespace:'0ab226-tools' , name: `${name}`, phase: 'build' , changeId:changeId, suffix: `-build-${changeId}` ,
instance: `${name}-build-${changeId}` , version:`${version}-${changeId}`, tag:`build-${version}-${changeId}`,
- releaseBranch: 'release-2.0.0'
+ releaseBranch: 'release-2.1.0'
},
dev: {namespace:'0ab226-dev' , name: `${name}`, phase: 'dev' , changeId:changeId, suffix: `-dev` ,
instance: `${name}-dev` , version:`${version}`, tag:`dev-${version}`, dbServiceName: 'tfrs-spilo',
@@ -45,7 +45,7 @@ const phases = {
schemaSpyAuditCpuRequest: '50m', schemaSpyAuditCpuLimit: '300m', schemaSpyAuditMemoryRequest: '256Mi', schemaSpyAuditMemoryLimit: '512Mi'
},
test: {namespace:'0ab226-test' , name: `${name}`, phase: 'test' , changeId:changeId, suffix: `-test` ,
- instance: `${name}-test` , version:`${version}`, tag:`test-${version}`, dbServiceName: 'patroni-master-test',
+ instance: `${name}-test` , version:`${version}`, tag:`test-${version}`, dbServiceName: 'tfrs-spilo',
frontendCpuRequest: '40m', frontendCpuLimit: '80m', frontendMemoryRequest: '60Mi', frontendMemoryLimit: '120Mi', frontendReplicas: 2,
frontendKeycloakAuthority: 'https://test.loginproxy.gov.bc.ca/auth', frontendKeycloakClientId: 'tfrs-on-gold-4308', frontendKeycloakCallbackUrl: `https://tfrs-test.${ocpName}.gov.bc.ca`,
frontendKeycloakLogoutUrl: `https://tfrs-test.${ocpName}.gov.bc.ca`,
diff --git a/backend/Dockerfile-django b/backend/Dockerfile-django
index 7ed6081ee..c81a89ba5 100644
--- a/backend/Dockerfile-django
+++ b/backend/Dockerfile-django
@@ -1,4 +1,4 @@
-FROM python:3.7-stretch
+FROM --platform=linux/amd64 python:3.7-stretch
ENV PYTHONUNBUFFERED 1
RUN mkdir /app
WORKDIR /app
diff --git a/backend/api/services/CreditTradeCommentActions.py b/backend/api/services/CreditTradeCommentActions.py
index 454c8f606..4343d64a2 100644
--- a/backend/api/services/CreditTradeCommentActions.py
+++ b/backend/api/services/CreditTradeCommentActions.py
@@ -20,7 +20,9 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
-from api.models import CreditTrade, CreditTradeComment
+from api.models import CreditTradeComment
+from api.models.CreditTrade import CreditTrade
+
from api.models.CreditTradeHistory import CreditTradeHistory
from api.permissions.CreditTradeComment import CreditTradeCommentPermissions
@@ -35,22 +37,25 @@ class CreditTradeCommentActions(object):
def available_comment_actions(request, trade: CreditTrade):
available_actions = []
- if CreditTradeCommentPermissions.user_can_comment(
- request.user, trade, False):
- available_actions.append('ADD_COMMENT')
+ if not trade.is_rescinded and trade.status.status not in ['Declined', 'Cancelled', 'Refused', 'Approved']:
- if CreditTradeCommentPermissions.user_can_comment(
- request.user, trade, True):
- available_actions.append('ADD_PRIVILEGED_COMMENT')
+ if CreditTradeCommentPermissions.user_can_comment(
+ request.user, trade, False):
+ available_actions.append('ADD_COMMENT')
- return available_actions
+ if CreditTradeCommentPermissions.user_can_comment(
+ request.user, trade, True):
+ available_actions.append('ADD_PRIVILEGED_COMMENT')
+
+ return available_actions
@staticmethod
def available_individual_comment_actions(request, comment: CreditTradeComment):
available_actions = []
+ trade = CreditTrade.objects.filter(id=comment.credit_trade_id).first()
if CreditTradeCommentPermissions.user_can_edit_comment(
- request.user, comment):
+ request.user, comment) and not trade.is_rescinded and trade.status.status not in ['Declined', 'Cancelled', 'Refused', 'Approved']:
available_actions = ['EDIT_COMMENT']
return available_actions
diff --git a/backend/api/services/SpreadSheetBuilder.py b/backend/api/services/SpreadSheetBuilder.py
index 83e919130..254e455a5 100644
--- a/backend/api/services/SpreadSheetBuilder.py
+++ b/backend/api/services/SpreadSheetBuilder.py
@@ -138,9 +138,17 @@ def add_credit_transfers(self, credit_trades, user):
credit_trade.fair_market_value_per_credit,
value_format)
- worksheet.write(row_index, 7, credit_trade.status.friendly_name)
- worksheet.write(row_index, 8, credit_trade.trade_effective_date,
- date_format)
+ if credit_trade.is_rescinded:
+ worksheet.write(row_index, 7, "Rescinded")
+ elif (not user.is_government_user) and (credit_trade.status.friendly_name == "Reviewed"):
+ worksheet.write(row_index, 7, "Signed")
+ else:
+ worksheet.write(row_index, 7, credit_trade.status.friendly_name)
+
+ if credit_trade.trade_effective_date:
+ worksheet.write(row_index, 8, credit_trade.trade_effective_date, date_format)
+ elif credit_trade.type.the_type in ["Credit Reduction", "Credit Validation"] and credit_trade.update_timestamp:
+ worksheet.write(row_index, 8, credit_trade.update_timestamp.date(), date_format)
comments = credit_trade.unprivileged_comments
diff --git a/backend/api/viewsets/ComplianceReport.py b/backend/api/viewsets/ComplianceReport.py
index 4a84e2543..34d9b0549 100644
--- a/backend/api/viewsets/ComplianceReport.py
+++ b/backend/api/viewsets/ComplianceReport.py
@@ -3,7 +3,7 @@
from django.db import transaction
from django.db.models import Count
-from django.http import JsonResponse, HttpResponse
+from django.http import HttpResponse
from rest_framework import viewsets, mixins, filters, status
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
@@ -17,15 +17,15 @@
ComplianceReportTypeSerializer, ComplianceReportListSerializer, \
ComplianceReportCreateSerializer, ComplianceReportUpdateSerializer, \
ComplianceReportDeleteSerializer, ComplianceReportDetailSerializer, \
- ComplianceReportValidationSerializer, ComplianceReportSnapshotSerializer, \
- ComplianceReportDashboardListSerializer
+ ComplianceReportValidationSerializer, ComplianceReportDashboardListSerializer
from api.serializers.ExclusionReport import \
ExclusionReportDetailSerializer, ExclusionReportUpdateSerializer, ExclusionReportValidationSerializer
from api.services.ComplianceReportService import ComplianceReportService
from api.services.ComplianceReportSpreadSheet import ComplianceReportSpreadsheet
from auditable.views import AuditableMixin
from api.paginations import BasicPagination
-from django.db.models import Q
+from django.db.models import Q, F, Value
+from django.db.models.functions import Concat
class ComplianceReportViewSet(AuditableMixin, mixins.CreateModelMixin,
@@ -69,10 +69,35 @@ def get_queryset(self):
request = self.request
if self.action == 'list' or self.action == 'paginated':
- qs = qs.annotate(Count('supplements')).filter(supplements__count=0)\
- .order_by('-compliance_period__effective_date')
-
+ qs = qs.annotate(Count('supplements')).filter(supplements__count=0)
if self.action == 'paginated':
+ sorts = request.data.get('sorts')
+ if sorts:
+ sortCondition = sorts[0].get('desc')
+ sortId = sorts[0].get('id')
+ key_maps = {'compliance-period':'compliance_period__description', 'organization':'organization__name', 'updateTimestamp':'compliance_period__effective_date'}
+ if sortId=='displayname':
+ if sortCondition:
+ qs = qs.annotate(display_name=Concat(F('type__the_type'), Value(' '), F('compliance_period__description'))).order_by('-display_name')
+ else:
+ qs = qs.annotate(display_name=Concat(F('type__the_type'), Value(' '), F('compliance_period__description'))).order_by('display_name')
+ elif sortId=='status':
+ if sortCondition:
+ qs =qs.order_by('-status__director_status__status', '-status__manager_status__status', '-status__analyst_status__status', '-status__fuel_supplier_status__status')
+ else:
+ qs = qs.order_by('status__director_status__status', 'status__manager_status__status', 'status__analyst_status__status', 'status__fuel_supplier_status__status')
+ elif sortId == 'supplemental-status':
+ # todo;
+ pass
+ elif sortId == 'current-status':
+ # todo;
+ pass
+ else:
+ sortType = "-" if sortCondition else ""
+ sortString = f"{sortType}{key_maps[sortId]}"
+ qs = qs.order_by(sortString)
+ else:
+ qs=qs.order_by('-compliance_period__effective_date')
filters = request.data.get('filters')
if filters:
for filter in filters:
@@ -81,29 +106,38 @@ def get_queryset(self):
if id and value:
if id == 'compliance-period':
qs = qs.filter(
- compliance_period__description__exact=value)
+ compliance_period__description__icontains=value)
elif id == 'organization':
qs = qs.filter(
organization__name__icontains=value)
elif id == 'displayname':
- qs = qs.filter(Q(nickname__isnull=True) | Q(
- nickname__icontains=value))
- # possible todo: deal with case where generated nicknames are used
+ qs = self.filter_displayname(qs, value.lower())
elif id == 'status':
qs = self.filter_compliance_status(
qs, value.lower())
elif id == 'supplemental-status':
- # todo; same as the todo above, along with the fact that we'll have to somehow define (annotate)
- # a deepest_supplemental_report field (via some sort of aggregation) which we can then filter on
+ qs = self.filter_supplemental_status(
+ qs, value.lower())
pass
elif id == 'current-status':
- # todo; same as the todo above
+ qs = self.filter_current_status(
+ qs, value.lower())
pass
elif id == 'updateTimestamp':
query = self.filter_timestamp(value)
qs = qs.filter(query)
return qs
+ def filter_displayname(self, qs, value):
+ if 'exclusion report'.find(value) != -1:
+ qs = qs.filter(Q(type__the_type='Exclusion Report'))
+ elif 'compliance report'.find(value) != -1:
+ qs = qs.filter(Q(type__the_type='Compliance Report'))
+ else:
+ qs = qs.annotate(display_name=Concat(F('type__the_type'), Value(' for '), F('compliance_period__description'))).filter(display_name__icontains=value)
+
+ return qs
+
def filter_timestamp(self, date):
date_query = None
date_tuple = date.split('-')
@@ -130,46 +164,88 @@ def filter_timestamp(self, date):
return date_query
def filter_compliance_status(self, qs, value):
- if value in 'recommended':
- return qs.filter(
- (Q(status__manager_status__status__icontains=value) |
- Q(status__analyst_status__status__icontains=value)) &
- (~Q(status__director_status__status__icontains='Accepted') &
- ~Q(status__director_status__status__icontains='Rejected'))
- )
- if value in 'supplemental requested':
+ if 'submitted'.find(value) != -1:
+ return qs.filter(
+ Q(status__analyst_status__status='Unreviewed') &
+ Q(status__director_status__status='Unreviewed') &
+ Q(status__fuel_supplier_status__status='Submitted') &
+ Q(status__manager_status__status='Unreviewed')
+ )
+
+ if 'accepted'.find(value) != -1:
return qs.filter(
- Q(status__manager_status__status__icontains=value) |
- Q(status__analyst_status__status__icontains=value)
+ Q(status__director_status__status='Accepted')
)
- if value in 'accepted':
+
+ if 'supplemental requested'.find(value) != -1:
return qs.filter(
- Q(status__director_status__status__icontains=value)
+ Q(status__manager_status__status='Requested Supplemental') |
+ Q(status__analyst_status__status='Requested Supplemental')
)
- if value in 'rejected':
+
+ if 'rejected'.find(value) != -1:
return qs.filter(
- Q(status__director_status__status__icontains=value)
+ Q(status__director_status__status='Rejected')
)
- if value in 'recommended acceptance - manager':
+
+ if value == 'recommended acceptance - manager' or 'manager'.find(value) != -1:
return qs.filter(
- Q(status__manager_status__status__icontains=value)
+ Q(status__manager_status__status='Recommended') &
+ ~Q(status__director_status__status='Accepted') &
+ ~Q(status__director_status__status='Rejected') &
+ ~Q(status__analyst_status__status='Requested Supplemental')
)
- if value in 'recommended rejection - manager':
+
+ if value == 'recommended acceptance - analyst' or 'analyst'.find(value) != -1:
return qs.filter(
- Q(status__manager_status__status__icontains=value)
+ Q(status__analyst_status__status='Recommended') &
+ Q(status__director_status__status='Unreviewed') &
+ Q(status__manager_status__status='Unreviewed')
)
- if value in 'recommended acceptance - analyst':
+
+ if value == 'recommended rejection - manager' or 'rejection'.find(value) != -1:
return qs.filter(
- Q(status__analyst_status__status__icontains=value)
+ Q(status__manager_status__status='Not Recommended')
)
- if value in 'recommended rejection - analyst':
+ if value == 'recommended rejection - analyst' or 'rejection'.find(value) != -1:
return qs.filter(
- Q(status__analyst_status__status__icontains=value)
+ Q(status__analyst_status__status='Not Recommended')
)
- return qs.filter(
- Q(status__fuel_supplier_status__status__icontains=value)
- )
+
+ return qs
+
+ def filter_supplemental_status(self, qs, value):
+ try:
+ latest_supplementals = self.get_latest_supplemental_reports()
+ ids = [s.id for s in latest_supplementals]
+ supplemental_reports = ComplianceReport.objects.filter(id__in=ids)
+ qs = self.filter_compliance_status(supplemental_reports, value)
+ except Exception as e:
+ print(e)
+ return qs
+
+ def filter_current_status(self, qs, value):
+ try:
+ latest_supplementals = self.get_latest_supplemental_reports()
+ ids = [s.id for s in latest_supplementals]
+ supplemental_reports = ComplianceReport.objects.filter(id__in=ids)
+ original_reports = qs.filter(Q(supplemental_reports=None))
+ unique_reports = original_reports | supplemental_reports
+ qs = self.filter_compliance_status(unique_reports, value)
+ except Exception as e:
+ print(e)
+ return qs
+
+ def get_latest_supplemental_reports(self):
+ latest_supplementals = ComplianceReport.objects.raw("""
+ select distinct on (p.id) c.*
+ from compliance_report p
+ left join compliance_report c on p.id = c.supplements_id
+ where c.status_id is not NULL
+ order by p.id desc, c.create_timestamp desc
+ """)
+ return latest_supplementals
def get_simple_queryset(self):
"""
diff --git a/backend/api/viewsets/Document.py b/backend/api/viewsets/Document.py
index 29a5703f7..08587cb53 100644
--- a/backend/api/viewsets/Document.py
+++ b/backend/api/viewsets/Document.py
@@ -112,7 +112,64 @@ def get_queryset(self):
)
request = self.request
if request.path.endswith('paginated') and request.method == 'POST':
- qs = qs.order_by('-id')
+ sort = request.data.get('sort')
+ filters = request.data.get('filters')
+ key_maps = {'title':'title', 'status':'status__status', 'attachment-type':'type__description', 'updateTimestamp': 'update_timestamp', 'organization':'create_user', 'id': 'id', 'credit-transaction-id':'credit_trades'}
+ if sort:
+ sortCondition = sort[0].get('desc')
+ sortId = sort[0].get('id')
+ sortType = "-" if sortCondition else ""
+ sortString = f"{sortType}{key_maps[sortId]}"
+ qs = qs.order_by(sortString)
+ if filters:
+ for filter in filters:
+ id = filter.get('id')
+ value = filter.get('value')
+ if id and value:
+ if id == 'id':
+ qs = qs.filter(id__icontains = value)
+ if id == 'organization':
+ organization_split = value.split()
+ q_object = None
+ for x in organization_split:
+ q_sub_object = Q(create_user__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
+ if id =='status':
+ qs = qs.filter(status__status__icontains = value)
+ if id == 'attachment-type':
+ type_split = value.split()
+ q_object = None
+ for x in type_split:
+ q_sub_object = Q(type__description__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
+ if id == 'title':
+ title_split = value.split()
+ q_object = None
+ for x in title_split:
+ q_sub_object = Q(title__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
+ if id == 'updateTimestamp':
+ date_split = value.split("-")
+ q_object = None
+ for x in date_split:
+ q_sub_object = Q(update_timestamp__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
return qs
def perform_create(self, serializer):
diff --git a/backend/api/viewsets/Notification.py b/backend/api/viewsets/Notification.py
index 7b0651934..c010cccaa 100644
--- a/backend/api/viewsets/Notification.py
+++ b/backend/api/viewsets/Notification.py
@@ -15,7 +15,8 @@
from api.serializers.Notifications import NotificationMessageSerializer
from auditable.views import AuditableMixin
from api.paginations import BasicPagination
-from django.db.models import Q
+from django.db.models import Q, F, Value
+from django.db.models.functions import Concat
class NotificationToken(object):
@@ -65,10 +66,25 @@ def get_queryset(self):
is_archived=False,
user=user
)
-
+
request = self.request
if request.path.endswith('processed_list') and request.method == 'POST':
- qs = qs.order_by('-create_timestamp')
+ sort = request.data.get('sort')
+ key_maps = {'notification':'message', 'date':'create_timestamp', 'creditTrade':'related_credit_trade__id', 'organization':'related_organization__name'}
+ if sort:
+ sortCondition = sort[0].get('desc')
+ sortId = sort[0].get('id')
+ if sortId=='user':
+ if sortCondition:
+ qs = qs = qs.annotate(display_name=Concat(F('originating_user__first_name'), Value(' '), F('originating_user__last_name'))).order_by('-display_name')
+ else:
+ qs = qs = qs.annotate(display_name=Concat(F('originating_user__first_name'), Value(' '), F('originating_user__last_name'))).order_by('display_name')
+ else:
+ sortType = "-" if sortCondition else ""
+ sortString = f"{sortType}{key_maps[sortId]}"
+ qs = qs.order_by(sortString)
+ else:
+ qs = qs.order_by('-create_timestamp')
filters = request.data.get('filters')
if filters:
for filter in filters:
@@ -77,9 +93,25 @@ def get_queryset(self):
if id and value:
if id == 'notification':
#todo: this can be improved
- qs = qs.filter(message__icontains = value)
+ notification_split = value.split()
+ q_object = None
+ for x in notification_split:
+ q_sub_object = Q(message__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
elif id == 'date':
- qs = qs.filter(update_timestamp__icontains = value)
+ date_split = value.split("-")
+ q_object = None
+ for x in date_split:
+ q_sub_object = Q(update_timestamp__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
elif id == 'user':
user_split = value.split()
q_object = None
@@ -88,7 +120,7 @@ def get_queryset(self):
if not q_object:
q_object = q_sub_object
else:
- q_object = q_object | q_sub_object
+ q_object = q_object & q_sub_object
qs = qs.filter(q_object)
elif id == 'creditTrade':
if value.isnumeric():
@@ -98,7 +130,15 @@ def get_queryset(self):
else:
qs = qs.none()
elif id == 'organization':
- qs = qs.filter(related_organization__name__icontains = value)
+ organization_split = value.split()
+ q_object = None
+ for x in organization_split:
+ q_sub_object = Q(related_organization__name__icontains = x)
+ if not q_object:
+ q_object = q_sub_object
+ else:
+ q_object = q_object & q_sub_object
+ qs = qs.filter(q_object)
return qs
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 09f1ddc06..72a1ebcce 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -3,7 +3,7 @@ asgiref==3.5.2
backports.zoneinfo==0.2.1
billiard==3.5.0.5
celery==4.2.0
-certifi==2022.9.24
+certifi==2022.12.7
cffi==1.15.1
charset-normalizer==2.1.1
coreapi==2.3.3
diff --git a/charts/tfrs-spilo/Chart.yaml b/charts/tfrs-spilo/Chart.yaml
index dcfc17b2e..b6c203df2 100644
--- a/charts/tfrs-spilo/Chart.yaml
+++ b/charts/tfrs-spilo/Chart.yaml
@@ -1,6 +1,6 @@
apiVersion: v2
-name: itvr-spilo
-description: A Helm chart for setting up splio for itvr project on Openshift
+name: tfrs-spilo
+description: A Helm chart for setting up splio for tfrs project on Openshift
# A chart can be either an 'application' or a 'library' chart.
#
diff --git a/charts/tfrs-spilo/Readme.md b/charts/tfrs-spilo/Readme.md
index dc6fc5b4c..9ca758e90 100644
--- a/charts/tfrs-spilo/Readme.md
+++ b/charts/tfrs-spilo/Readme.md
@@ -1,32 +1,82 @@
## Before running Helm
* Create secret tfrs-patroni-admin
+ * Create the secret by using tfrs/openshift-v4/templates/spilo/tfrs-patroni-admin.yaml, the three passwords are generated randomly
+
* Create secret tfrs-patroni-app
+ * Create the secret by using tfrs/openshift-v4/templates/spilo/tfrs-patroni-app.yaml, the three password fields must be in sync with the existing secret patroni-test
+ * It contains: app-db-name, app-db-password, app-db-username, metabaseuser-name, metabaseuser-password
+ * The replication- and superuser- are not needed
+ * If this secret is aleady existed, please verify the password fields
+
* Create Object Storage secret for database continuous backup, tfrs-object-storage
+ * Create the secret by using tfrs/openshift-v4/templates/object-storage/object-storage-secret.yaml
+ * The secret should have been created, verify it by using CyberDuck
+
+* Create secret tfrs-db-backup-s3
+ * It includes AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_ENDPOINT
+ * The values are in sync with secret tfrs-object-storage
+
+* Verify values-test.yaml. Create the bucket on object storage if needed
+
+* Add new KNPs templates/knp/knp-env-pr-new-tfrs-spilo.yaml
+ * oc process -f ./knp-env-pr-new-tfrs-spilo.yaml ENVIRONMENT=test | oc apply -f - -n 0ab226-test
## Heml command
-helm install -n 0ab226-dev -f ./values-dev.yaml tfrs-spilo .
-helm uninstall -n 0ab226-dev tfrs-spilo
+helm install -n 0ab226-test -f ./values-test.yaml tfrs-spilo .
+helm uninstall -n 0ab226-test tfrs-spilo
## Migrate Postgresql 10 on Patroni to 14 on Spilo container
+
+### Bring down the TFRS application and route the frontend to maintenance mode
+
+### Run a final backup on backup container
+
+### Create tfrs database user and database
+* Login to the tfrs-spilo leader pod
* If the username contains upper case letters, should be double quoted
-* create user for tfrs database, the username should be the same on v10 otherwise the restore may encounter issue
- * create user [username] with password '[password]'
-* create tfrs database
- * create database tfrs owner [username] ENCODING 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8'
-* login to spilo pods, run the following psql to only keep 24 hours log files, otherwise they take too much space
+ * create user for tfrs database, the username should be the same on v10 otherwise the restore may encounter issue
+ * create user [username] with password '[password]'
+ * The password can be found in secret tfrs-patroni-app
+ * create tfrs database
+ * create database tfrs owner [username] ENCODING 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8'
+### Reset postgresql logging
+* login tfrs-spilo leader pod, run the following psql to only keep 24 hours log files, otherwise they take too much space
ALTER SYSTEM SET log_filename='postgresql-%H.log';
ALTER SYSTEM SET log_connections='off';
ALTER SYSTEM SET log_disconnections='off';
ALTER SYSTEM SET log_checkpoints='off';
select pg_reload_conf();
-* Create metabase user
+### Create metabase user
+* login tfrs-spilo leader pod
CREATE USER metabaseuser WITH PASSWORD 'xxxxxx';
- GRANT CONNECT ON DATABASE zeva TO metabaseuser;
+ GRANT CONNECT ON DATABASE tfrs TO metabaseuser;
GRANT USAGE ON SCHEMA public TO metabaseuser;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabaseuser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO metabaseuser;
verify permissions are granted: select * from information_schema.role_table_grants where grantee='metabaseuser';
-* Backup tfrs database
- pg_dump tfrs > tfrs.sql
+
+## Backup the existing v10 database and restore to v14 cluster
+* Make sure the application is stopped
+* Login to patroni-test leader pod
+ * make an empty dir /home/postgres/migration and cd into it
+ * backup tfrs database: pg_dump tfrs > tfrs.sql
* Restore tfrs database
- psql tfrs < ./tfrs.sql >> ./restore.log 2>&1
+ * psql tfrs < ./tfrs.sql >> ./restore.log 2>&1
+ * verify the restore.log when complete
+
+* Point the applications to v14 cluster, update the enviuronment variables for
+ * backend: DATABASE_SERVICE_NAME, POSTGRESQL_SERVICE_HOST
+ * celery: DATABASE_SERVICE_NAME
+ * scan-handler: DATABASE_SERVICE_NAME
+* Bring down the v10 cluster
+* Bring down the maintenance page
+* Bring up the tfrs appliation
+* Update patroni backup to only backup minio data
+* Update metabase connection from CTHUB
+* Update dbServiceName to be tfrs-spilo in .pipeline/lib/config.js
+
+## Notes for uninstalling tfrs-spilo when needed
+* After the helm uninstall command, remember to remove the followings:
+ * The two configmaps: tfrs-spilo-config, tfrs-spilo-leader
+ * The PVCs storage-volume-tfrs-spilo-*
+ * The backup bucket in object storage
diff --git a/charts/tfrs-spilo/values-test.yaml b/charts/tfrs-spilo/values-test.yaml
index 8a186b1c2..190628154 100644
--- a/charts/tfrs-spilo/values-test.yaml
+++ b/charts/tfrs-spilo/values-test.yaml
@@ -5,7 +5,7 @@ spilo:
credentials:
useExistingSecret: true
existingSecret:
- name: itvr-patroni-admin
+ name: tfrs-patroni-admin
superuserKey: password-superuser
adminKey: password-admin
standbyKey: password-standby
@@ -16,17 +16,17 @@ spilo:
retainBackups: 3
storage: s3
s3:
- bucket: itvrts
- secretName: itvr-db-backup-s3
+ bucket: tfrsts/postgresbackup
+ secretName: tfrs-db-backup-s3
shipLogs:
enabled: false
# s3:
-# bucket: s3://itvrts
+# bucket: s3://tfrsts
# shipSchedule: 0 7 * * *
persistentVolume:
- size: 5Gi
+ size: 2Gi
storageClass: netapp-block-standard
resources:
diff --git a/docker-compose.yml b/docker-compose.yml
index 86c2950e5..b327a5ae9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,121 +1,127 @@
-version: '3'
+version: "3"
services:
- db:
- image: postgres
- container_name: tfrs_db
- environment:
- POSTGRES_DB: tfrs
- POSTGRES_USER: tfrs
- POSTGRES_PASSWORD: development_only
- ports:
- - 5432:5432
- volumes:
- - postgres_data:/var/lib/postgresql/data
- django:
- environment:
- - DATABASE_NAME=tfrs
- - DATABASE_USER=tfrs
- - DATABASE_PASSWORD=development_only
- - DATABASE_ENGINE=postgresql
- - DATABASE_SERVICE_NAME=postgresql
- - POSTGRESQL_SERVICE_HOST=db
- - POSTGRESQL_SERVICE_PORT=5432
- - RABBITMQ_VHOST=/tfrs
- - RABBITMQ_USER=rabbitmq
- - RABBITMQ_PASSWORD=rabbitmq
- - RABBITMQ_HOST=rabbit
- - RABBITMQ_PORT=5672
- - KEYCLOAK_ENABLED=True
- - KEYCLOAK_AUDIENCE=tfrs-on-gold-4308
- - KEYCLOAK_CLIENT_ID=tfrs-on-gold-4308
- - KEYCLOAK_REALM=standard
- - KEYCLOAK_ISSUER=https://dev.loginproxy.gov.bc.ca/auth/realms/standard
- - KEYCLOAK_CERTS_URL=https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs
- - KEYCLOAK_SA_BASEURL=https://dev.loginproxy.gov.bc.ca
- - KEYCLOAK_SA_REALM=tfrs-on-gold-4308
- - KEYCLOAK_SA_CLIENT_ID=tfrs-app-sa
- - KEYCLOAK_SA_CLIENT_SECRET=06dc71d6-1800-4f5d-b7b3-4c4fda226599
- - DOCUMENTS_API_ENABLED=True
- - SMTP_SERVER_HOST=smtplogger
- - SMTP_SERVER_PORT=2500
- - EMAIL_SENDING_ENABLED=True
- - EMAIL_FROM_ADDRESS=tfrs-dev@test.local
- - FUEL_CODES_API_ENABLED=True
- - CREDIT_CALCULATION_API_ENABLED=True
- - COMPLIANCE_REPORTING_API_ENABLED=True
- env_file:
- - minio.env
- depends_on:
- - db
- build:
- dockerfile: Dockerfile-django
- context: ./backend
- command: >
- bash -c
- "pip install -q -r requirements.txt &&
- /wfi/wait-for-it.sh -t 14400 rabbit:5672 &&
- /wfi/wait-for-it.sh -t 14400 db:5432 &&
- /wfi/wait-for-it.sh -t 14400 minio:9000 &&
- /wfi/wait-for-it.sh -t 14400 smtplogger:2500 &&
- python3 manage.py makemigrations &&
- python3 manage.py migrate &&
- python3 manage.py createcachetable &&
- python3 manage.py load_ops_data api/fixtures/development/dockerized.py &&
- supervisord"
- ports:
- - 8000:8000
- volumes:
- - ./backend:/app
- node:
- build:
- dockerfile: Dockerfile
- context: ./frontend
- command: >
- bash -c
- "npm install && npm run start"
- depends_on:
- - rabbit
- ports:
- - 3000:3000
- environment:
- - RABBITMQ_VHOST=/tfrs
- - RABBITMQ_USER=rabbitmq
- - RABBITMQ_PASSWORD=rabbitmq
- - RABBITMQ_HOST=rabbit
- - RABBITMQ_PORT=5672
- volumes:
- - ./frontend:/app
- - node_modules:/app/node_modules
- rabbit:
- image: rabbitmq:3.7-management
- hostname: "rabbit"
- environment:
- - RABBITMQ_DEFAULT_USER=rabbitmq
- - RABBITMQ_DEFAULT_PASS=rabbitmq
- - RABBITMQ_DEFAULT_VHOST=/tfrs
- - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit log_levels [{connection,error}]
- ports:
- - 15672:15672
- - 5672:5672
- minio:
- image: minio/minio
- volumes:
- - minio_data:/export
- environment:
- MINIO_ROOT_USER: 296e92217fa3479aaf9cc9641fdb6e0a
- MINIO_ROOT_PASSWORD: 778eecb24d7743b5a1b56bbf36a29d62
- ports:
- - 9000:9000
- command: "server /export"
- smtplogger:
- build:
- context: ./smtplogger
- dockerfile: Dockerfile-smtplogger
- ports:
- - 2500:2500
+ db:
+ platform: linux/amd64
+ image: postgres
+ container_name: tfrs_db
+ environment:
+ POSTGRES_DB: tfrs
+ POSTGRES_USER: tfrs
+ POSTGRES_PASSWORD: development_only
+ ports:
+ - 5432:5432
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ django:
+ platform: linux/amd64
+ environment:
+ - DATABASE_NAME=tfrs
+ - DATABASE_USER=tfrs
+ - DATABASE_PASSWORD=development_only
+ - DATABASE_ENGINE=postgresql
+ - DATABASE_SERVICE_NAME=postgresql
+ - POSTGRESQL_SERVICE_HOST=db
+ - POSTGRESQL_SERVICE_PORT=5432
+ - RABBITMQ_VHOST=/tfrs
+ - RABBITMQ_USER=rabbitmq
+ - RABBITMQ_PASSWORD=rabbitmq
+ - RABBITMQ_HOST=rabbit
+ - RABBITMQ_PORT=5672
+ - KEYCLOAK_ENABLED=True
+ - KEYCLOAK_AUDIENCE=tfrs-on-gold-4308
+ - KEYCLOAK_CLIENT_ID=tfrs-on-gold-4308
+ - KEYCLOAK_REALM=standard
+ - KEYCLOAK_ISSUER=https://dev.loginproxy.gov.bc.ca/auth/realms/standard
+ - KEYCLOAK_CERTS_URL=https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs
+ - KEYCLOAK_SA_BASEURL=https://dev.loginproxy.gov.bc.ca
+ - KEYCLOAK_SA_REALM=tfrs-on-gold-4308
+ - KEYCLOAK_SA_CLIENT_ID=tfrs-app-sa
+ - KEYCLOAK_SA_CLIENT_SECRET=06dc71d6-1800-4f5d-b7b3-4c4fda226599
+ - DOCUMENTS_API_ENABLED=True
+ - SMTP_SERVER_HOST=smtplogger
+ - SMTP_SERVER_PORT=2500
+ - EMAIL_SENDING_ENABLED=True
+ - EMAIL_FROM_ADDRESS=tfrs-dev@test.local
+ - FUEL_CODES_API_ENABLED=True
+ - CREDIT_CALCULATION_API_ENABLED=True
+ - COMPLIANCE_REPORTING_API_ENABLED=True
+ env_file:
+ - minio.env
+ depends_on:
+ - db
+ build:
+ dockerfile: Dockerfile-django
+ context: ./backend
+ command: >
+ bash -c
+ "pip install -q -r requirements.txt &&
+ /wfi/wait-for-it.sh -t 14400 rabbit:5672 &&
+ /wfi/wait-for-it.sh -t 14400 db:5432 &&
+ /wfi/wait-for-it.sh -t 14400 minio:9000 &&
+ /wfi/wait-for-it.sh -t 14400 smtplogger:2500 &&
+ python3 manage.py makemigrations &&
+ python3 manage.py migrate &&
+ python3 manage.py createcachetable &&
+ python3 manage.py load_ops_data api/fixtures/development/dockerized.py &&
+ supervisord"
+ ports:
+ - 8000:8000
+ volumes:
+ - ./backend:/app
+ node:
+ platform: linux/amd64
+ build:
+ dockerfile: Dockerfile
+ context: ./frontend
+ command: >
+ bash -c
+ "npm install && npm run start"
+ depends_on:
+ - rabbit
+ ports:
+ - 3000:3000
+ environment:
+ - RABBITMQ_VHOST=/tfrs
+ - RABBITMQ_USER=rabbitmq
+ - RABBITMQ_PASSWORD=rabbitmq
+ - RABBITMQ_HOST=rabbit
+ - RABBITMQ_PORT=5672
+ volumes:
+ - ./frontend:/app
+ - node_modules:/app/node_modules
+ rabbit:
+ platform: linux/amd64
+ image: rabbitmq:3.7-management
+ hostname: "rabbit"
+ environment:
+ - RABBITMQ_DEFAULT_USER=rabbitmq
+ - RABBITMQ_DEFAULT_PASS=rabbitmq
+ - RABBITMQ_DEFAULT_VHOST=/tfrs
+ - RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit log_levels [{connection,error}]
+ ports:
+ - 15672:15672
+ - 5672:5672
+ minio:
+ platform: linux/amd64
+ image: minio/minio
+ volumes:
+ - minio_data:/export
+ environment:
+ MINIO_ROOT_USER: 296e92217fa3479aaf9cc9641fdb6e0a
+ MINIO_ROOT_PASSWORD: 778eecb24d7743b5a1b56bbf36a29d62
+ ports:
+ - 9000:9000
+ command: "server /export"
+ smtplogger:
+ platform: linux/amd64
+ build:
+ context: ./smtplogger
+ dockerfile: Dockerfile-smtplogger
+ ports:
+ - 2500:2500
volumes:
- node_modules:
- postgres_data:
- postgres_keycloak_data:
- minio_data:
+ node_modules:
+ postgres_data:
+ postgres_keycloak_data:
+ minio_data:
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 846684668..f540c1ce2 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:14.16.1
+FROM --platform=linux/amd64 node:14.16.1
WORKDIR /app
diff --git a/frontend/package.json b/frontend/package.json
index 71e32fd32..83ecaebba 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "tfrs",
- "version": "2.0.0",
+ "version": "2.1.0",
"dependencies": {
"@babel/eslint-parser": "^7.19.1",
"@babel/polyfill": "^7.12.1",
@@ -22,7 +22,6 @@
"history": "^4.10.1",
"http-proxy": "^1.18.1",
"isomorphic-fetch": "^3.0.0",
- "jquery": "^3.6.1",
"jsonwebtoken": "^8.5.1",
"jwks-rsa": "^1.12.3",
"keycloak-js": "^19.0.3",
diff --git a/frontend/src/actions/complianceReporting.js b/frontend/src/actions/complianceReporting.js
index 7279dd106..cb943f6fc 100644
--- a/frontend/src/actions/complianceReporting.js
+++ b/frontend/src/actions/complianceReporting.js
@@ -202,7 +202,8 @@ class ComplianceReportingRestInterface extends GenericRestTemplate {
const page = data.page
const pageSize = data.pageSize
const filters = data.filters
- return axios.post(`${this.baseUrl}/paginated?page=${page}&size=${pageSize}`, { filters })
+ const sorts = data.sorts
+ return axios.post(`${this.baseUrl}/paginated?page=${page}&size=${pageSize}`, { filters, sorts })
}
* findPaginatedHandler () {
diff --git a/frontend/src/actions/documentUploads.js b/frontend/src/actions/documentUploads.js
index 408e2ddfb..f173926a6 100644
--- a/frontend/src/actions/documentUploads.js
+++ b/frontend/src/actions/documentUploads.js
@@ -116,11 +116,12 @@ const deleteDocumentUploadRequestError = error => ({
/*
* Get Documents
*/
-const getDocumentUploads = (pageNumber=1, pageSize=10, filters=[]) => (dispatch) => {
+const getDocumentUploads = (pageNumber=1, pageSize=10, filters=[], sort=[]) => (dispatch) => {
dispatch(getDocumentUploadRequests())
const url = Routes.BASE_URL + Routes.SECURE_DOCUMENT_UPLOAD.API + '/paginated?page=' + pageNumber + '&size=' + pageSize
const data = {
- filters
+ filters,
+ sort
}
return axios.post(url, data)
.then((response) => {
diff --git a/frontend/src/actions/keycloakActions.js b/frontend/src/actions/keycloakActions.js
index 5da010e95..eb72f16f7 100644
--- a/frontend/src/actions/keycloakActions.js
+++ b/frontend/src/actions/keycloakActions.js
@@ -34,18 +34,18 @@ export const resetAuth = () => ({
type: ActionTypes.RESET_AUTH
})
-export const loginKeycloakUserSuccess = (idToken, refreshToken, expiry) => ({
- payload: { idToken, refreshToken, expiry },
+export const loginKeycloakUserSuccess = (idToken, refreshToken, expiry, refreshExpiry) => ({
+ payload: { idToken, refreshToken, expiry, refreshExpiry },
type: ActionTypes.LOGIN_KEYCLOAK_USER_SUCCESS
})
-export const loginKeycloakRefreshSuccess = (refreshToken, expiry) => ({
- payload: { refreshToken, expiry },
+export const loginKeycloakRefreshSuccess = (refreshToken, expiry, refreshExpiry) => ({
+ payload: { refreshToken, expiry, refreshExpiry },
type: ActionTypes.LOGIN_KEYCLOAK_REFRESH_SUCCESS
})
-export const loginKeycloakSilentRefreshSuccess = (idToken, refreshToken, expiry) => ({
- payload: { idToken, refreshToken, expiry },
+export const loginKeycloakSilentRefreshSuccess = (idToken, refreshToken, expiry, refreshExpiry) => ({
+ payload: { idToken, refreshToken, expiry, refreshExpiry },
type: ActionTypes.LOGIN_KEYCLOAK_SILENT_REFRESH_SUCCESS
})
diff --git a/frontend/src/actions/notificationActions.js b/frontend/src/actions/notificationActions.js
index 5dfc58524..cab423f96 100644
--- a/frontend/src/actions/notificationActions.js
+++ b/frontend/src/actions/notificationActions.js
@@ -7,11 +7,12 @@ import * as Routes from '../constants/routes'
/*
* Get Notifications
*/
-const getNotifications = (pageNumber, pageSize, filters) => (dispatch) => {
+const getNotifications = (pageNumber, pageSize, filters, sort) => (dispatch) => {
dispatch(getNotificationsRequest())
const url = Routes.BASE_URL + Routes.NOTIFICATIONS.PROCESSED_LIST + '?page=' + pageNumber + '&size=' + pageSize
const data = {
- filters
+ filters,
+ sort
}
axios.post(url, data)
.then((response) => {
diff --git a/frontend/src/actions/userActions.js b/frontend/src/actions/userActions.js
index cf86d56cf..2b1f6f459 100644
--- a/frontend/src/actions/userActions.js
+++ b/frontend/src/actions/userActions.js
@@ -41,7 +41,6 @@ const updateUser = (id, payload) => (dispatch) => {
}
const getLoggedInUser = () => (dispatch) => {
- console.log('getLoggedInUser')
dispatch(getLoggedInUserRequest())
const url = Routes.BASE_URL + Routes.CURRENT_USER
axios.get(url)
diff --git a/frontend/src/app/components/Modal.js b/frontend/src/app/components/Modal.js
index e690ac006..d290d29c9 100644
--- a/frontend/src/app/components/Modal.js
+++ b/frontend/src/app/components/Modal.js
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
import Tooltip from '../../app/components/Tooltip'
import * as Lang from '../../constants/langEnUs'
-import $ from 'jquery'
const bootstrapClassFor = (extraConfirmType) => {
switch (extraConfirmType) {
diff --git a/frontend/src/app/components/Navbar.js b/frontend/src/app/components/Navbar.js
index cece5cb3e..07df2bb72 100644
--- a/frontend/src/app/components/Navbar.js
+++ b/frontend/src/app/components/Navbar.js
@@ -68,6 +68,8 @@ class Navbar extends Component {
const SecondLevelNavigation = (
+
+
}
+
{this.props.loggedInUser.displayName &&
,
{
@@ -212,7 +218,8 @@ ComplianceReportingContainer.propTypes = {
}),
getCompliancePeriods: PropTypes.func.isRequired,
getComplianceReports: PropTypes.func.isRequired,
- loggedInUser: PropTypes.shape().isRequired
+ loggedInUser: PropTypes.shape().isRequired,
+ savedState: PropTypes.shape().isRequired
}
const mapStateToProps = state => ({
@@ -230,7 +237,8 @@ const mapStateToProps = state => ({
item: state.rootReducer.exclusionReports.item,
success: state.rootReducer.exclusionReports.success
},
- loggedInUser: state.rootReducer.userRequest.loggedInUser
+ loggedInUser: state.rootReducer.userRequest.loggedInUser,
+ savedState: state.rootReducer.tableState.savedState
})
const mapDispatchToProps = {
@@ -238,6 +246,6 @@ const mapDispatchToProps = {
createExclusionReport: exclusionReports.create,
getCompliancePeriods,
getComplianceReports: complianceReporting.findPaginated
-};
+}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ComplianceReportingContainer))
diff --git a/frontend/src/compliance_reporting/ScheduleAssessmentContainer.js b/frontend/src/compliance_reporting/ScheduleAssessmentContainer.js
index fea19151b..6d952dc20 100644
--- a/frontend/src/compliance_reporting/ScheduleAssessmentContainer.js
+++ b/frontend/src/compliance_reporting/ScheduleAssessmentContainer.js
@@ -48,6 +48,21 @@ class ScheduleAssessmentContainer extends Component {
return
}
+ let hasDirectorAssessment = false;
+ if (this.props.complianceReport && ['Accepted'].indexOf(this.props.complianceReport.status.directorStatus) >= 0) {
+ hasDirectorAssessment = true
+ } else if (this.props.complianceReport &&
+ this.props.complianceReport.history &&
+ this.props.complianceReport.history.find(h =>
+ (['Accepted'].indexOf(h.status.directorStatus) >= 0))
+ ) {
+ hasDirectorAssessment = true;
+ }
+
+ if (!this.props.loggedInUser.isGovernmentUser && !hasDirectorAssessment) {
+ return null;
+ }
+
let part2Compliant = 'Did not supply Part 2 fuel'
let foundInScheduleB = false
let foundInScheduleC = false
diff --git a/frontend/src/compliance_reporting/ScheduleBContainer.js b/frontend/src/compliance_reporting/ScheduleBContainer.js
index b61ae7df8..0c1bf999e 100644
--- a/frontend/src/compliance_reporting/ScheduleBContainer.js
+++ b/frontend/src/compliance_reporting/ScheduleBContainer.js
@@ -421,13 +421,22 @@ class ScheduleBContainer extends Component {
)
}
+ // Check for existing scheduleDSheetIndex from records
+ let scheduleDSheetIndex = null
+ const scheduleBRecords = props.complianceReport?.scheduleB?.records
+ if (scheduleBRecords != null) {
+ const record = scheduleBRecords[row - 2]
+ scheduleDSheetIndex = record ? record.scheduleDSheetIndex : null
+ }
+
const values = {
customIntensity: grid[row][SCHEDULE_B.CARBON_INTENSITY_FUEL].value,
quantity: grid[row][SCHEDULE_B.QUANTITY].value,
fuelClass: grid[row][SCHEDULE_B.FUEL_CLASS].value,
fuelCode: grid[row][SCHEDULE_B.FUEL_CODE].value,
fuelType: grid[row][SCHEDULE_B.FUEL_TYPE].value,
- provisionOfTheAct: grid[row][SCHEDULE_B.PROVISION_OF_THE_ACT].value
+ provisionOfTheAct: grid[row][SCHEDULE_B.PROVISION_OF_THE_ACT].value,
+ scheduleD_sheetIndex: scheduleDSheetIndex
}
const response = ComplianceReportingService.computeCredits(context, values)
@@ -504,8 +513,9 @@ class ScheduleBContainer extends Component {
grid[row][SCHEDULE_B.FUEL_CODE].dataEditor = Select
grid[row][SCHEDULE_B.FUEL_CODE].valueViewer = (cellProps) => {
const selectedOption = cellProps.cell.getOptions().find(e =>
- String(e.id) === String(cellProps.value))
+ String(e.id) === String(response.parameters.scheduleD_sheetIndex))
if (selectedOption) {
+ grid[row][SCHEDULE_B.FUEL_CODE].value = selectedOption.id
return {selectedOption.descriptiveName}
}
return {cellProps.value}
diff --git a/frontend/src/compliance_reporting/components/ComplianceReportingPage.js b/frontend/src/compliance_reporting/components/ComplianceReportingPage.js
index dfe276ec4..0c8e28984 100644
--- a/frontend/src/compliance_reporting/components/ComplianceReportingPage.js
+++ b/frontend/src/compliance_reporting/components/ComplianceReportingPage.js
@@ -2,15 +2,15 @@ import React from 'react'
import PropTypes from 'prop-types'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
-import Loading from '../../app/components/Loading'
-import CONFIG from '../../config';
-import * as Lang from '../../constants/langEnUs';
-import PERMISSIONS_COMPLIANCE_REPORT from '../../constants/permissions/ComplianceReport';
-import ComplianceReportingTable from './ComplianceReportingTable';
+import CONFIG from '../../config'
+import * as Lang from '../../constants/langEnUs'
+import PERMISSIONS_COMPLIANCE_REPORT from '../../constants/permissions/ComplianceReport'
+import ComplianceReportingTable from './ComplianceReportingTable'
const ComplianceReportingPage = (props) => {
- const { isFetching, items, itemsCount } = props.complianceReports;
- const isEmpty = items.length === 0;
+ const { isFetching, items, itemsCount } = props.complianceReports
+ const isEmpty = items.length === 0
+ const filters = props.savedState['compliance-reporting']?.filtered
return (
{props.title}
@@ -112,6 +112,7 @@ const ComplianceReportingPage = (props) => {
isFetching={isFetching}
isEmpty={isEmpty}
loggedInUser={props.loggedInUser}
+ filters={filters}
/>
)
@@ -134,7 +135,8 @@ ComplianceReportingPage.propTypes = {
}).isRequired,
selectComplianceReport: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
- title: PropTypes.string.isRequired
+ title: PropTypes.string.isRequired,
+ savedState: PropTypes.shape().isRequired
}
export default ComplianceReportingPage
diff --git a/frontend/src/compliance_reporting/components/ComplianceReportingTable.js b/frontend/src/compliance_reporting/components/ComplianceReportingTable.js
index 2748036d1..43b4d8f27 100644
--- a/frontend/src/compliance_reporting/components/ComplianceReportingTable.js
+++ b/frontend/src/compliance_reporting/components/ComplianceReportingTable.js
@@ -8,44 +8,49 @@ import 'react-table/react-table.css'
import ReactTable from '../../app/components/StateSavingReactTable'
-import COMPLIANCE_REPORTING from '../../constants/routes/ComplianceReporting';
-import EXCLUSION_REPORTS from '../../constants/routes/ExclusionReports';
-import ComplianceReportStatus from './ComplianceReportStatus';
-import { withRouter } from '../../utils/withRouter';
-import { calculatePages} from '../../utils/functions'
+import COMPLIANCE_REPORTING from '../../constants/routes/ComplianceReporting'
+import EXCLUSION_REPORTS from '../../constants/routes/ExclusionReports'
+import ComplianceReportStatus from './ComplianceReportStatus'
+import { withRouter } from '../../utils/withRouter'
+import { calculatePages } from '../../utils/functions'
class ComplianceReportingTable extends Component {
-
constructor (props) {
- super(props);
+ super(props)
this.state = {
page: 1,
pageSize: 10,
- filters: []
+ filters: props.filters,
+ sorts: []
}
- this.handlePageChange = this.handlePageChange.bind(this);
- this.handlePageSizeChange = this.handlePageSizeChange.bind(this);
- this.handleFiltersChange = this.handleFiltersChange.bind(this);
+ this.handlePageChange = this.handlePageChange.bind(this)
+ this.handlePageSizeChange = this.handlePageSizeChange.bind(this)
+ this.handleFiltersChange = this.handleFiltersChange.bind(this)
+ this.handleSortsChange = this.handleSortsChange.bind(this)
}
componentDidUpdate (prevProps, prevState) {
- if (this.state.page !== prevState.page || this.state.pageSize !== prevState.pageSize || this.state.filters !== prevState.filters) {
- this.props.getComplianceReports({page: this.state.page, pageSize: this.state.pageSize, filters: this.state.filters})
+ if (this.state.page !== prevState.page || this.state.pageSize !== prevState.pageSize || this.state.filters !== prevState.filters || this.state.sorts !== prevState.sorts) {
+ this.props.getComplianceReports({ page: this.state.page, pageSize: this.state.pageSize, filters: this.state.filters, sorts: this.state.sorts })
}
}
handlePageChange (page) {
- this.setState({page: page});
+ this.setState({ page })
}
handlePageSizeChange (pageSize) {
- this.setState({pageSize: pageSize});
+ this.setState({ pageSize })
}
handleFiltersChange (filters) {
- this.setState({filters: filters});
+ this.setState({ filters })
+ }
+
+ handleSortsChange (sorts) {
+ this.setState({ sorts })
}
render () {
@@ -104,18 +109,18 @@ class ComplianceReportingTable extends Component {
minWidth: 75
}, {
accessor: (item) => {
- let report = item
- const { supplementalReports } = item
-
- if (supplementalReports.length > 0) {
- [report] = supplementalReports
- }
-
- while (report.supplementalReports && report.supplementalReports.length > 0) {
- [report] = report.supplementalReports
- }
-
- return ComplianceReportStatus(report)
+ // Temporarily left commented out for posterity and client feedback
+ // let report = item
+ // const { supplementalReports } = item
+ // if (supplementalReports.length > 0) {
+ // [report] = supplementalReports
+ // }
+ // while (report.supplementalReports && report.supplementalReports.length > 0) {
+ // [report] = report.supplementalReports
+ // }
+ // return ComplianceReportStatus(report)
+
+ return ComplianceReportStatus(item)
},
className: 'col-status',
Header: 'Current Status',
@@ -137,7 +142,7 @@ class ComplianceReportingTable extends Component {
)
}]
- const filterable = true;
+ const filterable = true
return (
{
- this.handlePageChange(pageIndex + 1);
+ this.handlePageChange(pageIndex + 1)
}}
onPageSizeChange={(pageSize, pageIndex) => {
- this.handlePageChange(1);
- this.handlePageSizeChange(pageSize);
+ this.handlePageChange(1)
+ this.handlePageSizeChange(pageSize)
}}
filtered={this.state.filters}
onFilteredChange={(filtered, column) => {
- this.handlePageChange(1);
- this.handleFiltersChange(filtered);
+ this.handlePageChange(1)
+ this.handleFiltersChange(filtered)
+ }}
+ sorts={this.state.sorts}
+ onSortedChange={(sorts, column) => {
+ this.handlePageChange(1)
+ this.handleSortsChange(sorts)
}}
/>
)
@@ -230,6 +240,6 @@ ComplianceReportingTable.propTypes = {
isGovernmentUser: PropTypes.bool
}).isRequired,
getComplianceReports: PropTypes.func.isRequired
-};
+}
export default withRouter(ComplianceReportingTable)
diff --git a/frontend/src/compliance_reporting/components/ScheduleTabs.js b/frontend/src/compliance_reporting/components/ScheduleTabs.js
index 0b3fdd0f4..8c0f89db7 100644
--- a/frontend/src/compliance_reporting/components/ScheduleTabs.js
+++ b/frontend/src/compliance_reporting/components/ScheduleTabs.js
@@ -19,23 +19,21 @@ const ScheduleTabs = (props) => {
let showAssessment = false
- if (props.complianceReport.status.directorStatus !== 'Rejected') {
- if (props.complianceReport && ['Accepted'].indexOf(props.complianceReport.status.directorStatus) >= 0) {
- showAssessment = true
- } else if (props.complianceReport &&
- props.complianceReport.history &&
- props.complianceReport.history.find(h =>
- (['Accepted'].indexOf(h.status.directorStatus) >= 0))
- ) {
- // at least one prior version was accepted
- showAssessment = true
- }
+ if (props.complianceReport && ['Accepted'].indexOf(props.complianceReport.status.directorStatus) >= 0) {
+ showAssessment = true
+ } else if (props.complianceReport &&
+ props.complianceReport.history &&
+ props.complianceReport.history.find(h =>
+ (['Accepted'].indexOf(h.status.directorStatus) >= 0))
+ ) {
+ // at least one prior version was accepted
+ showAssessment = true
+ }
- if (props.loggedInUser.isGovernmentUser &&
- (['Recommended', 'Not Recommended'].indexOf(props.complianceReport.status.analystStatus) >= 0 ||
- ['Recommended', 'Not Recommended'].indexOf(props.complianceReport.status.managerStatus) >= 0)) {
- showAssessment = true
- }
+ if (props.loggedInUser.isGovernmentUser &&
+ (['Recommended', 'Not Recommended'].indexOf(props.complianceReport.status.analystStatus) >= 0 ||
+ ['Recommended', 'Not Recommended'].indexOf(props.complianceReport.status.managerStatus) >= 0)) {
+ showAssessment = true
}
return (
diff --git a/frontend/src/compliance_reporting/services/ComplianceReportingService.js b/frontend/src/compliance_reporting/services/ComplianceReportingService.js
index 039cdc77a..aead38619 100644
--- a/frontend/src/compliance_reporting/services/ComplianceReportingService.js
+++ b/frontend/src/compliance_reporting/services/ComplianceReportingService.js
@@ -116,7 +116,8 @@ class ComplianceReportingService {
customIntensity,
fuelCode,
scheduleDIntensityValue,
- quantity
+ quantity,
+ scheduleD_sheetIndex
} = sourceValues
if (!fuelType) {
@@ -141,7 +142,8 @@ class ComplianceReportingService {
scheduleDSelectionRequired: false,
intensityInputRequired: false,
singleFuelClassAvailable: false,
- singleProvisionAvailable: false
+ singleProvisionAvailable: false,
+ scheduleD_sheetIndex: false
}
}
}
@@ -211,7 +213,8 @@ class ComplianceReportingService {
scheduleDSelectionRequired: false,
intensityInputRequired: false,
singleFuelClassAvailable: false,
- singleProvisionAvailable: false
+ singleProvisionAvailable: false,
+ scheduleD_sheetIndex: scheduleD_sheetIndex
}
}
diff --git a/frontend/src/constants/routes/Organizations.js b/frontend/src/constants/routes/Organizations.js
index 1824568c4..7b742f6d4 100644
--- a/frontend/src/constants/routes/Organizations.js
+++ b/frontend/src/constants/routes/Organizations.js
@@ -3,7 +3,7 @@ const BASE_PATH = '/organizations'
const ORGANIZATIONS = {
ADD_USER: `${BASE_PATH}/view/:organizationId/add-user`,
BULLETIN: 'https://www2.gov.bc.ca/assets/gov/farming-natural-resources-and-industry/electricity-alternative-energy/transportation/renewable-low-carbon-fuels/rlcf-013.pdf',
- CREDIT_MARKET_REPORT: 'https://www2.gov.bc.ca/assets/gov/farming-natural-resources-and-industry/electricity-alternative-energy/transportation/renewable-low-carbon-fuels/rlcf-017.pdf',
+ CREDIT_MARKET_REPORT: 'https://www2.gov.bc.ca/gov/content?id=4B2DC59D77F64C8491C5CDFCF8732F10',
DETAILS: `${BASE_PATH}/view/:id`,
EDIT: `${BASE_PATH}/edit/:id`,
EXPORT: `${BASE_PATH}/xls`,
diff --git a/frontend/src/credit_transfers/components/CreditTransferDetails.js b/frontend/src/credit_transfers/components/CreditTransferDetails.js
index 38a61da61..03d287eb7 100644
--- a/frontend/src/credit_transfers/components/CreditTransferDetails.js
+++ b/frontend/src/credit_transfers/components/CreditTransferDetails.js
@@ -189,8 +189,10 @@ const CreditTransferDetails = props => (
props.comments.length === 0,
BTN_SIGN_1_2: props.fields.terms.filter(term =>
term.value === true).length < props.signingAuthorityAssertions.items.length,
- BTN_SIGN_2_2: props.fields.terms.filter(term =>
- term.value === true).length < props.signingAuthorityAssertions.items.length
+ BTN_SIGN_2_2: props.loggedInUser.organization.statusDisplay !== 'Active' ? true : props.fields.terms.filter(term =>
+ term.value === true).length < props.signingAuthorityAssertions.items.length,
+ organizationName: props.loggedInUser.organization.name,
+ inactiveSupplier: props.loggedInUser.organization.statusDisplay !== 'Active'
}
}
id={props.id}
diff --git a/frontend/src/credit_transfers/components/CreditTransferFormButtons.js b/frontend/src/credit_transfers/components/CreditTransferFormButtons.js
index a2e189864..26d5f5c4c 100644
--- a/frontend/src/credit_transfers/components/CreditTransferFormButtons.js
+++ b/frontend/src/credit_transfers/components/CreditTransferFormButtons.js
@@ -108,10 +108,11 @@ const CreditTransferFormButtons = props => {
show={props.isCommenting || props.disabled.BTN_SIGN_2_2}
title={props.isCommenting
? Lang.TEXT_COMMENT_DIRTY
+ : (props.disabled.inactiveSupplier ? props.disabled.organizationName + ' is not currently recognized as an active fuel supplier in TFRS and is not permitted to buy credits. Inactive suppliers are only permitted to sell credits.'
: (props.permissions.BTN_SIGN_2_2
? 'Signing Authority Declaration needs to be accepted'
: 'You must be assigned the Signing Authority role in order to sign and send ' +
- 'a Credit Transfer Proposal to the Low Carbon Fuels Branch')}
+ 'a Credit Transfer Proposal to the Low Carbon Fuels Branch'))}
>