dt-common-device 7.4.0 → 7.6.0
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/alerts/Alert.model.js +8 -1
- package/dist/alerts/Alert.service.d.ts +24 -20
- package/dist/alerts/Alert.service.js +229 -139
- package/dist/alerts/AlertBuilder.d.ts +5 -0
- package/dist/alerts/AlertBuilder.js +7 -0
- package/dist/alerts/alert.types.d.ts +10 -4
- package/dist/alerts/alert.types.js +1 -0
- package/dist/constants/Service.d.ts +2 -1
- package/dist/constants/Service.js +1 -0
- package/dist/entities/device/local/interfaces/IDevice.d.ts +1 -1
- package/dist/entities/device/local/repository/Device.repository.d.ts +0 -1
- package/dist/entities/device/local/repository/Device.repository.js +0 -12
- package/dist/entities/device/local/services/Device.service.d.ts +0 -2
- package/dist/entities/device/local/services/Device.service.js +65 -68
- package/dist/events/EventHandler.d.ts +0 -1
- package/dist/events/EventHandler.js +0 -10
- package/dist/issues/Issue.service.d.ts +17 -2
- package/dist/issues/Issue.service.js +57 -33
- package/package.json +1 -1
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.AlertSchema = exports.AlertModel = void 0;
|
|
37
37
|
const mongoose_1 = __importStar(require("mongoose"));
|
|
38
38
|
const alert_types_1 = require("./alert.types");
|
|
39
|
+
const issues_1 = require("../issues");
|
|
39
40
|
// Main Alert schema
|
|
40
41
|
const AlertSchema = new mongoose_1.Schema({
|
|
41
42
|
category: {
|
|
@@ -50,7 +51,7 @@ const AlertSchema = new mongoose_1.Schema({
|
|
|
50
51
|
},
|
|
51
52
|
zoneId: {
|
|
52
53
|
type: String,
|
|
53
|
-
required:
|
|
54
|
+
required: false,
|
|
54
55
|
index: true,
|
|
55
56
|
},
|
|
56
57
|
title: {
|
|
@@ -74,6 +75,12 @@ const AlertSchema = new mongoose_1.Schema({
|
|
|
74
75
|
required: true,
|
|
75
76
|
index: true,
|
|
76
77
|
},
|
|
78
|
+
entitySubType: {
|
|
79
|
+
type: String,
|
|
80
|
+
enum: Object.values(issues_1.EntitySubType),
|
|
81
|
+
required: true,
|
|
82
|
+
index: true,
|
|
83
|
+
},
|
|
77
84
|
severity: {
|
|
78
85
|
type: String,
|
|
79
86
|
enum: Object.values(alert_types_1.AlertSeverity),
|
|
@@ -1,45 +1,49 @@
|
|
|
1
1
|
import { IAlertDocument } from "./Alert.model";
|
|
2
2
|
import { CreateAlertData, UpdateAlertData, AlertCategory, AlertSeverity, IAlertQuery } from "./alert.types";
|
|
3
3
|
import { AlertBuilder } from "./AlertBuilder";
|
|
4
|
+
import { IDevice } from "../entities/device/local/interfaces";
|
|
5
|
+
import { Source } from "../constants";
|
|
6
|
+
import { IUser } from "../entities/admin";
|
|
4
7
|
export declare class AlertService {
|
|
5
8
|
private readonly alertRepository;
|
|
6
9
|
constructor();
|
|
7
10
|
/**
|
|
8
11
|
* Create an operations alert using AlertBuilder
|
|
9
12
|
*/
|
|
10
|
-
raiseOperationsAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
13
|
+
raiseOperationsAlert(data: CreateAlertData): Promise<IAlertDocument | null>;
|
|
11
14
|
/**
|
|
12
15
|
* Create a security alert using AlertBuilder
|
|
13
16
|
*/
|
|
14
|
-
raiseSecurityAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
15
|
-
|
|
16
|
-
* Create an energy alert using AlertBuilder
|
|
17
|
-
*/
|
|
18
|
-
raiseEnergyAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
19
|
-
/**
|
|
20
|
-
* Create a device-specific alert using AlertBuilder
|
|
21
|
-
*/
|
|
17
|
+
raiseSecurityAlert(data: CreateAlertData): Promise<IAlertDocument | null>;
|
|
18
|
+
raiseDeviceAlert(data: CreateAlertData): Promise<IAlertDocument | null>;
|
|
22
19
|
/**
|
|
23
20
|
* Create a hub-specific alert using AlertBuilder
|
|
24
21
|
*/
|
|
25
|
-
raiseHubAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
raiseHubAlert(data: CreateAlertData): Promise<IAlertDocument | null>;
|
|
23
|
+
raiseDoorLeftOpenAlert(device: IDevice, zone: any, openThreshold: number, source: Source): Promise<IAlertDocument | null>;
|
|
24
|
+
raiseNewDeviceAlert(device: {
|
|
25
|
+
deviceId: string;
|
|
26
|
+
deviceType: {
|
|
27
|
+
type: any;
|
|
28
|
+
};
|
|
29
|
+
propertyId: string;
|
|
30
|
+
name: string;
|
|
31
|
+
}, source: Source, description?: string): Promise<IAlertDocument | null>;
|
|
32
|
+
raiseDoorOpenFrequentAlert(device: IDevice, zone: any, accessTimes: number, openThreshold: number, source: Source): Promise<IAlertDocument | null>;
|
|
33
|
+
raiseDoorOpenOutsideBusinessHoursAlert(device: IDevice, zone: any, source: Source): Promise<IAlertDocument | null>;
|
|
29
34
|
/**
|
|
30
35
|
* Raise alert for device coming online (OPERATIONAL only)
|
|
31
36
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
37
|
+
raiseDeviceOnlineAlert(device: IDevice, source: Source): Promise<IAlertDocument | null>;
|
|
38
|
+
raiseLockAccessEmergencyCodeAlert(device: IDevice, zone: any, source: Source): Promise<IAlertDocument | null>;
|
|
39
|
+
raiseLockAccessMasterCodeAlert(device: IDevice, zone: any, source: Source): Promise<IAlertDocument | null>;
|
|
40
|
+
raiseSpecificDoorAccessAlert(user: IUser, device: IDevice, zone: any, source: Source): Promise<IAlertDocument | null>;
|
|
41
|
+
raiseGuestLockFirstAccessAlert(user: IUser, device: IDevice, zone: any, source: Source): Promise<IAlertDocument | null>;
|
|
38
42
|
/**
|
|
39
43
|
* Create a new alert with business logic validation
|
|
40
44
|
* Accepts either a CreateAlertData object or an AlertBuilder instance
|
|
41
45
|
*/
|
|
42
|
-
createAlert(alertData: CreateAlertData | AlertBuilder): Promise<IAlertDocument>;
|
|
46
|
+
createAlert(alertData: CreateAlertData | AlertBuilder): Promise<IAlertDocument | null>;
|
|
43
47
|
/**
|
|
44
48
|
* Get alert by ID with business logic
|
|
45
49
|
*/
|
|
@@ -77,6 +77,8 @@ const Alert_repository_1 = require("./Alert.repository");
|
|
|
77
77
|
const Alert_model_1 = require("./Alert.model");
|
|
78
78
|
const alert_types_1 = require("./alert.types");
|
|
79
79
|
const AlertBuilder_1 = require("./AlertBuilder");
|
|
80
|
+
const constants_1 = require("../constants");
|
|
81
|
+
const audit_1 = require("../audit");
|
|
80
82
|
let AlertService = (() => {
|
|
81
83
|
let _classDecorators = [(0, typedi_1.Service)()];
|
|
82
84
|
let _classDescriptor;
|
|
@@ -92,13 +94,15 @@ let AlertService = (() => {
|
|
|
92
94
|
async raiseOperationsAlert(data) {
|
|
93
95
|
const alertBuilder = AlertBuilder_1.AlertBuilder.createOperationsAlert()
|
|
94
96
|
.setPropertyId(data.propertyId)
|
|
95
|
-
.setZoneId(data.zoneId)
|
|
97
|
+
.setZoneId(data.zoneId || "")
|
|
96
98
|
.setTitle(data.title)
|
|
97
99
|
.setDescription(data.description);
|
|
98
100
|
if (data.entityId)
|
|
99
101
|
alertBuilder.setEntityId(data.entityId);
|
|
100
102
|
if (data.entityType)
|
|
101
103
|
alertBuilder.setEntityType(data.entityType);
|
|
104
|
+
if (data.entitySubType)
|
|
105
|
+
alertBuilder.setEntitySubType(data.entitySubType);
|
|
102
106
|
if (data.createdBy)
|
|
103
107
|
alertBuilder.setCreatedBy(data.createdBy);
|
|
104
108
|
return await this.createAlert(alertBuilder);
|
|
@@ -109,56 +113,40 @@ let AlertService = (() => {
|
|
|
109
113
|
async raiseSecurityAlert(data) {
|
|
110
114
|
const alertBuilder = AlertBuilder_1.AlertBuilder.createSecurityAlert()
|
|
111
115
|
.setPropertyId(data.propertyId)
|
|
112
|
-
.setZoneId(data.zoneId)
|
|
116
|
+
.setZoneId(data.zoneId || "")
|
|
113
117
|
.setTitle(data.title)
|
|
114
118
|
.setDescription(data.description);
|
|
115
119
|
if (data.entityId)
|
|
116
120
|
alertBuilder.setEntityId(data.entityId);
|
|
117
121
|
if (data.entityType)
|
|
118
122
|
alertBuilder.setEntityType(data.entityType);
|
|
123
|
+
if (data.entitySubType)
|
|
124
|
+
alertBuilder.setEntitySubType(data.entitySubType);
|
|
119
125
|
if (data.createdBy)
|
|
120
126
|
alertBuilder.setCreatedBy(data.createdBy);
|
|
121
127
|
return await this.createAlert(alertBuilder);
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
*/
|
|
126
|
-
async raiseEnergyAlert(data) {
|
|
127
|
-
const alertBuilder = AlertBuilder_1.AlertBuilder.createEnergyAlert()
|
|
128
|
-
.setPropertyId(data.propertyId)
|
|
129
|
-
.setZoneId(data.zoneId)
|
|
129
|
+
async raiseDeviceAlert(data) {
|
|
130
|
+
const alertBuilder = AlertBuilder_1.AlertBuilder.createDeviceAlert(data.entityId || "", data.propertyId, data.zoneId || "")
|
|
130
131
|
.setTitle(data.title)
|
|
131
132
|
.setDescription(data.description);
|
|
132
|
-
if (data.
|
|
133
|
-
alertBuilder.
|
|
134
|
-
if (data.
|
|
135
|
-
alertBuilder.
|
|
133
|
+
if (data.category)
|
|
134
|
+
alertBuilder.setCategory(data.category);
|
|
135
|
+
if (data.severity)
|
|
136
|
+
alertBuilder.setSeverity(data.severity);
|
|
137
|
+
if (data.type)
|
|
138
|
+
alertBuilder.setType(data.type);
|
|
136
139
|
if (data.createdBy)
|
|
137
140
|
alertBuilder.setCreatedBy(data.createdBy);
|
|
141
|
+
if (data.entitySubType)
|
|
142
|
+
alertBuilder.setEntitySubType(data.entitySubType);
|
|
138
143
|
return await this.createAlert(alertBuilder);
|
|
139
144
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Create a device-specific alert using AlertBuilder
|
|
142
|
-
*/
|
|
143
|
-
// async raiseDeviceAlert(data: CreateAlertData): Promise<IAlertDocument> {
|
|
144
|
-
// const alertBuilder = AlertBuilder.createDeviceAlert(
|
|
145
|
-
// data.entityId || "",
|
|
146
|
-
// data.propertyId,
|
|
147
|
-
// data.zoneId
|
|
148
|
-
// )
|
|
149
|
-
// .setTitle(data.title)
|
|
150
|
-
// .setDescription(data.description);
|
|
151
|
-
// if (data.category) alertBuilder.setCategory(data.category);
|
|
152
|
-
// if (data.severity) alertBuilder.setSeverity(data.severity);
|
|
153
|
-
// if (data.type) alertBuilder.setType(data.type);
|
|
154
|
-
// if (data.createdBy) alertBuilder.setCreatedBy(data.createdBy);
|
|
155
|
-
// return await this.createAlert(alertBuilder);
|
|
156
|
-
// }
|
|
157
145
|
/**
|
|
158
146
|
* Create a hub-specific alert using AlertBuilder
|
|
159
147
|
*/
|
|
160
148
|
async raiseHubAlert(data) {
|
|
161
|
-
const alertBuilder = AlertBuilder_1.AlertBuilder.createHubAlert(data.entityId || "", data.propertyId, data.zoneId)
|
|
149
|
+
const alertBuilder = AlertBuilder_1.AlertBuilder.createHubAlert(data.entityId || "", data.propertyId, data.zoneId || "")
|
|
162
150
|
.setTitle(data.title)
|
|
163
151
|
.setDescription(data.description);
|
|
164
152
|
if (data.category)
|
|
@@ -169,125 +157,227 @@ let AlertService = (() => {
|
|
|
169
157
|
alertBuilder.setType(data.type);
|
|
170
158
|
if (data.createdBy)
|
|
171
159
|
alertBuilder.setCreatedBy(data.createdBy);
|
|
160
|
+
if (data.entitySubType)
|
|
161
|
+
alertBuilder.setEntitySubType(data.entitySubType);
|
|
172
162
|
return await this.createAlert(alertBuilder);
|
|
173
163
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
164
|
+
async raiseDoorLeftOpenAlert(device, zone, openThreshold, source) {
|
|
165
|
+
return await this.createAlert({
|
|
166
|
+
entityId: device.deviceId,
|
|
167
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
168
|
+
entitySubType: device.deviceType.type,
|
|
169
|
+
propertyId: device.propertyId,
|
|
170
|
+
zoneId: device.zoneId,
|
|
171
|
+
title: "Door Left Open",
|
|
172
|
+
description: `${zone?.name} has a door left open, for more than ${openThreshold} minutes.`,
|
|
173
|
+
createdBy: source,
|
|
174
|
+
category: alert_types_1.AlertCategory.SECURITY,
|
|
175
|
+
severity: alert_types_1.AlertSeverity.CRITICAL,
|
|
176
|
+
type: alert_types_1.AlertType.DOOR_LEFT_OPEN,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async raiseNewDeviceAlert(device, source, description) {
|
|
180
|
+
return await this.createAlert({
|
|
181
|
+
entityId: device.deviceId,
|
|
182
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
183
|
+
entitySubType: device?.deviceType?.type,
|
|
184
|
+
propertyId: device?.propertyId,
|
|
185
|
+
title: "New Device Detected",
|
|
186
|
+
description: description || `New device ${device?.name} has been detected.`,
|
|
187
|
+
createdBy: source,
|
|
188
|
+
category: alert_types_1.AlertCategory.OPERATIONS,
|
|
189
|
+
severity: alert_types_1.AlertSeverity.INFO,
|
|
190
|
+
type: alert_types_1.AlertType.ACCOUNT_NEW_DEVICE,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async raiseDoorOpenFrequentAlert(device, zone, accessTimes, openThreshold, source) {
|
|
194
|
+
return await this.createAlert({
|
|
195
|
+
entityId: device.deviceId,
|
|
196
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
197
|
+
entitySubType: device.deviceType.type,
|
|
198
|
+
propertyId: device.propertyId,
|
|
199
|
+
zoneId: device.zoneId,
|
|
200
|
+
title: "Door Open Frequently",
|
|
201
|
+
description: `${zone?.name} has been opened ${accessTimes} times in the last ${openThreshold} minutes.`,
|
|
202
|
+
createdBy: source,
|
|
203
|
+
category: alert_types_1.AlertCategory.OPERATIONS,
|
|
204
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
205
|
+
type: alert_types_1.AlertType.DOOR_OPEN_FREQUENT,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async raiseDoorOpenOutsideBusinessHoursAlert(device, zone, source) {
|
|
209
|
+
return await this.createAlert({
|
|
210
|
+
entityId: device.deviceId,
|
|
211
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
212
|
+
entitySubType: device.deviceType.type,
|
|
213
|
+
propertyId: device.propertyId,
|
|
214
|
+
zoneId: device.zoneId,
|
|
215
|
+
title: "Door Open Outside Business Hours",
|
|
216
|
+
description: `${zone?.name} has been opened outside business hours.`,
|
|
217
|
+
createdBy: source,
|
|
218
|
+
category: alert_types_1.AlertCategory.SECURITY,
|
|
219
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
220
|
+
type: alert_types_1.AlertType.DOOR_OPEN_OUTSIDE_BIZ_HOURS,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
197
223
|
/**
|
|
198
224
|
* Raise alert for device coming online (OPERATIONAL only)
|
|
199
225
|
*/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
226
|
+
async raiseDeviceOnlineAlert(device, source) {
|
|
227
|
+
return await this.raiseDeviceAlert({
|
|
228
|
+
entityId: device.deviceId,
|
|
229
|
+
propertyId: device.propertyId,
|
|
230
|
+
zoneId: device.zoneId,
|
|
231
|
+
title: "Device Online",
|
|
232
|
+
description: `Device ${device.name} is now online.`,
|
|
233
|
+
category: alert_types_1.AlertCategory.OPERATIONS,
|
|
234
|
+
severity: alert_types_1.AlertSeverity.INFO,
|
|
235
|
+
createdBy: source,
|
|
236
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
237
|
+
entitySubType: device.deviceType.type,
|
|
238
|
+
type: alert_types_1.AlertType.DEVICE_ONLINE,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async raiseLockAccessEmergencyCodeAlert(device, zone, source) {
|
|
242
|
+
return await this.createAlert({
|
|
243
|
+
entityId: device.deviceId,
|
|
244
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
245
|
+
entitySubType: device.deviceType.type,
|
|
246
|
+
propertyId: device.propertyId,
|
|
247
|
+
zoneId: device.zoneId,
|
|
248
|
+
title: "Emergency Code Used",
|
|
249
|
+
description: `${zone?.name} has been accessed using the emergency code.`,
|
|
250
|
+
createdBy: source,
|
|
251
|
+
category: alert_types_1.AlertCategory.SECURITY,
|
|
252
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
253
|
+
type: alert_types_1.AlertType.LOCK_ACCESS_EMERGENCY_CODE,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async raiseLockAccessMasterCodeAlert(device, zone, source) {
|
|
257
|
+
return await this.createAlert({
|
|
258
|
+
entityId: device.deviceId,
|
|
259
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
260
|
+
entitySubType: device.deviceType.type,
|
|
261
|
+
propertyId: device.propertyId,
|
|
262
|
+
zoneId: device.zoneId,
|
|
263
|
+
title: "Master Code Used",
|
|
264
|
+
description: `${zone?.name} has been accessed using the master code.`,
|
|
265
|
+
createdBy: source,
|
|
266
|
+
category: alert_types_1.AlertCategory.SECURITY,
|
|
267
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
268
|
+
type: alert_types_1.AlertType.LOCK_ACCESS_MASTER_CODE,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
async raiseSpecificDoorAccessAlert(user, device, zone, source) {
|
|
272
|
+
return await this.createAlert({
|
|
273
|
+
entityId: device.deviceId,
|
|
274
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
275
|
+
entitySubType: device.deviceType.type,
|
|
276
|
+
propertyId: device.propertyId,
|
|
277
|
+
zoneId: device.zoneId,
|
|
278
|
+
title: "Specific Door Access",
|
|
279
|
+
description: `${user?.firstName} ${user?.lastName} has accessed ${zone?.name}.`,
|
|
280
|
+
createdBy: source,
|
|
281
|
+
category: alert_types_1.AlertCategory.OTHER,
|
|
282
|
+
severity: alert_types_1.AlertSeverity.INFO,
|
|
283
|
+
type: alert_types_1.AlertType.SPECIFIC_DOOR_ACCESS,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
async raiseGuestLockFirstAccessAlert(user, device, zone, source) {
|
|
287
|
+
return await this.createAlert({
|
|
288
|
+
entityId: device.deviceId,
|
|
289
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
290
|
+
entitySubType: device.deviceType.type,
|
|
291
|
+
propertyId: device.propertyId,
|
|
292
|
+
zoneId: device.zoneId,
|
|
293
|
+
title: "Guest Used Code for First Time",
|
|
294
|
+
description: `${user?.firstName} ${user?.lastName} has used the code for the first time to access ${zone?.name}.`,
|
|
295
|
+
createdBy: source,
|
|
296
|
+
category: alert_types_1.AlertCategory.OTHER,
|
|
297
|
+
severity: alert_types_1.AlertSeverity.INFO,
|
|
298
|
+
type: alert_types_1.AlertType.GUEST_LOCK_FIRST_ACCESS,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
266
301
|
/**
|
|
267
302
|
* Create a new alert with business logic validation
|
|
268
303
|
* Accepts either a CreateAlertData object or an AlertBuilder instance
|
|
269
304
|
*/
|
|
270
305
|
async createAlert(alertData) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
processedAlertData.snoozeUntil
|
|
288
|
-
|
|
306
|
+
try {
|
|
307
|
+
let processedAlertData;
|
|
308
|
+
// Handle AlertBuilder instance
|
|
309
|
+
if (alertData instanceof AlertBuilder_1.AlertBuilder) {
|
|
310
|
+
processedAlertData = alertData.build();
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
processedAlertData = alertData;
|
|
314
|
+
}
|
|
315
|
+
// Business logic: Validate alert data
|
|
316
|
+
this.validateAlertData(processedAlertData);
|
|
317
|
+
// Business logic: Set default severity if not provided
|
|
318
|
+
if (!processedAlertData.severity) {
|
|
319
|
+
processedAlertData.severity = this.determineDefaultSeverity(processedAlertData.category, processedAlertData.type);
|
|
320
|
+
}
|
|
321
|
+
// Business logic: Validate snooze date is in the future
|
|
322
|
+
if (processedAlertData.snoozeUntil &&
|
|
323
|
+
processedAlertData.snoozeUntil <= new Date()) {
|
|
324
|
+
throw new Error("Snooze date must be in the future");
|
|
325
|
+
}
|
|
326
|
+
const existingAlert = await this.queryAlerts({
|
|
327
|
+
propertyId: processedAlertData.propertyId,
|
|
328
|
+
zoneId: processedAlertData.zoneId,
|
|
329
|
+
entityId: processedAlertData.entityId,
|
|
330
|
+
entityType: processedAlertData.entityType,
|
|
331
|
+
entitySubType: processedAlertData.entitySubType,
|
|
332
|
+
});
|
|
333
|
+
if (existingAlert.length > 0) {
|
|
334
|
+
await (0, audit_1.pushAudit)({
|
|
335
|
+
auditType: constants_1.DT_EVENT_TYPES.ALERT.CREATE.SKIPPED,
|
|
336
|
+
auditData: {
|
|
337
|
+
reason: "Alert already exists",
|
|
338
|
+
resource: audit_1.Resource.ALERT,
|
|
339
|
+
source: processedAlertData.createdBy,
|
|
340
|
+
propertyId: processedAlertData.propertyId,
|
|
341
|
+
entityId: processedAlertData.entityId,
|
|
342
|
+
entityType: processedAlertData.entityType,
|
|
343
|
+
entitySubType: processedAlertData.entitySubType,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
const alert = await this.alertRepository.create(processedAlertData);
|
|
349
|
+
await (0, audit_1.pushAudit)({
|
|
350
|
+
auditType: constants_1.DT_EVENT_TYPES.ALERT.CREATE.SUCCESS,
|
|
351
|
+
auditData: {
|
|
352
|
+
resource: audit_1.Resource.ALERT,
|
|
353
|
+
source: constants_1.Source.USER,
|
|
354
|
+
propertyId: processedAlertData.propertyId,
|
|
355
|
+
zoneId: processedAlertData.zoneId,
|
|
356
|
+
entityId: processedAlertData.entityId,
|
|
357
|
+
entityType: processedAlertData.entityType,
|
|
358
|
+
entitySubType: processedAlertData.entitySubType,
|
|
359
|
+
type: processedAlertData.type,
|
|
360
|
+
createdBy: processedAlertData.createdBy,
|
|
361
|
+
createdAt: new Date(),
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
return alert;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
await (0, audit_1.pushAudit)({
|
|
368
|
+
auditType: constants_1.DT_EVENT_TYPES.ALERT.CREATE.FAILED,
|
|
369
|
+
auditData: {
|
|
370
|
+
resource: audit_1.Resource.ALERT,
|
|
371
|
+
source: constants_1.Source.USER,
|
|
372
|
+
propertyId: alertData?.propertyId || "",
|
|
373
|
+
zoneId: alertData?.zoneId || "",
|
|
374
|
+
errorMessage: error.message,
|
|
375
|
+
error: error,
|
|
376
|
+
createdBy: alertData?.createdBy || "",
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
throw error;
|
|
289
380
|
}
|
|
290
|
-
return await this.alertRepository.create(processedAlertData);
|
|
291
381
|
}
|
|
292
382
|
/**
|
|
293
383
|
* Get alert by ID with business logic
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EntitySubType } from "../issues";
|
|
1
2
|
import { CreateAlertData, AlertCategory, AlertSeverity, EntityType, AlertType } from "./alert.types";
|
|
2
3
|
/**
|
|
3
4
|
* AlertBuilder - A builder pattern implementation for constructing CreateAlertData objects
|
|
@@ -48,6 +49,10 @@ export declare class AlertBuilder {
|
|
|
48
49
|
* Sets the entity type
|
|
49
50
|
*/
|
|
50
51
|
setEntityType(entityType: EntityType): this;
|
|
52
|
+
/**
|
|
53
|
+
* Sets the entity sub type
|
|
54
|
+
*/
|
|
55
|
+
setEntitySubType(entitySubType: EntitySubType): this;
|
|
51
56
|
/**
|
|
52
57
|
* Sets the alert severity (optional, defaults to LOW)
|
|
53
58
|
*/
|
|
@@ -88,6 +88,13 @@ class AlertBuilder {
|
|
|
88
88
|
this.data.entityType = entityType;
|
|
89
89
|
return this;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Sets the entity sub type
|
|
93
|
+
*/
|
|
94
|
+
setEntitySubType(entitySubType) {
|
|
95
|
+
this.data.entitySubType = entitySubType;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
91
98
|
/**
|
|
92
99
|
* Sets the alert severity (optional, defaults to LOW)
|
|
93
100
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityType } from "../issues/issue.types";
|
|
1
|
+
import { EntitySubType, EntityType } from "../issues/issue.types";
|
|
2
2
|
export declare enum AlertCategory {
|
|
3
3
|
OPERATIONS = "OPERATIONS",
|
|
4
4
|
SECURITY = "SECURITY",
|
|
@@ -20,18 +20,20 @@ export declare enum AlertType {
|
|
|
20
20
|
LOCK_ACCESS_EMERGENCY_CODE = "LOCK_ACCESS_EMERGENCY_CODE",
|
|
21
21
|
LOCK_ACCESS_MASTER_CODE = "LOCK_ACCESS_MASTER_CODE",
|
|
22
22
|
SPECIFIC_DOOR_ACCESS = "SPECIFIC_DOOR_ACCESS",
|
|
23
|
-
GUEST_LOCK_FIRST_ACCESS = "GUEST_LOCK_FIRST_ACCESS"
|
|
23
|
+
GUEST_LOCK_FIRST_ACCESS = "GUEST_LOCK_FIRST_ACCESS",
|
|
24
|
+
DEVICE_ONLINE = "DEVICE_ONLINE"
|
|
24
25
|
}
|
|
25
26
|
export interface AlertDocument {
|
|
26
27
|
_id: string;
|
|
27
28
|
category: AlertCategory;
|
|
28
29
|
propertyId: string;
|
|
29
|
-
zoneId
|
|
30
|
+
zoneId?: string;
|
|
30
31
|
type: AlertType;
|
|
31
32
|
title: string;
|
|
32
33
|
description: string;
|
|
33
34
|
entityId?: string;
|
|
34
35
|
entityType: EntityType;
|
|
36
|
+
entitySubType: EntitySubType;
|
|
35
37
|
severity: AlertSeverity;
|
|
36
38
|
isRead: boolean;
|
|
37
39
|
isActive: boolean;
|
|
@@ -45,11 +47,12 @@ export interface AlertDocument {
|
|
|
45
47
|
export interface CreateAlertData {
|
|
46
48
|
category: AlertCategory;
|
|
47
49
|
propertyId: string;
|
|
48
|
-
zoneId
|
|
50
|
+
zoneId?: string;
|
|
49
51
|
title: string;
|
|
50
52
|
description: string;
|
|
51
53
|
entityId?: string;
|
|
52
54
|
entityType: EntityType;
|
|
55
|
+
entitySubType: EntitySubType;
|
|
53
56
|
severity?: AlertSeverity;
|
|
54
57
|
type?: AlertType;
|
|
55
58
|
createdBy?: string;
|
|
@@ -59,8 +62,10 @@ export interface UpdateAlertData {
|
|
|
59
62
|
category?: AlertCategory;
|
|
60
63
|
title?: string;
|
|
61
64
|
description?: string;
|
|
65
|
+
zoneId?: string;
|
|
62
66
|
entityId?: string;
|
|
63
67
|
entityType?: EntityType;
|
|
68
|
+
entitySubType?: EntitySubType;
|
|
64
69
|
severity?: AlertSeverity;
|
|
65
70
|
type?: AlertType;
|
|
66
71
|
isRead?: boolean;
|
|
@@ -74,6 +79,7 @@ export interface IAlertQuery {
|
|
|
74
79
|
category?: AlertCategory;
|
|
75
80
|
severity?: AlertSeverity;
|
|
76
81
|
entityType?: EntityType;
|
|
82
|
+
entitySubType?: EntitySubType;
|
|
77
83
|
entityId?: string;
|
|
78
84
|
type?: AlertType | AlertType[];
|
|
79
85
|
isActive?: boolean;
|
|
@@ -26,6 +26,7 @@ var AlertType;
|
|
|
26
26
|
AlertType["LOCK_ACCESS_MASTER_CODE"] = "LOCK_ACCESS_MASTER_CODE";
|
|
27
27
|
AlertType["SPECIFIC_DOOR_ACCESS"] = "SPECIFIC_DOOR_ACCESS";
|
|
28
28
|
AlertType["GUEST_LOCK_FIRST_ACCESS"] = "GUEST_LOCK_FIRST_ACCESS";
|
|
29
|
+
AlertType["DEVICE_ONLINE"] = "DEVICE_ONLINE";
|
|
29
30
|
})(AlertType || (exports.AlertType = AlertType = {}));
|
|
30
31
|
// Re-export EntityType from issue.types.ts to avoid duplication
|
|
31
32
|
var issue_types_1 = require("../issues/issue.types");
|
|
@@ -11,7 +11,6 @@ export declare class DeviceRepository {
|
|
|
11
11
|
deleteDevice(deviceId: string): Promise<void>;
|
|
12
12
|
getDevices(deviceIds: string[], withHubDetails?: boolean): Promise<IDevice[]>;
|
|
13
13
|
getPropertyDevices(propertyId: string, selectDeviceId?: boolean, type?: string, withHubDetails?: boolean): Promise<IDevice[]>;
|
|
14
|
-
getPropertyDeviceIds(propertyId: string, selectDeviceId: boolean | undefined, manufacturer: string): Promise<any>;
|
|
15
14
|
getState(deviceId: string): Promise<any>;
|
|
16
15
|
setState(deviceId: string, state: any): Promise<any>;
|
|
17
16
|
getStatus(deviceId: string): Promise<Record<string, any>>;
|
|
@@ -129,18 +129,6 @@ let DeviceRepository = (() => {
|
|
|
129
129
|
throw new Error(`Failed to get property devices: ${error.message || "Unknown error"}`);
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
async getPropertyDeviceIds(propertyId, selectDeviceId = false, manufacturer) {
|
|
133
|
-
try {
|
|
134
|
-
const response = await this.axiosInstance.get(`/devices`, {
|
|
135
|
-
params: { propertyId, selectDeviceId, manufacturer },
|
|
136
|
-
});
|
|
137
|
-
return response.data;
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
(0, config_1.getConfig)().LOGGER.error(`Failed to get property device IDs for ${propertyId}:`, error);
|
|
141
|
-
throw new Error(`Failed to get property device IDs: ${error.message || "Unknown error"}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
132
|
async getState(deviceId) {
|
|
145
133
|
try {
|
|
146
134
|
const response = await this.axiosInstance.get(`/devices/${deviceId}/state`);
|
|
@@ -12,7 +12,6 @@ export declare class LocalDeviceService {
|
|
|
12
12
|
getDevice(deviceId: string, withHubDetails?: boolean): Promise<IDevice>;
|
|
13
13
|
getDevices(deviceIds: string[], withHubDetails?: boolean): Promise<IDevice[]>;
|
|
14
14
|
getPropertyDevices(propertyId: string, selectDeviceId?: boolean, type?: string, withHubDetails?: boolean): Promise<IDevice[]>;
|
|
15
|
-
getPropertyDeviceIds(propertyId: string, selectDeviceId: boolean | undefined, type: string): Promise<any>;
|
|
16
15
|
updateDevice(deviceId: string, body: Partial<IDevice>, auditBody: IAuditProperties): Promise<any>;
|
|
17
16
|
updateDevices(query: any, updateData: any): Promise<any>;
|
|
18
17
|
deleteDevice(deviceId: string, auditBody: IAuditProperties): Promise<any>;
|
|
@@ -29,7 +28,6 @@ export declare class LocalDeviceService {
|
|
|
29
28
|
private shouldUpdateBatteryLevel;
|
|
30
29
|
updateBatteryState(deviceId: string, batteryState: any): Promise<void>;
|
|
31
30
|
private getPropertyBatteryThreshold;
|
|
32
|
-
private checkForDeviceMalfunctions;
|
|
33
31
|
getMetaData(deviceId: string): Promise<any>;
|
|
34
32
|
setMetaData(deviceId: string, metaData: Record<string, any>, auditBody: IAuditProperties): Promise<any>;
|
|
35
33
|
getDevicesByZone(zoneId: string): Promise<import("../interfaces").IDtDevice[]>;
|
|
@@ -121,12 +121,6 @@ let LocalDeviceService = (() => {
|
|
|
121
121
|
}
|
|
122
122
|
return await this.deviceRepository.getPropertyDevices(propertyId, selectDeviceId, type, withHubDetails);
|
|
123
123
|
}
|
|
124
|
-
async getPropertyDeviceIds(propertyId, selectDeviceId = false, type) {
|
|
125
|
-
if (!propertyId) {
|
|
126
|
-
throw new Error("Property ID is required");
|
|
127
|
-
}
|
|
128
|
-
return await this.deviceRepository.getPropertyDeviceIds(propertyId, selectDeviceId, type);
|
|
129
|
-
}
|
|
130
124
|
async updateDevice(deviceId, body, auditBody) {
|
|
131
125
|
if (!deviceId) {
|
|
132
126
|
throw new Error("Device ID is required");
|
|
@@ -139,6 +133,13 @@ let LocalDeviceService = (() => {
|
|
|
139
133
|
const auditType = body?.status?.online
|
|
140
134
|
? constants_1.DT_EVENT_TYPES.DEVICE.STATUS.ONLINE
|
|
141
135
|
: constants_1.DT_EVENT_TYPES.DEVICE.STATUS.OFFLINE;
|
|
136
|
+
if (body?.status?.online) {
|
|
137
|
+
await this.handleOnlineStatus(device, device.status, body.status, auditBody?.source || Service_1.Source.SYSTEM, auditBody);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await this.handleOfflineStatus(device, device.status, body.status, auditBody?.source || Service_1.Source.SYSTEM, auditBody);
|
|
141
|
+
}
|
|
142
|
+
await this.setBatteryLevel(deviceId, body.state?.batteryPercentage?.value, auditBody?.source || Service_1.Source.SYSTEM, auditBody);
|
|
142
143
|
(0, audit_1.pushAudit)({
|
|
143
144
|
auditType,
|
|
144
145
|
auditData: {
|
|
@@ -165,7 +166,20 @@ let LocalDeviceService = (() => {
|
|
|
165
166
|
? constants_1.DT_EVENT_TYPES.DEVICE.STATUS.ONLINE
|
|
166
167
|
: constants_1.DT_EVENT_TYPES.DEVICE.STATUS.OFFLINE;
|
|
167
168
|
const devices = await this.deviceRepository.queryDevices(query);
|
|
168
|
-
|
|
169
|
+
for (const device of devices) {
|
|
170
|
+
const auditBody = {
|
|
171
|
+
source: Service_1.Source.DEVICE_ACTION,
|
|
172
|
+
resource: IAuditProperties_1.Resource.DEVICE,
|
|
173
|
+
propertyId: device.propertyId,
|
|
174
|
+
zoneId: device.zoneId,
|
|
175
|
+
deviceId: device.deviceId,
|
|
176
|
+
};
|
|
177
|
+
if (updateData?.status?.online) {
|
|
178
|
+
await this.handleOnlineStatus(device, device.status, updateData.status, Service_1.Source.DEVICE_ACTION, auditBody);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
await this.handleOfflineStatus(device, device.status, updateData.status, Service_1.Source.DEVICE_ACTION, auditBody);
|
|
182
|
+
}
|
|
169
183
|
(0, audit_1.pushAudit)({
|
|
170
184
|
auditType,
|
|
171
185
|
auditData: {
|
|
@@ -181,7 +195,7 @@ let LocalDeviceService = (() => {
|
|
|
181
195
|
newStatus: updateData.status,
|
|
182
196
|
},
|
|
183
197
|
});
|
|
184
|
-
}
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
return await this.deviceRepository.updateDevices(query, updateData);
|
|
187
201
|
}
|
|
@@ -224,8 +238,8 @@ let LocalDeviceService = (() => {
|
|
|
224
238
|
const oldStatus = device.status;
|
|
225
239
|
const currentTime = new Date().toISOString();
|
|
226
240
|
// Determine if the new status is ONLINE or OFFLINE
|
|
227
|
-
const isNewStatusOnline = newStatus
|
|
228
|
-
const isNewStatusOffline = newStatus
|
|
241
|
+
const isNewStatusOnline = newStatus?.liveStatus === "ONLINE";
|
|
242
|
+
const isNewStatusOffline = newStatus?.liveStatus === "OFFLINE";
|
|
229
243
|
if (isNewStatusOffline) {
|
|
230
244
|
// New Status = OFFLINE
|
|
231
245
|
await this.handleOfflineStatus(device, oldStatus, newStatus, source, auditBody, reason, currentTime);
|
|
@@ -249,6 +263,15 @@ let LocalDeviceService = (() => {
|
|
|
249
263
|
const eventType = newStatus.online
|
|
250
264
|
? constants_1.DT_EVENT_TYPES.DEVICE.STATUS.ONLINE
|
|
251
265
|
: constants_1.DT_EVENT_TYPES.DEVICE.STATUS.OFFLINE;
|
|
266
|
+
const devices = await this.deviceRepository.queryDevices(query);
|
|
267
|
+
for (const device of devices) {
|
|
268
|
+
if (newStatus.online) {
|
|
269
|
+
await this.handleOnlineStatus(device, device.status, newStatus, source, auditBody);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
await this.handleOfflineStatus(device, device.status, newStatus, source, auditBody);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
252
275
|
await this.deviceRepository.setStatusMany(query, newStatus);
|
|
253
276
|
await this.eventHandler.onStatusChangeMany(query, newStatus, auditBody, eventType);
|
|
254
277
|
}
|
|
@@ -286,8 +309,6 @@ let LocalDeviceService = (() => {
|
|
|
286
309
|
};
|
|
287
310
|
await this.deviceRepository.setStatus(device.deviceId, newStatus);
|
|
288
311
|
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody, constants_1.DT_EVENT_TYPES.DEVICE.STATUS.OFFLINE);
|
|
289
|
-
// Raise alert (OPERATIONAL only)
|
|
290
|
-
// await this.alertService.raiseDeviceOfflineAlert(device, source, reason);
|
|
291
312
|
// Raise issue when the device goes offline if longer than the baseline (OPERATIONAL only)
|
|
292
313
|
await this.issueService.createDeviceOfflineIssue(device, source, reason);
|
|
293
314
|
}
|
|
@@ -310,9 +331,14 @@ let LocalDeviceService = (() => {
|
|
|
310
331
|
newStatus.error = {}; // Clear the error
|
|
311
332
|
await this.deviceRepository.setStatus(device.deviceId, newStatus);
|
|
312
333
|
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody, constants_1.DT_EVENT_TYPES.DEVICE.STATUS.ONLINE);
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
// Resolve issue if exists
|
|
335
|
+
await this.issueService.performIssueAction({
|
|
336
|
+
entityId: device.deviceId,
|
|
337
|
+
entityType: issue_types_1.EntityType.DEVICE,
|
|
338
|
+
type: issue_types_1.IssueType.DEVICE_OFFLINE,
|
|
339
|
+
propertyId: device.propertyId,
|
|
340
|
+
zoneId: device.zoneId,
|
|
341
|
+
}, "resolve", source);
|
|
316
342
|
}
|
|
317
343
|
}
|
|
318
344
|
else if (isExistingStatusOffline) {
|
|
@@ -323,9 +349,14 @@ let LocalDeviceService = (() => {
|
|
|
323
349
|
newStatus.error = undefined; // Clear the error
|
|
324
350
|
await this.deviceRepository.setStatus(device.deviceId, newStatus);
|
|
325
351
|
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody, constants_1.DT_EVENT_TYPES.DEVICE.STATUS.ONLINE);
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
352
|
+
//Resolve issue if exists
|
|
353
|
+
await this.issueService.performIssueAction({
|
|
354
|
+
entityId: device.deviceId,
|
|
355
|
+
entityType: issue_types_1.EntityType.DEVICE,
|
|
356
|
+
type: issue_types_1.IssueType.DEVICE_OFFLINE,
|
|
357
|
+
propertyId: device.propertyId,
|
|
358
|
+
zoneId: device.zoneId,
|
|
359
|
+
}, "resolve", source);
|
|
329
360
|
}
|
|
330
361
|
}
|
|
331
362
|
async getDeviceBaseline(deviceId) {
|
|
@@ -347,43 +378,40 @@ let LocalDeviceService = (() => {
|
|
|
347
378
|
// Get device information
|
|
348
379
|
const device = await this.getDevice(deviceId);
|
|
349
380
|
// Fetch the old battery level state
|
|
350
|
-
const
|
|
381
|
+
const oldBatteryState = device.state?.batteryPercentage;
|
|
351
382
|
// Check if battery level has changed
|
|
352
|
-
const isDifferent = !(0, lodash_1.isEqual)(
|
|
383
|
+
const isDifferent = !(0, lodash_1.isEqual)(oldBatteryState?.value, batteryLevel);
|
|
353
384
|
if (isDifferent) {
|
|
354
385
|
// Check if current time is greater than or equal to last updated time (8-hour logic)
|
|
355
|
-
const shouldUpdate = this.shouldUpdateBatteryLevel(
|
|
386
|
+
const shouldUpdate = this.shouldUpdateBatteryLevel(oldBatteryState?.lastUpdated);
|
|
356
387
|
if (shouldUpdate) {
|
|
357
388
|
// Save the battery level in the device
|
|
358
389
|
await this.deviceRepository.setBatteryLevel(deviceId, batteryLevel);
|
|
359
390
|
await this.eventHandler.onBatteryLevelChange(deviceId, batteryLevel, auditBody);
|
|
360
|
-
if (
|
|
361
|
-
batteryLevel -
|
|
362
|
-
// Battery level has increased (considering as battery has been replaced)
|
|
363
|
-
await this.eventHandler.onBatteryReplaced(deviceId, batteryLevel, auditBody);
|
|
364
|
-
}
|
|
365
|
-
// Additional condition: if battery level is less than 50, update battery state to "replace"
|
|
366
|
-
if (batteryLevel < 50) {
|
|
391
|
+
if (oldBatteryState?.value < batteryLevel &&
|
|
392
|
+
batteryLevel - oldBatteryState?.value > 50) {
|
|
393
|
+
// Battery level has increased more than 50% (considering as battery has been replaced)
|
|
367
394
|
const batteryState = {
|
|
368
395
|
batteryState: {
|
|
369
|
-
value: "
|
|
396
|
+
value: "replaced",
|
|
370
397
|
lastUpdated: new Date().toISOString(),
|
|
371
398
|
},
|
|
372
399
|
};
|
|
373
400
|
await this.updateBatteryState(deviceId, batteryState);
|
|
374
|
-
await this.eventHandler.
|
|
401
|
+
await this.eventHandler.onBatteryReplaced(deviceId, batteryLevel, auditBody);
|
|
402
|
+
// Cancel any existing battery issue
|
|
403
|
+
await this.issueService.performIssueAction({
|
|
404
|
+
entityId: deviceId,
|
|
405
|
+
entityType: issue_types_1.EntityType.DEVICE,
|
|
406
|
+
type: issue_types_1.IssueType.BATTERY_LOW,
|
|
407
|
+
propertyId: device.propertyId,
|
|
408
|
+
zoneId: device.zoneId,
|
|
409
|
+
}, "resolve", source);
|
|
375
410
|
}
|
|
376
411
|
// Get property threshold
|
|
377
412
|
const propertyThreshold = await this.getPropertyBatteryThreshold(device.propertyId);
|
|
378
413
|
// Check if battery level is below threshold
|
|
379
414
|
if (batteryLevel < propertyThreshold) {
|
|
380
|
-
// Raise alert
|
|
381
|
-
// await this.alertService.raiseDeviceBatteryAlert(
|
|
382
|
-
// device,
|
|
383
|
-
// batteryLevel,
|
|
384
|
-
// propertyThreshold,
|
|
385
|
-
// source
|
|
386
|
-
// );
|
|
387
415
|
// Raise issue when the level is below threshold
|
|
388
416
|
await this.issueService.createDeviceBatteryIssue(device, batteryLevel, propertyThreshold, issue_types_1.IssuePriority.CRITICAL, source);
|
|
389
417
|
}
|
|
@@ -415,37 +443,6 @@ let LocalDeviceService = (() => {
|
|
|
415
443
|
}
|
|
416
444
|
return preferences?.settings?.batteryPreference?.value ?? 20;
|
|
417
445
|
}
|
|
418
|
-
async checkForDeviceMalfunctions(device, source, reason) {
|
|
419
|
-
// TODO: Implement device malfunction detection logic
|
|
420
|
-
// This should check for:
|
|
421
|
-
// - Lock jammed
|
|
422
|
-
// - Device not accepting codes
|
|
423
|
-
// - Other malfunction indicators
|
|
424
|
-
// For now, we'll check if the reason indicates a malfunction
|
|
425
|
-
const malfunctionIndicators = [
|
|
426
|
-
"jammed",
|
|
427
|
-
"not accepting codes",
|
|
428
|
-
"malfunction",
|
|
429
|
-
"error",
|
|
430
|
-
"failure",
|
|
431
|
-
];
|
|
432
|
-
const hasMalfunction = malfunctionIndicators.some((indicator) => reason?.toLowerCase().includes(indicator));
|
|
433
|
-
// if (hasMalfunction) {
|
|
434
|
-
// // Raise alert for device malfunction (READINESS + OPERATIONAL)
|
|
435
|
-
// await this.alertService.raiseDeviceIssueAlert(
|
|
436
|
-
// device,
|
|
437
|
-
// "Device Malfunction Detected",
|
|
438
|
-
// source,
|
|
439
|
-
// reason
|
|
440
|
-
// );
|
|
441
|
-
// // Raise issue for device malfunction (READINESS + OPERATIONAL)
|
|
442
|
-
// await this.issueService.createDeviceMalfunctionIssue(
|
|
443
|
-
// device,
|
|
444
|
-
// source,
|
|
445
|
-
// reason
|
|
446
|
-
// );
|
|
447
|
-
// }
|
|
448
|
-
}
|
|
449
446
|
async getMetaData(deviceId) {
|
|
450
447
|
if (!deviceId) {
|
|
451
448
|
throw new Error("Device ID is required");
|
|
@@ -9,7 +9,6 @@ export declare class EventHandler {
|
|
|
9
9
|
onStatusChange(deviceId: string, status: any, auditProperties: IAuditProperties, eventType: any): Promise<void>;
|
|
10
10
|
onStatusChangeMany(query: IStatusQuery, status: IStatus, auditProperties: IAuditProperties, eventType: any): Promise<void>;
|
|
11
11
|
onBatteryLevelChange(deviceId: string, batteryLevel: number, auditProperties: IAuditProperties): Promise<void>;
|
|
12
|
-
onBatteryStateChange(deviceId: string, batteryState: any, auditProperties: IAuditProperties): Promise<void>;
|
|
13
12
|
onBatteryReplaced(deviceId: string, batteryLevel: number, auditProperties: IAuditProperties): Promise<void>;
|
|
14
13
|
onDeviceMetaChange(deviceId: string, metaData: Record<string, any>, auditProperties: IAuditProperties): Promise<void>;
|
|
15
14
|
}
|
|
@@ -125,16 +125,6 @@ let EventHandler = (() => {
|
|
|
125
125
|
},
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
|
-
async onBatteryStateChange(deviceId, batteryState, auditProperties) {
|
|
129
|
-
await (0, audit_1.pushAudit)({
|
|
130
|
-
auditType: Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.STATE.REPLACE,
|
|
131
|
-
auditData: {
|
|
132
|
-
deviceId,
|
|
133
|
-
batteryState,
|
|
134
|
-
...auditProperties,
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
128
|
async onBatteryReplaced(deviceId, batteryLevel, auditProperties) {
|
|
139
129
|
await (0, audit_1.pushAudit)({
|
|
140
130
|
auditType: Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.REPLACED,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IIssueDocument } from "./Issue.model";
|
|
2
|
-
import { CreateIssueData, UpdateIssueData, AddCommentData, IssuePriority, IssuesCategory, EntityType, IIssueQuery, IssueType } from "./issue.types";
|
|
2
|
+
import { CreateIssueData, UpdateIssueData, AddCommentData, IssuePriority, IssuesCategory, EntityType, EntitySubType, IIssueQuery, IssueType } from "./issue.types";
|
|
3
3
|
import { IssueBuilder } from "./IssueBuilder";
|
|
4
4
|
import { Source } from "../constants/Service";
|
|
5
5
|
import { IDevice } from "../entities/device/local/interfaces";
|
|
@@ -47,10 +47,25 @@ export declare class IssueService {
|
|
|
47
47
|
* Create issue for device battery level below threshold (READINESS + OPERATIONAL + ENERGY)
|
|
48
48
|
*/
|
|
49
49
|
createDeviceBatteryIssue(device: IDevice, batteryLevel: number, threshold: number, priority: IssuePriority, source: Source): Promise<IIssueDocument | null>;
|
|
50
|
+
createAccountMissingDeviceIssue(device: {
|
|
51
|
+
deviceId: string;
|
|
52
|
+
deviceType: {
|
|
53
|
+
type: any;
|
|
54
|
+
};
|
|
55
|
+
propertyId: string;
|
|
56
|
+
name: string;
|
|
57
|
+
}, source: Source, reason?: string): Promise<IIssueDocument | null>;
|
|
58
|
+
createAccountUnauthorizedIssue(account: {
|
|
59
|
+
accountId: string;
|
|
60
|
+
accountType: EntitySubType;
|
|
61
|
+
propertyId: string;
|
|
62
|
+
name: string;
|
|
63
|
+
}, source: Source, reason?: string): Promise<IIssueDocument | null>;
|
|
50
64
|
/**
|
|
51
65
|
* Create issue for device malfunction (jammed or not accepting codes) (READINESS + OPERATIONAL)
|
|
52
66
|
*/
|
|
53
67
|
createDeviceMalfunctionIssue(device: IDevice, source: Source, reason?: string): Promise<IIssueDocument | null>;
|
|
68
|
+
createLockJammedIssue(device: IDevice, source: Source): Promise<IIssueDocument | null>;
|
|
54
69
|
/**
|
|
55
70
|
* Create a maintenance issue using IssueBuilder
|
|
56
71
|
*/
|
|
@@ -113,7 +128,7 @@ export declare class IssueService {
|
|
|
113
128
|
*/
|
|
114
129
|
unassignIssue(id: string, unassignedBy: string): Promise<IIssueDocument | null>;
|
|
115
130
|
/**
|
|
116
|
-
* Cancel/Close/Resolve an issue based on query
|
|
131
|
+
* Cancel/Close/Resolve/Ignore/In_Progress/On_Hold an issue based on query
|
|
117
132
|
* This method will find an issue matching the query and update its status
|
|
118
133
|
*/
|
|
119
134
|
performIssueAction(query: IIssueQuery, action: "cancel" | "close" | "resolve" | "ignore" | "in_progress" | "on_hold", updatedBy: string): Promise<IIssueDocument | null>;
|
|
@@ -243,38 +243,18 @@ let IssueService = (() => {
|
|
|
243
243
|
entitySubType: device.deviceType.type,
|
|
244
244
|
propertyId: device.propertyId,
|
|
245
245
|
zoneId: device.zoneId,
|
|
246
|
-
title:
|
|
246
|
+
title: device.deviceType.type.toLowerCase() === "hub"
|
|
247
|
+
? "Hub Offline"
|
|
248
|
+
: "Device Offline",
|
|
247
249
|
description: `${device.name} has been offline for longer than the threshold time. ${reason ? `Reason: ${reason}.` : ""}`,
|
|
248
250
|
createdBy: source,
|
|
249
251
|
category: issue_types_1.IssuesCategory.OPERATIONS,
|
|
250
252
|
priority: issue_types_1.IssuePriority.CRITICAL,
|
|
251
|
-
type:
|
|
253
|
+
type: device.deviceType.type.toLowerCase() === "hub"
|
|
254
|
+
? issue_types_1.IssueType.HUB_OFFLINE
|
|
255
|
+
: issue_types_1.IssueType.DEVICE_OFFLINE,
|
|
252
256
|
});
|
|
253
257
|
}
|
|
254
|
-
// async createDoorLeftOpenIssue(
|
|
255
|
-
// device: IDevice,
|
|
256
|
-
// source: Source,
|
|
257
|
-
// reason?: string
|
|
258
|
-
// ): Promise<IIssueDocument | null> {
|
|
259
|
-
// const zone = await Container.get(AdminService).getZone(device.zoneId);
|
|
260
|
-
// return await this.createDeviceIssue({
|
|
261
|
-
// entityId: device.deviceId,
|
|
262
|
-
// entityType: EntityType.DEVICE,
|
|
263
|
-
// entitySubType: device.deviceType.type as EntitySubType,
|
|
264
|
-
// propertyId: device.propertyId,
|
|
265
|
-
// zoneId: device.zoneId,
|
|
266
|
-
// title: "Door Left Open - Requires Attention",
|
|
267
|
-
// description: `${
|
|
268
|
-
// zone?.name
|
|
269
|
-
// } has a door left open, for more than 10 minutes. ${
|
|
270
|
-
// reason ? `Reason: ${reason}.` : ""
|
|
271
|
-
// }`,
|
|
272
|
-
// createdBy: source,
|
|
273
|
-
// category: IssuesCategory.SECURITY,
|
|
274
|
-
// priority: IssuePriority.HIGH,
|
|
275
|
-
// type: IssueType.DOOR_LEFT_OPEN,
|
|
276
|
-
// });
|
|
277
|
-
// }
|
|
278
258
|
/**
|
|
279
259
|
* Create issue for device battery level below threshold (READINESS + OPERATIONAL + ENERGY)
|
|
280
260
|
*/
|
|
@@ -285,7 +265,7 @@ let IssueService = (() => {
|
|
|
285
265
|
entitySubType: device.deviceType.type,
|
|
286
266
|
propertyId: device.propertyId,
|
|
287
267
|
zoneId: device.zoneId,
|
|
288
|
-
title: "Device Battery Low
|
|
268
|
+
title: "Device Battery Low",
|
|
289
269
|
description: `${device.name} battery level is ${batteryLevel}%, which is below the property threshold of ${threshold}%.`,
|
|
290
270
|
createdBy: source,
|
|
291
271
|
category: issue_types_1.IssuesCategory.ENERGY,
|
|
@@ -293,6 +273,34 @@ let IssueService = (() => {
|
|
|
293
273
|
type: issue_types_1.IssueType.BATTERY_LOW,
|
|
294
274
|
});
|
|
295
275
|
}
|
|
276
|
+
async createAccountMissingDeviceIssue(device, source, reason) {
|
|
277
|
+
return await this.createIssue({
|
|
278
|
+
entityId: device.deviceId,
|
|
279
|
+
entityType: issue_types_1.EntityType.DEVICE,
|
|
280
|
+
entitySubType: device.deviceType.type,
|
|
281
|
+
propertyId: device.propertyId,
|
|
282
|
+
title: "Device Missing",
|
|
283
|
+
description: `${device.name} is missing from the account. ${reason ? `Reason: ${reason}.` : ""}`,
|
|
284
|
+
createdBy: source,
|
|
285
|
+
category: issue_types_1.IssuesCategory.OPERATIONS,
|
|
286
|
+
priority: issue_types_1.IssuePriority.CRITICAL,
|
|
287
|
+
type: issue_types_1.IssueType.ACCOUNT_MISSING_DEVICE,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async createAccountUnauthorizedIssue(account, source, reason) {
|
|
291
|
+
return await this.createIssue({
|
|
292
|
+
entityId: account.accountId,
|
|
293
|
+
entityType: issue_types_1.EntityType.CLOUD_DEVICE_ACCOUNT,
|
|
294
|
+
entitySubType: account.accountType,
|
|
295
|
+
propertyId: account.propertyId,
|
|
296
|
+
title: "Account re-authorization required",
|
|
297
|
+
description: `${account.name}'s authorization has expired. Please re-authorize the account. ${reason ? `Reason: ${reason}.` : ""}`,
|
|
298
|
+
createdBy: source,
|
|
299
|
+
category: issue_types_1.IssuesCategory.OPERATIONS,
|
|
300
|
+
priority: issue_types_1.IssuePriority.CRITICAL,
|
|
301
|
+
type: issue_types_1.IssueType.ACCOUNT_UNAUTHORIZED,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
296
304
|
/**
|
|
297
305
|
* Create issue for device malfunction (jammed or not accepting codes) (READINESS + OPERATIONAL)
|
|
298
306
|
*/
|
|
@@ -303,7 +311,7 @@ let IssueService = (() => {
|
|
|
303
311
|
entitySubType: device.deviceType.type,
|
|
304
312
|
propertyId: device.propertyId,
|
|
305
313
|
zoneId: device.zoneId,
|
|
306
|
-
title: `Device Malfunction
|
|
314
|
+
title: `Device Malfunction`,
|
|
307
315
|
description: `${device.name} is malfunctioning. ${reason ? `Reason: ${reason}.` : ""}`,
|
|
308
316
|
createdBy: source,
|
|
309
317
|
category: issue_types_1.IssuesCategory.OPERATIONS,
|
|
@@ -311,6 +319,21 @@ let IssueService = (() => {
|
|
|
311
319
|
type: issue_types_1.IssueType.DEVICE_MALFUNCTION,
|
|
312
320
|
});
|
|
313
321
|
}
|
|
322
|
+
async createLockJammedIssue(device, source) {
|
|
323
|
+
return await this.createDeviceIssue({
|
|
324
|
+
entityId: device.deviceId,
|
|
325
|
+
entityType: issue_types_1.EntityType.DEVICE,
|
|
326
|
+
entitySubType: device.deviceType.type,
|
|
327
|
+
propertyId: device.propertyId,
|
|
328
|
+
zoneId: device.zoneId,
|
|
329
|
+
title: "Lock Jammed",
|
|
330
|
+
description: `${device.name} is jammed. Requires Immediate Attention.`,
|
|
331
|
+
createdBy: source,
|
|
332
|
+
category: issue_types_1.IssuesCategory.OPERATIONS,
|
|
333
|
+
priority: issue_types_1.IssuePriority.HIGH,
|
|
334
|
+
type: issue_types_1.IssueType.LOCK_JAMMED,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
314
337
|
/**
|
|
315
338
|
* Create a maintenance issue using IssueBuilder
|
|
316
339
|
*/
|
|
@@ -647,7 +670,7 @@ let IssueService = (() => {
|
|
|
647
670
|
return await issueModel.save();
|
|
648
671
|
}
|
|
649
672
|
/**
|
|
650
|
-
* Cancel/Close/Resolve an issue based on query
|
|
673
|
+
* Cancel/Close/Resolve/Ignore/In_Progress/On_Hold an issue based on query
|
|
651
674
|
* This method will find an issue matching the query and update its status
|
|
652
675
|
*/
|
|
653
676
|
async performIssueAction(query, action, updatedBy) {
|
|
@@ -913,10 +936,11 @@ let IssueService = (() => {
|
|
|
913
936
|
applyBusinessRules(filters) {
|
|
914
937
|
// Business logic: Apply additional filters based on business rules
|
|
915
938
|
const enhancedFilters = { ...filters };
|
|
916
|
-
//
|
|
917
|
-
if (!enhancedFilters.status
|
|
918
|
-
enhancedFilters.status
|
|
919
|
-
|
|
939
|
+
// Always exclude cancelled, resolved, and closed issues unless explicitly requested
|
|
940
|
+
if (!enhancedFilters.status) {
|
|
941
|
+
enhancedFilters.status = {
|
|
942
|
+
$nin: [issue_types_1.IssueStatus.CANCELLED, issue_types_1.IssueStatus.RESOLVED, issue_types_1.IssueStatus.CLOSED],
|
|
943
|
+
};
|
|
920
944
|
}
|
|
921
945
|
return enhancedFilters;
|
|
922
946
|
}
|