pricing - transaction roundedPrice rework
MODIFY
src/integration/pricing/ConsumptionPricer.ts
src/integration/pricing/ConsumptionPricer.ts
import PricingDefinition, { DimensionType, PricedConsumptionData, PricedDimensionData, PricingDimension, PricingRestriction, ResolvedPricingModel } from '../../types/Pricing';
import Consumption from '../../types/Consumption';
-import PricingHelper from './PricingHelper';
import Tenant from '../../types/Tenant';
import Utils from '../../utils/Utils';
import moment from 'moment';
@@ -120,6 +119,9 @@ export default class ConsumptionPricer {
let pricedData: PricedDimensionData;
const dimensionToPrice = activePricingDefinition.dimensions.energy;
const consumptionWh = this.consumptionData?.consumptionWh || 0;
+ if (typeof consumptionWh !== 'number') {
+ throw new Error('Unexpected situation - consumption is inconsistent');
+ }
if (dimensionToPrice.stepSize) {
if (this.consumptionData.cumulatedConsumptionWh > 0) {
const delta = Utils.createDecimal(this.consumptionData.cumulatedConsumptionWh).minus(this.getAbsorbedConsumption());
@@ -247,7 +249,7 @@ export default class ConsumptionPricer {
pricingDimension.pricedData = {
unitPrice: unitPrice,
amount: unitPrice,
- roundedAmount: PricingHelper.truncTo(unitPrice, 2),
+ roundedAmount: Utils.truncTo(unitPrice, 2),
quantity: 1 // Session
};
return pricingDimension.pricedData;
@@ -261,7 +263,7 @@ export default class ConsumptionPricer {
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
amount,
- roundedAmount: PricingHelper.truncTo(amount, 2),
+ roundedAmount: Utils.truncTo(amount, 2),
quantity,
stepSize: pricingDimension.stepSize,
};
@@ -280,7 +282,7 @@ export default class ConsumptionPricer {
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
amount,
- roundedAmount: PricingHelper.truncTo(amount, 2),
+ roundedAmount: Utils.truncTo(amount, 2),
quantity
};
// Add the consumption to the previous data (if any) - for the billing
@@ -315,7 +317,7 @@ export default class ConsumptionPricer {
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
amount,
- roundedAmount: PricingHelper.truncTo(amount, 2),
+ roundedAmount: Utils.truncTo(amount, 2),
quantity,
stepSize: pricingDimension.stepSize
};
@@ -332,7 +334,7 @@ export default class ConsumptionPricer {
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
amount,
- roundedAmount: PricingHelper.truncTo(amount, 2),
+ roundedAmount: Utils.truncTo(amount, 2),
quantity: seconds
};
// Add the consumption to the previous data (if any) - for the billing
@@ -345,9 +347,9 @@ export default class ConsumptionPricer {
// Add the consumption to the previous data (if any) - for the billing
const previousData = pricingDimension.pricedData;
if (previousData) {
- previousData.amount += pricedData.amount;
- previousData.quantity += pricedData.quantity;
- previousData.roundedAmount = PricingHelper.truncTo(previousData.amount, 2);
+ previousData.amount = Utils.createDecimal(previousData.amount).plus(pricedData.amount).toNumber();
+ previousData.quantity = Utils.createDecimal(previousData.quantity).plus(pricedData.quantity).toNumber();
+ previousData.roundedAmount = Utils.truncTo(previousData.amount, 2);
} else {
pricingDimension.pricedData = pricedData;
}

MODIFY
src/integration/pricing/PricingHelper.ts
src/integration/pricing/PricingHelper.ts
+/* eslint-disable max-len */
import { DimensionType, PricedConsumptionData, PricedDimensionData, PricingDimensions } from '../../types/Pricing';
import Decimal from 'decimal.js';
@@ -5,28 +6,32 @@ import Utils from '../../utils/Utils';
export default class PricingHelper {
- public static accumulatePricingDimensions(pricingDimensions: PricingDimensions[]): number {
+ 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);
const energy = PricingHelper.accumulatePricingDimension(pricingDimensions, DimensionType.ENERGY);
const chargingTime = PricingHelper.accumulatePricingDimension(pricingDimensions, DimensionType.CHARGING_TIME);
const parkingTime = PricingHelper.accumulatePricingDimension(pricingDimensions, DimensionType.PARKING_TIME);
// The final invoice shows individual lines per dimensions - each amount are truncated
- const total = new Decimal(0).plus(flatFee).plus(energy).plus(chargingTime).plus(parkingTime);
- return PricingHelper.truncTo(total.toNumber(), 2);
+ const cumulatedAmount = new Decimal(0).plus(flatFee.cumulatedAmount).plus(energy.cumulatedAmount).plus(chargingTime.cumulatedAmount).plus(parkingTime.cumulatedAmount).toNumber();
+ const cumulatedRoundedAmount = new Decimal(0).plus(flatFee.cumulatedRoundedAmount).plus(energy.cumulatedRoundedAmount).plus(chargingTime.cumulatedRoundedAmount).plus(parkingTime.cumulatedRoundedAmount).toNumber();
+ return {
+ cumulatedAmount,
+ cumulatedRoundedAmount,
+ };
}
- public static accumulatePricingDimension(pricingDimensions: PricingDimensions[], dimensionType: DimensionType): number {
- let total = new Decimal(0);
+ public static accumulatePricingDimension(pricingDimensions: PricingDimensions[], dimensionType: DimensionType): { cumulatedAmount: number, cumulatedRoundedAmount: number } {
+ let cumulatedAmount = 0;
+ let cumulatedRoundedAmount = 0;
pricingDimensions.forEach((pricingDimension) => {
- total = total.plus(pricingDimension[dimensionType]?.pricedData?.amount || 0);
+ cumulatedAmount = Utils.createDecimal(cumulatedAmount).plus(pricingDimension[dimensionType]?.pricedData?.amount || 0).toNumber();
+ cumulatedRoundedAmount = Utils.createDecimal(cumulatedRoundedAmount).plus(pricingDimension[dimensionType]?.pricedData?.roundedAmount || 0).toNumber();
});
- return PricingHelper.truncTo(total.toNumber(), 2);
- }
-
- public static truncTo(value: number, scale: number): number {
- const truncPower = Math.pow(10, scale);
- return Utils.createDecimal(value).times(truncPower).trunc().div(truncPower).toNumber();
+ return {
+ cumulatedAmount,
+ cumulatedRoundedAmount,
+ };
}
public static accumulatePricedConsumption(pricingData: PricedConsumptionData[]): PricedConsumptionData {

MODIFY
src/integration/pricing/sap-convergent-charging/SapConvergentChargingPricingIntegration.ts
src/integration/pricing/sap-convergent-charging/SapConvergentChargingPricingIntegration.ts
@@ -19,6 +19,7 @@ import SiteAreaStorage from '../../../storage/mongodb/SiteAreaStorage';
import StatefulChargingService from './StatefulChargingService';
import Tenant from '../../../types/Tenant';
import Transaction from '../../../types/Transaction';
+import Utils from '../../../utils/Utils';
import moment from 'moment-timezone';
const MODULE_NAME = 'ConvergentChargingPricingIntegration';
@@ -76,6 +77,7 @@ export default class SapConvergentChargingPricingIntegration extends PricingInte
cumulatedAmount: 0,
currencyCode: rateResult.transactionsToReserve.getCurrencyCode(),
roundedAmount: 0,
+ cumulatedRoundedAmount: 0,
pricingSource: PricingSource.CONVERGENT_CHARGING
};
}
@@ -99,6 +101,7 @@ export default class SapConvergentChargingPricingIntegration extends PricingInte
return {
roundedAmount: rateResult.amountToConfirm,
cumulatedAmount: rateResult.accumulatedAmount,
+ cumulatedRoundedAmount: Utils.truncTo(rateResult.accumulatedAmount,2),
currencyCode: rateResult.transactionsToConfirm.getCurrencyCode(),
amount: rateResult.transactionsToConfirm.getTotalUnroundedAmount(),
pricingSource: PricingSource.CONVERGENT_CHARGING
@@ -123,6 +126,7 @@ export default class SapConvergentChargingPricingIntegration extends PricingInte
return {
roundedAmount: rateResult.amountToConfirm,
cumulatedAmount: rateResult.accumulatedAmount,
+ cumulatedRoundedAmount: Utils.truncTo(rateResult.accumulatedAmount, 2),
currencyCode: rateResult.transactionsToConfirm.getCurrencyCode(),
amount: rateResult.transactionsToConfirm.getTotalUnroundedAmount(),
pricingSource: PricingSource.CONVERGENT_CHARGING

MODIFY
src/integration/pricing/simple-pricing/BuiltInPricingIntegration.ts
src/integration/pricing/simple-pricing/BuiltInPricingIntegration.ts
@@ -51,19 +51,24 @@ export default class BuiltInPricingIntegration extends PricingIntegration<Simple
roundedAmount: roundedAmount,
currencyCode: this.settings.currency,
cumulatedAmount: 0,
+ cumulatedRoundedAmount: 0,
};
if (!FeatureToggles.isFeatureActive(Feature.PRICING_NEW_MODEL)) {
// TODO - Old way of doing it - to be removed
pricedConsumption.cumulatedAmount = transaction.currentCumulatedPrice ? Utils.createDecimal(transaction.currentCumulatedPrice).plus(amount).toNumber() : amount;
+ pricedConsumption.cumulatedRoundedAmount = transaction.currentCumulatedRoundedPrice ? Utils.createDecimal(transaction.currentCumulatedRoundedPrice).plus(roundedAmount).toNumber() : amount;
} else {
// New logic - get the amount from the priced data to avoid rounding issues
- pricedConsumption.cumulatedAmount = this.computeCumulatedAmount(pricingModel);
+ const { cumulatedAmount, cumulatedRoundedAmount } = this.computeCumulatedAmount(pricingModel);
+ pricedConsumption.cumulatedAmount = cumulatedAmount;
+ pricedConsumption.cumulatedRoundedAmount = cumulatedRoundedAmount;
+
}
return Promise.resolve(pricedConsumption);
}
- private computeCumulatedAmount(pricingModel: ResolvedPricingModel): number {
+ private computeCumulatedAmount(pricingModel: ResolvedPricingModel): { cumulatedAmount: number, cumulatedRoundedAmount: number } {
const allDimensions: PricingDimensions[] = [];
pricingModel.pricingDefinitions.forEach((pricingDefinition) => {
if (pricingDefinition.dimensions) {

MODIFY
src/integration/pricing/simple-pricing/SimplePricingIntegration.ts
src/integration/pricing/simple-pricing/SimplePricingIntegration.ts
+/* eslint-disable max-len */
import { PricedConsumption, PricingSource } from '../../../types/Pricing';
import Consumption from '../../../types/Consumption';
@@ -40,7 +41,8 @@ export default class SimplePricingIntegration extends PricingIntegration<SimpleP
amount: amount,
roundedAmount: roundedAmount,
currencyCode: this.settings.currency,
- cumulatedAmount: transaction.currentCumulatedPrice ? Utils.createDecimal(transaction.currentCumulatedPrice).plus(amount).toNumber() : amount
+ cumulatedAmount: transaction.currentCumulatedPrice ? Utils.createDecimal(transaction.currentCumulatedPrice).plus(amount).toNumber() : amount,
+ cumulatedRoundedAmount: transaction.currentCumulatedRoundedPrice ? Utils.createDecimal(transaction.currentCumulatedRoundedPrice).plus(roundedAmount).toNumber() : roundedAmount
};
return pricedConsumption;
}

MODIFY
src/server/ocpp/utils/OCPPUtils.ts
src/server/ocpp/utils/OCPPUtils.ts
@@ -264,6 +264,7 @@ export default class OCPPUtils {
transaction.priceUnit = pricedConsumption.currencyCode;
transaction.pricingSource = pricedConsumption.pricingSource;
transaction.currentCumulatedPrice = pricedConsumption.amount;
+ transaction.currentCumulatedRoundedPrice = pricedConsumption.roundedAmount;
if (FeatureToggles.isFeatureActive(Feature.PRICING_NEW_MODEL)) {
// TODO - To be clarified - check for the PricingSource instead?
transaction.pricingModel = pricedConsumption.pricingModel;
@@ -282,6 +283,7 @@ export default class OCPPUtils {
consumption.pricingSource = pricedConsumption.pricingSource;
consumption.cumulatedAmount = pricedConsumption.cumulatedAmount;
transaction.currentCumulatedPrice = consumption.cumulatedAmount;
+ transaction.currentCumulatedRoundedPrice = pricedConsumption.cumulatedRoundedAmount;
}
break;
// Stop Transaction
@@ -296,6 +298,7 @@ export default class OCPPUtils {
consumption.pricingSource = pricedConsumption.pricingSource;
consumption.cumulatedAmount = pricedConsumption.cumulatedAmount;
transaction.currentCumulatedPrice = consumption.cumulatedAmount;
+ transaction.currentCumulatedRoundedPrice = pricedConsumption.cumulatedRoundedAmount;
}
break;
}
@@ -564,7 +567,8 @@ export default class OCPPUtils {
// Update transaction
transaction.roundedPrice = Utils.truncTo(transaction.price, 2);
transaction.stop.price = transaction.currentCumulatedPrice;
- transaction.stop.roundedPrice = Utils.truncTo(transaction.currentCumulatedPrice, 2);
+ // transaction.stop.roundedPrice = Utils.truncTo(transaction.currentCumulatedPrice, 2);
+ transaction.stop.roundedPrice = transaction.currentCumulatedRoundedPrice;
await TransactionStorage.saveTransaction(tenant, transaction);
}
@@ -584,7 +588,8 @@ export default class OCPPUtils {
totalDurationSecs: transaction.currentTotalDurationSecs,
inactivityStatus: Utils.getInactivityStatusLevel(chargingStation, transaction.connectorId, transaction.currentTotalInactivitySecs),
price: transaction.currentCumulatedPrice,
- roundedPrice: Utils.truncTo(transaction.currentCumulatedPrice, 2),
+ // roundedPrice: Utils.truncTo(transaction.currentCumulatedPrice, 2),
+ roundedPrice: transaction.currentCumulatedRoundedPrice,
priceUnit: transaction.priceUnit,
pricingSource: transaction.pricingSource,
};

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -80,6 +80,7 @@ export default class TransactionStorage {
currentTotalInactivitySecs: Utils.convertToInt(transactionToSave.currentTotalInactivitySecs),
currentInactivityStatus: transactionToSave.currentInactivityStatus,
currentCumulatedPrice: Utils.convertToFloat(transactionToSave.currentCumulatedPrice),
+ currentCumulatedRoundedPrice: Utils.convertToFloat(transactionToSave.currentCumulatedRoundedPrice),
transactionEndReceived: Utils.convertToBoolean(transactionToSave.transactionEndReceived),
currentInstantWatts: Utils.convertToFloat(transactionToSave.currentInstantWatts),
currentInstantWattsL1: Utils.convertToFloat(transactionToSave.currentInstantWattsL1),
@@ -136,6 +137,7 @@ export default class TransactionStorage {
delete transactionMDB.currentInstantWattsL3;
delete transactionMDB.currentInstantWattsDC;
delete transactionMDB.currentCumulatedPrice;
+ delete transactionMDB.currentCumulatedRoundedPrice;
delete transactionMDB.currentSignedData;
delete transactionMDB.currentStateOfCharge;
delete transactionMDB.currentTotalConsumptionWh;
@@ -571,6 +573,7 @@ export default class TransactionStorage {
totalConsumptionWattHours: { $sum: '$currentTotalConsumptionWh' },
totalDurationSecs: { $sum: '$currentTotalDurationSecs' },
totalPrice: { $sum: '$currentCumulatedPrice' },
+ totalRoundedPrice: { $sum: '$currentCumulatedRoundedPrice' },
totalInactivitySecs: { $sum: '$currentTotalInactivitySecs' },
currency: { $addToSet: '$priceUnit' },
count: { $sum: 1 }

MODIFY
src/types/Pricing.ts
src/types/Pricing.ts
@@ -92,6 +92,7 @@ export interface PricedConsumption {
amount: number;
cumulatedAmount: number;
roundedAmount: number;
+ cumulatedRoundedAmount: number;
currencyCode: string;
pricingSource: PricingSource;
pricingModel?: ResolvedPricingModel

MODIFY
src/types/Transaction.ts
src/types/Transaction.ts
@@ -92,6 +92,7 @@ export default interface Transaction extends AbstractCurrentConsumption {
currentTotalDurationSecs?: number;
transactionEndReceived?: boolean;
currentCumulatedPrice?: number;
+ currentCumulatedRoundedPrice?: number; // added to address rounding issues on multiple pricing dimension
currentSignedData?: string;
status?: ChargePointStatus;
numberOfMeterValues: number;

MODIFY
test/api/OCPPCommonTests.ts
test/api/OCPPCommonTests.ts
+/* eslint-disable max-len */
import { ChargePointErrorCode, ChargePointStatus, OCPP15TransactionData, OCPPAuthorizationStatus, OCPPMeterValue, OCPPReadingContext, OCPPStatusNotificationRequest, OCPPVersion } from '../../src/types/ocpp/OCPPServer';
import Transaction, { InactivityStatus } from '../../src/types/Transaction';
-import chai, { expect } from 'chai';
+import chai, { assert, expect } from 'chai';
import CentralServerService from './client/CentralServerService';
import ChargingStationContext from './context/ChargingStationContext';
import Constants from '../../src/utils/Constants';
import Factory from '../factories/Factory';
import { OCPPStatus } from '../../src/types/ocpp/OCPPClient';
-import { PricingSettingsType } from '../../src/types/Setting';
import { StatusCodes } from 'http-status-codes';
import Tag from '../../src/types/Tag';
import TenantContext from './context/TenantContext';
@@ -96,12 +96,12 @@ export default class OCPPCommonTests {
this.createAnyUser = createAnyUser;
}
- public setChargingStation(chargingStationContext) {
+ public setChargingStation(chargingStationContext: ChargingStationContext): void {
expect(chargingStationContext).to.exist;
this.chargingStationContext = chargingStationContext;
}
- public setUsers(startUserContext, stopUserContext?) {
+ public setUsers(startUserContext, stopUserContext?): void {
expect(startUserContext).to.exist;
this.transactionStartUser = startUserContext;
if (stopUserContext) {
@@ -118,14 +118,14 @@ export default class OCPPCommonTests {
}
}
- public async assignAnyUserToSite(siteContext) {
+ public async assignAnyUserToSite(siteContext): Promise<void> {
expect(siteContext).to.exist;
if (this.anyUser) {
await this.centralUserService.siteApi.addUsersToSite(siteContext.getSite().id, [this.anyUser.id]);
}
}
- public async before() {
+ public async before(): Promise<void> {
const allSettings = await this.centralUserService.settingApi.readAll({});
this.currentPricingSetting = allSettings.data.result.find((s) => s.identifier === 'pricing');
if (this.currentPricingSetting) {
@@ -155,6 +155,8 @@ export default class OCPPCommonTests {
this.energyActiveImportMeterValues = Array.from({ length: 12 }, () => faker.datatype.number({
min: 200, max: 500
})).concat([0, 0]);
+ // JUST A TEST to reproduce rounding issues
+ // this.energyActiveImportMeterValues = [ 0, 213, 266, 255, 363, 368, 1929, 0, 0, 0, 0, 0, 0, 0 ];
// SoC Meter Value (14 values)
this.socMeterValues = Array.from({ length: 8 }, () => faker.datatype.number({
min: 10, max: 90
@@ -216,8 +218,8 @@ export default class OCPPCommonTests {
}
// Meter Values params
this.transactionStartTime = moment().subtract(this.energyActiveImportMeterValues.length * this.meterValueIntervalSecs + 1, 'seconds').toDate();
- this.transactionTotalConsumptionWh = this.energyActiveImportMeterValues.reduce((sum, meterValue) => sum + meterValue);
- this.energyActiveImportEndMeterValue = this.energyActiveImportStartMeterValue + this.transactionTotalConsumptionWh;
+ this.transactionTotalConsumptionWh = this.energyActiveImportMeterValues.reduce((sum, meterValue) => Utils.createDecimal(sum).plus(meterValue).toNumber());
+ this.energyActiveImportEndMeterValue = Utils.createDecimal(this.energyActiveImportStartMeterValue).plus(this.transactionTotalConsumptionWh).toNumber();
this.transactionTotalInactivitySecs = this.energyActiveImportMeterValues.reduce(
(sum, meterValue) => (meterValue === 0 ? sum + this.meterValueIntervalSecs : sum), 0);
// Tags
@@ -242,7 +244,7 @@ export default class OCPPCommonTests {
}
}
- public async after() {
+ public async after(): Promise<void> {
if (this.currentPricingSetting) {
await this.centralUserService.settingApi.update(this.currentPricingSetting);
}
@@ -259,7 +261,7 @@ export default class OCPPCommonTests {
}
}
- public async testConnectorStatus() {
+ public async testConnectorStatus(): Promise<void> {
let response = await this.chargingStationContext.setConnectorStatus(this.chargingStationConnector1);
expect(response).to.eql({});
response = await this.chargingStationContext.setConnectorStatus(this.chargingStationConnector2);
@@ -282,7 +284,7 @@ export default class OCPPCommonTests {
});
}
- public async testChangeConnectorStatus() {
+ public async testChangeConnectorStatus(): Promise<void> {
// Set it to Occupied
this.chargingStationConnector1.status = ChargePointStatus.OCCUPIED;
this.chargingStationConnector1.timestamp = new Date().toISOString();
@@ -315,14 +317,14 @@ export default class OCPPCommonTests {
expect(response).to.eql({});
}
- public async testHeartbeat() {
+ public async testHeartbeat(): Promise<void> {
// Update Status of Connector 1
const response = await this.chargingStationContext.sendHeartbeat();
// Check
expect(response).to.have.property('currentTime');
}
- public async testClientIP() {
+ public async testClientIP(): Promise<void> {
// Read charging station
const response = await this.chargingStationContext.readChargingStation();
// Check the presence of the IP
@@ -330,7 +332,7 @@ export default class OCPPCommonTests {
expect(response.data.currentIPAddress).to.not.be.empty;
}
- public async testDataTransfer() {
+ public async testDataTransfer(): Promise<void> {
// Check
const response = await this.chargingStationContext.transferData({
'vendorId': 'Schneider Electric',
@@ -342,7 +344,7 @@ export default class OCPPCommonTests {
expect(response.status).to.equal(OCPPStatus.ACCEPTED);
}
- public async testChargingStationRegistrationWithInvalidToken() {
+ public async testChargingStationRegistrationWithInvalidToken(): Promise<void> {
try {
await this.chargingStationContext.sendBootNotification();
fail('BootNotification should fail');
@@ -351,7 +353,7 @@ export default class OCPPCommonTests {
}
}
- public async testChargingStationRegistrationWithInvalidIdentifier() {
+ public async testChargingStationRegistrationWithInvalidIdentifier(): Promise<void> {
try {
await this.chargingStationContext.sendBootNotification();
fail('BootNotification should fail');
@@ -360,7 +362,7 @@ export default class OCPPCommonTests {
}
}
- public async testAuthorizeUsers() {
+ public async testAuthorizeUsers(): Promise<void> {
// Asserts that the start user is authorized.
await this.testAuthorize(this.transactionStartUser.tags[0].id, OCPPStatus.ACCEPTED);
// Asserts that the stop user is authorized.
@@ -369,7 +371,7 @@ export default class OCPPCommonTests {
await this.testAuthorize('ThisIsATooTooTooLongTag', OCPPAuthorizationStatus.INVALID);
}
- public async testStartTransaction(validTransaction = true) {
+ public async testStartTransaction(validTransaction = true): Promise<void> {
// Start a new Transaction
const startTransactionResponse = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
@@ -403,7 +405,7 @@ export default class OCPPCommonTests {
}
}
- public async testStartSecondTransaction(withSoC = false) {
+ public async testStartSecondTransaction(): Promise<void> {
// Check on current transaction
expect(this.newTransaction).to.not.be.null;
// Set
@@ -431,7 +433,7 @@ export default class OCPPCommonTests {
expect(this.newTransaction.id).to.not.equal(transactionId);
}
- public async testRemoteStartTransactionWithNoBadge() {
+ public async testRemoteStartTransactionWithNoBadge(): Promise<void> {
const response = await this.centralUserService.chargingStationApi.remoteStartTransaction({
'chargingStationID': this.chargingStationContext.getChargingStation().id,
'args': {
@@ -441,7 +443,7 @@ export default class OCPPCommonTests {
expect(response.status).to.equal(StatusCodes.INTERNAL_SERVER_ERROR);
}
- public async testRemoteStartTransactionWithExternalUser() {
+ public async testRemoteStartTransactionWithExternalUser(): Promise<void> {
const response = await this.centralUserService.chargingStationApi.remoteStartTransaction({
'chargingStationID': this.chargingStationContext.getChargingStation().id,
'args': {
@@ -453,7 +455,7 @@ export default class OCPPCommonTests {
expect(response.status).to.equal(StatusCodes.INTERNAL_SERVER_ERROR);
}
- public async testRemoteStartTransactionWithUnassignedChargingStation() {
+ public async testRemoteStartTransactionWithUnassignedChargingStation(): Promise<void> {
console.log('yooo ' + this.transactionStartUser.tags[0].id);
const response = await this.centralUserService.chargingStationApi.remoteStartTransaction({
'chargingStationID': this.chargingStationContext.getChargingStation().id,
@@ -467,7 +469,7 @@ export default class OCPPCommonTests {
}
- public async testSendMeterValues(withSoC = false, withSignedData = false, withOnlyEndSignedData = false) {
+ public async testSendMeterValues(withSoC = false, withSignedData = false, withOnlyEndSignedData = false): Promise<void> {
// Check on Transaction
expect(this.newTransaction).to.not.be.null;
// Current Time matches Transaction one
@@ -548,7 +550,6 @@ export default class OCPPCommonTests {
currentTotalConsumptionWh: (currentEnergyActiveImportMeterValue - this.energyActiveImportStartMeterValue),
currentTotalDurationSecs: this.meterValueIntervalSecs * (index + 1),
currentTotalInactivitySecs: this.totalInactivities[index],
- currentCumulatedPrice: currentCumulatedPrice,
currentInactivityStatus: Utils.getInactivityStatusLevel(this.chargingStationContext.getChargingStation(),
this.newTransaction.connectorId, this.totalInactivities[index]),
});
@@ -560,11 +561,11 @@ export default class OCPPCommonTests {
currentTotalConsumptionWh: (currentEnergyActiveImportMeterValue - this.energyActiveImportStartMeterValue),
currentTotalDurationSecs: this.meterValueIntervalSecs * (index + 1),
currentTotalInactivitySecs: this.totalInactivities[index],
- currentCumulatedPrice: currentCumulatedPrice,
currentInactivityStatus: Utils.getInactivityStatusLevel(this.chargingStationContext.getChargingStation(),
this.newTransaction.connectorId, this.totalInactivities[index]),
});
}
+ assert(transactionValidation.data.currentCumulatedPrice === currentCumulatedPrice, `The cumulated price should be: ${currentCumulatedPrice} - actual value is: ` + transactionValidation.data.currentCumulatedPrice + ' for transaction: ' + this.newTransaction.id);
if (withSoC) {
expect(transactionValidation.data).to.deep.include({
currentStateOfCharge: this.socMeterValues[index]
@@ -615,7 +616,7 @@ export default class OCPPCommonTests {
}
}
- public async testStopTransaction(withSoC = false, withSignedData = false) {
+ public async testStopTransaction(withSoC = false, withSignedData = false): Promise<void> {
// Check on Transaction
expect(this.newTransaction).to.not.be.null;
expect(this.transactionCurrentTime).to.not.be.null;
@@ -636,66 +637,65 @@ export default class OCPPCommonTests {
// Check the Transaction
const transactionValidation = await this.basicTransactionValidation(this.newTransaction.id,
this.newTransaction.connectorId, this.newTransaction.meterStart, this.newTransaction.timestamp);
- const totalTransactionPrice = Utils.computeSimplePrice(this.pricekWh, this.transactionTotalConsumptionWh);
- expect(this.totalPrice).equal(totalTransactionPrice);
expect(transactionValidation.data).to.deep['containSubset']({
- 'signedData': (withSignedData ? this.transactionStartSignedData : ''),
- 'stop': {
- 'meterStop': this.energyActiveImportEndMeterValue,
- 'totalConsumptionWh': this.transactionTotalConsumptionWh,
- 'totalInactivitySecs': this.transactionTotalInactivitySecs,
- 'inactivityStatus': InactivityStatus.INFO,
- 'totalDurationSecs': moment.duration(moment(this.transactionCurrentTime).diff(this.newTransaction.timestamp)).asSeconds(),
- 'price': this.totalPrice,
- 'priceUnit': 'EUR',
- 'pricingSource': PricingSettingsType.SIMPLE,
- 'roundedPrice': Utils.truncTo(this.totalPrice, 2),
- 'tagID': this.transactionStopUser.tags[0].id,
- 'timestamp': this.transactionCurrentTime.toISOString(),
- 'signedData': (withSignedData ? this.transactionEndSignedData : ''),
- 'stateOfCharge': (withSoC ? this.socMeterValues[this.socMeterValues.length - 1] : 0),
- 'user': {
- 'id': this.transactionStopUser.id,
- 'name': this.transactionStopUser.name,
- 'firstName': this.transactionStopUser.firstName
+ signedData: (withSignedData ? this.transactionStartSignedData : ''),
+ stop: {
+ meterStop: this.energyActiveImportEndMeterValue,
+ totalConsumptionWh: this.transactionTotalConsumptionWh,
+ totalInactivitySecs: this.transactionTotalInactivitySecs,
+ inactivityStatus: InactivityStatus.INFO,
+ totalDurationSecs: moment.duration(moment(this.transactionCurrentTime).diff(this.newTransaction.timestamp)).asSeconds(),
+ tagID: this.transactionStopUser.tags[0].id,
+ timestamp: this.transactionCurrentTime.toISOString(),
+ signedData: (withSignedData ? this.transactionEndSignedData : ''),
+ stateOfCharge: (withSoC ? this.socMeterValues[this.socMeterValues.length - 1] : 0),
+ user: {
+ id: this.transactionStopUser.id,
+ name: this.transactionStopUser.name,
+ firstName: this.transactionStopUser.firstName
}
}
});
+ // Check priced data
+ const totalTransactionPrice = Utils.computeSimplePrice(this.pricekWh, this.transactionTotalConsumptionWh);
+ assert(Utils.createDecimal(this.totalPrice).equals(totalTransactionPrice), `The total transaction price should be: ${totalTransactionPrice} - actual value is: ${this.totalPrice}`);
+ // Check STOP priced data
+ this.checkPricedTransactionData(transactionValidation.data);
}
- public async testTransactionMetrics(withSoC = false, checkNewMeterValues = false) {
+ public async testTransactionMetrics(withSoC = false, checkNewMeterValues = false): Promise<void> {
// Check on Transaction
expect(this.newTransaction).to.not.be.null;
const response = await this.centralUserService.transactionApi.readAllConsumption({ TransactionId: this.newTransaction.id });
expect(response.status).to.equal(StatusCodes.OK);
- const totalTransactionPrice = Utils.computeSimplePrice(this.pricekWh, this.transactionTotalConsumptionWh);
- expect(this.totalPrice).equal(totalTransactionPrice);
// Check Headers
expect(response.data).to.deep['containSubset']({
- 'chargeBoxID': this.newTransaction.chargeBoxID,
- 'connectorId': this.newTransaction.connectorId,
- 'stop': {
- 'price': this.totalPrice,
- 'pricingSource': 'simple',
- 'roundedPrice': Utils.truncTo(this.totalPrice, 2),
- 'tagID': this.transactionStopUser.tags[0].id,
- 'totalConsumptionWh': this.transactionTotalConsumptionWh,
- 'totalInactivitySecs': this.transactionTotalInactivitySecs,
- 'inactivityStatus': InactivityStatus.INFO,
- 'stateOfCharge': (withSoC ? this.socMeterValues[this.socMeterValues.length - 1] : 0),
- 'user': {
- 'id': this.transactionStopUser.id,
- 'name': this.transactionStopUser.name,
- 'firstName': this.transactionStopUser.firstName
+ chargeBoxID: this.newTransaction.chargeBoxID,
+ connectorId: this.newTransaction.connectorId,
+ stop: {
+ tagID: this.transactionStopUser.tags[0].id,
+ totalConsumptionWh: this.transactionTotalConsumptionWh,
+ totalInactivitySecs: this.transactionTotalInactivitySecs,
+ inactivityStatus: InactivityStatus.INFO,
+ stateOfCharge: (withSoC ? this.socMeterValues[this.socMeterValues.length - 1] : 0),
+ user: {
+ id: this.transactionStopUser.id,
+ name: this.transactionStopUser.name,
+ firstName: this.transactionStopUser.firstName
}
},
- 'id': this.newTransaction.id,
- 'user': {
- 'id': this.transactionStartUser.id,
- 'name': this.transactionStartUser.name,
- 'firstName': this.transactionStartUser.firstName
+ id: this.newTransaction.id,
+ user: {
+ id: this.transactionStartUser.id,
+ name: this.transactionStartUser.name,
+ firstName: this.transactionStartUser.firstName
}
});
+ // Check priced data
+ const totalTransactionPrice = Utils.computeSimplePrice(this.pricekWh, this.transactionTotalConsumptionWh);
+ assert(this.totalPrice === totalTransactionPrice, `The total transaction price should be: ${totalTransactionPrice} - actual value is: ${this.totalPrice}`);
+ // Check STOP priced data
+ this.checkPricedTransactionData(response.data);
// Init
const transactionCurrentTime = moment(this.newTransaction.timestamp);
let transactionCumulatedConsumption = this.energyActiveImportStartMeterValue;
@@ -749,7 +749,7 @@ export default class OCPPCommonTests {
}
}
- public async testDeleteTransaction(noAuthorization = false) {
+ public async testDeleteTransaction(noAuthorization = false): Promise<void> {
// Delete the created entity
expect(this.newTransaction).to.not.be.null;
let response = await this.transactionStartUserService.transactionApi.delete(this.newTransaction.id);
@@ -766,18 +766,18 @@ export default class OCPPCommonTests {
this.newTransaction = null;
}
- public async testAuthorizeTagAsInteger() {
+ public async testAuthorizeTagAsInteger(): Promise<void> {
await this.testAuthorize(this.numberTag, OCPPStatus.ACCEPTED);
await this.testAuthorize(this.numberTag.toString(), OCPPStatus.ACCEPTED);
}
- public async testAuthorizeInvalidTag() {
+ public async testAuthorizeInvalidTag(): Promise<void> {
await this.testAuthorize(this.invalidTag, OCPPAuthorizationStatus.INVALID);
await this.testAuthorize('', OCPPAuthorizationStatus.INVALID);
await this.testAuthorize(null, OCPPAuthorizationStatus.INVALID);
}
- public async testStartTransactionWithConnectorIdAsString() {
+ public async testStartTransactionWithConnectorIdAsString(): Promise<void> {
const response = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
this.numberTag.toString(),
@@ -788,7 +788,7 @@ export default class OCPPCommonTests {
expect(response).to.be.transactionValid;
}
- public async testStartTransactionWithMeterStartGreaterZero() {
+ public async testStartTransactionWithMeterStartGreaterZero(): Promise<void> {
const response = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
this.numberTag.toString(),
@@ -799,7 +799,7 @@ export default class OCPPCommonTests {
expect(response).to.be.transactionValid;
}
- public async testStartTransactionWithInvalidTag() {
+ public async testStartTransactionWithInvalidTag(): Promise<void> {
let response = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
this.invalidTag,
@@ -823,7 +823,7 @@ export default class OCPPCommonTests {
expect(response).to.be.transactionStatus(OCPPAuthorizationStatus.INVALID);
}
- public async testStopTransactionWithoutTransactionData() {
+ public async testStopTransactionWithoutTransactionData(): Promise<void> {
const startTransactionResponse = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
this.numberTag.toString(),
@@ -841,7 +841,7 @@ export default class OCPPCommonTests {
expect(stopTransactionResponse.idTagInfo.status).to.equal(OCPPStatus.ACCEPTED);
}
- public async testStopTransactionWithTransactionData() {
+ public async testStopTransactionWithTransactionData(): Promise<void> {
const startTransactionResponse = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
this.numberTag.toString(),
@@ -909,7 +909,7 @@ export default class OCPPCommonTests {
expect(stopTransactionResponse.idTagInfo.status).to.equal(OCPPStatus.ACCEPTED);
}
- public async testStopTransactionWithInvalidTransactionData() {
+ public async testStopTransactionWithInvalidTransactionData(): Promise<void> {
const startTransactionResponse = await this.chargingStationContext.startTransaction(
this.chargingStationConnector1.connectorId,
this.numberTag.toString(),
@@ -982,7 +982,7 @@ export default class OCPPCommonTests {
expect(stopTransactionResponse.idTagInfo.status).to.equal(OCPPStatus.ACCEPTED);
}
- public async testRetrieveLastRebootDate() {
+ public async testRetrieveLastRebootDate(): Promise<void> {
const bootNotification = await this.chargingStationContext.sendBootNotification();
expect(bootNotification).to.not.be.null;
expect(bootNotification.status).to.eql(OCPPStatus.ACCEPTED);
@@ -1028,7 +1028,7 @@ export default class OCPPCommonTests {
}
}
- public async testTransactionIgnoringClockMeterValues() {
+ public async testTransactionIgnoringClockMeterValues(): Promise<void> {
const meterStart = 0;
let meterValue = meterStart;
const currentTime = moment();
@@ -1145,21 +1145,28 @@ export default class OCPPCommonTests {
const transactionResponse = await this.centralUserService.transactionApi.readById(transactionId);
expect(transactionResponse.status).to.equal(StatusCodes.OK);
expect(transactionResponse.data).to.deep['containSubset']({
- 'id': transactionId,
- 'timestamp': timestamp,
- 'chargeBoxID': this.chargingStationContext.getChargingStation().id,
- 'connectorId': connectorId,
- 'tagID': this.transactionStartUser.tags[0].id,
- 'meterStart': meterStart,
- 'userID': this.transactionStartUser.id,
- 'siteAreaID': this.chargingStationContext.getChargingStation().siteAreaID,
- 'siteID': this.chargingStationContext.getChargingStation().siteID,
- 'user': {
- 'id': this.transactionStartUser.id,
- 'name': this.transactionStartUser.name,
- 'firstName': this.transactionStartUser.firstName
+ id: transactionId,
+ timestamp: timestamp,
+ chargeBoxID: this.chargingStationContext.getChargingStation().id,
+ connectorId: connectorId,
+ tagID: this.transactionStartUser.tags[0].id,
+ meterStart: meterStart,
+ userID: this.transactionStartUser.id,
+ siteAreaID: this.chargingStationContext.getChargingStation().siteAreaID,
+ siteID: this.chargingStationContext.getChargingStation().siteID,
+ user: {
+ id: this.transactionStartUser.id,
+ name: this.transactionStartUser.name,
+ firstName: this.transactionStartUser.firstName
}
});
return transactionResponse;
}
+
+ private checkPricedTransactionData(data): void {
+ assert(data.stop.pricingSource === 'simple', 'The pricing source is not correct');
+ const expectedRoundedPrice = Utils.truncTo(this.totalPrice, 2);
+ assert(Utils.createDecimal(data.stop.price).equals(this.totalPrice), `The total transaction price should be: ${this.totalPrice} - actual value is: ` + data.stop.price);
+ assert(Utils.createDecimal(data.stop.roundedPrice).equals(expectedRoundedPrice), `The total transaction price should be: ${expectedRoundedPrice} - actual value is: ` + data.stop.roundedPrice);
+ }
}

pricing - transaction roundedPrice rework