Fixed wrong success messages when pushing of OCPI CDRs failed
Added more error handling
MODIFY
src/client/ocpi/CpoOCPIClient.ts
src/client/ocpi/CpoOCPIClient.ts
@@ -6,7 +6,6 @@ import { OCPIAllowed, OCPIAuthorizationInfo } from '../../types/ocpi/OCPIAuthori
import { OCPIAuthMethod, OCPISession, OCPISessionStatus } from '../../types/ocpi/OCPISession';
import { OCPICapability, OCPIEvse, OCPIEvseStatus } from '../../types/ocpi/OCPIEvse';
import { OCPILocation, OCPILocationOptions, OCPILocationReference, OCPILocationType } from '../../types/ocpi/OCPILocation';
-import User, { UserRole, UserStatus } from '../../types/User';
import { AxiosResponse } from 'axios';
import BackendError from '../../exception/BackendError';
@@ -38,6 +37,7 @@ import TagStorage from '../../storage/mongodb/TagStorage';
import Tenant from '../../types/Tenant';
import Transaction from '../../types/Transaction';
import TransactionStorage from '../../storage/mongodb/TransactionStorage';
+import User from '../../types/User';
import UserStorage from '../../storage/mongodb/UserStorage';
import Utils from '../../utils/Utils';
import _ from 'lodash';
@@ -370,7 +370,7 @@ export default class CpoOCPIClient extends OCPIClient {
module: MODULE_NAME, method: 'postCdr',
});
}
- if (!transaction.ocpiData?.session) {
+ if (!transaction.ocpiData?.session?.id) {
throw new BackendError({
...LoggingHelper.getTransactionProperties(transaction),
action: ServerAction.OCPI_CPO_PUSH_CDRS,

MODIFY
src/server/ocpi/OCPIFacade.ts
src/server/ocpi/OCPIFacade.ts
@@ -77,10 +77,10 @@ export default class OCPIFacade {
}
public static async processEndTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation,
- siteArea: SiteArea, user: User, action: ServerAction): Promise<void> {
+ siteArea: SiteArea, user: User, action: ServerAction): Promise<boolean> {
if (!Utils.isTenantComponentActive(tenant, TenantComponents.OCPI) ||
!chargingStation.issuer || !chargingStation.public || !siteArea.accessControl || user.issuer) {
- return;
+ return false;
}
// Get OCPI CPO client
const ocpiClient = await OCPIFacade.checkAndGetOcpiCpoClient(
@@ -100,8 +100,8 @@ export default class OCPIFacade {
if (ocpiLock) {
try {
// Roaming
- ocpiCdrSent = true;
- await OCPIFacade.processEndTransaction(tenant, transaction, chargingStation, siteArea, transaction.user, action);
+ ocpiCdrSent = await OCPIFacade.processEndTransaction(
+ tenant, transaction, chargingStation, siteArea, transaction.user, action);
} finally {
// Release the lock
await LockingManager.release(ocpiLock);

MODIFY
src/server/oicp/OICPFacade.ts
src/server/oicp/OICPFacade.ts
@@ -83,10 +83,10 @@ export default class OICPFacade {
}
public static async processEndTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation,
- siteArea: SiteArea, user: User, action: ServerAction): Promise<void> {
+ siteArea: SiteArea, user: User, action: ServerAction): Promise<boolean> {
if (!Utils.isTenantComponentActive(tenant, TenantComponents.OICP) ||
!chargingStation.issuer || !chargingStation.public || !siteArea.accessControl) {
- return;
+ return false;
}
// Get OICP CPO client
const oicpClient = await OICPFacade.checkAndGetOICPCpoClient(
@@ -97,7 +97,7 @@ export default class OICPFacade {
public static async checkAndSendTransactionCdr(tenant: Tenant, transaction: Transaction,
chargingStation: ChargingStation, siteArea: SiteArea, action: ServerAction): Promise<boolean> {
- let OICPCdrSent = false;
+ let oicpCdrSent = false;
// CDR not already pushed
if (Utils.isTenantComponentActive(tenant, TenantComponents.OICP) &&
transaction.oicpData?.session && !transaction.oicpData.cdr?.SessionID) {
@@ -106,15 +106,15 @@ export default class OICPFacade {
if (OICPLock) {
try {
// Roaming
- OICPCdrSent = true;
- await OICPFacade.processEndTransaction(tenant, transaction, chargingStation, siteArea, transaction.user, action);
+ oicpCdrSent = await OICPFacade.processEndTransaction(
+ tenant, transaction, chargingStation, siteArea, transaction.user, action);
} finally {
// Release the lock
await LockingManager.release(OICPLock);
}
}
}
- return OICPCdrSent;
+ return oicpCdrSent;
}
public static async updateConnectorStatus(tenant: Tenant, chargingStation: ChargingStation, connector: Connector): Promise<void> {

MODIFY
src/server/rest/v1/service/TransactionService.ts
src/server/rest/v1/service/TransactionService.ts
@@ -114,6 +114,7 @@ export default class TransactionService {
}
if (transaction.refundData && !!transaction.refundData.refundId && transaction.refundData.status !== RefundStatus.CANCELLED) {
await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: req.tenant.id,
user: req.user, actionOnUser: (transaction.user ? transaction.user : null),
module: MODULE_NAME, method: 'handleRefundTransactions',
@@ -126,6 +127,7 @@ export default class TransactionService {
// Check auth
if (!await Authorizations.canRefundTransaction(req.user, transaction)) {
throw new AppAuthError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
action: Action.REFUND_TRANSACTION, entity: Entity.TRANSACTION,
@@ -175,6 +177,17 @@ export default class TransactionService {
}
public static async handlePushTransactionCdr(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ if (!Utils.isComponentActiveFromToken(req.user, TenantComponents.OCPI) &&
+ !Utils.isComponentActiveFromToken(req.user, TenantComponents.OICP)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ entity: Entity.TRANSACTION, action: Action.UPDATE,
+ module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ inactiveComponent: `${TenantComponents.OCPI}, ${TenantComponents.OICP}` as TenantComponents,
+ user: req.user
+ });
+ }
// Filter
const filteredRequest = TransactionValidator.getInstance().validateTransactionCdrPushReq(req.body);
// Check Mandatory fields
@@ -186,6 +199,7 @@ export default class TransactionService {
// Check auth
if (!await Authorizations.canUpdateTransaction(req.user, transaction)) {
throw new AppAuthError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
action: Action.UPDATE, entity: Entity.TRANSACTION,
@@ -197,9 +211,38 @@ export default class TransactionService {
const chargingStation = await ChargingStationStorage.getChargingStation(req.tenant, transaction.chargeBoxID, { withSiteArea: true });
UtilsService.assertObjectExists(action, chargingStation, `Charging Station ID '${transaction.chargeBoxID}' does not exist`,
MODULE_NAME, 'handlePushTransactionCdr', req.user);
- // Check Issuer
+ // Check Charging Station
+ if (!chargingStation.issuer) {
+ throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Charging Station belongs to an external organization`,
+ module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ user: req.user, action
+ });
+ }
+ if (!chargingStation.public) {
+ throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Charging Station is not public`,
+ module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ user: req.user, action
+ });
+ }
+ if (chargingStation.siteArea && !chargingStation.siteArea.accessControl) {
+ throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Charging Station access control is inactive on Site Area '${chargingStation.siteArea.name}'`,
+ module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ user: req.user, action
+ });
+ }
+ // Check Transaction
if (!transaction.issuer) {
throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.TRANSACTION_NOT_FROM_TENANT,
message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Transaction belongs to an external organization`,
module: MODULE_NAME, method: 'handlePushTransactionCdr',
@@ -209,6 +252,7 @@ export default class TransactionService {
// No Roaming Cdr to push
if (!transaction.oicpData?.session && !transaction.ocpiData?.session) {
throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.TRANSACTION_WITH_NO_OCPI_DATA,
message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} No OCPI or OICP Session data`,
module: MODULE_NAME, method: 'handlePushTransactionCdr',
@@ -220,6 +264,7 @@ export default class TransactionService {
// CDR already pushed
if (transaction.ocpiData.cdr?.id) {
throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.TRANSACTION_CDR_ALREADY_PUSHED,
message: `The CDR of the Transaction ID '${transaction.id}' has already been pushed`,
module: MODULE_NAME, method: 'handlePushTransactionCdr',
@@ -227,12 +272,21 @@ export default class TransactionService {
});
}
// OCPI: Post the CDR
- const ocpiUpdated = await OCPIFacade.checkAndSendTransactionCdr(
+ const ocpiCdrSent = await OCPIFacade.checkAndSendTransactionCdr(
req.tenant, transaction, chargingStation, chargingStation.siteArea, action);
- if (ocpiUpdated) {
+ if (!ocpiCdrSent) {
+ throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `The CDR of the Transaction ID '${transaction.id}' has not been sent`,
+ module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ user: req.user, action
+ });
+ }
// Save
await TransactionStorage.saveTransactionOcpiData(req.tenant, transaction.id, transaction.ocpiData);
await Logging.logInfo({
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: req.tenant.id,
action, module: MODULE_NAME, method: 'handlePushTransactionCdr',
user: req.user, actionOnUser: (transaction.user ? transaction.user : null),
@@ -240,12 +294,12 @@ export default class TransactionService {
detailedMessages: { cdr: transaction.ocpiData.cdr }
});
}
- }
// Check OICP
if (transaction.oicpData?.session) {
// CDR already pushed
if (transaction.oicpData.cdr?.SessionID) {
throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.TRANSACTION_CDR_ALREADY_PUSHED,
message: `The CDR of the transaction ID '${transaction.id}' has already been pushed`,
module: MODULE_NAME, method: 'handlePushTransactionCdr',
@@ -253,21 +307,28 @@ export default class TransactionService {
});
}
// OICP: Post the CDR
- const oicpUpdated = await OICPFacade.checkAndSendTransactionCdr(
+ const oicpCdrSent = await OICPFacade.checkAndSendTransactionCdr(
req.tenant, transaction, chargingStation, chargingStation.siteArea, action);
- if (oicpUpdated) {
+ if (!oicpCdrSent) {
+ throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `The CDR of the Transaction ID '${transaction.id}' has not been sent`,
+ module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ user: req.user, action
+ });
+ }
// Save
await TransactionStorage.saveTransactionOicpData(req.tenant, transaction.id, transaction.oicpData);
await Logging.logInfo({
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: req.tenant.id,
- action,
- user: req.user, actionOnUser: (transaction.user ? transaction.user : null),
- module: MODULE_NAME, method: 'handlePushTransactionCdr',
+ user: req.user, actionOnUser: (transaction.user ?? null),
+ action, module: MODULE_NAME, method: 'handlePushTransactionCdr',
message: `CDR of Transaction ID '${transaction.id}' has been pushed successfully`,
detailedMessages: { cdr: transaction.ocpiData.cdr }
});
}
- }
res.json(Constants.REST_RESPONSE_SUCCESS);
next();
}
@@ -418,6 +479,7 @@ export default class TransactionService {
// Check Transaction
if (!await Authorizations.canReadTransaction(req.user, transaction)) {
throw new AppAuthError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
action: Action.READ, entity: Entity.TRANSACTION,
@@ -441,6 +503,7 @@ export default class TransactionService {
if (filteredRequest.StartDateTime && filteredRequest.EndDateTime &&
moment(filteredRequest.StartDateTime).isAfter(moment(filteredRequest.EndDateTime))) {
throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.GENERAL_ERROR,
message: `The requested start date '${new Date(filteredRequest.StartDateTime).toISOString()}' is after the requested end date '${new Date(filteredRequest.StartDateTime).toISOString()}' `,
module: MODULE_NAME, method: 'handleGetConsumptionFromTransaction',
@@ -503,6 +566,7 @@ export default class TransactionService {
// Check Transaction
if (!await Authorizations.canReadTransaction(req.user, transaction)) {
throw new AppAuthError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPAuthError.FORBIDDEN,
user: req.user,
action: Action.READ, entity: Entity.TRANSACTION,
@@ -711,6 +775,7 @@ export default class TransactionService {
MODULE_NAME, 'handleExportTransactionOcpiCdr', req.user);
if (!transaction?.ocpiData) {
throw new AppError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.GENERAL_ERROR,
message: `Transaction ID '${transaction.id}' does not contain roaming data`,
module: MODULE_NAME, method: 'handleExportTransactionOcpiCdr',
@@ -868,28 +933,36 @@ export default class TransactionService {
message: `Transaction ID '${transactionID}' does not exist`,
detailedMessages: { transaction }
});
+ continue;
+ }
// Already Refunded
- } else if (refundConnector && !refundConnector.canBeDeleted(transaction)) {
+ if (refundConnector && !refundConnector.canBeDeleted(transaction)) {
result.inError++;
await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: loggedUser.tenantID,
user: loggedUser,
action, module: MODULE_NAME, method: 'handleDeleteTransactions',
message: `Transaction ID '${transactionID}' has been refunded and cannot be deleted`,
detailedMessages: { transaction }
});
+ continue;
+ }
// Billed
- } else if (billingImpl && transaction.billingData?.stop?.status === BillingStatus.BILLED) {
+ if (billingImpl && transaction.billingData?.stop?.status === BillingStatus.BILLED) {
result.inError++;
await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: loggedUser.tenantID,
user: loggedUser,
action, module: MODULE_NAME, method: 'handleDeleteTransactions',
message: `Transaction ID '${transactionID}' has been billed and cannot be deleted`,
detailedMessages: { transaction }
});
+ continue;
+ }
// Transaction in progress
- } else if (!transaction.stop) {
+ if (!transaction.stop) {
if (!transaction.chargeBox) {
transactionsIDsToDelete.push(transactionID);
} else {
@@ -903,13 +976,12 @@ export default class TransactionService {
// To Delete
transactionsIDsToDelete.push(transactionID);
}
- } else {
- transactionsIDsToDelete.push(transactionID);
+ continue;
}
+ transactionsIDsToDelete.push(transactionID);
}
// Delete All Transactions
result.inSuccess = await TransactionStorage.deleteTransactions(tenant, transactionsIDsToDelete);
- // Log
await Logging.logActionsResponse(loggedUser.tenantID,
ServerAction.TRANSACTIONS_DELETE,
MODULE_NAME, 'deleteTransactions', result,
@@ -1043,6 +1115,7 @@ export default class TransactionService {
await ChargingStationStorage.saveChargingStationConnectors(req.tenant, chargingStation.id, chargingStation.connectors);
}
await Logging.logInfo({
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: req.tenant.id,
user: req.user, actionOnUser: transaction.userID,
action, module: MODULE_NAME, method: 'transactionSoftStop',
@@ -1052,7 +1125,7 @@ export default class TransactionService {
// Transaction is still ongoing
if (!chargingStation.inactive && connector.currentTransactionID === transaction.id) {
throw new AppError({
- ...LoggingHelper.getChargingStationProperties(chargingStation),
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.GENERAL_ERROR,
message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Cannot soft stop an ongoing Transaction`,
module: MODULE_NAME, method: 'transactionSoftStop',
@@ -1064,7 +1137,7 @@ export default class TransactionService {
req.tenant, transaction, chargingStation, chargingStation.siteArea);
if (!success) {
throw new AppError({
- ...LoggingHelper.getChargingStationProperties(chargingStation),
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPError.GENERAL_ERROR,
message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Transaction cannot be stopped`,
module: MODULE_NAME, method: 'transactionSoftStop',
@@ -1072,7 +1145,7 @@ export default class TransactionService {
});
}
await Logging.logInfo({
- ...LoggingHelper.getChargingStationProperties(chargingStation),
+ ...LoggingHelper.getTransactionProperties(transaction),
tenantID: req.tenant.id,
user: req.user, actionOnUser: transaction.userID,
module: MODULE_NAME, method: 'transactionSoftStop',
@@ -1093,6 +1166,7 @@ export default class TransactionService {
// Check auth
if (!await Authorizations.canUpdateTransaction(user, transaction)) {
throw new AppAuthError({
+ ...LoggingHelper.getTransactionProperties(transaction),
errorCode: HTTPAuthError.FORBIDDEN,
user, action: Action.UPDATE, entity: Entity.TRANSACTION,
module: MODULE_NAME, method: 'checkAndGetTransactionChargingStationConnector',

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -906,7 +906,6 @@ export default class UtilsService {
action: Action, entity: Entity, module: string, method: string): void {
// Check from token
const active = Utils.isComponentActiveFromToken(userToken, component);
- // Throw
if (!active) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,

Fixed wrong success messages when pushing of OCPI CDRs failed
Added more error handling