pricing - investigation to preserve amount precision on chunks
MODIFY
src/integration/pricing/ConsumptionChunkPricer.ts
src/integration/pricing/ConsumptionChunkPricer.ts
@@ -273,7 +273,7 @@ export default class ConsumptionChunkPricer {
// throw new Error('Unexpected situation - priceFlatFeeDimension should be called only once per session');
return {
unitPrice: 0,
- amount: 0,
+ amount: Utils.createDecimal(0),
roundedAmount: 0,
quantity: 0 // Session
};
@@ -281,7 +281,7 @@ export default class ConsumptionChunkPricer {
// First call for this dimension
pricingDimension.pricedData = {
unitPrice: unitPrice,
- amount: unitPrice,
+ amount: Utils.createDecimal(unitPrice),
roundedAmount: Utils.truncTo(unitPrice, 2),
quantity: 1 // Session
};
@@ -291,7 +291,7 @@ export default class ConsumptionChunkPricer {
private priceConsumptionStep(pricingDimension: PricingDimension, steps: number): PricedDimensionData {
const unitPrice = pricingDimension.price || 0; // Eur/wWh
const quantity = Utils.createDecimal(steps).times(pricingDimension.stepSize).toNumber(); // Wh
- const amount = Utils.createDecimal(unitPrice).times(quantity).div(1000).toNumber(); // Eur
+ const amount = Utils.createDecimal(unitPrice).times(quantity).div(1000); // Eur
// Price the consumption
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
@@ -309,7 +309,7 @@ export default class ConsumptionChunkPricer {
private priceConsumptionWh(pricingDimension: PricingDimension, consumptionWh: number): PricedDimensionData {
const unitPrice = pricingDimension.price || 0; // Eur/kWh
const quantity = Utils.createDecimal(consumptionWh).toNumber(); // Wh
- const amount = Utils.createDecimal(unitPrice).times(consumptionWh).div(1000).toNumber(); // Eur
+ const amount = Utils.createDecimal(unitPrice).times(consumptionWh).div(1000); // Eur
// const consumptionkWh = Utils.createDecimal(consumptionWh).div(1000).toNumber();
// Price the consumption
const pricedData: PricedDimensionData = {
@@ -344,7 +344,7 @@ export default class ConsumptionChunkPricer {
private priceTimeSteps(pricingDimension: PricingDimension, steps: number): PricedDimensionData {
const unitPrice = pricingDimension.price || 0; // Eur/hour
const quantity = Utils.createDecimal(steps).times(pricingDimension.stepSize).toNumber(); // seconds
- const amount = Utils.createDecimal(unitPrice).times(quantity).div(3600).toNumber(); // Eur
+ const amount = Utils.createDecimal(unitPrice).times(quantity).div(3600); // Eur
// Price the consumption
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
@@ -361,7 +361,7 @@ export default class ConsumptionChunkPricer {
private priceTimeSpent(pricingDimension: PricingDimension, seconds: number): PricedDimensionData {
const unitPrice = pricingDimension.price || 0; // Eur/hour
- const amount = Utils.createDecimal(unitPrice).times(seconds).div(3600).toNumber(); // Eur
+ const amount = Utils.createDecimal(unitPrice).times(seconds).div(3600); // Eur
// Price the consumption
const pricedData: PricedDimensionData = {
unitPrice: unitPrice,
@@ -379,7 +379,7 @@ export default class ConsumptionChunkPricer {
// Add the consumption to the previous data (if any) - for the billing
const previousData = pricingDimension.pricedData;
if (previousData) {
- previousData.amount = Utils.createDecimal(previousData.amount).plus(pricedData.amount).toNumber();
+ previousData.amount = Utils.createDecimal(previousData.amount).plus(pricedData.amount);
previousData.quantity = Utils.createDecimal(previousData.quantity).plus(pricedData.quantity).toNumber();
previousData.roundedAmount = Utils.truncTo(previousData.amount, 2);
} else {

MODIFY
src/integration/pricing/PricingHelper.ts
src/integration/pricing/PricingHelper.ts
@@ -51,13 +51,13 @@ export default class PricingHelper {
if (!accumulatedData[dimensionType]) {
const emptyDimensionData: PricedDimensionData = {
quantity:0,
- amount: 0,
+ amount: Utils.createDecimal(0),
roundedAmount: 0
};
accumulatedData[dimensionType] = emptyDimensionData;
}
accumulatedData[dimensionType].quantity = Utils.createDecimal(accumulatedData[dimensionType].quantity).plus(pricedData[dimensionType].quantity).toNumber();
- accumulatedData[dimensionType].amount = Utils.createDecimal(accumulatedData[dimensionType].amount).plus(pricedData[dimensionType].amount).toNumber();
+ accumulatedData[dimensionType].amount = Utils.createDecimal(accumulatedData[dimensionType].amount).plus(pricedData[dimensionType].amount);
// accumulatedData[dimensionType].roundedAmount = Utils.createDecimal(accumulatedData[dimensionType].roundedAmount).plus(pricedData[dimensionType].roundedAmount).toNumber();
accumulatedData[dimensionType].roundedAmount = Utils.truncTo(accumulatedData[dimensionType].amount, 2);
}

MODIFY
src/types/Pricing.ts
src/types/Pricing.ts
import { AuthorizationActions } from './Authorization';
import { ConnectorType } from './ChargingStation';
import CreatedUpdatedProps from './CreatedUpdatedProps';
+import Decimal from 'decimal.js';
import Utils from '../utils/Utils';
export enum PricingEntity {
@@ -124,7 +125,7 @@ export interface PricedConsumptionData {
export interface PricedDimensionData {
unitPrice?: number;
- amount: number;
+ amount: number | Decimal; // Very important - switch to Decimal as soon as possible to preserve precision
roundedAmount: number;
quantity: number;
stepSize?: number;

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -546,10 +546,13 @@ export default class Utils {
return Utils.convertToFloat((Utils.createDecimal(wattHours).div(1000)));
}
- public static createDecimal(value: number): Decimal {
+ public static createDecimal(value: number | Decimal): Decimal {
if (Utils.isNullOrUndefined(value)) {
value = 0;
}
+ if (value instanceof Decimal) {
+ return value;
+ }
return new Decimal(value);
}
@@ -1065,7 +1068,7 @@ export default class Utils {
return Decimal.min(value1, value2).toNumber();
}
- public static truncTo(value: number, scale: number): number {
+ public static truncTo(value: number | Decimal, scale: number): number {
const truncPower = Math.pow(10, scale);
return Utils.createDecimal(value).mul(truncPower).trunc().div(truncPower).toNumber();
}

pricing - investigation to preserve amount precision on chunks