From 0021a0a8ff9d035034c3f5efb17d31722b937f1a Mon Sep 17 00:00:00 2001
From: Kuan Fan <31664961+kuanfandevops@users.noreply.github.com>
Date: Tue, 21 Jun 2022 17:08:33 -0700
Subject: [PATCH] Tracking pull request to merge release-1.3.0 to main (#163)
* update to 1.3.0
* add CORS_ORIGIN_WHITELIST fr task queue (#164)
* Itvr 343 , itvr 342 (#165)
* -fixes complete your application text
* -moves bceid button into a paragraph below login banner
-removes padding on whats-needed
-fixes login square margins and height for small screens
* -adds id prop so redirect works for household
* -title change on form (#166)
* itvr 334 - email changes (#167)
* itvr-334--email-changes
* more email changes
* rebase to release-1.3.0 (#171)
* -changes wording to add 'pre-approval' to titles and login (#172)
* limit DOB year to 4 char (#170)
* NCDA - notify of newly issued rebate. (#169)
* update admin panel
* still trying to see what's wrong here
* format payload for odata
* comment out response printing
* remove duplicate model
* itvr-346--prevent-duplicate-rebates (#173)
* itvr-347--restrict-file-uploads-to-exactly-2 (#176)
* add household initiated (#174)
* add household initiated
* update constraint
* Add NCDA secret (#175)
* add NCDA secret
* update readme
* itvr-308--tracking--test-site-only (#177)
* -removes 'responsive' text from date input (#178)
-adds new date input to spouse form
* manually send rebate signal (#179)
* update resource (#181)
* update spilo resource and add one test (#182)
* fix rebate tests (#180)
* Fix/ncda tidy (#183)
* save id of sharepoint item
* fix comment
* -removes logout button from index (#184)
-adds media queries to footer
-cleans up footer css
* add address and postal fields to household (#185)
* household_member_bcsc_address (#186)
* Fix/contact us (#187)
* link to general bc gov contact us
* update description
* fix backend dl check (#188)
* fix backend dl check
* add household_initiated status
* itvr-333 - partial changes to console (#168)
* -updates approve/reject buttons
-adds some code to change template for change_list
* -removes extra code
* Clamav 1.3.0 (#189)
* upgrade backend cpu or workers have issue
Co-authored-by: Emily <44536222+emi-hi@users.noreply.github.com>
Co-authored-by: tim738745 <98717409+tim738745@users.noreply.github.com>
Co-authored-by: vibhiquartech <105294710+vibhiquartech@users.noreply.github.com>
Co-authored-by: Naomi
---
.pipeline/lib/config.js | 22 +-
chart/itvr/charts/itvr-spilo/values-prod.yaml | 8 +-
django/api/admin.py | 9 +
django/api/migrations/0001_initial.py | 16 +-
django/api/models/go_electric_rebate.py | 14 +-
.../models/go_electric_rebate_application.py | 17 ++
django/api/models/household_member.py | 3 +
django/api/serializers/application_form.py | 44 +--
django/api/serializers/household_member.py | 5 +-
django/api/services/ncda.py | 184 ++++++++++++
django/api/services/rebate.py | 22 +-
.../api/services/tests/test_issue_rebate.py | 5 +
django/api/services/tests/test_rebate.py | 4 +-
django/api/settings.py | 21 +-
django/api/signals.py | 18 +-
django/api/tasks.py | 61 ++--
.../change_list.html | 6 +
.../submit_line.html | 4 +-
django/api/viewsets/application_form.py | 23 ++
docker-compose.yml | 2 +
frontend/package-lock.json | 278 +++++++++++++++---
frontend/package.json | 5 +-
frontend/public/index.html | 3 +-
frontend/public/tracker.js | 40 +++
frontend/src/components/BCEIDLogin.js | 63 ++++
frontend/src/components/BottomBanner.js | 36 ---
frontend/src/components/Footer.js | 4 +-
frontend/src/components/Form.js | 53 +++-
frontend/src/components/Header.js | 8 +-
frontend/src/components/IndividualLogin.js | 19 +-
frontend/src/components/SpouseForm.js | 32 +-
.../src/components/upload/FileDropArea.js | 4 +-
frontend/src/components/upload/Upload.js | 4 +-
frontend/src/pages/Form.js | 2 +-
frontend/src/pages/HouseholdLogin.js | 19 +-
frontend/src/pages/index.js | 13 -
.../src/stories/HouseholdLogin.stories.jsx | 11 -
frontend/src/stories/Login.stories.jsx | 15 +
frontend/src/styles/Footer.scss | 90 +++---
frontend/src/styles/Header.scss | 13 +-
frontend/src/styles/Login.scss | 10 +-
frontend/src/utility.js | 1 +
openshift/templates/backend/README.md | 2 +-
openshift/templates/backend/backend-dc.yaml | 25 ++
.../templates/backend/itvr-ncda-secret.yaml | 25 ++
openshift/templates/clamav/Dockerfile | 18 ++
openshift/templates/clamav/clamav-bc.yaml | 73 +++++
openshift/templates/clamav/clamav-dc.yaml | 135 +++++++++
.../frontend/frontend-dc-docker.yaml | 13 +-
.../templates/task-queue/task-queue-dc.yaml | 2 +
tests/perf/testfrontend.js | 15 +
51 files changed, 1226 insertions(+), 293 deletions(-)
create mode 100644 django/api/services/ncda.py
create mode 100644 django/api/templates/admin/api/submittedgoelectricrebateapplication/change_list.html
create mode 100644 frontend/public/tracker.js
create mode 100644 frontend/src/components/BCEIDLogin.js
delete mode 100644 frontend/src/stories/HouseholdLogin.stories.jsx
create mode 100644 frontend/src/stories/Login.stories.jsx
create mode 100644 openshift/templates/backend/itvr-ncda-secret.yaml
create mode 100644 openshift/templates/clamav/Dockerfile
create mode 100644 openshift/templates/clamav/clamav-bc.yaml
create mode 100644 openshift/templates/clamav/clamav-dc.yaml
create mode 100644 tests/perf/testfrontend.js
diff --git a/.pipeline/lib/config.js b/.pipeline/lib/config.js
index ff3a0fd5..a9f6afc3 100644
--- a/.pipeline/lib/config.js
+++ b/.pipeline/lib/config.js
@@ -39,45 +39,45 @@ const phases = {
instance: `${name}-dev-${changeId}` , version:`${version}-${changeId}`, tag:`dev-${version}-${changeId}`,
host: `itvr-dev-${changeId}.${ocpName}.gov.bc.ca`, djangoDebug: 'True', logoutHostName: 'logontest.gov.bc.ca',
metabaseCpuRequest: '200m', metabaseCpuLimit: '300m', metabaseMemoryRequest: '500Mi', metabaseMemoryLimit: '2Gi', metabaseReplicas: 1,
- frontendCpuRequest: '70m', frontendCpuLimit: '210m', frontendMemoryRequest: '300Mi', frontendMemoryLimit: '600Mi', frontendReplicas: 1,
+ frontendCpuRequest: '30m', frontendCpuLimit: '60m', frontendMemoryRequest: '30Mi', frontendMemoryLimit: '60Mi', frontendReplicas: 1,
reactAppBCSCKeycloakClientId: 'itvr', reactAppBCSCKeycloakRealm: 'rzh2zkjq', reactAppBCSCKeycloakUrl: 'https://dev.oidc.gov.bc.ca/auth/', reactAppApiBase: `https://itvr-backend-dev-${changeId}.apps.silver.devops.gov.bc.ca`,
reactAppBCeIDKeycloakClientId: 'itvr-2674', reactAppBCeIDKeycloakRealm: 'onestopauth-basic', reactAppBCeIDKeycloakUrl: 'https://dev.oidc.gov.bc.ca/auth/',
backendCpuRequest: '60m', backendCpuLimit: '120m', backendMemoryRequest: '120Mi', backendMemoryLimit: '240Mi', backendHealthCheckDelay: 30, backendHost: `itvr-backend-dev-${changeId}.${ocpName}.gov.bc.ca`, backendReplicas: 1, backendDjangoDebug: 'True', bucketName: 'itvrdv',
minioCpuRequest: '30m', minioCpuLimit: '100m', minioMemoryRequest: '150Mi', minioMemoryLimit: '300Mi', minioPvcSize: '3Gi',
schemaspyCpuRequest: '50m', schemaspyCpuLimit: '200m', schemaspyMemoryRequest: '150M', schemaspyMemoryLimit: '300M', schemaspyHealthCheckDelay: 160,
rabbitmqCpuRequest: '250m', rabbitmqCpuLimit: '700m', rabbitmqMemoryRequest: '500M', rabbitmqMemoryLimit: '1G', rabbitmqPvcSize: '1G', rabbitmqReplica: 1, rabbitmqPostStartSleep: 120, storageClass: 'netapp-block-standard',
- patroniCpuRequest: '30m', patroniCpuLimit: '60m', patroniMemoryRequest: '80Mi', patroniMemoryLimit: '160Mi', patroniPvcSize: '2G', patroniReplica: 2, storageClass: 'netapp-block-standard', ocpName: `${ocpName}`,
- taskQueueCpuRequest: '40m', taskQueueCpuLimit: '120m', taskQueueMemoryRequest: '120Mi', taskQueueMemoryLimit: '240Mi', taskQueueReplicas: 1, taskQueueDjangoDebug: 'True',},
+ patroniCpuRequest: '60m', patroniCpuLimit: '120m', patroniMemoryRequest: '200Mi', patroniMemoryLimit: '400Mi', patroniPvcSize: '2G', patroniReplica: 2, storageClass: 'netapp-block-standard', ocpName: `${ocpName}`,
+ taskQueueCpuRequest: '60m', taskQueueCpuLimit: '120m', taskQueueMemoryRequest: '200Mi', taskQueueMemoryLimit: '400Mi', taskQueueReplicas: 1, taskQueueDjangoDebug: 'True',},
test: {namespace:'ac294c-test', name: `${name}`, ssoSuffix:'-test',
ssoName:'test.oidc.gov.bc.ca', phase: 'test' , changeId:`${changeId}`, suffix: `-test`,
instance: `${name}-test`, version:`${version}`, tag:`test-${version}`,
host: `itvr-test.${ocpName}.gov.bc.ca`, djangoDebug: 'False', logoutHostName: 'logontest.gov.bc.ca',
metabaseCpuRequest: '200m', metabaseCpuLimit: '300m', metabaseMemoryRequest: '500Mi', metabaseMemoryLimit: '2Gi', metabaseReplicas: 1,
- frontendCpuRequest: '70m', frontendCpuLimit: '210m', frontendMemoryRequest: '300Mi', frontendMemoryLimit: '600Mi', frontendReplicas: 2, frontendMinReplicas: 1, frontendMaxReplicas: 3,
+ frontendCpuRequest: '30m', frontendCpuLimit: '60m', frontendMemoryRequest: '30Mi', frontendMemoryLimit: '60Mi', frontendReplicas: 2, frontendMinReplicas: 1, frontendMaxReplicas: 3,
reactAppBCSCKeycloakClientId: 'itvr', reactAppBCSCKeycloakRealm: 'rzh2zkjq', reactAppBCSCKeycloakUrl: 'https://test.oidc.gov.bc.ca/auth/', reactAppApiBase: `https://itvr-backend-test.apps.silver.devops.gov.bc.ca`,
reactAppBCeIDKeycloakClientId: 'itvr-2674', reactAppBCeIDKeycloakRealm: 'onestopauth-basic', reactAppBCeIDKeycloakUrl: 'https://test.oidc.gov.bc.ca/auth/',
- backendCpuRequest: '40m', backendCpuLimit: '120m', backendMemoryRequest: '120Mi', backendMemoryLimit: '240Mi', backendHealthCheckDelay: 30, backendReplicas: 2, backendMinReplicas: 1, backendMaxReplicas: 3, backendHost: `itvr-backend-test.${ocpName}.gov.bc.ca`, backendDjangoDebug: 'False', bucketName: 'itvrts',
+ backendCpuRequest: '30m', backendCpuLimit: '60m', backendMemoryRequest: '120Mi', backendMemoryLimit: '240Mi', backendHealthCheckDelay: 30, backendReplicas: 2, backendMinReplicas: 1, backendMaxReplicas: 3, backendHost: `itvr-backend-test.${ocpName}.gov.bc.ca`, backendDjangoDebug: 'False', bucketName: 'itvrts',
minioCpuRequest: '30m', minioCpuLimit: '100m', minioMemoryRequest: '150Mi', minioMemoryLimit: '300Mi', minioPvcSize: '3G',
schemaspyCpuRequest: '20m', schemaspyCpuLimit: '200m', schemaspyMemoryRequest: '150M', schemaspyMemoryLimit: '300M', schemaspyHealthCheckDelay: 160,
rabbitmqCpuRequest: '250m', rabbitmqCpuLimit: '700m', rabbitmqMemoryRequest: '500M', rabbitmqMemoryLimit: '700M', rabbitmqPvcSize: '1G', rabbitmqReplica: 2, rabbitmqPostStartSleep: 120, storageClass: 'netapp-block-standard',
- patroniCpuRequest: '200m', patroniCpuLimit: '400m', patroniMemoryRequest: '250Mi', patroniMemoryLimit: '500Mi', patroniPvcSize: '5G', patroniReplica: 2, storageClass: 'netapp-block-standard', ocpName: `${ocpName}`,
- taskQueueCpuRequest: '40m', taskQueueCpuLimit: '120m', taskQueueMemoryRequest: '120Mi', taskQueueMemoryLimit: '240Mi', taskQueueReplicas: 1, taskQueueDjangoDebug: 'False',},
+ patroniCpuRequest: '60m', patroniCpuLimit: '120m', patroniMemoryRequest: '200Mi', patroniMemoryLimit: '400Mi', patroniPvcSize: '5G', patroniReplica: 2, storageClass: 'netapp-block-standard', ocpName: `${ocpName}`,
+ taskQueueCpuRequest: '60m', taskQueueCpuLimit: '120m', taskQueueMemoryRequest: '200Mi', taskQueueMemoryLimit: '400Mi', taskQueueReplicas: 1, taskQueueDjangoDebug: 'False',},
prod: {namespace:'ac294c-prod', name: `${name}`, ssoSuffix:'',
ssoName:'oidc.gov.bc.ca', phase: 'prod' , changeId:`${changeId}`, suffix: `-prod`,
instance: `${name}-prod`, version:`${version}`, tag:`prod-${version}`,
metabaseCpuRequest: '200m', metabaseCpuLimit: '300m', metabaseMemoryRequest: '500Mi', metabaseMemoryLimit: '2Gi', metabaseReplicas: 1,
host: `itvr-prod.${ocpName}.gov.bc.ca`, djangoDebug: 'False', logoutHostName: 'logon7.gov.bc.ca',
- frontendCpuRequest: '140m', frontendCpuLimit: '280m', frontendMemoryRequest: '600Mi', frontendMemoryLimit: '1200Mi', frontendReplicas: 2, frontendMinReplicas: 2, frontendMaxReplicas: 5,
+ frontendCpuRequest: '30m', frontendCpuLimit: '60m', frontendMemoryRequest: '30Mi', frontendMemoryLimit: '60Mi', frontendReplicas: 2, frontendMinReplicas: 2, frontendMaxReplicas: 5,
reactAppBCSCKeycloakClientId: 'itvr', reactAppBCSCKeycloakRealm: 'rzh2zkjq', reactAppBCSCKeycloakUrl: 'https://oidc.gov.bc.ca/auth/', reactAppApiBase: `https://itvr-backend-prod.apps.silver.devops.gov.bc.ca`,
reactAppBCeIDKeycloakClientId: 'itvr-2674', reactAppBCeIDKeycloakRealm: 'onestopauth-basic', reactAppBCeIDKeycloakUrl: 'https://oidc.gov.bc.ca/auth/',
- backendCpuRequest: '80m', backendCpuLimit: '160m', backendMemoryRequest: '240Mi', backendMemoryLimit: '480Mi', backendHealthCheckDelay: 30, backendReplicas: 3, backendMinReplicas: 3, backendMaxReplicas: 5, backendHost: `itvr-backend-prod.${ocpName}.gov.bc.ca`, backendDjangoDebug: 'False', bucketName: 'itvrpr',
+ backendCpuRequest: '30m', backendCpuLimit: '60m', backendMemoryRequest: '240Mi', backendMemoryLimit: '480Mi', backendHealthCheckDelay: 30, backendReplicas: 3, backendMinReplicas: 3, backendMaxReplicas: 5, backendHost: `itvr-backend-prod.${ocpName}.gov.bc.ca`, backendDjangoDebug: 'False', bucketName: 'itvrpr',
minioCpuRequest: '30m', minioCpuLimit: '100m', minioMemoryRequest: '150Mi', minioMemoryLimit: '300Mi', minioPvcSize: '3G',
schemaspyCpuRequest: '50m', schemaspyCpuLimit: '400m', schemaspyMemoryRequest: '150M', schemaspyMemoryLimit: '300M', schemaspyHealthCheckDelay: 160,
rabbitmqCpuRequest: '250m', rabbitmqCpuLimit: '700m', rabbitmqMemoryRequest: '500M', rabbitmqMemoryLimit: '1G', rabbitmqPvcSize: '5G', rabbitmqReplica: 2, rabbitmqPostStartSleep: 120, storageClass: 'netapp-block-standard',
- patroniCpuRequest: '200m', patroniCpuLimit: '400m', patroniMemoryRequest: '250Mi', patroniMemoryLimit: '500Mi', patroniPvcSize: '8G', patroniReplica: 3, storageClass: 'netapp-block-standard', ocpName: `${ocpName}`,
- taskQueueCpuRequest: '80m', taskQueueCpuLimit: '160m', taskQueueMemoryRequest: '150Mi', taskQueueMemoryLimit: '300Mi', taskQueueReplicas: 1, taskQueueDjangoDebug: 'False',}
+ patroniCpuRequest: '60m', patroniCpuLimit: '120m', patroniMemoryRequest: '200Mi', patroniMemoryLimit: '400Mi', patroniPvcSize: '5G', patroniReplica: 3, storageClass: 'netapp-block-standard', ocpName: `${ocpName}`,
+ taskQueueCpuRequest: '60m', taskQueueCpuLimit: '120m', taskQueueMemoryRequest: '200Mi', taskQueueMemoryLimit: '400Mi', taskQueueReplicas: 1, taskQueueDjangoDebug: 'False',}
};
diff --git a/chart/itvr/charts/itvr-spilo/values-prod.yaml b/chart/itvr/charts/itvr-spilo/values-prod.yaml
index 70dceac4..3e822b66 100644
--- a/chart/itvr/charts/itvr-spilo/values-prod.yaml
+++ b/chart/itvr/charts/itvr-spilo/values-prod.yaml
@@ -31,11 +31,11 @@ spilo:
resources:
limits:
- cpu: 240m
- memory: 800Mi
- requests:
cpu: 120m
- memory: 400Mi
+ memory: 400Mi
+ requests:
+ cpu: 60m
+ memory: 200Mi
podDisruptionBudget:
enabled: false
diff --git a/django/api/admin.py b/django/api/admin.py
index 0408a1a6..89bed27f 100644
--- a/django/api/admin.py
+++ b/django/api/admin.py
@@ -4,6 +4,7 @@
SubmittedGoElectricRebateApplication,
)
from .models.household_member import HouseholdMember
+from .models.go_electric_rebate import GoElectricRebate
class HouseholdApplicationInline(admin.StackedInline):
@@ -15,6 +16,9 @@ class HouseholdApplicationInline(admin.StackedInline):
"first_name",
"middle_names",
"date_of_birth",
+ "bcsc_address",
+ "bcsc_city",
+ "bcsc_postal_code",
"doc1_tag",
"doc2_tag",
"consent_personal",
@@ -89,3 +93,8 @@ def response_change(self, request, obj):
obj.status = GoElectricRebateApplication.Status.DECLINED
obj.save(update_fields=["status"])
return ret
+
+
+@admin.register(GoElectricRebate)
+class GoElectricRebateAdmin(admin.ModelAdmin):
+ pass
diff --git a/django/api/migrations/0001_initial.py b/django/api/migrations/0001_initial.py
index 7e46489a..e24b29c8 100644
--- a/django/api/migrations/0001_initial.py
+++ b/django/api/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.0.1 on 2022-06-12 23:01
+# Generated by Django 4.0.1 on 2022-06-21 17:05
import api.validators
from django.conf import settings
@@ -59,6 +59,9 @@ class Migration(migrations.Migration):
('first_name', models.CharField(max_length=250)),
('middle_names', models.CharField(blank=True, max_length=250, null=True)),
('date_of_birth', models.DateField(validators=[api.validators.validate_driving_age])),
+ ('bcsc_address', models.CharField(blank=True, max_length=250, null=True)),
+ ('bcsc_city', models.CharField(blank=True, max_length=250, null=True)),
+ ('bcsc_postal_code', models.CharField(blank=True, max_length=6, null=True)),
('doc1', models.ImageField(blank=True, null=True, upload_to='docs')),
('doc2', models.ImageField(blank=True, null=True, upload_to='docs')),
('consent_personal', models.BooleanField(validators=[api.validators.validate_consent])),
@@ -73,14 +76,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='GoElectricRebate',
fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
- ('id', models.AutoField(primary_key=True, serialize=False)),
- ('drivers_licence', models.CharField(max_length=8, validators=[django.core.validators.MinLengthValidator(7)])),
+ ('drivers_licence', models.CharField(max_length=8, unique=True, validators=[django.core.validators.MinLengthValidator(7)])),
('last_name', models.CharField(max_length=250)),
('expiry_date', models.DateField()),
('rebate_max_amount', models.IntegerField(default=0)),
- ('rebate_state', models.BooleanField(default=False)),
+ ('redeemed', models.BooleanField(default=False)),
+ ('ncda_id', models.IntegerField(blank=True, null=True)),
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='api.goelectricrebateapplication')),
],
options={
@@ -99,4 +103,8 @@ class Migration(migrations.Migration):
},
bases=('api.goelectricrebateapplication',),
),
+ migrations.AddConstraint(
+ model_name='goelectricrebateapplication',
+ constraint=models.UniqueConstraint(condition=models.Q(('status__in', ['household_initiated', 'submitted', 'approved', 'redeemed', 'verified'])), fields=('drivers_licence',), name='verify_rebate_status'),
+ ),
]
diff --git a/django/api/models/go_electric_rebate.py b/django/api/models/go_electric_rebate.py
index fddbd2f9..1ea82b68 100644
--- a/django/api/models/go_electric_rebate.py
+++ b/django/api/models/go_electric_rebate.py
@@ -5,25 +5,29 @@
BooleanField,
PROTECT,
ForeignKey,
- AutoField,
+ UUIDField,
)
from django.core.validators import MinLengthValidator
-
from django_extensions.db.models import TimeStampedModel
from django.utils.translation import gettext_lazy as _
from api.models.go_electric_rebate_application import GoElectricRebateApplication
class GoElectricRebate(TimeStampedModel):
- id = AutoField(primary_key=True)
application = ForeignKey(
GoElectricRebateApplication, on_delete=PROTECT, blank=True, null=True
)
drivers_licence = CharField(
- max_length=8, unique=False, validators=[MinLengthValidator(7)]
+ max_length=8, unique=True, validators=[MinLengthValidator(7)]
)
last_name = CharField(max_length=250, unique=False)
expiry_date = DateField()
rebate_max_amount = IntegerField(default=0)
- rebate_state = BooleanField(default=False)
+ redeemed = BooleanField(default=False)
+ # sharepoint id. If something goes wrong with notification we can find
+ # issued rebates with blank NCDA ids to try resending.
+ ncda_id = IntegerField(blank=True, null=True)
+
+ def __str__(self):
+ return "DL: " + self.drivers_licence + ", $" + str(self.rebate_max_amount)
diff --git a/django/api/models/go_electric_rebate_application.py b/django/api/models/go_electric_rebate_application.py
index 6f2e8aa1..fa88720a 100644
--- a/django/api/models/go_electric_rebate_application.py
+++ b/django/api/models/go_electric_rebate_application.py
@@ -11,6 +11,8 @@
ForeignKey,
TextChoices,
Manager,
+ Q,
+ UniqueConstraint,
)
from encrypted_fields.fields import EncryptedCharField
from django.utils.html import mark_safe
@@ -103,6 +105,21 @@ def __str__(self):
class Meta:
db_table = "go_electric_rebate_application"
+ constraints = [
+ UniqueConstraint(
+ fields=["drivers_licence"],
+ condition=Q(
+ status__in=[
+ "household_initiated",
+ "submitted",
+ "approved",
+ "redeemed",
+ "verified",
+ ]
+ ),
+ name="verify_rebate_status",
+ )
+ ]
# This is for the admin panel
diff --git a/django/api/models/household_member.py b/django/api/models/household_member.py
index a7f2abe7..fd015038 100644
--- a/django/api/models/household_member.py
+++ b/django/api/models/household_member.py
@@ -35,6 +35,9 @@ class HouseholdMember(TimeStampedModel):
first_name = CharField(max_length=250, unique=False)
middle_names = CharField(max_length=250, unique=False, blank=True, null=True)
date_of_birth = DateField(validators=[validate_driving_age])
+ bcsc_address = CharField(max_length=250, unique=False, blank=True, null=True)
+ bcsc_city = CharField(max_length=250, unique=False, blank=True, null=True)
+ bcsc_postal_code = CharField(max_length=6, unique=False, blank=True, null=True)
doc1 = ImageField(upload_to="docs", blank=True, null=True)
def doc1_tag(self):
diff --git a/django/api/serializers/application_form.py b/django/api/serializers/application_form.py
index 2c35e773..6649575d 100644
--- a/django/api/serializers/application_form.py
+++ b/django/api/serializers/application_form.py
@@ -4,7 +4,8 @@
from rest_framework.parsers import FormParser, MultiPartParser
from datetime import date
from django.core.exceptions import ValidationError
-
+from rest_framework.response import Response
+from rest_framework import status
class ApplicationFormCreateSerializer(ModelSerializer):
parser_classes = (
@@ -88,25 +89,28 @@ def create(self, validated_data):
user = request.user
spouse_email = request.data.get("spouse_email")
- obj = GoElectricRebateApplication.objects.create(
- sin=validated_data["sin"],
- status=self._get_status(validated_data),
- email=validated_data["email"],
- drivers_licence=validated_data["drivers_licence"],
- last_name=user.last_name,
- first_name=user.first_name,
- date_of_birth=user.date_of_birth,
- address=user.street_address,
- city=user.locality,
- postal_code=user.postal_code,
- tax_year=self._get_tax_year(),
- application_type=validated_data["application_type"],
- spouse_email=spouse_email,
- user=user,
- consent_personal=validated_data["consent_personal"],
- consent_tax=validated_data["consent_tax"],
- )
- return obj
+ try:
+ obj = GoElectricRebateApplication.objects.create(
+ sin=validated_data["sin"],
+ status=self._get_status(validated_data),
+ email=validated_data["email"],
+ drivers_licence=validated_data["drivers_licence"],
+ last_name=user.last_name,
+ first_name=user.first_name,
+ date_of_birth=user.date_of_birth,
+ address=user.street_address,
+ city=user.locality,
+ postal_code=user.postal_code,
+ tax_year=self._get_tax_year(),
+ application_type=validated_data["application_type"],
+ spouse_email=spouse_email,
+ user=user,
+ consent_personal=validated_data["consent_personal"],
+ consent_tax=validated_data["consent_tax"],
+ )
+ return obj
+ except Exception as e:
+ return Response({"response": str(e)}, status=status.HTTP_400_BAD_REQUEST)
def _get_status(self, validated_data):
application_type = validated_data["application_type"]
diff --git a/django/api/serializers/household_member.py b/django/api/serializers/household_member.py
index a0ac2559..06f15ca8 100644
--- a/django/api/serializers/household_member.py
+++ b/django/api/serializers/household_member.py
@@ -13,7 +13,7 @@ class HouseholdMemberApplicationCreateSerializer(ModelSerializer):
class Meta:
model = HouseholdMember
- exclude = ["user"]
+ exclude = ["user", "bcsc_address", "bcsc_city", "bcsc_postal_code"]
class HouseholdMemberApplicationCreateSerializerDefault(
@@ -67,6 +67,9 @@ def create(self, validated_data):
last_name=user.last_name,
first_name=user.first_name,
date_of_birth=user.date_of_birth,
+ bcsc_address=user.street_address,
+ bcsc_city=user.locality,
+ bcsc_postal_code=user.postal_code,
user=user,
consent_personal=validated_data["consent_personal"],
consent_tax=validated_data["consent_tax"],
diff --git a/django/api/services/ncda.py b/django/api/services/ncda.py
new file mode 100644
index 00000000..b6dbf5de
--- /dev/null
+++ b/django/api/services/ncda.py
@@ -0,0 +1,184 @@
+import requests
+import json
+from django.conf import settings
+from api.models.go_electric_rebate import GoElectricRebate
+
+
+def get_ncda_service_token() -> str:
+ client_id = settings.NCDA_CLIENT_ID
+ client_secret = settings.NCDA_CLIENT_SECRET
+ resource = settings.NCDA_RESOURCE
+ url = settings.NCDA_AUTH_URL
+ payload = {
+ "grant_type": "client_credentials",
+ "client_credentials": "client_credentials",
+ "client_id": client_id,
+ "client_secret": client_secret,
+ "resource": resource,
+ }
+
+ headers = {"content-type": "application/x-www-form-urlencoded"}
+
+ token_rs = requests.post(
+ url,
+ data=payload,
+ headers=headers,
+ verify=True,
+ )
+ token_rs.raise_for_status()
+ return token_rs.json()["access_token"]
+
+
+# Tell NCDA about a newly issued rebate.
+def notify(drivers_licence, last_name, expiry_date, rebate_amount, rebate_id):
+ api_endpoint = settings.NCDA_SHAREPOINT_URL
+ access_token = get_ncda_service_token()
+
+ # {
+ # "__metadata": {"type": "SP.Data.ITVREligibilityListItem"},
+ # "Title": "77777777",
+ # "LastName": "Test7",
+ # "ExpiryDT": "6/22/2023",
+ # "MaxRebateAmt": "1500",
+ # "Status": "Not-Redeemed",
+ # }
+ payload = json.dumps(
+ {
+ "__metadata": {"type": "SP.Data.ITVREligibilityListItem"},
+ "Title": drivers_licence,
+ "LastName": last_name,
+ "ExpiryDT": expiry_date,
+ "MaxRebateAmt": rebate_amount,
+ "Status": "Not-Redeemed",
+ }
+ )
+
+ headers = {
+ "Authorization": "Bearer " + access_token,
+ "Accept": "application/json;odata=verbose",
+ "Content-Type": "application/json;odata=verbose",
+ }
+
+ url = api_endpoint + "/lists/getbytitle('ITVREligibility')/items"
+
+ ncda_rs = requests.post(
+ url,
+ data=payload,
+ headers=headers,
+ verify=True,
+ )
+
+ # {
+ # "d": {
+ # "__metadata": {
+ # "id": "57bc90e6-6774-416e-8daa-090385ef8f45",
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)",
+ # "etag": '"1"',
+ # "type": "SP.Data.ITVREligibilityListItem",
+ # },
+ # "FirstUniqueAncestorSecurableObject": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/FirstUniqueAncestorSecurableObject"
+ # }
+ # },
+ # "RoleAssignments": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/RoleAssignments"
+ # }
+ # },
+ # "AttachmentFiles": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/AttachmentFiles"
+ # }
+ # },
+ # "ContentType": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/ContentType"
+ # }
+ # },
+ # "GetDlpPolicyTip": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/GetDlpPolicyTip"
+ # }
+ # },
+ # "FieldValuesAsHtml": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/FieldValuesAsHtml"
+ # }
+ # },
+ # "FieldValuesAsText": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/FieldValuesAsText"
+ # }
+ # },
+ # "FieldValuesForEdit": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/FieldValuesForEdit"
+ # }
+ # },
+ # "File": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/File"
+ # }
+ # },
+ # "Folder": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/Folder"
+ # }
+ # },
+ # "LikedByInformation": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/LikedByInformation"
+ # }
+ # },
+ # "ParentList": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/ParentList"
+ # }
+ # },
+ # "Properties": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/Properties"
+ # }
+ # },
+ # "Versions": {
+ # "__deferred": {
+ # "uri": "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/Web/Lists(guid'7aea54d3-9935-40a4-88cd-3ddd74c7d270')/Items(19)/Versions"
+ # }
+ # },
+ # "FileSystemObjectType": 0,
+ # "Id": 19,
+ # "ServerRedirectedEmbedUri": null,
+ # "ServerRedirectedEmbedUrl": "",
+ # "ID": 19,
+ # "ContentTypeId": "0x01008074AAC5CAD1A241B1109194FE6D8D5D00747BB541D61F1F4CA7028C9C97E823CB",
+ # "Title": "23456781",
+ # "Modified": "2022-06-16T22:51:39Z",
+ # "Created": "2022-06-16T22:51:39Z",
+ # "AuthorId": 1073741822,
+ # "EditorId": 1073741822,
+ # "OData__UIVersionString": "1.0",
+ # "Attachments": false,
+ # "GUID": "9e4bceff-29c5-41d0-889a-c320f32b64da",
+ # "ComplianceAssetId": null,
+ # "LastName": "Aro",
+ # "ExpiryDT": "2023-06-16T07:00:00Z",
+ # "MaxRebateAmt": 1400,
+ # "Status": "Not-Redeemed",
+ # "ClaimType": null,
+ # "OData__vti_ItemDeclaredRecord": null,
+ # }
+ # }
+
+ # 500 Server Error: Internal Server Error for url:
+ # is sent for duplicate driver's license no etc. Might come up in dev
+ # Our unique index on driver's license in the rebate table
+ # should prevent this in prod.
+ print(ncda_rs.text)
+
+ ncda_rs.raise_for_status()
+
+ data = ncda_rs.json()
+ ncda_id = data["d"]["ID"]
+
+ GoElectricRebate.objects.filter(pk=rebate_id).update(ncda_id=ncda_id)
diff --git a/django/api/services/rebate.py b/django/api/services/rebate.py
index 1069b7f7..12357be7 100644
--- a/django/api/services/rebate.py
+++ b/django/api/services/rebate.py
@@ -7,15 +7,21 @@
# gets applications from rebates
def get_applications(rebates):
+ result = {}
ids = []
if rebates is not None:
ids = list(rebates)
- return GoElectricRebateApplication.objects.in_bulk(ids)
+ applications = GoElectricRebateApplication.objects.filter(id__in=ids).filter(
+ status__exact=GoElectricRebateApplication.Status.VERIFIED
+ )
+ for application in applications:
+ result[application.id] = application
+ return result
# saves approved rebates to the rebate table; returns the saved rebates
def save_rebates(rebates, applications):
- result = []
+ created_rebates = []
if rebates is not None and applications is not None:
rebate_objs = []
for application_id, rebate_amount in rebates.items():
@@ -28,11 +34,17 @@ def save_rebates(rebates, applications):
last_name=application.last_name,
expiry_date=date.today() + timedelta(days=365),
rebate_max_amount=rebate_amount,
- rebate_state=False,
+ redeemed=False,
)
rebate_objs.append(rebate_obj)
- result = GoElectricRebate.objects.bulk_create(rebate_objs)
- return result
+ created_rebates = GoElectricRebate.objects.bulk_create(rebate_objs)
+ for rebate in created_rebates:
+ post_save.send(
+ sender=GoElectricRebate,
+ instance=rebate,
+ created=True,
+ )
+ return created_rebates
# updates application statuses; emits signals manually
diff --git a/django/api/services/tests/test_issue_rebate.py b/django/api/services/tests/test_issue_rebate.py
index c1fc7314..54637816 100644
--- a/django/api/services/tests/test_issue_rebate.py
+++ b/django/api/services/tests/test_issue_rebate.py
@@ -14,6 +14,11 @@ class TestIssueRebate(TestRebate):
@classmethod
def setUpClass(self):
super().setUpClass()
+ applications = GoElectricRebateApplication.objects.all()
+ for application in applications:
+ application.status = GoElectricRebateApplication.Status.VERIFIED
+ GoElectricRebateApplication.objects.bulk_update(applications, ["status"])
+
self.rebates = {
"9uXLvNQS5vkKnscD": 2000,
"B5t92XeH7NnFUwxc": 4000,
diff --git a/django/api/services/tests/test_rebate.py b/django/api/services/tests/test_rebate.py
index 657825aa..38046483 100644
--- a/django/api/services/tests/test_rebate.py
+++ b/django/api/services/tests/test_rebate.py
@@ -49,7 +49,7 @@ def setUpClass(self):
city="Victoria",
postal_code="v8s4j9",
tax_year=2020,
- drivers_licence="1234567",
+ drivers_licence="1234568",
)
GoElectricRebateApplication.objects.create(
id="ctW8gU57YX4xfQ9o",
@@ -68,7 +68,7 @@ def setUpClass(self):
city="Victoria",
postal_code="v8s4j9",
tax_year=2020,
- drivers_licence="1234567",
+ drivers_licence="1234569",
)
HouseholdMember.objects.create(
application_id="9uXLvNQS5vkKnscD",
diff --git a/django/api/settings.py b/django/api/settings.py
index e4fd6f3d..20fcb24e 100644
--- a/django/api/settings.py
+++ b/django/api/settings.py
@@ -202,7 +202,7 @@
"queue_limit": 50,
"bulk": 10,
"orm": "default",
- "save_limit": -1,
+ "save_limit": 20 if DEBUG else -1,
}
CACHES = {
@@ -230,3 +230,22 @@
"B": {"individual_income": 90000, "household_income": 145000, "rebate": 2000},
"A": {"individual_income": 80000, "household_income": 125000, "rebate": 4000},
}
+
+# NCDA Sharepoint config
+NCDA_CLIENT_ID = os.getenv(
+ "NCDA_CLIENT_ID",
+ "d4d97d40-bb26-44f8-ba70-c677471d6cc1@1d4864aa-f2da-42dc-a62a-34b4dd790b6a",
+)
+NCDA_CLIENT_SECRET = os.getenv("NCDA_CLIENT_SECRET")
+NCDA_RESOURCE = os.getenv(
+ "NCDA_RESOURCE",
+ "00000003-0000-0ff1-ce00-000000000000/newcardealers.sharepoint.com@1d4864aa-f2da-42dc-a62a-34b4dd790b6a",
+)
+NCDA_AUTH_URL = os.getenv(
+ "NCDA_AUTH_URL",
+ "https://accounts.accesscontrol.windows.net/1d4864aa-f2da-42dc-a62a-34b4dd790b6a/tokens/OAuth/2/",
+)
+NCDA_SHAREPOINT_URL = os.getenv(
+ "NCDA_SHAREPOINT_URL",
+ "https://newcardealers.sharepoint.com/sites/ElectricVehicleRebateApplications/_api/web",
+)
diff --git a/django/api/signals.py b/django/api/signals.py
index a3fcfad8..1174b7ec 100644
--- a/django/api/signals.py
+++ b/django/api/signals.py
@@ -1,6 +1,9 @@
from django.db.models.signals import post_save
-from .models.go_electric_rebate_application import GoElectricRebateApplication
+from .models.go_electric_rebate_application import (
+ GoElectricRebateApplication,
+)
from .models.household_member import HouseholdMember
+from .models.go_electric_rebate import GoElectricRebate
from django.dispatch import receiver
from django.conf import settings
from api.models.household_member import HouseholdMember
@@ -74,3 +77,16 @@ def after_status_change(sender, instance, created, **kwargs):
instance.id,
instance.tax_year,
)
+
+
+@receiver(post_save, sender=GoElectricRebate)
+def after_rebate_issued(sender, instance, created, **kwargs):
+ if created:
+ async_task(
+ "api.services.ncda.notify",
+ instance.drivers_licence,
+ instance.last_name,
+ instance.expiry_date.strftime("%m/%d/%Y"),
+ str(instance.rebate_max_amount),
+ instance.id,
+ )
diff --git a/django/api/tasks.py b/django/api/tasks.py
index 34e9d513..0bb407f7 100644
--- a/django/api/tasks.py
+++ b/django/api/tasks.py
@@ -78,9 +78,11 @@ def send_individual_confirm(recipient_email, application_id):
Passenger Vehicle Rebate program application.
+ Thank you.
+
We have received your application for a rebate under the CleanBC Go
- Electric Passenger Vehicle Rebate program.
+ Electric Passenger Vehicle Rebate program. You can expect to get an email reply with the result of your application within 3 weeks.
Please keep this e-mail for your records.
@@ -100,13 +102,6 @@ def send_spouse_initial_message(recipient_email, application_id, initiator_email
-
- This email was generated by the CleanBC Go Electric
- Passenger Vehicle Rebate program application.
-
-
- Dear Applicant,
-
You are receiving this e-mail as you have been identified as a
spouse under a household rebate application for the CleanBC Go
@@ -120,11 +115,11 @@ def send_spouse_initial_message(recipient_email, application_id, initiator_email
{origin}/household?q={application_id}
-
+
If you are not the intended person to receive this email, please
contact the CleanBC Go Electric Passenger Vehicle Rebate program at
ZEVPrograms@gov.bc.ca
-
+
Additional Questions?
@@ -152,7 +147,7 @@ def send_household_confirm(recipient_email, application_id):
We have now received all documentation for your application for a
household rebate under the CleanBC Go Electric Passenger Vehicle
- Rebate program.
+ Rebate program. You can expect to get an email reply with the result of your application within 3 weeks.
Please keep this e-mail for your records.
@@ -175,14 +170,9 @@ def send_reject(recipient_email, application_id):
This email was generated by the CleanBC Go Electric Passenger
Vehicle Rebate program application.
- Dear Applicant,
-
- You are receiving this e-mail as a response to your application for
- a rebate under the CleanBC Go Electric Passenger Vehicle Rebate
- program.
+ Dear Applicant,
- We would like to notify you, that your application has not been
- approved.
+ Your application has not been approved.
Some examples of why this may have happened include:
@@ -196,6 +186,8 @@ def send_reject(recipient_email, application_id):
+ You are encouraged to correct these issues and submit another application.
+
Questions?
Please feel free to contact us at ZEVPrograms@gov.bc.ca
@@ -219,34 +211,29 @@ def send_approve(recipient_email, application_id, rebate_amount):
This email was generated by the CleanBC Go Electric Passenger
Vehicle Rebate program application.
- Dear Applicant,
-
- You are receiving this e-mail as a response to your application for
- a rebate under the CleanBC Go Electric Passenger Vehicle Rebate
- program.
+ Dear Applicant,
- We would like to notify you, that your application has been approved
- and you are entitled to a maximum rebate amount of ${rebate_amount}.
+
+ Your application has been approved for a rebate amount of up to ${rebate_amount}.
+ The full amount applies to purchases of battery electric and long-range plug-in hybrids.
+ For plug-in hybrids with ranges less than 85 km the rebate amount is half.
+
Your rebate will expire one year from today’s date.
Next steps:
- Please allow X business days for your information to be populated into
- the rebate database at B.C. automotive dealerships.
-
- Bring your drivers license with you to an automotive dealer in B.C.
+ Your approval is now linked to your driver’s licence. Bring your driver's licence with you to a new car dealer in B.C.
- Use your rebate at the time of vehicle purchase to realize cost savings
- on your new zero-emission vehicle!
+ Claim your rebate at the time of vehicle purchase to save money on your new zero-emission vehicle!
- Please note: This e-mail confirms that you have been approved for a
+
Please note: This e-mail confirms that you have been approved for a
rebate under the CleanBC Go Electric Light-Duty Vehicle program only.
Accessing the rebate is conditional on Program funds being available
- at the time of vehicle purchase.
+ at the time of vehicle purchase.
Questions?
@@ -273,13 +260,9 @@ def send_not_approve(recipient_email, application_id, tax_year):
This email was generated by the CleanBC Go Electric Passenger
Vehicle Rebate program application.
- Dear Applicant,
-
- You are receiving this e-mail as a response to your application for
- a rebate under the CleanBC Go Electric Passenger Vehicle Rebate
- program.
+ Dear Applicant,
- We would like to notify you, that your application has not been approved.
+ Your application has not been approved.
Some examples of why this may have happened include:
diff --git a/django/api/templates/admin/api/submittedgoelectricrebateapplication/change_list.html b/django/api/templates/admin/api/submittedgoelectricrebateapplication/change_list.html
new file mode 100644
index 00000000..a656dc8e
--- /dev/null
+++ b/django/api/templates/admin/api/submittedgoelectricrebateapplication/change_list.html
@@ -0,0 +1,6 @@
+{% extends "admin/change_list.html" %} {% load i18n admin_urls jazzmin %} {%
+get_jazzmin_ui_tweaks as jazzmin_ui %}
+
+
+{% block content_title %} Submitted Rebate Applications {% endblock %}
+
diff --git a/django/api/templates/admin/api/submittedgoelectricrebateapplication/submit_line.html b/django/api/templates/admin/api/submittedgoelectricrebateapplication/submit_line.html
index c531f5e9..7a8c126f 100644
--- a/django/api/templates/admin/api/submittedgoelectricrebateapplication/submit_line.html
+++ b/django/api/templates/admin/api/submittedgoelectricrebateapplication/submit_line.html
@@ -14,7 +14,7 @@
@@ -23,7 +23,7 @@
diff --git a/django/api/viewsets/application_form.py b/django/api/viewsets/application_form.py
index ef004f13..e2860acd 100644
--- a/django/api/viewsets/application_form.py
+++ b/django/api/viewsets/application_form.py
@@ -39,3 +39,26 @@ def get_serializer_class(self):
return ApplicationFormCreateSerializerBCSC
return ApplicationFormCreateSerializerDefault
return ApplicationFormSerializer
+
+ @action(detail=False, methods=["GET"], url_path="check_status")
+ def check_status(self, request, pk=None):
+ drivers_licence = request.query_params.get("drivers_license", None)
+ dl_not_valid = (
+ GoElectricRebateApplication.objects.filter(
+ drivers_licence__exact=drivers_licence
+ )
+ .filter(
+ status__in=[
+ GoElectricRebateApplication.Status.SUBMITTED,
+ GoElectricRebateApplication.Status.HOUSEHOLD_INITIATED,
+ GoElectricRebateApplication.Status.VERIFIED,
+ GoElectricRebateApplication.Status.APPROVED,
+ GoElectricRebateApplication.Status.REDEEMED,
+ ]
+ )
+ .exists()
+ )
+
+ if dl_not_valid:
+ return Response({"drivers_license_valid": "false"})
+ return Response({"drivers_license_valid": "true"})
diff --git a/docker-compose.yml b/docker-compose.yml
index a8018587..4db019e0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -60,6 +60,7 @@ services:
- CHES_EMAIL_URL
- SEND_EMAIL
- BYPASS_AUTHENTICATION
+ - NCDA_CLIENT_SECRET
volumes:
- ./django:/api
ports:
@@ -87,6 +88,7 @@ services:
- CHES_AUTH_URL
- CHES_EMAIL_URL
- SEND_EMAIL
+ - NCDA_CLIENT_SECRET
volumes:
- ./django:/api
depends_on:
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 521c197d..232854b0 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,23 +1,26 @@
{
"name": "frontend",
- "version": "1.2.0",
+ "version": "1.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "frontend",
- "version": "1.2.0",
+ "version": "1.3.0",
"dependencies": {
+ "@date-io/date-fns": "^2.14.0",
"@emotion/react": "^11.6.0",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.4.4",
"@mui/material": "^5.2.2",
"@mui/styles": "^5.2.2",
+ "@mui/x-date-pickers": "^5.0.0-alpha.6",
"@react-keycloak/web": "^3.4.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.0",
+ "date-fns": "^2.28.0",
"jwt-decode": "^3.1.2",
"keycloak-js": "^17.0.0",
"prop-types": "^15.8.1",
@@ -2202,6 +2205,75 @@
"postcss": "^8.3"
}
},
+ "node_modules/@date-io/core": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.14.0.tgz",
+ "integrity": "sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw=="
+ },
+ "node_modules/@date-io/date-fns": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.14.0.tgz",
+ "integrity": "sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==",
+ "dependencies": {
+ "@date-io/core": "^2.14.0"
+ },
+ "peerDependencies": {
+ "date-fns": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "date-fns": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@date-io/dayjs": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.14.0.tgz",
+ "integrity": "sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==",
+ "dependencies": {
+ "@date-io/core": "^2.14.0"
+ },
+ "peerDependencies": {
+ "dayjs": "^1.8.17"
+ },
+ "peerDependenciesMeta": {
+ "dayjs": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@date-io/luxon": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.14.0.tgz",
+ "integrity": "sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==",
+ "dependencies": {
+ "@date-io/core": "^2.14.0"
+ },
+ "peerDependencies": {
+ "luxon": "^1.21.3 || ^2.x"
+ },
+ "peerDependenciesMeta": {
+ "luxon": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@date-io/moment": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.14.0.tgz",
+ "integrity": "sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==",
+ "dependencies": {
+ "@date-io/core": "^2.14.0"
+ },
+ "peerDependencies": {
+ "moment": "^2.24.0"
+ },
+ "peerDependenciesMeta": {
+ "moment": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -2270,12 +2342,12 @@
}
},
"node_modules/@emotion/cache": {
- "version": "11.7.1",
- "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
- "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
+ "version": "11.9.3",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.9.3.tgz",
+ "integrity": "sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==",
"dependencies": {
"@emotion/memoize": "^0.7.4",
- "@emotion/sheet": "^1.1.0",
+ "@emotion/sheet": "^1.1.1",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "4.0.13"
@@ -2408,15 +2480,14 @@
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"node_modules/@emotion/react": {
- "version": "11.8.1",
- "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.8.1.tgz",
- "integrity": "sha512-XGaie4nRxmtP1BZYBXqC5JGqMYF2KRKKI7vjqNvQxyRpekVAZhb6QqrElmZCAYXH1L90lAelADSVZC4PFsrJ8Q==",
+ "version": "11.9.3",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.3.tgz",
+ "integrity": "sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
- "@emotion/cache": "^11.7.1",
- "@emotion/serialize": "^1.0.2",
- "@emotion/sheet": "^1.1.0",
+ "@emotion/cache": "^11.9.3",
+ "@emotion/serialize": "^1.0.4",
"@emotion/utils": "^1.1.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
@@ -2435,9 +2506,9 @@
}
},
"node_modules/@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.4.tgz",
+ "integrity": "sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==",
"dependencies": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
@@ -2447,9 +2518,9 @@
}
},
"node_modules/@emotion/sheet": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
- "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.1.tgz",
+ "integrity": "sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA=="
},
"node_modules/@emotion/styled": {
"version": "11.8.1",
@@ -3743,6 +3814,61 @@
"react": "^17.0.0"
}
},
+ "node_modules/@mui/x-date-pickers": {
+ "version": "5.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.6.tgz",
+ "integrity": "sha512-2JeagDwwa/V2XPj243cZg5ReZ2553OzukUAfbdxXwj9gGGLeXjBa95NP4kPOBOze4tJq1y/4aYt/aK50aZWElQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.17.2",
+ "@date-io/date-fns": "^2.14.0",
+ "@date-io/dayjs": "^2.14.0",
+ "@date-io/luxon": "^2.14.0",
+ "@date-io/moment": "^2.14.0",
+ "@mui/utils": "^5.4.1",
+ "clsx": "^1.1.1",
+ "prop-types": "^15.7.2",
+ "react-transition-group": "^4.4.2",
+ "rifm": "^0.12.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@mui/material": "^5.4.1",
+ "@mui/system": "^5.4.1",
+ "date-fns": "^2.25.0",
+ "dayjs": "^1.10.7",
+ "luxon": "^1.28.0 || ^2.0.0",
+ "moment": "^2.29.1",
+ "react": "^17.0.2 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -16504,6 +16630,18 @@
"node": ">=10"
}
},
+ "node_modules/date-fns": {
+ "version": "2.28.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
+ "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==",
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
@@ -27789,6 +27927,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rifm": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz",
+ "integrity": "sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==",
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -33865,6 +34011,43 @@
"postcss-value-parser": "^4.2.0"
}
},
+ "@date-io/core": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.14.0.tgz",
+ "integrity": "sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw=="
+ },
+ "@date-io/date-fns": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.14.0.tgz",
+ "integrity": "sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==",
+ "requires": {
+ "@date-io/core": "^2.14.0"
+ }
+ },
+ "@date-io/dayjs": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.14.0.tgz",
+ "integrity": "sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==",
+ "requires": {
+ "@date-io/core": "^2.14.0"
+ }
+ },
+ "@date-io/luxon": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.14.0.tgz",
+ "integrity": "sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==",
+ "requires": {
+ "@date-io/core": "^2.14.0"
+ }
+ },
+ "@date-io/moment": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.14.0.tgz",
+ "integrity": "sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==",
+ "requires": {
+ "@date-io/core": "^2.14.0"
+ }
+ },
"@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -33920,12 +34103,12 @@
}
},
"@emotion/cache": {
- "version": "11.7.1",
- "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
- "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
+ "version": "11.9.3",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.9.3.tgz",
+ "integrity": "sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==",
"requires": {
"@emotion/memoize": "^0.7.4",
- "@emotion/sheet": "^1.1.0",
+ "@emotion/sheet": "^1.1.1",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "4.0.13"
@@ -34059,24 +34242,23 @@
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"@emotion/react": {
- "version": "11.8.1",
- "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.8.1.tgz",
- "integrity": "sha512-XGaie4nRxmtP1BZYBXqC5JGqMYF2KRKKI7vjqNvQxyRpekVAZhb6QqrElmZCAYXH1L90lAelADSVZC4PFsrJ8Q==",
+ "version": "11.9.3",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.3.tgz",
+ "integrity": "sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
- "@emotion/cache": "^11.7.1",
- "@emotion/serialize": "^1.0.2",
- "@emotion/sheet": "^1.1.0",
+ "@emotion/cache": "^11.9.3",
+ "@emotion/serialize": "^1.0.4",
"@emotion/utils": "^1.1.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
}
},
"@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.4.tgz",
+ "integrity": "sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
@@ -34086,9 +34268,9 @@
}
},
"@emotion/sheet": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
- "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.1.tgz",
+ "integrity": "sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA=="
},
"@emotion/styled": {
"version": "11.8.1",
@@ -34986,6 +35168,23 @@
"react-is": "^17.0.2"
}
},
+ "@mui/x-date-pickers": {
+ "version": "5.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.6.tgz",
+ "integrity": "sha512-2JeagDwwa/V2XPj243cZg5ReZ2553OzukUAfbdxXwj9gGGLeXjBa95NP4kPOBOze4tJq1y/4aYt/aK50aZWElQ==",
+ "requires": {
+ "@babel/runtime": "^7.17.2",
+ "@date-io/date-fns": "^2.14.0",
+ "@date-io/dayjs": "^2.14.0",
+ "@date-io/luxon": "^2.14.0",
+ "@date-io/moment": "^2.14.0",
+ "@mui/utils": "^5.4.1",
+ "clsx": "^1.1.1",
+ "prop-types": "^15.7.2",
+ "react-transition-group": "^4.4.2",
+ "rifm": "^0.12.1"
+ }
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -44938,6 +45137,11 @@
"whatwg-url": "^8.0.0"
}
},
+ "date-fns": {
+ "version": "2.28.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
+ "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
+ },
"debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
@@ -53314,6 +53518,12 @@
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
},
+ "rifm": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz",
+ "integrity": "sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==",
+ "requires": {}
+ },
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index d31dcbce..a6dd7b1b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,18 +1,21 @@
{
"name": "frontend",
- "version": "1.2.0",
+ "version": "1.3.0",
"private": true,
"dependencies": {
+ "@date-io/date-fns": "^2.14.0",
"@emotion/react": "^11.6.0",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.4.4",
"@mui/material": "^5.2.2",
"@mui/styles": "^5.2.2",
+ "@mui/x-date-pickers": "^5.0.0-alpha.6",
"@react-keycloak/web": "^3.4.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.0",
+ "date-fns": "^2.28.0",
"jwt-decode": "^3.1.2",
"keycloak-js": "^17.0.0",
"prop-types": "^15.8.1",
diff --git a/frontend/public/index.html b/frontend/public/index.html
index 0a6828f3..3524c545 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -7,7 +7,7 @@
+
You need to enable JavaScript to run this app.
diff --git a/frontend/public/tracker.js b/frontend/public/tracker.js
new file mode 100644
index 00000000..51641efd
--- /dev/null
+++ b/frontend/public/tracker.js
@@ -0,0 +1,40 @@
+if (window.itvr_config && window.itvr_config.REACT_APP_ENV === 'test') {
+ //
+ (function (p, l, o, w, i, n, g) {
+ if (!p[i]) {
+ p.GlobalSnowplowNamespace = p.GlobalSnowplowNamespace || [];
+ p.GlobalSnowplowNamespace.push(i);
+ p[i] = function () {
+ (p[i].q = p[i].q || []).push(arguments);
+ };
+ p[i].q = p[i].q || [];
+ n = l.createElement(o);
+ g = l.getElementsByTagName(o)[0];
+ n.async = 1;
+ n.src = w;
+ g.parentNode.insertBefore(n, g);
+ }
+ })(
+ window,
+ document,
+ 'script',
+ 'https://www2.gov.bc.ca/StaticWebResources/static/sp/sp-2-14-0.js',
+ 'snowplow'
+ );
+ var collector = 'spm.apps.gov.bc.ca';
+ window.snowplow('newTracker', 'rt', collector, {
+ appId: 'Snowplow_standalone',
+ cookieLifetime: 86400 * 548,
+ platform: 'web',
+ post: true,
+ forceSecureTracker: true,
+ contexts: {
+ webPage: true,
+ performanceTiming: true
+ }
+ });
+ window.snowplow('enableActivityTracking', 30, 30); // Ping every 30 seconds after 30 seconds
+ window.snowplow('enableLinkClickTracking');
+ window.snowplow('trackPageView');
+ //
+}
diff --git a/frontend/src/components/BCEIDLogin.js b/frontend/src/components/BCEIDLogin.js
new file mode 100644
index 00000000..41285374
--- /dev/null
+++ b/frontend/src/components/BCEIDLogin.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import { BCEID_KEYCLOAK_REALM } from '../config';
+import { useKeycloak } from '@react-keycloak/web';
+import { keycloakInitOptions, keycloaks } from '../keycloak';
+const BCEIDLogin = (props) => {
+ const { type = '', householdApplicationId = '' } = props;
+ const { keycloak } = useKeycloak();
+ const redirectUri = householdApplicationId
+ ? `${window.location.origin}/householdForm?q=${householdApplicationId}`
+ : `${window.location.origin}/form`;
+
+ return (
+
+
Basic BCeID Account
+
+
+
Alternate method to confirm your identity.
+
+
+
+
{
+ localStorage.setItem('keycloakRealm', BCEID_KEYCLOAK_REALM);
+ if (keycloak.realm === BCEID_KEYCLOAK_REALM) {
+ keycloak.login({
+ idpHint: 'bceid-basic',
+ redirectUri: redirectUri
+ });
+ } else {
+ const bceidKeycloak = keycloaks[BCEID_KEYCLOAK_REALM];
+ bceidKeycloak.init(keycloakInitOptions).then(() => {
+ bceidKeycloak.login({
+ idpHint: 'bceid-basic',
+ redirectUri: redirectUri
+ });
+ });
+ }
+ }}
+ >
+ Log in with BCeID
+
+
+ );
+};
+
+export default BCEIDLogin;
diff --git a/frontend/src/components/BottomBanner.js b/frontend/src/components/BottomBanner.js
index 2235df0f..c1c66afe 100644
--- a/frontend/src/components/BottomBanner.js
+++ b/frontend/src/components/BottomBanner.js
@@ -55,42 +55,6 @@ const BottomBanner = (props) => {
-
- Basic BCeID
- {
- localStorage.setItem('keycloakRealm', BCEID_KEYCLOAK_REALM);
- if (keycloak.realm === BCEID_KEYCLOAK_REALM) {
- keycloak.login({
- idpHint: 'bceid-basic',
- redirectUri: redirectUri
- });
- } else {
- const bceidKeycloak = keycloaks[BCEID_KEYCLOAK_REALM];
- bceidKeycloak.init(keycloakInitOptions).then(() => {
- bceidKeycloak.login({
- idpHint: 'bceid-basic',
- redirectUri: redirectUri
- });
- });
- }
- }}
- >
- Log in with BCeID
-
-
-
-
>
diff --git a/frontend/src/components/Footer.js b/frontend/src/components/Footer.js
index fa54ea33..76777540 100644
--- a/frontend/src/components/Footer.js
+++ b/frontend/src/components/Footer.js
@@ -27,7 +27,9 @@ const Footer = () => {
- Contact Us
+
+ Contact Us
+
diff --git a/frontend/src/components/Form.js b/frontend/src/components/Form.js
index 8971e525..c99609b3 100644
--- a/frontend/src/components/Form.js
+++ b/frontend/src/components/Form.js
@@ -26,6 +26,9 @@ import Loading from './Loading';
import { useKeycloak } from '@react-keycloak/web';
import BCSCInfo from './BCSCInfo';
import { addTokenFields } from '../keycloak';
+import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
+import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
+import { DatePicker } from '@mui/x-date-pickers/DatePicker';
export const defaultValues = {
sin: '',
@@ -47,6 +50,8 @@ export const defaultValues = {
const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
const [loading, setLoading] = useState(false);
+ const [DOB, setDOB] = useState(new Date());
+ const [submitStatus, setSubmitStatus] = useState(true);
const queryClient = useQueryClient();
const { keycloak } = useKeycloak();
const kcToken = keycloak.tokenParsed;
@@ -82,6 +87,12 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
const onSubmit = (data) => {
setNumberOfErrors(0);
setLoading(true);
+ if (kcToken.identity_provider !== 'bcsc') {
+ data = {
+ ...data,
+ date_of_birth: data.date_of_birth.toISOString().slice(0, 10)
+ };
+ }
mutation.mutate(data, {
onSuccess: (data, variables, context) => {
const id = data.data.id;
@@ -95,6 +106,15 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
});
};
+ const checkDLStatus = (dl) => {
+ const detailUrl = `/api/application-form/check_status/?drivers_license=${dl}`;
+ axiosInstance.current.get(detailUrl).then((response) => {
+ response.data.drivers_license_valid === 'false'
+ ? setSubmitStatus(false)
+ : setSubmitStatus(true);
+ });
+ };
+
const onError = (errors) => {
const numberOfErrors = Object.keys(errors).length;
setNumberOfErrors(numberOfErrors);
@@ -177,7 +197,7 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
secure form submission
@@ -256,12 +276,20 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
name="date_of_birth"
control={control}
render={({ field }) => (
- setValue('date_of_birth', e.target.value)}
- />
+
+ {
+ setValue('date_of_birth', newDate);
+ setDOB(newDate);
+ }}
+ renderInput={(params) => }
+ />
+
)}
rules={{
validate: (inputtedDOB) => {
@@ -400,6 +428,12 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
{errors?.drivers_licence?.type === 'validate' && (
Not a valid B.C. Driver's Licence Number
)}
+ {!submitStatus && (
+
+ Error: This driver's licence number has already been submitted or
+ issued a rebate.
+
+ )}
BC Driver's Licence number (used for redeeming your rebate):
@@ -416,6 +450,9 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
DL:
}
onChange={(e) => setValue('drivers_licence', e.target.value)}
+ onBlur={(e) => {
+ checkDLStatus(e.target.value);
+ }}
/>
)}
rules={{
@@ -459,7 +496,7 @@ const Form = ({ setNumberOfErrors, setErrorsExistCounter }) => {
paddingX: '30px',
paddingY: '10px'
}}
- disabled={loading}
+ disabled={loading || !submitStatus}
>
Submit Application
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 68564484..9f549a5e 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -12,7 +12,13 @@ const Header = (props) => {
>
- CleanBC Go Electric
+
+ CleanBC Go Electric
+
Passenger vehicle rebates
diff --git a/frontend/src/components/IndividualLogin.js b/frontend/src/components/IndividualLogin.js
index a91c51e9..f2e1a214 100644
--- a/frontend/src/components/IndividualLogin.js
+++ b/frontend/src/components/IndividualLogin.js
@@ -2,7 +2,7 @@ import React from 'react';
import BottomBanner from './BottomBanner';
import Box from '@mui/material/Box';
import INeedHelp from './INeedHelp';
-
+import BCEIDLogin from './BCEIDLogin';
const IndividualLogin = () => {
return (
@@ -15,7 +15,7 @@ const IndividualLogin = () => {
BC Driver’s Licence
Used to connect you with your rebate.
- BC Services Card app or Basic BCeID
+ BC Services Card app
Used to confirm your identity.
The{' '}
@@ -24,17 +24,7 @@ const IndividualLogin = () => {
is the simplest method to log in and confirm your identity.
-
- You can also log in with a{' '}
-
- Basic BCeID account
-
- . If you log in with BCeID you will need to upload images of your BC
- Driver’s Licence and a secondary piece of ID.{' '}
-
- Learn more about ID requirements.
-
-
+
Household applications
For a household application your spouse or common law partner will
@@ -43,9 +33,10 @@ const IndividualLogin = () => {
+
{
formState: { errors },
setValue
} = methods;
+ const [DOB, setDOB] = useState(new Date());
const axiosInstance = useAxios();
const queryFn = () =>
@@ -82,6 +86,12 @@ const SpouseForm = ({ id, setNumberOfErrors, setErrorsExistCounter }) => {
const onSubmit = (data) => {
setNumberOfErrors(0);
setLoading(true);
+ if (kcToken.identity_provider !== 'bcsc') {
+ data = {
+ ...data,
+ date_of_birth: data.date_of_birth.toISOString().slice(0, 10)
+ };
+ }
mutation.mutate(data, {
onSuccess: (data, variables, context) => {
let refinedData = data.data;
@@ -118,7 +128,7 @@ const SpouseForm = ({ id, setNumberOfErrors, setErrorsExistCounter }) => {