diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml index 0ef2dbd62..390317eb9 100644 --- a/.github/workflows/dev-build.yaml +++ b/.github/workflows/dev-build.yaml @@ -1,11 +1,11 @@ ## For each release, please update the value of workflow name, branches and PR_NUMBER ## Also update frontend/package.json version -name: Dev Build 1.53.0 +name: Dev Build 1.54.0 on: push: - branches: [ release-1.53.0 ] + branches: [ release-1.54.0 ] paths: - frontend/** - backend/** @@ -14,8 +14,8 @@ on: env: ## The pull request number of the Tracking pull request to merge the release branch to main - PR_NUMBER: 1976 - VERSION: 1.53.0 + PR_NUMBER: 2027 + VERSION: 1.54.0 GIT_URL: https://github.com/bcgov/zeva.git TOOLS_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-tools DEV_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-dev @@ -32,7 +32,7 @@ jobs: call-unit-test: uses: ./.github/workflows/unit-test-template.yaml with: - pr-number: 1976 + pr-number: 2027 build: diff --git a/.github/workflows/release-build.yaml b/.github/workflows/release-build.yaml index 11c8e8362..72d823da8 100644 --- a/.github/workflows/release-build.yaml +++ b/.github/workflows/release-build.yaml @@ -1,7 +1,7 @@ ## For each release, please update the value of workflow name, branches and PR_NUMBER ## Also update frontend/package.json version -name: Release Build 1.53.0 +name: Release Build 1.54.0 on: workflow_dispatch: @@ -9,8 +9,8 @@ on: env: ## The pull request number of the Tracking pull request to merge the release branch to main - PR_NUMBER: 1976 - VERSION: 1.53.0 + PR_NUMBER: 2027 + VERSION: 1.54.0 GIT_URL: https://github.com/bcgov/zeva.git TOOLS_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-tools DEV_NAMESPACE: ${{ secrets.OPENSHIFT_NAMESPACE_PLATE }}-dev @@ -27,7 +27,7 @@ jobs: call-unit-test: uses: ./.github/workflows/unit-test-template.yaml with: - pr-number: 1976 + pr-number: 2027 build: diff --git a/backend/api/migrations/0004_auto_20231024_0908.py b/backend/api/migrations/0004_auto_20231024_0908.py new file mode 100644 index 000000000..c99daf71a --- /dev/null +++ b/backend/api/migrations/0004_auto_20231024_0908.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.20 on 2023-10-24 16:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_auto_20230918_1445'), + ] + + operations = [ + migrations.AddField( + model_name='modelyearreportassessment', + name='display', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='modelyearreportassessmentcomment', + name='display', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='modelyearreportldvsales', + name='display', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='modelyearreportmake', + name='display', + field=models.BooleanField(default=True), + ) + ] diff --git a/backend/api/models/model_year_report.py b/backend/api/models/model_year_report.py index 62627c370..ec14ab8ac 100644 --- a/backend/api/models/model_year_report.py +++ b/backend/api/models/model_year_report.py @@ -213,7 +213,8 @@ def get_credit_reductions(self): model_year_report_id=self.id, category__in=[ 'ClassAReduction', 'UnspecifiedClassCreditReduction', - 'CreditDeficit', 'ProvisionalBalanceAfterCreditReduction' + 'CreditDeficit', 'ProvisionalBalanceAfterCreditReduction', + 'ReductionsToOffsetDeficit' ] ) diff --git a/backend/api/models/model_year_report_assessment.py b/backend/api/models/model_year_report_assessment.py index 15ffd85d7..a4ce3e4dc 100644 --- a/backend/api/models/model_year_report_assessment.py +++ b/backend/api/models/model_year_report_assessment.py @@ -29,6 +29,10 @@ class ModelYearReportAssessment(Auditable): decimal_places=2, db_comment='amount of administrative penalty' ) + display = models.BooleanField( + default=True, + db_comment="field to determine if we should display this info" + ) class Meta: db_table = 'model_year_report_assessment' diff --git a/backend/api/models/model_year_report_assessment_comment.py b/backend/api/models/model_year_report_assessment_comment.py index e748040f7..9cf34e11a 100644 --- a/backend/api/models/model_year_report_assessment_comment.py +++ b/backend/api/models/model_year_report_assessment_comment.py @@ -27,6 +27,10 @@ class ModelYearReportAssessmentComment(Auditable): db_column='assessment_comment', db_comment="Comment left by idir about model year report" ) + display = models.BooleanField( + default=True, + db_comment="field to determine if we should display this info" + ) class Meta: db_table = 'model_year_report_assessment_comment' diff --git a/backend/api/models/model_year_report_ldv_sales.py b/backend/api/models/model_year_report_ldv_sales.py index ba372500d..59cf0e9bc 100644 --- a/backend/api/models/model_year_report_ldv_sales.py +++ b/backend/api/models/model_year_report_ldv_sales.py @@ -31,6 +31,10 @@ class ModelYearReportLDVSales(Auditable): default=False, db_comment="Flag. True if this edit came from a government user." ) + display = models.BooleanField( + default=True, + db_comment="field to determine if we should display this info" + ) class Meta: db_table = "model_year_report_ldv_sales" diff --git a/backend/api/models/model_year_report_make.py b/backend/api/models/model_year_report_make.py index 3a0092de1..891f5cacb 100644 --- a/backend/api/models/model_year_report_make.py +++ b/backend/api/models/model_year_report_make.py @@ -31,6 +31,10 @@ class ModelYearReportMake(Auditable): default=False, db_comment="Flag. True if this edit came from a government user." ) + display = models.BooleanField( + default=True, + db_comment="field to determine if we should display this info" + ) class Meta: db_table = 'model_year_report_make' diff --git a/backend/api/serializers/credit_agreement_comment.py b/backend/api/serializers/credit_agreement_comment.py index 874598314..2087338c3 100644 --- a/backend/api/serializers/credit_agreement_comment.py +++ b/backend/api/serializers/credit_agreement_comment.py @@ -22,7 +22,7 @@ def get_create_user(self, obj): class Meta: model = CreditAgreementComment fields = ( - 'id', 'comment', 'create_timestamp', 'create_user', 'to_director' + 'id', 'comment', 'create_timestamp', 'update_timestamp', 'create_user', 'to_director' ) read_only_fields = ( 'id', diff --git a/backend/api/serializers/credit_transfer_comment.py b/backend/api/serializers/credit_transfer_comment.py index 3df3648ba..55a3b6f01 100644 --- a/backend/api/serializers/credit_transfer_comment.py +++ b/backend/api/serializers/credit_transfer_comment.py @@ -25,7 +25,7 @@ def get_create_user(self, obj): class Meta: model = CreditTransferComment fields = ( - 'id', 'comment', 'create_timestamp', 'create_user', + 'id', 'comment', 'create_timestamp', 'update_timestamp', 'create_user', ) read_only_fields = ( 'id', diff --git a/backend/api/serializers/model_year_report_assessment.py b/backend/api/serializers/model_year_report_assessment.py index 00f3c7de9..a6c3e7b22 100644 --- a/backend/api/serializers/model_year_report_assessment.py +++ b/backend/api/serializers/model_year_report_assessment.py @@ -145,8 +145,8 @@ def get_assessment(self, obj): def get_assessment_comment(self, obj): request = self.context.get('request') assessment_comment = ModelYearReportAssessmentComment.objects.filter( - model_year_report=obj - + model_year_report=obj, + display=True ).order_by('-create_timestamp') if not request.user.is_government: assessment_comment = ModelYearReportAssessmentComment.objects.filter( diff --git a/backend/api/serializers/model_year_report_ldv_sales.py b/backend/api/serializers/model_year_report_ldv_sales.py index 06adddf90..dbe0208ce 100644 --- a/backend/api/serializers/model_year_report_ldv_sales.py +++ b/backend/api/serializers/model_year_report_ldv_sales.py @@ -16,5 +16,5 @@ class ModelYearReportLDVSalesSerializer(ModelSerializer): class Meta: model = ModelYearReportLDVSales fields = ( - 'id', 'ldv_sales', 'model_year', + 'id', 'ldv_sales', 'model_year' ) diff --git a/backend/api/serializers/vehicle.py b/backend/api/serializers/vehicle.py index 3da2ead65..cae6b553f 100644 --- a/backend/api/serializers/vehicle.py +++ b/backend/api/serializers/vehicle.py @@ -1,4 +1,5 @@ from django.core.exceptions import PermissionDenied +from django.db.models import Q from enumfields.drf import EnumField, EnumSupportSerializerMixin from rest_framework.serializers import ModelSerializer, \ SerializerMethodField, SlugRelatedField, ValidationError @@ -475,11 +476,10 @@ def get_pending_sales(self, instance): return SalesSubmissionContent.objects.filter( xls_make__iexact=instance.make, xls_model__iexact=instance.model_name, - xls_model_year=str(instance.model_year.name) + '.0', submission__validation_status__in=[ "SUBMITTED", "RECOMMEND_APPROVAL", "RECOMMEND_REJECTION", "CHECKED", ] - ).count() + ).filter(Q(xls_model_year=str(instance.model_year.name) + '.0') | Q(xls_model_year=str(instance.model_year.name))).count() def get_sales_issued(self, instance): return RecordOfSale.objects.filter( diff --git a/backend/api/services/credit_agreement_comment.py b/backend/api/services/credit_agreement_comment.py new file mode 100644 index 000000000..d2962d1e8 --- /dev/null +++ b/backend/api/services/credit_agreement_comment.py @@ -0,0 +1,9 @@ +from api.models.credit_agreement_comment import CreditAgreementComment + + +def get_comment(comment_id): + return CreditAgreementComment.objects.filter(id=comment_id).first() + + +def delete_comment(comment_id): + CreditAgreementComment.objects.filter(id=comment_id).delete() diff --git a/backend/api/services/credit_transfer_comment.py b/backend/api/services/credit_transfer_comment.py new file mode 100644 index 000000000..9168d8f90 --- /dev/null +++ b/backend/api/services/credit_transfer_comment.py @@ -0,0 +1,9 @@ +from api.models.credit_transfer_comment import CreditTransferComment + + +def get_comment(comment_id): + return CreditTransferComment.objects.filter(id=comment_id).first() + + +def delete_comment(comment_id): + CreditTransferComment.objects.filter(id=comment_id).delete() diff --git a/backend/api/services/model_year_report.py b/backend/api/services/model_year_report.py index 037393f0e..79f47732c 100644 --- a/backend/api/services/model_year_report.py +++ b/backend/api/services/model_year_report.py @@ -326,7 +326,7 @@ def adjust_credits(id, request): # overrides reductions = ModelYearReportComplianceObligation.objects.filter( model_year_report_id=id, - category__in=['ClassAReduction', 'UnspecifiedClassCreditReduction'], + category__in=['ClassAReduction', 'UnspecifiedClassCreditReduction', 'ReductionsToOffsetDeficit'], from_gov=from_gov, ).order_by('model_year__name', 'update_timestamp') diff --git a/backend/api/services/sales_spreadsheet.py b/backend/api/services/sales_spreadsheet.py index bdc96d0e3..e055cc52a 100644 --- a/backend/api/services/sales_spreadsheet.py +++ b/backend/api/services/sales_spreadsheet.py @@ -23,6 +23,8 @@ from api.models.vehicle import Vehicle from api.models.icbc_snapshot_data import IcbcSnapshotData from api.models.record_of_sale_statuses import RecordOfSaleStatuses +from api.models.vehicle_statuses import VehicleDefinitionStatuses +from api.services.sales_submission import get_vehicle_structures, get_map_of_sales_submission_content_ids_to_vehicles, get_map_of_vins_to_records_of_sales logger = logging.getLogger('zeva.sales_spreadsheet') @@ -374,7 +376,7 @@ def validate_spreadsheet(data, user_organization=None, skip_authorization=False) def get_error(content): - warnings = content.warnings + warnings = content.warnings_list.split(",") if content.warnings_list is not None else [] error = '' if 'ROW_NOT_SELECTED' in warnings and content.reason: error += content.reason @@ -407,6 +409,9 @@ def get_error(content): if 'ROW_NOT_SELECTED' in warnings and error == '': error += 'VIN not registered in BC; ' + + if 'WRONG_MODEL_YEAR' in warnings: + error += 'Wrong model year to be issued with compliance report; ' return error @@ -503,7 +508,7 @@ def create_errors_spreadsheet(submission_id, organization_id, stream): current_vehicle_col_width = 13 for content in submission_content: - if len(content.warnings) == 0: + if content.warnings_list is None: next() row += 1 @@ -593,11 +598,17 @@ def create_details_spreadsheet(submission_id, stream): current_vehicle_col_width = 13 icbc_match = False + vehicle_structures = get_vehicle_structures(sales_submission, VehicleDefinitionStatuses.VALIDATED) + map_of_sales_submission_content_ids_to_vehicles = get_map_of_sales_submission_content_ids_to_vehicles(submission_content, vehicle_structures) + map_of_vins_to_records_of_sales = get_map_of_vins_to_records_of_sales(submission_content) for content in submission_content: validated = 'No' - if content.record_of_sale: - if content.record_of_sale.validation_status == RecordOfSaleStatuses.VALIDATED: - validated = 'Yes' + records_of_sales = map_of_vins_to_records_of_sales.get(content.xls_vin) + if records_of_sales is not None: + for record_of_sale in records_of_sales: + if record_of_sale.validation_status == RecordOfSaleStatuses.VALIDATED and record_of_sale.submission == sales_submission and record_of_sale.vehicle == map_of_sales_submission_content_ids_to_vehicles.get(content.id): + validated = 'Yes' + break try: icbc_record = icbc_data.get(vin=content.xls_vin) icbc_match = True diff --git a/backend/api/services/vehicle.py b/backend/api/services/vehicle.py index c0ee804b0..bb1406730 100644 --- a/backend/api/services/vehicle.py +++ b/backend/api/services/vehicle.py @@ -49,13 +49,13 @@ def vehicles_sales(model_year, organization): ).filter( Q(Q( Q(xls_sale_date__lte=sales_to_date) & - Q(xls_model_year=str(report_year) + ".0") & + (Q(xls_model_year=str(report_year) + ".0") | Q(xls_model_year=str(report_year))) & Q(xls_date_type="3") & ~Q(xls_sale_date="") ) | Q( Q(xls_sale_date__lte=to_date_str) & - Q(xls_model_year=str(report_year) + ".0") & + (Q(xls_model_year=str(report_year) + ".0") | Q(xls_model_year=str(report_year))) & Q(xls_date_type="1") & ~Q(xls_sale_date="") ) diff --git a/backend/api/utilities/comment.py b/backend/api/utilities/comment.py new file mode 100644 index 000000000..a314de187 --- /dev/null +++ b/backend/api/utilities/comment.py @@ -0,0 +1,4 @@ +def update_comment_text(comment, comment_text): + comment.comment = comment_text + comment.save(update_fields=["comment", "update_timestamp"]) + return comment diff --git a/backend/api/viewsets/credit_agreement.py b/backend/api/viewsets/credit_agreement.py index 4eb85a0fd..ecbca464a 100644 --- a/backend/api/viewsets/credit_agreement.py +++ b/backend/api/viewsets/credit_agreement.py @@ -6,6 +6,7 @@ from rest_framework import mixins, viewsets, permissions from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework import status from api.models.credit_agreement import CreditAgreement from api.models.credit_agreement_transaction_types import \ @@ -23,6 +24,9 @@ from api.services.send_email import notifications_credit_agreement from api.models.model_year_report import ModelYearReport from api.serializers.model_year_report import ModelYearReportSerializer, ModelYearReportsSerializer +from api.serializers.credit_agreement_comment import CreditAgreementCommentSerializer +from api.services.credit_agreement_comment import get_comment, delete_comment +from api.utilities.comment import update_comment_text class CreditAgreementViewSet( @@ -134,3 +138,25 @@ def list(self, request): serializer = self.get_serializer(credit_agreements, many=True) return Response(serializer.data) + + @action(detail=True, methods=["PATCH"]) + def update_comment(self, request, pk): + comment_id = request.data.get("comment_id") + comment_text = request.data.get("comment_text") + username = request.user.username + comment = get_comment(comment_id) + if username == comment.create_user: + updated_comment = update_comment_text(comment, comment_text) + serializer = CreditAgreementCommentSerializer(updated_comment) + return Response(serializer.data) + return Response(status=status.HTTP_403_FORBIDDEN) + + @action(detail=True, methods=["PATCH"]) + def delete_comment(self, request, pk): + comment_id = request.data.get("comment_id") + username = request.user.username + comment = get_comment(comment_id) + if username == comment.create_user: + delete_comment(comment_id) + return Response(status=status.HTTP_200_OK) + return Response(status=status.HTTP_403_FORBIDDEN) diff --git a/backend/api/viewsets/credit_transfer.py b/backend/api/viewsets/credit_transfer.py index 2c1602341..474fe3b1e 100644 --- a/backend/api/viewsets/credit_transfer.py +++ b/backend/api/viewsets/credit_transfer.py @@ -1,6 +1,7 @@ import logging from rest_framework import mixins, status, viewsets from rest_framework.response import Response +from rest_framework.decorators import action from django.db.models import Q from api.models.credit_transfer import CreditTransfer @@ -8,9 +9,12 @@ from api.permissions.credit_transfer import CreditTransferPermissions from api.serializers.credit_transfer import CreditTransferSerializer, \ CreditTransferSaveSerializer, CreditTransferListSerializer +from api.serializers.credit_transfer_comment import CreditTransferCommentSerializer from auditable.views import AuditableMixin from api.services.send_email import notifications_credit_transfers from api.services.credit_transaction import validate_transfer +from api.services.credit_transfer_comment import get_comment, delete_comment +from api.utilities.comment import update_comment_text LOGGER = logging.getLogger(__name__) @@ -99,3 +103,25 @@ def perform_update(self, serializer, *args, **kwargs): elif old_transfer_status == CreditTransferStatuses.APPROVED and new_transfer_status == CreditTransferStatuses.APPROVED: transfer_saved_by_analyst = True notifications_credit_transfers(transfer, transfer_sent_back_to_analyst, transfer_saved_by_analyst) + + @action(detail=True, methods=["PATCH"]) + def update_comment(self, request, pk): + comment_id = request.data.get("comment_id") + comment_text = request.data.get("comment_text") + username = request.user.username + comment = get_comment(comment_id) + if username == comment.create_user: + updated_comment = update_comment_text(comment, comment_text) + serializer = CreditTransferCommentSerializer(updated_comment) + return Response(serializer.data) + return Response(status=status.HTTP_403_FORBIDDEN) + + @action(detail=True, methods=["PATCH"]) + def delete_comment(self, request, pk): + comment_id = request.data.get("comment_id") + username = request.user.username + comment = get_comment(comment_id) + if username == comment.create_user: + delete_comment(comment_id) + return Response(status=status.HTTP_200_OK) + return Response(status=status.HTTP_403_FORBIDDEN) diff --git a/backend/api/viewsets/model_year_report.py b/backend/api/viewsets/model_year_report.py index a6171a11c..238532612 100644 --- a/backend/api/viewsets/model_year_report.py +++ b/backend/api/viewsets/model_year_report.py @@ -145,7 +145,7 @@ def retrieve(self, request, pk=None): makes_list = ( ModelYearReportMake.objects.filter( - model_year_report_id=report.id, from_gov=False + model_year_report_id=report.id, from_gov=False, display=True ) .values("make") .distinct() @@ -180,7 +180,7 @@ def retrieve(self, request, pk=None): if not avg_sales: report_ldv_sales = ( ModelYearReportLDVSales.objects.filter( - model_year_report_id=pk, model_year__name=model_year_int + model_year_report_id=pk, model_year__name=model_year_int, display=True ) .order_by("-update_timestamp") .first() @@ -477,6 +477,31 @@ def submission(self, request): if validation_status == "ASSESSED": adjust_credits(model_year_report_id, request) + + if validation_status == "DRAFT": + ModelYearReportLDVSales.objects.filter( + model_year_report_id=model_year_report_id, + from_gov=True + ).update( + display=False + ) + ModelYearReportMake.objects.filter( + model_year_report_id=model_year_report_id, + from_gov=True + ).update( + display=False + ) + ModelYearReportAssessment.objects.filter( + model_year_report_id=model_year_report_id + ).update( + display=False + ) + ModelYearReportAssessmentComment.objects.filter( + model_year_report_id=model_year_report_id, + to_director=True + ).update( + display=False + ) if confirmations: for confirmation in confirmations: diff --git a/backend/api/viewsets/vehicle.py b/backend/api/viewsets/vehicle.py index 891ac6a44..633c3590f 100644 --- a/backend/api/viewsets/vehicle.py +++ b/backend/api/viewsets/vehicle.py @@ -47,7 +47,7 @@ def get_serializer_class(self): def get_queryset(self): request = self.request - + queryset = Vehicle.objects.filter( organization_id=request.user.organization.id ).exclude(validation_status=VehicleDefinitionStatuses.DELETED) @@ -58,7 +58,8 @@ def get_queryset(self): VehicleDefinitionStatuses.SUBMITTED, VehicleDefinitionStatuses.VALIDATED, VehicleDefinitionStatuses.REJECTED, - VehicleDefinitionStatuses.CHANGES_REQUESTED + VehicleDefinitionStatuses.CHANGES_REQUESTED, + VehicleDefinitionStatuses.DELETED ] ) diff --git a/developer-guide.md b/developer-guide.md index de397ccc1..3f941f814 100644 --- a/developer-guide.md +++ b/developer-guide.md @@ -87,10 +87,10 @@ docker-compose exec db psql -U postgres zeva ### To insert your first idir user INSERT INTO user_profile ( - create_timestamp, update_timestamp, username, first_name, last_name, is_active, keycloak_email, display_name, organization_id, create_user + create_timestamp, update_timestamp, username, first_name, last_name, is_active, keycloak_email, display_name, organization_id, create_user) VALUES ( - NOW(), NOW(), 'idirusername', 'Firstname', 'Lastname', TRUE, - 'idir.email@gov.bc.ca', 'displayname', 1, 'SYSTEM' ); + NOW(), NOW(), 'idirusername', 'Firstname', 'Lastname', TRUE, + 'idir.email@gov.bc.ca', 'displayname', 1, 'SYSTEM'); #### Copy down Test/Prod data from Openshift @@ -105,7 +105,7 @@ There are # 2 Arguments # example command -- . import-data.sh test 398cd4661173 +- . import-data.sh dev 398cd4661173 You will need to be logged into Openshift using oc You will also need your ZEVA postgres docker container running @@ -113,3 +113,16 @@ You will also need your ZEVA postgres docker container running if theres permission issues with lchown while running the script, run the script step by step (comment out after line 57, double check that it copied the tar file into it, then comment out the earlier steps and run it again with just the later lines) if there's still issues, it might be a corrupted .tar file. See if someone else can export it and put it in the openshift folder. Then comment out the import script up until the tar gets copied into a local container. Then try running it. + +Another issue that has come up was fixed by removing a lock in the database. +Locks were removed by using this statement: +SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE pid <> pg_backend_pid(); +this may be enough to run the script but also the public schema can be deleted and recreated +DROP SCHEMA public cascade; + +CREATE SCHEMA public AUTHORIZATION postgres; + +then the script can be run. + diff --git a/frontend/package.json b/frontend/package.json index 1bfefaad7..3d51f9ec4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "zeva-frontend", - "version": "1.53.0", + "version": "1.54.0", "private": true, "dependencies": { "@babel/eslint-parser": "^7.19.1", diff --git a/frontend/src/app/routes/CreditAgreements.js b/frontend/src/app/routes/CreditAgreements.js index cc433ee45..2dfc64766 100644 --- a/frontend/src/app/routes/CreditAgreements.js +++ b/frontend/src/app/routes/CreditAgreements.js @@ -8,7 +8,9 @@ const CREDIT_AGREEMENTS = { MINIO_URL: `${API_BASE_PATH}/:id/minio_url`, TRANSACTION_TYPES: `${API_BASE_PATH}/transaction_types`, MODEL_YEAR_REPORTS: `${API_BASE_PATH}/model_year_reports`, - COMMENT_SAVE: `${API_BASE_PATH}/:id/comment_save` + COMMENT_SAVE: `${API_BASE_PATH}/:id/comment_save`, + UPDATE_COMMENT: `${API_BASE_PATH}/:id/update_comment`, + DELETE_COMMENT: `${API_BASE_PATH}/:id/delete_comment`, } export default CREDIT_AGREEMENTS diff --git a/frontend/src/app/routes/CreditTransfers.js b/frontend/src/app/routes/CreditTransfers.js index 81abf1214..22f3ab11f 100644 --- a/frontend/src/app/routes/CreditTransfers.js +++ b/frontend/src/app/routes/CreditTransfers.js @@ -4,7 +4,9 @@ const CREDIT_REQUESTS = { NEW: `${API_BASE_PATH}/new`, LIST: API_BASE_PATH, DETAILS: `${API_BASE_PATH}/:id`, - EDIT: `${API_BASE_PATH}/:id/edit` + EDIT: `${API_BASE_PATH}/:id/edit`, + UPDATE_COMMENT: `${API_BASE_PATH}/:id/update_comment`, + DELETE_COMMENT: `${API_BASE_PATH}/:id/delete_comment`, } export default CREDIT_REQUESTS diff --git a/frontend/src/app/utilities/__tests__/calculateCreditReduction.test.js b/frontend/src/app/utilities/__tests__/calculateCreditReduction.test.js index 672b679a7..cf782abb1 100644 --- a/frontend/src/app/utilities/__tests__/calculateCreditReduction.test.js +++ b/frontend/src/app/utilities/__tests__/calculateCreditReduction.test.js @@ -138,7 +138,7 @@ describe('calculateCreditReduction', () => { const radioId = 'A' - const result = calculateCreditReduction(balances, classAReductions, unspecifiedReductions, radioId) + const result = calculateCreditReduction(balances, classAReductions, unspecifiedReductions, radioId, {}) result.balances.forEach((balance) => { expect(balance.creditA).toBeCloseTo(balance.creditA, 2) diff --git a/frontend/src/app/utilities/calculateCreditReduction.js b/frontend/src/app/utilities/calculateCreditReduction.js index 3fc14468a..134dfc380 100644 --- a/frontend/src/app/utilities/calculateCreditReduction.js +++ b/frontend/src/app/utilities/calculateCreditReduction.js @@ -69,7 +69,8 @@ const calculateCreditReduction = ( argBalances, classAReductions, unspecifiedReductions, - radioId + radioId, + carryOverDeficits ) => { let balances = argBalances.map((each) => ({ ...each })) // clone the balances array @@ -229,6 +230,16 @@ const calculateCreditReduction = ( }) } + for (const [modelYear, deficit] of Object.entries(carryOverDeficits)) { + if (deficit.A > 0 || deficit.unspecified > 0) { + deficits.push({ + modelYear, + creditA: deficit.A, + creditB: deficit.unspecified + }) + } + } + return { balances: updatedBalances, deductions, diff --git a/frontend/src/app/utilities/getComplianceObligationDetails.js b/frontend/src/app/utilities/getComplianceObligationDetails.js index 9ad141421..f3ecbf85e 100644 --- a/frontend/src/app/utilities/getComplianceObligationDetails.js +++ b/frontend/src/app/utilities/getComplianceObligationDetails.js @@ -1,7 +1,9 @@ -const getComplianceObligationDetails = (complianceResponseDetails) => { +import getNewProvisionalBalance from "./getNewProvisionalBalance" + +const getComplianceObligationDetails = (complianceResponseDetails, creditOffsetSelection) => { const creditBalanceStart = {} const creditBalanceEnd = {} - const provisionalBalance = {} + const provisionalProvisionalBalance = {} const pendingBalance = [] const transfersIn = [] const transfersOut = [] @@ -11,7 +13,7 @@ const getComplianceObligationDetails = (complianceResponseDetails) => { const administrativeAllocation = [] const administrativeReduction = [] const automaticAdministrativePenalty = [] - const deficits = [] + const deficitCollection = {} let pendingBalanceExist = false complianceResponseDetails.forEach((item) => { @@ -23,14 +25,23 @@ const getComplianceObligationDetails = (complianceResponseDetails) => { endingBalanceB = Number(creditBalanceEnd[item.modelYear.name].B) } + // if some value in creditBalanceStart is < 0, assume it is in deficits if (item.category === 'creditBalanceStart') { - creditBalanceStart[item.modelYear.name] = { - A: Number(item.creditAValue), - B: Number(item.creditBValue) + const startA = Number(item.creditAValue) + const startB = Number(item.creditBValue) + if (startA >= 0 || startB >= 0) { + if (item.modelYear.name in creditBalanceStart) { + creditBalanceStart[item.modelYear.name].A += (startA >= 0 ? startA : 0) + creditBalanceStart[item.modelYear.name].B += (startB >= 0 ? startB : 0) + } else { + creditBalanceStart[item.modelYear.name] = { + A: startA >= 0 ? startA : 0, + B: startB >= 0 ? startB : 0 + } + } + endingBalanceA += (startA >= 0 ? startA : 0) + endingBalanceB += (startB >= 0 ? startB : 0) } - - endingBalanceA += Number(item.creditAValue) - endingBalanceB += Number(item.creditBValue) } if (item.category === 'deficit') { @@ -44,8 +55,15 @@ const getComplianceObligationDetails = (complianceResponseDetails) => { } } - endingBalanceA -= Number(item.creditAValue) - endingBalanceB -= Number(item.creditBValue) + if (item.modelYear.name in deficitCollection) { + deficitCollection[item.modelYear.name].A += Number(item.creditAValue) + deficitCollection[item.modelYear.name].unspecified += Number(item.creditBValue) + } else { + deficitCollection[item.modelYear.name] = { + A: Number(item.creditAValue), + unspecified: Number(item.creditBValue) + } + } } if (item.category === 'transfersIn') { @@ -153,9 +171,9 @@ const getComplianceObligationDetails = (complianceResponseDetails) => { } }) - // go through every year in end balance and push to provisional + // go through every year in end balance and push to provisionalProvisionalBalance Object.keys(creditBalanceEnd).forEach((item) => { - provisionalBalance[item] = { + provisionalProvisionalBalance[item] = { A: Number(creditBalanceEnd[item].A), B: Number(creditBalanceEnd[item].B) } @@ -163,24 +181,39 @@ const getComplianceObligationDetails = (complianceResponseDetails) => { // go through every item in pending and add to total if year already there or create new pendingBalance.forEach((item) => { - if (provisionalBalance[item.modelYear]) { - provisionalBalance[item.modelYear].A += Number(item.A) - provisionalBalance[item.modelYear].B += Number(item.B) + if (provisionalProvisionalBalance[item.modelYear]) { + provisionalProvisionalBalance[item.modelYear].A += Number(item.A) + provisionalProvisionalBalance[item.modelYear].B += Number(item.B) } else { - provisionalBalance[item.modelYear] = { + provisionalProvisionalBalance[item.modelYear] = { A: item.A, B: item.B } } }) + const { provisionalBalance, reductionsToOffsetDeficit, carryOverDeficits } = getNewProvisionalBalance(provisionalProvisionalBalance, deficitCollection, creditOffsetSelection) + + //add deficits to creditBalanceEnd + Object.entries(deficitCollection).forEach(([modelYear, deficits]) => { + if (modelYear in creditBalanceEnd) { + creditBalanceEnd[modelYear].A -= deficits.A + creditBalanceEnd[modelYear].B -= deficits.unspecified + } else { + creditBalanceEnd[modelYear] = { + A: deficits.A * -1, + B: deficits.unspecified * -1 + } + } + }) + return { creditBalanceEnd, creditBalanceStart, creditsIssuedSales, - deficits, pendingBalance, pendingBalanceExist, + provisionalProvisionalBalance, provisionalBalance, transfersIn, transfersOut, @@ -188,7 +221,10 @@ const getComplianceObligationDetails = (complianceResponseDetails) => { purchaseAgreement, administrativeAllocation, administrativeReduction, - automaticAdministrativePenalty + automaticAdministrativePenalty, + deficitCollection, + reductionsToOffsetDeficit, + carryOverDeficits } } diff --git a/frontend/src/app/utilities/getModelYearReportCreditBalances.js b/frontend/src/app/utilities/getModelYearReportCreditBalances.js index cc697ecfe..e80e8f56e 100644 --- a/frontend/src/app/utilities/getModelYearReportCreditBalances.js +++ b/frontend/src/app/utilities/getModelYearReportCreditBalances.js @@ -31,8 +31,9 @@ const getModelYearReportCreditBalances = (modelYearReportId) => { const complianceResponseDetails = creditActivityResponse.data.complianceObligation const { - provisionalBalance - } = getComplianceObligationDetails(complianceResponseDetails) + provisionalBalance, + carryOverDeficits + } = getComplianceObligationDetails(complianceResponseDetails, creditReductionSelection) const { modelYear, @@ -98,7 +99,8 @@ const getModelYearReportCreditBalances = (modelYearReportId) => { tempBalances, tempClassAReductions, tempUnspecifiedReductions, - creditReductionSelection + creditReductionSelection, + carryOverDeficits ) return { diff --git a/frontend/src/app/utilities/getNewProvisionalBalance.js b/frontend/src/app/utilities/getNewProvisionalBalance.js new file mode 100644 index 000000000..794a235df --- /dev/null +++ b/frontend/src/app/utilities/getNewProvisionalBalance.js @@ -0,0 +1,119 @@ +const getNewProvisionalBalance = (provisionalProvisionalBalance, deficitCollection, creditOffsetSelection) => { + const provisionalBalance = getBalancesCopy(provisionalProvisionalBalance) + const deficitBalances = getBalancesCopy(deficitCollection) + + // offset A deficit first + offset(provisionalBalance, deficitBalances, 'A', 'A') + + // offset unspecified deficit according to creditOffsetSelection + if (!creditOffsetSelection || creditOffsetSelection === 'B') { + offset(provisionalBalance, deficitBalances, 'B', 'unspecified') + offset(provisionalBalance, deficitBalances, 'A', 'unspecified') + } else { + offset(provisionalBalance, deficitBalances, 'A', 'unspecified') + offset(provisionalBalance, deficitBalances, 'B', 'unspecified') + } + const reductionsToOffsetDeficit = getReductions(provisionalProvisionalBalance, provisionalBalance) + return { + provisionalBalance, + reductionsToOffsetDeficit, + carryOverDeficits: deficitBalances + } +} + +// the "balances" parameter should be an object where the values are of the type {A: primitive of type Number, B: primitive of type Number} +const getBalancesCopy = (balances) => { + const result = {} + Object.entries(balances).forEach(([key, credits]) => { + result[key] = { ...credits } + }) + return result +} + +// reduces balances accordingly; the "balances" and "deficitBalances" parameters should be objects where the keys are model years (for the sake of iteration order) +// and values of "balances" are of the type {A: number, B: number} +// and values of "deficitBalances" are of the type {A: number >= 0, unspecified: number >= 0} +const offset = (balances, deficitBalances, creditType, deficitType) => { + let totalCredit = 0 + let totalDeficit = 0 + for (const units of Object.values(balances)) { + if (units[creditType] > 0) { + totalCredit += units[creditType] + } + } + for (const deficits of Object.values(deficitBalances)) { + totalDeficit += deficits[deficitType] + } + + if (totalCredit === totalDeficit) { + for (const units of Object.values(balances)) { + if (units[creditType] > 0) { + units[creditType] = 0 + } + } + for (const deficits of Object.values(deficitBalances)) { + deficits[deficitType] = 0 + } + } else if (totalCredit < totalDeficit) { + for (const units of Object.values(balances)) { + if (units[creditType] > 0) { + units[creditType] = 0 + } + } + let runningDeficit = 0 + for (const deficits of Object.values(deficitBalances)) { + runningDeficit += deficits[deficitType] + if (runningDeficit < totalCredit) { + deficits[deficitType] = 0 + } else { + deficits[deficitType] = runningDeficit - totalCredit + break + } + } + } else if (totalCredit > totalDeficit) { + for (const deficits of Object.values(deficitBalances)) { + deficits[deficitType] = 0 + } + let runningCredit = 0 + for (const units of Object.values(balances)) { + if (units[creditType] > 0) { + runningCredit += units[creditType] + if (runningCredit < totalDeficit) { + units[creditType] = 0 + } else { + units[creditType] = runningCredit - totalDeficit + break + } + } + } + } +} + +// "initialBalances" should be objects whose values are of the type {A: number, B: number} +// "endingBalances" should be objects whose values are of the type {A: number, B: number} +const getReductions = (initialBalances, endingBalances) => { + const result = {} + for (const [modelYear, balance] of Object.entries(initialBalances)) { + let reductionA = 0 + let reductionB = 0 + const initialBalanceA = balance.A + const initialBalanceB = balance.B + const endingBalanceA = endingBalances[modelYear] ? endingBalances[modelYear].A : Number.POSITIVE_INFINITY + const endingBalanceB = endingBalances[modelYear] ? endingBalances[modelYear].B : Number.POSITIVE_INFINITY + if (initialBalanceA > 0 && endingBalanceA >= 0 && initialBalanceA > endingBalanceA) { + reductionA = initialBalanceA - endingBalanceA + } + if (initialBalanceB > 0 && endingBalanceB >= 0 && initialBalanceB > endingBalanceB) { + reductionB = initialBalanceB - endingBalanceB + } + if (reductionA || reductionB) { + result[modelYear] = { + A: reductionA, + B: reductionB + } + } + } + return result +} + +export default getNewProvisionalBalance diff --git a/frontend/src/app/utilities/getNewStructures.js b/frontend/src/app/utilities/getNewStructures.js new file mode 100644 index 000000000..458f9c577 --- /dev/null +++ b/frontend/src/app/utilities/getNewStructures.js @@ -0,0 +1,30 @@ +const getNewBalancesStructure = (balances) => { + const result = [] + Object.keys(balances).forEach((year) => { + const { A: creditA, B: creditB } = balances[year] + result.push({ + modelYear: Number(year), + creditA, + creditB + }) + }) + return result +} + +const getNewDeficitsStructure = (deficits) => { + const result = [] + Object.keys(deficits).forEach((year) => { + const deficitA = deficits[year].A + const deficitUnspecified = deficits[year].unspecified + if (deficitA > 0 || deficitUnspecified > 0) { + result.push({ + modelYear: Number(year), + creditA: deficitA, + creditB: deficitUnspecified + }) + } + }) + return result +} + +export { getNewBalancesStructure, getNewDeficitsStructure } diff --git a/frontend/src/app/utilities/upload.js b/frontend/src/app/utilities/upload.js index 00be190bb..166ed3e97 100644 --- a/frontend/src/app/utilities/upload.js +++ b/frontend/src/app/utilities/upload.js @@ -93,4 +93,41 @@ const chunkUpload = (url, files) => { }) } -export { upload, chunkUpload } +const getFileUploadPromises = (urlToGetPresignedUrl, files, updateProgressBars) => { + const result = [] + files.forEach((file, index) => { + const uploadPromise = new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => { + const blob = reader.result + axios.get(urlToGetPresignedUrl).then((response) => { + const { url: uploadUrl, minioObjectName } = response.data + axios.put(uploadUrl, blob, { + headers: { + Authorization: null + }, + onUploadProgress: (progressEvent) => { + if (updateProgressBars) { + updateProgressBars(progressEvent, index) + } + resolve({ + filename: file.name, + mimeType: file.type, + minioObjectName, + size: file.size + }) + } + }) + .catch((error) => { + reject(error) + }) + }) + } + reader.readAsArrayBuffer(file) + }) + result.push(uploadPromise) + }) + return result +} + +export { upload, chunkUpload, getFileUploadPromises } diff --git a/frontend/src/compliance/AssessmentContainer.js b/frontend/src/compliance/AssessmentContainer.js index 799b5d6be..b6fd9443a 100644 --- a/frontend/src/compliance/AssessmentContainer.js +++ b/frontend/src/compliance/AssessmentContainer.js @@ -14,6 +14,7 @@ import getComplianceObligationDetails from '../app/utilities/getComplianceObliga import getTotalReduction from '../app/utilities/getTotalReduction' import getUnspecifiedClassReduction from '../app/utilities/getUnspecifiedClassReduction' import ROUTES_SUPPLEMENTARY from '../app/routes/SupplementaryReport' +import { getNewBalancesStructure, getNewDeficitsStructure } from '../app/utilities/getNewStructures' const AssessmentContainer = (props) => { const { keycloak, user } = props @@ -286,8 +287,11 @@ const AssessmentContainer = (props) => { purchaseAgreement, administrativeAllocation, administrativeReduction, - automaticAdministrativePenalty - } = getComplianceObligationDetails(complianceResponseDetails) + automaticAdministrativePenalty, + deficitCollection, + reductionsToOffsetDeficit, + carryOverDeficits + } = getComplianceObligationDetails(complianceResponseDetails, creditReductionSelection) setPendingBalanceExist(tempPendingBalanceExist) @@ -296,6 +300,8 @@ const AssessmentContainer = (props) => { creditBalanceEnd, pendingBalance, provisionalBalance, + deficitCollection, + reductionsToOffsetDeficit, transactions: { creditsIssuedSales, transfersIn, @@ -323,16 +329,7 @@ const AssessmentContainer = (props) => { ) setTotalReduction(tempTotalReduction) - const tempBalances = [] - - Object.keys(provisionalBalance).forEach((year) => { - const { A: creditA, B: creditB } = provisionalBalance[year] - tempBalances.push({ - modelYear: Number(year), - creditA, - creditB - }) - }) + const tempBalances = getNewBalancesStructure(provisionalBalance) setBalances(tempBalances) @@ -357,13 +354,14 @@ const AssessmentContainer = (props) => { tempBalances, tempClassAReductions, tempUnspecifiedReductions, - creditReductionSelection + creditReductionSelection, + carryOverDeficits ) if (tempSupplierClass === 'S') { setUpdatedBalances({ balances: tempBalances, - deficits: [] + deficits: getNewDeficitsStructure(carryOverDeficits) }) } else { setDeductions(creditReduction.deductions) @@ -414,8 +412,27 @@ const AssessmentContainer = (props) => { if (status === 'RECOMMENDED') { const reportDetailsArray = [] Object.keys(creditDetails).forEach((each) => { + if (each === 'provisionalProvisionalBalance' || each === 'carryOverDeficits') { + return + } Object.keys(creditDetails[each]).forEach((year) => { - if (each !== 'transactions' && each !== 'pendingBalance') { + if (each === 'deficitCollection') { + const a = creditDetails[each][year].A + const b = creditDetails[each][year].unspecified + reportDetailsArray.push({ + category: 'deficit', + year, + a, + b + }) + } else if (each === 'reductionsToOffsetDeficit') { + reportDetailsArray.push({ + category: 'ReductionsToOffsetDeficit', + year, + a: creditDetails[each][year].A, + b: creditDetails[each][year].B + }) + } else if (each !== 'transactions' && each !== 'pendingBalance') { const a = creditDetails[each][year].A const b = creditDetails[each][year].B reportDetailsArray.push({ diff --git a/frontend/src/compliance/ComplianceObligationContainer.js b/frontend/src/compliance/ComplianceObligationContainer.js index 662838925..1d632c979 100644 --- a/frontend/src/compliance/ComplianceObligationContainer.js +++ b/frontend/src/compliance/ComplianceObligationContainer.js @@ -14,6 +14,8 @@ import getTotalReduction from '../app/utilities/getTotalReduction' import getUnspecifiedClassReduction from '../app/utilities/getUnspecifiedClassReduction' import getComplianceObligationDetails from '../app/utilities/getComplianceObligationDetails' import deleteModelYearReport from '../app/utilities/deleteModelYearReport' +import getNewProvisionalBalance from '../app/utilities/getNewProvisionalBalance' +import { getNewBalancesStructure, getNewDeficitsStructure } from '../app/utilities/getNewStructures' const ComplianceObligationContainer = (props) => { const { user } = props @@ -113,7 +115,8 @@ const ComplianceObligationContainer = (props) => { balances, tempClassAReductions, tempUnspecifiedReductions, - creditReductionSelection + creditReductionSelection, + reportDetails.carryOverDeficits ) if (supplierClass !== 'S') { @@ -141,14 +144,17 @@ const ComplianceObligationContainer = (props) => { } const handleUnspecifiedCreditReduction = (radioId) => { + const { provisionalBalance, reductionsToOffsetDeficit, carryOverDeficits } = getNewProvisionalBalance(reportDetails.provisionalProvisionalBalance, reportDetails.deficitCollection, radioId) + const newBalances = getNewBalancesStructure(provisionalBalance) if (supplierClass !== 'S') { setCreditReductionSelection(radioId) const creditReduction = calculateCreditReduction( - balances, + newBalances, classAReductions, unspecifiedReductions, - radioId + radioId, + carryOverDeficits ) setDeductions(creditReduction.deductions) @@ -157,7 +163,15 @@ const ComplianceObligationContainer = (props) => { balances: creditReduction.balances, deficits: creditReduction.deficits }) + } else { + const newDeficits = getNewDeficitsStructure(carryOverDeficits) + setUpdatedBalances({ + balances: newBalances, + deficits: newDeficits + }) } + setReportDetails({ ...reportDetails, provisionalBalance, reductionsToOffsetDeficit, carryOverDeficits }) + setBalances(newBalances) } const handleDelete = () => { @@ -167,8 +181,27 @@ const ComplianceObligationContainer = (props) => { const handleSave = () => { const reportDetailsArray = [] Object.keys(reportDetails).forEach((each) => { + if (each === 'provisionalProvisionalBalance' || each === 'carryOverDeficits') { + return + } Object.keys(reportDetails[each]).forEach((year) => { - if (each !== 'transactions' && each !== 'pendingBalance') { + if (each === 'deficitCollection') { + const a = reportDetails[each][year].A + const b = reportDetails[each][year].unspecified + reportDetailsArray.push({ + category: 'deficit', + year, + a, + b + }) + } else if (each === 'reductionsToOffsetDeficit') { + reportDetailsArray.push({ + category: 'ReductionsToOffsetDeficit', + year, + a: reportDetails[each][year].A, + b: reportDetails[each][year].B + }) + } else if (each !== 'transactions' && each !== 'pendingBalance') { const a = reportDetails[each][year].A const b = reportDetails[each][year].B reportDetailsArray.push({ @@ -317,7 +350,7 @@ const ComplianceObligationContainer = (props) => { const complianceResponseDetails = complianceResponse.data.complianceObligation - const { ldvSales } = complianceResponse.data + const { ldvSales, fromSnapshot } = complianceResponse.data if (ldvSales) { setSales(Number(ldvSales)) @@ -328,6 +361,7 @@ const ComplianceObligationContainer = (props) => { creditBalanceStart, creditsIssuedSales, pendingBalance, + provisionalProvisionalBalance, provisionalBalance, transfersIn, transfersOut, @@ -335,8 +369,11 @@ const ComplianceObligationContainer = (props) => { purchaseAgreement, administrativeAllocation, administrativeReduction, - automaticAdministrativePenalty - } = getComplianceObligationDetails(complianceResponseDetails) + automaticAdministrativePenalty, + deficitCollection, + reductionsToOffsetDeficit, + carryOverDeficits + } = getComplianceObligationDetails(complianceResponseDetails, radioSelection) if (pendingBalance && pendingBalance.length > 0) { setPendingBalanceExist(true) @@ -346,7 +383,11 @@ const ComplianceObligationContainer = (props) => { creditBalanceStart, creditBalanceEnd, pendingBalance, + provisionalProvisionalBalance, provisionalBalance, + deficitCollection, + reductionsToOffsetDeficit, + carryOverDeficits, transactions: { creditsIssuedSales, transfersIn, @@ -374,16 +415,7 @@ const ComplianceObligationContainer = (props) => { ) setTotalReduction(tempTotalReduction) - const tempBalances = [] - - Object.keys(provisionalBalance).forEach((year) => { - const { A: creditA, B: creditB } = provisionalBalance[year] - tempBalances.push({ - modelYear: Number(year), - creditA, - creditB - }) - }) + const tempBalances = getNewBalancesStructure(provisionalBalance) setBalances(tempBalances) @@ -424,13 +456,14 @@ const ComplianceObligationContainer = (props) => { tempBalances, tempClassAReductions, tempUnspecifiedReductions, - radioSelection + radioSelection, + carryOverDeficits ) if (tempSupplierClass === 'S') { setUpdatedBalances({ balances: tempBalances, - deficits: [] + deficits: getNewDeficitsStructure(carryOverDeficits) }) } else { setDeductions(creditReduction.deductions) diff --git a/frontend/src/compliance/ComplianceReportSummaryContainer.js b/frontend/src/compliance/ComplianceReportSummaryContainer.js index 61e7025bf..021bfac8f 100644 --- a/frontend/src/compliance/ComplianceReportSummaryContainer.js +++ b/frontend/src/compliance/ComplianceReportSummaryContainer.js @@ -189,8 +189,12 @@ const ComplianceReportSummaryContainer = (props) => { const bValue = parseFloat(item.creditBValue) creditBalanceStart.A += aValue creditBalanceStart.B += bValue - provisionalBalanceAfterCreditReduction.A += aValue - provisionalBalanceAfterCreditReduction.B += bValue + if (aValue > 0) { + provisionalBalanceAfterCreditReduction.A += aValue + } + if (bValue > 0) { + provisionalBalanceAfterCreditReduction.B += bValue + } } if (item.category === 'creditBalanceEnd') { @@ -222,6 +226,13 @@ const ComplianceReportSummaryContainer = (props) => { provisionalBalanceAfterCreditReduction.B -= bValue } + if (item.category === 'ReductionsToOffsetDeficit') { + const aValue = parseFloat(item.creditAValue) + const bValue = parseFloat(item.creditBValue) + provisionalBalanceAfterCreditReduction.A -= aValue + provisionalBalanceAfterCreditReduction.B -= bValue + } + if (item.category === 'pendingBalance') { const aValue = parseFloat(item.creditAValue) const bValue = parseFloat(item.creditBValue) diff --git a/frontend/src/compliance/components/ComplianceObligationDetailsPage.js b/frontend/src/compliance/components/ComplianceObligationDetailsPage.js index f0357ea61..dc494ecd5 100644 --- a/frontend/src/compliance/components/ComplianceObligationDetailsPage.js +++ b/frontend/src/compliance/components/ComplianceObligationDetailsPage.js @@ -121,7 +121,7 @@ const ComplianceObligationDetailsPage = (props) => {