@zetra/citrineos-transactions 1.8.3-fork.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/module/2.0.1/MessageApi.d.ts +30 -0
- package/dist/module/2.0.1/MessageApi.js +62 -0
- package/dist/module/2.0.1/MessageApi.js.map +1 -0
- package/dist/module/CostCalculator.d.ts +24 -0
- package/dist/module/CostCalculator.js +52 -0
- package/dist/module/CostCalculator.js.map +1 -0
- package/dist/module/CostNotifier.d.ts +27 -0
- package/dist/module/CostNotifier.js +60 -0
- package/dist/module/CostNotifier.js.map +1 -0
- package/dist/module/DataApi.d.ts +43 -0
- package/dist/module/DataApi.js +94 -0
- package/dist/module/DataApi.js.map +1 -0
- package/dist/module/Scheduler.d.ts +12 -0
- package/dist/module/Scheduler.js +33 -0
- package/dist/module/Scheduler.js.map +1 -0
- package/dist/module/StatusNotificationService.d.ts +19 -0
- package/dist/module/StatusNotificationService.js +137 -0
- package/dist/module/StatusNotificationService.js.map +1 -0
- package/dist/module/TransactionService.d.ts +25 -0
- package/dist/module/TransactionService.js +246 -0
- package/dist/module/TransactionService.js.map +1 -0
- package/dist/module/interface.d.ts +5 -0
- package/dist/module/interface.js +5 -0
- package/dist/module/interface.js.map +1 -0
- package/dist/module/model/tariffs.d.ts +10 -0
- package/dist/module/model/tariffs.js +14 -0
- package/dist/module/model/tariffs.js.map +1 -0
- package/dist/module/module.d.ts +122 -0
- package/dist/module/module.js +500 -0
- package/dist/module/module.js.map +1 -0
- package/package.json +25 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { CrudRepository, OCPP1_6, OCPP2_0_1 } from '@citrineos/base';
|
|
5
|
+
import { Component, Connector, EvseType, OCPP1_6_Mapper, OCPP2_0_1_Mapper, StatusNotification, Variable, } from '@citrineos/data';
|
|
6
|
+
import { Logger } from 'tslog';
|
|
7
|
+
export class StatusNotificationService {
|
|
8
|
+
_componentRepository;
|
|
9
|
+
_deviceModelRepository;
|
|
10
|
+
_locationRepository;
|
|
11
|
+
_logger;
|
|
12
|
+
constructor(componentRepository, deviceModelRepository, locationRepository, logger) {
|
|
13
|
+
this._componentRepository = componentRepository;
|
|
14
|
+
this._deviceModelRepository = deviceModelRepository;
|
|
15
|
+
this._locationRepository = locationRepository;
|
|
16
|
+
this._logger = logger
|
|
17
|
+
? logger.getSubLogger({ name: this.constructor.name })
|
|
18
|
+
: new Logger({ name: this.constructor.name });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Stores an internal record of the incoming status, then updates the device model for the updated connector.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} stationId - The Charging Station sending the status notification request
|
|
24
|
+
* @param {StatusNotificationRequest} statusNotificationRequest
|
|
25
|
+
*/
|
|
26
|
+
async processStatusNotification(tenantId, stationId, statusNotificationRequest) {
|
|
27
|
+
const chargingStation = await this._locationRepository.readChargingStationByStationId(tenantId, stationId);
|
|
28
|
+
if (chargingStation) {
|
|
29
|
+
const statusNotification = StatusNotification.build({
|
|
30
|
+
tenantId,
|
|
31
|
+
stationId,
|
|
32
|
+
...statusNotificationRequest,
|
|
33
|
+
});
|
|
34
|
+
await this._locationRepository.addStatusNotificationToChargingStation(tenantId, stationId, statusNotification);
|
|
35
|
+
const connector = {
|
|
36
|
+
tenantId,
|
|
37
|
+
connectorId: statusNotificationRequest.connectorId,
|
|
38
|
+
stationId,
|
|
39
|
+
status: OCPP2_0_1_Mapper.LocationMapper.mapConnectorStatus(statusNotificationRequest.connectorStatus),
|
|
40
|
+
timestamp: statusNotificationRequest.timestamp
|
|
41
|
+
? statusNotificationRequest.timestamp
|
|
42
|
+
: new Date().toISOString(),
|
|
43
|
+
};
|
|
44
|
+
await this._locationRepository.createOrUpdateConnector(tenantId, connector);
|
|
45
|
+
let components = await this._componentRepository.readAllByQuery(tenantId, {
|
|
46
|
+
where: {
|
|
47
|
+
tenantId,
|
|
48
|
+
name: 'Connector',
|
|
49
|
+
},
|
|
50
|
+
include: [
|
|
51
|
+
{
|
|
52
|
+
model: EvseType,
|
|
53
|
+
where: {
|
|
54
|
+
id: statusNotificationRequest.evseId,
|
|
55
|
+
connectorId: statusNotificationRequest.connectorId,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
model: Variable,
|
|
60
|
+
where: {
|
|
61
|
+
name: 'AvailabilityState',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
components = components.filter((component) => component.variables?.length && component.variables.length > 0);
|
|
67
|
+
if (components.length === 0) {
|
|
68
|
+
this._logger.warn('Missing component or variable for status notification. Status notification cannot be assigned to device model.');
|
|
69
|
+
}
|
|
70
|
+
for (const component of components) {
|
|
71
|
+
const variable = component.variables?.[0];
|
|
72
|
+
const reportDataType = {
|
|
73
|
+
component: component,
|
|
74
|
+
variable: variable,
|
|
75
|
+
variableAttribute: [
|
|
76
|
+
{
|
|
77
|
+
value: statusNotificationRequest.connectorStatus,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
await this._deviceModelRepository.createOrUpdateDeviceModelByStationId(tenantId, reportDataType, stationId, statusNotificationRequest.timestamp);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this._logger.warn(`Charging station ${stationId} not found. Status notification cannot be associated with a charging station.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async processOcpp16StatusNotification(tenantId, stationId, statusNotificationRequest) {
|
|
89
|
+
const chargingStation = await this._locationRepository.readChargingStationByStationId(tenantId, stationId);
|
|
90
|
+
if (chargingStation) {
|
|
91
|
+
const matchingEvse = chargingStation.evses?.find((evse) => evse.connectors?.find((connector) => connector.connectorId === statusNotificationRequest.connectorId));
|
|
92
|
+
const statusNotificationInput = {
|
|
93
|
+
tenantId,
|
|
94
|
+
...statusNotificationRequest,
|
|
95
|
+
stationId,
|
|
96
|
+
connectorStatus: statusNotificationRequest.status,
|
|
97
|
+
};
|
|
98
|
+
if (matchingEvse) {
|
|
99
|
+
statusNotificationInput.evseId = matchingEvse.evseTypeId;
|
|
100
|
+
}
|
|
101
|
+
const statusNotification = StatusNotification.build(statusNotificationInput);
|
|
102
|
+
await this._locationRepository.addStatusNotificationToChargingStation(tenantId, stationId, statusNotification);
|
|
103
|
+
const connector = {
|
|
104
|
+
tenantId,
|
|
105
|
+
connectorId: statusNotificationRequest.connectorId,
|
|
106
|
+
stationId,
|
|
107
|
+
status: OCPP1_6_Mapper.LocationMapper.mapStatusNotificationRequestStatusToConnectorStatus(statusNotificationRequest.status),
|
|
108
|
+
timestamp: statusNotificationRequest.timestamp
|
|
109
|
+
? statusNotificationRequest.timestamp
|
|
110
|
+
: new Date().toISOString(),
|
|
111
|
+
errorCode: OCPP1_6_Mapper.LocationMapper.mapStatusNotificationRequestErrorCodeToConnectorErrorCode(statusNotificationRequest.errorCode),
|
|
112
|
+
info: statusNotificationRequest.info,
|
|
113
|
+
vendorId: statusNotificationRequest.vendorId,
|
|
114
|
+
vendorErrorCode: statusNotificationRequest.vendorErrorCode,
|
|
115
|
+
};
|
|
116
|
+
if (chargingStation.use16StatusNotification0 && statusNotificationRequest.connectorId === 0) {
|
|
117
|
+
// update all connectors
|
|
118
|
+
await this._locationRepository.updateAllConnectorsByQuery(tenantId, {
|
|
119
|
+
...connector,
|
|
120
|
+
connectorId: undefined,
|
|
121
|
+
}, {
|
|
122
|
+
where: {
|
|
123
|
+
stationId,
|
|
124
|
+
tenantId,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
await this._locationRepository.createOrUpdateConnector(tenantId, connector);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this._logger.warn(`Charging station ${stationId} not found. Status notification cannot be associated with a charging station.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=StatusNotificationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusNotificationService.js","sourceRoot":"","sources":["../../src/module/StatusNotificationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IAuthorizer, IMessageContext, MeterValueDto } from '@citrineos/base';
|
|
2
|
+
import { OCPP1_6, OCPP2_0_1 } from '@citrineos/base';
|
|
3
|
+
import type { IAuthorizationRepository, ILocationRepository, IOCPPMessageRepository, IReservationRepository, ITransactionEventRepository, MeterValue } from '@citrineos/data';
|
|
4
|
+
import { Transaction } from '@citrineos/data';
|
|
5
|
+
import type { ILogObj } from 'tslog';
|
|
6
|
+
import { Logger } from 'tslog';
|
|
7
|
+
export declare class TransactionService {
|
|
8
|
+
private _transactionEventRepository;
|
|
9
|
+
private _authorizeRepository;
|
|
10
|
+
private _locationRepository;
|
|
11
|
+
private _reservationRepository;
|
|
12
|
+
private _ocppMessageRepository;
|
|
13
|
+
private _logger;
|
|
14
|
+
private _authorizers;
|
|
15
|
+
constructor(transactionEventRepository: ITransactionEventRepository, authorizeRepository: IAuthorizationRepository, locationRepository: ILocationRepository, reservationRepository: IReservationRepository, ocppMessageRepository: IOCPPMessageRepository, realTimeAuthorizer: IAuthorizer, authorizers?: IAuthorizer[], logger?: Logger<ILogObj>);
|
|
16
|
+
recalculateTotalKwh(transaction: Transaction, newMeterValues: MeterValueDto[]): Promise<number>;
|
|
17
|
+
authorizeOcpp201IdToken(tenantId: number, transactionEvent: OCPP2_0_1.TransactionEventRequest, messageContext: IMessageContext): Promise<OCPP2_0_1.TransactionEventResponse>;
|
|
18
|
+
createMeterValues(tenantId: number, meterValues: [OCPP2_0_1.MeterValueType, ...OCPP2_0_1.MeterValueType[]], transactionDbId?: number | null, transactionId?: string | null, tariffId?: number | null): Promise<MeterValue[]>;
|
|
19
|
+
authorizeOcpp16IdToken(context: IMessageContext, idToken: string, connectorId: number): Promise<OCPP1_6.StartTransactionResponse>;
|
|
20
|
+
deactivateReservation(tenantId: number, transactionId: string, reservationId: number, stationId: string): Promise<void>;
|
|
21
|
+
updateTransactionStatus(tenantId: number, stationId: string, correlationId: string, ongoingIndicator: boolean): Promise<void>;
|
|
22
|
+
private _applyAuthorizers;
|
|
23
|
+
private _hasConcurrentTransactions;
|
|
24
|
+
private _mapAuthorizationDtoToIdTokenInfo;
|
|
25
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { AuthorizationStatusEnum, MessageOrigin, MeterValueUtils, OCPP1_6, OCPP2_0_1, } from '@citrineos/base';
|
|
2
|
+
import { OCPP1_6_Mapper, OCPP2_0_1_Mapper, Transaction } from '@citrineos/data';
|
|
3
|
+
import { Logger } from 'tslog';
|
|
4
|
+
export class TransactionService {
|
|
5
|
+
_transactionEventRepository;
|
|
6
|
+
_authorizeRepository;
|
|
7
|
+
_locationRepository;
|
|
8
|
+
_reservationRepository;
|
|
9
|
+
_ocppMessageRepository;
|
|
10
|
+
_logger;
|
|
11
|
+
_authorizers;
|
|
12
|
+
constructor(transactionEventRepository, authorizeRepository, locationRepository, reservationRepository, ocppMessageRepository, realTimeAuthorizer, authorizers, logger) {
|
|
13
|
+
this._transactionEventRepository = transactionEventRepository;
|
|
14
|
+
this._authorizeRepository = authorizeRepository;
|
|
15
|
+
this._locationRepository = locationRepository;
|
|
16
|
+
this._reservationRepository = reservationRepository;
|
|
17
|
+
this._ocppMessageRepository = ocppMessageRepository;
|
|
18
|
+
this._logger = logger
|
|
19
|
+
? logger.getSubLogger({ name: this.constructor.name })
|
|
20
|
+
: new Logger({ name: this.constructor.name });
|
|
21
|
+
this._authorizers = [realTimeAuthorizer, ...(authorizers || [])];
|
|
22
|
+
}
|
|
23
|
+
async recalculateTotalKwh(transaction, newMeterValues) {
|
|
24
|
+
let meterStart = transaction.meterStart;
|
|
25
|
+
if (meterStart === null || meterStart === undefined) {
|
|
26
|
+
meterStart = MeterValueUtils.getMeterStart(newMeterValues);
|
|
27
|
+
transaction.set('meterStart', meterStart);
|
|
28
|
+
}
|
|
29
|
+
const totalKwh = MeterValueUtils.getTotalKwh(newMeterValues, transaction.totalKwh ?? 0, meterStart ?? undefined);
|
|
30
|
+
transaction.set('totalKwh', totalKwh);
|
|
31
|
+
await transaction.save();
|
|
32
|
+
this._logger.debug(`Recalculated ${totalKwh} kWh for ${transaction.id} transaction`);
|
|
33
|
+
return totalKwh;
|
|
34
|
+
}
|
|
35
|
+
async authorizeOcpp201IdToken(tenantId, transactionEvent, messageContext) {
|
|
36
|
+
const idToken = transactionEvent.idToken;
|
|
37
|
+
const authorizations = await this._authorizeRepository.readAllByQuerystring(tenantId, {
|
|
38
|
+
idToken: idToken.idToken,
|
|
39
|
+
type: OCPP2_0_1_Mapper.AuthorizationMapper.fromIdTokenEnumType(idToken.type),
|
|
40
|
+
});
|
|
41
|
+
const response = {
|
|
42
|
+
idTokenInfo: {
|
|
43
|
+
status: OCPP2_0_1.AuthorizationStatusEnumType.Unknown,
|
|
44
|
+
// TODO determine how/if to set personalMessage
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
if (authorizations.length !== 1) {
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
const authorization = authorizations[0];
|
|
51
|
+
// Extract DTO fields from sequelize Model<any, any> objects
|
|
52
|
+
const idTokenInfo = OCPP2_0_1_Mapper.AuthorizationMapper.toIdTokenInfo(authorization);
|
|
53
|
+
if (idTokenInfo.status !== OCPP2_0_1.AuthorizationStatusEnumType.Accepted) {
|
|
54
|
+
// IdTokenInfo.status is one of Blocked, Expired, Invalid, NoCredit
|
|
55
|
+
// N.B. Other non-Accepted statuses should not be allowed to be stored.
|
|
56
|
+
response.idTokenInfo = idTokenInfo;
|
|
57
|
+
return response;
|
|
58
|
+
}
|
|
59
|
+
if (idTokenInfo.cacheExpiryDateTime && new Date() > new Date(idTokenInfo.cacheExpiryDateTime)) {
|
|
60
|
+
response.idTokenInfo = {
|
|
61
|
+
status: OCPP2_0_1.AuthorizationStatusEnumType.Invalid,
|
|
62
|
+
groupIdToken: idTokenInfo.groupIdToken,
|
|
63
|
+
// TODO determine how/if to set personalMessage
|
|
64
|
+
};
|
|
65
|
+
return response;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (authorization.concurrentTransaction === true &&
|
|
69
|
+
transactionEvent.eventType === OCPP2_0_1.TransactionEventEnumType.Started) {
|
|
70
|
+
const hasConcurrent = await this._hasConcurrentTransactions(tenantId, authorization.id);
|
|
71
|
+
if (hasConcurrent) {
|
|
72
|
+
response.idTokenInfo = {
|
|
73
|
+
status: OCPP2_0_1.AuthorizationStatusEnumType.ConcurrentTx,
|
|
74
|
+
};
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let evse = undefined;
|
|
79
|
+
let connector = undefined;
|
|
80
|
+
if (transactionEvent.evse) {
|
|
81
|
+
if (transactionEvent.evse.connectorId) {
|
|
82
|
+
connector = await this._locationRepository.readConnectorByStationIdAndOcpp201EvseType(tenantId, messageContext.stationId, transactionEvent.evse);
|
|
83
|
+
}
|
|
84
|
+
evse =
|
|
85
|
+
connector?.evse ??
|
|
86
|
+
(await this._locationRepository.readEvseByStationIdAndOcpp201EvseId(tenantId, messageContext.stationId, transactionEvent.evse.id));
|
|
87
|
+
}
|
|
88
|
+
const result = await this._applyAuthorizers(authorization, messageContext, evse, connector);
|
|
89
|
+
response.idTokenInfo = this._mapAuthorizationDtoToIdTokenInfo(authorization, result);
|
|
90
|
+
}
|
|
91
|
+
this._logger.debug('idToken Authorization final status:', response.idTokenInfo.status);
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
async createMeterValues(tenantId, meterValues, transactionDbId, transactionId, tariffId) {
|
|
95
|
+
return Promise.all(meterValues.map(async (meterValue) => {
|
|
96
|
+
const hasPeriodic = meterValue.sampledValue?.some((s) => s.context === OCPP2_0_1.ReadingContextEnumType.Sample_Periodic);
|
|
97
|
+
if (transactionDbId && hasPeriodic) {
|
|
98
|
+
return await this._transactionEventRepository.createMeterValue(tenantId, meterValue, transactionDbId, transactionId, tariffId);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
return await this._transactionEventRepository.createMeterValue(tenantId, meterValue);
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
async authorizeOcpp16IdToken(context, idToken, connectorId) {
|
|
106
|
+
const response = {
|
|
107
|
+
idTagInfo: {
|
|
108
|
+
status: OCPP1_6.StartTransactionResponseStatus.Invalid,
|
|
109
|
+
},
|
|
110
|
+
transactionId: 0, // default zero for rejected transaction
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
// Find authorization
|
|
114
|
+
const tenantId = context.tenantId;
|
|
115
|
+
const authorizations = await this._authorizeRepository.readAllByQuerystring(tenantId, {
|
|
116
|
+
idToken: idToken,
|
|
117
|
+
});
|
|
118
|
+
if (authorizations.length !== 1) {
|
|
119
|
+
this._logger.error(`Found invalid authorizations ${JSON.stringify(authorizations)} for idToken: ${idToken}`);
|
|
120
|
+
return response;
|
|
121
|
+
}
|
|
122
|
+
const authorization = authorizations[0];
|
|
123
|
+
// Check expiration and status
|
|
124
|
+
if (!authorization.status) {
|
|
125
|
+
response.idTagInfo.status = OCPP1_6.StartTransactionResponseStatus.Accepted;
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
const idTokenInfoStatus = OCPP1_6_Mapper.AuthorizationMapper.toStartTransactionResponseStatus(authorization.status);
|
|
129
|
+
if (idTokenInfoStatus !== OCPP1_6.StartTransactionResponseStatus.Accepted) {
|
|
130
|
+
response.idTagInfo.status = idTokenInfoStatus;
|
|
131
|
+
return response;
|
|
132
|
+
}
|
|
133
|
+
if (authorization.cacheExpiryDateTime &&
|
|
134
|
+
new Date() > new Date(authorization.cacheExpiryDateTime)) {
|
|
135
|
+
response.idTagInfo.status = OCPP1_6.StartTransactionResponseStatus.Expired;
|
|
136
|
+
return response;
|
|
137
|
+
}
|
|
138
|
+
// Check concurrent transactions
|
|
139
|
+
const hasConcurrent = await this._hasConcurrentTransactions(tenantId, authorization.id);
|
|
140
|
+
if (hasConcurrent) {
|
|
141
|
+
response.idTagInfo.status = OCPP1_6.StartTransactionResponseStatus.ConcurrentTx;
|
|
142
|
+
return response;
|
|
143
|
+
}
|
|
144
|
+
// Check authorizers
|
|
145
|
+
const connector = await this._locationRepository.readConnectorByStationIdAndOcpp16ConnectorId(tenantId, context.stationId, connectorId);
|
|
146
|
+
response.idTagInfo.status =
|
|
147
|
+
OCPP1_6_Mapper.AuthorizationMapper.toStartTransactionResponseStatus(await this._applyAuthorizers(authorization, context, connector?.evse, connector));
|
|
148
|
+
if (response.idTagInfo.status !== OCPP1_6.StartTransactionResponseStatus.Accepted) {
|
|
149
|
+
return response;
|
|
150
|
+
}
|
|
151
|
+
// Accept the idToken
|
|
152
|
+
response.idTagInfo.status = OCPP1_6.StartTransactionResponseStatus.Accepted;
|
|
153
|
+
response.idTagInfo.expiryDate = authorization.cacheExpiryDateTime;
|
|
154
|
+
if (authorization.groupAuthorizationId) {
|
|
155
|
+
// Look up the referenced Authorization for parentIdTag
|
|
156
|
+
const parentAuth = await this._authorizeRepository.readOnlyOneByQuery(tenantId, {
|
|
157
|
+
where: { id: authorization.groupAuthorizationId },
|
|
158
|
+
});
|
|
159
|
+
if (parentAuth) {
|
|
160
|
+
response.idTagInfo.parentIdTag = parentAuth.idToken;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return response;
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
this._logger.error(`Authorization for idToken ${idToken} failed.`, e);
|
|
167
|
+
response.idTagInfo.status = OCPP1_6.StartTransactionResponseStatus.Invalid;
|
|
168
|
+
return response;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async deactivateReservation(tenantId, transactionId, reservationId, stationId) {
|
|
172
|
+
await this._reservationRepository.updateAllByQuery(tenantId, {
|
|
173
|
+
terminatedByTransaction: transactionId,
|
|
174
|
+
isActive: false,
|
|
175
|
+
}, {
|
|
176
|
+
where: {
|
|
177
|
+
tenantId,
|
|
178
|
+
id: reservationId,
|
|
179
|
+
stationId: stationId,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async updateTransactionStatus(tenantId, stationId, correlationId, ongoingIndicator) {
|
|
184
|
+
const request = await this._ocppMessageRepository.readOnlyOneByQuery(tenantId, {
|
|
185
|
+
where: {
|
|
186
|
+
tenantId,
|
|
187
|
+
stationId,
|
|
188
|
+
correlationId,
|
|
189
|
+
origin: MessageOrigin.ChargingStationManagementSystem,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
if (!request) {
|
|
193
|
+
this._logger.error(`No valid GetTransactionStatusRequest found for correlationId ${correlationId}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const transactionId = request.message[3].transactionId;
|
|
197
|
+
if (!transactionId) {
|
|
198
|
+
this._logger.error(`No valid transactionId found from the message ${request.message[3]}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const updatedTransaction = await this._transactionEventRepository.updateTransactionByStationIdAndTransactionId(tenantId, { isActive: ongoingIndicator }, transactionId, stationId);
|
|
202
|
+
if (!updatedTransaction) {
|
|
203
|
+
this._logger.error(`Update transaction ${transactionId} failed.`);
|
|
204
|
+
}
|
|
205
|
+
this._logger.info(`Updated transaction ${transactionId} isActive to ${ongoingIndicator}`);
|
|
206
|
+
}
|
|
207
|
+
async _applyAuthorizers(authorization, messageContext, evse, connector) {
|
|
208
|
+
let result = authorization.status;
|
|
209
|
+
for (const authorizer of this._authorizers) {
|
|
210
|
+
if (result !== AuthorizationStatusEnum.Accepted) {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
result = await authorizer.authorize(authorization, messageContext, evse, connector);
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
async _hasConcurrentTransactions(tenantId, authorizationId) {
|
|
218
|
+
const activeTransactions = await this._transactionEventRepository.readAllActiveTransactionsByAuthorizationId(tenantId, authorizationId);
|
|
219
|
+
return activeTransactions.length > 0;
|
|
220
|
+
}
|
|
221
|
+
_mapAuthorizationDtoToIdTokenInfo(dto, status) {
|
|
222
|
+
return {
|
|
223
|
+
status: OCPP2_0_1_Mapper.AuthorizationMapper.fromAuthorizationStatusEnumType(status),
|
|
224
|
+
cacheExpiryDateTime: dto.cacheExpiryDateTime ?? null,
|
|
225
|
+
chargingPriority: dto.chargingPriority ?? null,
|
|
226
|
+
language1: dto.language1 ?? null,
|
|
227
|
+
language2: dto.language2 ?? null,
|
|
228
|
+
groupIdToken: dto.groupAuthorization
|
|
229
|
+
? {
|
|
230
|
+
idToken: dto.groupAuthorization?.idToken ?? '',
|
|
231
|
+
type: dto.groupAuthorization?.idTokenType
|
|
232
|
+
? OCPP2_0_1_Mapper.AuthorizationMapper.toIdTokenEnumType(dto.groupAuthorization?.idTokenType)
|
|
233
|
+
: '',
|
|
234
|
+
}
|
|
235
|
+
: null,
|
|
236
|
+
personalMessage: dto.personalMessage
|
|
237
|
+
? {
|
|
238
|
+
content: dto.personalMessage.content ?? '',
|
|
239
|
+
language: dto.personalMessage.language ?? '',
|
|
240
|
+
format: dto.personalMessage.format ?? OCPP2_0_1.MessageFormatEnumType.ASCII,
|
|
241
|
+
}
|
|
242
|
+
: null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=TransactionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransactionService.js","sourceRoot":"","sources":["../../src/module/TransactionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/module/interface.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
export class UpsertTariffRequest {
|
|
5
|
+
id;
|
|
6
|
+
currency;
|
|
7
|
+
pricePerKwh;
|
|
8
|
+
pricePerMin;
|
|
9
|
+
pricePerSession;
|
|
10
|
+
taxRate;
|
|
11
|
+
authorizationAmount;
|
|
12
|
+
paymentFee;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=tariffs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tariffs.js","sourceRoot":"","sources":["../../../src/module/model/tariffs.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AACtC,MAAM,OAAO,mBAAmB;IAC9B,EAAE,CAAU;IACZ,QAAQ,CAAU;IAElB,WAAW,CAAU;IACrB,WAAW,CAAU;IACrB,eAAe,CAAU;IACzB,OAAO,CAAU;IAEjB,mBAAmB,CAAU;IAC7B,UAAU,CAAU;CACrB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { BootstrapConfig, CallAction, HandlerProperties, IAuthorizer, ICache, IFileStorage, IMessage, IMessageHandler, IMessageSender, SystemConfig } from '@citrineos/base';
|
|
2
|
+
import { AbstractModule, CrudRepository, OCPP1_6, OCPP2_0_1, OCPPValidator } from '@citrineos/base';
|
|
3
|
+
import type { IAuthorizationRepository, IDeviceModelRepository, ILocationRepository, IOCPPMessageRepository, IReservationRepository, ITariffRepository, ITransactionEventRepository } from '@citrineos/data';
|
|
4
|
+
import { Component } from '@citrineos/data';
|
|
5
|
+
import type { ILogObj } from 'tslog';
|
|
6
|
+
import { Logger } from 'tslog';
|
|
7
|
+
import { StatusNotificationService } from './StatusNotificationService.js';
|
|
8
|
+
import { TransactionService } from './TransactionService.js';
|
|
9
|
+
/**
|
|
10
|
+
* Component that handles transaction related messages.
|
|
11
|
+
*/
|
|
12
|
+
export declare class TransactionsModule extends AbstractModule {
|
|
13
|
+
_requests: CallAction[];
|
|
14
|
+
_responses: CallAction[];
|
|
15
|
+
protected _transactionEventRepository: ITransactionEventRepository;
|
|
16
|
+
protected _authorizeRepository: IAuthorizationRepository;
|
|
17
|
+
protected _deviceModelRepository: IDeviceModelRepository;
|
|
18
|
+
protected _componentRepository: CrudRepository<Component>;
|
|
19
|
+
protected _locationRepository: ILocationRepository;
|
|
20
|
+
protected _tariffRepository: ITariffRepository;
|
|
21
|
+
protected _reservationRepository: IReservationRepository;
|
|
22
|
+
protected _ocppMessageRepository: IOCPPMessageRepository;
|
|
23
|
+
protected _transactionService: TransactionService;
|
|
24
|
+
protected _statusNotificationService: StatusNotificationService;
|
|
25
|
+
protected _fileStorage: IFileStorage;
|
|
26
|
+
private readonly _authorizers;
|
|
27
|
+
private readonly _realTimeAuthorizer;
|
|
28
|
+
private readonly _signedMeterValuesUtil;
|
|
29
|
+
private _costNotifier;
|
|
30
|
+
private _costCalculator;
|
|
31
|
+
private readonly _sendCostUpdatedOnMeterValue;
|
|
32
|
+
private readonly _costUpdatedInterval;
|
|
33
|
+
/**
|
|
34
|
+
* This is the constructor function that initializes the {@link TransactionsModule}.
|
|
35
|
+
*
|
|
36
|
+
* @param {BootstrapConfig & SystemConfig} config - The `config` contains configuration settings for the module.
|
|
37
|
+
*
|
|
38
|
+
* @param {ICache} [cache] - The cache instance which is shared among the modules & Central System to pass information such as blacklisted actions or boot status.
|
|
39
|
+
*
|
|
40
|
+
* @param {IFileStorage} [fileStorage] - The `fileStorage` allows access to the configured file storage.
|
|
41
|
+
*
|
|
42
|
+
* @param {IMessageSender} [sender] - The `sender` parameter is an optional parameter that represents an instance of the {@link IMessageSender} interface.
|
|
43
|
+
* It is used to send messages from the central system to external systems or devices. If no `sender` is provided, a default {@link RabbitMqSender} instance is created and used.
|
|
44
|
+
*
|
|
45
|
+
* @param {IMessageHandler} [handler] - The `handler` parameter is an optional parameter that represents an instance of the {@link IMessageHandler} interface.
|
|
46
|
+
* It is used to handle incoming messages and dispatch them to the appropriate methods or functions. If no `handler` is provided, a default {@link RabbitMqReceiver} instance is created and used.
|
|
47
|
+
*
|
|
48
|
+
* @param {Logger<ILogObj>} [logger] - The `logger` parameter is an optional parameter that represents an instance of {@link Logger<ILogObj>}.
|
|
49
|
+
* It is used to propagate system-wide logger settings and will serve as the parent logger for any sub-component logging. If no `logger` is provided, a default {@link Logger<ILogObj>} instance is created and used.
|
|
50
|
+
*
|
|
51
|
+
* @param {ITransactionEventRepository} [transactionEventRepository] - An optional parameter of type {@link ITransactionEventRepository} which represents a repository for accessing and manipulating transaction event data.
|
|
52
|
+
* If no `transactionEventRepository` is provided, a default {@link sequelize:transactionEventRepository} instance
|
|
53
|
+
* is created and used.
|
|
54
|
+
*
|
|
55
|
+
* @param {IAuthorizationRepository} [authorizeRepository] - An optional parameter of type {@link IAuthorizationRepository} which represents a repository for accessing and manipulating authorization data.
|
|
56
|
+
* If no `authorizeRepository` is provided, a default {@link sequelize:authorizeRepository} instance is
|
|
57
|
+
* created and used.
|
|
58
|
+
*
|
|
59
|
+
* @param {IDeviceModelRepository} [deviceModelRepository] - An optional parameter of type {@link IDeviceModelRepository} which represents a repository for accessing and manipulating variable attribute data.
|
|
60
|
+
* If no `deviceModelRepository` is provided, a default {@link sequelize:deviceModelRepository} instance is
|
|
61
|
+
* created and used.
|
|
62
|
+
*
|
|
63
|
+
* @param {CrudRepository<Component>} [componentRepository] - An optional parameter of type {@link CrudRepository<Component>} which represents a repository for accessing and manipulating component data.
|
|
64
|
+
* If no `componentRepository` is provided, a default {@link sequelize:componentRepository} instance is
|
|
65
|
+
* created and used.
|
|
66
|
+
*
|
|
67
|
+
* @param {ILocationRepository} [locationRepository] - An optional parameter of type {@link ILocationRepository} which represents a repository for accessing and manipulating location and charging station data.
|
|
68
|
+
* If no `locationRepository` is provided, a default {@link sequelize:locationRepository} instance is
|
|
69
|
+
* created and used.
|
|
70
|
+
*
|
|
71
|
+
* @param {CrudRepository<Component>} [componentRepository] - An optional parameter of type {@link CrudRepository<Component>} which represents a repository for accessing and manipulating component data.
|
|
72
|
+
* If no `componentRepository` is provided, a default {@link sequelize:componentRepository} instance is
|
|
73
|
+
* created and used.
|
|
74
|
+
*
|
|
75
|
+
* @param {ILocationRepository} [locationRepository] - An optional parameter of type {@link ILocationRepository} which represents a repository for accessing and manipulating location and charging station data.
|
|
76
|
+
* If no `locationRepository` is provided, a default {@link sequelize:locationRepository} instance is
|
|
77
|
+
* created and used.
|
|
78
|
+
*
|
|
79
|
+
* @param {ITariffRepository} [tariffRepository] - An optional parameter of type {@link ITariffRepository} which
|
|
80
|
+
* represents a repository for accessing and manipulating tariff data.
|
|
81
|
+
* If no `tariffRepository` is provided, a default {@link sequelize:tariffRepository} instance is
|
|
82
|
+
* created and used.
|
|
83
|
+
*
|
|
84
|
+
* @param {IReservationRepository} [reservationRepository] - An optional parameter of type {@link IReservationRepository}
|
|
85
|
+
* which represents a repository for accessing and manipulating reservation data.
|
|
86
|
+
* If no `reservationRepository` is provided, a default {@link sequelize:reservationRepository} instance is created and used.
|
|
87
|
+
*
|
|
88
|
+
* @param {IOCPPMessageRepository} [ocppMessageRepository] - An optional parameter of type {@link IOCPPMessageRepository}
|
|
89
|
+
* which represents a repository for accessing and manipulating OCPP Message data.
|
|
90
|
+
* If no `ocppMessageRepository` is provided, a default {@link sequelize:ocppMessageRepository} instance is created and used.
|
|
91
|
+
*
|
|
92
|
+
* @param {IAuthorizer[]} [authorizers] - An optional parameter of type {@link IAuthorizer[]} which represents
|
|
93
|
+
* a list of authorizers that can be used to authorize requests.
|
|
94
|
+
*
|
|
95
|
+
* @param {IAuthorizer} [realTimeAuthorizer] - An optional parameter of type {@link IAuthorizer} which represents
|
|
96
|
+
* a real-time authorizer that can be used to authorize real-time requests.
|
|
97
|
+
*/
|
|
98
|
+
constructor(config: BootstrapConfig & SystemConfig, cache: ICache, fileStorage: IFileStorage, sender?: IMessageSender, handler?: IMessageHandler, logger?: Logger<ILogObj>, ocppValidator?: OCPPValidator, transactionEventRepository?: ITransactionEventRepository, authorizeRepository?: IAuthorizationRepository, deviceModelRepository?: IDeviceModelRepository, componentRepository?: CrudRepository<Component>, locationRepository?: ILocationRepository, tariffRepository?: ITariffRepository, reservationRepository?: IReservationRepository, ocppMessageRepository?: IOCPPMessageRepository, realTimeAuthorizer?: IAuthorizer, authorizers?: IAuthorizer[]);
|
|
99
|
+
get transactionEventRepository(): ITransactionEventRepository;
|
|
100
|
+
get authorizeRepository(): IAuthorizationRepository;
|
|
101
|
+
get deviceModelRepository(): IDeviceModelRepository;
|
|
102
|
+
get tariffRepository(): ITariffRepository;
|
|
103
|
+
get ocppMessageRepository(): IOCPPMessageRepository;
|
|
104
|
+
/**
|
|
105
|
+
* Handle OCPP 2.0.1 requests
|
|
106
|
+
*/
|
|
107
|
+
protected _handleTransactionEvent(message: IMessage<OCPP2_0_1.TransactionEventRequest>, props?: HandlerProperties): Promise<void>;
|
|
108
|
+
protected _handleMeterValues(message: IMessage<OCPP2_0_1.MeterValuesRequest>, props?: HandlerProperties): Promise<void>;
|
|
109
|
+
protected _handleStatusNotification(message: IMessage<OCPP2_0_1.StatusNotificationRequest>, props?: HandlerProperties): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Handle OCPP 2.0.1 responses
|
|
112
|
+
*/
|
|
113
|
+
protected _handleCostUpdated(message: IMessage<OCPP2_0_1.CostUpdatedResponse>, props?: HandlerProperties): void;
|
|
114
|
+
protected _handleGetTransactionStatus(message: IMessage<OCPP2_0_1.GetTransactionStatusResponse>, props?: HandlerProperties): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Handle OCPP 1.6 requests
|
|
117
|
+
*/
|
|
118
|
+
protected _handleOcpp16StatusNotification(message: IMessage<OCPP1_6.StatusNotificationRequest>, props?: HandlerProperties): Promise<void>;
|
|
119
|
+
protected _handleOcpp16MeterValues(message: IMessage<OCPP1_6.MeterValuesRequest>, props?: HandlerProperties): Promise<void>;
|
|
120
|
+
protected _handleOcpp16StartTransaction(message: IMessage<OCPP1_6.StartTransactionRequest>, props?: HandlerProperties): Promise<void>;
|
|
121
|
+
protected _handleOcpp16StopTransaction(message: IMessage<OCPP1_6.StopTransactionRequest>, props?: HandlerProperties): Promise<void>;
|
|
122
|
+
}
|