diff --git a/backend/.s2i/bin/assemble b/backend/.s2i/bin/assemble deleted file mode 100755 index ae8d905f8..000000000 --- a/backend/.s2i/bin/assemble +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash - -function is_django_installed() { - python -c "import django" &>/dev/null -} - -function should_collectstatic() { - is_django_installed && [[ -z "$DISABLE_COLLECTSTATIC" ]] -} - -function virtualenv_bin() { - # New versions of Python (>3.6) should use venv module - # from stdlib instead of virtualenv package - python3.9 -m venv $1 -} - -# Install pipenv or micropipenv to the separate virtualenv to isolate it -# from system Python packages and packages in the main -# virtualenv. Executable is simlinked into ~/.local/bin -# to be accessible. This approach is inspired by pipsi -# (pip script installer). -function install_tool() { - echo "---> Installing $1 packaging tool ..." - VENV_DIR=$HOME/.local/venvs/$1 - virtualenv_bin "$VENV_DIR" - # First, try to install the tool without --isolated which means that if you - # have your own PyPI mirror, it will take it from there. If this try fails, try it - # again with --isolated which ignores external pip settings (env vars, config file) - # and installs the tool from PyPI (needs internet connetion). - # $1$2 combines package name with [extras] or version specifier if is defined as $2``` - if ! $VENV_DIR/bin/pip install -U $1$2; then - echo "WARNING: Installation of $1 failed, trying again from official PyPI with pip --isolated install" - $VENV_DIR/bin/pip install --isolated -U $1$2 # Combines package name with [extras] or version specifier if is defined as $2``` - fi - mkdir -p $HOME/.local/bin - ln -s $VENV_DIR/bin/$1 $HOME/.local/bin/$1 -} - -set -e - -# First of all, check that we don't have disallowed combination of ENVs -if [[ ! -z "$ENABLE_PIPENV" && ! -z "$ENABLE_MICROPIPENV" ]]; then - echo "ERROR: Pipenv and micropipenv cannot be enabled at the same time!" - # podman/buildah does not relay this exit code but it will be fixed hopefuly - # https://github.com/containers/buildah/issues/2305 - exit 3 -fi - -shopt -s dotglob -echo "---> Installing application source ..." -mv /tmp/src/* "$HOME" - -# set permissions for any installed artifacts -fix-permissions /opt/app-root -P - - -if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then - echo "---> Upgrading pip, setuptools and wheel to latest version ..." - if ! pip install -U pip setuptools wheel; then - echo "WARNING: Installation of the latest pip, setuptools and wheel failed, trying again from official PyPI with pip --isolated install" - pip install --isolated -U pip setuptools wheel - fi -fi - -if [[ ! -z "$ENABLE_PIPENV" ]]; then - if [[ ! -z "$PIN_PIPENV_VERSION" ]]; then - # Add == as a prefix to pipenv version, if defined - PIN_PIPENV_VERSION="==$PIN_PIPENV_VERSION" - fi - install_tool "pipenv" "$PIN_PIPENV_VERSION" - echo "---> Installing dependencies via pipenv ..." - if [[ -f Pipfile ]]; then - pipenv install --deploy - elif [[ -f requirements.txt ]]; then - pipenv install -r requirements.txt - fi - # pipenv check -elif [[ ! -z "$ENABLE_MICROPIPENV" ]]; then - install_tool "micropipenv" "[toml]" - echo "---> Installing dependencies via micropipenv ..." - # micropipenv detects Pipfile.lock and requirements.txt in this order - micropipenv install --deploy -elif [[ -f requirements.txt ]]; then - if [[ -z "${ARTIFACTORY_USER}" ]]; then - echo "---> Installing dependencies from external repo ..." - pip install -r requirements.txt - else - echo "---> Installing dependencies from artifactory ..." - # pip install -i https://$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD@artifacts.developer.gov.bc.ca/artifactory/api/pypi/pypi-remote/simple -r requirements.txt - pip install -r requirements.txt - fi -fi - -if [[ -f setup.py && -z "$DISABLE_SETUP_PY_PROCESSING" ]]; then - echo "---> Installing application ..." - pip install . -fi - -if should_collectstatic; then - ( - echo "---> Collecting Django static files ..." - - APP_HOME=$(readlink -f "${APP_HOME:-.}") - # Change the working directory to APP_HOME - PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" - cd "$APP_HOME" - - # Look for 'manage.py' in the current directory - manage_file=./manage.py - - if [[ ! -f "$manage_file" ]]; then - echo "WARNING: seems that you're using Django, but we could not find a 'manage.py' file." - echo "'manage.py collectstatic' ignored." - exit - fi - - if ! python $manage_file collectstatic --dry-run --noinput &> /dev/null; then - echo "WARNING: could not run 'manage.py collectstatic'. To debug, run:" - echo " $ python $manage_file collectstatic --noinput" - echo "Ignore this warning if you're not serving static files with Django." - exit - fi - - python $manage_file collectstatic --noinput - ) -fi - -# set permissions for any installed artifacts -fix-permissions /opt/app-root -P \ No newline at end of file diff --git a/backend/.s2i/environment b/backend/.s2i/environment deleted file mode 100644 index 18f3b0467..000000000 --- a/backend/.s2i/environment +++ /dev/null @@ -1 +0,0 @@ -DISABLE_MIGRATE=1 diff --git a/backend/api/mixins/user_mixin.py b/backend/api/mixins/user_mixin.py new file mode 100644 index 000000000..44081039a --- /dev/null +++ b/backend/api/mixins/user_mixin.py @@ -0,0 +1,49 @@ +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from api.models.user_profile import UserProfile +from api.serializers.user import UserBasicSerializer +from api.serializers.organization import OrganizationNameSerializer + + +def get_user_data(username, request): + if username is None: + return f"{username} does not exist on the object." + user_profile = UserProfile.objects.filter(username=username).first() + if not user_profile: + return { + "display_name": username + } # Return the username if the user profile doesn't exist + + is_government = False + if request is not None: + is_government = request.user.is_government + + if not user_profile.is_government or is_government: + # If the user is non-government or the requesting user is government, return full data + serializer = UserBasicSerializer(user_profile, read_only=True) + return serializer.data + else: + # If the requesting user is non-government and the user is government, limit info + organization = OrganizationNameSerializer( + user_profile.organization, read_only=True + ) + return { + "display_name": "Government User", + "is_government": user_profile.is_government, + "organization": organization.data, + } + + +class UserSerializerMixin(ModelSerializer): + create_user = SerializerMethodField() + + update_user = SerializerMethodField() + + def get_create_user(self, obj): + username = getattr(obj, "create_user", None) + request = self.context.get("request") + return get_user_data(username, request) + + def get_update_user(self, obj): + username = getattr(obj, "update_user", None) + request = self.context.get("request") + return get_user_data(username, request) diff --git a/backend/api/serializers/credit_agreement.py b/backend/api/serializers/credit_agreement.py index a56a37a86..8564cd2e8 100644 --- a/backend/api/serializers/credit_agreement.py +++ b/backend/api/serializers/credit_agreement.py @@ -9,8 +9,6 @@ from api.models.credit_agreement_transaction_types import CreditAgreementTransactionTypes from api.models.credit_class import CreditClass from api.models.model_year import ModelYear -from api.models.user_profile import UserProfile -from api.serializers.user import MemberSerializer from api.serializers.credit_agreement_attachment import CreditAgreementAttachmentSerializer from api.serializers.credit_agreement_comment import CreditAgreementCommentSerializer from api.serializers.credit_agreement_content import \ @@ -18,20 +16,9 @@ from .organization import OrganizationSerializer from api.models.credit_agreement_history import CreditAgreementHistory from api.services.minio import minio_remove_object +from api.mixins.user_mixin import UserSerializerMixin - -class CreditAgreementBaseSerializer: - def get_update_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.update_user) - - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - return serializer.data - - return obj.update_user - - -class CreditAgreementSerializer(ModelSerializer, CreditAgreementBaseSerializer): +class CreditAgreementSerializer(UserSerializerMixin): organization = OrganizationSerializer(read_only=True) transaction_type = EnumField(CreditAgreementTransactionTypes) credit_agreement_content = CreditAgreementContentSerializer( @@ -40,25 +27,16 @@ class CreditAgreementSerializer(ModelSerializer, CreditAgreementBaseSerializer): status = EnumField(CreditAgreementStatuses) comments = SerializerMethodField() attachments = SerializerMethodField() - update_user = SerializerMethodField() - - def get_update_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.update_user) - - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - return serializer.data - - return obj.update_user def get_comments(self, obj): + request = self.context.get('request') agreement_comment = CreditAgreementComment.objects.filter( credit_agreement=obj ).order_by('-create_timestamp') if agreement_comment.exists(): serializer = CreditAgreementCommentSerializer( - agreement_comment, read_only=True, many=True + agreement_comment, read_only=True, many=True, context={"request": request} ) return serializer.data @@ -287,15 +265,13 @@ class Meta: class CreditAgreementListSerializer( - ModelSerializer, EnumSupportSerializerMixin, CreditAgreementBaseSerializer + UserSerializerMixin, EnumSupportSerializerMixin ): - organization = OrganizationSerializer() credit_agreement_content = CreditAgreementContentSerializer( many=True, read_only=True ) status = EnumField(CreditAgreementStatuses) - update_user = SerializerMethodField() transaction_type = EnumField(CreditAgreementTransactionTypes) class Meta: diff --git a/backend/api/serializers/credit_agreement_comment.py b/backend/api/serializers/credit_agreement_comment.py index 2087338c3..fd89e2e16 100644 --- a/backend/api/serializers/credit_agreement_comment.py +++ b/backend/api/serializers/credit_agreement_comment.py @@ -1,24 +1,11 @@ -from rest_framework.serializers import ModelSerializer, \ - SerializerMethodField from api.models.credit_agreement_comment import CreditAgreementComment -from api.models.user_profile import UserProfile -from api.serializers.user import MemberSerializer, UserSerializer +from api.mixins.user_mixin import UserSerializerMixin - -class CreditAgreementCommentSerializer(ModelSerializer): +class CreditAgreementCommentSerializer(UserSerializerMixin): """ Serializer for credit agreement comments """ - create_user = SerializerMethodField() - - def get_create_user(self, obj): - user = UserProfile.objects.filter(username=obj.create_user).first() - if user is None: - return obj.create_user - - serializer = MemberSerializer(user, read_only=True) - return serializer.data - + class Meta: model = CreditAgreementComment fields = ( diff --git a/backend/api/serializers/credit_transfer.py b/backend/api/serializers/credit_transfer.py index f39250941..33d6c3ab8 100644 --- a/backend/api/serializers/credit_transfer.py +++ b/backend/api/serializers/credit_transfer.py @@ -18,29 +18,12 @@ CreditTransferCommentSerializer from api.serializers.credit_transfer_content import \ CreditTransferContentSerializer, CreditTransferContentSaveSerializer -from api.serializers.user import UserBasicSerializer from api.serializers.organization import OrganizationNameSerializer, OrganizationSerializer from api.services.credit_transaction import calculate_insufficient_credits from api.services.send_email import notifications_credit_transfers +from api.mixins.user_mixin import UserSerializerMixin - -class CreditTransferBaseSerializer: - def get_update_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.update_user) - - if user_profile.exists(): - serializer = UserBasicSerializer(user_profile.first(), read_only=True) - return serializer.data - - return obj.update_user - - def get_create_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.create_user) - if user_profile.exists(): - serializer = UserBasicSerializer(user_profile.first(), read_only=True) - return serializer.data - return obj.create_user - +class CreditTransferBaseSerializer(UserSerializerMixin): def get_history(self, obj): request = self.context.get('request') if request.user.is_government: @@ -66,22 +49,21 @@ def get_history(self, obj): class CreditTransferHistorySerializer( - ModelSerializer, EnumSupportSerializerMixin, - CreditTransferBaseSerializer + CreditTransferBaseSerializer, + EnumSupportSerializerMixin, ): - create_user = SerializerMethodField() - update_user = SerializerMethodField() status = EnumField(CreditTransferStatuses) comment = SerializerMethodField() def get_comment(self, obj): + request = self.context.get('request') credit_transfer_comment = CreditTransferComment.objects.filter( credit_transfer_history=obj ).first() if credit_transfer_comment: serializer = CreditTransferCommentSerializer( - credit_transfer_comment, read_only=True, many=False + credit_transfer_comment, read_only=True, many=False, context={'request': request} ) return serializer.data return None @@ -95,8 +77,8 @@ class Meta: class CreditTransferListSerializer( - ModelSerializer, EnumSupportSerializerMixin, - CreditTransferBaseSerializer + CreditTransferBaseSerializer, + EnumSupportSerializerMixin ): history = SerializerMethodField() credit_to = OrganizationNameSerializer() @@ -105,7 +87,6 @@ class CreditTransferListSerializer( ) debit_from = OrganizationNameSerializer() status = SerializerMethodField() - update_user = SerializerMethodField() def get_status(self, obj): request = self.context.get('request') @@ -126,8 +107,8 @@ class Meta: class CreditTransferSerializer( - ModelSerializer, EnumSupportSerializerMixin, - CreditTransferBaseSerializer + CreditTransferBaseSerializer, + EnumSupportSerializerMixin, ): history = SerializerMethodField() credit_to = OrganizationNameSerializer() @@ -136,7 +117,6 @@ class CreditTransferSerializer( ) debit_from = OrganizationNameSerializer() status = SerializerMethodField() - update_user = SerializerMethodField() sufficient_credits = SerializerMethodField() pending = SerializerMethodField() diff --git a/backend/api/serializers/credit_transfer_comment.py b/backend/api/serializers/credit_transfer_comment.py index 55a3b6f01..ab8885f30 100644 --- a/backend/api/serializers/credit_transfer_comment.py +++ b/backend/api/serializers/credit_transfer_comment.py @@ -1,26 +1,14 @@ """ Credit Transfer Comment Serializer """ -from rest_framework.serializers import ModelSerializer, SerializerMethodField -from api.models.user_profile import UserProfile from api.models.credit_transfer_comment import CreditTransferComment -from api.serializers.user import MemberSerializer +from api.mixins.user_mixin import UserSerializerMixin - -class CreditTransferCommentSerializer(ModelSerializer): +class CreditTransferCommentSerializer(UserSerializerMixin): """ Serializer for Credit Transfer comments """ - create_user = SerializerMethodField() - - def get_create_user(self, obj): - user = UserProfile.objects.filter(username=obj.create_user).first() - if user is None: - return obj.create_user - - serializer = MemberSerializer(user, read_only=True) - return serializer.data class Meta: model = CreditTransferComment diff --git a/backend/api/serializers/model_year_report.py b/backend/api/serializers/model_year_report.py index b0994543d..e7deb77b3 100644 --- a/backend/api/serializers/model_year_report.py +++ b/backend/api/serializers/model_year_report.py @@ -164,7 +164,7 @@ def get_makes(self, obj): def get_statuses(self, obj): request = self.context.get("request") - return get_model_year_report_statuses(obj, request.user) + return get_model_year_report_statuses(obj, request) def get_model_year_report_history(self, obj): request = self.context.get("request") @@ -192,7 +192,7 @@ def get_model_year_report_history(self, obj): create_user__in=users, ) - serializer = ModelYearReportHistorySerializer(history, many=True) + serializer = ModelYearReportHistorySerializer(history, many=True, context={"request": request}) return serializer.data diff --git a/backend/api/serializers/model_year_report_assessment.py b/backend/api/serializers/model_year_report_assessment.py index 5bbb31e93..f822b57ab 100644 --- a/backend/api/serializers/model_year_report_assessment.py +++ b/backend/api/serializers/model_year_report_assessment.py @@ -158,7 +158,7 @@ def get_assessment_comment(self, obj): if not assessment_comment: return [] serializer = ModelYearReportAssessmentCommentSerializer( - assessment_comment, read_only=True, many=True + assessment_comment, read_only=True, many=True, context={'request': request} ) return serializer.data diff --git a/backend/api/serializers/model_year_report_assessment_comment.py b/backend/api/serializers/model_year_report_assessment_comment.py index 378e8231c..75f4c841b 100644 --- a/backend/api/serializers/model_year_report_assessment_comment.py +++ b/backend/api/serializers/model_year_report_assessment_comment.py @@ -1,24 +1,11 @@ -from rest_framework.serializers import ModelSerializer, \ - SerializerMethodField from api.models.model_year_report_assessment_comment import ModelYearReportAssessmentComment -from api.models.user_profile import UserProfile -from api.serializers.user import MemberSerializer +from api.mixins.user_mixin import UserSerializerMixin - -class ModelYearReportAssessmentCommentSerializer(ModelSerializer): +class ModelYearReportAssessmentCommentSerializer(UserSerializerMixin): """ Serializer for assessment comments """ - create_user = SerializerMethodField() - - def get_create_user(self, obj): - user = UserProfile.objects.filter(username=obj.create_user).first() - if user is None: - return obj.create_user - - serializer = MemberSerializer(user, read_only=True) - return serializer.data - + class Meta: model = ModelYearReportAssessmentComment fields = ( diff --git a/backend/api/serializers/model_year_report_confirmation.py b/backend/api/serializers/model_year_report_confirmation.py index 2378de2a4..2f4a360d3 100644 --- a/backend/api/serializers/model_year_report_confirmation.py +++ b/backend/api/serializers/model_year_report_confirmation.py @@ -1,24 +1,13 @@ -from rest_framework.serializers import ModelSerializer, SerializerMethodField - from api.models.model_year_report_confirmation import \ ModelYearReportConfirmation -from api.models.user_profile import UserProfile -from api.serializers.user import UserSerializer from api.serializers.signing_authority_assertion import \ SigningAuthorityAssertionSerializer +from api.mixins.user_mixin import UserSerializerMixin -class ModelYearReportConfirmationSerializer(ModelSerializer): - create_user = SerializerMethodField() +class ModelYearReportConfirmationSerializer(UserSerializerMixin): signing_authority_assertion = SigningAuthorityAssertionSerializer() - def get_create_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.create_user) - if user_profile.exists(): - serializer = UserSerializer(user_profile.first(), read_only=True) - return serializer.data - - return obj.create_user class Meta: model = ModelYearReportConfirmation diff --git a/backend/api/serializers/model_year_report_history.py b/backend/api/serializers/model_year_report_history.py index d1fc40069..af85ac859 100644 --- a/backend/api/serializers/model_year_report_history.py +++ b/backend/api/serializers/model_year_report_history.py @@ -1,25 +1,13 @@ from enumfields.drf import EnumField -from rest_framework.serializers import ModelSerializer, SerializerMethodField - from api.models.model_year_report_history import ModelYearReportHistory from api.models.model_year_report_statuses import ModelYearReportStatuses -from api.models.user_profile import UserProfile -from api.serializers.user import MemberSerializer - +from api.mixins.user_mixin import UserSerializerMixin -class ModelYearReportHistorySerializer(ModelSerializer): - create_user = SerializerMethodField() +class ModelYearReportHistorySerializer(UserSerializerMixin): + validation_status = EnumField(ModelYearReportStatuses, read_only=True) - def get_create_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.create_user) - - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - return serializer.data - - return obj.create_user - + class Meta: model = ModelYearReportHistory fields = ('create_timestamp', 'create_user', 'validation_status') diff --git a/backend/api/serializers/model_year_report_noa.py b/backend/api/serializers/model_year_report_noa.py index 42dce717a..fe6c23849 100644 --- a/backend/api/serializers/model_year_report_noa.py +++ b/backend/api/serializers/model_year_report_noa.py @@ -6,9 +6,9 @@ from api.models.supplemental_report_history import SupplementalReportHistory from api.models.supplemental_report import SupplementalReport from api.models.user_profile import UserProfile -from api.serializers.user import MemberSerializer from django.db.models import Q from api.utilities.report_history import exclude_from_history +from api.mixins.user_mixin import UserSerializerMixin class ModelYearReportNoaSerializer(ModelSerializer): @@ -27,21 +27,14 @@ class Meta: ) -class SupplementalNOASerializer(ModelSerializer): +class SupplementalNOASerializer(UserSerializerMixin): status = SerializerMethodField() - update_user = SerializerMethodField() is_reassessment = SerializerMethodField() display_superseded_text = SerializerMethodField() def get_status(self, obj): return obj.validation_status.value - def get_update_user(self, obj): - user = UserProfile.objects.filter(username=obj.update_user).first() - if user is None: - return obj.create_user - return user.display_name - def get_display_superseded_text(self, obj): if obj.validation_status == ModelYearReportStatuses.ASSESSED: model_year_report_id = SupplementalReport.objects.filter( @@ -71,14 +64,13 @@ def get_is_reassessment(self, obj): class Meta: model = SupplementalReportHistory fields = ( - 'update_timestamp', 'status', 'id', 'update_user', 'supplemental_report_id', 'display_superseded_text', - 'is_reassessment' + 'update_timestamp', 'status', 'id', 'supplemental_report_id', 'display_superseded_text', + 'is_reassessment', 'update_user' ) -class ModelYearReportHistorySerializer(ModelSerializer): +class ModelYearReportHistorySerializer(UserSerializerMixin): status = SerializerMethodField() - create_user = SerializerMethodField() is_reassessment = SerializerMethodField() def get_is_reassessment(self, obj): @@ -91,15 +83,6 @@ def get_is_reassessment(self, obj): def get_status(self, obj): return obj.validation_status.value - def get_create_user(self, obj): - user = UserProfile.objects.filter(username=obj.create_user).first() - if user is None: - return None - - serializer = MemberSerializer(user) - - return serializer.data - class Meta: model = ModelYearReportHistory fields = ( @@ -113,7 +96,7 @@ class Meta: fields = ModelYearReportHistorySerializer.Meta.fields + ('supplemental_report_id',) -class SupplementalReportSerializer(ModelSerializer): +class SupplementalReportSerializer(UserSerializerMixin): status = SerializerMethodField() history = SerializerMethodField() is_supplementary = SerializerMethodField() @@ -145,7 +128,7 @@ def get_history(self, obj): refined_history = exclude_from_history(history, request.user) - serializer = SupplementalReportHistorySerializer(refined_history, many=True) + serializer = SupplementalReportHistorySerializer(refined_history, many=True, context={'request': request}) return serializer.data class Meta: @@ -156,7 +139,7 @@ class Meta: ) -class SupplementalModelYearReportSerializer(ModelSerializer): +class SupplementalModelYearReportSerializer(UserSerializerMixin): status = SerializerMethodField() history = SerializerMethodField() supplemental_id = SerializerMethodField() @@ -186,7 +169,7 @@ def get_history(self, obj): refined_history = exclude_from_history(history, request.user) - serializer = ModelYearReportHistorySerializer(refined_history, many=True) + serializer = ModelYearReportHistorySerializer(refined_history, many=True, context={'request': request}) return serializer.data diff --git a/backend/api/serializers/model_year_report_supplemental.py b/backend/api/serializers/model_year_report_supplemental.py index 56e8f7d5f..726e022f8 100644 --- a/backend/api/serializers/model_year_report_supplemental.py +++ b/backend/api/serializers/model_year_report_supplemental.py @@ -27,12 +27,12 @@ SupplementalReportComment from api.services.minio import minio_get_object from api.models.user_profile import UserProfile -from api.serializers.user import MemberSerializer from api.models.supplemental_report_supplier_information import \ SupplementalReportSupplierInformation +from api.mixins.user_mixin import UserSerializerMixin +class ModelYearReportZevSalesSerializer(UserSerializerMixin): -class ModelYearReportZevSalesSerializer(ModelSerializer): class Meta: model = SupplementalReportSales fields = '__all__' @@ -40,6 +40,17 @@ class Meta: class ModelYearReportSupplementalCreditActivitySerializer(ModelSerializer): model_year = ModelYearSerializer() + category = SerializerMethodField() + + def get_category(self, obj): + context = getattr(self, "context", None) + if context is not None and context.get("category_transforms") is not None: + category_transforms = context.get("category_transforms") + category = obj.category + new_category = category_transforms.get(category) + if new_category is not None: + return new_category + return obj.category class Meta: model = SupplementalReportCreditActivity @@ -49,7 +60,8 @@ class Meta: ) -class ModelYearReportSupplementalCommentSerializer(ModelSerializer): +class ModelYearReportSupplementalCommentSerializer(UserSerializerMixin): + class Meta: model = SupplementalReportComment fields = ( @@ -60,19 +72,10 @@ class Meta: ) -class SupplementalReportAssessmentCommentSerializer(ModelSerializer): +class SupplementalReportAssessmentCommentSerializer(UserSerializerMixin): """ Serializer for supplemental report assessment comments """ - create_user = SerializerMethodField() - - def get_create_user(self, obj): - user = UserProfile.objects.filter(username=obj.create_user).first() - if user is None: - return obj.create_user - - serializer = MemberSerializer(user, read_only=True) - return serializer.data class Meta: model = SupplementalReportAssessmentComment @@ -201,7 +204,7 @@ def get_assessment_comment(self, obj): if not assessment_comment: return [] serializer = SupplementalReportAssessmentCommentSerializer( - assessment_comment, read_only=True, many=True + assessment_comment, read_only=True, many=True, context={'request': request} ) return serializer.data @@ -213,7 +216,7 @@ class Meta: ) -class ModelYearReportSupplementalSerializer(ModelSerializer): +class ModelYearReportSupplementalSerializer(UserSerializerMixin): status = EnumField(ModelYearReportStatuses) credit_activity = SerializerMethodField() supplier_information = SerializerMethodField() @@ -222,7 +225,6 @@ class ModelYearReportSupplementalSerializer(ModelSerializer): attachments = SerializerMethodField() from_supplier_comments = SerializerMethodField() actual_status = SerializerMethodField() - create_user = SerializerMethodField() reassessment = SerializerMethodField() def get_reassessment(self, obj): @@ -279,13 +281,6 @@ def get_reassessment(self, obj): 'status': obj.status.value } - def get_create_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.create_user) - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - return serializer.data - return obj.create_user - def get_actual_status(self, obj): request = self.context.get('request') # is this a reassessment report? if so this is the actual status @@ -353,6 +348,7 @@ def get_supplier_information(self, obj): return serializer.data def get_zev_sales(self, obj): + request = self.context.get('request') sales_queryset = SupplementalReportSales.objects.filter( supplemental_report_id=obj.id ) @@ -362,7 +358,7 @@ def get_zev_sales(self, obj): # supplemental_report_id=obj.supplemental_id # ) - sales_serializer = ModelYearReportZevSalesSerializer(sales_queryset, many=True) + sales_serializer = ModelYearReportZevSalesSerializer(sales_queryset, many=True, context={'request': request}) return sales_serializer.data diff --git a/backend/api/serializers/sales_submission.py b/backend/api/serializers/sales_submission.py index ad5b9720b..73c64fe8c 100644 --- a/backend/api/serializers/sales_submission.py +++ b/backend/api/serializers/sales_submission.py @@ -20,9 +20,7 @@ from api.models.icbc_snapshot_data import IcbcSnapshotData from api.serializers.sales_submission_comment import \ SalesSubmissionCommentSerializer -from api.models.user_profile import UserProfile from api.models.vin_statuses import VINStatuses -from api.serializers.user import MemberSerializer from api.serializers.organization import OrganizationSerializer from api.serializers.sales_submission_content import \ SalesSubmissionContentSerializer @@ -31,25 +29,9 @@ from api.models.sales_evidence import SalesEvidence from api.serializers.sales_evidence import SalesEvidenceSerializer from api.services.minio import minio_remove_object - +from api.mixins.user_mixin import UserSerializerMixin class BaseSerializer(): - def get_update_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.update_user) - - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - return serializer.data - - return obj.update_user - - def get_create_user(self, obj): - user_profile = UserProfile.objects.filter(username=obj.create_user) - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - return serializer.data - return obj.create_user - def get_validation_status(self, obj): request = self.context.get('request') @@ -68,10 +50,8 @@ def get_validation_status(self, obj): class SalesSubmissionHistorySerializer( - ModelSerializer, EnumSupportSerializerMixin, BaseSerializer + UserSerializerMixin, EnumSupportSerializerMixin, BaseSerializer ): - create_user = SerializerMethodField() - update_user = SerializerMethodField() validation_status = SerializerMethodField() class Meta: @@ -221,15 +201,14 @@ class Meta: model = SalesSubmission fields = [ 'id', 'submission_history_timestamp', 'submission_history', 'organization', 'totals', 'unselected', - 'total_warnings', 'total_credits', 'validation_status' + 'total_warnings', 'total_credits', 'validation_status', 'submission_date' ] class SalesSubmissionListSerializer( - SalesSubmissionBaseListSerializer + UserSerializerMixin, SalesSubmissionBaseListSerializer ): organization = OrganizationSerializer(read_only=True) - update_user = SerializerMethodField() class Meta(SalesSubmissionBaseListSerializer.Meta): fields = SalesSubmissionBaseListSerializer.Meta.fields + [ @@ -289,18 +268,16 @@ class Meta: class SalesSubmissionSerializer( - ModelSerializer, EnumSupportSerializerMixin, + UserSerializerMixin, EnumSupportSerializerMixin, BaseSerializer ): evidence = SerializerMethodField() content = SerializerMethodField() - create_user = SerializerMethodField() eligible = SerializerMethodField() history = SerializerMethodField() icbc_current_to = SerializerMethodField() organization = OrganizationSerializer(read_only=True) sales_submission_comment = SerializerMethodField() - update_user = SerializerMethodField() validation_status = SerializerMethodField() def get_evidence(self, instance): diff --git a/backend/api/serializers/sales_submission_comment.py b/backend/api/serializers/sales_submission_comment.py index 43b6d783c..9cc0c8569 100644 --- a/backend/api/serializers/sales_submission_comment.py +++ b/backend/api/serializers/sales_submission_comment.py @@ -1,32 +1,15 @@ """ Sales Submission Comment Serializer """ -from rest_framework.serializers import ModelSerializer, SerializerMethodField -from api.models.user_profile import UserProfile from api.models.sales_submission_comment import SalesSubmissionComment -from api.serializers.user import MemberSerializer +from api.mixins.user_mixin import UserSerializerMixin -class SalesSubmissionCommentSerializer(ModelSerializer): + +class SalesSubmissionCommentSerializer(UserSerializerMixin): """ Serializer for sales submission comments """ - create_user = SerializerMethodField() - - def get_create_user(self, obj): - request = self.context.get('request') - commenting_user = UserProfile.objects.filter(username=obj.create_user).first() - if commenting_user is None: - return obj.create_user - if not commenting_user.is_government or request.user.is_government: - ## if the commentor is not government or the request - # user is government, show all the data - serializer = MemberSerializer(commenting_user, read_only=True) - return serializer.data - else: - #if the request user is not government and the commenter - #is government - return {'display_name': 'Government User'} def update(self, instance, validated_data): instance.comment = validated_data.get("comment") diff --git a/backend/api/serializers/user.py b/backend/api/serializers/user.py index 01e69a717..e51899c23 100644 --- a/backend/api/serializers/user.py +++ b/backend/api/serializers/user.py @@ -38,7 +38,7 @@ class UserBasicSerializer(serializers.ModelSerializer): class Meta: model = UserProfile fields = ( - 'display_name', 'organization', 'is_government' + 'id', 'display_name', 'organization', 'is_government' ) class UserSerializer(serializers.ModelSerializer): diff --git a/backend/api/services/model_year_report.py b/backend/api/services/model_year_report.py index 5c5beccc2..52d49c923 100644 --- a/backend/api/services/model_year_report.py +++ b/backend/api/services/model_year_report.py @@ -39,8 +39,9 @@ from api.models.model_year_report_make import ModelYearReportMake from api.models.model_year_report_assessment import ModelYearReportAssessment from api.models.model_year_report_assessment_comment import ModelYearReportAssessmentComment +from ..mixins.user_mixin import get_user_data -def get_model_year_report_statuses(report, request_user=None): +def get_model_year_report_statuses(report, request=None): supplier_information_status = 'UNSAVED' consumer_sales_status = 'UNSAVED' compliance_obligation_status = 'UNSAVED' @@ -112,16 +113,11 @@ def get_model_year_report_statuses(report, request_user=None): consumer_sales_status = 'SUBMITTED' compliance_obligation_status = 'SUBMITTED' summary_status = 'SUBMITTED' - - user_profile = UserProfile.objects.filter(username=report.update_user) - - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - - summary_confirmed_by = { - 'create_timestamp': report.update_timestamp, - 'create_user': serializer.data - } + create_user = get_user_data(report.create_user, request) + assessment_confirmed_by = { + 'create_timestamp': report.create_timestamp, ##there are some discrepancies in the + 'create_user': create_user ## create/update timestamps and users so i made a guess here + } if report.validation_status == ModelYearReportStatuses.RECOMMENDED: assessment_status = 'RECOMMENDED' @@ -130,21 +126,17 @@ def get_model_year_report_statuses(report, request_user=None): compliance_obligation_status = 'RECOMMENDED' summary_status = 'RECOMMENDED' - if not request_user.is_government: + if not request.user.is_government: assessment_status = 'SUBMITTED' supplier_information_status = 'SUBMITTED' consumer_sales_status = 'SUBMITTED' compliance_obligation_status = 'SUBMITTED' summary_status = 'SUBMITTED' - - user_profile = UserProfile.objects.filter(username=report.update_user) - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - - assessment_confirmed_by = { - 'create_timestamp': report.update_timestamp, - 'create_user': serializer.data - } + create_user = get_user_data(report.create_user, request) + assessment_confirmed_by = { + 'create_timestamp': report.update_timestamp, + 'create_user': create_user + } if report.validation_status == ModelYearReportStatuses.RETURNED: assessment_status = 'RETURNED' @@ -152,21 +144,17 @@ def get_model_year_report_statuses(report, request_user=None): consumer_sales_status = 'RETURNED' compliance_obligation_status = 'RETURNED' summary_status = 'RETURNED' - if not request_user.is_government: + if not request.user.is_government: assessment_status = 'SUBMITTED' supplier_information_status = 'SUBMITTED' consumer_sales_status = 'SUBMITTED' compliance_obligation_status = 'SUBMITTED' summary_status = 'SUBMITTED' - - user_profile = UserProfile.objects.filter(username=report.update_user) - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - - assessment_confirmed_by = { - 'create_timestamp': report.update_timestamp, - 'create_user': serializer.data - } + create_user = get_user_data(report.create_user, request) + assessment_confirmed_by = { + 'create_timestamp': report.create_timestamp, + 'create_user': create_user + } if report.validation_status == ModelYearReportStatuses.ASSESSED: supplier_information_status = 'ASSESSED' @@ -174,14 +162,11 @@ def get_model_year_report_statuses(report, request_user=None): compliance_obligation_status = 'ASSESSED' summary_status = 'ASSESSED' assessment_status = 'ASSESSED' - user_profile = UserProfile.objects.filter(username=report.update_user) - if user_profile.exists(): - serializer = MemberSerializer(user_profile.first(), read_only=True) - - assessment_confirmed_by = { - 'create_timestamp': report.update_timestamp, - 'create_user': serializer.data - } + create_user = get_user_data(report.create_user, request) + assessment_confirmed_by = { + 'update_timestamp': report.update_timestamp, + 'update_user': create_user + } return { 'supplier_information': { diff --git a/backend/api/services/supplemental_report.py b/backend/api/services/supplemental_report.py index 587bcad74..0df719c6e 100644 --- a/backend/api/services/supplemental_report.py +++ b/backend/api/services/supplemental_report.py @@ -1,5 +1,9 @@ from api.models.supplemental_report import SupplementalReport from api.models.model_year_report_statuses import ModelYearReportStatuses +from api.models.model_year_report import ModelYearReport +from api.models.supplemental_report_credit_activity import ( + SupplementalReportCreditActivity, +) def get_map_of_model_year_report_ids_to_latest_supplemental_ids( @@ -55,3 +59,56 @@ def get_latest_assessed_supplemental(model_year_report): if report_in_question is not None: result = SupplementalReport.objects.get(id=report_in_question.id) return result + + +def get_previous_reassessment_credit_activity(model_year_report_id, category): + result = [] + report = ( + ModelYearReport.objects.filter(id=model_year_report_id) + .select_related("organization", "model_year") + .first() + ) + if report is not None: + previous_model_year = int(report.model_year.name) - 1 + previous_report = ( + ModelYearReport.objects.filter(organization=report.organization) + .filter(model_year__name=previous_model_year) + .filter( + validation_status__in=[ + ModelYearReportStatuses.ASSESSED, + ModelYearReportStatuses.REASSESSED, + ] + ) + .first() + ) + if previous_report is not None: + supplemental_report = ( + SupplementalReport.objects.filter(model_year_report=previous_report) + .filter( + status__in=[ + ModelYearReportStatuses.ASSESSED, + ModelYearReportStatuses.REASSESSED, + ] + ) + .order_by("-create_timestamp") + .first() + ) + if supplemental_report is not None: + result = list( + SupplementalReportCreditActivity.objects.filter( + supplemental_report=supplemental_report + ) + .filter(category=category) + .select_related("model_year") + ) + return result + + +def get_reassessment_credit_activity(supplemental_id, category): + return list( + SupplementalReportCreditActivity.objects.filter( + supplemental_report=supplemental_id + ) + .filter(category=category) + .select_related("model_year") + ) diff --git a/backend/api/tests/test_organizations.py b/backend/api/tests/test_organizations.py index 01953f6a8..a27019fc6 100644 --- a/backend/api/tests/test_organizations.py +++ b/backend/api/tests/test_organizations.py @@ -1,8 +1,236 @@ from .base_test_case import BaseTestCase - +from api.models.organization import Organization +import json +from api.models.organization_ldv_sales import OrganizationLDVSales class TestOrganizations(BaseTestCase): + def setUp(self): + super().setUp() + organizations = Organization.objects.filter( + is_government=False, + is_active=True + ) + self.user = self.users['RTAN_BCEID'] + self.organization = Organization.objects.get(id=self.user.organization_id) + filtered_organizations = [org for org in organizations if org != self.organization] + self.other_organization = filtered_organizations[0] + + """ + BCEID USERS + """ + + """a bceid user can see the list of orgs with names but no balances""" + def test_bceid_get_list_of_orgs(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations") + data = response.json()[0] + response_keys = list(data.keys()) + self.assertEqual(response.status_code, 200) + self.assertNotIn('balance', response_keys) - def test_get_my_organization(self): + """a user can see the details of their own organization """ + def test_bceid_get_my_organization_details(self): response = self.clients['RTAN_BCEID'].get("/api/organizations/mine") + data = response.json() + response_keys = list(data.keys()) + balance = self.organization.balance + self.assertEqual(response.status_code, 200) + self.assertIn('balance', response_keys) + self.assertIn('users', response_keys) + self.assertIn('organizationAddress', response_keys) + self.assertEqual(balance.get('A'), balance['A']) + self.assertEqual(balance.get('B'), balance['B']) + + """a bceid user cannot see balance or sales of a different organization""" + def test_bceid_get_other_organization_details(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}".format(self.other_organization.id)) + data = response.json() + response_keys = list(data.keys()) + self.assertEqual(response.status_code, 404) + self.assertNotIn('balance', response_keys) + self.assertNotIn('users', response_keys) + self.assertNotIn('organizationAddress', response_keys) + + """a bceid user cannot see the users of a different organization""" + def test_bceid_get_other_organization_users(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/users".format(self.other_organization.id)) + data = response.json() + response_keys = list(data.keys()) + self.assertEqual(response.status_code, 404) + self.assertNotIn('users', response_keys) + + """a bceid user cannot see the sales of other orgs""" + def test_bceid_get_sales(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/sales".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see the transactions of specific orgs""" + def test_bceid_get_supplier_transactions(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/supplier_transactions".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see the ldv sales of specific orgs""" + def test_bceid_get_ldvsales(self): + response = self.clients['RTAN_BCEID'].put("/api/organizations/{}/ldv_sales".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see other orgs recent supplier balance""" + def test_bceid_get_recent_supplier_balance(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/recent_supplier_balance".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see another orgs assessed supplementals""" + def test_bceid_get_assessed_supplementals_map_other_org(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/assessed_supplementals_map".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user can see their own assessed supplementals""" + def test_bceid_get_assessed_supplementals_map(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/assessed_supplementals_map".format(self.organization.id)) + self.assertEqual(response.status_code, 200) + + """a bceid user can see myr ids from their own org""" + def test_bceid_get_most_recent_myr_id(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/most_recent_myr_id".format(self.organization.id)) + self.assertEqual(response.status_code, 200) + + """a bceid user cannot see myr ids from other orgs""" + def test_bceid_get_most_recent_myr_id_other_org(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/most_recent_myr_id".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see compliance years for another org""" + def test_bceid_get_compliance_years_other_org(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/compliance_years".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user can see compliance years for their own org""" + def test_bceid_get_compliance_years(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/compliance_years".format(self.organization.id)) + self.assertEqual(response.status_code, 200) + + """a bceid user cannot see transactions listed by year for their org""" + def test_bceid_list_by_year(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/list_by_year".format(self.organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see transactions listed by year for another org""" + def test_bceid_list_by_year_other_org(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/list_by_year".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user cannot see model years for another org""" + def test_bceid_get_model_years_other_org(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/model_years".format(self.other_organization.id)) + self.assertEqual(response.status_code, 403) + + """a bceid user can see model years for their own org""" + def test_bceid_get_model_years(self): + response = self.clients['RTAN_BCEID'].get("/api/organizations/{}/model_years".format(self.organization.id)) + self.assertEqual(response.status_code, 200) + + """ + IDIR USERS + """ + + """an idir user can see the list of orgs with balance, ldv sales, etc""" + def test_idir_get_list_of_orgs(self): + response = self.clients['RTAN'].get("/api/organizations") + data = response.json()[0] + response_keys = list(data.keys()) + self.assertEqual(response.status_code, 200) + self.assertIn('balance', response_keys) + self.assertIn('avgLdvSales', response_keys) + self.assertIn('organizationAddress', response_keys) + self.assertIn('name', response_keys) + self.assertIn('isActive', response_keys) + self.assertIn('supplierClass', response_keys) + self.assertIn('isGovernment', response_keys) + self.assertIn('ldvSales', response_keys) + self.assertIn('hasSubmittedReport', response_keys) + self.assertIn('firstModelYear', response_keys) + self.assertIn('hasReport', response_keys) + + """an idir user can see the balance and sales of an organization""" + def test_idir_get_organization_details(self): + response = self.clients['RTAN'].get("/api/organizations/{}".format(self.other_organization.id)) + data = response.json() + response_keys = list(data.keys()) + self.assertEqual(response.status_code, 200) + self.assertIn('balance', response_keys) + self.assertIn('avgLdvSales', response_keys) + self.assertIn('organizationAddress', response_keys) + self.assertIn('name', response_keys) + self.assertIn('isActive', response_keys) + self.assertIn('supplierClass', response_keys) + self.assertIn('isGovernment', response_keys) + self.assertIn('ldvSales', response_keys) + self.assertIn('hasSubmittedReport', response_keys) + self.assertIn('firstModelYear', response_keys) + self.assertIn('hasReport', response_keys) + + """an idir user can see the users of an organization""" + def test_idir_get_organization_users(self): + response = self.clients['RTAN'].get("/api/organizations/{}/users".format(self.other_organization.id)) + data = response.json() + response_keys = list(data.keys()) + self.assertEqual(response.status_code, 200) + self.assertIn('users', response_keys) + + """an idir user can see the sales of specific orgs""" + def test_idir_get_sales(self): + response = self.clients['RTAN'].get("/api/organizations/{}/sales".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can see the recent supplier balance""" + def test_idir_recent_supplier_balance(self): + response = self.clients['RTAN'].get("/api/organizations/{}/recent_supplier_balance".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can see transactions""" + def test_idir_get_transactions(self): + response = self.clients['RTAN'].get("/api/organizations/{}/supplier_transactions".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can PUT ldv sales""" + def test_idir_put_ldv_sales(self): + response = self.clients['RTAN'].put("/api/organizations/{}/ldv_sales".format(self.organization.id), + {"model_year": '2024', "ldv_sales": '123'}, + content_type='application/json') + self.assertEqual(response.status_code, 200) + + """an idir user can PUT(delete) ldv sales""" + def test_idir_delete_ldv_sales(self): + self.clients['RTAN'].put("/api/organizations/{}/ldv_sales".format(self.organization.id), + {"model_year": '2020', "ldv_sales": '111'}, + content_type='application/json') + ldv = OrganizationLDVSales.objects.first() + response = self.clients['RTAN'].put("/api/organizations/{}/ldv_sales".format(self.organization.id), + {"id": ldv.id}, + content_type='application/json') + self.assertEqual(response.status_code, 200) + + """an idir user can see assessed supplementals map""" + def test_idir_get_assessed_supplementals_map(self): + response = self.clients['RTAN'].get("/api/organizations/{}/assessed_supplementals_map".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can see myr ids for an org""" + def test_idir_get_most_recent_myr_id(self): + response = self.clients['RTAN'].get("/api/organizations/{}/most_recent_myr_id".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can see compliance years for an org""" + def test_idir_get_compliance_years(self): + response = self.clients['RTAN'].get("/api/organizations/{}/compliance_years".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can see transactions listed by year an org""" + def test_idir_list_by_year(self): + response = self.clients['RTAN'].get("/api/organizations/{}/list_by_year".format(self.other_organization.id)) + self.assertEqual(response.status_code, 200) + + """an idir user can see model years for an org""" + def test_idir_get_model_years(self): + response = self.clients['RTAN'].get("/api/organizations/{}/model_years".format(self.other_organization.id)) self.assertEqual(response.status_code, 200) + diff --git a/backend/api/tests/test_users.py b/backend/api/tests/test_users.py new file mode 100644 index 000000000..76e0b1b80 --- /dev/null +++ b/backend/api/tests/test_users.py @@ -0,0 +1,99 @@ +from .base_test_case import BaseTestCase +import random +from ..models.user_profile import UserProfile +from django.urls import reverse +from rest_framework import status +from ..models.organization import Organization +from ..models.role import Role +class TestUsers(BaseTestCase): + def setUp(self): + super().setUp() + organizations = Organization.objects.filter( + is_government=False, + is_active=True + ) + self.user1 = self.users['RTAN_BCEID'] + self.org1 = self.users[self.user1.username].organization + filtered_organizations = [org for org in organizations if org != self.org1] + self.user2 = UserProfile.objects.create( + username="testuser", + organization=self.org1, + first_name="user", + last_name="two", + display_name="test user 2", + keycloak_email="user2@email.com" + ) + other_org = random.choice(filtered_organizations) + self.user3 = UserProfile.objects.create( + username="otherorguser", + organization=other_org, + first_name="user", + last_name="three", + display_name="test user 3", + keycloak_email="user3@email.com" + ) + role_queryset = Role.objects.filter(is_government_role= 'False') + self.roles = list(role_queryset.values_list('id', flat=True)) + + """ assert that a user can get their own details""" + def test_get_current_user(self): + response = self.clients[self.user1.username].get("/api/users/current") + self.assertEqual(response.status_code, 200) + + """user can get details of users in their own org""" + def test_get_details_other_user_in_org(self): + response = self.clients[self.user1.username].get("/api/users/{}".format(self.user2.id)) + self.assertEqual(response.status_code, 200) + + def test_get_details_other_users_other_org(self): + """user cannot get details of users in other orgs""" + response = self.clients[self.user1.username].get("/api/users/{}".format(self.user3.id)) + self.assertEqual(response.status_code, 404) + + """user can update their own profile""" + def test_edit_self(self): + response = self.clients[self.user1.username].put( + "/api/users/{}".format(self.user1.id), + {'first_name':"test change", + 'last_name': self.user1.last_name, + 'title': 'director', + 'username': self.user1.username, + 'keycloak_email': 'test@email.com', + 'roles': self.roles}, + content_type='application/json', + ) + self.assertEquals(response.data['first_name'], 'test change') + self.assertEqual(response.status_code, 200) + + """that user can update other users in their own org""" + def test_edit_user_in_same_org(self): + response = self.clients[self.user1.username].put( + "/api/users/{}".format(self.user2.id), + {'first_name':"test change", + 'last_name': self.user2.last_name, + 'title': 'director', + 'username': self.user2.username, + 'keycloak_email': self.user2.keycloak_email, + 'roles': self.roles}, content_type='application/json',) + + self.assertEquals(response.data['first_name'], 'test change') + self.assertEqual(response.status_code, 200) + + """updating a user from another org should fail""" + def test_edit_user_in_other_org(self): + response = self.clients[self.user1.username].put( + "/api/users/{}".format(self.user3.id), + {'first_name':"test change", + 'last_name': self.user3.last_name, + 'title': 'director', + 'username': self.user3.username, + 'keycloak_email': self.user3.keycloak_email, + 'roles': self.roles + }, content_type='application/json',) + self.assertEqual(response.status_code, 404) + self.assertEqual(response.data['detail'], 'Not found.') + # Ensure that 'first_name' is not in the response data (since it's a 404) + self.assertNotIn('first_name', response.data) + # get the user3 object and make sure the first name hasnt changed + user3_updated = UserProfile.objects.get(id=self.user3.id) + self.assertEquals(user3_updated.first_name, self.user3.first_name) \ No newline at end of file diff --git a/backend/api/viewsets/model_year_report.py b/backend/api/viewsets/model_year_report.py index 7e7272891..282bef2c3 100644 --- a/backend/api/viewsets/model_year_report.py +++ b/backend/api/viewsets/model_year_report.py @@ -168,7 +168,7 @@ def retrieve(self, request, pk=None): model_year_report_id=pk ) - history = ModelYearReportHistorySerializer(history_list, many=True) + history = ModelYearReportHistorySerializer(history_list, many=True, context={'request': request}) confirmations = ( ModelYearReportConfirmation.objects.filter(model_year_report_id=pk) @@ -221,7 +221,7 @@ def retrieve(self, request, pk=None): "create_user": report.create_user, "confirmations": confirmations, "ldv_sales": report.ldv_sales, - "statuses": get_model_year_report_statuses(report, request.user), + "statuses": get_model_year_report_statuses(report, request), "ldv_sales_previous": ldv_sales_previous.data if ldv_sales_previous else [], @@ -755,7 +755,6 @@ def supplemental(self, request, pk): if not data: data = SupplementalReport() data.model_year_report_id = report.id - serializer = ModelYearReportSupplementalSerializer( data, context={"request": request} ) @@ -1231,10 +1230,11 @@ def supplemental_comment_delete(self, request, pk): @action(detail=True, methods=["get"]) def assessed_supplementals(self, request, pk): + print('::::::::', request) report = get_object_or_404(ModelYearReport, pk=pk) data = report.get_assessed_supplementals() serializer = ModelYearReportSupplementalSerializer( - data, context={"request": request}, many=True + data, many=True, context={"request": request} ) return Response(serializer.data) diff --git a/backend/api/viewsets/model_year_report_compliance_obligation.py b/backend/api/viewsets/model_year_report_compliance_obligation.py index acee14477..20e4bcf35 100644 --- a/backend/api/viewsets/model_year_report_compliance_obligation.py +++ b/backend/api/viewsets/model_year_report_compliance_obligation.py @@ -37,9 +37,15 @@ from api.services.summary import parse_summary_serializer, \ get_current_year_balance from api.models.organization_deficits import OrganizationDeficits -from api.services.supplemental_report import get_latest_assessed_supplemental +from api.services.supplemental_report import ( + get_latest_assessed_supplemental, + get_previous_reassessment_credit_activity, + get_reassessment_credit_activity +) from api.services.model_year_report_ldv_sales import get_most_recent_ldv_sales from api.permissions.same_organization import SameOrganizationPermissions +from api.models.supplemental_report import SupplementalReport +from api.serializers.model_year_report_supplemental import ModelYearReportSupplementalCreditActivitySerializer class ModelYearReportComplianceObligationViewset(viewsets.GenericViewSet): @@ -47,6 +53,12 @@ class ModelYearReportComplianceObligationViewset(viewsets.GenericViewSet): same_org_permissions_context = { "default_manager": ModelYearReport.objects, "default_path_to_org": ("organization",), + "custom_pk_actions": { + "reassessment_credit_activity": { + "manager": SupplementalReport.objects, + "path_to_org": ("model_year_report", "organization") + } + } } http_method_names = ['get', 'post', 'patch'] @@ -528,3 +540,21 @@ def details(self, request, pk=None): 'compliance_offset': compliance_offset, 'ldv_sales': get_most_recent_ldv_sales(report) if most_recent_ldv_sales else report.ldv_sales }) + + # pk should be MYR id + @action(detail=True, methods=['get']) + def previous_reassessment_credit_activity(self, request, pk=None): + credit_activity = get_previous_reassessment_credit_activity(pk, "ProvisionalBalanceAfterCreditReduction") + serializer = ModelYearReportSupplementalCreditActivitySerializer( + credit_activity, + many=True, + context={"category_transforms": {"ProvisionalBalanceAfterCreditReduction": "PreviousReassessmentEndingBalance"}} + ) + return Response(serializer.data) + + # pk should be a supplemental id + @action(detail=True, methods=['get']) + def reassessment_credit_activity(self, request, pk=None): + credit_activity = get_reassessment_credit_activity(pk, "PreviousReassessmentEndingBalance") + serializer = ModelYearReportSupplementalCreditActivitySerializer(credit_activity, many=True) + return Response(serializer.data) \ No newline at end of file diff --git a/backend/api/viewsets/model_year_report_consumer_sales.py b/backend/api/viewsets/model_year_report_consumer_sales.py index 240fd0519..799ecae24 100644 --- a/backend/api/viewsets/model_year_report_consumer_sales.py +++ b/backend/api/viewsets/model_year_report_consumer_sales.py @@ -149,7 +149,7 @@ def retrieve(self, request, pk): create_user__in=users ) - history = ModelYearReportHistorySerializer(history_list, many=True) + history = ModelYearReportHistorySerializer(history_list, many=True, context={'request': request}) validation_status = report.validation_status.value if not request.user.is_government and \ diff --git a/backend/api/viewsets/organization.py b/backend/api/viewsets/organization.py index ade3f6885..f9e6aefc2 100644 --- a/backend/api/viewsets/organization.py +++ b/backend/api/viewsets/organization.py @@ -2,7 +2,7 @@ from rest_framework import mixins, viewsets from rest_framework.decorators import action from rest_framework.response import Response - +from rest_framework import status from api.decorators.permission import permission_required from api.models.organization import Organization from api.models.organization_ldv_sales import OrganizationLDVSales @@ -17,6 +17,7 @@ from api.serializers.organization_ldv_sales import \ OrganizationLDVSalesSerializer from api.permissions.organization import OrganizationPermissions +from api.permissions.same_organization import SameOrganizationPermissions from auditable.views import AuditableCreateMixin, AuditableUpdateMixin from api.services.supplemental_report import get_map_of_model_year_report_ids_to_latest_supplemental_ids from api.services.credit_transaction import ( @@ -46,9 +47,13 @@ class OrganizationViewSet( This viewset automatically provides `list`, `create`, `retrieve`, and `update` actions. """ - permission_classes = [OrganizationPermissions] + permission_classes = [OrganizationPermissions & SameOrganizationPermissions] http_method_names = ['get', 'post', 'put', 'patch'] - + same_org_permissions_context = { + "default_manager": Organization.objects, + "default_path_to_org": (), + "actions_not_to_check": ["retrieve", "partial_update", "users", "list", "mine"] + } serializer_classes = { 'default': OrganizationSerializer, 'mine': OrganizationWithMembersSerializer, @@ -124,7 +129,7 @@ def sales(self, request, pk=None): Get the sales submissions of a specific organization """ if not request.user.is_government: - return Response(None) + return Response({'detail': 'You do not have permission to perform this action.'}, status=status.HTTP_403_FORBIDDEN) sales = SalesSubmission.objects.filter( organization_id=pk @@ -144,7 +149,7 @@ def sales(self, request, pk=None): @method_decorator(permission_required('VIEW_SALES')) def recent_supplier_balance(self, request, pk=None): if not request.user.is_government: - return Response(None) + return Response({'detail': 'You do not have permission to perform this action.'}, status=status.HTTP_403_FORBIDDEN) timestamp = get_timestamp_of_most_recent_reduction(pk) q_obj = None @@ -177,7 +182,7 @@ def supplier_transactions(self, request, pk=None): def ldv_sales(self, request, pk=None): delete_id = request.data.get('id', None) if not request.user.is_government: - return Response(None) + return Response({'detail': 'You do not have permission to perform this action.'}, status=status.HTTP_403_FORBIDDEN) organization = self.get_object() if delete_id: @@ -233,7 +238,7 @@ def compliance_years(self, request, pk=None): @method_decorator(permission_required('VIEW_SALES')) def list_by_year(self, request, pk=None): if not request.user.is_government: - return Response([]) + return Response({'detail': 'You do not have permission to perform this action.'}, status=status.HTTP_403_FORBIDDEN) compliance_year = request.GET.get('year', None) if compliance_year: compliance_period_bounds = get_compliance_period_bounds(compliance_year) diff --git a/backend/api/viewsets/user.py b/backend/api/viewsets/user.py index de0253503..40055124a 100644 --- a/backend/api/viewsets/user.py +++ b/backend/api/viewsets/user.py @@ -1,7 +1,7 @@ from rest_framework import mixins, viewsets from rest_framework.decorators import action from rest_framework.response import Response - +from rest_framework import status from api.models.user_profile import UserProfile from api.permissions.user import UserPermissions from api.serializers.user import UserSerializer, UserSaveSerializer @@ -53,7 +53,9 @@ def current(self, request): @action(detail=False) def download_active(self, request): - - active_bceid_users_excel = create_bceid_emails_sheet() - - return active_bceid_users_excel + user = self.request.user + if user.is_government: + active_bceid_users_excel = create_bceid_emails_sheet() + return active_bceid_users_excel + else: + return Response({'detail': 'You do not have permission to perform this action.'}, status=status.HTTP_403_FORBIDDEN) diff --git a/frontend/.s2i/bin/assemble b/frontend/.s2i/bin/assemble deleted file mode 100755 index 19faefa24..000000000 --- a/frontend/.s2i/bin/assemble +++ /dev/null @@ -1,130 +0,0 @@ -sh-5.1$ cat assemble -#!/bin/bash - -# Prevent running assemble in builders different than official STI image. -# The official nodejs:8-onbuild already run npm install and use different -# application folder. -[ -d "/usr/src/app" ] && exit 0 - -set -e - -# FIXME: Linking of global modules is disabled for now as it causes npm failures -# under RHEL7 -# Global modules good to have -# npmgl=$(grep "^\s*[^#\s]" ../etc/npm_global_module_list | sort -u) -# Available global modules; only match top-level npm packages -#global_modules=$(npm ls -g 2> /dev/null | perl -ne 'print "$1\n" if /^\S+\s(\S+)\@[\d\.-]+/' | sort -u) -# List all modules in common -#module_list=$(/usr/bin/comm -12 <(echo "${global_modules}") | tr '\n' ' ') -# Link the modules -#npm link $module_list - -safeLogging () { - if [[ $1 =~ http[s]?://.*@.*$ ]]; then - echo $1 | sed 's/^.*@/redacted@/' - else - echo $1 - fi -} - -shopt -s dotglob -if [ -d /tmp/artifacts ] && [ "$(ls /tmp/artifacts/ 2>/dev/null)" ]; then - echo "---> Restoring previous build artifacts ..." - mv -T --verbose /tmp/artifacts/node_modules "${HOME}/node_modules" -fi - -echo "---> Installing application source ..." -mv /tmp/src/* ./ - -# Fix source directory permissions -fix-permissions ./ - -if [ ! -z $HTTP_PROXY ]; then - echo "---> Setting npm http proxy to" $(safeLogging $HTTP_PROXY) - npm config set proxy $HTTP_PROXY -fi - -if [ ! -z $http_proxy ]; then - echo "---> Setting npm http proxy to" $(safeLogging $http_proxy) - npm config set proxy $http_proxy -fi - -if [ ! -z $HTTPS_PROXY ]; then - echo "---> Setting npm https proxy to" $(safeLogging $HTTPS_PROXY) - npm config set https-proxy $HTTPS_PROXY -fi - -if [ ! -z $https_proxy ]; then - echo "---> Setting npm https proxy to" $(safeLogging $https_proxy) - npm config set https-proxy $https_proxy -fi - -# Change the npm registry mirror if provided -if [ -n "$NPM_MIRROR" ]; then - npm config set registry $NPM_MIRROR -fi - -# Set the DEV_MODE to false by default. -if [ -z "$DEV_MODE" ]; then - export DEV_MODE=false -fi - -# If NODE_ENV is not set by the user, then NODE_ENV is determined by whether -# the container is run in development mode. -if [ -z "$NODE_ENV" ]; then - if [ "$DEV_MODE" == true ]; then - export NODE_ENV=development - else - export NODE_ENV=production - fi -fi - -if [ "$NODE_ENV" != "production" ]; then - - echo "---> Building your Node application from source" - npm install - -else - - echo "---> Have to set DEV_MODE and NODE_ENV to empty otherwise the deployment can not be started" - echo "---> It'll have error like can not resolve source-map-loader..." - export DEV_MODE="" - export NODE_ENV="" - - if [[ -z "${ARTIFACTORY_USER}" ]]; then - echo "---> Installing all dependencies from external repo" - else - echo "---> Installing all dependencies from Artifactory" - npm config set registry https://artifacts.developer.gov.bc.ca/artifactory/api/npm/npm-remote/ - curl -u $ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD https://artifacts.developer.gov.bc.ca/artifactory/api/npm/auth >> ~/.npmrc - fi - - echo "---> Installing all dependencies" - NODE_ENV=development npm install - - #do not fail when there is no build script - echo "---> Building in production mode" - npm run build --if-present - - echo "---> Pruning the development dependencies" - npm prune - - NPM_TMP=$(npm config get tmp) - if ! mountpoint $NPM_TMP; then - echo "---> Cleaning the $NPM_TMP/npm-*" - rm -rf $NPM_TMP/npm-* - fi - - # Clear the npm's cache and tmp directories only if they are not a docker volumes - NPM_CACHE=$(npm config get cache) - if ! mountpoint $NPM_CACHE; then - echo "---> Cleaning the npm cache $NPM_CACHE" - #As of npm@5 even the 'npm cache clean --force' does not fully remove the cache directory - # instead of $NPM_CACHE* use $NPM_CACHE/*. - # We do not want to delete .npmrc file. - rm -rf "${NPM_CACHE:?}/" - fi -fi - -# Fix source directory permissions -fix-permissions ./ \ No newline at end of file diff --git a/frontend/src/app/router.js b/frontend/src/app/router.js index 5df1e79f8..e77bc1271 100644 --- a/frontend/src/app/router.js +++ b/frontend/src/app/router.js @@ -352,10 +352,13 @@ class Router extends Component { ( + user.isGovernment + && ( + ) )} /> { const result = { reconciledAssessmentData: assessmentData, @@ -151,6 +152,21 @@ const reconcileSupplementaries = ( } } + if (complianceData?.complianceObligation && previousReassessmentCreditActivities) { + const creditActivites = complianceData.complianceObligation + for (const previousReassessmentCreditActivity of previousReassessmentCreditActivities) { + const category = previousReassessmentCreditActivity.category + const modelYear = previousReassessmentCreditActivity.modelYear.name + if (category === 'PreviousReassessmentEndingBalance') { + const creditActivityInQuestion = creditActivites.find((activity) => activity.category === 'creditBalanceStart' && activity.modelYear.name === modelYear) + if (creditActivityInQuestion) { + creditActivityInQuestion.creditAValue = previousReassessmentCreditActivity.creditAValue + creditActivityInQuestion.creditBValue = previousReassessmentCreditActivity.creditBValue + } + } + } + } + return result } diff --git a/frontend/src/credits/components/CreditRequestListTable.js b/frontend/src/credits/components/CreditRequestListTable.js index 4a35b9de1..fb3dfbc51 100644 --- a/frontend/src/credits/components/CreditRequestListTable.js +++ b/frontend/src/credits/components/CreditRequestListTable.js @@ -13,6 +13,7 @@ import ROUTES_CREDIT_REQUESTS from '../../app/routes/CreditRequests' import calculateNumberOfPages from '../../app/utilities/calculateNumberOfPages' import CustomFilterComponent from '../../app/components/CustomFilterComponent' import moment from 'moment-timezone' +import isLegacySubmission from '../../app/utilities/isLegacySubmission' const CreditRequestListTable = (props) => { const { @@ -40,6 +41,15 @@ const CreditRequestListTable = (props) => { }) } + const getSubmissionTotals = (submission) => { + if (['DRAFT', 'SUBMITTED'].indexOf(submission.validationStatus) >= 0) { + const totals = submission.totals.vins + submission.unselected + return totals > 0 ? totals : '-' + } + + return submission.totals.vins > 0 ? submission.totals.vins : '-' + } + const columns = [ { id: 'id', @@ -80,20 +90,32 @@ const CreditRequestListTable = (props) => { }, { accessor: (item) => { - if (['DRAFT', 'SUBMITTED'].indexOf(item.validationStatus) >= 0) { - const totals = item.totals.vins + item.unselected - return totals > 0 ? totals : '-' + if (isLegacySubmission(item)) { + return getSubmissionTotals(item) } - - return item.totals.vins > 0 ? item.totals.vins : '-' + return '-' }, className: 'text-right', - Header: 'Total Eligible ZEVs Supplied', + Header: 'Total Eligible Sales', maxWidth: 150, id: 'total-sales', filterable: false, sortable: false }, + { + accessor: (item) => { + if (!isLegacySubmission(item)) { + return getSubmissionTotals(item) + } + return '-' + }, + className: 'text-right', + Header: 'Total Eligible ZEVs Supplied', + maxWidth: 150, + id: 'total-supplied', + filterable: false, + sortable: false + }, { accessor: (item) => (item.totalWarnings > 0 ? item.totalWarnings : '-'), className: 'text-right', diff --git a/frontend/src/credits/components/CreditTransfersDetailsPage.js b/frontend/src/credits/components/CreditTransfersDetailsPage.js index 5a749d11e..2616ff1ce 100644 --- a/frontend/src/credits/components/CreditTransfersDetailsPage.js +++ b/frontend/src/credits/components/CreditTransfersDetailsPage.js @@ -336,8 +336,9 @@ const CreditTransfersDetailsPage = (props) => { {latestSubmit && (
Signed and submitted by {latestSubmit.createUser.displayName}{' '} - of  - {latestSubmit.createUser.organization.name}  + {latestSubmit.createUser.organization && + <>of {latestSubmit.createUser.organization.name}  + } {moment(latestSubmit.createTimestamp) .tz('America/Vancouver') .format('YYYY-MM-DD hh:mm:ss z')} diff --git a/frontend/src/supplementary/SupplementaryContainer.js b/frontend/src/supplementary/SupplementaryContainer.js index 20d07de6b..2f0efa7f1 100644 --- a/frontend/src/supplementary/SupplementaryContainer.js +++ b/frontend/src/supplementary/SupplementaryContainer.js @@ -41,6 +41,7 @@ const SupplementaryContainer = (props) => { ]) const location = useLocation() const [reassessmentReductions, setReassessmentReductions] = useState({}) + const [previousReassessmentCreditActivities, setPreviousReassessmentCreditActivities] = useState([]) const query = qs.parse(location.search, { ignoreQueryPrefix: true }) @@ -460,6 +461,17 @@ const SupplementaryContainer = (props) => { } creditActivity.push(creditActivityAddition) } + if (!supplementaryId) { + for (const each of previousReassessmentCreditActivities) { + const category = each.category + if (category === 'PreviousReassessmentEndingBalance') { + const creditActivityAddition = { ... each } + creditActivityAddition.modelYear = each.modelYear.name + delete creditActivityAddition.id + creditActivity.push(creditActivityAddition) + } + } + } setNewData({ ...newData, creditActivity }) if (status) { @@ -575,28 +587,38 @@ const SupplementaryContainer = (props) => { const refreshDetails = () => { setLoading(true) + const promises = [ + axios.get( + `${ROUTES_SUPPLEMENTARY.DETAILS.replace(':id', id)}?supplemental_id=${ + supplementaryId || '' + }` + ), + axios.get( + `${ROUTES_SUPPLEMENTARY.ASSESSED_SUPPLEMENTALS.replace(':id', id)}` + ), + axios.get( + `${ROUTES_COMPLIANCE.REPORT_COMPLIANCE_DETAILS_BY_ID.replace(':id', id)}?most_recent_ldv_sales=true&use_from_gov_snapshot=True` + ), + axios.get(ROUTES_COMPLIANCE.RATIOS), + axios.get( + `${ROUTES_SUPPLEMENTARY.ASSESSMENT.replace( + ':id', + id + )}?supplemental_id=${supplementaryId || ''}` + ) + ] + if (supplementaryId) { + promises.push( + axios.get(ROUTES_COMPLIANCE.REASSESSMENT_CREDIT_ACTIVITY.replace(':supp_id', supplementaryId)) + ) + } else { + promises.push( + axios.get(ROUTES_COMPLIANCE.PREVIOUS_REASSESSMENT_CREDIT_ACTIVITY.replace(':id', id)) + ) + } axios - .all([ - axios.get( - `${ROUTES_SUPPLEMENTARY.DETAILS.replace(':id', id)}?supplemental_id=${ - supplementaryId || '' - }` - ), - axios.get( - `${ROUTES_SUPPLEMENTARY.ASSESSED_SUPPLEMENTALS.replace(':id', id)}` - ), - axios.get( - `${ROUTES_COMPLIANCE.REPORT_COMPLIANCE_DETAILS_BY_ID.replace(':id', id)}?most_recent_ldv_sales=true&use_from_gov_snapshot=True` - ), - axios.get(ROUTES_COMPLIANCE.RATIOS), - axios.get( - `${ROUTES_SUPPLEMENTARY.ASSESSMENT.replace( - ':id', - id - )}?supplemental_id=${supplementaryId || ''}` - ) - ]) + .all(promises) .then( axios.spread( ( @@ -604,7 +626,8 @@ const SupplementaryContainer = (props) => { assessedSupplementals, complianceResponse, ratioResponse, - assessmentResponse + assessmentResponse, + previousReassessmentCreditActivityResponse ) => { if (response.data) { let assessedSupplementalsData = assessedSupplementals.data @@ -635,6 +658,7 @@ const SupplementaryContainer = (props) => { } } } + const previousReassessmentCreditActivityResponseData = previousReassessmentCreditActivityResponse.data const { reconciledAssessmentData, reconciledLdvSales, @@ -642,7 +666,8 @@ const SupplementaryContainer = (props) => { } = reconcileSupplementaries( response.data.assessmentData, assessedSupplementalsData, - complianceResponse.data + complianceResponse.data, + previousReassessmentCreditActivityResponseData ) if (reconciledAssessmentData) { response.data.assessmentData = reconciledAssessmentData @@ -803,6 +828,7 @@ const SupplementaryContainer = (props) => { if (reconciledComplianceObligation) { setObligationDetails(reconciledComplianceObligation) + setPreviousReassessmentCreditActivities(previousReassessmentCreditActivityResponseData) } if (reconciledLdvSales) { diff --git a/openshift/templates/backend/backend-bc.yaml b/openshift/templates/backend/backend-bc.yaml deleted file mode 100644 index 280c4f925..000000000 --- a/openshift/templates/backend/backend-bc.yaml +++ /dev/null @@ -1,84 +0,0 @@ -apiVersion: template.openshift.io/v1 -kind: Template -metadata: - creationTimestamp: null - name: zeva-backend-bc -parameters: - - name: NAME - displayName: - description: the module name entered when run yo bcdk:pipeline, which is zeva - required: true - - name: SUFFIX - displayName: - description: sample is -pr-0 - required: true - - name: VERSION - displayName: - description: image tag name for output - required: true - - name: GIT_URL - displayName: - description: zeva repo - required: true - - name: GIT_REF - displayName: - description: zeva branch name of the pr - required: true -objects: - - apiVersion: build.openshift.io/v1 - kind: BuildConfig - metadata: - annotations: - description: Defines how to build the application - creationTimestamp: null - name: ${NAME}-backend${SUFFIX} - labels: - shared: "true" - spec: - nodeSelector: null - output: - to: - kind: ImageStreamTag - name: ${NAME}-backend:${VERSION} - postCommit: {} - resources: - limits: - cpu: 1000m - memory: 2Gi - requests: - cpu: 500m - memory: 1Gi - runPolicy: SerialLatestOnly - source: - contextDir: backend - git: - ref: ${GIT_REF} - uri: ${GIT_URL} - type: Git - strategy: - sourceStrategy: - env: - - name: ARTIFACTORY_USER - valueFrom: - secretKeyRef: - name: artifacts-zeva-artifactory-service-account-xgchey - key: username - - name: ARTIFACTORY_PASSWORD - valueFrom: - secretKeyRef: - name: artifacts-zeva-artifactory-service-account-xgchey - key: password - from: - kind: ImageStreamTag - name: python-39:1-74 - pullSecret: - name: zeva-image-pull-secret - forcePull: true - noCache: true - type: Source - triggers: - - imageChange: {} - type: ImageChange - - type: ConfigChange - status: - lastVersion: 0 \ No newline at end of file diff --git a/openshift/templates/knp/1-base.yaml b/openshift/templates/knp/1-base.yaml deleted file mode 100644 index 804b11a91..000000000 --- a/openshift/templates/knp/1-base.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -apiVersion: template.openshift.io/v1 -kind: Template -labels: - template: zeva-network-policy -metadata: - name: zeva-network-policy -parameters: - - name: ENVIRONMENT - displayName: null - description: such as dev, test or prod - required: true -objects: - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-from-openshift-ingress - spec: - # This policy allows any pod with a route & service combination - # to accept traffic from the OpenShift router pods. This is - # required for things outside of OpenShift (like the Internet) - # to reach your pods. - ingress: - - from: - - namespaceSelector: - matchLabels: - network.openshift.io/policy-group: ingress - podSelector: {} - policyTypes: - - Ingress diff --git a/openshift/templates/knp/2-apps.yaml b/openshift/templates/knp/2-apps.yaml deleted file mode 100644 index 890ba059f..000000000 --- a/openshift/templates/knp/2-apps.yaml +++ /dev/null @@ -1,59 +0,0 @@ ---- -apiVersion: template.openshift.io/v1 -kind: Template -labels: - template: zeva-network-policy -metadata: - name: zeva-network-policy -parameters: - - name: SUFFIX - displayName: null - description: such as -dev or -dev-1513 - required: true - - name: ENVIRONMENT - displayName: null - description: such as dev, test or prod - required: true -objects: - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-backend-accepts-${ENVIRONMENT} - spec: - ## Allow backend to accept communication from frontend - ## Allow backend to accept communication from schemaspy - podSelector: - matchLabels: - app.kubernetes.io/instance: zeva-backend${SUFFIX} - ingress: - - from: - - podSelector: - matchLabels: - app.kubernetes.io/instance: zeva-frontend${SUFFIX} - ports: - - protocol: TCP - port: 8080 - - from: - - podSelector: - matchLabels: - name: schemaspy-public-${ENVIRONMENT} - ports: - - protocol: TCP - port: 8080 - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-minio-accepts-backend - spec: - ## Allow minio to accept communication from backend - podSelector: - matchLabels: - app: zeva-minio-${ENVIRONMENT} - ingress: - - from: - - podSelector: - matchLabels: - app.kubernetes.io/instance: zeva-backend${SUFFIX} - ports: - - protocol: TCP - port: 9000 diff --git a/openshift/templates/knp/3-spilo.yaml b/openshift/templates/knp/3-spilo.yaml deleted file mode 100644 index 5c6910915..000000000 --- a/openshift/templates/knp/3-spilo.yaml +++ /dev/null @@ -1,86 +0,0 @@ ---- -apiVersion: template.openshift.io/v1 -kind: Template -labels: - template: zeva-spilo-network-policy -metadata: - name: zeva-spilo-network-policy -parameters: - - name: SUFFIX - displayName: null - description: such as -dev or -dev-1513 - required: true - - name: ENVIRONMENT - displayName: null - description: such as dev, test or prod - required: true -objects: - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-zeva-spilo-accepts-${ENVIRONMENT} - spec: - ## Allow zeva-spilo to accept communications from backend - ## Allow zeva-spilo to accept communications from backend-mid - ## Allow zeva-spilo to accept communications from schema-public - ## Allow zeva-spilo to accept communications from schema-audit - ## Allow zeva-spilo to accept communications from backup-container - ## Allow zeva-spilo to accept communications from backup cronjob - ## Allow zeva-spilo to accept communications from metabase from cthub - podSelector: - matchLabels: - app.kubernetes.io/instance: zeva-spilo${SUFFIX} - ingress: - - from: - - podSelector: - matchLabels: - app.kubernetes.io/instance: zeva-backend${SUFFIX} - ports: - - protocol: TCP - port: 5432 - - from: - - podSelector: - matchLabels: - openshift.io/deployer-pod.type: hook-mid - ports: - - protocol: TCP - port: 5432 - - from: - - podSelector: - matchLabels: - name: zeva-schema-spy-public-${ENVIRONMENT} - ports: - - protocol: TCP - port: 5432 - - from: - - podSelector: - matchLabels: - name: zeva-schema-spy-audit-${ENVIRONMENT} - ports: - - protocol: TCP - port: 5432 - - from: - - podSelector: - matchLabels: - name: patroni-backup - ports: - - protocol: TCP - port: 5432 - - from: - - podSelector: - matchLabels: - cronjob: zeva-db-backup - ports: - - protocol: TCP - port: 5432 - - from: - - namespaceSelector: - matchLabels: - name: 30b186 - environment: ${ENVIRONMENT} - - podSelector: - matchLabels: - app: metabase - ports: - - protocol: TCP - port: 5432 diff --git a/openshift/templates/knp/README.md b/openshift/templates/knp/README.md deleted file mode 100644 index dd97e45ef..000000000 --- a/openshift/templates/knp/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Quick Start allow all network policies - -## knp-quick-start.yaml -It has the following three policies, these policies will open all communications. Typically they can be applied at the early stage of the project during the development phase. -* deny-by-default -* allow-from-openshift-ingress -* allow-all-internal - -# Application Network Security Policies -Applying the following policies allows Zeva applications communicated properly under security rules. -* 1-base.yaml -* 2-apps.yaml -* 3-spilo.yaml diff --git a/openshift/templates/knp/Zeva-Git-Model.drawio b/openshift/templates/knp/Zeva-Git-Model.drawio deleted file mode 100644 index c3717357e..000000000 --- a/openshift/templates/knp/Zeva-Git-Model.drawio +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/openshift/templates/knp/knp-diagram.drawio b/openshift/templates/knp/knp-diagram.drawio deleted file mode 100644 index 9b032b9b6..000000000 --- a/openshift/templates/knp/knp-diagram.drawio +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openshift/templates/knp/knp-quick-start.yaml b/openshift/templates/knp/knp-quick-start.yaml deleted file mode 100644 index e7a97a6b3..000000000 --- a/openshift/templates/knp/knp-quick-start.yaml +++ /dev/null @@ -1,60 +0,0 @@ ---- -apiVersion: template.openshift.io/v1 -kind: Template -labels: - template: zeva-network-policy -metadata: - name: zeva-network-policy -parameters: - - name: ENVIRONMENT - displayName: null - description: such as dev, test or prod - required: true - - name: NAMESPACE_PREFIX - displayName: null - description: the namespace prefix - required: true -objects: - - kind: NetworkPolicy - apiVersion: networking.k8s.io/v1 - metadata: - name: deny-by-default - spec: - # The default posture for a security first namespace is to - # deny all traffic. If not added this rule will be added - # by Platform Services during environment cut-over. - podSelector: {} - ingress: [] - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-from-openshift-ingress - spec: - # This policy allows any pod with a route & service combination - # to accept traffic from the OpenShift router pods. This is - # required for things outside of OpenShift (like the Internet) - # to reach your pods. - ingress: - - from: - - namespaceSelector: - matchLabels: - network.openshift.io/policy-group: ingress - podSelector: {} - policyTypes: - - Ingress - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-all-internal - spec: - # Allow all pods within the current namespace to communicate - # to one another. - ingress: - - from: - - namespaceSelector: - matchLabels: - environment: ${ENVIRONMENT} - name: ${NAMESPACE_PREFIX} - podSelector: {} - policyTypes: - - Ingress \ No newline at end of file