Pricing - ResolvedPricingModel in transactions
MODIFY
src/integration/pricing/PricingFactory.ts
src/integration/pricing/PricingFactory.ts
@@ -30,11 +30,11 @@ export default class PricingFactory {
case PricingSettingsType.SIMPLE:
// Simple Pricing implementation
if (FeatureToggles.isFeatureActive(Feature.PRICING_NEW_MODEL)) {
- pricingIntegrationImpl = new SimplePricingIntegration(tenant, pricingSetting.simple);
- } else {
// TODO - to be clarified - feature hidden behind a feature toggle for now!
// Do we need a dedicated PricingSettingsType?
pricingIntegrationImpl = new BuiltInPricingIntegration(tenant, pricingSetting.simple);
+ } else {
+ pricingIntegrationImpl = new SimplePricingIntegration(tenant, pricingSetting.simple);
}
break;
default:

MODIFY
src/integration/pricing/simple-pricing/BuiltInPricingIntegration.ts
src/integration/pricing/simple-pricing/BuiltInPricingIntegration.ts
-import { PricedConsumption, PricingSource } from '../../../types/Pricing';
+import { BuiltInPricedConsumption, PricedConsumption, PricingSource, ResolvedPricingModel } from '../../../types/Pricing';
import Consumption from '../../../types/Consumption';
import PricingIntegration from '../PricingIntegration';
+import PricingStorage from '../../../storage/mongodb/PricingStorage';
import { SimplePricingSetting } from '../../../types/Setting';
import Tenant from '../../../types/Tenant';
import Transaction from '../../../types/Transaction';
import Utils from '../../../utils/Utils';
-export default class SimplePricingIntegration extends PricingIntegration<SimplePricingSetting> {
+export default class BuiltInPricingIntegration extends PricingIntegration<SimplePricingSetting> {
constructor(tenant: Tenant, readonly settings: SimplePricingSetting) {
super(tenant, settings);
}
public async startSession(transaction: Transaction, consumptionData: Consumption): Promise<PricedConsumption> {
- return this.computePrice(transaction, consumptionData);
+ const pricedConsumption = await this.computePrice(transaction, consumptionData);
+ const pricingModel = await this.resolvePricingContext(transaction);
+ const builtInPricedConsumption: BuiltInPricedConsumption = {
+ ...pricedConsumption,
+ pricingModel
+ };
+ return Promise.resolve(builtInPricedConsumption);
}
public async updateSession(transaction: Transaction, consumptionData: Consumption): Promise<PricedConsumption> {
@@ -24,7 +31,6 @@ export default class SimplePricingIntegration extends PricingIntegration<SimpleP
return this.computePrice(transaction, consumptionData);
}
- // eslint-disable-next-line @typescript-eslint/require-await
private async computePrice(transaction: Transaction, consumptionData: Consumption): Promise<PricedConsumption> {
let amount: number;
let roundedAmount: number;
@@ -42,6 +48,36 @@ export default class SimplePricingIntegration extends PricingIntegration<SimpleP
currencyCode: this.settings.currency,
cumulatedAmount: transaction.currentCumulatedPrice ? Utils.createDecimal(transaction.currentCumulatedPrice).plus(amount).toNumber() : amount
};
- return pricedConsumption;
+ return Promise.resolve(pricedConsumption);
+ }
+
+ private async resolvePricingContext(transaction: Transaction): Promise<ResolvedPricingModel> {
+ // -----------------------------------------------------------------------------------------
+ // TODO - We need to find the pricing model to apply by resolving the hierarchy of contexts
+ // that may override (or extend) the pricing definitions.
+ // Forseen hierarchy is:
+ // - Tenant/Organization
+ // - Company
+ // - Site
+ // - Site Area
+ // - Charging Station
+ // - User Group
+ // - User
+ // Of course, the date has an impact as well
+ // -----------------------------------------------------------------------------------------
+ // First implementation:
+ // - we only have a single pricing model which is defined for the tenant
+ // - we simply get the latest created one
+ // -----------------------------------------------------------------------------------------
+ let pricingModel: ResolvedPricingModel = null;
+ const pricingModelResults = await PricingStorage.getPricingModels(this.tenant, {}, { limit: 1, skip: 0, sort: { createdOn: -1 } });
+ if (pricingModelResults.count > 0) {
+ const { pricingDefinitions } = pricingModelResults.result[0];
+ pricingModel = {
+ pricingDefinitions
+ };
+ }
+ // TODO - No pricing definition? => Throw an exception ?
+ return Promise.resolve(pricingModel);
}
}

MODIFY
src/server/ocpp/utils/OCPPUtils.ts
src/server/ocpp/utils/OCPPUtils.ts
import { BillingDataTransactionStart, BillingDataTransactionStop } from '../../../types/Billing';
+import { BuiltInPricedConsumption, PricedConsumption } from '../../../types/Pricing';
import { ChargingProfile, ChargingProfilePurposeType } from '../../../types/ChargingProfile';
import ChargingStation, { ChargingStationCapabilities, ChargingStationOcppParameters, ChargingStationTemplate, Connector, ConnectorCurrentLimitSource, CurrentType, OcppParameter, SiteAreaLimitSource, StaticLimitAmps, TemplateUpdate, TemplateUpdateResult } from '../../../types/ChargingStation';
+import FeatureToggles, { Feature } from '../../../utils/FeatureToggles';
import { OCPPChangeConfigurationCommandParam, OCPPChangeConfigurationCommandResult, OCPPChargingProfileStatus, OCPPConfigurationStatus, OCPPGetConfigurationCommandParam, OCPPGetConfigurationCommandResult, OCPPResetCommandResult, OCPPResetStatus, OCPPResetType } from '../../../types/ocpp/OCPPClient';
import { OCPPMeasurand, OCPPNormalizedMeterValue, OCPPPhase, OCPPReadingContext, OCPPStopTransactionRequestExtended, OCPPUnitOfMeasure, OCPPValueFormat } from '../../../types/ocpp/OCPPServer';
import { OICPIdentification, OICPSessionID } from '../../../types/oicp/OICPIdentification';
@@ -28,7 +30,6 @@ import OCPPStorage from '../../../storage/mongodb/OCPPStorage';
import OICPClientFactory from '../../../client/oicp/OICPClientFactory';
import { OICPRole } from '../../../types/oicp/OICPRole';
import OICPUtils from '../../oicp/OICPUtils';
-import { PricedConsumption } from '../../../types/Pricing';
import PricingFactory from '../../../integration/pricing/PricingFactory';
import { PricingSettingsType } from '../../../types/Setting';
import RegistrationToken from '../../../types/RegistrationToken';
@@ -252,6 +253,9 @@ export default class OCPPUtils {
transaction.priceUnit = pricedConsumption.currencyCode;
transaction.pricingSource = pricedConsumption.pricingSource;
transaction.currentCumulatedPrice = pricedConsumption.amount;
+ if (FeatureToggles.isFeatureActive(Feature.PRICING_NEW_MODEL)) {
+ transaction.pricingModel = (pricedConsumption as BuiltInPricedConsumption)?.pricingModel;
+ }
}
break;
// Meter Values

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -69,6 +69,7 @@ export default class TransactionStorage {
roundedPrice: Utils.convertToFloat(transactionToSave.roundedPrice),
priceUnit: transactionToSave.priceUnit,
pricingSource: transactionToSave.pricingSource,
+ pricingModel: transactionToSave.pricingModel,
stateOfCharge: transactionToSave.stateOfCharge,
timezone: transactionToSave.timezone,
signedData: transactionToSave.signedData,

MODIFY
src/types/Pricing.ts
src/types/Pricing.ts
@@ -7,6 +7,11 @@ export default interface PricingModel extends CreatedUpdatedProps, Authorization
pricingDefinitions: PricingDefinition[];
}
+export interface ResolvedPricingModel {
+ // Put there only the information that is to be kept with the Transaction
+ pricingDefinitions: PricingDefinition[];
+}
+
export interface PricingDefinition {
name: string, // Short marketing name - e.g.: BLUE Tariff,
description: string, // A long description to explain it, e.g.: Time-based pricing for low charging stations
@@ -58,6 +63,10 @@ export interface PricedConsumption {
pricingSource: PricingSource;
}
+export interface BuiltInPricedConsumption extends PricedConsumption {
+ pricingModel: ResolvedPricingModel
+}
+
export enum PricingSource {
SIMPLE = 'simple',
CONVERGENT_CHARGING = 'convergentCharging',

MODIFY
src/types/Transaction.ts
src/types/Transaction.ts
import { Car, CarCatalog } from './Car';
import { ChargePointStatus, OCPP15TransactionData, OCPPMeterValue } from './ocpp/OCPPServer';
import Consumption, { AbstractCurrentConsumption } from './Consumption';
+import PricingModel, { ResolvedPricingModel } from './Pricing';
import ChargingStation from '../types/ChargingStation';
import Company from './Company';
@@ -8,7 +9,6 @@ import { OCPICdr } from './ocpi/OCPICdr';
import { OCPISession } from './ocpi/OCPISession';
import { OICPChargeDetailRecord } from './oicp/OICPChargeDetailRecord';
import { OICPSession } from './oicp/OICPSession';
-import PricingModel from './Pricing';
import Site from './Site';
import SiteArea from './SiteArea';
import Tag from './Tag';
@@ -82,7 +82,7 @@ export default interface Transaction extends AbstractCurrentConsumption {
roundedPrice?: number;
priceUnit?: string;
pricingSource?: string;
- pricingModel?: PricingModel,
+ pricingModel?: ResolvedPricingModel,
stateOfCharge: number;
timezone: string;
currentTimestamp?: Date;

Pricing - ResolvedPricingModel in transactions