pricing - fetch connector tariff
MODIFY
src/assets/server/rest/v1/schemas/user/user-default-tag-car-get.json
src/assets/server/rest/v1/schemas/user/user-default-tag-car-get.json
"$ref": "user#/definitions/id"
},
"ChargingStationID": {
- "$ref": "common#/definitions/string-id"
+ "$ref": "chargingstation#/definitions/id"
+ },
+ "ConnectorID": {
+ "$ref": "chargingstation#/definitions/connector/properties/connectorId"
},
"ProjectFields": {
"$ref": "common#/definitions/projectFields"

MODIFY
src/integration/pricing/PricingEngine.ts
src/integration/pricing/PricingEngine.ts
/* eslint-disable max-len */
-import PricingDefinition, { PricedConsumptionData, PricingEntity, PricingStaticRestriction, ResolvedPricingDefinition, ResolvedPricingModel } from '../../types/Pricing';
+import PricingDefinition, { PricedConsumptionData, PricingContext, PricingEntity, PricingStaticRestriction, ResolvedPricingDefinition, ResolvedPricingModel } from '../../types/Pricing';
-import ChargingStation from '../../types/ChargingStation';
import Constants from '../../utils/Constants';
import Consumption from '../../types/Consumption';
import ConsumptionPricer from './ConsumptionPricer';
@@ -10,7 +9,6 @@ import LoggingHelper from '../../utils/LoggingHelper';
import PricingStorage from '../../storage/mongodb/PricingStorage';
import { ServerAction } from '../../types/Server';
import Tenant from '../../types/Tenant';
-import Transaction from '../../types/Transaction';
import Utils from '../../utils/Utils';
import moment from 'moment';
@@ -18,19 +16,19 @@ const MODULE_NAME = 'PricingEngine';
export default class PricingEngine {
- public static async resolvePricingContext(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation): Promise<ResolvedPricingModel> {
+ public static async resolvePricingContext(pricingContext: PricingContext): Promise<ResolvedPricingModel> {
// Merge the pricing definitions from the different contexts
const pricingDefinitions: ResolvedPricingDefinition[] = [];
// pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction.userID));
- pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction, chargingStation, PricingEntity.CHARGING_STATION, transaction.chargeBoxID?.toString()));
- // pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction, chargingStation, PricingEntity.SITE_AREA, transaction.siteAreaID?.toString()));
- pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction, chargingStation, PricingEntity.SITE, transaction.siteID?.toString()));
- // pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction, chargingStation, PricingEntity.COMPANY, transaction.companyID?.toString()));
- pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction, chargingStation, PricingEntity.TENANT, tenant.id));
- if (!transaction.timezone) {
+ pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(pricingContext, PricingEntity.CHARGING_STATION, pricingContext.chargingStationID));
+ // pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, pricingContext, PricingEntity.SITE_AREA, transaction.siteAreaID));
+ pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(pricingContext, PricingEntity.SITE, pricingContext.siteID));
+ // pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, pricingContext, PricingEntity.COMPANY, transaction.companyID));
+ pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(pricingContext, PricingEntity.TENANT, pricingContext.tenant.id));
+ if (!pricingContext.timezone) {
await Logging.logWarning({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
+ ...LoggingHelper.getPricingContextProperties(pricingContext),
+ tenantID: pricingContext.tenant.id,
module: MODULE_NAME,
action: ServerAction.PRICING,
method: 'resolvePricingContext',
@@ -41,14 +39,14 @@ export default class PricingEngine {
const resolvedPricingModel: ResolvedPricingModel = {
pricerContext: {
flatFeeAlreadyPriced: false,
- sessionStartDate: transaction.timestamp,
- timezone: transaction.timezone
+ sessionStartDate: pricingContext.timestamp,
+ timezone: pricingContext.timezone
},
pricingDefinitions
};
await Logging.logInfo({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
+ ...LoggingHelper.getPricingContextProperties(pricingContext),
+ tenantID: pricingContext.tenant.id,
module: MODULE_NAME,
action: ServerAction.PRICING,
method: 'resolvePricingContext',
@@ -76,11 +74,11 @@ export default class PricingEngine {
return pricedData.filter((pricingConsumptionData) => !!pricingConsumptionData);
}
- private static async getPricingDefinitions4Entity(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, entityType: PricingEntity, entityID: string): Promise<ResolvedPricingDefinition[]> {
+ private static async getPricingDefinitions4Entity(pricingContext: PricingContext, entityType: PricingEntity, entityID: string): Promise<ResolvedPricingDefinition[]> {
if (!entityID) {
await Logging.logWarning({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
+ ...LoggingHelper.getPricingContextProperties(pricingContext),
+ tenantID: pricingContext.tenant.id,
module: MODULE_NAME,
action: ServerAction.PRICING,
method: 'getPricingDefinitions4Entity',
@@ -88,18 +86,18 @@ export default class PricingEngine {
});
return [];
}
- let pricingDefinitions = await PricingEngine.fetchPricingDefinitions4Entity(tenant, entityType, entityID);
+ let pricingDefinitions = await PricingEngine.fetchPricingDefinitions4Entity(pricingContext.tenant, entityType, entityID);
pricingDefinitions = pricingDefinitions || [];
const actualPricingDefinitions = pricingDefinitions.filter((pricingDefinition) =>
PricingEngine.checkEntityType(pricingDefinition, entityType)
).filter((pricingDefinition) =>
- PricingEngine.checkStaticRestrictions(pricingDefinition, transaction, chargingStation)
+ PricingEngine.checkStaticRestrictions(pricingDefinition, pricingContext)
).map((pricingDefinition) =>
PricingEngine.shrinkPricingDefinition(pricingDefinition)
);
await Logging.logDebug({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
+ ...LoggingHelper.getPricingContextProperties(pricingContext),
+ tenantID: pricingContext.tenant.id,
module: MODULE_NAME,
action: ServerAction.PRICING,
method: 'getPricingDefinitions4Entity',
@@ -124,12 +122,12 @@ export default class PricingEngine {
return (pricingDefinition.entityType === entityType) ? pricingDefinition : null;
}
- private static checkStaticRestrictions(pricingDefinition: PricingDefinition, transaction: Transaction, chargingStation: ChargingStation) : PricingDefinition {
+ private static checkStaticRestrictions(pricingDefinition: PricingDefinition, pricingContext: PricingContext) : PricingDefinition {
if (pricingDefinition.staticRestrictions) {
if (
- !PricingEngine.checkDateValidity(pricingDefinition.staticRestrictions, transaction)
- || !PricingEngine.checkConnectorType(pricingDefinition.staticRestrictions, transaction, chargingStation)
- || !PricingEngine.checkConnectorPower(pricingDefinition.staticRestrictions, transaction, chargingStation)
+ !PricingEngine.checkDateValidity(pricingDefinition.staticRestrictions, pricingContext)
+ || !PricingEngine.checkConnectorType(pricingDefinition.staticRestrictions, pricingContext)
+ || !PricingEngine.checkConnectorPower(pricingDefinition.staticRestrictions, pricingContext)
) {
return null;
}
@@ -152,23 +150,23 @@ export default class PricingEngine {
return resolvedPricingDefinition;
}
- private static checkDateValidity(staticRestrictions: PricingStaticRestriction, transaction: Transaction): boolean {
+ private static checkDateValidity(staticRestrictions: PricingStaticRestriction, pricingContext: PricingContext): boolean {
if (!Utils.isNullOrUndefined(staticRestrictions.validFrom)) {
- if (moment(transaction.timestamp).isBefore(staticRestrictions.validFrom)) {
+ if (moment(pricingContext.timestamp).isBefore(staticRestrictions.validFrom)) {
return false;
}
}
if (!Utils.isNullOrUndefined(staticRestrictions.validTo)) {
- if (moment(transaction.timestamp).isSameOrAfter(staticRestrictions.validTo)) {
+ if (moment(pricingContext.timestamp).isSameOrAfter(staticRestrictions.validTo)) {
return false;
}
}
return true;
}
- private static checkConnectorType(staticRestrictions: PricingStaticRestriction, transaction: Transaction, chargingStation: ChargingStation): boolean {
+ private static checkConnectorType(staticRestrictions: PricingStaticRestriction, pricingContext: PricingContext): boolean {
if (!Utils.isNullOrUndefined(staticRestrictions.connectorType)) {
- const connectorType = Utils.getConnectorFromID(chargingStation, transaction.connectorId)?.type;
+ const connectorType = Utils.getConnectorFromID(pricingContext.chargingStation, pricingContext.connectorId)?.type;
if (staticRestrictions.connectorType !== connectorType) {
return false;
}
@@ -176,9 +174,9 @@ export default class PricingEngine {
return true;
}
- private static checkConnectorPower(staticRestrictions: PricingStaticRestriction, transaction: Transaction, chargingStation: ChargingStation): boolean {
+ private static checkConnectorPower(staticRestrictions: PricingStaticRestriction, pricingContext: PricingContext): boolean {
if (!Utils.isNullOrUndefined(staticRestrictions.connectorPowerkW)) {
- const connectorPowerWatts = Utils.getConnectorFromID(chargingStation, transaction.connectorId)?.power;
+ const connectorPowerWatts = Utils.getConnectorFromID(pricingContext.chargingStation, pricingContext.connectorId)?.power;
if (!Utils.createDecimal(connectorPowerWatts).div(1000).equals(staticRestrictions.connectorPowerkW)) {
return false;
}

MODIFY
src/integration/pricing/PricingHelper.ts
src/integration/pricing/PricingHelper.ts
/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable max-len */
-import { DimensionType, PricedConsumptionData, PricedDimensionData, PricingDimensions } from '../../types/Pricing';
+import { DimensionType, PricedConsumptionData, PricedDimensionData, PricingContext, PricingDimensions } from '../../types/Pricing';
+import ChargingStation from '../../types/ChargingStation';
import Decimal from 'decimal.js';
+import Tenant from '../../types/Tenant';
+import Transaction from '../../types/Transaction';
+import User from '../../types/User';
import Utils from '../../utils/Utils';
export default class PricingHelper {
+ public static buildTransactionPricingContext(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation): PricingContext {
+ return {
+ tenant,
+ userID: transaction.userID,
+ companyID: transaction.companyID,
+ siteID : transaction.siteID,
+ siteAreaID: transaction.siteAreaID,
+ chargingStationID : transaction.chargeBoxID,
+ connectorId : transaction.connectorId,
+ timestamp : transaction.timestamp,
+ timezone : transaction.timezone,
+ chargingStation : chargingStation,
+ };
+ }
+
+ public static buildUserPricingContext(tenant: Tenant, user: User, chargingStation: ChargingStation, connectorId: number, timestamp: Date): PricingContext {
+ return {
+ tenant,
+ userID: user.id,
+ companyID: chargingStation.companyID,
+ siteID : chargingStation.siteID,
+ siteAreaID: chargingStation.siteAreaID,
+ chargingStationID : chargingStation.id,
+ connectorId : connectorId,
+ timestamp,
+ timezone : Utils.getTimezone(chargingStation.coordinates),
+ chargingStation,
+ };
+ }
+
+ public static checkContextConsistency(pricingContext: PricingContext): boolean {
+ if (pricingContext.tenant && pricingContext.userID && pricingContext.companyID && pricingContext.siteID && pricingContext.siteAreaID
+ && pricingContext.chargingStationID && pricingContext.chargingStation && pricingContext.chargingStation.coordinates
+ && pricingContext.connectorId && pricingContext.timestamp
+ && pricingContext.timestamp) {
+ return true;
+ }
+ return false;
+ }
+
public static accumulatePricingDimensions(pricingDimensions: PricingDimensions[]): { cumulatedAmount: number, cumulatedRoundedAmount: number } {
// Apply the same logic than the invoice to avoid rounding issues
const flatFee = PricingHelper.accumulatePricingDimension(pricingDimensions, DimensionType.FLAT_FEE);

MODIFY
src/integration/pricing/PricingIntegration.ts
src/integration/pricing/PricingIntegration.ts
+import { PricedConsumption, PricingContext, ResolvedPricingModel } from '../../types/Pricing';
+
import ChargingStation from '../../types/ChargingStation';
import Consumption from '../../types/Consumption';
-import { PricedConsumption } from '../../types/Pricing';
import { PricingSetting } from '../../types/Setting';
import Tenant from '../../types/Tenant';
import Transaction from '../../types/Transaction';
@@ -25,4 +26,7 @@ export default abstract class PricingIntegration<T extends PricingSetting> {
public abstract stopSession(transaction: Transaction, consumptionData: Consumption, chargingStation?: ChargingStation): Promise<PricedConsumption>;
public abstract endSession(transaction: Transaction, consumptionData: Consumption, chargingStation?: ChargingStation): Promise<PricedConsumption>;
+
+ public abstract resolvePricingContext(pricingContext: PricingContext): Promise<ResolvedPricingModel>;
}
+

MODIFY
src/integration/pricing/sap-convergent-charging/SapConvergentChargingPricingIntegration.ts
src/integration/pricing/sap-convergent-charging/SapConvergentChargingPricingIntegration.ts
import { ChargeableItemProperty, ConfirmationItem, ReservationItem, Type } from './model/ChargeableItem';
import { ChargingProfilePurposeType, Profile } from '../../../types/ChargingProfile';
-import { PricedConsumption, PricingSource } from '../../../types/Pricing';
+import { PricedConsumption, PricingContext, PricingSource, ResolvedPricingModel } from '../../../types/Pricing';
import { StartRateRequest, StopRateRequest, UpdateRateRequest } from './model/RateRequest';
import BackendError from '../../../exception/BackendError';
@@ -28,11 +28,16 @@ const MODULE_NAME = 'ConvergentChargingPricingIntegration';
export default class SapConvergentChargingPricingIntegration extends PricingIntegration<ConvergentChargingPricingSetting> {
public statefulChargingService: StatefulChargingService;
- constructor(tenant: Tenant, setting: ConvergentChargingPricingSetting) {
+ public constructor(tenant: Tenant, setting: ConvergentChargingPricingSetting) {
super(tenant, setting);
}
- public consumptionToChargeableItemProperties(transaction: Transaction, consumptionData: Consumption) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ public async resolvePricingContext(pricingContext: PricingContext): Promise<ResolvedPricingModel> {
+ return Promise.resolve(null);
+ }
+
+ public consumptionToChargeableItemProperties(transaction: Transaction, consumptionData: Consumption): any {
const timezone = transaction.timezone;
const startedAt = timezone ? moment.tz(consumptionData.startedAt, timezone) : moment.utc(consumptionData.startedAt).local();
const endedAt = timezone ? moment.tz(consumptionData.endedAt, timezone) : moment.utc(consumptionData.endedAt).local();
@@ -50,7 +55,7 @@ export default class SapConvergentChargingPricingIntegration extends PricingInte
];
}
- public computeSessionId(transaction: Transaction, consumptionData: Consumption) {
+ public computeSessionId(transaction: Transaction, consumptionData: Consumption): number {
const timestamp = transaction.timestamp instanceof Date ? transaction.timestamp : new Date(transaction.timestamp);
const dataId = consumptionData.userID + consumptionData.chargeBoxID + consumptionData.connectorId.toString() + timestamp.toISOString();
if (dataId.length === 0) {
@@ -163,7 +168,7 @@ export default class SapConvergentChargingPricingIntegration extends PricingInte
return null;
}
- public async handleError(transaction: Transaction, action: ServerAction, consumptionData: Consumption, result) {
+ public async handleError(transaction: Transaction, action: ServerAction, consumptionData: Consumption, result): Promise<void> {
const chargingResult = result.data.chargingResult;
const chargingStation: ChargingStation = await ChargingStationStorage.getChargingStation(this.tenant, transaction.chargeBoxID);
await Logging.logError({
@@ -196,7 +201,7 @@ export default class SapConvergentChargingPricingIntegration extends PricingInte
}
}
- public async handleAlertNotification(transaction: Transaction, consumptionData: Consumption, rateResult) {
+ public async handleAlertNotification(transaction: Transaction, consumptionData: Consumption, rateResult): Promise<void> {
let chargingStation: ChargingStation = null;
if (rateResult.transactionsToConfirm) {
for (const ccTransaction of rateResult.transactionsToConfirm.ccTransactions) {

MODIFY
src/integration/pricing/simple-pricing/BuiltInPricingIntegration.ts
src/integration/pricing/simple-pricing/BuiltInPricingIntegration.ts
/* eslint-disable max-len */
-import { PricedConsumption, PricingDimensions, PricingSource, ResolvedPricingDefinition, ResolvedPricingModel } from '../../../types/Pricing';
+import { PricedConsumption, PricingContext, PricingDimensions, PricingSource, ResolvedPricingDefinition, ResolvedPricingModel } from '../../../types/Pricing';
import ChargingStation from '../../../types/ChargingStation';
import Consumption from '../../../types/Consumption';
@@ -67,11 +67,30 @@ export default class BuiltInPricingIntegration extends PricingIntegration<Simple
return pricedConsumption;
}
+ public async resolvePricingContext(pricingContext: PricingContext): Promise<ResolvedPricingModel> {
+ if (!PricingHelper.checkContextConsistency(pricingContext)) {
+ await Logging.logError({
+ ...LoggingHelper.getPricingContextProperties(pricingContext),
+ tenantID: this.tenant.id,
+ module: MODULE_NAME,
+ action: ServerAction.PRICING,
+ method: 'resolvePricingContext',
+ message: 'Pricing context does not provide the required information'
+ });
+ return null;
+ }
+ const resolvedPricingModel: ResolvedPricingModel = await PricingEngine.resolvePricingContext(pricingContext);
+ // Fallback when no pricing definition matches
+ resolvedPricingModel.pricingDefinitions.push(this.getDefaultPricingDefinition());
+ return resolvedPricingModel;
+ }
+
private async computePrice(transaction: Transaction, consumptionData: Consumption, chargingStation: ChargingStation): Promise<PricedConsumption> {
let pricingModel = transaction.pricingModel;
if (!pricingModel) {
// This should happen only on the first call (i.e.: on a start transaction)
- pricingModel = await this.resolvePricingContext(this.tenant, transaction, chargingStation);
+ const pricingContext = PricingHelper.buildTransactionPricingContext(this.tenant, transaction, chargingStation);
+ pricingModel = await this.resolvePricingContext(pricingContext);
}
const pricingConsumptionData = PricingEngine.priceConsumption(this.tenant, pricingModel, consumptionData);
const { flatFee, energy, chargingTime, parkingTime } = pricingConsumptionData;
@@ -105,13 +124,6 @@ export default class BuiltInPricingIntegration extends PricingIntegration<Simple
return PricingHelper.accumulatePricingDimensions(allDimensions);
}
- private async resolvePricingContext(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation): Promise<ResolvedPricingModel> {
- const resolvedPricingModel: ResolvedPricingModel = await PricingEngine.resolvePricingContext(tenant, transaction, chargingStation);
- // Fallback when no pricing definition matches
- resolvedPricingModel.pricingDefinitions.push(this.getDefaultPricingDefinition());
- return resolvedPricingModel;
- }
-
private getDefaultPricingDefinition(): ResolvedPricingDefinition {
const simplePricingDefinition: ResolvedPricingDefinition = {
name: 'No Tariff',

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
-import { Action, AuthorizationFilter, Entity } from '../../../../types/Authorization';
+import { Action, Entity } from '../../../../types/Authorization';
import { ActionsResponse, ImportStatus } from '../../../../types/GlobalType';
import { AsyncTaskType, AsyncTasks } from '../../../../types/AsyncTask';
import Busboy, { FileInfo } from 'busboy';
@@ -6,6 +6,7 @@ import { Car, CarType } from '../../../../types/Car';
import { DataResult, UserDataResult } from '../../../../types/DataResult';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
+import { ResolvedPricingDefinition, ResolvedPricingModel } from '../../../../types/Pricing';
import Tenant, { TenantComponents } from '../../../../types/Tenant';
import User, { ImportedUser, UserRequiredImportProperties, UserRole } from '../../../../types/User';
@@ -17,6 +18,7 @@ import Authorizations from '../../../../authorization/Authorizations';
import BillingFactory from '../../../../integration/billing/BillingFactory';
import CSVError from 'csvtojson/v2/CSVError';
import CarStorage from '../../../../storage/mongodb/CarStorage';
+import ChargingStation from '../../../../types/ChargingStation';
import ChargingStationStorage from '../../../../storage/mongodb/ChargingStationStorage';
import Constants from '../../../../utils/Constants';
import EmspOCPIClient from '../../../../client/ocpi/EmspOCPIClient';
@@ -30,9 +32,10 @@ import OCPIClientFactory from '../../../../client/ocpi/OCPIClientFactory';
import { OCPIRole } from '../../../../types/ocpi/OCPIRole';
import { OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
import OCPIUtils from '../../../ocpi/OCPIUtils';
+import PricingFactory from '../../../../integration/pricing/PricingFactory';
+import PricingHelper from '../../../../integration/pricing/PricingHelper';
import { Readable } from 'stream';
import { ServerAction } from '../../../../types/Server';
-import SiteStorage from '../../../../storage/mongodb/SiteStorage';
import { StartTransactionErrorCode } from '../../../../types/Transaction';
import TagStorage from '../../../../storage/mongodb/TagStorage';
import { UserInErrorType } from '../../../../types/InError';
@@ -83,23 +86,29 @@ export default class UserService {
}
}
let withBillingChecks = true ;
+ let pricingDefinitions = null;
if (filteredRequest.ChargingStationID) {
// TODO - The ChargingStationID is optional but only for backward compatibility reasons - make it mandatory as soon as possible
const chargingStation = await ChargingStationStorage.getChargingStation(req.tenant, filteredRequest.ChargingStationID,
{ withSiteArea: true },
- ['id', 'siteArea.id', 'siteArea.accessControl']);
+ ['id', 'companyID', 'siteID', 'siteAreaID', 'coordinates', 'siteArea.accessControl']);
if (!chargingStation.siteArea.accessControl) {
// The access control is switched off - so billing checks are useless
withBillingChecks = false;
}
+ if (filteredRequest.ConnectorId) {
+ // Fetch all pricing definitions matching the current context + connectorId
+ pricingDefinitions = await UserService.fetchPricingDefinitions(action, req.tenant, req.user, user, chargingStation, filteredRequest.ConnectorId);
}
+ }
+ // Check for billing errors
const errorCodes: Array<StartTransactionErrorCode> = [];
if (withBillingChecks) {
// Check for the billing prerequisites (such as the user's payment method)
await UserService.checkBillingErrorCodes(action, req.tenant, req.user, user, errorCodes);
}
res.json({
- tag, car, errorCodes
+ tag, car, errorCodes, pricingDefinitions
});
next();
}
@@ -818,6 +827,21 @@ export default class UserService {
}
}
+ // await UserService.fetchPricingDefinitions(action, req.tenant, req.user, user, filteredRequest.ChargingStationID, filteredRequest.args?.connectorId, pricingDefinitions);
+ private static async fetchPricingDefinitions(action: ServerAction, tenant: Tenant,
+ loggedUser: UserToken, user: User, chargingStation: ChargingStation, connectorId: number) : Promise<ResolvedPricingDefinition[]> {
+
+ if (Utils.isComponentActiveFromToken(loggedUser, TenantComponents.PRICING)) {
+ const pricingImpl = await PricingFactory.getPricingImpl(tenant);
+ if (pricingImpl) {
+ const pricingContext = PricingHelper.buildUserPricingContext(tenant, user, chargingStation, connectorId, new Date());
+ const pricingModel: ResolvedPricingModel = await pricingImpl.resolvePricingContext(pricingContext);
+ return pricingModel.pricingDefinitions;
+ }
+ }
+ return null;
+ }
+
private static async checkBillingErrorCodes(action: ServerAction, tenant: Tenant,
loggedUser: UserToken, user: User, errorCodes: StartTransactionErrorCode[]) {
if (Utils.isComponentActiveFromToken(loggedUser, TenantComponents.BILLING)) {

MODIFY
src/types/Pricing.ts
src/types/Pricing.ts
+import ChargingStation, { ConnectorType } from './ChargingStation';
+
import { AuthorizationActions } from './Authorization';
-import { ConnectorType } from './ChargingStation';
import CreatedUpdatedProps from './CreatedUpdatedProps';
import Decimal from 'decimal.js';
-import Utils from '../utils/Utils';
+import Tenant from './Tenant';
export enum PricingEntity {
TENANT = 'Tenant',
@@ -20,6 +21,19 @@ export enum DimensionType {
PARKING_TIME = 'parkingTime'
}
+export interface PricingContext {
+ tenant: Tenant,
+ userID: string
+ companyID: string,
+ siteID: string,
+ siteAreaID: string,
+ chargingStationID: string,
+ connectorId: number,
+ timezone: string,
+ timestamp: Date,
+ chargingStation: ChargingStation
+}
+
export interface ResolvedPricingModel {
// Put there only the information that is to be kept with the Transaction
pricingDefinitions: ResolvedPricingDefinition[];

MODIFY
src/types/requests/HttpUserRequest.ts
src/types/requests/HttpUserRequest.ts
@@ -103,4 +103,5 @@ export interface HttpEulaRequest {
export interface HttpUserDefaultTagCar {
UserID: string;
ChargingStationID?: string; // TODO: Backward-compatibility issue - This should be mandatory! - change it as soon as possible
+ ConnectorId?: number // TODO: Backward-compatibility issue - This should be mandatory! - change it as soon as possible
}

MODIFY
src/utils/LoggingHelper.ts
src/utils/LoggingHelper.ts
import Asset from '../types/Asset';
import { Car } from '../types/Car';
import ChargingStation from '../types/ChargingStation';
+import { PricingContext } from '../types/Pricing';
import RegistrationToken from '../types/RegistrationToken';
import Site from '../types/Site';
import SiteArea from '../types/SiteArea';
@@ -11,6 +12,16 @@ import WSWrapper from '../server/ocpp/json/WSWrapper';
export default class LoggingHelper {
+ public static getPricingContextProperties(pricingContext: PricingContext): { siteID: string; siteAreaID: string; companyID: string; chargingStationID: string; actionOnUser: string } {
+ return {
+ siteID: pricingContext.siteID,
+ siteAreaID: pricingContext.siteAreaID,
+ companyID: pricingContext.companyID,
+ chargingStationID: pricingContext.chargingStationID,
+ actionOnUser: pricingContext.userID,
+ };
+ }
+
public static getTransactionProperties(transaction: Transaction): { siteID: string; siteAreaID: string; companyID: string; chargingStationID: string; actionOnUser: User } {
return {
siteID: transaction.siteID,

pricing - fetch connector tariff