feat(subaccounts): notify the user when the subaccount is activated
MODIFY
src/assets/i18n/en.json
src/assets/i18n/en.json
"title": "Billing sub-account",
"body": "You have been invited to create a billing sub-account. Please check your email to complete the process"
},
+ "billingSubAccountActivated": {
+ "title": "Billing sub-account activated",
+ "body": "Your billing sub-account has been activated"
+ },
"endUserErrorNotification": {
"title": "A User Reported an Error",
"body": "'{{userName}}' reported: '{{errorTitle}}' - '{{errorDescription}}' in organization '{{tenantName}}'"

ADD
src/assets/server/notification/email/en/billing-sub-account-activated.json
+{
+ "subject": "Billing - Sub-account activated",
+ "tenant": "<%- tenantID %>",
+ "baseURL": "<%- evseDashboardURL %>",
+ "body": {
+ "header": {
+ "title": "Billing - Sub-account activated",
+ "image": {
+ "left": {
+ "height": 60,
+ "url": "<%- tenantLogoURL %>"
+ },
+ "right": {
+ "height": 50,
+ "url": "<%- evseDashboardURL %>/assets/img/info.png"
+ }
+ }
+ },
+ "beforeActionLines": [
+ "Dear <%- (user.firstName ? user.firstName : user.name) %>,",
+ "",
+ "Your billing sub-account has been activated."
+ ],
+ "afterActionLines": [
+ "Best Regards,",
+ "EV Admins."
+ ],
+ "footer": {
+ }
+ }
+}

MODIFY
src/notification/NotificationHandler.ts
src/notification/NotificationHandler.ts
import User, { UserRole } from '../types/User';
-import UserNotifications, { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, NotificationSource, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserNotificationKeys, VerificationEmailNotification } from '../types/UserNotifications';
+import UserNotifications, { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingSubAccountActivationNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, NotificationSource, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserNotificationKeys, VerificationEmailNotification } from '../types/UserNotifications';
import ChargingStation from '../types/ChargingStation';
import Configuration from '../utils/Configuration';
@@ -1395,6 +1395,34 @@ export default class NotificationHandler {
}
}
+ public static async sendBillingSubAccountActivationNotification(tenant: Tenant, notificationID: string, user: User,
+ sourceData: BillingSubAccountActivationNotification): Promise<void> {
+ if (tenant.id !== Constants.DEFAULT_TENANT_ID) {
+ // Get the Tenant logo
+ if (Utils.isNullOrUndefined(tenant.logo) || tenant.logo === '') {
+ const tenantLogo = await TenantStorage.getTenantLogo(tenant);
+ tenant.logo = tenantLogo.logo;
+ }
+ sourceData.tenantLogoURL = tenant.logo;
+ // For each Sources
+ for (const notificationSource of NotificationHandler.notificationSources) {
+ // Active?
+ if (notificationSource.enabled) {
+ try {
+ // Save
+ await NotificationHandler.saveNotification(
+ tenant, notificationSource.channel, notificationID, ServerAction.BILLING_ACTIVATE_SUB_ACCOUNT, { user });
+ // Send
+ void notificationSource.notificationTask.sendBillingSubAccountActivationNotification(
+ sourceData, user, tenant, NotificationSeverity.INFO);
+ } catch (error) {
+ await Logging.logActionExceptionMessage(tenant.id, ServerAction.BILLING_ACTIVATE_SUB_ACCOUNT, error);
+ }
+ }
+ }
+ }
+ }
+
private static async saveNotification(tenant: Tenant, channel: string, notificationID: string, sourceDescr: ServerAction,
extraParams: { user?: User, chargingStation?: ChargingStation, notificationData?: any } = {}): Promise<void> {
// Save it

MODIFY
src/notification/NotificationTask.ts
src/notification/NotificationTask.ts
-import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../types/UserNotifications';
+import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingSubAccountActivationNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../types/UserNotifications';
import Tenant from '../types/Tenant';
import User from '../types/User';
@@ -34,6 +34,7 @@ export default interface NotificationTask {
sendBillingNewInvoicePaid(data: BillingNewInvoiceNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;
sendBillingNewInvoiceOpen(data: BillingNewInvoiceNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;
sendBillingSubAccountCreationLink(data: BillingSubAccountCreationLinkNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;
+ sendBillingSubAccountActivationNotification(data: BillingSubAccountActivationNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;
sendAccountVerificationNotification(data: AccountVerificationNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;
sendAdminAccountVerificationNotification(data: AdminAccountVerificationNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;
sendUserCreatePassword(data: UserCreatePassword, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void>;

MODIFY
src/notification/email/EMailNotificationTask.ts
src/notification/email/EMailNotificationTask.ts
-import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
+import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingSubAccountActivationNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
import { Message, SMTPClient, SMTPError } from 'emailjs';
import BackendError from '../../exception/BackendError';
import Configuration from '../../utils/Configuration';
import Constants from '../../utils/Constants';
import EmailConfiguration from '../../types/configuration/EmailConfiguration';
+import { HttpBillingActivateSubAccount } from '../../types/requests/HttpBillingRequest';
import Logging from '../../utils/Logging';
import NotificationTask from '../NotificationTask';
import { ServerAction } from '../../types/Server';
@@ -144,6 +145,10 @@ export default class EMailNotificationTask implements NotificationTask {
return this.prepareAndSendEmail('billing-sub-account-created', data, user, tenant, severity);
}
+ public async sendBillingSubAccountActivationNotification(data: BillingSubAccountActivationNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void> {
+ return this.prepareAndSendEmail('billing-sub-account-activated', data, user, tenant, severity);
+ }
+
// TODO : Delete sendBillingNewInvoice ??
public async sendBillingNewInvoice(data: BillingNewInvoiceNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void> {
return this.prepareAndSendEmail('billing-new-invoice', data, user, tenant, severity);

MODIFY
src/notification/remote-push-notification/RemotePushNotificationTask.ts
src/notification/remote-push-notification/RemotePushNotificationTask.ts
-import { AccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingPeriodicOperationFailedNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserNotificationType, VerificationEmailNotification } from '../../types/UserNotifications';
+import { AccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingPeriodicOperationFailedNotification, BillingSubAccountActivationNotification, BillingSubAccountCreationLinkNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserNotificationType, VerificationEmailNotification } from '../../types/UserNotifications';
import User, { UserStatus } from '../../types/User';
import Configuration from '../../utils/Configuration';
@@ -353,6 +353,18 @@ export default class RemotePushNotificationTask implements NotificationTask {
title, body, user, { 'onboardingLink': data.onboardingLink }, severity);
}
+ public async sendBillingSubAccountActivationNotification(data: BillingSubAccountActivationNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void> {
+ // Set the locale
+ const i18nManager = I18nManager.getInstanceForLocale(user.locale);
+ // Get Message Text
+ const title = i18nManager.translate('notifications.billingSubAccountActivated.title');
+ const body = i18nManager.translate('notifications.billingSubAccountActivated.body',
+ { tenantName: tenant.name });
+ // Send Notification
+ return this.sendRemotePushNotificationToUser(tenant, UserNotificationType.BILLING_SUB_ACCOUNT_ACTIVATED,
+ title, body, user, {}, severity);
+ }
+
public async sendComputeAndApplyChargingProfilesFailed(data: ComputeAndApplyChargingProfilesFailedNotification,
user: User, tenant: Tenant, severity: NotificationSeverity): Promise<void> {
// Set the locale

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -551,10 +551,12 @@ export default class BillingService {
// Activate and save the sub account
subAccount.pending = false;
await BillingStorage.saveSubAccount(req.tenant, subAccount);
-
- // Notify the user by mail
- // void NotificationHandler.sendBillingSubAccountCreationLink(
- // req.tenant, Utils.generateUUID(), user, { onboardingLink: subAccount.activationLink, evseDashboardURL: Utils.buildEvseURL(req.tenant.subdomain), user });
+ // Get the sub account owner
+ const user = await UserStorage.getUser(req.tenant, subAccount.userID);
+ UtilsService.assertObjectExists(action, user, `User ID '${subAccount.userID}' does not exist`, MODULE_NAME, 'handleActivateSubAccount', req.user);
+ // Notify the user
+ void NotificationHandler.sendBillingSubAccountActivationNotification(
+ req.tenant, Utils.generateUUID(), user, { evseDashboardURL: Utils.buildEvseURL(req.tenant.subdomain), user });
res.status(StatusCodes.OK).json(subAccount);
next();
}

MODIFY
src/types/UserNotifications.ts
src/types/UserNotifications.ts
@@ -72,6 +72,7 @@ export enum UserNotificationType {
BILLING_PERIODIC_OPERATION_FAILED = 'BillingPeriodicOperationFailed',
BILLING_NEW_INVOICE = 'BillingNewInvoice',
BILLING_CREATE_SUB_ACCOUNT = 'BillingCreateSubAccount',
+ BILLING_SUB_ACCOUNT_ACTIVATED = 'BillingSubAccountActivated',
CAR_CATALOG_SYNCHRONIZATION_FAILED = 'CarCatalogSynchronizationFailed',
CHECK_AND_APPLY_SMART_CHARGING_FAILED = 'ComputeAndApplyChargingProfilesFailed',
SESSION_NOT_STARTED_AFTER_AUTHORIZE = 'SessionNotStartedAfterAuthorize',
@@ -302,6 +303,11 @@ export interface BillingSubAccountCreationLinkNotification extends BaseNotificat
user: User;
}
+export interface BillingSubAccountActivationNotification extends BaseNotification {
+ evseDashboardURL: string;
+ user: User;
+}
+
export interface CarCatalogSynchronizationFailedNotification extends BaseNotification {
nbrCarsInError: number;
evseDashboardURL: string;

feat(subaccounts): notify the user when the subaccount is activated