Added Start/Stop notif in OCPI eMSP Service
MODIFY
src/server/ocpi/service/OCPIUtilsService.ts
src/server/ocpi/service/OCPIUtilsService.ts
@@ -18,6 +18,7 @@ import DbParams from '../../../types/database/DbParams';
import { HTTPError } from '../../../types/HTTPError';
import Logging from '../../../utils/Logging';
import LoggingHelper from '../../../utils/LoggingHelper';
+import NotificationHelper from '../../../utils/NotificationHelper';
import { OCPIBusinessDetails } from '../../../types/ocpi/OCPIBusinessDetails';
import { OCPICdr } from '../../../types/ocpi/OCPICdr';
import { OCPIResponse } from '../../../types/ocpi/OCPIResponse';
@@ -291,7 +292,9 @@ export default class OCPIUtilsService {
}
}
- public static async processEmspTransactionFromSession(tenant: Tenant, session: OCPISession, action: ServerAction, transaction?: Transaction): Promise<void> {
+ public static async processEmspTransactionFromSession(tenant: Tenant, session: OCPISession, action: ServerAction,
+ transaction?: Transaction, user?: User): Promise<void> {
+ let newTransaction = false;
if (!OCPIUtilsService.validateEmspSession(session)) {
throw new AppError({
module: MODULE_NAME, method: 'processEmspTransactionFromSession', action,
@@ -310,16 +313,29 @@ export default class OCPIUtilsService {
}
// Get Transaction
if (!transaction) {
- transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, session.id);
+ transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, session.id, { withUser: true });
+ user = transaction?.user;
}
// Check the CDR
- if (transaction && transaction.ocpiData?.cdr?.id) {
+ if (transaction?.ocpiData?.cdr?.id) {
await Logging.logWarning({
...LoggingHelper.getTransactionProperties(transaction),
tenantID: tenant.id,
actionOnUser: transaction.userID,
module: MODULE_NAME, method: 'processEmspTransactionFromSession', action,
- message: `Transaction ID '${transaction.id}' already has a CDR and will not be updated with OCPI Session`,
+ message: `Transaction ID '${transaction.id}' has already a CDR`,
+ detailedMessages: { session, transaction }
+ });
+ return;
+ }
+ // Check the Session Status
+ if (transaction?.ocpiData?.session?.status === OCPISessionStatus.COMPLETED) {
+ await Logging.logWarning({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ tenantID: tenant.id,
+ actionOnUser: transaction.userID,
+ module: MODULE_NAME, method: 'processEmspTransactionFromSession', action,
+ message: `Transaction ID '${transaction.id}' has already been completed`,
detailedMessages: { session, transaction }
});
return;
@@ -345,6 +361,7 @@ export default class OCPIUtilsService {
}
// Create Transaction
if (!transaction) {
+ newTransaction = true;
// Get the Tag
const tag = await TagStorage.getTag(tenant, session.auth_id, { withUser: true });
if (!tag) {
@@ -358,7 +375,7 @@ export default class OCPIUtilsService {
});
}
// Get User
- const user = tag.user;
+ user = tag.user;
if (!user) {
throw new AppError({
...LoggingHelper.getChargingStationProperties(chargingStation),
@@ -464,9 +481,13 @@ export default class OCPIUtilsService {
inactivityStatus: transaction.currentInactivityStatus,
userID: transaction.userID
};
+ NotificationHelper.notifyStopTransaction(tenant, chargingStation, transaction, user);
}
await TransactionStorage.saveTransaction(tenant, transaction);
await OCPPUtils.updateChargingStationConnectorRuntimeDataWithTransaction(tenant, chargingStation, transaction, true);
+ if (newTransaction) {
+ NotificationHelper.notifyStartTransaction(tenant, transaction, chargingStation, user);
+ }
}
public static async processEmspCdr(tenant: Tenant, cdr: OCPICdr, action: ServerAction): Promise<void> {

MODIFY
src/server/ocpi/service/emsp/v2.1.1/EMSPSessionsService.ts
src/server/ocpi/service/emsp/v2.1.1/EMSPSessionsService.ts
@@ -96,7 +96,7 @@ export default class EMSPSessionsService {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, sessionID);
+ const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, sessionID, { withUser: true });
if (!transaction) {
throw new AppError({
module: MODULE_NAME, method: 'handlePatchSession', action,
@@ -108,7 +108,7 @@ export default class EMSPSessionsService {
// Merge
_.merge(transaction.ocpiData.session, req.body);
// Update
- await OCPIUtilsService.processEmspTransactionFromSession(tenant, transaction.ocpiData.session, ServerAction.OCPI_EMSP_UPDATE_SESSION, transaction);
+ await OCPIUtilsService.processEmspTransactionFromSession(tenant, transaction.ocpiData.session, ServerAction.OCPI_EMSP_UPDATE_SESSION, transaction, transaction.user);
res.json(OCPIUtils.success({}));
next();
}

MODIFY
src/server/ocpp/services/OCPPService.ts
src/server/ocpp/services/OCPPService.ts
@@ -22,6 +22,7 @@ import LockingManager from '../../../locking/LockingManager';
import Logging from '../../../utils/Logging';
import LoggingHelper from '../../../utils/LoggingHelper';
import NotificationHandler from '../../../notification/NotificationHandler';
+import NotificationHelper from '../../../utils/NotificationHelper';
import OCPIFacade from '../../ocpi/OCPIFacade';
import OCPPCommon from '../utils/OCPPCommon';
import { OCPPHeader } from '../../../types/ocpp/OCPPHeader';
@@ -391,7 +392,7 @@ export default class OCPPService {
// Save
await ChargingStationStorage.saveChargingStation(tenant, chargingStation);
// Notify
- this.notifyStartTransaction(tenant, newTransaction, chargingStation, user);
+ NotificationHelper.notifyStartTransaction(tenant, newTransaction, chargingStation, user);
await Logging.logInfo({
...LoggingHelper.getChargingStationProperties(chargingStation),
tenantID: tenant.id,
@@ -496,7 +497,7 @@ export default class OCPPService {
// Save the transaction
await TransactionStorage.saveTransaction(tenant, transaction);
// Notify
- this.notifyStopTransaction(tenant, chargingStation, transaction, user, alternateUser);
+ NotificationHelper.notifyStopTransaction(tenant, chargingStation, transaction, user, alternateUser);
// Recompute the Smart Charging Plan
await this.triggerSmartChargingStopTransaction(tenant, chargingStation, transaction);
await Logging.logInfo({
@@ -1100,61 +1101,6 @@ export default class OCPPService {
}
}
- private notifyEndOfCharge(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
- if (this.chargingStationConfig.notifEndOfChargeEnabled && transaction.user) {
- // Get the i18n lib
- const i18nManager = I18nManager.getInstanceForLocale(transaction.user.locale);
- // Notify (Async)
- void NotificationHandler.sendEndOfCharge(
- tenant,
- transaction.id.toString() + '-EOC',
- transaction.user,
- chargingStation,
- {
- user: transaction.user,
- transactionId: transaction.id,
- chargeBoxID: chargingStation.id,
- siteID: chargingStation.siteID,
- siteAreaID: chargingStation.siteAreaID,
- companyID: chargingStation.companyID,
- connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
- totalConsumption: i18nManager.formatNumber(Math.round(transaction.currentTotalConsumptionWh / 10) / 100),
- stateOfCharge: transaction.currentStateOfCharge,
- totalDuration: this.transactionDurationToString(transaction),
- evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress'),
- evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
- }
- );
- }
- }
-
- private notifyOptimalChargeReached(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
- if (this.chargingStationConfig.notifBeforeEndOfChargeEnabled && transaction.user) {
- // Get the i18n lib
- const i18nManager = I18nManager.getInstanceForLocale(transaction.user.locale);
- // Notification Before End Of Charge (Async)
- void NotificationHandler.sendOptimalChargeReached(
- tenant,
- transaction.id.toString() + '-OCR',
- transaction.user,
- chargingStation,
- {
- user: transaction.user,
- chargeBoxID: chargingStation.id,
- siteID: chargingStation.siteID,
- siteAreaID: chargingStation.siteAreaID,
- companyID: chargingStation.companyID,
- transactionId: transaction.id,
- connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
- totalConsumption: i18nManager.formatNumber(Math.round(transaction.currentTotalConsumptionWh / 10) / 100),
- stateOfCharge: transaction.currentStateOfCharge,
- evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress'),
- evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
- }
- );
- }
- }
-
private async checkNotificationEndOfCharge(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
// Transaction in progress?
if (!transaction?.stop && transaction.currentTotalConsumptionWh > 0) {
@@ -1163,11 +1109,11 @@ export default class OCPPService {
// Check if battery is full (100%)
if (transaction.currentStateOfCharge === 100) {
// Send Notification
- this.notifyEndOfCharge(tenant, chargingStation, transaction);
+ NotificationHelper.notifyEndOfCharge(tenant, chargingStation, transaction);
// Check if optimal charge has been reached (85%)
} else if (transaction.currentStateOfCharge >= this.chargingStationConfig.notifBeforeEndOfChargePercent) {
// Send Notification
- this.notifyOptimalChargeReached(tenant, chargingStation, transaction);
+ NotificationHelper.notifyOptimalChargeReached(tenant, chargingStation, transaction);
}
// No battery information: check last consumptions
} else {
@@ -1186,7 +1132,7 @@ export default class OCPPService {
consumption.limitAmps >= StaticLimitAmps.MIN_LIMIT_PER_PHASE * Utils.getNumberOfConnectedPhases(chargingStation, null, transaction.connectorId)));
// Send Notification
if (noConsumption) {
- this.notifyEndOfCharge(tenant, chargingStation, transaction);
+ NotificationHelper.notifyEndOfCharge(tenant, chargingStation, transaction);
}
}
}
@@ -1194,33 +1140,6 @@ export default class OCPPService {
}
}
- private transactionInactivityToString(transaction: Transaction, user: User, i18nHourShort = 'h') {
- const i18nManager = I18nManager.getInstanceForLocale(user ? user.locale : Constants.DEFAULT_LANGUAGE);
- // Get total
- const totalInactivitySecs = transaction.stop.totalInactivitySecs;
- // None?
- if (totalInactivitySecs === 0) {
- return `0${i18nHourShort}00 (${i18nManager.formatPercentage(0)})`;
- }
- // Build the inactivity percentage
- const totalInactivityPercent = i18nManager.formatPercentage(Math.round((totalInactivitySecs / transaction.stop.totalDurationSecs) * 100) / 100);
- return moment.duration(totalInactivitySecs, 's').format(`h[${i18nHourShort}]mm`, { trim: false }) + ` (${totalInactivityPercent})`;
- }
-
- private transactionDurationToString(transaction: Transaction): string {
- let totalDuration;
- if (!transaction.stop) {
- totalDuration = moment.duration(moment(transaction.lastConsumption.timestamp).diff(moment(transaction.timestamp))).asSeconds();
- } else {
- totalDuration = moment.duration(moment(transaction.stop.timestamp).diff(moment(transaction.timestamp))).asSeconds();
- }
- return moment.duration(totalDuration, 's').format('h[h]mm', { trim: false });
- }
-
- private buildTransactionDuration(transaction: Transaction): string {
- return moment.duration(transaction.stop.totalDurationSecs, 's').format('h[h]mm', { trim: false });
- }
-
private filterMeterValuesOnSpecificChargingStations(tenant: Tenant, chargingStation: ChargingStation, meterValues: OCPPNormalizedMeterValues) {
// Clean up Sample.Clock meter value
if (chargingStation.chargePointVendor !== ChargerVendor.ABB ||
@@ -1390,28 +1309,6 @@ export default class OCPPService {
} while (activeTransaction);
}
- private notifyStartTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User) {
- if (user) {
- void NotificationHandler.sendSessionStarted(
- tenant,
- transaction.id.toString(),
- user,
- chargingStation,
- {
- 'user': user,
- 'transactionId': transaction.id,
- 'chargeBoxID': chargingStation.id,
- 'siteID': chargingStation.siteID,
- 'siteAreaID': chargingStation.siteAreaID,
- 'companyID': chargingStation.companyID,
- 'connectorId': Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
- 'evseDashboardURL': Utils.buildEvseURL(tenant.subdomain),
- 'evseDashboardChargingStationURL': Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress')
- }
- );
- }
- }
-
private getStopTransactionTagId(stopTransaction: OCPPStopTransactionRequestExtended, transaction: Transaction): string {
// Stopped Remotely?
if (transaction.remotestop) {
@@ -1433,71 +1330,6 @@ export default class OCPPService {
return transaction.tagID;
}
- private notifyStopTransaction(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction, user: User, alternateUser: User) {
- // User provided?
- if (user) {
- // Get the i18n lib
- const i18nManager = I18nManager.getInstanceForLocale(user.locale);
- // Send Notification (Async)
- void NotificationHandler.sendEndOfSession(
- tenant,
- transaction.id.toString() + '-EOS',
- user,
- chargingStation,
- {
- user: user,
- alternateUser: (alternateUser ? alternateUser : null),
- transactionId: transaction.id,
- chargeBoxID: chargingStation.id,
- siteID: chargingStation.siteID,
- siteAreaID: chargingStation.siteAreaID,
- companyID: chargingStation.companyID,
- connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
- totalConsumption: i18nManager.formatNumber(Math.round(transaction.stop.totalConsumptionWh / 10) / 100),
- totalDuration: this.buildTransactionDuration(transaction),
- totalInactivity: this.transactionInactivityToString(transaction, user),
- stateOfCharge: transaction.stop.stateOfCharge,
- evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#history'),
- evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
- }
- );
- // Notify Signed Data
- if (transaction.stop.signedData !== '') {
- // Send Notification (Async)
- void NotificationHandler.sendEndOfSignedSession(
- tenant,
- transaction.id.toString() + '-EOSS',
- user,
- chargingStation,
- {
- user: user,
- alternateUser: (alternateUser ? alternateUser : null),
- transactionId: transaction.id,
- chargeBoxID: chargingStation.id,
- connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
- tagId: transaction.tagID,
- startDate: transaction.timestamp.toLocaleString(user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
- endDate: transaction.stop.timestamp.toLocaleString(user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
- meterStart: (transaction.meterStart / 1000).toLocaleString(
- (user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
- { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }),
- meterStop: (transaction.stop.meterStop / 1000).toLocaleString(
- (user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
- { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }),
- totalConsumption: (transaction.stop.totalConsumptionWh / 1000).toLocaleString(
- (user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
- { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }),
- price: transaction.stop.price,
- relativeCost: (transaction.stop.price / (transaction.stop.totalConsumptionWh / 1000)),
- startSignedData: transaction.signedData,
- endSignedData: transaction.stop.signedData,
- evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
- }
- );
- }
- }
- }
-
private async triggerSmartCharging(tenant: Tenant, chargingStation: ChargingStation) {
// Smart Charging must be active
if (Utils.isTenantComponentActive(tenant, TenantComponents.SMART_CHARGING)) {

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -1075,9 +1075,11 @@ export default class TransactionStorage {
return transactionsMDB.count === 1 ? transactionsMDB.result[0] : null;
}
- public static async getOCPITransactionBySessionID(tenant: Tenant, sessionID: string): Promise<Transaction> {
+ public static async getOCPITransactionBySessionID(tenant: Tenant, sessionID: string,
+ params: { withUser?: boolean } = {}): Promise<Transaction> {
const transactionsMDB = await TransactionStorage.getTransactions(tenant,
{
+ withUser: params.withUser,
ocpiSessionID: sessionID
}, Constants.DB_PARAMS_SINGLE_RECORD);
return transactionsMDB.count === 1 ? transactionsMDB.result[0] : null;

ADD
src/utils/NotificationHelper.ts
+import ChargingStation from '../types/ChargingStation';
+import Constants from './Constants';
+import I18nManager from './I18nManager';
+import NotificationHandler from '../notification/NotificationHandler';
+import Tenant from '../types/Tenant';
+import Transaction from '../types/Transaction';
+import User from '../types/User';
+import Utils from './Utils';
+
+export default class NotificationHelper {
+ public static notifyStartTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User) {
+ if (user) {
+ void NotificationHandler.sendSessionStarted(
+ tenant,
+ transaction.id.toString(),
+ user,
+ chargingStation,
+ {
+ 'user': user,
+ 'transactionId': transaction.id,
+ 'chargeBoxID': chargingStation.id,
+ 'siteID': chargingStation.siteID,
+ 'siteAreaID': chargingStation.siteAreaID,
+ 'companyID': chargingStation.companyID,
+ 'connectorId': Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
+ 'evseDashboardURL': Utils.buildEvseURL(tenant.subdomain),
+ 'evseDashboardChargingStationURL': Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress')
+ }
+ );
+ }
+ }
+
+ public static notifyEndOfCharge(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
+ if (transaction.user) {
+ // Get the i18n lib
+ const i18nManager = I18nManager.getInstanceForLocale(transaction.user.locale);
+ // Notify (Async)
+ void NotificationHandler.sendEndOfCharge(
+ tenant,
+ transaction.id.toString() + '-EOC',
+ transaction.user,
+ chargingStation,
+ {
+ user: transaction.user,
+ transactionId: transaction.id,
+ chargeBoxID: chargingStation.id,
+ siteID: chargingStation.siteID,
+ siteAreaID: chargingStation.siteAreaID,
+ companyID: chargingStation.companyID,
+ connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
+ totalConsumption: i18nManager.formatNumber(Math.round(transaction.currentTotalConsumptionWh / 10) / 100),
+ stateOfCharge: transaction.currentStateOfCharge,
+ totalDuration: Utils.transactionDurationToString(transaction),
+ evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress'),
+ evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
+ }
+ );
+ }
+ }
+
+ public static notifyOptimalChargeReached(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
+ if (transaction.user) {
+ // Get the i18n lib
+ const i18nManager = I18nManager.getInstanceForLocale(transaction.user.locale);
+ // Notification Before End Of Charge (Async)
+ void NotificationHandler.sendOptimalChargeReached(
+ tenant,
+ transaction.id.toString() + '-OCR',
+ transaction.user,
+ chargingStation,
+ {
+ user: transaction.user,
+ chargeBoxID: chargingStation.id,
+ siteID: chargingStation.siteID,
+ siteAreaID: chargingStation.siteAreaID,
+ companyID: chargingStation.companyID,
+ transactionId: transaction.id,
+ connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
+ totalConsumption: i18nManager.formatNumber(Math.round(transaction.currentTotalConsumptionWh / 10) / 100),
+ stateOfCharge: transaction.currentStateOfCharge,
+ evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress'),
+ evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
+ }
+ );
+ }
+ }
+
+ public static notifyStopTransaction(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction, user: User, alternateUser?: User) {
+ // User provided?
+ if (user) {
+ // Get the i18n lib
+ const i18nManager = I18nManager.getInstanceForLocale(user.locale);
+ // Send Notification (Async)
+ void NotificationHandler.sendEndOfSession(
+ tenant,
+ transaction.id.toString() + '-EOS',
+ user,
+ chargingStation,
+ {
+ user: user,
+ alternateUser: alternateUser ?? null,
+ transactionId: transaction.id,
+ chargeBoxID: chargingStation.id,
+ siteID: chargingStation.siteID,
+ siteAreaID: chargingStation.siteAreaID,
+ companyID: chargingStation.companyID,
+ connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
+ totalConsumption: i18nManager.formatNumber(Math.round(transaction.stop.totalConsumptionWh / 10) / 100),
+ totalDuration: Utils.transactionDurationToString(transaction),
+ totalInactivity: Utils.transactionInactivityToString(transaction, user),
+ stateOfCharge: transaction.stop.stateOfCharge,
+ evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#history'),
+ evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
+ }
+ );
+ // Notify Signed Data
+ if (transaction.stop.signedData !== '') {
+ // Send Notification (Async)
+ void NotificationHandler.sendEndOfSignedSession(
+ tenant,
+ transaction.id.toString() + '-EOSS',
+ user,
+ chargingStation,
+ {
+ user: user,
+ alternateUser: (alternateUser ? alternateUser : null),
+ transactionId: transaction.id,
+ chargeBoxID: chargingStation.id,
+ connectorId: Utils.getConnectorLetterFromConnectorID(transaction.connectorId),
+ tagId: transaction.tagID,
+ startDate: transaction.timestamp.toLocaleString(user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
+ endDate: transaction.stop.timestamp.toLocaleString(user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
+ meterStart: (transaction.meterStart / 1000).toLocaleString(
+ (user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
+ { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }),
+ meterStop: (transaction.stop.meterStop / 1000).toLocaleString(
+ (user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
+ { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }),
+ totalConsumption: (transaction.stop.totalConsumptionWh / 1000).toLocaleString(
+ (user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-')),
+ { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }),
+ price: transaction.stop.price,
+ relativeCost: (transaction.stop.price / (transaction.stop.totalConsumptionWh / 1000)),
+ startSignedData: transaction.signedData,
+ endSignedData: transaction.stop.signedData,
+ evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
+ }
+ );
+ }
+ }
+ }
+}

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -16,6 +16,7 @@ import Configuration from './Configuration';
import ConnectorStats from '../types/ConnectorStats';
import Constants from './Constants';
import { Decimal } from 'decimal.js';
+import I18nManager from './I18nManager';
import { Promise } from 'bluebird';
import QRCode from 'qrcode';
import { Request } from 'express';
@@ -1674,4 +1675,27 @@ export default class Utils {
public static hash(data: string): string {
return crypto.createHash('sha256').update(data).digest('hex');
}
+
+ public static transactionDurationToString(transaction: Transaction): string {
+ let totalDurationSecs;
+ if (transaction.stop) {
+ totalDurationSecs = moment.duration(moment(transaction.stop.timestamp).diff(moment(transaction.timestamp))).asSeconds();
+ } else {
+ totalDurationSecs = moment.duration(moment(transaction.lastConsumption.timestamp).diff(moment(transaction.timestamp))).asSeconds();
+ }
+ return moment.duration(totalDurationSecs, 's').format('h[h]mm', { trim: false });
+ }
+
+ public static transactionInactivityToString(transaction: Transaction, user: User, i18nHourShort = 'h') {
+ const i18nManager = I18nManager.getInstanceForLocale(user ? user.locale : Constants.DEFAULT_LANGUAGE);
+ // Get total
+ const totalInactivitySecs = transaction.stop.totalInactivitySecs;
+ // None?
+ if (totalInactivitySecs === 0) {
+ return `0${i18nHourShort}00 (${i18nManager.formatPercentage(0)})`;
+ }
+ // Build the inactivity percentage
+ const totalInactivityPercent = i18nManager.formatPercentage(Math.round((totalInactivitySecs / transaction.stop.totalDurationSecs) * 100) / 100);
+ return moment.duration(totalInactivitySecs, 's').format(`h[${i18nHourShort}]mm`, { trim: false }) + ` (${totalInactivityPercent})`;
+ }
}

Added Start/Stop notif in OCPI eMSP Service