Fixed eMSP Sessions (need to test in prod)
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -209,7 +209,7 @@ export default class Authorizations {
alternateUser = result.user;
alternateTag = result.tag;
// Get User and Tag that started the Transaction
- user = await UserStorage.getUserByTagId(tenant, transaction.tagID);
+ user = await UserStorage.getUserByTagID(tenant, transaction.tagID);
tag = await TagStorage.getTag(tenant, transaction.tagID);
} else {
// Check User
@@ -807,7 +807,7 @@ export default class Authorizations {
// Access Control is disabled?
if (!chargingStation.siteArea.accessControl) {
// No ACL: Always try to get the user
- const user = await UserStorage.getUserByTagId(tenant, tagID);
+ const user = await UserStorage.getUserByTagID(tenant, tagID);
const tag = await TagStorage.getTag(tenant, tagID);
return { user, tag };
}

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
@@ -323,7 +323,7 @@ export default class OCPIUtilsService {
}
}
- public static async updateTransaction(tenant: Tenant, session: OCPISession): Promise<void> {
+ public static async updateTransaction(tenant: Tenant, session: OCPISession, transaction?: Transaction): Promise<void> {
if (!OCPIUtilsService.validateSession(session)) {
throw new AppError({
module: MODULE_NAME, method: 'updateTransaction',
@@ -333,31 +333,37 @@ export default class OCPIUtilsService {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
+ // Init default values
if (!session.total_cost) {
session.total_cost = 0;
}
if (!session.kwh) {
session.kwh = 0;
}
- let transaction: Transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, session.id);
+ // Get Transaction
+ if (!transaction) {
+ transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, session.id);
+ }
+ // Create Transaction
if (!transaction) {
const user = await UserStorage.getUser(tenant, session.auth_id);
if (!user) {
throw new AppError({
module: MODULE_NAME, method: 'updateTransaction',
errorCode: HTTPError.GENERAL_ERROR,
- message: `No User found for auth_id ${session.auth_id}`,
+ message: `No User found with ID '${session.auth_id}'`,
detailedMessages: { session },
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
const evse = session.location.evses[0];
- const chargingStation = await ChargingStationStorage.getChargingStationByOcpiEvseID(tenant, evse.evse_id);
+ const chargingStation = await ChargingStationStorage.getChargingStationByOcpiLocationEvseUid(
+ tenant, session.location.id, evse.uid);
if (!chargingStation) {
throw new AppError({
module: MODULE_NAME, method: 'updateTransaction',
errorCode: HTTPError.GENERAL_ERROR,
- message: `No Charging Station found with ID '${evse.uid}' in Location '${session.location.id}'`,
+ message: `Charging Station with EVSE ID '${evse.uid}' and Location ID '${session.location.id}' does not exist`,
detailedMessages: { session },
ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR
});
@@ -411,6 +417,7 @@ export default class OCPIUtilsService {
});
return;
}
+ // Create Consumption
if (session.kwh > 0) {
await OCPIUtilsService.computeAndSaveConsumption(tenant, transaction, session);
}

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/cpo/CPOCommandsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/cpo/CPOCommandsEndpoint.ts
@@ -117,7 +117,7 @@ export default class CPOCommandsEndpoint extends AbstractEndpoint {
await Logging.logError({
tenantID: tenant.id,
action: ServerAction.OCPI_START_SESSION,
- message: `Charging Station with EVSE UID '${startSession.evse_uid}' in Location ID '${startSession.location_id}' has not been found`,
+ message: `Charging Station with EVSE ID '${startSession.evse_uid}' and Location ID '${startSession.location_id}' does not exist`,
module: MODULE_NAME, method: 'remoteStartSession',
detailedMessages: { startSession }
});

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPLocationsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPLocationsEndpoint.ts
@@ -336,7 +336,7 @@ export default class EMSPLocationsEndpoint extends AbstractEndpoint {
chargingStationID: evseUID, companyID: company.id, siteID: site.id, siteAreaID: siteArea.id,
action, module: MODULE_NAME, method: 'checkAndGetEvse',
errorCode: StatusCodes.NOT_FOUND,
- message: `Charging Station with EVSE ID '${evseUID}' does not exists`,
+ message: `Charging Station with EVSE ID '${evseUID}' and Location ID '${locationID}' does not exist`,
ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR,
detailedMessages: { locationID, evseUID, siteArea }
});

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPSessionsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPSessionsEndpoint.ts
@@ -15,6 +15,7 @@ import { StatusCodes } from 'http-status-codes';
import Tenant from '../../../../../types/Tenant';
import Transaction from '../../../../../types/Transaction';
import TransactionStorage from '../../../../../storage/mongodb/TransactionStorage';
+import _ from 'lodash';
const MODULE_NAME = 'EMSPSessionsEndpoint';
@@ -26,11 +27,11 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
public async process(req: Request, res: Response, next: NextFunction, tenant: Tenant, ocpiEndpoint: OCPIEndpoint): Promise<OCPIResponse> {
switch (req.method) {
case 'GET':
- return await this.getSessionRequest(req, res, next, tenant);
+ return this.getSessionRequest(req, res, next, tenant);
case 'PATCH':
- return await this.patchSessionRequest(req, res, next, tenant);
+ return this.patchSessionRequest(req, res, next, tenant);
case 'PUT':
- return await this.putSessionRequest(req, res, next, tenant);
+ return this.putSessionRequest(req, res, next, tenant);
}
}
@@ -70,9 +71,9 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
urlSegment.shift();
// Get filters
const countryCode = urlSegment.shift();
- const partyId = urlSegment.shift();
- const sessionId = urlSegment.shift();
- if (!countryCode || !partyId || !sessionId) {
+ const partyID = urlSegment.shift();
+ const sessionID = urlSegment.shift();
+ if (!countryCode || !partyID || !sessionID) {
throw new AppError({
action: ServerAction.OCPI_PUT_SESSION,
module: MODULE_NAME, method: 'putSessionRequest',
@@ -81,16 +82,17 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const session: OCPISession = req.body as OCPISession;
+ const session = req.body as OCPISession;
if (!session.id) {
- session.id = sessionId;
- } else if (session.id !== sessionId) {
+ session.id = sessionID;
+ } else if (session.id !== sessionID) {
throw new AppError({
action: ServerAction.OCPI_PUT_SESSION,
module: MODULE_NAME, method: 'putSessionRequest',
errorCode: StatusCodes.BAD_REQUEST,
- message: `Transaction ID '${session.id}' does not match request parameter '${sessionId}'`,
- ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
+ ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR,
+ message: `Transaction ID '${session.id}' mismatch in URL`,
+ detailedMessages: { sessionID, session }
});
}
await OCPIUtilsService.updateTransaction(tenant, session);
@@ -103,9 +105,9 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
urlSegment.shift();
// Get filters
const countryCode = urlSegment.shift();
- const partyId = urlSegment.shift();
- const sessionId = urlSegment.shift();
- if (!countryCode || !partyId || !sessionId) {
+ const partyID = urlSegment.shift();
+ const sessionID = urlSegment.shift();
+ if (!countryCode || !partyID || !sessionID) {
throw new AppError({
action: ServerAction.OCPI_PATCH_SESSION,
module: MODULE_NAME, method: 'patchSessionRequest',
@@ -114,49 +116,20 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, sessionId);
+ const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, sessionID);
if (!transaction) {
throw new AppError({
action: ServerAction.OCPI_PATCH_SESSION,
module: MODULE_NAME, method: 'patchSessionRequest',
errorCode: HTTPError.GENERAL_ERROR,
- message: `No Transaction found for OCPI Transaction ID '${sessionId}'`,
+ message: `No Transaction found for OCPI Session ID '${sessionID}'`,
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- let patched = false;
- const sessionPatched: Partial<OCPISession> = req.body as Partial<OCPISession>;
- if (sessionPatched.status) {
- transaction.ocpiData.session.status = sessionPatched.status;
- patched = true;
- }
- if (sessionPatched.end_datetime) {
- transaction.ocpiData.session.end_datetime = sessionPatched.end_datetime;
- patched = true;
- }
- if (sessionPatched.kwh) {
- transaction.ocpiData.session.kwh = sessionPatched.kwh;
- patched = true;
- }
- if (sessionPatched.currency) {
- transaction.ocpiData.session.currency = sessionPatched.currency;
- patched = true;
- }
- if (sessionPatched.total_cost) {
- transaction.ocpiData.session.total_cost = sessionPatched.total_cost;
- patched = true;
- }
- if (!patched) {
- throw new AppError({
- action: ServerAction.OCPI_PATCH_SESSION,
- module: MODULE_NAME, method: 'patchSessionRequest',
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'Missing request parameters',
- detailedMessages: { sessionPatched },
- ocpiError: OCPIStatusCode.CODE_2002_NOT_ENOUGH_INFORMATION_ERROR
- });
- }
- await OCPIUtilsService.updateTransaction(tenant, transaction.ocpiData.session);
+ // Merge
+ _.merge(transaction.ocpiData.session, req.body);
+ // Update
+ await OCPIUtilsService.updateTransaction(tenant, transaction.ocpiData.session, transaction);
return OCPIUtils.success({});
}
}

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPTokensEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPTokensEndpoint.ts
import { NextFunction, Request, Response } from 'express';
-import { OCPIAllowed, OCPIAuthorizationInfo } from '../../../../../types/ocpi/OCPIAuthorizationInfo';
-
-import AbstractEndpoint from '../../AbstractEndpoint';
-import AbstractOCPIService from '../../../AbstractOCPIService';
import AppError from '../../../../../exception/AppError';
import ChargingStationStorage from '../../../../../storage/mongodb/ChargingStationStorage';
-import Constants from '../../../../../utils/Constants';
+import TagStorage from '../../../../../storage/mongodb/TagStorage';
import { HTTPError } from '../../../../../types/HTTPError';
+import { OCPIAllowed } from '../../../../../types/ocpi/OCPIAuthorizationInfo';
import OCPIEndpoint from '../../../../../types/ocpi/OCPIEndpoint';
import { OCPILocationReference } from '../../../../../types/ocpi/OCPILocation';
import { OCPIResponse } from '../../../../../types/ocpi/OCPIResponse';
import { OCPIStatusCode } from '../../../../../types/ocpi/OCPIStatusCode';
-import OCPIUtils from '../../../OCPIUtils';
-import OCPIUtilsService from '../OCPIUtilsService';
import { ServerAction } from '../../../../../types/Server';
-import TagStorage from '../../../../../storage/mongodb/TagStorage';
import Tenant from '../../../../../types/Tenant';
import { UserStatus } from '../../../../../types/User';
+import Constants from '../../../../../utils/Constants';
import Utils from '../../../../../utils/Utils';
+import AbstractOCPIService from '../../../AbstractOCPIService';
+import OCPIUtils from '../../../OCPIUtils';
+import AbstractEndpoint from '../../AbstractEndpoint';
+import OCPIUtilsService from '../OCPIUtilsService';
+
const MODULE_NAME = 'EMSPTokensEndpoint';
@@ -61,8 +61,8 @@ export default class EMSPTokensEndpoint extends AbstractEndpoint {
// Remove action
urlSegment.shift();
// Get filters
- const tokenId = urlSegment.shift();
- if (!tokenId) {
+ const tokenID = urlSegment.shift();
+ if (!tokenID) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
@@ -71,54 +71,59 @@ export default class EMSPTokensEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const locationReference: OCPILocationReference = req.body;
- if (!locationReference) {
+ const location = req.body as OCPILocationReference;
+ if (!location) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Missing LocationReference',
- ocpiError: OCPIStatusCode.CODE_2002_NOT_ENOUGH_INFORMATION_ERROR
+ ocpiError: OCPIStatusCode.CODE_2002_NOT_ENOUGH_INFORMATION_ERROR,
+ message: 'Missing Location data',
+ detailedMessages: { tokenID, location }
});
}
- if (Utils.isEmptyArray(locationReference.evse_uids)) {
+ if (Utils.isEmptyArray(location.evse_uids)) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
errorCode: HTTPError.GENERAL_ERROR,
+ ocpiError: OCPIStatusCode.CODE_2002_NOT_ENOUGH_INFORMATION_ERROR,
message: 'Missing Charging Station ID',
- ocpiError: OCPIStatusCode.CODE_2002_NOT_ENOUGH_INFORMATION_ERROR
+ detailedMessages: { tokenID, location }
});
}
- if (locationReference.evse_uids.length > 1) {
+ if (location.evse_uids.length > 1) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Does not support authorization request on multiple Charging Stations',
- ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
+ ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR,
+ message: 'Does not support authorization on multiple Charging Stations',
+ detailedMessages: { tokenID, location }
});
}
// Get the Charging Station
const chargingStation = await ChargingStationStorage.getChargingStationByOcpiLocationEvseUid(
- tenant, locationReference.location_id, locationReference.evse_uids[0]);
+ tenant, location.location_id, location.evse_uids[0]);
if (!chargingStation) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
errorCode: HTTPError.GENERAL_ERROR,
- message: `Unknown Charging Station '${locationReference.evse_uids[0]}'`,
- ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR
+ ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR,
+ message: `Charging Station with EVSE ID '${location.evse_uids[0]}' and Location ID '${location.location_id}' does not exist`,
+ detailedMessages: { tokenID, location }
});
}
- const tag = await TagStorage.getTag(tenant, tokenId, { withUser: true });
+ const tag = await TagStorage.getTag(tenant, tokenID, { withUser: true });
if (!tag?.user) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
errorCode: HTTPError.GENERAL_ERROR,
- message: 'UNKNOWN USER',
- ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
+ ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR,
+ message: `Unkwnown User for OCPI Token ID '${tokenID}'`,
+ detailedMessages: { tokenID, location }
});
}
let allowedStatus: OCPIAllowed;
@@ -139,7 +144,7 @@ export default class EMSPTokensEndpoint extends AbstractEndpoint {
return OCPIUtils.success({
allowed: allowedStatus,
authorization_id: Utils.generateUUID(),
- location: locationReference
+ location: location
});
}
}

MODIFY
src/server/ocpp/services/OCPPService.ts
src/server/ocpp/services/OCPPService.ts
@@ -589,7 +589,7 @@ export default class OCPPService {
alternateUser = authorizedUsers.alternateUser;
} else {
// Get the User
- user = await UserStorage.getUserByTagId(tenant, tagId);
+ user = await UserStorage.getUserByTagID(tenant, tagId);
}
// Already Stopped?
if (transaction.stop) {

MODIFY
src/storage/mongodb/ChargingStationStorage.ts
src/storage/mongodb/ChargingStationStorage.ts
@@ -115,25 +115,6 @@ export default class ChargingStationStorage {
return chargingStationsMDB.count === 1 ? chargingStationsMDB.result[0] : null;
}
- public static async getChargingStationByOcpiEvseID(tenant: Tenant, ocpiEvseID: string = Constants.UNKNOWN_STRING_ID,
- projectFields?: string[]): Promise<ChargingStation> {
- const chargingStationsMDB = await ChargingStationStorage.getChargingStations(tenant, {
- ocpiEvseID,
- withSiteArea: true,
- }, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
- // No unique key on OCPI Location (avoid create several Site Area with the same location ID)
- if (chargingStationsMDB.count > 1) {
- await Logging.logWarning({
- tenantID: tenant.id,
- action: ServerAction.UNKNOWN_ACTION,
- module: MODULE_NAME, method: 'getSiteAreaByOcpiLocationUid',
- message: `Multiple Charging Station with same OCPI EVSE ID '${ocpiEvseID}'`,
- detailedMessages: { ocpiEvseID, chargingStations: chargingStationsMDB.result }
- });
- }
- return chargingStationsMDB.count >= 1 ? chargingStationsMDB.result[0] : null;
- }
-
public static async getChargingStationByOcpiLocationEvseUid(tenant: Tenant, ocpiLocationID: string = Constants.UNKNOWN_STRING_ID,
ocpiEvseUid: string = Constants.UNKNOWN_STRING_ID,
projectFields?: string[]): Promise<ChargingStation> {
@@ -159,7 +140,7 @@ export default class ChargingStationStorage {
params: {
search?: string; chargingStationIDs?: string[]; chargingStationSerialNumbers?: string[]; siteAreaIDs?: string[]; withNoSiteArea?: boolean;
connectorStatuses?: string[]; connectorTypes?: string[]; statusChangedBefore?: Date; withSiteArea?: boolean; withUser?: boolean;
- ocpiEvseUid?: string; ocpiEvseID?: string; ocpiLocationID?: string; oicpEvseID?: string;
+ ocpiEvseUid?: string; ocpiLocationID?: string; oicpEvseID?: string;
siteIDs?: string[]; companyIDs?: string[]; withSite?: boolean; includeDeleted?: boolean; offlineSince?: Date; issuer?: boolean;
locCoordinates?: number[]; locMaxDistanceMeters?: number; public?: boolean;
},
@@ -228,10 +209,6 @@ export default class ChargingStationStorage {
if (params.ocpiLocationID) {
filters['ocpiData.evses.location_id'] = params.ocpiLocationID;
}
- // OCPI Evse ID
- if (params.ocpiEvseID) {
- filters['ocpiData.evses.evse_id'] = params.ocpiEvseID;
- }
// OICP Evse ID
if (params.oicpEvseID) {
filters['oicpData.evses.EvseID'] = params.oicpEvseID;

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -79,7 +79,7 @@ export default class UserStorage {
return eula;
}
- public static async getUserByTagId(tenant: Tenant, tagID: string = Constants.UNKNOWN_STRING_ID): Promise<User> {
+ public static async getUserByTagID(tenant: Tenant, tagID: string = Constants.UNKNOWN_STRING_ID): Promise<User> {
const tagMDB = await TagStorage.getTag(tenant, tagID, { withUser: true });
return tagMDB ? tagMDB.user : null;
}

Fixed eMSP Sessions (need to test in prod)