diff --git a/package-lock.json b/package-lock.json index e60af356fb..2ca6577d6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ev-server", - "version": "2.4.72", + "version": "2.4.73", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d4e0065b8c..521dfbf3e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ev-server", - "version": "2.4.72", + "version": "2.4.73", "engines": { "node": "14.x.x", "npm": "6.x.x" diff --git a/src/assets/configs-aws b/src/assets/configs-aws index 6281db138c..2dc38e36a5 160000 --- a/src/assets/configs-aws +++ b/src/assets/configs-aws @@ -1 +1 @@ -Subproject commit 6281db138c29805f3695bf751b40917a306721e7 +Subproject commit 2dc38e36a569dc1266e48d85e7e87ed0d3f18528 diff --git a/src/async-task/AsyncTaskManager.ts b/src/async-task/AsyncTaskManager.ts index 66d18a95a2..f9cbcd420b 100644 --- a/src/async-task/AsyncTaskManager.ts +++ b/src/async-task/AsyncTaskManager.ts @@ -48,7 +48,7 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'handleAsyncTasks', - message: 'Checking Async Task to process...' + message: 'Checking asynchronous task to process...' }); const processedTask: ActionsResponse = { inError: 0, @@ -69,7 +69,7 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'handleAsyncTasks', - message: `${asyncTasks.result.length} Async Task(s) are going to be processed...` + message: `${asyncTasks.result.length} asynchronous task(s) are going to be processed...` }); await Promise.map(asyncTasks.result, async (asyncTask: AsyncTask) => { @@ -120,7 +120,7 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'handleAsyncTasks', - message: `The Async Task '${asyncTask.name}' is unknown` + message: `The asynchronous task '${asyncTask.name}' is unknown` }); } if (abstractAsyncTask) { @@ -140,7 +140,7 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'handleAsyncTasks', - message: `The task '${asyncTask.name}' is running...` + message: `The asynchronous task '${asyncTask.name}' is running...` }); // Run await abstractAsyncTask.run(); @@ -157,7 +157,7 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'handleAsyncTasks', - message: `The task '${asyncTask.name}' has been processed in ${asyncTaskTotalDurationSecs} secs` + message: `The asynchronous task '${asyncTask.name}' has been processed in ${asyncTaskTotalDurationSecs} secs` }); } catch (error) { processedTask.inError++; @@ -172,8 +172,8 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, module: MODULE_NAME, method: 'handleAsyncTasks', action: ServerAction.ASYNC_TASK, - message: `Error while running the Async Task '${asyncTask.name}': ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack, asyncTask } + message: `Error while running the asynchronous task '${asyncTask.name}': ${error.message}`, + detailedMessages: { error: error.stack, asyncTask } }); } finally { // Release lock @@ -187,17 +187,17 @@ export default class AsyncTaskManager { const totalDurationSecs = Utils.truncTo((new Date().getTime() - startTime) / 1000, 2); void Logging.logActionsResponse(Constants.DEFAULT_TENANT, ServerAction.ASYNC_TASK, MODULE_NAME, 'handleAsyncTasks', processedTask, - `{{inSuccess}} Async Task(s) were successfully processed in ${totalDurationSecs} secs`, - `{{inError}} Async Task(s) failed to be processed in ${totalDurationSecs} secs`, - `{{inSuccess}} Async Task(s) were successfully processed in ${totalDurationSecs} secs and {{inError}} failed`, - 'No Async Task to process' + `{{inSuccess}} asynchronous task(s) were successfully processed in ${totalDurationSecs} secs`, + `{{inError}} asynchronous task(s) failed to be processed in ${totalDurationSecs} secs`, + `{{inSuccess}} asynchronous task(s) were successfully processed in ${totalDurationSecs} secs and {{inError}} failed`, + 'No asynchronous task to process' ); } else { await Logging.logInfo({ tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'handleAsyncTasks', - message: 'No Async Task to process' + message: 'No asynchronous task to process' }); } } @@ -206,14 +206,14 @@ export default class AsyncTaskManager { public static async createAndSaveAsyncTasks(asyncTask: Omit): Promise { // Check if (Utils.isNullOrUndefined(asyncTask)) { - throw new Error('The Async Task must not be null'); + throw new Error('The asynchronous task must not be null'); } // Check if (Utils.isNullOrUndefined(asyncTask.name)) { - throw new Error('The Name of the Async Task is mandatory'); + throw new Error('The Name of the asynchronous task is mandatory'); } if (!Utils.isNullOrUndefined(asyncTask.parameters) && (typeof asyncTask.parameters !== 'object')) { - throw new Error('The Parameters of the Async Task must be a Json document'); + throw new Error('The Parameters of the asynchronous task must be a Json document'); } // Set asyncTask.status = AsyncTaskStatus.PENDING; @@ -225,7 +225,7 @@ export default class AsyncTaskManager { tenantID: Constants.DEFAULT_TENANT, action: ServerAction.ASYNC_TASK, module: MODULE_NAME, method: 'createAndSaveAsyncTasks', - message: `The task '${asyncTask.name}' has been saved successfully and will be processed soon` + message: `The asynchronous task '${asyncTask.name}' has been saved successfully and will be processed soon` }); } } diff --git a/src/async-task/tasks/TagsImportAsyncTask.ts b/src/async-task/tasks/TagsImportAsyncTask.ts index 6b203de336..436112aa6e 100644 --- a/src/async-task/tasks/TagsImportAsyncTask.ts +++ b/src/async-task/tasks/TagsImportAsyncTask.ts @@ -102,7 +102,7 @@ export default class TagsImportAsyncTask extends AbstractAsyncTask { action: ServerAction.TAGS_IMPORT, module: MODULE_NAME, method: 'processTenant', message: `Error when importing Tag ID '${importedTag.id}': ${error.message}`, - detailedMessages: { tag: importedTag, error: error.message, stack: error.stack } + detailedMessages: { tag: importedTag, error: error.stack } }); } } diff --git a/src/async-task/tasks/UsersImportAsyncTask.ts b/src/async-task/tasks/UsersImportAsyncTask.ts index ff29f08ad8..761f83603e 100644 --- a/src/async-task/tasks/UsersImportAsyncTask.ts +++ b/src/async-task/tasks/UsersImportAsyncTask.ts @@ -91,7 +91,7 @@ export default class UsersImportAsyncTask extends AbstractAsyncTask { action: ServerAction.USERS_IMPORT, module: MODULE_NAME, method: 'processTenant', message: `Error when importing User with email '${importedUser.email}': ${error.message}`, - detailedMessages: { user: importedUser, error: error.message, stack: error.stack } + detailedMessages: { user: importedUser, error: error.stack } }); } } diff --git a/src/authorization/AuthorizationsDefinition.ts b/src/authorization/AuthorizationsDefinition.ts index 86bb0524a9..87ff163d7c 100644 --- a/src/authorization/AuthorizationsDefinition.ts +++ b/src/authorization/AuthorizationsDefinition.ts @@ -97,7 +97,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { { resource: Entity.COMPANIES, action: Action.LIST, attributes: [ - 'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn', + 'id', 'name', 'address.city', 'address.country', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn', 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName' ] }, @@ -133,7 +133,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { { resource: Entity.SITES, action: Action.LIST, attributes: [ - 'id', 'name', 'address', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer', + 'id', 'name', 'address.city', 'address.country', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer', 'autoUserSiteAssignment', 'distanceMeters', 'public', 'createdOn', 'lastChangedOn', 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName' ] @@ -154,7 +154,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { { resource: Entity.SITE_AREAS, action: Action.LIST, attributes: [ - 'id', 'name', 'siteID', 'maximumPower', 'voltage', 'numberOfPhases', 'accessControl', 'smartCharging', 'address', + 'id', 'name', 'siteID', 'maximumPower', 'voltage', 'numberOfPhases', 'accessControl', 'smartCharging', 'address.city', 'address.country', 'site.id', 'site.name', 'issuer', 'distanceMeters', 'createdOn', 'createdBy', 'lastChangedOn', 'lastChangedBy' ] }, @@ -393,7 +393,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { args: { filters: ['AssignedSitesCompanies'] } }, attributes: [ - 'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn' + 'id', 'name', 'address.city', 'address.country', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn' ] }, { @@ -419,7 +419,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { { resource: Entity.SITES, action: Action.LIST, attributes: [ - 'id', 'name', 'address', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer', + 'id', 'name', 'address.city', 'address.country', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer', 'autoUserSiteAssignment', 'distanceMeters', 'public', 'createdOn', 'lastChangedOn', ], condition: { @@ -441,7 +441,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { { resource: Entity.SITE_AREAS, action: Action.LIST, attributes: [ - 'id', 'name', 'siteID', 'maximumPower', 'voltage', 'numberOfPhases', 'accessControl', 'smartCharging', 'address', + 'id', 'name', 'siteID', 'maximumPower', 'voltage', 'numberOfPhases', 'accessControl', 'smartCharging', 'address.city', 'address.country', 'site.id', 'site.name', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn' ], condition: { @@ -615,7 +615,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { }, { resource: Entity.COMPANIES, action: Action.LIST, attributes: [ - 'id', 'name', 'address', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn' + 'id', 'name', 'address.city', 'address.country', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn' ] }, { @@ -625,7 +625,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { }, { resource: Entity.SITES, action: Action.LIST, attributes: [ - 'id', 'name', 'address', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer', + 'id', 'name', 'address.city', 'address.country', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer', 'autoUserSiteAssignment', 'distanceMeters', 'public', 'createdOn', 'lastChangedOn', ] }, @@ -637,7 +637,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = { }, { resource: Entity.SITE_AREAS, action: Action.LIST, attributes: [ - 'id', 'name', 'siteID', 'maximumPower', 'voltage', 'numberOfPhases', 'accessControl', 'smartCharging', 'address', + 'id', 'name', 'siteID', 'maximumPower', 'voltage', 'numberOfPhases', 'accessControl', 'smartCharging', 'address.city', 'address.country', 'site.id', 'site.name', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn' ] }, @@ -848,7 +848,7 @@ export default class AuthorizationsDefinition { source: Constants.CENTRAL_SERVER, module: MODULE_NAME, method: 'constructor', message: 'Unable to init authorization definition', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -874,7 +874,7 @@ export default class AuthorizationsDefinition { module: MODULE_NAME, method: 'getScopes', message: 'Unable to load available scopes', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } return scopes; @@ -890,7 +890,7 @@ export default class AuthorizationsDefinition { module: MODULE_NAME, method: 'can', message: 'Unable to check authorization', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -908,7 +908,7 @@ export default class AuthorizationsDefinition { module: MODULE_NAME, method: 'canPerformAction', message: 'Unable to check authorization', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/client/ocpp/json/JsonRestChargingStationClient.ts b/src/client/ocpp/json/JsonRestChargingStationClient.ts index 8a7b62cce6..c297f3f204 100644 --- a/src/client/ocpp/json/JsonRestChargingStationClient.ts +++ b/src/client/ocpp/json/JsonRestChargingStationClient.ts @@ -152,7 +152,7 @@ export default class JsonRestChargingStationClient extends ChargingStationClient action: ServerAction.WS_REST_CLIENT_CONNECTION_ERROR, module: MODULE_NAME, method: 'onError', message: `Connection error to '${this.serverURL}: ${error.toString()}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); // Terminate WS in error this.terminateConnection(); diff --git a/src/client/ocpp/soap/SoapChargingStationClient.ts b/src/client/ocpp/soap/SoapChargingStationClient.ts index 72119fef95..0761f4b759 100644 --- a/src/client/ocpp/soap/SoapChargingStationClient.ts +++ b/src/client/ocpp/soap/SoapChargingStationClient.ts @@ -64,7 +64,7 @@ export default class SoapChargingStationClient extends ChargingStationClient { source: scsc.chargingStation.id, module: MODULE_NAME, method: 'getChargingStationClient', message: `Error when creating SOAP client: ${error.toString()}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); reject(`Error when creating SOAP client for charging station with ID ${scsc.chargingStation.id}: ${error.message}`); } else { diff --git a/src/client/oicp/CpoOICPClient.ts b/src/client/oicp/CpoOICPClient.ts index a7d9a2d877..ad3f1bd002 100644 --- a/src/client/oicp/CpoOICPClient.ts +++ b/src/client/oicp/CpoOICPClient.ts @@ -527,8 +527,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(pushEvseDataResponse, requestError), module: MODULE_NAME, method: 'pushEvseData', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, evse: payload, response: pushEvseDataResponse, } @@ -582,8 +581,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(pushEvseStatusResponse, requestError), module: MODULE_NAME, method: 'pushEvseStatus', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, evseStatus: payload, response: pushEvseStatusResponse, } @@ -640,8 +638,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(authorizeResponse, requestError), module: MODULE_NAME, method: 'authorizeStart', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, authorize: payload, response: authorizeResponse, } @@ -709,8 +706,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(authorizeResponse, requestError), module: MODULE_NAME, method: 'authorizeStop', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, authorize: payload, response: authorizeResponse, } @@ -816,8 +812,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(pushCdrResponse, requestError), module: MODULE_NAME, method: 'pushCdr', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, cdr: payload, response: pushCdrResponse, } @@ -892,9 +887,8 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(notificationStartResponse, requestError), module: MODULE_NAME, method: 'sendChargingNotificationStart', detailedMessages: { - error: requestError?.message, chargingStart: payload, - stack: requestError?.stack, + error: requestError?.stack, response: notificationStartResponse, } }); @@ -972,8 +966,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(notificationProgressResponse, requestError), module: MODULE_NAME, method: 'sendChargingNotificationProgress', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, chargingProgress: payload, response: notificationProgressResponse, } @@ -1066,8 +1059,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(notificationEndResponse, requestError), module: MODULE_NAME, method: 'sendChargingNotificationEnd', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, chargingEnd: payload, response: notificationEndResponse, } @@ -1139,8 +1131,7 @@ export default class CpoOICPClient extends OICPClient { message: this.buildOICPChargingNotificationErrorMessage(notificationErrorResponse, requestError), module: MODULE_NAME, method: 'sendChargingNotificationError', detailedMessages: { - error: requestError?.message, - stack: requestError?.stack, + error: requestError?.stack, chargingError: payload, response: notificationErrorResponse, } diff --git a/src/integration/asset/greencom/GreencomAssetIntegration.ts b/src/integration/asset/greencom/GreencomAssetIntegration.ts index bfff9c4022..24350cd02e 100644 --- a/src/integration/asset/greencom/GreencomAssetIntegration.ts +++ b/src/integration/asset/greencom/GreencomAssetIntegration.ts @@ -63,7 +63,7 @@ export default class GreencomAssetIntegration extends AssetIntegration method: 'retrieveConsumption', action: ServerAction.RETRIEVE_ASSET_CONSUMPTION, message: 'Error while retrieving the asset consumption', - detailedMessages: { request, token, error: error.message, stack: error.stack, asset } + detailedMessages: { request, token, error: error.stack, asset } }); } } diff --git a/src/integration/billing/BillingIntegration.ts b/src/integration/billing/BillingIntegration.ts index 8cbb7b92e4..b4a9f8734b 100644 --- a/src/integration/billing/BillingIntegration.ts +++ b/src/integration/billing/BillingIntegration.ts @@ -109,7 +109,7 @@ export default abstract class BillingIntegration { action: ServerAction.BILLING_SYNCHRONIZE_USER, module: MODULE_NAME, method: 'synchronizeUser', message: `Failed to synchronize user: '${user.id}' - '${user.email}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } else { @@ -153,7 +153,7 @@ export default abstract class BillingIntegration { action: ServerAction.BILLING_FORCE_SYNCHRONIZE_USER, module: MODULE_NAME, method: 'forceSynchronizeUser', message: `Failed to force the synchronization of user: '${user.id}' - '${user.email}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } return billingUser; @@ -195,7 +195,7 @@ export default abstract class BillingIntegration { for (const invoice of invoices.result) { try { // Skip invoices that are already PAID or not relevant for the current billing process - if (this.invoiceMustBeSkipped(invoice)) { + if (this.isInvoiceOutOfPeriodicOperationScope(invoice)) { continue; } // Make sure to avoid trying to charge it again too soon @@ -211,7 +211,11 @@ export default abstract class BillingIntegration { }); continue; } - await this.chargeInvoice(invoice); + const newInvoice = await this.chargeInvoice(invoice); + if (this.isInvoiceOutOfPeriodicOperationScope(newInvoice)) { + // The new invoice may now have a different status - and this impacts the pagination + skip--; // This is very important! + } await Logging.logInfo({ tenantID: this.tenant.id, source: Constants.CENTRAL_SERVER, @@ -230,7 +234,7 @@ export default abstract class BillingIntegration { actionOnUser: invoice.user, module: MODULE_NAME, method: 'chargeInvoices', message: `Failed to charge invoice '${invoice.id}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -275,7 +279,7 @@ export default abstract class BillingIntegration { action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'sendInvoiceNotification', message: `Failed to send notification for invoice '${billingInvoice.id}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); return false; } @@ -436,7 +440,7 @@ export default abstract class BillingIntegration { actionOnUser: invoice.user, module: MODULE_NAME, method: '_clearAllInvoiceTestData', message: `Failed to clear invoice test data - Invoice: '${invoice.id}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -481,7 +485,7 @@ export default abstract class BillingIntegration { action: ServerAction.BILLING_TEST_DATA_CLEANUP, module: MODULE_NAME, method: '_clearTransactionsTestData', message: 'Failed to clear transaction billing data', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } })); @@ -509,7 +513,7 @@ export default abstract class BillingIntegration { actionOnUser: user, module: MODULE_NAME, method: '_clearAllUsersTestData', message: `Failed to clear user test data - User: '${user.id}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -542,7 +546,7 @@ export default abstract class BillingIntegration { await UserStorage.saveUserBillingData(this.tenant.id, user.id, null); } - private invoiceMustBeSkipped(invoice: BillingInvoice): boolean { + private isInvoiceOutOfPeriodicOperationScope(invoice: BillingInvoice): boolean { if (invoice.status === BillingInvoiceStatus.DRAFT && this.settings.billing?.periodicBillingAllowed) { return false; } @@ -566,15 +570,25 @@ export default abstract class BillingIntegration { startDateTime = moment().date(0).date(1).startOf('day').toDate(); // 1st day of the previous month 00:00:00 (AM) endDateTime = moment().date(1).startOf('day').toDate(); // 1st day of this month 00:00:00 (AM) } + // Filter the invoice status based on the billing settings + let invoiceStatus; + if (this.settings.billing?.periodicBillingAllowed) { + // Let's finalize DRAFT invoices and trigger a payment attemnpt for unpaid invoices as well + invoiceStatus = [ BillingInvoiceStatus.DRAFT, BillingInvoiceStatus.OPEN ]; + } else { + // Let's trigger a new payment attemnpt for unpaid invoices + invoiceStatus = [ BillingInvoiceStatus.OPEN ]; + } // Now return the query parameters return { - // ------------------------------------------------------------------------------ - // ACHTUNG!!! Make sure not to filter on data which is changed while paginating! - // Filtering on the invoice status is not possible here - // ------------------------------------------------------------------------------ + // -------------------------------------------------------------------------------- + // ACHTUNG!!! Make sure to adapt the paging logic when the data used for filtering + // is also updated by the periodic operation + // -------------------------------------------------------------------------------- filter: { startDateTime, - endDateTime + endDateTime, + invoiceStatus }, limit, sort: { createdOn: 1 } // Sort by creation date - process the eldest first! diff --git a/src/integration/billing/stripe/StripeBillingIntegration.ts b/src/integration/billing/stripe/StripeBillingIntegration.ts index ac0ab41702..524e23c4ff 100644 --- a/src/integration/billing/stripe/StripeBillingIntegration.ts +++ b/src/integration/billing/stripe/StripeBillingIntegration.ts @@ -68,7 +68,7 @@ export default class StripeBillingIntegration extends BillingIntegration { module: MODULE_NAME, method: 'checkConnection', action: ServerAction.CHECK_BILLING_CONNECTION, message: 'Failed to connect to Stripe - Key is inconsistent', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } // Try to connect @@ -81,7 +81,7 @@ export default class StripeBillingIntegration extends BillingIntegration { module: MODULE_NAME, method: 'checkConnection', action: ServerAction.CHECK_BILLING_CONNECTION, message: 'Failed to connect to Stripe', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -235,7 +235,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TAXES, module: MODULE_NAME, method: 'getTaxes', message: 'Failed to retrieve tax rates', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } return taxes; @@ -257,7 +257,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TAXES, module: MODULE_NAME, method: 'getTaxRate', message: 'Failed to retrieve tax rate', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } return taxRate; @@ -301,7 +301,9 @@ export default class StripeBillingIntegration extends BillingIntegration { const { id: invoiceID, customer, number, livemode: liveMode, amount_due: amount, amount_paid: amountPaid, status, currency: invoiceCurrency, invoice_pdf: downloadUrl, metadata, hosted_invoice_url: payInvoiceUrl } = stripeInvoice; const customerID = customer as string; const currency = invoiceCurrency?.toUpperCase(); - const createdOn = moment.unix(stripeInvoice.created).toDate(); // epoch to Date! + // The invoice date may change when finalizing a DRAFT invoice + const epoch = stripeInvoice.status_transitions?.finalized_at || stripeInvoice.created; + const createdOn = moment.unix(epoch).toDate(); // epoch to Date! // Check metadata consistency - userID is mandatory! const userID = metadata?.userID; if (!userID) { @@ -421,7 +423,7 @@ export default class StripeBillingIntegration extends BillingIntegration { actionOnUser: billingInvoice.user, module: MODULE_NAME, method: 'chargeInvoice', message: `Payment attempt failed - stripe invoice: '${billingInvoice.invoiceID}'`, - detailedMessages: { error: operationResult.error.message, stack: operationResult.error.stack } + detailedMessages: { error: operationResult.error.stack } }); } } @@ -459,7 +461,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_PERFORM_OPERATIONS, module: MODULE_NAME, method: '_updateTransactionsBillingData', message: 'Failed to update transaction billing data', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } })); @@ -688,7 +690,7 @@ export default class StripeBillingIntegration extends BillingIntegration { actionOnUser: user, module: MODULE_NAME, method: '_getPaymentMethods', message: 'Failed to retrieve payment methods', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } return paymentMethods; @@ -728,7 +730,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_DELETE_PAYMENT_METHOD, module: MODULE_NAME, method: '_detachPaymentMethod', message: `Failed to detach payment method - customer '${customerID}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); // Send some feedback return { @@ -923,6 +925,25 @@ export default class StripeBillingIntegration extends BillingIntegration { status: BillingStatus.UNBILLED }; } + // Do not bill suspicious StopTransaction events + if (FeatureToggles.isFeatureActive(Feature.BILLING_CHECK_THRESHOLD_ON_STOP) && !Utils.isDevelopmentEnv()) { + // Suspicious StopTransaction may occur after a 'Housing temperature approaching limit' error on some charging stations + const timeSpent = this.computeTimeSpentInSeconds(transaction); + // TODO - make it part of the pricing or billing settings! + if (timeSpent < 60 /* seconds */ || transaction.stop.totalConsumptionWh < 1000 /* 1kWh */) { + await Logging.logWarning({ + tenantID: this.tenant.id, + user: transaction.userID, + source: Constants.CENTRAL_SERVER, + action: ServerAction.BILLING_TRANSACTION, + module: MODULE_NAME, method: 'stopTransaction', + message: `Transaction data is suspicious - billing operation has been aborted - transaction ID: ${transaction.id}` + }); + return { + status: BillingStatus.UNBILLED + }; + } + } // Create and Save async task await AsyncTaskManager.createAndSaveAsyncTasks({ name: AsyncTasks.BILL_TRANSACTION, @@ -970,7 +991,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'billTransaction', message: `Failed to bill the transaction - Transaction ID '${transaction.id}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } return { @@ -1137,7 +1158,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'billInvoiceItem', message: `Payment attempt failed - stripe invoice: '${stripeInvoice?.id}'`, - detailedMessages: { error: operationResult.error.message, stack: operationResult.error.stack } + detailedMessages: { error: operationResult.error.stack } }); } if (operationResult?.invoice) { @@ -1209,13 +1230,18 @@ export default class StripeBillingIntegration extends BillingIntegration { return transaction.user.locale ? transaction.user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-'); } - private convertTimeSpentToString(transaction: Transaction): string { + private computeTimeSpentInSeconds(transaction: Transaction): number { let totalDuration: number; 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 totalDuration; + } + + private convertTimeSpentToString(transaction: Transaction): string { + const totalDuration = this.computeTimeSpentInSeconds(transaction); return moment.duration(totalDuration, 's').format('h[h]mm', { trim: false }); } @@ -1410,7 +1436,7 @@ export default class StripeBillingIntegration extends BillingIntegration { module: MODULE_NAME, method: 'getStripeCustomer', action: ServerAction.BILLING, message: `Customer ID is inconsistent - ${customerID}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -1441,7 +1467,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'precheckStartTransactionPrerequisites', message: 'Stripe Prerequisites to start a transaction are not met', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); return [StartTransactionErrorCode.BILLING_NO_SETTINGS]; } @@ -1454,7 +1480,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'precheckStartTransactionPrerequisites', message: 'Billing setting prerequisites to start a transaction are not met', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); errorCodes.push(StartTransactionErrorCode.BILLING_NO_TAX); } @@ -1471,7 +1497,7 @@ export default class StripeBillingIntegration extends BillingIntegration { action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'precheckStartTransactionPrerequisites', message: `User prerequisites to start a transaction are not met - user: ${user.id}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); // TODO - return a more precise error code when payment method has expired errorCodes.push(StartTransactionErrorCode.BILLING_NO_PAYMENT_METHOD); diff --git a/src/integration/car/CarIntegration.ts b/src/integration/car/CarIntegration.ts index 1cfb1a967d..1a44e97ce9 100644 --- a/src/integration/car/CarIntegration.ts +++ b/src/integration/car/CarIntegration.ts @@ -92,7 +92,7 @@ export default abstract class CarIntegration { action: ServerAction.SYNCHRONIZE_CAR_CATALOGS, module: MODULE_NAME, method: 'synchronizeCarCatalogs', message: `${externalCar.id} - ${externalCar.vehicleMake} - ${externalCar.vehicleModel} got synchronization error`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/integration/car/ev-database/EVDatabaseCarIntegration.ts b/src/integration/car/ev-database/EVDatabaseCarIntegration.ts index bf8358a9d2..c8e9e0ca41 100644 --- a/src/integration/car/ev-database/EVDatabaseCarIntegration.ts +++ b/src/integration/car/ev-database/EVDatabaseCarIntegration.ts @@ -226,7 +226,7 @@ export default class EVDatabaseCarIntegration extends CarIntegration { action: ServerAction.SYNCHRONIZE_CAR_CATALOGS, module: MODULE_NAME, method: 'getCarCatalogThumb', message: `${carCatalog.id} - ${carCatalog.vehicleMake} - ${carCatalog.vehicleModel} - Cannot retrieve image from URL '${carCatalog.imageURLs[0]}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -246,7 +246,7 @@ export default class EVDatabaseCarIntegration extends CarIntegration { action: ServerAction.SYNCHRONIZE_CAR_CATALOGS, module: MODULE_NAME, method: 'getCarCatalogImage', message: `${carCatalog.id} - ${carCatalog.vehicleMake} - ${carCatalog.vehicleModel} - Cannot retrieve image from URL '${imageURL}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts b/src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts index b27d4812f7..280d71a25d 100644 --- a/src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts +++ b/src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts @@ -219,7 +219,7 @@ export default abstract class ChargingStationVendorIntegration { action: ServerAction.CHARGING_PROFILE_UPDATE, message: 'Error occurred while setting the Charging Profile', module: MODULE_NAME, method: 'setChargingProfile', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (!error.status) { throw error; @@ -312,7 +312,7 @@ export default abstract class ChargingStationVendorIntegration { action: ServerAction.CHARGING_PROFILE_DELETE, message: 'Error occurred while clearing the Charging Profile', module: MODULE_NAME, method: 'clearChargingProfile', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); throw error; } @@ -364,7 +364,7 @@ export default abstract class ChargingStationVendorIntegration { action: ServerAction.CHARGING_STATION_GET_COMPOSITE_SCHEDULE, message: 'Error occurred while getting the Composite Schedule', module: MODULE_NAME, method: 'getCompositeSchedule', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (!error.status) { throw error; @@ -463,7 +463,7 @@ export default abstract class ChargingStationVendorIntegration { action: ServerAction.GET_CONNECTOR_CURRENT_LIMIT, message: `Cannot retrieve the current limit on Connector ID '${connectorID}'`, module: MODULE_NAME, method: 'getCurrentConnectorLimit', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } // Default on current connector diff --git a/src/integration/refund/concur b/src/integration/refund/concur index 540976a46b..14450d0357 160000 --- a/src/integration/refund/concur +++ b/src/integration/refund/concur @@ -1 +1 @@ -Subproject commit 540976a46b52cad99ea96b4b1a3500a89076a4ab +Subproject commit 14450d03571c917d96170e8c5de5e06668b223f5 diff --git a/src/integration/smart-charging/SmartChargingIntegration.ts b/src/integration/smart-charging/SmartChargingIntegration.ts index 50c7f80e52..e691eacb84 100644 --- a/src/integration/smart-charging/SmartChargingIntegration.ts +++ b/src/integration/smart-charging/SmartChargingIntegration.ts @@ -66,7 +66,7 @@ export default abstract class SmartChargingIntegration { - const chargingStation = await ChargingStationStorage.getChargingStation(this.getTenantID(), this.getChargingStationID(), { issuer: true }); - if (chargingStation) { - // if (chargingStation?.registrationStatus === RegistrationStatus.ACCEPTED) { - await ChargingStationStorage.saveChargingStationLastSeen(this.getTenantID(), this.getChargingStationID(), - { - lastSeen: new Date() - }); + // Update once every 60s + if (!this.lastSeen || (Date.now() - this.lastSeen.getTime()) > Constants.LAST_SEEN_UPDATE_INTERVAL_MILLIS) { + // Update last seen + this.lastSeen = new Date(); + const chargingStation = await ChargingStationStorage.getChargingStation(this.getTenantID(), + this.getChargingStationID(), { issuer: true }, ['id']); + if (chargingStation) { + await ChargingStationStorage.saveChargingStationLastSeen(this.getTenantID(), this.getChargingStationID(), + { lastSeen: this.lastSeen }); + } } } } - diff --git a/src/server/ocpp/json/WSConnection.ts b/src/server/ocpp/json/WSConnection.ts index bfdd5bbdb5..6291e4337e 100644 --- a/src/server/ocpp/json/WSConnection.ts +++ b/src/server/ocpp/json/WSConnection.ts @@ -126,11 +126,14 @@ export default abstract class WSConnection { // Cloud Foundry? if (Configuration.isCloudFoundry()) { // Yes: Save the CF App and Instance ID to call the Charging Station from the Rest server - const chargingStation = await ChargingStationStorage.getChargingStation(this.tenantID, this.getChargingStationID()); + const chargingStation = await ChargingStationStorage.getChargingStation(this.tenantID, this.getChargingStationID(), {}, ['id']); if (chargingStation) { // Update CF Instance await ChargingStationStorage.saveChargingStationCFApplicationIDAndInstanceIndex( this.tenantID, chargingStation.id, Configuration.getCFApplicationIDAndInstanceIndex()); + // Update Last Seen + await ChargingStationStorage.saveChargingStationLastSeen(this.getTenantID(), + chargingStation.id, { lastSeen: new Date() }); } } } catch (error) { @@ -141,7 +144,7 @@ export default abstract class WSConnection { action: ServerAction.WS_CONNECTION, module: MODULE_NAME, method: 'initialize', message: `Invalid Tenant '${this.tenantID}' in URL '${this.getURL()}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/server/ocpp/services/OCPPService.ts b/src/server/ocpp/services/OCPPService.ts index d1d5361db3..b7cd5a11ab 100644 --- a/src/server/ocpp/services/OCPPService.ts +++ b/src/server/ocpp/services/OCPPService.ts @@ -116,7 +116,7 @@ export default class OCPPService { }; } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.BOOT_NOTIFICATION, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.BOOT_NOTIFICATION, error, { bootNotification }); // Reject return { status: RegistrationStatus.REJECTED, @@ -168,7 +168,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.HEARTBEAT, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.HEARTBEAT, error, { heartbeat }); return { currentTime: new Date().toISOString() }; @@ -205,7 +205,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.STATUS_NOTIFICATION, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.STATUS_NOTIFICATION, error, { statusNotification }); return {}; } } @@ -290,7 +290,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.METER_VALUES, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.METER_VALUES, error, { meterValues }); } return {}; } @@ -330,7 +330,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.AUTHORIZE, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.AUTHORIZE, error, { authorize }); // Rejected return { idTagInfo: { @@ -368,7 +368,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.DIAGNOSTICS_STATUS_NOTIFICATION, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.DIAGNOSTICS_STATUS_NOTIFICATION, error, { diagnosticsStatusNotification }); return {}; } } @@ -403,7 +403,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.FIRMWARE_STATUS_NOTIFICATION, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.FIRMWARE_STATUS_NOTIFICATION, error, { firmwareStatusNotification }); return {}; } } @@ -449,7 +449,8 @@ export default class OCPPService { source: chargingStation.id, module: MODULE_NAME, method: 'handleStartTransaction', action: ServerAction.START_TRANSACTION, user: user, - message: `${OCPPUtils.buildConnectorInfo(transaction.connectorId, transaction.id)} Transaction has been started successfully` + message: `${OCPPUtils.buildConnectorInfo(transaction.connectorId, transaction.id)} Transaction has been started successfully`, + detailedMessages: { transaction, startTransaction } }); // Accepted return { @@ -464,7 +465,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.START_TRANSACTION, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.START_TRANSACTION, error, { startTransaction }); // Invalid return { transactionId: 0, @@ -504,7 +505,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.CHARGING_STATION_DATA_TRANSFER, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.CHARGING_STATION_DATA_TRANSFER, error, { dataTransfer }); // Rejected return { status: OCPPDataTransferStatus.REJECTED @@ -559,7 +560,6 @@ export default class OCPPService { this.notifyStopTransaction(tenant, chargingStation, transaction, user, alternateUser); // Recompute the Smart Charging Plan await this.triggerSmartChargingStopTransaction(tenant, chargingStation, transaction); - // Log await Logging.logInfo({ tenantID: tenant.id, source: chargingStation.id, @@ -582,7 +582,7 @@ export default class OCPPService { } } catch (error) { this.addChargingStationToException(error, headers.chargeBoxIdentity); - await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.STOP_TRANSACTION, error); + await Logging.logActionExceptionMessage(headers.tenantID, ServerAction.STOP_TRANSACTION, error, { stopTransaction }); // Invalid return { idTagInfo: { @@ -657,7 +657,7 @@ export default class OCPPService { module: MODULE_NAME, method: 'triggerSmartChargingStopTransaction', action: ServerAction.STOP_TRANSACTION, message: `${OCPPUtils.buildConnectorInfo(transaction.connectorId, transaction.id)} Smart Charging exception occurred`, - detailedMessages: { error: error.message, stack: error.stack, transaction, chargingStation } + detailedMessages: { error: error.stack, transaction, chargingStation } }); } }, Constants.DELAY_SMART_CHARGING_EXECUTION_MILLIS); @@ -690,7 +690,7 @@ export default class OCPPService { action: ServerAction.CHARGING_PROFILE_DELETE, message: `${OCPPUtils.buildConnectorInfo(transaction.connectorId, transaction.id)} Cannot delete TX Charging Profile with ID '${chargingProfile.id}'`, module: MODULE_NAME, method: 'deleteAllTransactionTxProfile', - detailedMessages: { error: error.message, stack: error.stack, chargingProfile } + detailedMessages: { error: error.stack, chargingProfile } }); } } @@ -754,7 +754,7 @@ export default class OCPPService { module: MODULE_NAME, method: 'processSmartChargingStatusNotification', action: ServerAction.STATUS_NOTIFICATION, message: `${OCPPUtils.buildConnectorInfo(connector.connectorId, connector.currentTransactionID)} Smart Charging exception occurred`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -976,7 +976,7 @@ export default class OCPPService { module: MODULE_NAME, method: 'updateOCPIConnectorStatus', action: ServerAction.OCPI_PATCH_STATUS, message: `An error occurred while patching the charging station status of ${chargingStation.id}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -995,7 +995,7 @@ export default class OCPPService { module: MODULE_NAME, method: 'updateOICPConnectorStatus', action: ServerAction.OICP_UPDATE_EVSE_STATUS, message: `An error occurred while updating the charging station status of ${chargingStation.id}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/server/ocpp/utils/OCPPUtils.ts b/src/server/ocpp/utils/OCPPUtils.ts index be426e0b3b..76f1f51cd4 100644 --- a/src/server/ocpp/utils/OCPPUtils.ts +++ b/src/server/ocpp/utils/OCPPUtils.ts @@ -75,7 +75,7 @@ export default class OCPPUtils { user: transaction.userID, module: MODULE_NAME, method: 'processTransactionRoaming', message: `${OCPPUtils.buildConnectorInfo(transaction.connectorId, transaction.id)} Roaming exception occurred: ${error.message as string}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -284,7 +284,7 @@ export default class OCPPUtils { user: transaction.userID, action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'processTransactionBilling', - message, detailedMessages: { error: error.message, stack: error.stack } + message, detailedMessages: { error: error.stack } }); // Prevent from starting a transaction when Billing prerequisites are not met throw new BackendError({ @@ -292,7 +292,7 @@ export default class OCPPUtils { user: transaction.user, action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'processTransactionBilling', - message, detailedMessages: { error: error.message, stack: error.stack } + message, detailedMessages: { error: error.stack } }); } break; @@ -313,7 +313,7 @@ export default class OCPPUtils { user: transaction.userID, action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'processTransactionBilling', - message, detailedMessages: { error: error.message, stack: error.stack } + message, detailedMessages: { error: error.stack } }); } break; @@ -335,7 +335,7 @@ export default class OCPPUtils { user: transaction.userID, action: ServerAction.BILLING_TRANSACTION, module: MODULE_NAME, method: 'processTransactionBilling', - message, detailedMessages: { error: error.message, stack: error.stack } + message, detailedMessages: { error: error.stack } }); } break; @@ -645,6 +645,7 @@ export default class OCPPUtils { stopTransaction: OCPPStopTransactionRequestExtended, user: User, alternateUser: User, tagId: string): void { // Set final data transaction.stop = { + reason: stopTransaction.reason, meterStop: stopTransaction.meterStop, timestamp: Utils.convertToDate(stopTransaction.timestamp), userID: (alternateUser ? alternateUser.id : (user ? user.id : null)), @@ -1290,7 +1291,7 @@ export default class OCPPUtils { action: ServerAction.CHARGING_PROFILE_DELETE, module: MODULE_NAME, method: 'clearAndDeleteChargingProfilesForSiteArea', message: `Error while clearing the charging profile for chargingStation ${chargingProfile.chargingStationID}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); actionsResponse.inError++; } @@ -1335,7 +1336,7 @@ export default class OCPPUtils { action: ServerAction.CHARGING_PROFILE_DELETE, message: 'Error occurred while clearing the Charging Profile', module: MODULE_NAME, method: 'clearAndDeleteChargingProfile', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); throw error; } @@ -1695,7 +1696,7 @@ export default class OCPPUtils { action: ServerAction.CHARGING_STATION_CHANGE_CONFIGURATION, module: MODULE_NAME, method: 'updateChargingStationOcppParametersWithTemplate', message: `Error in changing ${!Utils.isUndefined(currentOcppParam) && 'non existent '}OCPP Parameter '${ocppParameter.key}' from '${currentOcppParam?.value}' to '${ocppParameter.value}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/server/odata/ODataRestAdapter.ts b/src/server/odata/ODataRestAdapter.ts index 98dc98a946..1798f5e2a4 100644 --- a/src/server/odata/ODataRestAdapter.ts +++ b/src/server/odata/ODataRestAdapter.ts @@ -102,7 +102,7 @@ export default class ODataRestAdapter { module: MODULE_NAME, method: 'query', action: ServerAction.ODATA_SERVER, message: error.message, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); cb(error); } diff --git a/src/server/odata/odata-schema/ODataSchema.ts b/src/server/odata/odata-schema/ODataSchema.ts index 6d14bcff07..24648668f6 100644 --- a/src/server/odata/odata-schema/ODataSchema.ts +++ b/src/server/odata/odata-schema/ODataSchema.ts @@ -48,7 +48,7 @@ export default class ODataSchema { source: 'ODataServer', method: 'getSchema', action: ServerAction.ODATA_SERVER, message: 'Unauthorized Access', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); res.send(StatusCodes.UNAUTHORIZED); return; diff --git a/src/server/oicp/AbstractOICPService.ts b/src/server/oicp/AbstractOICPService.ts index b24e4a3b5e..dacf95a27d 100644 --- a/src/server/oicp/AbstractOICPService.ts +++ b/src/server/oicp/AbstractOICPService.ts @@ -153,7 +153,7 @@ export default abstract class AbstractOICPService { module: MODULE_NAME, method: endpointName, message: `<< OICP Response Error ${req.method} ${req.originalUrl}`, action: ServerAction.OICP_ENDPOINT, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); await Logging.logActionExceptionMessage(req.user && req.user.tenantID ? req.user.tenantID : Constants.DEFAULT_TENANT, ServerAction.OICP_ENDPOINT, error); let errorCode: any = {}; diff --git a/src/server/rest/v1/service/AssetService.ts b/src/server/rest/v1/service/AssetService.ts index 2f3e785c4e..bd8cbed48d 100644 --- a/src/server/rest/v1/service/AssetService.ts +++ b/src/server/rest/v1/service/AssetService.ts @@ -233,7 +233,7 @@ export default class AssetService { module: MODULE_NAME, method: 'handleCheckAssetConnection', message: 'Asset connection failed', action: action, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); // Create fail response res.json(Object.assign({ connectionIsValid: false }, Constants.REST_RESPONSE_SUCCESS)); diff --git a/src/server/rest/v1/service/AuthService.ts b/src/server/rest/v1/service/AuthService.ts index b0e628ae08..8588bbe0b8 100644 --- a/src/server/rest/v1/service/AuthService.ts +++ b/src/server/rest/v1/service/AuthService.ts @@ -526,7 +526,7 @@ export default class AuthService { action: action, user: user, message: 'User cannot be created in the billing system', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/server/rest/v1/service/BillingService.ts b/src/server/rest/v1/service/BillingService.ts index b3761ab03a..32dff121c1 100644 --- a/src/server/rest/v1/service/BillingService.ts +++ b/src/server/rest/v1/service/BillingService.ts @@ -73,7 +73,7 @@ export default class BillingService { module: MODULE_NAME, method: 'handleClearBillingTestData', message: 'Failed to clear billing test data', action: action, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); const operationResult: BillingOperationResult = { succeeded: false, @@ -120,7 +120,7 @@ export default class BillingService { module: MODULE_NAME, method: 'handleCheckBillingConnection', message: 'Billing connection failed', action: action, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); res.json(Object.assign({ connectionIsValid: false }, Constants.REST_RESPONSE_SUCCESS)); } diff --git a/src/server/rest/v1/service/ChargingStationService.ts b/src/server/rest/v1/service/ChargingStationService.ts index 5738c10468..a2f7ca87d0 100644 --- a/src/server/rest/v1/service/ChargingStationService.ts +++ b/src/server/rest/v1/service/ChargingStationService.ts @@ -121,7 +121,7 @@ export default class ChargingStationService { action: action, user: req.user, message: `Unable to remove charging station ${chargingStation.id} from IOP`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -153,7 +153,7 @@ export default class ChargingStationService { action: action, user: req.user, message: `Unable to insert or remove charging station ${chargingStation.id} from HBS`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -310,7 +310,7 @@ export default class ChargingStationService { message: 'Error occurred while restarting the charging station', module: MODULE_NAME, method: 'handleUpdateChargingStationParams', user: req.user, actionOnUser: req.user, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -702,7 +702,7 @@ export default class ChargingStationService { message: 'Error occurred while clearing Charging Profile', module: MODULE_NAME, method: 'handleDeleteChargingProfile', user: req.user, actionOnUser: req.user, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } // Ok @@ -860,7 +860,7 @@ export default class ChargingStationService { action: action, user: req.user, message: `Unable to remove charging station ${chargingStation.id} from HBS`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -1114,7 +1114,7 @@ export default class ChargingStationService { action: action, message: `Firmware '${filteredRequest.FileName}' has not been found!`, module: MODULE_NAME, method: 'handleGetFirmware', - detailedMessages: { error: error.message, stack: error.stack }, + detailedMessages: { error: error.stack }, }); // Remove file related headers res.setHeader('Content-Type', 'text/plain'); @@ -1340,6 +1340,13 @@ export default class ChargingStationService { } // Filter const filteredRequest = ChargingStationValidator.getInstance().validateChargingStationsGetReq(req.query); + // Create GPS Coordinates + if (filteredRequest.LocLongitude && filteredRequest.LocLatitude) { + filteredRequest.LocCoordinates = [ + Utils.convertToFloat(filteredRequest.LocLongitude), + Utils.convertToFloat(filteredRequest.LocLatitude) + ]; + } // Check Users let userProject: string[] = []; if ((await Authorizations.canListUsers(req.user)).authorized) { @@ -1349,10 +1356,10 @@ export default class ChargingStationService { if (!projectFields) { projectFields = [ 'id', 'inactive', 'connectorsStatus', 'connectorsConsumption', 'public', 'firmwareVersion', 'chargePointVendor', 'chargePointModel', - 'ocppVersion', 'ocppProtocol', 'lastSeen', 'firmwareUpdateStatus', 'coordinates', 'issuer', 'voltage', + 'ocppVersion', 'ocppProtocol', 'lastSeen', 'firmwareUpdateStatus', 'coordinates', 'issuer', 'voltage', 'distanceMeters', 'siteAreaID', 'siteArea.id', 'siteArea.name', 'siteArea.siteID', 'siteArea.site.name', 'siteArea.address', 'siteID', 'maximumPower', 'powerLimitUnit', 'chargePointModel', 'chargePointSerialNumber', 'chargeBoxSerialNumber', 'connectors.connectorId', 'connectors.status', 'connectors.type', 'connectors.power', 'connectors.errorCode', - 'connectors.currentTotalConsumptionWh', 'connectors.currentInstantWatts', 'connectors.currentStateOfCharge', + 'connectors.currentTotalConsumptionWh', 'connectors.currentInstantWatts', 'connectors.currentStateOfCharge', 'connectors.info', 'connectors.currentTransactionID', 'connectors.currentTotalInactivitySecs', 'connectors.currentTagID', 'chargePoints', 'lastReboot', 'createdOn', ...userProject ]; @@ -1738,7 +1745,7 @@ export default class ChargingStationService { message: `OCPP Command '${command}' has failed`, module: MODULE_NAME, method: 'handleChargingStationCommand', user: user, - detailedMessages: { error: error.message, stack: error.stack, params } + detailedMessages: { error: error.stack, params } }); } } diff --git a/src/server/rest/v1/service/LoggingService.ts b/src/server/rest/v1/service/LoggingService.ts index 95c3694347..b4e728112f 100644 --- a/src/server/rest/v1/service/LoggingService.ts +++ b/src/server/rest/v1/service/LoggingService.ts @@ -34,11 +34,6 @@ export default class LoggingService { public static async handleGetLog(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise { // Filter const filteredRequest = LoggingSecurity.filterLogRequest(req.query); - // Get logs - const logging = await LoggingStorage.getLog(req.user.tenantID, filteredRequest.ID, [ - 'id', 'level', 'timestamp', 'type', 'source', 'host', 'process', 'action', 'message', - 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages' - ]); // Check auth if (!await Authorizations.canReadLog(req.user)) { throw new AppAuthError({ @@ -48,7 +43,11 @@ export default class LoggingService { module: MODULE_NAME, method: 'handleGetLog' }); } - // Return + // Get Log + const logging = await LoggingStorage.getLog(req.user.tenantID, filteredRequest.ID, [ + 'id', 'level', 'timestamp', 'type', 'source', 'host', 'process', 'action', 'message', + 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages' + ]); res.json(logging); next(); } diff --git a/src/server/rest/v1/service/SessionHashService.ts b/src/server/rest/v1/service/SessionHashService.ts index f2941feb19..0ab463ae91 100644 --- a/src/server/rest/v1/service/SessionHashService.ts +++ b/src/server/rest/v1/service/SessionHashService.ts @@ -1,5 +1,4 @@ import { NextFunction, Request, Response } from 'express'; -import User, { UserStatus } from '../../../../types/User'; import AppError from '../../../../exception/AppError'; import Constants from '../../../../utils/Constants'; @@ -9,6 +8,7 @@ import Logging from '../../../../utils/Logging'; import { ServerAction } from '../../../../types/Server'; import Tenant from '../../../../types/Tenant'; import TenantStorage from '../../../../storage/mongodb/TenantStorage'; +import User from '../../../../types/User'; import UserStorage from '../../../../storage/mongodb/UserStorage'; import Utils from '../../../../utils/Utils'; diff --git a/src/server/rest/v1/service/SiteAreaService.ts b/src/server/rest/v1/service/SiteAreaService.ts index 5ae49fc2b7..94f490880c 100644 --- a/src/server/rest/v1/service/SiteAreaService.ts +++ b/src/server/rest/v1/service/SiteAreaService.ts @@ -385,7 +385,7 @@ export default class SiteAreaService { module: MODULE_NAME, method: 'handleUpdateSiteArea', action: action, message: 'An error occurred while trying to call smart charging', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } finally { // Release lock diff --git a/src/server/rest/v1/service/TagService.ts b/src/server/rest/v1/service/TagService.ts index 027f52248c..0f9831f847 100644 --- a/src/server/rest/v1/service/TagService.ts +++ b/src/server/rest/v1/service/TagService.ts @@ -209,7 +209,7 @@ export default class TagService { action: action, module: MODULE_NAME, method: 'handleCreateTag', message: `Unable to synchronize tokens of user ${filteredRequest.userID} with IOP`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -352,7 +352,7 @@ export default class TagService { module: MODULE_NAME, method: 'handleUpdateTag', user: req.user, actionOnUser: user, message: `Unable to synchronize tokens of user ${filteredRequest.userID} with IOP`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -466,7 +466,7 @@ export default class TagService { action: action, user: req.user.id, message: `Exception while parsing the CSV '${filename}': ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (!res.headersSent) { res.writeHead(HTTPError.INVALID_FILE_FORMAT); @@ -539,7 +539,7 @@ export default class TagService { action: action, user: req.user.id, message: `Invalid Json file '${filename}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (!res.headersSent) { res.writeHead(HTTPError.INVALID_FILE_FORMAT); @@ -604,7 +604,7 @@ export default class TagService { action: action, user: user.id, message: `Cannot import ${error.writeErrors.length as number} tags!`, - detailedMessages: { error: error.message, stack: error.stack, tagsError: error.writeErrors } + detailedMessages: { error: error.stack, tagsError: error.writeErrors } }); } tagsToBeImported.length = 0; @@ -668,7 +668,7 @@ export default class TagService { module: MODULE_NAME, method: 'handleDeleteTags', action: action, message: `Unable to synchronize tokens of user ${tag.userID} with IOP`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -800,7 +800,7 @@ export default class TagService { module: MODULE_NAME, method: 'processTag', action: action, message: `User cannot be imported tag ${newImportedTag.id}`, - detailedMessages: { tag: newImportedTag, error: error.message, stack: error.stack } + detailedMessages: { tag: newImportedTag, error: error.stack } }); } // Save it later on @@ -812,7 +812,7 @@ export default class TagService { module: MODULE_NAME, method: 'importTag', action: action, message: `Tag ID '${importedTag.id}' cannot be imported`, - detailedMessages: { tag: importedTag, error: error.message, stack: error.stack } + detailedMessages: { tag: importedTag, error: error.stack } }); return false; } diff --git a/src/server/rest/v1/service/TransactionService.ts b/src/server/rest/v1/service/TransactionService.ts index 7d77d594b2..44251db6ba 100644 --- a/src/server/rest/v1/service/TransactionService.ts +++ b/src/server/rest/v1/service/TransactionService.ts @@ -50,8 +50,7 @@ export default class TransactionService { 'currentCumulatedPrice', 'currentInactivityStatus', 'roundedPrice', 'price', 'priceUnit', 'tagID', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', 'stop.meterStop', - 'billingData.stop.invoiceNumber', - 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', + 'billingData.stop.invoiceNumber', 'stop.reason', 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', ])); next(); } @@ -526,7 +525,7 @@ export default class TransactionService { 'id', 'chargeBoxID', 'timestamp', 'issuer', 'stateOfCharge', 'timezone', 'connectorId', 'meterStart', 'siteAreaID', 'siteID', 'currentTotalDurationSecs', 'currentTotalInactivitySecs', 'currentInstantWatts', 'currentTotalConsumptionWh', 'currentStateOfCharge', 'currentInactivityStatus', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', - 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', 'stop.pricingSource', + 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', 'stop.pricingSource', 'stop.reason', 'userID', ]; // Check Cars @@ -629,7 +628,7 @@ export default class TransactionService { 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email', 'roundedPrice', 'price', 'priceUnit', 'stop.userID', 'stop.user.id', 'stop.user.name', 'stop.user.firstName', 'stop.user.email', 'currentTotalDurationSecs', 'currentTotalInactivitySecs', 'currentInstantWatts', 'currentTotalConsumptionWh', 'currentStateOfCharge', - 'currentCumulatedPrice', 'currentInactivityStatus', 'signedData', + 'currentCumulatedPrice', 'currentInactivityStatus', 'signedData', 'stop.reason', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', 'stop.meterStop', 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', 'stop.pricingSource', 'stop.signedData', 'stop.tagID', 'tag.description', 'billingData.stop.status', 'billingData.stop.invoiceID', 'billingData.stop.invoiceStatus', 'billingData.stop.invoiceNumber', @@ -670,8 +669,7 @@ export default class TransactionService { 'currentTotalDurationSecs', 'currentTotalInactivitySecs', 'currentInstantWatts', 'currentTotalConsumptionWh', 'currentStateOfCharge', 'currentInactivityStatus', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', - 'billingData.stop.invoiceNumber', - 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', + 'billingData.stop.invoiceNumber', 'stop.reason', 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', ]); res.json(transactions); next(); @@ -708,8 +706,7 @@ export default class TransactionService { 'id', 'chargeBoxID', 'timestamp', 'issuer', 'stateOfCharge', 'timezone', 'connectorId', 'meterStart', 'siteAreaID', 'siteID', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', 'stop.meterStop', - 'billingData.stop.invoiceNumber', - 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', + 'billingData.stop.invoiceNumber', 'stop.reason', 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', ]); res.json(transactions); next(); @@ -727,9 +724,8 @@ export default class TransactionService { 'id', 'chargeBoxID', 'timestamp', 'issuer', 'stateOfCharge', 'timezone', 'connectorId', 'meterStart', 'siteAreaID', 'siteID', 'refundData.reportId', 'refundData.refundedAt', 'refundData.status', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', - 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', - 'billingData.stop.invoiceNumber', - 'tagID', 'stop.tagID', + 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', 'billingData.stop.invoiceNumber', + 'tagID', 'stop.tagID', 'stop.reason', ]); res.json(transactions); next(); @@ -795,8 +791,7 @@ export default class TransactionService { 'id', 'chargeBoxID', 'timestamp', 'issuer', 'stateOfCharge', 'timezone', 'connectorId', 'meterStart', 'siteAreaID', 'siteID', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', - 'billingData.stop.invoiceNumber', - 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', 'tag.description' + 'billingData.stop.invoiceNumber', 'stop.reason', 'ocpi', 'ocpiWithCdr', 'tagID', 'stop.tagID', 'tag.description' ]); } @@ -814,8 +809,7 @@ export default class TransactionService { 'refundData.reportId', 'refundData.refundedAt', 'refundData.status', 'stop.roundedPrice', 'stop.price', 'stop.priceUnit', 'stop.inactivityStatus', 'stop.stateOfCharge', 'stop.timestamp', 'stop.totalConsumptionWh', 'stop.totalDurationSecs', 'stop.totalInactivitySecs', 'stop.extraInactivitySecs', - 'billingData.stop.invoiceNumber', - 'tagID', 'stop.tagID', + 'billingData.stop.invoiceNumber', 'stop.reason', 'tagID', 'stop.tagID', ]); } @@ -864,6 +858,30 @@ export default class TransactionService { module: MODULE_NAME, method: 'handleGetTransactionsInError' }); } + let projectFields = [ + 'id', 'chargeBoxID', 'timestamp', 'issuer', 'stateOfCharge', 'timezone', 'connectorId', + 'meterStart', 'siteAreaID', 'siteID', 'errorCode', 'uniqueId', 'stop.totalConsumptionWh', + 'stop.totalDurationSecs' + ]; + // Check Users + if ((await Authorizations.canListUsers(req.user)).authorized) { + if (projectFields) { + projectFields = [ + ...projectFields, + 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email', 'tagID', + 'stop.userID', 'stop.user.id', 'stop.user.name', 'stop.user.firstName', 'stop.user.email', 'stop.tagID' + ]; + } + } + // Check Cars + if (Utils.isComponentActiveFromToken(req.user, TenantComponents.CAR)) { + if (await Authorizations.canListCars(req.user)) { + projectFields = [ + ...projectFields, + 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion', + ]; + } + } const filter: any = {}; // Filter const filteredRequest = TransactionSecurity.filterTransactionsInErrorRequest(req.query); @@ -886,10 +904,8 @@ export default class TransactionService { skip: filteredRequest.Skip, sort: filteredRequest.SortFields }, - [ - 'id', 'chargeBoxID', 'timestamp', 'issuer', 'stateOfCharge', 'timezone', 'connectorId', - 'meterStart', 'siteAreaID', 'siteID', 'errorCode', 'uniqueId' - ]); + projectFields + ); // Return res.json(transactions); next(); diff --git a/src/server/rest/v1/service/UserService.ts b/src/server/rest/v1/service/UserService.ts index c2ee712587..6ce598bea5 100644 --- a/src/server/rest/v1/service/UserService.ts +++ b/src/server/rest/v1/service/UserService.ts @@ -502,7 +502,7 @@ export default class UserService { action: action, user: req.user.id, message: `Exception while parsing the CSV '${filename}': ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (!res.headersSent) { res.writeHead(HTTPError.INVALID_FILE_FORMAT); @@ -575,7 +575,7 @@ export default class UserService { action: action, user: req.user.id, message: `Invalid Json file '${filename}'`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (!res.headersSent) { res.writeHead(HTTPError.INVALID_FILE_FORMAT); @@ -720,7 +720,7 @@ export default class UserService { action: action, user: user.id, message: `Cannot import ${error.writeErrors.length as number} users!`, - detailedMessages: { error: error.message, stack: error.stack, tagsError: error.writeErrors } + detailedMessages: { error: error.stack, tagsError: error.writeErrors } }); } usersToBeImported.length = 0; @@ -826,7 +826,7 @@ export default class UserService { module: MODULE_NAME, method: 'importUser', action: action, message: 'User cannot be imported', - detailedMessages: { user: importedUser, error: error.message, stack: error.stack } + detailedMessages: { user: importedUser, error: error.stack } }); return false; } @@ -848,7 +848,7 @@ export default class UserService { method: 'checkBillingErrorCodes', action: action, message: `Start Transaction checks failed for ${user.id}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); // Billing module is ON but the settings are not set or inconsistent errorCodes.push(StartTransactionErrorCode.BILLING_INCONSISTENT_SETTINGS); @@ -892,7 +892,7 @@ export default class UserService { message: 'Error occurred in billing system', module: MODULE_NAME, method: 'checkAndDeleteUserBilling', user: loggedUser, actionOnUser: user, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -927,7 +927,7 @@ export default class UserService { method: 'checkAndDeleteUserOCPI', action: ServerAction.USER_DELETE, message: `Unable to disable tokens of user ${user.id} with IOP`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -973,7 +973,7 @@ export default class UserService { module: MODULE_NAME, method: 'updateUserBilling', user: loggedUser, actionOnUser: user, message: 'User cannot be updated in billing system', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/server/rest/v1/validator/ChargingStationValidator.ts b/src/server/rest/v1/validator/ChargingStationValidator.ts index b2a16546ac..91ad36317f 100644 --- a/src/server/rest/v1/validator/ChargingStationValidator.ts +++ b/src/server/rest/v1/validator/ChargingStationValidator.ts @@ -62,12 +62,6 @@ export default class ChargingStationValidator extends SchemaValidator { public validateChargingStationsGetReq(data: any): HttpChargingStationsRequest { // Validate schema this.validate(this.chargingStationsGet, data); - if (data.LocLongitude && data.LocLatitude) { - data.LocCoordinates = [ - Utils.convertToFloat(data.LocLongitude), - Utils.convertToFloat(data.LocLatitude) - ]; - } return data; } diff --git a/src/start.ts b/src/start.ts index 6135aa921c..096ac125de 100644 --- a/src/start.ts +++ b/src/start.ts @@ -157,7 +157,7 @@ export default class Bootstrap { action: ServerAction.STARTUP, module: MODULE_NAME, method: 'start', message: 'Unexpected exception', - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -234,7 +234,7 @@ export default class Bootstrap { action: ServerAction.STARTUP, module: MODULE_NAME, method: 'startMasters', message: `Unexpected exception ${cluster.isWorker ? 'in worker ' + cluster.worker.id.toString() : 'in master'}: ${error.toString()}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } @@ -329,7 +329,7 @@ export default class Bootstrap { action: ServerAction.STARTUP, module: MODULE_NAME, method: 'startServersListening', message: `Unexpected exception ${cluster.isWorker ? 'in worker ' + cluster.worker.id.toString() : 'in master'}: ${error.toString()}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } } diff --git a/src/storage/mongodb/LoggingStorage.ts b/src/storage/mongodb/LoggingStorage.ts index 3e1d880bc3..df29bd7e1a 100644 --- a/src/storage/mongodb/LoggingStorage.ts +++ b/src/storage/mongodb/LoggingStorage.ts @@ -163,12 +163,7 @@ export default class LoggingStorage { aggregation.push({ $limit: Constants.DB_RECORD_COUNT_CEIL }); } const loggingsCountMDB = await global.database.getCollection(tenantID, 'logs') - .aggregate([...aggregation, { $count: 'count' }], { - collation: { - locale: Constants.DEFAULT_LOCALE, - strength: 2 - } - }) + .aggregate([...aggregation, { $count: 'count' }]) .toArray(); // Check if only the total count is requested if (dbParams.onlyRecordCount) { diff --git a/src/storage/mongodb/MongoDBStorage.ts b/src/storage/mongodb/MongoDBStorage.ts index 2e7801083e..2209703618 100644 --- a/src/storage/mongodb/MongoDBStorage.ts +++ b/src/storage/mongodb/MongoDBStorage.ts @@ -103,10 +103,10 @@ export default class MongoDBStorage { ]); // Tags await this.handleIndexesInCollection(tenantID, 'tags', [ + { fields: { visualID: 1 }, options: { unique: true } }, { fields: { deleted: 1, createdOn: 1 } }, { fields: { issuer: 1, createdOn: 1 } }, { fields: { userID: 1, issuer: 1 } }, - { fields: { visualID: 1 } } ]); // Sites/Users await this.handleIndexesInCollection(tenantID, 'siteusers', [ @@ -270,6 +270,10 @@ export default class MongoDBStorage { await this.handleIndexesInCollection(Constants.DEFAULT_TENANT, 'tenants', [ { fields: { subdomain: 1 }, options: { unique: true } }, ]); + // Performances + await this.handleIndexesInCollection(Constants.DEFAULT_TENANT, 'performances', [ + { fields: { timestamp: 1, group: 1, tenantID: 1 } }, + ]); // Users await this.handleIndexesInCollection(Constants.DEFAULT_TENANT, 'users', [ { fields: { email: 1 }, options: { unique: true } } @@ -342,17 +346,25 @@ export default class MongoDBStorage { // Indexes? if (indexes) { // Get current indexes - const databaseIndexes = await this.db.collection(tenantCollectionName).listIndexes().toArray(); + let databaseIndexes = await this.db.collection(tenantCollectionName).listIndexes().toArray(); // Drop indexes for (const databaseIndex of databaseIndexes) { if (databaseIndex.key._id) { continue; } - const foundIndex = indexes.find((index) => this.buildIndexName(index.fields) === databaseIndex.name); + let foundIndex = indexes.find((index) => this.buildIndexName(index.fields) === databaseIndex.name); + // Check DB unique index + const databaseIndexISUnique = !!databaseIndex?.unique; + const indexIsUnique = !!foundIndex.options?.unique; + if (indexIsUnique !== databaseIndexISUnique) { + // Delete the index + foundIndex = null; + } + // Delete the index if (!foundIndex) { if (Utils.isDevelopmentEnv()) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - console.log(`Drop index ${databaseIndex.name} on collection ${tenantID}.${name}`); + console.log(`Drop index ${databaseIndex.name} in collection ${tenantID}.${name}`); } try { await this.db.collection(tenantCollectionName).dropIndex(databaseIndex.key); @@ -362,10 +374,13 @@ export default class MongoDBStorage { } } } + // Get updated indexes + databaseIndexes = await this.db.collection(tenantCollectionName).listIndexes().toArray(); // Create indexes for (const index of indexes) { - const foundIndex = databaseIndexes.find((databaseIndex) => this.buildIndexName(index.fields) === databaseIndex.name); - if (!foundIndex) { + const foundDatabaseIndex = databaseIndexes.find((databaseIndex) => this.buildIndexName(index.fields) === databaseIndex.name); + // Create the index + if (!foundDatabaseIndex) { if (Utils.isDevelopmentEnv()) { console.log(`Create index ${JSON.stringify(index)} on collection ${tenantID}.${name}`); } diff --git a/src/storage/mongodb/MongoDBStorageNotification.ts b/src/storage/mongodb/MongoDBStorageNotification.ts index 0af32715e0..aa7705de1d 100644 --- a/src/storage/mongodb/MongoDBStorageNotification.ts +++ b/src/storage/mongodb/MongoDBStorageNotification.ts @@ -61,7 +61,7 @@ export default class MongoDBStorageNotification { action: ServerAction.DB_WATCH, module: MODULE_NAME, method: 'handleDBChangeStreamError', message: `Error occurred in watching database: ${error}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); } diff --git a/src/storage/mongodb/OCPPStorage.ts b/src/storage/mongodb/OCPPStorage.ts index 3fd3f3af3a..2907e60b5e 100644 --- a/src/storage/mongodb/OCPPStorage.ts +++ b/src/storage/mongodb/OCPPStorage.ts @@ -449,7 +449,7 @@ export default class OCPPStorage { module: MODULE_NAME, method: 'saveMeterValues', action: ServerAction.METER_VALUES, message: 'An error occurred while trying to save the meter value', - detailedMessages: { error: error.message, stack: error.stack, meterValue: meterValueToSave, meterValues: meterValuesToSave } + detailedMessages: { error: error.stack, meterValue: meterValueToSave, meterValues: meterValuesToSave } }); } } diff --git a/src/storage/mongodb/TransactionStorage.ts b/src/storage/mongodb/TransactionStorage.ts index 81052fa13f..1c030aaba8 100644 --- a/src/storage/mongodb/TransactionStorage.ts +++ b/src/storage/mongodb/TransactionStorage.ts @@ -111,6 +111,7 @@ export default class TransactionStorage { timestamp: Utils.convertToDate(transactionToSave.stop.timestamp), tagID: transactionToSave.stop.tagID, meterStop: transactionToSave.stop.meterStop, + reason: transactionToSave.stop.reason, transactionData: transactionToSave.stop.transactionData, stateOfCharge: Utils.convertToInt(transactionToSave.stop.stateOfCharge), signedData: transactionToSave.stop.signedData, @@ -895,7 +896,7 @@ export default class TransactionStorage { aggregation.push({ $match: match }); - // Charging Station? + // Charging Station if (params.withChargingStations || (params.errorType && params.errorType.includes(TransactionInErrorType.OVER_CONSUMPTION))) { // Add Charge Box @@ -905,11 +906,16 @@ export default class TransactionStorage { }); DatabaseUtils.pushConvertObjectIDToString(aggregation, 'chargeBox.siteAreaID'); } - // Add respective users + // User DatabaseUtils.pushUserLookupInAggregation({ tenantID, aggregation: aggregation, asField: 'user', localField: 'userID', foreignField: '_id', oneToOneCardinality: true, oneToOneCardinalityNotNull: false }); + // Car Catalog + DatabaseUtils.pushCarCatalogLookupInAggregation({ + tenantID: Constants.DEFAULT_TENANT, aggregation: aggregation, asField: 'carCatalog', localField: 'carCatalogID', + foreignField: '_id', oneToOneCardinality: true + }); // Used only in the error type : missing_user if (params.errorType && params.errorType.includes(TransactionInErrorType.MISSING_USER)) { // Site Area diff --git a/src/types/Transaction.ts b/src/types/Transaction.ts index ab2ed0874c..0d4cfafbe3 100644 --- a/src/types/Transaction.ts +++ b/src/types/Transaction.ts @@ -122,6 +122,7 @@ export interface CSPhasesUsed { export interface TransactionStop { timestamp: Date; meterStop: number; + reason?: string; tagID: string; userID: string; user?: User; diff --git a/src/types/requests/HttpChargingStationRequest.ts b/src/types/requests/HttpChargingStationRequest.ts index b344246233..e4b0acc50b 100644 --- a/src/types/requests/HttpChargingStationRequest.ts +++ b/src/types/requests/HttpChargingStationRequest.ts @@ -42,6 +42,8 @@ export interface HttpChargingStationsRequest extends HttpDatabaseRequest { SiteAreaID?: string; IncludeDeleted?: boolean; ErrorType?: string; + LocLongitude?: number; + LocLatitude?: number; LocCoordinates?: number[]; LocMaxDistanceMeters?: number; } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index b2e38c7f10..4c92d16042 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -17,6 +17,8 @@ export default class Constants { public static readonly AXIOS_DEFAULT_TIMEOUT = 60000; + public static readonly LAST_SEEN_UPDATE_INTERVAL_MILLIS = 60000; + public static readonly DC_CHARGING_STATION_DEFAULT_EFFICIENCY_PERCENT = 80; public static readonly AMPERAGE_DETECTION_THRESHOLD = 0.5; diff --git a/src/utils/FeatureToggles.ts b/src/utils/FeatureToggles.ts index 8a1d791bf7..fd1314a1ed 100644 --- a/src/utils/FeatureToggles.ts +++ b/src/utils/FeatureToggles.ts @@ -6,6 +6,7 @@ export enum Feature { BILLING_CHECK_USER_BILLING_DATA, BILLING_ITEM_WITH_PARKING_TIME, BILLING_ITEM_WITH_START_DATE, + BILLING_CHECK_THRESHOLD_ON_STOP, } export default class FeatureToggles { @@ -16,6 +17,7 @@ export default class FeatureToggles { Feature.BILLING_CHECK_USER_BILLING_DATA, // Feature.BILLING_ITEM_WITH_PARKING_TIME, Feature.BILLING_ITEM_WITH_START_DATE, + Feature.BILLING_CHECK_THRESHOLD_ON_STOP, ]; // Check whether the feature is active or not! diff --git a/src/utils/Logging.ts b/src/utils/Logging.ts index a801a062e1..6f5cad177c 100644 --- a/src/utils/Logging.ts +++ b/src/utils/Logging.ts @@ -67,7 +67,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module, method, message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); @@ -85,7 +85,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module, method, message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); @@ -325,7 +325,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module: MODULE_NAME, method: 'logExpressResponse', message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); @@ -343,7 +343,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module: MODULE_NAME, method: 'logExpressResponse', message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); @@ -425,7 +425,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module: MODULE_NAME, method: 'logAxiosResponse', message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); @@ -443,7 +443,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module: MODULE_NAME, method: 'logAxiosResponse', message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); @@ -551,18 +551,18 @@ export default class Logging { } // Used to log exception in catch(...) only - public static async logActionExceptionMessage(tenantID: string, action: ServerAction, exception: Error): Promise { + public static async logActionExceptionMessage(tenantID: string, action: ServerAction, exception: Error, detailedMessages = {}): Promise { // Log App Error if (exception instanceof AppError) { - await Logging._logActionAppExceptionMessage(tenantID, action, exception); + await Logging._logActionAppExceptionMessage(tenantID, action, exception, detailedMessages); // Log Backend Error } else if (exception instanceof BackendError) { - await Logging._logActionBackendExceptionMessage(tenantID, action, exception); + await Logging._logActionBackendExceptionMessage(tenantID, action, exception, detailedMessages); // Log Auth Error } else if (exception instanceof AppAuthError) { - await Logging._logActionAppAuthExceptionMessage(tenantID, action, exception); + await Logging._logActionAppAuthExceptionMessage(tenantID, action, exception, detailedMessages); } else { - await Logging._logActionExceptionMessage(tenantID, action, exception); + await Logging._logActionExceptionMessage(tenantID, action, exception, detailedMessages); } } @@ -599,8 +599,7 @@ export default class Logging { next(); } - private static async _logActionExceptionMessage(tenantID: string, action: ServerAction, exception: any): Promise { - // Log + private static async _logActionExceptionMessage(tenantID: string, action: ServerAction, exception: any, detailedMessages = {}): Promise { await Logging.logError({ tenantID: tenantID, type: LogType.SECURITY, @@ -610,11 +609,11 @@ export default class Logging { method: exception.method, action: action, message: exception.message, - detailedMessages: { stack: exception.stack } + detailedMessages: { stack: exception.stack, ...detailedMessages } }); } - private static async _logActionAppExceptionMessage(tenantID: string, action: ServerAction, exception: AppError): Promise { + private static async _logActionAppExceptionMessage(tenantID: string, action: ServerAction, exception: AppError, detailedMessages = {}): Promise { // Add Exception stack if (exception.params.detailedMessages) { exception.params.detailedMessages = { @@ -637,11 +636,11 @@ export default class Logging { method: exception.params.method, action: action, message: exception.message, - detailedMessages: exception.params.detailedMessages + detailedMessages: { ...exception.params.detailedMessages, ...detailedMessages } }); } - private static async _logActionBackendExceptionMessage(tenantID: string, action: ServerAction, exception: BackendError): Promise { + private static async _logActionBackendExceptionMessage(tenantID: string, action: ServerAction, exception: BackendError, detailedMessages = {}): Promise { // Add Exception stack if (exception.params.detailedMessages) { exception.params.detailedMessages = { @@ -664,24 +663,24 @@ export default class Logging { message: exception.message, user: exception.params.user, actionOnUser: exception.params.actionOnUser, - detailedMessages: exception.params.detailedMessages + detailedMessages: { ...exception.params.detailedMessages, ...detailedMessages } }); } // Used to check URL params (not in catch) - private static async _logActionAppAuthExceptionMessage(tenantID: string, action: ServerAction, exception: AppAuthError): Promise { + private static async _logActionAppAuthExceptionMessage(tenantID: string, action: ServerAction, exception: AppAuthError, detailedMessages = {}): Promise { // Log await Logging.logSecurityError({ tenantID: tenantID, type: LogType.SECURITY, user: exception.params.user, actionOnUser: exception.params.actionOnUser, - module: exception.params.module, - method: exception.params.method, + module: exception.params.module, method: exception.params.method, action: action, message: exception.message, detailedMessages: [{ - 'stack': exception.stack + stack: exception.stack, + ...detailedMessages }] }); } @@ -695,13 +694,12 @@ export default class Logging { user: user, tenantID: tenant, actionOnUser: error.params.actionOnUser, - module: module, - method: method, + module: module, method: method, action: action, message: error.message, detailedMessages: [{ - 'details': error.params.detailedMessages, - 'stack': error.stack + details: error.params.detailedMessages, + stack: error.stack }] }; } @@ -710,13 +708,12 @@ export default class Logging { user: user, tenantID: tenant, actionOnUser: error.actionOnUser, - module: module, - method: method, + module: module, method: method, action: action, message: error.message, detailedMessages: [{ - 'details': error.detailedMessages, - 'stack': error.stack + details: error.detailedMessages, + stack: error.stack }] }; } @@ -941,7 +938,7 @@ export default class Logging { action: ServerAction.PERFORMANCES, module, method: 'traceChargingStationActionEnd', message: `${message}: ${error.message}`, - detailedMessages: { error: error.message, stack: error.stack } + detailedMessages: { error: error.stack } }); if (Utils.isDevelopmentEnv()) { console.warn(chalk.yellow('====================================')); diff --git a/test/api/BillingStripeTestData.ts b/test/api/BillingStripeTestData.ts index 1b04d288ed..dbf39a96be 100644 --- a/test/api/BillingStripeTestData.ts +++ b/test/api/BillingStripeTestData.ts @@ -51,7 +51,8 @@ export default class StripeIntegrationTestData { ...Factory.user.build(), name: 'BILLING-TEST', firstName: 'Billing Integration Tests', - issuer: true + issuer: true, + locale: 'fr_FR' } as User; // Let's create a new user const userData = await this.adminUserService.createEntity( diff --git a/test/api/BillingTest.ts b/test/api/BillingTest.ts index 09118304bf..cc9230c06f 100644 --- a/test/api/BillingTest.ts +++ b/test/api/BillingTest.ts @@ -11,7 +11,7 @@ import Constants from '../../src/utils/Constants'; import ContextDefinition from './context/ContextDefinition'; import ContextProvider from './context/ContextProvider'; import Cypher from '../../src/utils/Cypher'; -import { DataResult } from '../types/DataResult'; +import { DataResult } from '../../src/types/DataResult'; import Factory from '../factories/Factory'; import MongoDBStorage from '../../src/storage/mongodb/MongoDBStorage'; import { ObjectID } from 'mongodb'; @@ -425,6 +425,29 @@ describe('Billing Service', function() { await testData.billingImpl.forceSynchronizeUser(basicUser); }); + xdescribe('Tune user profiles', () => { + // eslint-disable-next-line @typescript-eslint/require-await + before(async () => { + testData.userContext = testData.adminUserContext; + assert(testData.userContext, 'User context cannot be null'); + testData.userService = testData.adminUserService; + assert(!!testData.userService, 'User service cannot be null'); + // await testData.setBillingSystemValidCredentials(); + }); + + it('Should change admin user locale to fr_FR', async () => { + const user: User = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.DEFAULT_ADMIN); + const { id, email, name, firstName } = user; + await testData.userService.updateEntity(testData.userService.userApi, { id, email, name, firstName, locale: 'fr_FR' }, true); + }); + + it('Should change basic user locale to es_ES', async () => { + const user: User = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.BASIC_USER); + const { id, email, name, firstName } = user; + await testData.userService.updateEntity(testData.userService.userApi, { id, email, name, firstName, locale: 'es_ES' }, true); + }); + }); + describe('Where admin user (essential)', () => { // eslint-disable-next-line @typescript-eslint/require-await before(async () => {