pricing - checkPricingDefinitionRestrictions DRAFT
MODIFY
src/integration/pricing/PricingEngine.ts
src/integration/pricing/PricingEngine.ts
/* eslint-disable @typescript-eslint/member-ordering */
-import { PricingConsumptionData, PricingDefinition, PricingDimension, PricingDimensionData, ResolvedPricingModel } from '../../types/Pricing';
+import { PricingConsumptionData, PricingDefinition, PricingDimension, PricingDimensionData, PricingRestriction, ResolvedPricingModel } from '../../types/Pricing';
import Consumption from '../../types/Consumption';
import PricingStorage from '../../storage/mongodb/PricingStorage';
@@ -42,12 +42,73 @@ export default class PricingEngine {
return Promise.resolve(pricingModel);
}
+ static checkPricingDefinitionRestrictions(pricingDefinition: PricingDefinition, consumptionData: Consumption) : PricingDefinition {
+ if (pricingDefinition.restrictions) {
+ if (!PricingEngine.checkRestrictionMinPower(pricingDefinition.restrictions, consumptionData)
+ || !PricingEngine.checkRestrictionMaxPower(pricingDefinition.restrictions, consumptionData)
+ || !PricingEngine.checkRestrictionMinDuration(pricingDefinition.restrictions, consumptionData)
+ || !PricingEngine.checkRestrictionMaxDuration(pricingDefinition.restrictions, consumptionData)) {
+ // -----------------------------------------------------------------------------------------
+ // TODO - to be clarified - why don't we put "date validity" at the pricing model level????
+ // -----------------------------------------------------------------------------------------
+ // startTime?: string, // Start time of day, for example 13:30, valid from this time of the day. Must be in 24h format with leading zeros. Hour/Minute se
+ // endTime?: string, // End time of day, for example 19:45, valid until this time of the day. Same syntax as start_time
+ // startDate?: string, // Start date, for example: 2015-12-24, valid from this day
+ // endDate?: string, // End date, for example: 2015-12-27, valid until this day (excluding this day)
+ // daysOfWeek?: DayOfWeek[], // Which day(s) of the week this tariff is valid
+ return null;
+ }
+ }
+ // a definition matching the restrictions has been found
+ return pricingDefinition;
+ }
+
+ static checkRestrictionMinPower(restrictions: PricingRestriction, consumptionData: Consumption): boolean {
+ if (!Utils.isNullOrUndefined(restrictions.minPowerkW)) {
+ if (Utils.createDecimal(consumptionData.cumulatedConsumptionWh).dividedBy(1000).lessThan(restrictions.minPowerkW)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static checkRestrictionMaxPower(restrictions: PricingRestriction, consumptionData: Consumption): boolean {
+ if (!Utils.isNullOrUndefined(restrictions.maxPowerkW)) {
+ if (Utils.createDecimal(consumptionData.cumulatedConsumptionWh).dividedBy(1000).greaterThanOrEqualTo(restrictions.maxPowerkW)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static checkRestrictionMinDuration(restrictions: PricingRestriction, consumptionData: Consumption): boolean {
+ if (!Utils.isNullOrUndefined(restrictions.minDurationSecs)) {
+ if (Utils.createDecimal(consumptionData.totalDurationSecs).lessThan(restrictions.minDurationSecs)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static checkRestrictionMaxDuration(restrictions: PricingRestriction, consumptionData: Consumption): boolean {
+ if (!Utils.isNullOrUndefined(restrictions.maxDurationSecs)) {
+ if (Utils.createDecimal(consumptionData.totalDurationSecs).greaterThanOrEqualTo(restrictions.maxDurationSecs)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
static async priceFinalConsumption(tenant: Tenant, transaction: Transaction, consumptionData: Consumption): Promise<PricingConsumptionData> {
+ // Check the restrictions to find the pricing definition matching the current context
+ const actualPricingDefinitions = transaction.pricingModel.pricingDefinitions.filter((pricingDefinition) =>
+ PricingEngine.checkPricingDefinitionRestrictions(pricingDefinition, consumptionData)
+ );
// Build the consumption data for each dimension
- const flatFee: PricingDimensionData = PricingEngine.priceFlatFeeConsumption(tenant, transaction, consumptionData);
- const energy: PricingDimensionData = PricingEngine.priceEnergyConsumption(tenant, transaction, consumptionData);
- const chargingTime: PricingDimensionData = PricingEngine.priceChargingTimeConsumption(tenant, transaction, consumptionData);
- const parkingTime: PricingDimensionData = PricingEngine.priceParkingTimeConsumption(tenant, transaction, consumptionData);
+ const flatFee: PricingDimensionData = PricingEngine.priceFlatFeeConsumption(actualPricingDefinitions, consumptionData);
+ const energy: PricingDimensionData = PricingEngine.priceEnergyConsumption(actualPricingDefinitions, consumptionData);
+ const chargingTime: PricingDimensionData = PricingEngine.priceChargingTimeConsumption(actualPricingDefinitions, consumptionData);
+ const parkingTime: PricingDimensionData = PricingEngine.priceParkingTimeConsumption(actualPricingDefinitions, consumptionData);
// For now we can have up to 4 dimensions
const pricingConsumptionData: PricingConsumptionData = {
flatFee,
@@ -55,7 +116,7 @@ export default class PricingEngine {
chargingTime,
parkingTime,
};
-
+ // Remove unset properties
if (!pricingConsumptionData.flatFee) {
delete pricingConsumptionData.flatFee;
}
@@ -71,37 +132,37 @@ export default class PricingEngine {
return Promise.resolve(pricingConsumptionData);
}
- private static priceFlatFeeConsumption(tenant: Tenant, transaction: Transaction, consumptionData: Consumption): PricingDimensionData {
+ private static priceFlatFeeConsumption(pricingDefinitions: PricingDefinition[], consumptionData: Consumption): PricingDimensionData {
let pricingDimensionData: PricingDimensionData = null;
const quantity = 1; // To be clarified - Flat Fee is billing billed once per sessions
- pricingDimensionData = PricingEngine.PriceDimensionConsumption(transaction.pricingModel, 'flatFee', quantity);
+ pricingDimensionData = PricingEngine.PriceDimensionConsumption(pricingDefinitions, 'flatFee', quantity);
return pricingDimensionData;
}
- private static priceEnergyConsumption(tenant: Tenant, transaction: Transaction, consumptionData: Consumption): PricingDimensionData {
+ private static priceEnergyConsumption(pricingDefinitions: PricingDefinition[], consumptionData: Consumption): PricingDimensionData {
let pricingDimensionData: PricingDimensionData = null;
const quantity = Utils.createDecimal(consumptionData?.cumulatedConsumptionWh).dividedBy(1000).toNumber(); // Total consumption in kW.h
- pricingDimensionData = PricingEngine.PriceDimensionConsumption(transaction.pricingModel, 'energy', quantity);
+ pricingDimensionData = PricingEngine.PriceDimensionConsumption(pricingDefinitions, 'energy', quantity);
return pricingDimensionData;
}
- private static priceParkingTimeConsumption(tenant: Tenant, transaction: Transaction, consumptionData: Consumption): PricingDimensionData {
+ private static priceParkingTimeConsumption(pricingDefinitions: PricingDefinition[], consumptionData: Consumption): PricingDimensionData {
let pricingDimensionData: PricingDimensionData = null;
- const hours = Utils.createDecimal(consumptionData?.totalDurationSecs).dividedBy(3600).toNumber();
- pricingDimensionData = PricingEngine.PriceDimensionConsumption(transaction.pricingModel, 'parkingTime', hours);
+ const hours = Utils.createDecimal(consumptionData?.totalInactivitySecs).dividedBy(3600).toNumber();
+ pricingDimensionData = PricingEngine.PriceDimensionConsumption(pricingDefinitions, 'parkingTime', hours);
return pricingDimensionData;
}
- private static priceChargingTimeConsumption(tenant: Tenant, transaction: Transaction, consumptionData: Consumption): PricingDimensionData {
+ private static priceChargingTimeConsumption(pricingDefinitions: PricingDefinition[], consumptionData: Consumption): PricingDimensionData {
let pricingDimensionData: PricingDimensionData = null;
- const hours = Utils.createDecimal(consumptionData?.totalInactivitySecs).dividedBy(60).toNumber();
- pricingDimensionData = PricingEngine.PriceDimensionConsumption(transaction.pricingModel, 'chargingTime', hours);
+ const hours = Utils.createDecimal(consumptionData?.totalDurationSecs).dividedBy(3600).toNumber();
+ pricingDimensionData = PricingEngine.PriceDimensionConsumption(pricingDefinitions, 'chargingTime', hours);
return pricingDimensionData;
}
- static PriceDimensionConsumption(pricingModel: ResolvedPricingModel, dimensionType: string, quantity = 0): PricingDimensionData {
- // Search for the first pricing definition matching the current dimension type and the pricing restrictions if any!
- const activePricingDefinitions = pricingModel.pricingDefinitions.filter((pricingDefinition) =>
+ static PriceDimensionConsumption(actualPricingDefinitions: PricingDefinition[], dimensionType: string, quantity = 0): PricingDimensionData {
+ // Search for the first pricing definition matching the current dimension type
+ const activePricingDefinitions = actualPricingDefinitions.filter((pricingDefinition) =>
// We search for a pricing definition where the current dimension exists
PricingEngine.checkPricingDimensionRestrictions(pricingDefinition, dimensionType)
);
@@ -130,7 +191,6 @@ export default class PricingEngine {
}
static priceDimension(pricingDimension: PricingDimension, quantity: number): PricingDimensionData {
-
let amount: number;
if (pricingDimension.stepSize) {
// --------------------------------------------------------------------------------------------

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -255,13 +255,21 @@ 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 parkingPrice: PricingDimension = {
+ const price4ParkingTime: PricingDimension = {
price: 0.75,
active: true
};
const fastChargerRestrictions: PricingRestriction = {
minPowerkW: 40000
};
+ const priceEachSessionWithFlatFee: PricingDimension = {
+ price: 1.25,
+ active: true
+ };
+ const price4ChargingTime: PricingDimension = {
+ price: 0.15,
+ active: true
+ };
const price4TheEnergy: PricingDimension = {
price: 0.35,
active: true
@@ -271,9 +279,10 @@ class TestData {
description: 'Tariff for fast chargers',
restrictions: fastChargerRestrictions,
dimensions: {
- // chargingTime: parkingPrice, // parking time is free while charging
+ flatFee: priceEachSessionWithFlatFee,
+ // chargingTime: price4ChargingTime, // parking time is free while charging
energy: price4TheEnergy,
- parkingTime: parkingPrice, // Parking time is not free when not charging
+ parkingTime: price4ParkingTime, // Parking time is not free when not charging
}
};
const lowConsumptionRestrictions: PricingRestriction = {
@@ -284,9 +293,10 @@ class TestData {
description: 'Tariff for low EVSE',
restrictions: lowConsumptionRestrictions,
dimensions: {
- chargingTime: parkingPrice,
- // energy: price4TheEnergy, // do not bill the energy - bill the parking time instead
- parkingTime: parkingPrice,
+ flatFee: priceEachSessionWithFlatFee,
+ chargingTime: price4ChargingTime,
+ energy: price4TheEnergy, // do not bill the energy - bill the parking time instead
+ parkingTime: price4ParkingTime,
}
};
const pricingModel = response?.data;

pricing - checkPricingDefinitionRestrictions DRAFT