pricing - consider trasaction timezone when check time restrictions
MODIFY
src/integration/pricing/ConsumptionPricer.ts
src/integration/pricing/ConsumptionPricer.ts
@@ -63,19 +63,20 @@ export default class ConsumptionPricer {
return true;
}
-
private checkTimeValidity(restrictions: PricingRestriction): boolean {
+ // The time range restriction must consider the charging station timezone
+ const timezone = this.pricingModel.pricerContext.timezone;
if (!Utils.isNullOrUndefined(restrictions.timeFrom)) {
- const hours = Utils.convertToInt(restrictions.timeFrom.slice(0, 2));
- const minutes = Utils.convertToInt(restrictions.timeFrom.slice(3));
- if (moment(this.consumptionData.startedAt).isBefore(new Date().setHours(hours, minutes))) {
+ const hour = Utils.convertToInt(restrictions.timeFrom.slice(0, 2));
+ const minute = Utils.convertToInt(restrictions.timeFrom.slice(3));
+ if (moment(this.consumptionData.startedAt).tz(timezone).isBefore(moment().tz(timezone).set({ hour, minute }))) {
return false;
}
}
if (!Utils.isNullOrUndefined(restrictions.timeTo)) {
- const hours = Utils.convertToInt(restrictions.timeTo.slice(0, 2));
- const minutes = Utils.convertToInt(restrictions.timeTo.slice(3));
- if (moment(this.consumptionData.startedAt).isSameOrAfter(new Date().setHours(hours, minutes))) {
+ const hour = Utils.convertToInt(restrictions.timeTo.slice(0, 2));
+ const minute = Utils.convertToInt(restrictions.timeTo.slice(3));
+ if (moment(this.consumptionData.startedAt).tz(timezone).isSameOrAfter(moment().tz(timezone).set({ hour, minute }))) {
return false;
}
}

MODIFY
src/integration/pricing/PricingEngine.ts
src/integration/pricing/PricingEngine.ts
@@ -36,7 +36,8 @@ export default class PricingEngine {
const resolvedPricingModel: ResolvedPricingModel = {
pricerContext: {
flatFeeAlreadyPriced: false,
- sessionStartDate: transaction.timestamp
+ sessionStartDate: transaction.timestamp,
+ timezone: transaction.timezone
},
pricingDefinitions
};

MODIFY
src/types/Pricing.ts
src/types/Pricing.ts
@@ -31,6 +31,7 @@ export interface ConsumptionPricerContext {
lastAbsorbedConsumption?: number, // IMPORTANT - used to price E when stepSize is set!
lastAbsorbedChargingTime?: Date // IMPORTANT - used to price CT when stepSize is set!
lastAbsorbedParkingTime?: Date, // IMPORTANT - used to price PT when stepSize is set!
+ timezone: string; // IMPORTANT - time range restrictions must consider the charging station location (and its time zone)
}
interface PricingDefinitionInternal {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -19,6 +19,7 @@ import { UserInErrorType } from '../../src/types/InError';
import chaiSubset from 'chai-subset';
import config from '../config';
import global from '../../src/types/GlobalType';
+import moment from 'moment';
import responseHelper from '../helpers/responseHelper';
chai.use(chaiSubset);
@@ -787,8 +788,10 @@ describe('Billing', function() {
});
it('should bill an invoice taking the Time into account', async () => {
- await billingTestHelper.initChargingStationContext2TestFastCharger('NEXT_HOUR');
- await billingTestHelper.initChargingStationContext2TestFastCharger('THIS_HOUR');
+ const atThatParticularMoment = moment();
+ await billingTestHelper.initChargingStationContext2TestTimeRestrictions('OTHER_HOURS', atThatParticularMoment);
+ await billingTestHelper.initChargingStationContext2TestTimeRestrictions('NEXT_HOUR', atThatParticularMoment);
+ await billingTestHelper.initChargingStationContext2TestTimeRestrictions('FOR_HALF_AN_HOUR', atThatParticularMoment);
// A tariff applied immediately
await billingTestHelper.userService.billingApi.forceSynchronizeUser({ id: billingTestHelper.userContext.id });
const userWithBillingData = await billingTestHelper.billingImpl.getUser(billingTestHelper.userContext);

MODIFY
test/api/BillingTestHelper.ts
test/api/BillingTestHelper.ts
@@ -286,7 +286,32 @@ export default class BillingTestHelper {
// Sets all other days as the days allowed for this pricing definition
daysOfWeek: [ DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY ].filter((day) => day !== moment().isoWeekday())
};
- } else if (testMode === 'THIS_HOUR') {
+ } else {
+ dimensions = {
+ energy: {
+ price: 0.50,
+ active: true
+ }
+ };
+ }
+ await this.createTariff4ChargingStation(testMode, this.chargingStationContext.getChargingStation(), dimensions, ConnectorType.COMBO_CCS, restrictions);
+ return this.chargingStationContext;
+ }
+
+ public async initChargingStationContext2TestTimeRestrictions(testMode = 'E', aParticularMoment: moment.Moment) : Promise<ChargingStationContext> {
+ // Charing Station Context
+ this.siteContext = this.tenantContext.getSiteContext(ContextDefinition.SITE_CONTEXTS.SITE_BASIC);
+ this.siteAreaContext = this.siteContext.getSiteAreaContext(ContextDefinition.SITE_AREA_CONTEXTS.WITH_SMART_CHARGING_DC);
+ this.chargingStationContext = this.siteAreaContext.getChargingStationContext(ContextDefinition.CHARGING_STATION_CONTEXTS.ASSIGNED_OCPP16 + '-' + ContextDefinition.SITE_CONTEXTS.SITE_BASIC + '-' + ContextDefinition.SITE_AREA_CONTEXTS.WITH_SMART_CHARGING_DC);
+ assert(!!this.chargingStationContext, 'Charging station context should not be null');
+ // Make sure to take into account the Charging Station location and its timezone
+ const timezone = Utils.getTimezone(this.chargingStationContext.getChargingStation().coordinates);
+ // The moment has to be cloned to have stable tests results!
+ const atThatMoment = aParticularMoment.clone().tz(timezone);
+ // Let's create a pricing definition
+ let dimensions: PricingDimensions;
+ let restrictions: PricingRestriction;
+ if (testMode === 'FOR_HALF_AN_HOUR') {
dimensions = {
energy: {
price: 3,
@@ -294,9 +319,9 @@ export default class BillingTestHelper {
}
};
restrictions = {
- daysOfWeek: [ moment().isoWeekday() ], // Sets today as the only day allowed for this pricing definition
- timeFrom: moment().format('HH:mm'), // From this hour
- timeTo: moment().add(30, 'minutes').format('HH:mm'), // Validity for half an hour
+ daysOfWeek: [ atThatMoment.isoWeekday() ], // Sets today as the only day allowed for this pricing definition
+ timeFrom: atThatMoment.format('HH:mm'), // From this hour
+ timeTo: atThatMoment.add(30, 'minutes').format('HH:mm'), // Validity for half an hour
};
} else if (testMode === 'NEXT_HOUR') {
dimensions = {
@@ -306,16 +331,27 @@ export default class BillingTestHelper {
},
};
restrictions = {
- daysOfWeek: [ moment().isoWeekday() ], // Sets today as the only day allowed for this pricing definition
- timeFrom: moment().add(30, 'minutes').format('HH:mm'), // Valid in half an hour
- timeTo: moment().add(30 + 60, 'minutes').format('HH:mm'), // for one hour
+ daysOfWeek: [ atThatMoment.isoWeekday() ], // Sets today as the only day allowed for this pricing definition
+ timeFrom: atThatMoment.add(30, 'minutes').format('HH:mm'), // Valid in half an hour
+ timeTo: atThatMoment.add(30 + 60, 'minutes').format('HH:mm'), // for one hour
};
- } else {
+ } else { /* if (testMode === 'OTHER_HOURS') */
dimensions = {
+ flatFee: {
+ price: 0,
+ active: true
+ },
energy: {
- price: 0.50,
+ price: 666, // Weird value used to detect inconsistent pricing context resolution
active: true
- }
+ },
+ chargingTime: {
+ price: 666, // Weird value used to detect inconsistent pricing context resolution
+ active: true
+ },
+ };
+ restrictions = {
+ daysOfWeek: [ atThatMoment.isoWeekday() ], // Sets today as the only day allowed for this pricing definition
};
}
await this.createTariff4ChargingStation(testMode, this.chargingStationContext.getChargingStation(), dimensions, ConnectorType.COMBO_CCS, restrictions);

pricing - consider trasaction timezone when check time restrictions