Stats typing, optimizing db, polishing
MODIFY
src/server/rest/v1/service/StatisticService.ts
src/server/rest/v1/service/StatisticService.ts
-import { Action, Entity } from '../../../../types/Authorization';
import { NextFunction, Request, Response } from 'express';
-import StatisticFilter, { StatsGroupBy } from '../../../../types/Statistic';
-
-import AppAuthError from '../../../../exception/AppAuthError';
+import moment from 'moment';
import Authorizations from '../../../../authorization/Authorizations';
-import Constants from '../../../../utils/Constants';
+import AppAuthError from '../../../../exception/AppAuthError';
+import StatisticsStorage from '../../../../storage/mongodb/StatisticsStorage';
+import { Action, Entity } from '../../../../types/Authorization';
import { HTTPAuthError } from '../../../../types/HTTPError';
import HttpStatisticsRequest from '../../../../types/requests/HttpStatisticRequest';
import { ServerAction } from '../../../../types/Server';
-import StatisticSecurity from './security/StatisticSecurity';
-import StatisticsStorage from '../../../../storage/mongodb/StatisticsStorage';
+import StatisticFilter, { ChargingStationStats, StatsDataCategory, StatsDataScope, StatsDataType, StatsGroupBy, UserStats } from '../../../../types/Statistic';
import TenantComponents from '../../../../types/TenantComponents';
-import User from '../../../../types/User';
import UserToken from '../../../../types/UserToken';
+import Constants from '../../../../utils/Constants';
import Utils from '../../../../utils/Utils';
+import StatisticSecurity from './security/StatisticSecurity';
import UtilsService from './UtilsService';
-import moment from 'moment';
+
const MODULE_NAME = 'StatisticService';
export default class StatisticService {
- static async handleGetChargingStationConsumptionStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetChargingStationConsumptionStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetChargingStationConsumptionStatistics');
@@ -29,10 +28,8 @@ export default class StatisticService {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
- action: Action.LIST,
- entity: Entity.TRANSACTIONS,
- module: MODULE_NAME,
- method: 'handleGetChargingStationConsumptionStatistics'
+ action: Action.LIST, entity: Entity.TRANSACTIONS,
+ module: MODULE_NAME, method: 'handleGetChargingStationConsumptionStatistics'
});
}
// Filter
@@ -43,13 +40,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getChargingStationStats(
req.user.tenantID, filter, StatsGroupBy.CONSUMPTION);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'C');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.CHARGING_STATION);
// Return
res.json(transactions);
next();
}
- static async handleGetChargingStationUsageStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetChargingStationUsageStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetChargingStationUsageStatistics');
@@ -72,13 +70,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getChargingStationStats(
req.user.tenantID, filter, StatsGroupBy.USAGE);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'C');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.CHARGING_STATION);
// Return
res.json(transactions);
next();
}
- static async handleGetChargingStationInactivityStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetChargingStationInactivityStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetChargingStationInactivityStatistics');
@@ -101,13 +100,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getChargingStationStats(
req.user.tenantID, filter, StatsGroupBy.INACTIVITY);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'C');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.CHARGING_STATION);
// Return
res.json(transactions);
next();
}
- static async handleGetChargingStationTransactionsStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetChargingStationTransactionsStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetChargingStationTransactionsStatistics');
@@ -130,13 +130,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getChargingStationStats(
req.user.tenantID, filter, StatsGroupBy.TRANSACTIONS);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'C');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.CHARGING_STATION);
// Return
res.json(transactions);
next();
}
- static async handleGetChargingStationPricingStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetChargingStationPricingStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetChargingStationPricingStatistics');
@@ -159,13 +160,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getChargingStationStats(
req.user.tenantID, filter, StatsGroupBy.PRICING);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'C');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.CHARGING_STATION);
// Return
res.json(transactions);
next();
}
- static async handleGetUserConsumptionStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetUserConsumptionStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetUserConsumptionStatistics');
@@ -188,13 +190,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getUserStats(
req.user.tenantID, filter, StatsGroupBy.CONSUMPTION);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'U');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.USER);
// Return
res.json(transactions);
next();
}
- static async handleGetUserUsageStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetUserUsageStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetUserUsageStatistics');
@@ -217,13 +220,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getUserStats(
req.user.tenantID, filter, StatsGroupBy.USAGE);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'U');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.USER);
// Return
res.json(transactions);
next();
}
- static async handleGetUserInactivityStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetUserInactivityStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetUserInactivityStatistics');
@@ -246,13 +250,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getUserStats(
req.user.tenantID, filter, StatsGroupBy.INACTIVITY);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'U');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.USER);
// Return
res.json(transactions);
next();
}
- static async handleGetUserTransactionsStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetUserTransactionsStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetUserTransactionsStatistics');
@@ -275,13 +280,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getUserStats(
req.user.tenantID, filter, StatsGroupBy.TRANSACTIONS);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'U');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.USER);
// Return
res.json(transactions);
next();
}
- static async handleGetUserPricingStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleGetUserPricingStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleGetUserPricingStatistics');
@@ -304,13 +310,14 @@ export default class StatisticService {
const transactionStats = await StatisticsStorage.getUserStats(
req.user.tenantID, filter, StatsGroupBy.PRICING);
// Convert
- const transactions = StatisticService.convertToGraphData(transactionStats, 'U');
+ const transactions = StatisticService.convertToGraphData(
+ transactionStats, StatsDataCategory.USER);
// Return
res.json(transactions);
next();
}
- static async handleExportStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ static async handleExportStatistics(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.STATISTICS,
Action.LIST, Entity.TRANSACTIONS, MODULE_NAME, 'handleExportStatistics');
@@ -332,32 +339,31 @@ export default class StatisticService {
// Decisions
let groupBy: string;
switch (filteredRequest.DataType) {
- case 'Consumption':
+ case StatsDataType.CONSUMPTION:
groupBy = StatsGroupBy.CONSUMPTION;
break;
- case 'Usage':
+ case StatsDataType.USAGE:
groupBy = StatsGroupBy.USAGE;
break;
- case 'Inactivity':
+ case StatsDataType.INACTIVITY:
groupBy = StatsGroupBy.INACTIVITY;
break;
- case 'Transactions':
+ case StatsDataType.TRANSACTION:
groupBy = StatsGroupBy.TRANSACTIONS;
break;
- case 'Pricing':
+ case StatsDataType.PRICING:
groupBy = StatsGroupBy.PRICING;
break;
default:
groupBy = StatsGroupBy.CONSUMPTION;
}
- let method: string;
- if (filteredRequest.DataCategory === 'C') {
- method = 'getChargingStationStats';
+ // Query data
+ let transactionStats: ChargingStationStats[] | UserStats[];
+ if (filteredRequest.DataCategory === StatsDataCategory.CHARGING_STATION) {
+ transactionStats = await StatisticsStorage.getChargingStationStats(req.user.tenantID, filter, groupBy);
} else {
- method = 'getUserStats';
+ transactionStats = await StatisticsStorage.getUserStats(req.user.tenantID, filter, groupBy);
}
- // Query data
- const transactionStats = await StatisticsStorage[method](req.user.tenantID, filter, groupBy);
// Set the attachement name
res.attachment('exported-' + filteredRequest.DataType.toLowerCase() + '-statistics.csv');
// Build the result
@@ -413,8 +419,8 @@ export default class StatisticService {
return filter;
}
- static convertToGraphData(transactionStats: any[], dataCategory: string) {
- const transactions = [];
+ static convertToGraphData(transactionStats: ChargingStationStats[] | UserStats[], dataCategory: string): any[] {
+ const transactions: Record<string, number>[] = [];
// Create
if (transactionStats && transactionStats.length > 0) {
// Create
@@ -424,28 +430,28 @@ export default class StatisticService {
let userName: string;
for (const transactionStat of transactionStats) {
// Init
- if (transactionStat._id.unit && (unit !== transactionStat._id.unit)) {
+ if (transactionStat.unit && (unit !== transactionStat.unit)) {
// Set
- month = transactionStat._id.month;
- unit = transactionStat._id.unit;
+ month = transactionStat.month;
+ unit = transactionStat.unit;
// Create new
transaction = {};
- transaction.month = transactionStat._id.month - 1;
- transaction.unit = transactionStat._id.unit;
+ transaction.month = transactionStat.month - 1;
+ transaction.unit = transactionStat.unit;
// Add
if (transaction) {
transactions.push(transaction);
}
}
- if (month !== transactionStat._id.month) {
+ if (month !== transactionStat.month) {
// Set
- month = transactionStat._id.month;
+ month = transactionStat.month;
// Create new
transaction = {};
- transaction.month = transactionStat._id.month - 1;
- if (transactionStat._id.unit) {
- unit = transactionStat._id.unit;
- transaction.unit = transactionStat._id.unit;
+ transaction.month = transactionStat.month - 1;
+ if (transactionStat.unit) {
+ unit = transactionStat.unit;
+ transaction.unit = transactionStat.unit;
}
// Add
if (transaction) {
@@ -453,91 +459,95 @@ export default class StatisticService {
}
}
// Set key figure (total)
- if (dataCategory === 'C') {
- transaction[transactionStat._id.chargeBox] = transactionStat.total;
+ if (dataCategory === StatsDataCategory.CHARGING_STATION) {
+ const chargingStationStats = transactionStat as ChargingStationStats;
+ transaction[chargingStationStats.chargeBox] = chargingStationStats.total;
} else {
+ const userStats = transactionStat as UserStats;
// We can have duplicate user names, like 'Unknown'
- userName = Utils.buildUserFullName(transactionStat.user, false, false);
+ userName = Utils.buildUserFullName(userStats.user, false, false);
if (userName in transaction) {
- transaction[userName] += transactionStat.total;
+ transaction[userName] += userStats.total;
} else {
- transaction[userName] = transactionStat.total;
+ transaction[userName] = userStats.total;
}
- // User names are not sorted, but this is not needed for the current charts (separate/different sorting)
}
}
}
return transactions;
}
- static getPricingCell(transaction: any, numberOfTransaction: number): string[] {
- if (transaction._id.unit) {
- return [numberOfTransaction.toString(), transaction._id.unit];
+ static getPricingCell(transaction: ChargingStationStats | UserStats, numberOfTransactions: number): string[] {
+ if (transaction.unit) {
+ return [numberOfTransactions.toString(), transaction.unit];
}
- return [numberOfTransaction.toString(), ' '];
+ return [numberOfTransactions.toString(), ' '];
}
// Build header row
- static getYearAndMonthCells(year: number | string, dataScope?: string) : string {
+ static getYearAndMonthCells(year: number | string, dataScope?: StatsDataScope) : string {
if (year && year !== '0') {
- const yearHeader = 'year';
- if (dataScope && dataScope === 'month') {
- return [yearHeader, 'month'].join(Constants.CSV_SEPARATOR);
+ const yearHeader = StatsDataScope.YEAR;
+ if (dataScope === StatsDataScope.MONTH) {
+ return [yearHeader, StatsDataScope.MONTH].join(Constants.CSV_SEPARATOR);
}
return yearHeader;
}
}
// Build dataType cells
- static getdataTypeCells = (dataType: string) : string => {
+ static getDataTypeCells = (dataType: StatsDataType) : string => {
switch (dataType) {
- case 'Consumption':
+ case StatsDataType.CONSUMPTION:
return 'consumption';
- case 'Usage':
+ case StatsDataType.USAGE:
return 'usage';
- case 'Inactivity':
+ case StatsDataType.INACTIVITY:
return 'inactivity';
- case 'Transactions':
+ case StatsDataType.TRANSACTION:
return 'numberOfSessions';
- case 'Pricing':
+ case StatsDataType.PRICING:
return ['price', 'priceUnit'].join(Constants.CSV_SEPARATOR);
default:
return '';
}
};
- static convertToCSV(transactionStats: any[], dataCategory: string, dataType: string, year: number | string, dataScope?: string): string {
+ static convertToCSV(transactionStats: ChargingStationStats[] | UserStats[],
+ dataCategory: StatsDataCategory, dataType: StatsDataType, year: number | string, dataScope?: StatsDataScope): string {
const headers = [
- dataCategory === 'C' ? 'chargingStation' : 'user',
+ dataCategory === StatsDataCategory.CHARGING_STATION ? 'chargingStation' : 'user',
StatisticService.getYearAndMonthCells(year, dataScope),
- StatisticService.getdataTypeCells(dataType)
+ StatisticService.getDataTypeCells(dataType)
];
let index: number;
const transactions = [];
if (transactionStats && transactionStats.length > 0) {
for (const transactionStat of transactionStats) {
- if (!year || year === '0' || !dataScope || (dataScope && dataScope !== 'month')) {
+ if (!year || year === '0' || !dataScope || (dataScope && dataScope !== StatsDataScope.MONTH)) {
// Annual or overall values
- transactionStat._id.month = 0;
+ transactionStat.month = 0;
index = -1;
if (transactions && transactions.length > 0) {
- if (dataCategory === 'C') {
+ if (dataCategory === StatsDataCategory.CHARGING_STATION) {
+ const chargingStationStats = transactionStat as ChargingStationStats;
index = transactions.findIndex((record) => {
- if (!record._id.unit || !transactionStat._id.unit) {
- return (record._id.chargeBox === transactionStat._id.chargeBox);
+ if (!record.unit || !transactionStat.unit) {
+ return (record.chargeBox === chargingStationStats.chargeBox);
}
- return ((record._id.chargeBox === transactionStat._id.chargeBox)
- && (record._id.unit === transactionStat._id.unit));
+ return ((record.chargeBox === chargingStationStats.chargeBox)
+ && (record.unit === chargingStationStats.unit));
});
} else {
+ const userStats = transactionStat as UserStats;
index = transactions.findIndex((record) => {
- if (!record._id.unit || !transactionStat._id.unit) {
- return ((record.user.name === transactionStat.user.name)
- && (record.user.firstName === transactionStat.user.firstName));
+ if (!record.unit || !userStats.unit) {
+ return ((record.user.name === userStats.user.name)
+ && (record.user.firstName === userStats.user.firstName));
}
- return ((record.user.name === transactionStat.user.name)
- && (record.user.firstName === transactionStat.user.firstName)
- && (record._id.unit === transactionStat._id.unit));
+ return ((record.user.name === userStats.user.name)
+ && (record.user.firstName === userStats.user.firstName)
+ && (record.unit === userStats.unit));
});
}
}
@@ -546,49 +556,51 @@ export default class StatisticService {
} else {
transactions[index].total += transactionStat.total;
}
- } else if (dataCategory === 'C') {
- transactions.push(transactionStat);
+ } else if (dataCategory === StatsDataCategory.CHARGING_STATION) {
+ const chargingStationStats = transactionStat as ChargingStationStats;
+ transactions.push(chargingStationStats);
} else {
+ const userStats = transactionStat as UserStats;
// Treat duplicate names (like 'Unknown')
index = transactions.findIndex((record) => {
- if (!record._id.unit || !transactionStat._id.unit) {
- return ((record._id.month === transactionStat._id.month)
- && (record.user.name === transactionStat.user.name)
- && (record.user.firstName === transactionStat.user.firstName));
- }
- return ((record._id.month === transactionStat._id.month)
- && (record.user.name === transactionStat.user.name)
- && (record.user.firstName === transactionStat.user.firstName)
- && (record._id.unit === transactionStat._id.unit));
+ if (!record.unit || !userStats.unit) {
+ return ((record.month === userStats.month)
+ && (record.user.name === userStats.user.name)
+ && (record.user.firstName === userStats.user.firstName));
+ }
+ return ((record.month === userStats.month)
+ && (record.user.name === userStats.user.name)
+ && (record.user.firstName === userStats.user.firstName)
+ && (record.unit === userStats.unit));
});
if (index < 0) {
- transactions.push(transactionStat);
+ transactions.push(userStats);
} else {
- transactions[index].total += transactionStat.total;
+ transactions[index].total += userStats.total;
}
}
}
- if (dataCategory === 'C') {
+ if (dataCategory === StatsDataCategory.CHARGING_STATION) {
// Sort by Charging Station and month
transactions.sort((rec1, rec2) => {
- if (rec1._id.chargeBox > rec2._id.chargeBox) {
+ if (rec1.chargeBox > rec2.chargeBox) {
return 1;
}
- if (rec1._id.chargeBox < rec2._id.chargeBox) {
+ if (rec1.chargeBox < rec2.chargeBox) {
return -1;
}
// Charging Station is the same, now compare month
- if (rec1._id.month > rec2._id.month) {
+ if (rec1.month > rec2.month) {
return 1;
}
- if (rec1._id.month < rec2._id.month) {
+ if (rec1.month < rec2.month) {
return -1;
}
- if (rec1._id.unit && rec2._id.unit) {
- if (rec1._id.unit > rec2._id.unit) {
+ if (rec1.unit && rec2.unit) {
+ if (rec1.unit > rec2.unit) {
return 1;
}
- if (rec1._id.unit < rec2._id.unit) {
+ if (rec1.unit < rec2.unit) {
return -1;
}
}
@@ -610,17 +622,17 @@ export default class StatisticService {
return -1;
}
// Name and first name are identical, now compare month
- if (rec1._id.month > rec2._id.month) {
+ if (rec1.month > rec2.month) {
return 1;
}
- if (rec1._id.month < rec2._id.month) {
+ if (rec1.month < rec2.month) {
return -1;
}
- if (rec1._id.unit && rec2._id.unit) {
- if (rec1._id.unit > rec2._id.unit) {
+ if (rec1.unit && rec2.unit) {
+ if (rec1.unit > rec2.unit) {
return 1;
}
- if (rec1._id.unit < rec2._id.unit) {
+ if (rec1.unit < rec2.unit) {
return -1;
}
}
@@ -635,10 +647,10 @@ export default class StatisticService {
// anyway only locale 'en-US' is supported here as could be seen by:
// const supportedLocales = Intl.NumberFormat.supportedLocalesOf(['fr-FR', 'en-US', 'de-DE']);
const row = [
- dataCategory === 'C' ? transaction._id.chargeBox : Utils.buildUserFullName(transaction.user, false),
+ dataCategory === StatsDataCategory.CHARGING_STATION ? transaction.chargeBox : Utils.buildUserFullName(transaction.user, false),
year && year !== '0' ? year : '',
- transaction._id.month > 0 ? transaction._id.month : '',
- dataType === 'Pricing' ? StatisticService.getPricingCell(transaction, numberOfTransaction) : numberOfTransaction.toString()
+ transaction.month > 0 ? transaction.month : '',
+ dataType === StatsDataType.PRICING ? StatisticService.getPricingCell(transaction, numberOfTransaction) : numberOfTransaction.toString()
].map((value) => typeof value === 'string' ? '"' + value.replace(/^"|"$/g, '') + '"' : value);
return row;
}).join(Constants.CR_LF);

MODIFY
src/storage/mongodb/StatisticsStorage.ts
src/storage/mongodb/StatisticsStorage.ts
-import StatisticFilter, { StatsGroupBy } from '../../types/Statistic';
+import StatisticFilter, { ChargingStationStats, StatsGroupBy, UserStats } from '../../types/Statistic';
import DatabaseUtils from './DatabaseUtils';
import Logging from '../../utils/Logging';
@@ -9,58 +9,58 @@ const MODULE_NAME = 'StatisticsStorage';
export default class StatisticsStorage {
- static async getChargingStationStats(tenantID: string, filters: StatisticFilter, groupBy: string): Promise<any[]> {
+ static async getChargingStationStats(tenantID: string, params: StatisticFilter, groupBy: string): Promise<ChargingStationStats[]> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getChargingStationStats');
// Check Tenant
await DatabaseUtils.checkTenant(tenantID);
// Build filter
- const match: any = {};
+ const filters: any = {};
// Date provided?
- if (filters.startDateTime || filters.endDateTime) {
- match.timestamp = {};
+ if (params.startDateTime || params.endDateTime) {
+ filters.timestamp = {};
}
// Start date
- if (filters.startDateTime) {
- match.timestamp.$gte = Utils.convertToDate(filters.startDateTime);
+ if (params.startDateTime) {
+ filters.timestamp.$gte = Utils.convertToDate(params.startDateTime);
}
// End date
- if (filters.endDateTime) {
- match.timestamp.$lte = Utils.convertToDate(filters.endDateTime);
+ if (params.endDateTime) {
+ filters.timestamp.$lte = Utils.convertToDate(params.endDateTime);
}
// Check stop transaction
- if (filters.stop) {
- match.stop = filters.stop;
+ if (params.stop) {
+ filters.stop = params.stop;
}
// Site
- if (!Utils.isEmptyArray(filters.siteIDs)) {
- match.siteID = {
- $in: filters.siteIDs.map((siteID) => Utils.convertToObjectID(siteID))
+ if (!Utils.isEmptyArray(params.siteIDs)) {
+ filters.siteID = {
+ $in: params.siteIDs.map((siteID) => Utils.convertToObjectID(siteID))
};
}
// Filter on Site Area?
- if (!Utils.isEmptyArray(filters.siteAreaIDs)) {
- match.siteAreaID = {
- $in: filters.siteAreaIDs.map((siteAreaID) => Utils.convertToObjectID(siteAreaID))
+ if (!Utils.isEmptyArray(params.siteAreaIDs)) {
+ filters.siteAreaID = {
+ $in: params.siteAreaIDs.map((siteAreaID) => Utils.convertToObjectID(siteAreaID))
};
}
// Filter on Charge Box?
- if (!Utils.isEmptyArray(filters.chargeBoxIDs)) {
- match.chargeBoxID = {
- $in: filters.chargeBoxIDs.map((chargeBoxID) => chargeBoxID)
+ if (!Utils.isEmptyArray(params.chargeBoxIDs)) {
+ filters.chargeBoxID = {
+ $in: params.chargeBoxIDs.map((chargeBoxID) => chargeBoxID)
};
}
// Filter on User?
- if (!Utils.isEmptyArray(filters.userIDs)) {
- match.userID = {
- $in: filters.userIDs.map((userID) => Utils.convertToObjectID(userID))
+ if (!Utils.isEmptyArray(params.userIDs)) {
+ filters.userID = {
+ $in: params.userIDs.map((userID) => Utils.convertToObjectID(userID))
};
}
// Create Aggregation
const aggregation = [];
// Filters
aggregation.push({
- $match: match
+ $match: filters
});
// Group
switch (groupBy) {
@@ -111,71 +111,77 @@ export default class StatisticsStorage {
});
break;
}
+ // Replace root
+ aggregation.push({
+ $replaceRoot: {
+ newRoot: { month: '$_id.month', unit: '$_id.unit', chargeBox: '$_id.chargeBox', total: '$total' }
+ }
+ });
// Sort
aggregation.push({
- $sort: { '_id.month': 1, '_id.unit': 1, '_id.chargeBox': 1 }
+ $sort: { 'month': 1, 'unit': 1, 'chargeBox': 1 }
});
// Read DB
- const transactionStatsMDB = await global.database.getCollection<any>(tenantID, 'transactions')
+ const chargingStationStatsMDB = await global.database.getCollection<any>(tenantID, 'transactions')
.aggregate(aggregation, { allowDiskUse: true })
.toArray();
// Debug
- await Logging.traceEnd(tenantID, MODULE_NAME, 'getChargingStationStats', uniqueTimerID, transactionStatsMDB);
- return transactionStatsMDB;
+ await Logging.traceEnd(tenantID, MODULE_NAME, 'getChargingStationStats', uniqueTimerID, chargingStationStatsMDB);
+ return chargingStationStatsMDB;
}
- static async getUserStats(tenantID: string, filters: StatisticFilter, groupBy: string): Promise<any[]> {
+ static async getUserStats(tenantID: string, params: StatisticFilter, groupBy: string): Promise<UserStats[]> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getUserStats');
// Check Tenant
await DatabaseUtils.checkTenant(tenantID);
// Build filter
- const match: any = {};
+ const filters: any = {};
// Date provided?
- if (filters.startDateTime || filters.endDateTime) {
- match.timestamp = {};
+ if (params.startDateTime || params.endDateTime) {
+ filters.timestamp = {};
}
// Start date
- if (filters.startDateTime) {
- match.timestamp.$gte = Utils.convertToDate(filters.startDateTime);
+ if (params.startDateTime) {
+ filters.timestamp.$gte = Utils.convertToDate(params.startDateTime);
}
// End date
- if (filters.endDateTime) {
- match.timestamp.$lte = Utils.convertToDate(filters.endDateTime);
+ if (params.endDateTime) {
+ filters.timestamp.$lte = Utils.convertToDate(params.endDateTime);
}
// Check stop tr
- if (filters.stop) {
- match.stop = filters.stop;
+ if (params.stop) {
+ filters.stop = params.stop;
}
// Filter on Site?
- if (!Utils.isEmptyArray(filters.siteIDs)) {
- match.siteID = {
- $in: filters.siteIDs.map((siteID) => Utils.convertToObjectID(siteID))
+ if (!Utils.isEmptyArray(params.siteIDs)) {
+ filters.siteID = {
+ $in: params.siteIDs.map((siteID) => Utils.convertToObjectID(siteID))
};
}
// Filter on Site Area?
- if (!Utils.isEmptyArray(filters.siteAreaIDs)) {
- match.siteAreaID = {
- $in: filters.siteAreaIDs.map((siteAreaID) => Utils.convertToObjectID(siteAreaID))
+ if (!Utils.isEmptyArray(params.siteAreaIDs)) {
+ filters.siteAreaID = {
+ $in: params.siteAreaIDs.map((siteAreaID) => Utils.convertToObjectID(siteAreaID))
};
}
// Filter on Charge Box?
- if (!Utils.isEmptyArray(filters.chargeBoxIDs)) {
- match.chargeBoxID = {
- $in: filters.chargeBoxIDs.map((chargeBoxID) => chargeBoxID)
+ if (!Utils.isEmptyArray(params.chargeBoxIDs)) {
+ filters.chargeBoxID = {
+ $in: params.chargeBoxIDs.map((chargeBoxID) => chargeBoxID)
};
}
// Filter on User?
- if (!Utils.isEmptyArray(filters.userIDs)) {
- match.userID = {
- $in: filters.userIDs.map((userID) => Utils.convertToObjectID(userID))
+ if (!Utils.isEmptyArray(params.userIDs)) {
+ filters.userID = {
+ $in: params.userIDs.map((userID) => Utils.convertToObjectID(userID))
};
}
// Create Aggregation
const aggregation = [];
// Filters
aggregation.push({
- $match: match
+ $match: filters
});
// Group
switch (groupBy) {
@@ -226,29 +232,31 @@ export default class StatisticsStorage {
});
break;
}
- // Resolve Users
- aggregation.push({
- $lookup: {
- from: DatabaseUtils.getCollectionName(tenantID, 'users'),
- localField: '_id.userID',
- foreignField: '_id',
- as: 'user'
- }
- });
+ // Lookup for users
+ DatabaseUtils.pushUserLookupInAggregation({
+ tenantID, aggregation, localField: '_id.userID', foreignField: '_id',
+ asField: 'user', oneToOneCardinality: true, oneToOneCardinalityNotNull: true
+ }, [ { $project: { _id: 1, name: 1, firstName: 1 } } ]);
// Single Record
aggregation.push({
$unwind: { 'path': '$user', 'preserveNullAndEmptyArrays': true }
});
+ // Replace root
+ aggregation.push({
+ $replaceRoot: {
+ newRoot: { month: '$_id.month', unit: '$_id.unit', user: '$user', total: '$total' }
+ }
+ });
// Sort
aggregation.push({
- $sort: { '_id.month': 1, '_id.unit': 1, '_id.chargeBox': 1 } // Instead of chargeBox userID ?
+ $sort: { 'month': 1, 'unit': 1, 'userID': 1 } // Instead of chargeBox userID ?
});
// Read DB
- const transactionStatsMDB = await global.database.getCollection<any>(tenantID, 'transactions')
+ const userStatsMDB = await global.database.getCollection<any>(tenantID, 'transactions')
.aggregate(aggregation, { allowDiskUse: true })
.toArray();
// Debug
- await Logging.traceEnd(tenantID, MODULE_NAME, 'getUserStats', uniqueTimerID, transactionStatsMDB);
- return transactionStatsMDB;
+ await Logging.traceEnd(tenantID, MODULE_NAME, 'getUserStats', uniqueTimerID, userStatsMDB);
+ return userStatsMDB;
}
}

MODIFY
src/types/Statistic.ts
src/types/Statistic.ts
+import User from './User';
export default interface StatisticFilter {
year?: number;
@@ -28,3 +29,35 @@ export enum StatsGroupBy {
PRICING = 'P',
}
+export enum StatsDataCategory {
+ CHARGING_STATION = 'C',
+ USER = 'U',
+}
+
+export enum StatsDataType {
+ CONSUMPTION = 'Consumption',
+ USAGE = 'Usage',
+ INACTIVITY = 'Inactivity',
+ TRANSACTION = 'Transactions',
+ PRICING = 'Pricing',
+}
+
+export enum StatsDataScope {
+ YEAR = 'year',
+ MONTH = 'month',
+}
+
+export interface ChargingStationStats {
+ chargeBox: string;
+ month: number;
+ total: number;
+ unit: string;
+}
+
+export interface UserStats {
+ userID: string;
+ user?: User;
+ month: number;
+ total: number;
+ unit: string;
+}

MODIFY
src/types/requests/HttpStatisticRequest.ts
src/types/requests/HttpStatisticRequest.ts
+import { StatsDataCategory, StatsDataScope, StatsDataType } from '../Statistic';
export default interface HttpStatisticsRequest {
Year?: number;
@@ -12,9 +13,9 @@ export default interface HttpStatisticsRequest {
ChargeBoxIDs?: string[];
UserID?: string;
UserIDs?: string[];
- DataType?: string;
- DataCategory?: string;
- DataScope?: string;
+ DataType?: StatsDataType;
+ DataCategory?: StatsDataCategory;
+ DataScope?: StatsDataScope;
}
export interface HttpMetricsStatisticsRequest {

Stats typing, optimizing db, polishing