pricing - resolve pricing context
MODIFY
src/integration/pricing/PricingEngine.ts
src/integration/pricing/PricingEngine.ts
/* eslint-disable @typescript-eslint/member-ordering */
-import { PricingConsumptionData, PricingDefinition, PricingDimension, PricingDimensionData, PricingRestriction, ResolvedPricingModel } from '../../types/Pricing';
+import PricingModel, { PricingConsumptionData, PricingDefinition, PricingDimension, PricingDimensionData, PricingRestriction, ResolvedPricingModel } from '../../types/Pricing';
import Consumption from '../../types/Consumption';
import PricingStorage from '../../storage/mongodb/PricingStorage';
@@ -24,22 +24,37 @@ export default class PricingEngine {
// - Charging Station
// - User Group
// - User
- // Of course, the date has an impact as well
+ // Of course, the date has an impact as well ... but not sure were to check for it!
// -----------------------------------------------------------------------------------------
- // 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(tenant, {}, { limit: 1, skip: 0, sort: { createdOn: -1 } });
- if (pricingModelResults.count > 0) {
- const { pricingDefinitions } = pricingModelResults.result[0];
- pricingModel = {
+ // Merge the pricing definitions from the different contexts
+ const pricingDefinitions: PricingDefinition[] = [];
+ pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction.siteAreaID));
+ pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction.siteID));
+ pricingDefinitions.push(...await PricingEngine.getPricingDefinitions4Entity(tenant, transaction.companyID));
+ // TODO - No pricing definition? => Throw an exception ? or create dynamically a simple one based on the simple pricing settings?
+ const resolvedPricingModel: ResolvedPricingModel = {
pricingDefinitions
};
+ return Promise.resolve(resolvedPricingModel);
}
- // TODO - No pricing definition? => Throw an exception ?
- return Promise.resolve(pricingModel);
+
+ static async getPricingDefinitions4Entity(tenant: Tenant, entityID: string): Promise<PricingDefinition[]> {
+ const pricingModel: PricingModel = await PricingEngine.getPricingModel4Entity(tenant, entityID);
+ return pricingModel?.pricingDefinitions || [];
+ }
+
+ static async getPricingModel4Entity(tenant: Tenant, entityID: string): Promise<PricingModel> {
+ if (entityID) {
+ const contextIDs = [ entityID ];
+ const pricingModelResults = await PricingStorage.getPricingModels(tenant, { contextIDs }, { limit: 1, skip: 0, sort: { createdOn: -1 } });
+ if (pricingModelResults.count > 0) {
+ // ---------------------------------------------------------------------
+ // TODO - First implementation: we simply return the latest created one
+ // ---------------------------------------------------------------------
+ return pricingModelResults.result[0];
+ }
+ }
+ return null;
}
static checkPricingDefinitionRestrictions(pricingDefinition: PricingDefinition, consumptionData: Consumption) : PricingDefinition {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -244,8 +244,18 @@ class TestData {
}
public async checkPricingModel(): Promise<void> {
+ const company = (await this.adminUserService.companyApi.readAll({}, { limit: 0, skip: 0 }))?.data?.result?.[0];
+ assert(company, 'The Company should not be null');
+ await this.createTariff4Company(company.id);
+
+ const site = (await this.adminUserService.siteApi.readAll({}, { limit: 0, skip: 0 }))?.data?.result?.[0];
+ assert(site, 'The Site should not be null');
+ await this.createTariff4Site(site.id);
+ }
+
+ public async createTariff4Company(companyID: string): Promise<void> {
const initialPricingModel: Partial<PricingModel> = {
- contextID: null, // a pricing model for the tenant
+ contextID: companyID, // a pricing model for the company
pricingDefinitions: []
};
let response = await this.adminUserService.pricingApi.createPricingModel(initialPricingModel);
@@ -255,58 +265,88 @@ class TestData {
const pricingModelId = response?.data?.id;
response = await this.adminUserService.pricingApi.readPricingModel(pricingModelId);
assert(response?.data?.id === pricingModelId, 'The ID should be: ' + pricingModelId);
- const price4ParkingTime: PricingDimension = {
- price: 0.75,
- active: true
- };
- const fastChargerRestrictions: PricingRestriction = {
- minPowerkW: 40000
- };
- const priceEachSessionWithFlatFee: PricingDimension = {
+
+ const tariff: PricingDefinition = {
+ name: 'GREEN Tariff',
+ description: 'Tariff for low EVSE',
+ restrictions: {
+ maxPowerkW: 40000,
+ },
+ dimensions: {
+ flatFee: {
price: 1.25,
active: true
- };
- const price4ChargingTime: PricingDimension = {
+ },
+ chargingTime: {
price: 0.15,
active: true
- };
- const price4TheEnergy: PricingDimension = {
+ },
+ energy: {
price: 0.35,
active: true
- };
- const tariff1: PricingDefinition = {
- name: 'RED Tariff',
- description: 'Tariff for fast chargers',
- restrictions: fastChargerRestrictions,
- dimensions: {
- flatFee: priceEachSessionWithFlatFee,
- // chargingTime: price4ChargingTime, // parking time is free while charging
- energy: price4TheEnergy,
- parkingTime: price4ParkingTime, // Parking time is not free when not charging
+ },
+ parkingTime: {
+ price: 0.75,
+ active: true
+ },
}
};
- const lowConsumptionRestrictions: PricingRestriction = {
- maxPowerkW: 40000,
+
+ const pricingModel = response?.data;
+ pricingModel.pricingDefinitions = [tariff];
+ response = await this.adminUserService.pricingApi.updatePricingModel(pricingModel);
+ assert(response?.data?.status === 'Success', 'The operation should succeed');
+ }
+
+
+ public async createTariff4Site(siteID: string): Promise<void> {
+ const initialPricingModel: Partial<PricingModel> = {
+ contextID: siteID, // a pricing model for the site
+ pricingDefinitions: []
};
- const tariff2: PricingDefinition = {
- name: 'GREEN Tariff',
- description: 'Tariff for low EVSE',
- restrictions: lowConsumptionRestrictions,
+ let response = await this.adminUserService.pricingApi.createPricingModel(initialPricingModel);
+ assert(response?.data?.status === 'Success', 'The operation should succeed');
+ assert(response?.data?.id, 'The ID should not be null');
+
+ const pricingModelId = response?.data?.id;
+ response = await this.adminUserService.pricingApi.readPricingModel(pricingModelId);
+ assert(response?.data?.id === pricingModelId, 'The ID should be: ' + pricingModelId);
+
+ const tariff: PricingDefinition = {
+ name: 'RED Tariff',
+ description: 'Tariff for fast chargers',
+ restrictions: {
+ minPowerkW: 40000,
+ },
dimensions: {
- flatFee: priceEachSessionWithFlatFee,
- chargingTime: price4ChargingTime,
- energy: price4TheEnergy, // do not bill the energy - bill the parking time instead
- parkingTime: price4ParkingTime,
+ flatFee: {
+ price: 2.25,
+ active: true
+ },
+ chargingTime: {
+ price: 0,
+ active: false
+ },
+ energy: {
+ price: 0.75,
+ active: true
+ },
+ parkingTime: {
+ price: 0,
+ active: true
+ },
}
};
+
const pricingModel = response?.data;
- pricingModel.pricingDefinitions = [tariff1, tariff2];
+ pricingModel.pricingDefinitions = [tariff];
response = await this.adminUserService.pricingApi.updatePricingModel(pricingModel);
assert(response?.data?.status === 'Success', 'The operation should succeed');
}
}
+
const testData: TestData = new TestData();
describe('Billing Settings', function() {

pricing - resolve pricing context