Fixed notification issue on offline charging stations
MODIFY
src/notification/NotificationHandler.ts
src/notification/NotificationHandler.ts
@@ -32,7 +32,7 @@ export default class NotificationHandler {
];
static async saveNotification(tenantID: string, channel: string, notificationID: string,
- sourceDescr: ServerAction, user?: User, chargingStation?: ChargingStation, notificationData?: object): Promise<void> {
+ sourceDescr: ServerAction, user?: User, chargingStation?: ChargingStation, notificationData?: any): Promise<void> {
// Save it
await NotificationStorage.saveNotification(tenantID, {
timestamp: new Date(),
@@ -85,38 +85,23 @@ export default class NotificationHandler {
}
static async hasNotifiedSource(tenantID: string, channel: string, sourceDescr: string, chargeBoxID: string, userID: string,
- interval?: { intervalMins?: number; intervalKey?: object }): Promise<boolean> {
+ interval: { intervalMins: number; additionalFilters?: any }): Promise<boolean> {
try {
// Check
if (interval && interval.intervalMins) {
- const params: any = {
- channel,
- sourceDescr
- };
- if (interval.intervalKey) {
- params.data = interval.intervalKey;
- }
- if (chargeBoxID) {
- params.chargeBoxID = chargeBoxID;
- }
- if (userID) {
- params.userID = userID;
- }
// Save it
const notifications = await NotificationStorage.getNotifications(
- tenantID,
- params,
- Constants.DB_PARAMS_MAX_LIMIT
+ tenantID, {
+ channel,
+ sourceDescr,
+ chargeBoxID,
+ userID,
+ dateFrom: interval.intervalMins ? moment().subtract(interval.intervalMins, 'minute').toDate() : null,
+ additionalFilters: interval.additionalFilters ? interval.additionalFilters : null,
+ },
+ Constants.DB_PARAMS_COUNT_ONLY
);
- // Check
- if (notifications.count > 0) {
- // Get the first one (ordered desc)
- const notification: Notification = notifications.result[0];
- const diffMinutes = moment.duration(moment().diff(moment(notification.timestamp))).asMinutes();
- if (diffMinutes <= interval.intervalMins) {
- return true;
- }
- }
+ return notifications.count > 0;
}
// Default
return false;
@@ -161,7 +146,7 @@ export default class NotificationHandler {
// Check notification
const hasBeenNotified = await NotificationHandler.hasNotifiedSource(
tenantID, notificationSource.channel, ServerAction.END_OF_CHARGE,
- chargingStation.id, user.id, { intervalMins, intervalKey: { transactionId: sourceData.transactionId } });
+ chargingStation.id, user.id, { intervalMins, additionalFilters: { transactionId: sourceData.transactionId } });
if (!hasBeenNotified) {
// Enabled?
if (user.notificationsActive && user.notifications.sendEndOfCharge) {

MODIFY
src/scheduler/tasks/CheckOfflineChargingStationsTask.ts
src/scheduler/tasks/CheckOfflineChargingStationsTask.ts
@@ -19,19 +19,17 @@ export default class CheckOfflineChargingStationsTask extends SchedulerTask {
if (await LockingManager.acquire(offlineChargingStationLock)) {
try {
// Compute the date some minutes ago
- const someMinutesAgo = moment().subtract(
+ const offlineSince = moment().subtract(
Utils.getChargingStationHeartbeatMaxIntervalSecs(), 'seconds').toDate();
- const params = {
+ const chargingStations = await ChargingStationStorage.getChargingStations(tenant.id, {
issuer: true,
- offlineSince: someMinutesAgo
- };
- const chargingStations = await ChargingStationStorage.getChargingStations(tenant.id, params, Constants.DB_PARAMS_MAX_LIMIT);
+ offlineSince
+ }, Constants.DB_PARAMS_MAX_LIMIT);
if (chargingStations.count > 0) {
const chargingStationIDs: string = chargingStations.result.map((chargingStation) => chargingStation.id).join(', ');
// Send notification
await NotificationHandler.sendOfflineChargingStations(
- tenant.id,
- {
+ tenant.id, {
chargeBoxIDs: chargingStationIDs,
evseDashboardURL: Utils.buildEvseURL(tenant.subdomain)
}

MODIFY
src/storage/mongodb/NotificationStorage.ts
src/storage/mongodb/NotificationStorage.ts
@@ -13,7 +13,8 @@ const MODULE_NAME = 'NotificationStorage';
export default class NotificationStorage {
static async getNotifications(tenantID: string,
- params: { userID?: string; dateFrom?: Date; channel?: string; sourceId?: string; sourceDescr?: string; data?: object; chargeBoxID?: string },
+ params: { userID?: string; dateFrom?: Date; channel?: string; sourceId?: string;
+ sourceDescr?: string; additionalFilters?: any; chargeBoxID?: string },
dbParams: DbParams): Promise<DataResult<Notification>> {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getNotifications');
@@ -49,9 +50,9 @@ export default class NotificationStorage {
filters.userID = Utils.convertToObjectID(params.userID);
}
// Set Data
- if (params.data) {
- for (const key in params.data) {
- filters[`data.${key}`] = params.data[key];
+ if (params.additionalFilters) {
+ for (const key in params.additionalFilters) {
+ filters[`data.${key}`] = params.additionalFilters[key];
}
}
// Set SourceId?
@@ -79,31 +80,15 @@ export default class NotificationStorage {
}
// Remove the limit
aggregation.pop();
- // Add Charge Box
- aggregation.push({
- $lookup: {
- from: DatabaseUtils.getCollectionName(tenantID, 'chargingstations'),
- localField: 'chargeBoxID',
- foreignField: '_id',
- as: 'chargeBox'
- }
- });
- // Single Record
- aggregation.push({
- $unwind: { 'path': '$chargeBox', 'preserveNullAndEmptyArrays': true }
+ // Charge Box
+ DatabaseUtils.pushChargingStationLookupInAggregation({
+ tenantID, aggregation: aggregation, localField: 'chargeBoxID', foreignField: '_id',
+ asField: 'chargeBox', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
- // Add User
- aggregation.push({
- $lookup: {
- from: DatabaseUtils.getCollectionName(tenantID, 'users'),
- localField: 'userID',
- foreignField: '_id',
- as: 'user'
- }
- });
- // Single Record
- aggregation.push({
- $unwind: { 'path': '$user', 'preserveNullAndEmptyArrays': true }
+ // Users
+ DatabaseUtils.pushUserLookupInAggregation({
+ tenantID, aggregation: aggregation, asField: 'user', localField: 'userID',
+ foreignField: '_id', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
// Sort
if (!dbParams.sort) {

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -546,34 +546,19 @@ export default class TransactionStorage {
aggregation.push({
$limit: dbParams.limit
});
- // Add Charge Box
+ // Charge Box
DatabaseUtils.pushChargingStationLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- localField: 'chargeBoxID',
- foreignField: '_id',
- asField: 'chargeBox',
- oneToOneCardinality: true,
- oneToOneCardinalityNotNull: false
+ tenantID, aggregation: aggregation, localField: 'chargeBoxID', foreignField: '_id',
+ asField: 'chargeBox', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
- // Add Users
+ // Users
DatabaseUtils.pushUserLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- asField: 'user',
- localField: 'userID',
- foreignField: '_id',
- oneToOneCardinality: true,
- oneToOneCardinalityNotNull: false
+ tenantID, aggregation: aggregation, asField: 'user', localField: 'userID',
+ foreignField: '_id', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
DatabaseUtils.pushUserLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- asField: 'stop.user',
- localField: 'stop.userID',
- foreignField: '_id',
- oneToOneCardinality: true,
- oneToOneCardinalityNotNull: false
+ tenantID, aggregation: aggregation, asField: 'stop.user', localField: 'stop.userID',
+ foreignField: '_id', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
// Rename ID
DatabaseUtils.pushRenameDatabaseIDToNumber(aggregation);
@@ -811,37 +796,21 @@ export default class TransactionStorage {
(params.errorType && params.errorType.includes(TransactionInErrorType.OVER_CONSUMPTION))) {
// Add Charge Box
DatabaseUtils.pushChargingStationLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- localField: 'chargeBoxID',
- foreignField: '_id',
- asField: 'chargeBox',
- oneToOneCardinality: true,
- oneToOneCardinalityNotNull: false,
- pipelineMatch: { 'issuer': true }
+ tenantID, aggregation: aggregation, localField: 'chargeBoxID', foreignField: '_id', asField: 'chargeBox',
+ oneToOneCardinality: true, oneToOneCardinalityNotNull: false, pipelineMatch: { 'issuer': true }
});
}
// Add respective users
DatabaseUtils.pushUserLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- asField: 'user',
- localField: 'userID',
- foreignField: '_id',
- oneToOneCardinality: true,
- oneToOneCardinalityNotNull: false
+ tenantID, aggregation: aggregation, asField: 'user', localField: 'userID',
+ foreignField: '_id', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
// Used only in the error type : missing_user
if (params.errorType && params.errorType.includes(TransactionInErrorType.MISSING_USER)) {
// Site Area
DatabaseUtils.pushSiteAreaLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- localField: 'siteAreaID',
- foreignField: '_id',
- asField: 'siteArea',
- oneToOneCardinality: true,
- objectIDFields: ['createdBy', 'lastChangedBy']
+ tenantID, aggregation: aggregation, localField: 'siteAreaID', foreignField: '_id',
+ asField: 'siteArea', oneToOneCardinality: true, objectIDFields: ['createdBy', 'lastChangedBy']
});
}
// Build facets for each type of error if any
@@ -860,14 +829,10 @@ export default class TransactionStorage {
// Add a unique identifier as we may have the same Charging Station several time
aggregation.push({ $addFields: { 'uniqueId': { $concat: [{ $substr: ['$_id', 0, -1] }, '#', '$errorCode'] } } });
}
+ // Users
DatabaseUtils.pushUserLookupInAggregation({
- tenantID,
- aggregation: aggregation,
- asField: 'stop.user',
- localField: 'stop.userID',
- foreignField: '_id',
- oneToOneCardinality: true,
- oneToOneCardinalityNotNull: false
+ tenantID, aggregation: aggregation, asField: 'stop.user', localField: 'stop.userID',
+ foreignField: '_id', oneToOneCardinality: true, oneToOneCardinalityNotNull: false
});
// Rename ID
DatabaseUtils.pushRenameDatabaseIDToNumber(aggregation);

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
-import { OCPPAttribute, OCPPLocation, OCPPMeasurand, OCPPPhase, OCPPReadingContext, OCPPUnitOfMeasure, OCPPValueFormat } from '../types/ocpp/OCPPServer';
-
import { OcppParameter } from '../types/ChargingStation';
+import DbParams from '../types/database/DbParams';
+import { OCPPAttribute, OCPPLocation, OCPPMeasurand, OCPPPhase, OCPPReadingContext, OCPPUnitOfMeasure, OCPPValueFormat } from '../types/ocpp/OCPPServer';
import Tenant from '../types/Tenant';
+
export default class Constants {
public static readonly CSV_SEPARATOR = '\t'; // Cannot store regex in enum
@@ -10,9 +11,9 @@ export default class Constants {
public static readonly DB_RECORD_COUNT_CEIL = 2000;
public static readonly DB_RECORD_COUNT_NO_LIMIT = Number.MAX_SAFE_INTEGER;
- public static readonly DB_PARAMS_MAX_LIMIT = { limit: Constants.DB_RECORD_COUNT_NO_LIMIT, skip: 0 };
- public static readonly DB_PARAMS_SINGLE_RECORD = { limit: 1, skip: 0 };
- public static readonly DB_PARAMS_COUNT_ONLY = { limit: Constants.DB_RECORD_COUNT_NO_LIMIT, skip: 0, onlyRecordCount: true };
+ public static readonly DB_PARAMS_MAX_LIMIT: DbParams = { limit: Constants.DB_RECORD_COUNT_NO_LIMIT, skip: 0, sort: null };
+ public static readonly DB_PARAMS_SINGLE_RECORD: DbParams = { limit: 1, skip: 0, sort: null };
+ public static readonly DB_PARAMS_COUNT_ONLY: DbParams = { limit: Constants.DB_RECORD_COUNT_NO_LIMIT, skip: 0, onlyRecordCount: true, sort: null };
public static readonly DEFAULT_TENANT = 'default';
public static readonly DEFAULT_TENANT_OBJECT= {

Fixed notification issue on offline charging stations