dt-common-device 7.2.0 → 7.2.4
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 +5 -0
- package/dist/alerts/Alert.repository.js +10 -0
- package/dist/alerts/Alert.service.d.ts +6 -10
- package/dist/alerts/Alert.service.js +103 -75
- package/dist/alerts/AlertBuilder.d.ts +0 -4
- package/dist/alerts/AlertBuilder.js +0 -8
- package/dist/alerts/alert.types.d.ts +15 -1
- package/dist/alerts/alert.types.js +13 -2
- package/dist/constants/Event.d.ts +8 -7
- package/dist/constants/Event.js +8 -7
- package/dist/db/db.js +2 -2
- package/dist/entities/admin/Admin.service.d.ts +1 -0
- package/dist/entities/admin/Admin.service.js +6 -0
- package/dist/entities/device/local/services/Device.service.d.ts +1 -0
- package/dist/entities/device/local/services/Device.service.js +19 -0
- package/dist/events/DeviceEventHandler.js +1 -1
- package/dist/events/EventHandler.d.ts +1 -0
- package/dist/events/EventHandler.js +10 -0
- package/dist/issues/Issue.model.js +4 -0
- package/dist/issues/Issue.repository.js +12 -0
- package/dist/issues/Issue.service.d.ts +5 -5
- package/dist/issues/Issue.service.js +202 -74
- package/dist/issues/IssueBuilder.d.ts +0 -4
- package/dist/issues/IssueBuilder.js +1 -9
- package/dist/issues/issue.types.d.ts +7 -11
- package/dist/issues/issue.types.js +0 -10
- package/package.json +1 -1
|
@@ -79,6 +79,11 @@ const AlertSchema = new mongoose_1.Schema({
|
|
|
79
79
|
enum: Object.values(alert_types_1.AlertSeverity),
|
|
80
80
|
default: alert_types_1.AlertSeverity.LOW,
|
|
81
81
|
},
|
|
82
|
+
type: {
|
|
83
|
+
type: String,
|
|
84
|
+
enum: Object.values(alert_types_1.AlertType),
|
|
85
|
+
required: true,
|
|
86
|
+
},
|
|
82
87
|
isRead: {
|
|
83
88
|
type: Boolean,
|
|
84
89
|
default: false,
|
|
@@ -68,6 +68,16 @@ let AlertRepository = (() => {
|
|
|
68
68
|
query.isRead = filters.isRead;
|
|
69
69
|
if (!filters.includeDeleted)
|
|
70
70
|
query.isDeleted = false;
|
|
71
|
+
if (filters.type)
|
|
72
|
+
query.type = filters.type;
|
|
73
|
+
// Date filtering
|
|
74
|
+
if (filters.startDate || filters.endDate) {
|
|
75
|
+
query.createdAt = {};
|
|
76
|
+
if (filters.startDate)
|
|
77
|
+
query.createdAt.$gte = filters.startDate;
|
|
78
|
+
if (filters.endDate)
|
|
79
|
+
query.createdAt.$lte = filters.endDate;
|
|
80
|
+
}
|
|
71
81
|
return query;
|
|
72
82
|
}
|
|
73
83
|
/**
|
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
import { IAlertDocument } from "./Alert.model";
|
|
2
|
-
import { CreateAlertData, UpdateAlertData, AlertCategory, AlertSeverity,
|
|
2
|
+
import { CreateAlertData, UpdateAlertData, AlertCategory, AlertSeverity, IAlertQuery } from "./alert.types";
|
|
3
3
|
import { Source } from "../constants/Service";
|
|
4
4
|
import { AlertBuilder } from "./AlertBuilder";
|
|
5
5
|
import { IDevice } from "../entities/device/local/interfaces";
|
|
6
6
|
export declare class AlertService {
|
|
7
7
|
private readonly alertRepository;
|
|
8
8
|
constructor();
|
|
9
|
-
/**
|
|
10
|
-
* Create a readiness alert using AlertBuilder
|
|
11
|
-
*/
|
|
12
|
-
raiseReadinessAlert(propertyId: string, zoneId: string, title: string, description: string, entityId?: string, entityType?: EntityType, createdBy?: string): Promise<IAlertDocument>;
|
|
13
9
|
/**
|
|
14
10
|
* Create an operations alert using AlertBuilder
|
|
15
11
|
*/
|
|
16
|
-
raiseOperationsAlert(
|
|
12
|
+
raiseOperationsAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
17
13
|
/**
|
|
18
14
|
* Create a security alert using AlertBuilder
|
|
19
15
|
*/
|
|
20
|
-
raiseSecurityAlert(
|
|
16
|
+
raiseSecurityAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
21
17
|
/**
|
|
22
18
|
* Create an energy alert using AlertBuilder
|
|
23
19
|
*/
|
|
24
|
-
raiseEnergyAlert(
|
|
20
|
+
raiseEnergyAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
25
21
|
/**
|
|
26
22
|
* Create a device-specific alert using AlertBuilder
|
|
27
23
|
*/
|
|
28
|
-
raiseDeviceAlert(
|
|
24
|
+
raiseDeviceAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
29
25
|
/**
|
|
30
26
|
* Create a hub-specific alert using AlertBuilder
|
|
31
27
|
*/
|
|
32
|
-
raiseHubAlert(
|
|
28
|
+
raiseHubAlert(data: CreateAlertData): Promise<IAlertDocument>;
|
|
33
29
|
/**
|
|
34
30
|
* Raise alert for device going offline (OPERATIONAL only)
|
|
35
31
|
*/
|
|
@@ -86,127 +86,150 @@ let AlertService = (() => {
|
|
|
86
86
|
constructor() {
|
|
87
87
|
this.alertRepository = typedi_1.default.get(Alert_repository_1.AlertRepository);
|
|
88
88
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Create a readiness alert using AlertBuilder
|
|
91
|
-
*/
|
|
92
|
-
async raiseReadinessAlert(propertyId, zoneId, title, description, entityId, entityType, createdBy) {
|
|
93
|
-
const alertBuilder = AlertBuilder_1.AlertBuilder.createReadinessAlert()
|
|
94
|
-
.setPropertyId(propertyId)
|
|
95
|
-
.setZoneId(zoneId)
|
|
96
|
-
.setTitle(title)
|
|
97
|
-
.setDescription(description);
|
|
98
|
-
if (entityId)
|
|
99
|
-
alertBuilder.setEntityId(entityId);
|
|
100
|
-
if (entityType)
|
|
101
|
-
alertBuilder.setEntityType(entityType);
|
|
102
|
-
if (createdBy)
|
|
103
|
-
alertBuilder.setCreatedBy(createdBy);
|
|
104
|
-
return await this.createAlert(alertBuilder);
|
|
105
|
-
}
|
|
106
89
|
/**
|
|
107
90
|
* Create an operations alert using AlertBuilder
|
|
108
91
|
*/
|
|
109
|
-
async raiseOperationsAlert(
|
|
92
|
+
async raiseOperationsAlert(data) {
|
|
110
93
|
const alertBuilder = AlertBuilder_1.AlertBuilder.createOperationsAlert()
|
|
111
|
-
.setPropertyId(propertyId)
|
|
112
|
-
.setZoneId(zoneId)
|
|
113
|
-
.setTitle(title)
|
|
114
|
-
.setDescription(description);
|
|
115
|
-
if (entityId)
|
|
116
|
-
alertBuilder.setEntityId(entityId);
|
|
117
|
-
if (entityType)
|
|
118
|
-
alertBuilder.setEntityType(entityType);
|
|
119
|
-
if (createdBy)
|
|
120
|
-
alertBuilder.setCreatedBy(createdBy);
|
|
94
|
+
.setPropertyId(data.propertyId)
|
|
95
|
+
.setZoneId(data.zoneId)
|
|
96
|
+
.setTitle(data.title)
|
|
97
|
+
.setDescription(data.description);
|
|
98
|
+
if (data.entityId)
|
|
99
|
+
alertBuilder.setEntityId(data.entityId);
|
|
100
|
+
if (data.entityType)
|
|
101
|
+
alertBuilder.setEntityType(data.entityType);
|
|
102
|
+
if (data.createdBy)
|
|
103
|
+
alertBuilder.setCreatedBy(data.createdBy);
|
|
121
104
|
return await this.createAlert(alertBuilder);
|
|
122
105
|
}
|
|
123
106
|
/**
|
|
124
107
|
* Create a security alert using AlertBuilder
|
|
125
108
|
*/
|
|
126
|
-
async raiseSecurityAlert(
|
|
109
|
+
async raiseSecurityAlert(data) {
|
|
127
110
|
const alertBuilder = AlertBuilder_1.AlertBuilder.createSecurityAlert()
|
|
128
|
-
.setPropertyId(propertyId)
|
|
129
|
-
.setZoneId(zoneId)
|
|
130
|
-
.setTitle(title)
|
|
131
|
-
.setDescription(description);
|
|
132
|
-
if (entityId)
|
|
133
|
-
alertBuilder.setEntityId(entityId);
|
|
134
|
-
if (entityType)
|
|
135
|
-
alertBuilder.setEntityType(entityType);
|
|
136
|
-
if (createdBy)
|
|
137
|
-
alertBuilder.setCreatedBy(createdBy);
|
|
111
|
+
.setPropertyId(data.propertyId)
|
|
112
|
+
.setZoneId(data.zoneId)
|
|
113
|
+
.setTitle(data.title)
|
|
114
|
+
.setDescription(data.description);
|
|
115
|
+
if (data.entityId)
|
|
116
|
+
alertBuilder.setEntityId(data.entityId);
|
|
117
|
+
if (data.entityType)
|
|
118
|
+
alertBuilder.setEntityType(data.entityType);
|
|
119
|
+
if (data.createdBy)
|
|
120
|
+
alertBuilder.setCreatedBy(data.createdBy);
|
|
138
121
|
return await this.createAlert(alertBuilder);
|
|
139
122
|
}
|
|
140
123
|
/**
|
|
141
124
|
* Create an energy alert using AlertBuilder
|
|
142
125
|
*/
|
|
143
|
-
async raiseEnergyAlert(
|
|
126
|
+
async raiseEnergyAlert(data) {
|
|
144
127
|
const alertBuilder = AlertBuilder_1.AlertBuilder.createEnergyAlert()
|
|
145
|
-
.setPropertyId(propertyId)
|
|
146
|
-
.setZoneId(zoneId)
|
|
147
|
-
.setTitle(title)
|
|
148
|
-
.setDescription(description);
|
|
149
|
-
if (entityId)
|
|
150
|
-
alertBuilder.setEntityId(entityId);
|
|
151
|
-
if (entityType)
|
|
152
|
-
alertBuilder.setEntityType(entityType);
|
|
153
|
-
if (createdBy)
|
|
154
|
-
alertBuilder.setCreatedBy(createdBy);
|
|
128
|
+
.setPropertyId(data.propertyId)
|
|
129
|
+
.setZoneId(data.zoneId)
|
|
130
|
+
.setTitle(data.title)
|
|
131
|
+
.setDescription(data.description);
|
|
132
|
+
if (data.entityId)
|
|
133
|
+
alertBuilder.setEntityId(data.entityId);
|
|
134
|
+
if (data.entityType)
|
|
135
|
+
alertBuilder.setEntityType(data.entityType);
|
|
136
|
+
if (data.createdBy)
|
|
137
|
+
alertBuilder.setCreatedBy(data.createdBy);
|
|
155
138
|
return await this.createAlert(alertBuilder);
|
|
156
139
|
}
|
|
157
140
|
/**
|
|
158
141
|
* Create a device-specific alert using AlertBuilder
|
|
159
142
|
*/
|
|
160
|
-
async raiseDeviceAlert(
|
|
161
|
-
const alertBuilder = AlertBuilder_1.AlertBuilder.createDeviceAlert(
|
|
162
|
-
.setTitle(title)
|
|
163
|
-
.setDescription(description);
|
|
164
|
-
if (category)
|
|
165
|
-
alertBuilder.setCategory(category);
|
|
166
|
-
if (severity)
|
|
167
|
-
alertBuilder.setSeverity(severity);
|
|
168
|
-
if (
|
|
169
|
-
alertBuilder.setCreatedBy(
|
|
143
|
+
async raiseDeviceAlert(data) {
|
|
144
|
+
const alertBuilder = AlertBuilder_1.AlertBuilder.createDeviceAlert(data.entityId || "", data.propertyId, data.zoneId)
|
|
145
|
+
.setTitle(data.title)
|
|
146
|
+
.setDescription(data.description);
|
|
147
|
+
if (data.category)
|
|
148
|
+
alertBuilder.setCategory(data.category);
|
|
149
|
+
if (data.severity)
|
|
150
|
+
alertBuilder.setSeverity(data.severity);
|
|
151
|
+
if (data.createdBy)
|
|
152
|
+
alertBuilder.setCreatedBy(data.createdBy);
|
|
170
153
|
return await this.createAlert(alertBuilder);
|
|
171
154
|
}
|
|
172
155
|
/**
|
|
173
156
|
* Create a hub-specific alert using AlertBuilder
|
|
174
157
|
*/
|
|
175
|
-
async raiseHubAlert(
|
|
176
|
-
const alertBuilder = AlertBuilder_1.AlertBuilder.createHubAlert(
|
|
177
|
-
.setTitle(title)
|
|
178
|
-
.setDescription(description);
|
|
179
|
-
if (category)
|
|
180
|
-
alertBuilder.setCategory(category);
|
|
181
|
-
if (severity)
|
|
182
|
-
alertBuilder.setSeverity(severity);
|
|
183
|
-
if (createdBy)
|
|
184
|
-
alertBuilder.setCreatedBy(createdBy);
|
|
158
|
+
async raiseHubAlert(data) {
|
|
159
|
+
const alertBuilder = AlertBuilder_1.AlertBuilder.createHubAlert(data.entityId || "", data.propertyId, data.zoneId)
|
|
160
|
+
.setTitle(data.title)
|
|
161
|
+
.setDescription(data.description);
|
|
162
|
+
if (data.category)
|
|
163
|
+
alertBuilder.setCategory(data.category);
|
|
164
|
+
if (data.severity)
|
|
165
|
+
alertBuilder.setSeverity(data.severity);
|
|
166
|
+
if (data.createdBy)
|
|
167
|
+
alertBuilder.setCreatedBy(data.createdBy);
|
|
185
168
|
return await this.createAlert(alertBuilder);
|
|
186
169
|
}
|
|
187
170
|
/**
|
|
188
171
|
* Raise alert for device going offline (OPERATIONAL only)
|
|
189
172
|
*/
|
|
190
173
|
async raiseDeviceOfflineAlert(device, source, reason) {
|
|
191
|
-
return await this.raiseDeviceAlert(
|
|
174
|
+
return await this.raiseDeviceAlert({
|
|
175
|
+
entityId: device.deviceId,
|
|
176
|
+
propertyId: device.propertyId,
|
|
177
|
+
zoneId: device.zoneId,
|
|
178
|
+
title: "Device Offline",
|
|
179
|
+
description: `Device ${device.name} has gone offline. ${reason ? `Reason: ${reason}` : ""}`,
|
|
180
|
+
category: alert_types_1.AlertCategory.OPERATIONS,
|
|
181
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
182
|
+
createdBy: source,
|
|
183
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
184
|
+
});
|
|
192
185
|
}
|
|
193
186
|
/**
|
|
194
187
|
* Raise alert for device coming online (OPERATIONAL only)
|
|
195
188
|
*/
|
|
196
189
|
async raiseDeviceOnlineAlert(device, source, reason) {
|
|
197
|
-
return await this.raiseDeviceAlert(
|
|
190
|
+
return await this.raiseDeviceAlert({
|
|
191
|
+
entityId: device.deviceId,
|
|
192
|
+
propertyId: device.propertyId,
|
|
193
|
+
zoneId: device.zoneId,
|
|
194
|
+
title: "Device Online",
|
|
195
|
+
description: `Device ${device.name} is now online. ${reason ? `Reason: ${reason}` : ""}`,
|
|
196
|
+
category: alert_types_1.AlertCategory.OPERATIONS,
|
|
197
|
+
severity: alert_types_1.AlertSeverity.INFO,
|
|
198
|
+
createdBy: source,
|
|
199
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
200
|
+
});
|
|
198
201
|
}
|
|
199
202
|
/**
|
|
200
203
|
* Raise alert for device battery level below threshold (READINESS + OPERATIONAL + ENERGY)
|
|
201
204
|
*/
|
|
202
205
|
async raiseDeviceBatteryAlert(device, batteryLevel, threshold, source) {
|
|
203
|
-
return await this.raiseDeviceAlert(
|
|
206
|
+
return await this.raiseDeviceAlert({
|
|
207
|
+
entityId: device.deviceId,
|
|
208
|
+
propertyId: device.propertyId,
|
|
209
|
+
zoneId: device.zoneId,
|
|
210
|
+
title: "Device Battery Low",
|
|
211
|
+
description: `Device ${device.name} (${device.deviceId}) battery level is ${batteryLevel}%, which is below the property threshold of ${threshold}%.`,
|
|
212
|
+
category: alert_types_1.AlertCategory.ENERGY,
|
|
213
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
214
|
+
createdBy: source,
|
|
215
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
216
|
+
});
|
|
204
217
|
}
|
|
205
218
|
/**
|
|
206
219
|
* Raise alert for device issue (jammed or malfunctioned) (READINESS + OPERATIONAL)
|
|
207
220
|
*/
|
|
208
221
|
async raiseDeviceIssueAlert(device, issueType, source, reason) {
|
|
209
|
-
return await this.raiseDeviceAlert(
|
|
222
|
+
return await this.raiseDeviceAlert({
|
|
223
|
+
entityId: device.deviceId,
|
|
224
|
+
propertyId: device.propertyId,
|
|
225
|
+
zoneId: device.zoneId,
|
|
226
|
+
title: `Device Issue - ${issueType}`,
|
|
227
|
+
description: `Device ${device.name} has an issue: ${issueType}. ${reason ? `Reason: ${reason}` : ""}`,
|
|
228
|
+
category: alert_types_1.AlertCategory.OPERATIONS,
|
|
229
|
+
severity: alert_types_1.AlertSeverity.HIGH,
|
|
230
|
+
createdBy: source,
|
|
231
|
+
entityType: alert_types_1.EntityType.DEVICE,
|
|
232
|
+
});
|
|
210
233
|
}
|
|
211
234
|
/**
|
|
212
235
|
* Create a new alert with business logic validation
|
|
@@ -521,6 +544,12 @@ let AlertService = (() => {
|
|
|
521
544
|
if (filters.skip && filters.skip < 0) {
|
|
522
545
|
throw new Error("Skip must be non-negative");
|
|
523
546
|
}
|
|
547
|
+
// Validate date filters
|
|
548
|
+
if (filters.startDate &&
|
|
549
|
+
filters.endDate &&
|
|
550
|
+
filters.startDate > filters.endDate) {
|
|
551
|
+
throw new Error("Start date must be before or equal to end date");
|
|
552
|
+
}
|
|
524
553
|
}
|
|
525
554
|
validateUpdateData(data) {
|
|
526
555
|
if (data.title && data.title.trim().length < 3) {
|
|
@@ -538,7 +567,6 @@ let AlertService = (() => {
|
|
|
538
567
|
determineDefaultSeverity(category) {
|
|
539
568
|
// Business logic: Determine default severity based on category
|
|
540
569
|
const categorySeverities = {
|
|
541
|
-
[alert_types_1.AlertCategory.READINESS]: alert_types_1.AlertSeverity.LOW,
|
|
542
570
|
[alert_types_1.AlertCategory.OPERATIONS]: alert_types_1.AlertSeverity.HIGH,
|
|
543
571
|
[alert_types_1.AlertCategory.SECURITY]: alert_types_1.AlertSeverity.CRITICAL,
|
|
544
572
|
[alert_types_1.AlertCategory.ENERGY]: alert_types_1.AlertSeverity.LOW,
|
|
@@ -73,10 +73,6 @@ export declare class AlertBuilder {
|
|
|
73
73
|
* Resets the builder to its initial state
|
|
74
74
|
*/
|
|
75
75
|
reset(): this;
|
|
76
|
-
/**
|
|
77
|
-
* Creates a new builder instance with predefined values for common alert types
|
|
78
|
-
*/
|
|
79
|
-
static createReadinessAlert(): AlertBuilder;
|
|
80
76
|
static createOperationsAlert(): AlertBuilder;
|
|
81
77
|
static createSecurityAlert(): AlertBuilder;
|
|
82
78
|
static createEnergyAlert(): AlertBuilder;
|
|
@@ -152,14 +152,6 @@ class AlertBuilder {
|
|
|
152
152
|
this.data = {};
|
|
153
153
|
return this;
|
|
154
154
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Creates a new builder instance with predefined values for common alert types
|
|
157
|
-
*/
|
|
158
|
-
static createReadinessAlert() {
|
|
159
|
-
return new AlertBuilder()
|
|
160
|
-
.setCategory(alert_types_1.AlertCategory.READINESS)
|
|
161
|
-
.setSeverity(alert_types_1.AlertSeverity.LOW);
|
|
162
|
-
}
|
|
163
155
|
static createOperationsAlert() {
|
|
164
156
|
return new AlertBuilder()
|
|
165
157
|
.setCategory(alert_types_1.AlertCategory.OPERATIONS)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { EntityType } from "../issues/issue.types";
|
|
2
2
|
export declare enum AlertCategory {
|
|
3
|
-
READINESS = "READINESS",
|
|
4
3
|
OPERATIONS = "OPERATIONS",
|
|
5
4
|
SECURITY = "SECURITY",
|
|
6
5
|
ENERGY = "ENERGY",
|
|
@@ -12,11 +11,23 @@ export declare enum AlertSeverity {
|
|
|
12
11
|
HIGH = "HIGH",
|
|
13
12
|
CRITICAL = "CRITICAL"
|
|
14
13
|
}
|
|
14
|
+
export declare enum AlertType {
|
|
15
|
+
ACCOUNT_NEW_DEVICE = "ACCOUNT_NEW_DEVICE",
|
|
16
|
+
DEVICE_TAMPER_ATTEMPT = "DEVICE_TAMPER_ATTEMPT",
|
|
17
|
+
DOOR_LEFT_OPEN = "DOOR_LEFT_OPEN",
|
|
18
|
+
DOOR_OPEN_FREQUENT = "DOOR_OPEN_FREQUENT",
|
|
19
|
+
DOOR_OPEN_OUTSIDE_BIZ_HOURS = "DOOR_OPEN_OUTSIDE_BIZ_HOURS",
|
|
20
|
+
LOCK_ACCESS_EMERGENCY_CODE = "LOCK_ACCESS_EMERGENCY_CODE",
|
|
21
|
+
LOCK_ACCESS_MASTER_CODE = "LOCK_ACCESS_MASTER_CODE",
|
|
22
|
+
SPECIFIC_DOOR_ACCESS = "SPECIFIC_DOOR_ACCESS",
|
|
23
|
+
GUEST_LOCK_FIRST_ACCESS = "GUEST_LOCK_FIRST_ACCESS"
|
|
24
|
+
}
|
|
15
25
|
export interface AlertDocument {
|
|
16
26
|
_id: string;
|
|
17
27
|
category: AlertCategory;
|
|
18
28
|
propertyId: string;
|
|
19
29
|
zoneId: string;
|
|
30
|
+
type: AlertType;
|
|
20
31
|
title: string;
|
|
21
32
|
description: string;
|
|
22
33
|
entityId?: string;
|
|
@@ -62,9 +73,12 @@ export interface IAlertQuery {
|
|
|
62
73
|
severity?: AlertSeverity;
|
|
63
74
|
entityType?: EntityType;
|
|
64
75
|
entityId?: string;
|
|
76
|
+
type?: AlertType;
|
|
65
77
|
isActive?: boolean;
|
|
66
78
|
isRead?: boolean;
|
|
67
79
|
includeDeleted?: boolean;
|
|
80
|
+
startDate?: Date;
|
|
81
|
+
endDate?: Date;
|
|
68
82
|
sort?: {
|
|
69
83
|
[key: string]: 1 | -1;
|
|
70
84
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EntityType = exports.AlertSeverity = exports.AlertCategory = void 0;
|
|
3
|
+
exports.EntityType = exports.AlertType = exports.AlertSeverity = exports.AlertCategory = void 0;
|
|
4
4
|
var AlertCategory;
|
|
5
5
|
(function (AlertCategory) {
|
|
6
|
-
AlertCategory["READINESS"] = "READINESS";
|
|
7
6
|
AlertCategory["OPERATIONS"] = "OPERATIONS";
|
|
8
7
|
AlertCategory["SECURITY"] = "SECURITY";
|
|
9
8
|
AlertCategory["ENERGY"] = "ENERGY";
|
|
@@ -16,6 +15,18 @@ var AlertSeverity;
|
|
|
16
15
|
AlertSeverity["HIGH"] = "HIGH";
|
|
17
16
|
AlertSeverity["CRITICAL"] = "CRITICAL";
|
|
18
17
|
})(AlertSeverity || (exports.AlertSeverity = AlertSeverity = {}));
|
|
18
|
+
var AlertType;
|
|
19
|
+
(function (AlertType) {
|
|
20
|
+
AlertType["ACCOUNT_NEW_DEVICE"] = "ACCOUNT_NEW_DEVICE";
|
|
21
|
+
AlertType["DEVICE_TAMPER_ATTEMPT"] = "DEVICE_TAMPER_ATTEMPT";
|
|
22
|
+
AlertType["DOOR_LEFT_OPEN"] = "DOOR_LEFT_OPEN";
|
|
23
|
+
AlertType["DOOR_OPEN_FREQUENT"] = "DOOR_OPEN_FREQUENT";
|
|
24
|
+
AlertType["DOOR_OPEN_OUTSIDE_BIZ_HOURS"] = "DOOR_OPEN_OUTSIDE_BIZ_HOURS";
|
|
25
|
+
AlertType["LOCK_ACCESS_EMERGENCY_CODE"] = "LOCK_ACCESS_EMERGENCY_CODE";
|
|
26
|
+
AlertType["LOCK_ACCESS_MASTER_CODE"] = "LOCK_ACCESS_MASTER_CODE";
|
|
27
|
+
AlertType["SPECIFIC_DOOR_ACCESS"] = "SPECIFIC_DOOR_ACCESS";
|
|
28
|
+
AlertType["GUEST_LOCK_FIRST_ACCESS"] = "GUEST_LOCK_FIRST_ACCESS";
|
|
29
|
+
})(AlertType || (exports.AlertType = AlertType = {}));
|
|
19
30
|
// Re-export EntityType from issue.types.ts to avoid duplication
|
|
20
31
|
var issue_types_1 = require("../issues/issue.types");
|
|
21
32
|
Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return issue_types_1.EntityType; } });
|
|
@@ -41,7 +41,9 @@ export declare const DT_EVENT_TYPES: {
|
|
|
41
41
|
UNKNOWN: string;
|
|
42
42
|
};
|
|
43
43
|
BATTERY: {
|
|
44
|
-
|
|
44
|
+
STATE: {
|
|
45
|
+
REPLACE: string;
|
|
46
|
+
};
|
|
45
47
|
LEVEL: {
|
|
46
48
|
UNKNOWN: string;
|
|
47
49
|
NORMAL: string;
|
|
@@ -172,27 +174,26 @@ export declare const DT_EVENT_TYPES: {
|
|
|
172
174
|
};
|
|
173
175
|
UPDATE: {
|
|
174
176
|
SUCCESS: string;
|
|
177
|
+
FAILED: string;
|
|
175
178
|
};
|
|
176
179
|
DELETE: {
|
|
177
180
|
SUCCESS: string;
|
|
178
|
-
|
|
179
|
-
CANCEL: {
|
|
180
|
-
SUCCESS: string;
|
|
181
|
-
};
|
|
182
|
-
RESOLVE: {
|
|
183
|
-
SUCCESS: string;
|
|
181
|
+
FAILED: string;
|
|
184
182
|
};
|
|
185
183
|
};
|
|
186
184
|
ALERT: {
|
|
187
185
|
CREATE: {
|
|
188
186
|
SUCCESS: string;
|
|
187
|
+
FAILED: string;
|
|
189
188
|
SKIPPED: string;
|
|
190
189
|
};
|
|
191
190
|
UPDATE: {
|
|
192
191
|
SUCCESS: string;
|
|
192
|
+
FAILED: string;
|
|
193
193
|
};
|
|
194
194
|
DELETE: {
|
|
195
195
|
SUCCESS: string;
|
|
196
|
+
FAILED: string;
|
|
196
197
|
};
|
|
197
198
|
};
|
|
198
199
|
};
|
package/dist/constants/Event.js
CHANGED
|
@@ -44,7 +44,9 @@ exports.DT_EVENT_TYPES = {
|
|
|
44
44
|
UNKNOWN: "device.status.unknown",
|
|
45
45
|
},
|
|
46
46
|
BATTERY: {
|
|
47
|
-
|
|
47
|
+
STATE: {
|
|
48
|
+
REPLACE: "device.battery.state.replace",
|
|
49
|
+
},
|
|
48
50
|
LEVEL: {
|
|
49
51
|
UNKNOWN: "device.battery.level.unknown",
|
|
50
52
|
NORMAL: "device.battery.level.normal",
|
|
@@ -175,27 +177,26 @@ exports.DT_EVENT_TYPES = {
|
|
|
175
177
|
},
|
|
176
178
|
UPDATE: {
|
|
177
179
|
SUCCESS: "issue.update.success",
|
|
180
|
+
FAILED: "issue.update.failed",
|
|
178
181
|
},
|
|
179
182
|
DELETE: {
|
|
180
183
|
SUCCESS: "issue.delete.success",
|
|
184
|
+
FAILED: "issue.delete.failed",
|
|
181
185
|
},
|
|
182
|
-
CANCEL: {
|
|
183
|
-
SUCCESS: "issue.cancel.success",
|
|
184
|
-
},
|
|
185
|
-
RESOLVE: {
|
|
186
|
-
SUCCESS: "issue.resolve.success",
|
|
187
|
-
}
|
|
188
186
|
},
|
|
189
187
|
ALERT: {
|
|
190
188
|
CREATE: {
|
|
191
189
|
SUCCESS: "alert.create.success",
|
|
190
|
+
FAILED: "alert.create.failed",
|
|
192
191
|
SKIPPED: "alert.create.skipped",
|
|
193
192
|
},
|
|
194
193
|
UPDATE: {
|
|
195
194
|
SUCCESS: "alert.update.success",
|
|
195
|
+
FAILED: "alert.update.failed",
|
|
196
196
|
},
|
|
197
197
|
DELETE: {
|
|
198
198
|
SUCCESS: "alert.delete.success",
|
|
199
|
+
FAILED: "alert.delete.failed",
|
|
199
200
|
},
|
|
200
201
|
},
|
|
201
202
|
};
|
package/dist/db/db.js
CHANGED
|
@@ -44,10 +44,10 @@ const connectDatabase = async () => {
|
|
|
44
44
|
try {
|
|
45
45
|
const URI = (0, config_1.getMongoUri)();
|
|
46
46
|
await mongoose_1.default.connect(URI);
|
|
47
|
-
|
|
47
|
+
(0, config_1.getLogger)().info(`Connected to MongoDB at ${URI.split("/").at(-1)}`);
|
|
48
48
|
}
|
|
49
49
|
catch (error) {
|
|
50
|
-
|
|
50
|
+
(0, config_1.getLogger)().error("Error connecting to MongoDB:", error);
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
exports.connectDatabase = connectDatabase;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IAccessGroup, IUser, IZone } from "./IAdmin";
|
|
2
2
|
export declare class AdminService {
|
|
3
3
|
private readonly adminRepository;
|
|
4
|
+
private readonly redisUtils;
|
|
4
5
|
constructor();
|
|
5
6
|
getZonesByAccessGroupIds(accessGroupIds: string[]): Promise<any[] | undefined>;
|
|
6
7
|
getZonesByAccessGroups(accessGroupIds: string[]): Promise<any[] | undefined>;
|
|
@@ -74,6 +74,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
74
74
|
exports.AdminService = void 0;
|
|
75
75
|
const typedi_1 = __importStar(require("typedi"));
|
|
76
76
|
const Admin_repository_1 = require("./Admin.repository");
|
|
77
|
+
const redis_utils_1 = require("../../utils/redis.utils");
|
|
77
78
|
let AdminService = (() => {
|
|
78
79
|
let _classDecorators = [(0, typedi_1.Service)()];
|
|
79
80
|
let _classDescriptor;
|
|
@@ -82,6 +83,7 @@ let AdminService = (() => {
|
|
|
82
83
|
var AdminService = _classThis = class {
|
|
83
84
|
constructor() {
|
|
84
85
|
this.adminRepository = typedi_1.default.get(Admin_repository_1.AdminRepository);
|
|
86
|
+
this.redisUtils = typedi_1.default.get(redis_utils_1.RedisUtils);
|
|
85
87
|
}
|
|
86
88
|
async getZonesByAccessGroupIds(accessGroupIds) {
|
|
87
89
|
try {
|
|
@@ -124,6 +126,10 @@ let AdminService = (() => {
|
|
|
124
126
|
if (!zoneId) {
|
|
125
127
|
throw new Error("Zone ID is required");
|
|
126
128
|
}
|
|
129
|
+
const cachedZone = await this.redisUtils.get(`zone:${zoneId}`);
|
|
130
|
+
if (cachedZone)
|
|
131
|
+
return JSON.parse(cachedZone);
|
|
132
|
+
// Do Not Cache Zone if not found in Redis Here! Its being cached in Smart-Cloud
|
|
127
133
|
const zone = await this.adminRepository.getZone(zoneId);
|
|
128
134
|
if (!zone)
|
|
129
135
|
return null;
|
|
@@ -26,6 +26,7 @@ export declare class LocalDeviceService {
|
|
|
26
26
|
getBatteryLevel(deviceId: string): Promise<Record<string, any>>;
|
|
27
27
|
setBatteryLevel(deviceId: string, batteryLevel: number, source: Source, auditBody: IAuditProperties): Promise<void>;
|
|
28
28
|
private shouldUpdateBatteryLevel;
|
|
29
|
+
updateBatteryState(deviceId: string, batteryState: any): Promise<void>;
|
|
29
30
|
private getPropertyBatteryThreshold;
|
|
30
31
|
private checkForDeviceMalfunctions;
|
|
31
32
|
getMetaData(deviceId: string): Promise<any>;
|
|
@@ -355,6 +355,17 @@ let LocalDeviceService = (() => {
|
|
|
355
355
|
// Save the battery level in the device
|
|
356
356
|
await this.deviceRepository.setBatteryLevel(deviceId, batteryLevel);
|
|
357
357
|
await this.eventHandler.onBatteryLevelChange(deviceId, batteryLevel, auditBody);
|
|
358
|
+
// Additional condition: if battery level is less than 50, update battery state to "replace"
|
|
359
|
+
if (batteryLevel < 50) {
|
|
360
|
+
const batteryState = {
|
|
361
|
+
batteryState: {
|
|
362
|
+
value: "replace",
|
|
363
|
+
lastUpdated: new Date().toISOString(),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
await this.updateBatteryState(deviceId, batteryState);
|
|
367
|
+
await this.eventHandler.onBatteryStateChange(deviceId, batteryState, auditBody);
|
|
368
|
+
}
|
|
358
369
|
// Get property threshold
|
|
359
370
|
const propertyThreshold = await this.getPropertyBatteryThreshold(device.propertyId);
|
|
360
371
|
// Check if battery level is below threshold
|
|
@@ -377,6 +388,14 @@ let LocalDeviceService = (() => {
|
|
|
377
388
|
// Return true if current time is greater than or equal to last updated time + 8 hours
|
|
378
389
|
return currentTime >= lastUpdateTime + eightHoursInMs;
|
|
379
390
|
}
|
|
391
|
+
async updateBatteryState(deviceId, batteryState) {
|
|
392
|
+
try {
|
|
393
|
+
await this.deviceRepository.updateDevice(deviceId, batteryState);
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
console.error("Error updating battery state:", error);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
380
399
|
async getPropertyBatteryThreshold(propertyId) {
|
|
381
400
|
// TODO: Implement property battery threshold retrieval
|
|
382
401
|
// This should return the battery threshold for the specific property
|
|
@@ -90,7 +90,7 @@ let DeviceEventHandler = (() => {
|
|
|
90
90
|
Event_1.DT_EVENT_TYPES.DEVICE.STATUS.ONLINE,
|
|
91
91
|
Event_1.DT_EVENT_TYPES.DEVICE.STATUS.OFFLINE,
|
|
92
92
|
Event_1.DT_EVENT_TYPES.DEVICE.STATUS.UNKNOWN,
|
|
93
|
-
Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.
|
|
93
|
+
Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.STATE.REPLACE,
|
|
94
94
|
Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.LEVEL.UNKNOWN,
|
|
95
95
|
Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.LEVEL.LOW,
|
|
96
96
|
Event_1.DT_EVENT_TYPES.DEVICE.BATTERY.LEVEL.CRITICAL,
|
|
@@ -9,5 +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>;
|
|
12
13
|
onDeviceMetaChange(deviceId: string, metaData: Record<string, any>, auditProperties: IAuditProperties): Promise<void>;
|
|
13
14
|
}
|
|
@@ -125,6 +125,16 @@ 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
|
+
}
|
|
128
138
|
async onDeviceMetaChange(deviceId, metaData, auditProperties) {
|
|
129
139
|
await (0, audit_1.pushAudit)({
|
|
130
140
|
auditType: Event_1.DT_EVENT_TYPES.DEVICE.METADATA.UPDATED,
|
|
@@ -64,10 +64,22 @@ let IssueRepository = (() => {
|
|
|
64
64
|
query.category = filters.category;
|
|
65
65
|
if (filters.entityType)
|
|
66
66
|
query.entityType = filters.entityType;
|
|
67
|
+
if (filters.entitySubType)
|
|
68
|
+
query.entitySubType = filters.entitySubType;
|
|
67
69
|
if (filters.entityId)
|
|
68
70
|
query.entityId = filters.entityId;
|
|
71
|
+
if (filters.type)
|
|
72
|
+
query.type = filters.type;
|
|
69
73
|
if (!filters.includeDeleted)
|
|
70
74
|
query.isDeleted = false;
|
|
75
|
+
// Date filtering
|
|
76
|
+
if (filters.startDate || filters.endDate) {
|
|
77
|
+
query.createdAt = {};
|
|
78
|
+
if (filters.startDate)
|
|
79
|
+
query.createdAt.$gte = filters.startDate;
|
|
80
|
+
if (filters.endDate)
|
|
81
|
+
query.createdAt.$lte = filters.endDate;
|
|
82
|
+
}
|
|
71
83
|
if (filters.sort)
|
|
72
84
|
query.sort = filters.sort;
|
|
73
85
|
if (filters.limit)
|
|
@@ -6,10 +6,6 @@ import { IDevice } from "../entities/device/local/interfaces";
|
|
|
6
6
|
export declare class IssueService {
|
|
7
7
|
private readonly issueRepository;
|
|
8
8
|
constructor();
|
|
9
|
-
/**
|
|
10
|
-
* Create a readiness issue using IssueBuilder
|
|
11
|
-
*/
|
|
12
|
-
createReadinessIssue(data: CreateIssueData): Promise<IIssueDocument | null>;
|
|
13
9
|
/**
|
|
14
10
|
* Create an operations issue using IssueBuilder
|
|
15
11
|
*/
|
|
@@ -47,7 +43,6 @@ export declare class IssueService {
|
|
|
47
43
|
* Create issue for device going offline longer than baseline
|
|
48
44
|
*/
|
|
49
45
|
createDeviceOfflineIssue(device: IDevice, source: Source, reason?: string): Promise<IIssueDocument | null>;
|
|
50
|
-
createDoorLeftOpenIssue(device: IDevice, source: Source, reason?: string): Promise<IIssueDocument | null>;
|
|
51
46
|
/**
|
|
52
47
|
* Create issue for device battery level below threshold (READINESS + OPERATIONAL + ENERGY)
|
|
53
48
|
*/
|
|
@@ -117,6 +112,11 @@ export declare class IssueService {
|
|
|
117
112
|
* Unassign an issue
|
|
118
113
|
*/
|
|
119
114
|
unassignIssue(id: string, unassignedBy: string): Promise<IIssueDocument | null>;
|
|
115
|
+
/**
|
|
116
|
+
* Cancel/Close/Resolve an issue based on query
|
|
117
|
+
* This method will find an issue matching the query and update its status
|
|
118
|
+
*/
|
|
119
|
+
performIssueAction(query: IIssueQuery, action: "cancel" | "close" | "resolve" | "ignore" | "in_progress" | "on_hold", updatedBy: string): Promise<IIssueDocument | null>;
|
|
120
120
|
/**
|
|
121
121
|
* Get issues assigned to a user with business logic
|
|
122
122
|
*/
|
|
@@ -77,6 +77,7 @@ const Issue_repository_1 = require("./Issue.repository");
|
|
|
77
77
|
const Issue_model_1 = require("./Issue.model");
|
|
78
78
|
const issue_types_1 = require("./issue.types");
|
|
79
79
|
const IssueBuilder_1 = require("./IssueBuilder");
|
|
80
|
+
const Service_1 = require("../constants/Service");
|
|
80
81
|
const Admin_service_1 = require("../entities/admin/Admin.service");
|
|
81
82
|
const audit_1 = require("../audit");
|
|
82
83
|
const constants_1 = require("../constants");
|
|
@@ -90,29 +91,6 @@ let IssueService = (() => {
|
|
|
90
91
|
constructor() {
|
|
91
92
|
this.issueRepository = typedi_1.default.get(Issue_repository_1.IssueRepository);
|
|
92
93
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Create a readiness issue using IssueBuilder
|
|
95
|
-
*/
|
|
96
|
-
async createReadinessIssue(data) {
|
|
97
|
-
const issueBuilder = IssueBuilder_1.IssueBuilder.createReadinessIssue()
|
|
98
|
-
.setPropertyId(data.propertyId)
|
|
99
|
-
.setTitle(data.title)
|
|
100
|
-
.setDescription(data.description)
|
|
101
|
-
.setCreatedBy(data.createdBy);
|
|
102
|
-
if (data.entityId)
|
|
103
|
-
issueBuilder.setEntityId(data.entityId);
|
|
104
|
-
if (data.entityType)
|
|
105
|
-
issueBuilder.setEntityType(data.entityType);
|
|
106
|
-
if (data.assignedTo)
|
|
107
|
-
issueBuilder.setAssignedTo(data.assignedTo);
|
|
108
|
-
if (data.dueDate)
|
|
109
|
-
issueBuilder.setDueDate(data.dueDate);
|
|
110
|
-
if (data.zoneId)
|
|
111
|
-
issueBuilder.setZoneId(data.zoneId);
|
|
112
|
-
if (data.type)
|
|
113
|
-
issueBuilder.setType(data.type);
|
|
114
|
-
return await this.createIssue(issueBuilder);
|
|
115
|
-
}
|
|
116
94
|
/**
|
|
117
95
|
* Create an operations issue using IssueBuilder
|
|
118
96
|
*/
|
|
@@ -267,22 +245,30 @@ let IssueService = (() => {
|
|
|
267
245
|
type: issue_types_1.IssueType.DEVICE_OFFLINE,
|
|
268
246
|
});
|
|
269
247
|
}
|
|
270
|
-
async createDoorLeftOpenIssue(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
248
|
+
// async createDoorLeftOpenIssue(
|
|
249
|
+
// device: IDevice,
|
|
250
|
+
// source: Source,
|
|
251
|
+
// reason?: string
|
|
252
|
+
// ): Promise<IIssueDocument | null> {
|
|
253
|
+
// const zone = await Container.get(AdminService).getZone(device.zoneId);
|
|
254
|
+
// return await this.createDeviceIssue({
|
|
255
|
+
// entityId: device.deviceId,
|
|
256
|
+
// entityType: EntityType.DEVICE,
|
|
257
|
+
// entitySubType: device.deviceType.type as EntitySubType,
|
|
258
|
+
// propertyId: device.propertyId,
|
|
259
|
+
// zoneId: device.zoneId,
|
|
260
|
+
// title: "Door Left Open - Requires Attention",
|
|
261
|
+
// description: `${
|
|
262
|
+
// zone?.name
|
|
263
|
+
// } has a door left open, for more than 10 minutes. ${
|
|
264
|
+
// reason ? `Reason: ${reason}.` : ""
|
|
265
|
+
// }`,
|
|
266
|
+
// createdBy: source,
|
|
267
|
+
// category: IssuesCategory.SECURITY,
|
|
268
|
+
// priority: IssuePriority.HIGH,
|
|
269
|
+
// type: IssueType.DOOR_LEFT_OPEN,
|
|
270
|
+
// });
|
|
271
|
+
// }
|
|
286
272
|
/**
|
|
287
273
|
* Create issue for device battery level below threshold (READINESS + OPERATIONAL + ENERGY)
|
|
288
274
|
*/
|
|
@@ -356,51 +342,88 @@ let IssueService = (() => {
|
|
|
356
342
|
* Accepts either a CreateIssueData object or an IssueBuilder instance
|
|
357
343
|
*/
|
|
358
344
|
async createIssue(issueData) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
processedIssueData.dueDate
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
345
|
+
try {
|
|
346
|
+
let processedIssueData;
|
|
347
|
+
// Handle IssueBuilder instance
|
|
348
|
+
if (issueData instanceof IssueBuilder_1.IssueBuilder) {
|
|
349
|
+
processedIssueData = issueData.build();
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
processedIssueData = issueData;
|
|
353
|
+
}
|
|
354
|
+
// Business logic: Validate issue data
|
|
355
|
+
this.validateIssueData(processedIssueData);
|
|
356
|
+
// Business logic: Set default priority if not provided
|
|
357
|
+
if (!processedIssueData.priority) {
|
|
358
|
+
processedIssueData.priority = this.determineDefaultPriority(processedIssueData.category);
|
|
359
|
+
}
|
|
360
|
+
// Business logic: Validate due date is in the future
|
|
361
|
+
if (processedIssueData.dueDate &&
|
|
362
|
+
processedIssueData.dueDate <= new Date()) {
|
|
363
|
+
throw new Error("Due date must be in the future");
|
|
364
|
+
}
|
|
365
|
+
if (processedIssueData?.zoneId && !processedIssueData?.zoneName) {
|
|
366
|
+
const zone = await typedi_1.default.get(Admin_service_1.AdminService).getZone(processedIssueData.zoneId);
|
|
367
|
+
processedIssueData.zoneName = zone?.name || "";
|
|
368
|
+
}
|
|
369
|
+
const existingIssue = await this.query({
|
|
370
|
+
propertyId: processedIssueData.propertyId,
|
|
371
|
+
zoneId: processedIssueData.zoneId,
|
|
372
|
+
entityId: processedIssueData.entityId,
|
|
373
|
+
entityType: processedIssueData.entityType,
|
|
374
|
+
entitySubType: processedIssueData.entitySubType,
|
|
375
|
+
type: processedIssueData.type,
|
|
376
|
+
});
|
|
377
|
+
if (existingIssue.length > 0) {
|
|
378
|
+
await (0, audit_1.pushAudit)({
|
|
379
|
+
auditType: constants_1.DT_EVENT_TYPES.ISSUE.CREATE.SKIPPED,
|
|
380
|
+
auditData: {
|
|
381
|
+
reason: "Issue already exists",
|
|
382
|
+
propertyId: processedIssueData.propertyId,
|
|
383
|
+
entityId: processedIssueData.entityId,
|
|
384
|
+
entityType: processedIssueData.entityType,
|
|
385
|
+
entitySubType: processedIssueData.entitySubType,
|
|
386
|
+
type: processedIssueData.type,
|
|
387
|
+
resource: audit_1.Resource.ISSUE,
|
|
388
|
+
source: processedIssueData.createdBy,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
const issue = await this.issueRepository.create(processedIssueData);
|
|
386
394
|
await (0, audit_1.pushAudit)({
|
|
387
|
-
auditType: constants_1.DT_EVENT_TYPES.ISSUE.CREATE.
|
|
395
|
+
auditType: constants_1.DT_EVENT_TYPES.ISSUE.CREATE.SUCCESS,
|
|
388
396
|
auditData: {
|
|
389
|
-
|
|
397
|
+
resource: audit_1.Resource.ISSUE,
|
|
398
|
+
source: Service_1.Source.USER,
|
|
390
399
|
propertyId: processedIssueData.propertyId,
|
|
400
|
+
zoneId: processedIssueData.zoneId,
|
|
391
401
|
entityId: processedIssueData.entityId,
|
|
392
402
|
entityType: processedIssueData.entityType,
|
|
393
403
|
entitySubType: processedIssueData.entitySubType,
|
|
394
404
|
type: processedIssueData.type,
|
|
405
|
+
createdBy: processedIssueData.createdBy,
|
|
406
|
+
createdAt: new Date(),
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
await dt_pub_sub_1.eventDispatcher.publishEvent(constants_1.DT_EVENT_TYPES.ISSUE.CREATE.SUCCESS, issue, "dt-common-device");
|
|
410
|
+
return issue;
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
await (0, audit_1.pushAudit)({
|
|
414
|
+
auditType: constants_1.DT_EVENT_TYPES.ISSUE.CREATE.FAILED,
|
|
415
|
+
auditData: {
|
|
395
416
|
resource: audit_1.Resource.ISSUE,
|
|
396
|
-
source:
|
|
417
|
+
source: Service_1.Source.USER,
|
|
418
|
+
propertyId: issueData?.propertyId || "",
|
|
419
|
+
zoneId: issueData?.zoneId || "",
|
|
420
|
+
errorMessage: error.message,
|
|
421
|
+
error: error,
|
|
422
|
+
createdBy: issueData?.createdBy || "",
|
|
397
423
|
},
|
|
398
424
|
});
|
|
399
|
-
|
|
425
|
+
throw error;
|
|
400
426
|
}
|
|
401
|
-
const issue = await this.issueRepository.create(processedIssueData);
|
|
402
|
-
await dt_pub_sub_1.eventDispatcher.publishEvent(constants_1.DT_EVENT_TYPES.ISSUE.CREATE.SUCCESS, issue, "dt-common-device");
|
|
403
|
-
return issue;
|
|
404
427
|
}
|
|
405
428
|
/**
|
|
406
429
|
* Get issue by ID with business logic
|
|
@@ -613,6 +636,106 @@ let IssueService = (() => {
|
|
|
613
636
|
issueModel.unassign(unassignedBy);
|
|
614
637
|
return await issueModel.save();
|
|
615
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Cancel/Close/Resolve an issue based on query
|
|
641
|
+
* This method will find an issue matching the query and update its status
|
|
642
|
+
*/
|
|
643
|
+
async performIssueAction(query, action, updatedBy) {
|
|
644
|
+
try {
|
|
645
|
+
if (!updatedBy) {
|
|
646
|
+
throw new Error("Updated by user is required");
|
|
647
|
+
}
|
|
648
|
+
// Find the issue based on the query
|
|
649
|
+
const issues = await this.query(query);
|
|
650
|
+
if (issues.length === 0) {
|
|
651
|
+
return null; // No issue found matching the query
|
|
652
|
+
}
|
|
653
|
+
if (issues.length > 1) {
|
|
654
|
+
throw new Error(`Multiple issues found matching the query. Please provide more specific criteria.`);
|
|
655
|
+
}
|
|
656
|
+
const issue = issues[0];
|
|
657
|
+
// Check if issue can be cancelled/closed/resolved
|
|
658
|
+
if (issue.status === issue_types_1.IssueStatus.CANCELLED) {
|
|
659
|
+
throw new Error("Issue is already cancelled");
|
|
660
|
+
}
|
|
661
|
+
if (issue.status === issue_types_1.IssueStatus.CLOSED) {
|
|
662
|
+
throw new Error("Issue is already closed");
|
|
663
|
+
}
|
|
664
|
+
if (issue.status === issue_types_1.IssueStatus.RESOLVED) {
|
|
665
|
+
throw new Error("Issue is already resolved");
|
|
666
|
+
}
|
|
667
|
+
if (issue.status === issue_types_1.IssueStatus.IGNORED) {
|
|
668
|
+
throw new Error("Issue is already ignored");
|
|
669
|
+
}
|
|
670
|
+
// Determine the new status based on the action
|
|
671
|
+
let newStatus;
|
|
672
|
+
let updateData = {
|
|
673
|
+
updatedBy,
|
|
674
|
+
updatedAt: new Date(),
|
|
675
|
+
};
|
|
676
|
+
switch (action) {
|
|
677
|
+
case "in_progress":
|
|
678
|
+
newStatus = issue_types_1.IssueStatus.IN_PROGRESS;
|
|
679
|
+
break;
|
|
680
|
+
case "resolve":
|
|
681
|
+
newStatus = issue_types_1.IssueStatus.RESOLVED;
|
|
682
|
+
updateData.resolvedAt = new Date();
|
|
683
|
+
break;
|
|
684
|
+
case "close":
|
|
685
|
+
newStatus = issue_types_1.IssueStatus.CLOSED;
|
|
686
|
+
break;
|
|
687
|
+
case "cancel":
|
|
688
|
+
newStatus = issue_types_1.IssueStatus.CANCELLED;
|
|
689
|
+
break;
|
|
690
|
+
case "on_hold":
|
|
691
|
+
newStatus = issue_types_1.IssueStatus.ON_HOLD;
|
|
692
|
+
break;
|
|
693
|
+
case "ignore":
|
|
694
|
+
newStatus = issue_types_1.IssueStatus.IGNORED;
|
|
695
|
+
break;
|
|
696
|
+
default:
|
|
697
|
+
throw new Error("Invalid action. Must be 'cancel', 'close', or 'resolve'");
|
|
698
|
+
}
|
|
699
|
+
updateData.status = newStatus;
|
|
700
|
+
// Update the issue
|
|
701
|
+
const updatedIssue = await this.updateIssue(issue._id, updateData);
|
|
702
|
+
if (updatedIssue) {
|
|
703
|
+
// Log the action for audit purposes
|
|
704
|
+
console.log(`Issue ${issue._id} has been ${action}ed by ${updatedBy}`);
|
|
705
|
+
await (0, audit_1.pushAudit)({
|
|
706
|
+
auditType: constants_1.DT_EVENT_TYPES.ISSUE.UPDATE.SUCCESS,
|
|
707
|
+
auditData: {
|
|
708
|
+
resource: audit_1.Resource.ISSUE,
|
|
709
|
+
source: Service_1.Source.USER,
|
|
710
|
+
propertyId: issue.propertyId,
|
|
711
|
+
zoneId: issue.zoneId,
|
|
712
|
+
oldStatus: issue.status,
|
|
713
|
+
newStatus,
|
|
714
|
+
action,
|
|
715
|
+
updatedBy,
|
|
716
|
+
updatedAt: new Date(),
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return updatedIssue;
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
await (0, audit_1.pushAudit)({
|
|
724
|
+
auditType: constants_1.DT_EVENT_TYPES.ISSUE.UPDATE.FAILED,
|
|
725
|
+
auditData: {
|
|
726
|
+
resource: audit_1.Resource.ISSUE,
|
|
727
|
+
source: Service_1.Source.USER,
|
|
728
|
+
propertyId: query.propertyId || "",
|
|
729
|
+
zoneId: query.zoneId || "",
|
|
730
|
+
errorMessage: error.message,
|
|
731
|
+
error: error,
|
|
732
|
+
updatedBy,
|
|
733
|
+
updatedAt: new Date(),
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
616
739
|
/**
|
|
617
740
|
* Get issues assigned to a user with business logic
|
|
618
741
|
*/
|
|
@@ -721,6 +844,12 @@ let IssueService = (() => {
|
|
|
721
844
|
if (filters.skip && filters.skip < 0) {
|
|
722
845
|
throw new Error("Skip must be non-negative");
|
|
723
846
|
}
|
|
847
|
+
// Validate date filters
|
|
848
|
+
if (filters.startDate &&
|
|
849
|
+
filters.endDate &&
|
|
850
|
+
filters.startDate > filters.endDate) {
|
|
851
|
+
throw new Error("Start date must be before or equal to end date");
|
|
852
|
+
}
|
|
724
853
|
}
|
|
725
854
|
validateUpdateData(data) {
|
|
726
855
|
if (data.title && data.title.trim().length < 5) {
|
|
@@ -764,7 +893,6 @@ let IssueService = (() => {
|
|
|
764
893
|
determineDefaultPriority(category) {
|
|
765
894
|
// Business logic: Determine default priority based on category
|
|
766
895
|
const categoryPriorities = {
|
|
767
|
-
[issue_types_1.IssuesCategory.READINESS]: issue_types_1.IssuePriority.LOW,
|
|
768
896
|
[issue_types_1.IssuesCategory.OPERATIONS]: issue_types_1.IssuePriority.HIGH,
|
|
769
897
|
[issue_types_1.IssuesCategory.SECURITY]: issue_types_1.IssuePriority.CRITICAL,
|
|
770
898
|
[issue_types_1.IssuesCategory.ENERGY]: issue_types_1.IssuePriority.HIGH,
|
|
@@ -87,10 +87,6 @@ export declare class IssueBuilder {
|
|
|
87
87
|
* Resets the builder to its initial state
|
|
88
88
|
*/
|
|
89
89
|
reset(): this;
|
|
90
|
-
/**
|
|
91
|
-
* Creates a new builder instance with predefined values for common issue types
|
|
92
|
-
*/
|
|
93
|
-
static createReadinessIssue(): IssueBuilder;
|
|
94
90
|
static createOperationsIssue(): IssueBuilder;
|
|
95
91
|
static createSecurityIssue(): IssueBuilder;
|
|
96
92
|
static createEnergyIssue(): IssueBuilder;
|
|
@@ -182,14 +182,6 @@ class IssueBuilder {
|
|
|
182
182
|
this.data = {};
|
|
183
183
|
return this;
|
|
184
184
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Creates a new builder instance with predefined values for common issue types
|
|
187
|
-
*/
|
|
188
|
-
static createReadinessIssue() {
|
|
189
|
-
return new IssueBuilder()
|
|
190
|
-
.setCategory(issue_types_1.IssuesCategory.READINESS)
|
|
191
|
-
.setPriority(issue_types_1.IssuePriority.LOW);
|
|
192
|
-
}
|
|
193
185
|
static createOperationsIssue() {
|
|
194
186
|
return new IssueBuilder()
|
|
195
187
|
.setCategory(issue_types_1.IssuesCategory.OPERATIONS)
|
|
@@ -255,7 +247,7 @@ class IssueBuilder {
|
|
|
255
247
|
*/
|
|
256
248
|
static createMaintenanceIssue(propertyId, type, entityId, entityType) {
|
|
257
249
|
const builder = new IssueBuilder()
|
|
258
|
-
.setCategory(issue_types_1.IssuesCategory.
|
|
250
|
+
.setCategory(issue_types_1.IssuesCategory.OPERATIONS)
|
|
259
251
|
.setPropertyId(propertyId)
|
|
260
252
|
.setType(type)
|
|
261
253
|
.setPriority(issue_types_1.IssuePriority.LOW);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export declare enum IssuesCategory {
|
|
2
|
-
READINESS = "READINESS",
|
|
3
2
|
OPERATIONS = "OPERATIONS",
|
|
4
3
|
SECURITY = "SECURITY",
|
|
5
4
|
ENERGY = "ENERGY",
|
|
@@ -53,24 +52,16 @@ export declare enum IssueType {
|
|
|
53
52
|
DEVICE_OFFLINE = "DEVICE_OFFLINE",
|
|
54
53
|
HUB_OFFLINE = "HUB_OFFLINE",
|
|
55
54
|
ACCOUNT_UNAUTHORIZED = "ACCOUNT_UNAUTHORIZED",
|
|
56
|
-
ACCOUNT_NEW_DEVICE = "ACCOUNT_NEW_DEVICE",
|
|
57
55
|
ACCOUNT_MISSING_DEVICE = "ACCOUNT_MISSING_DEVICE",
|
|
58
|
-
DEVICE_TAMPER_ATTEMPT = "DEVICE_TAMPER_ATTEMPT",
|
|
59
56
|
LOCK_JAMMED = "LOCK_JAMMED",
|
|
60
|
-
DEVICE_MALFUNCTION = "DEVICE_MALFUNCTION"
|
|
61
|
-
DOOR_LEFT_OPEN = "DOOR_LEFT_OPEN",
|
|
62
|
-
DOOR_OPEN_FREQUENT = "DOOR_OPEN_FREQUENT",
|
|
63
|
-
DOOR_OPEN_OUTSIDE_BIZ_HOURS = "DOOR_OPEN_OUTSIDE_BIZ_HOURS",
|
|
64
|
-
LOCK_ACCESS_EMERGENCY_CODE = "LOCK_ACCESS_EMERGENCY_CODE",
|
|
65
|
-
LOCK_ACCESS_MASTER_CODE = "LOCK_ACCESS_MASTER_CODE",
|
|
66
|
-
SPECIFIC_DOOR_ACCESS = "SPECIFIC_DOOR_ACCESS",
|
|
67
|
-
GUEST_LOCK_FIRST_ACCESS = "GUEST_LOCK_FIRST_ACCESS"
|
|
57
|
+
DEVICE_MALFUNCTION = "DEVICE_MALFUNCTION"
|
|
68
58
|
}
|
|
69
59
|
export interface IssueDocument {
|
|
70
60
|
_id: string;
|
|
71
61
|
category: IssuesCategory;
|
|
72
62
|
propertyId: string;
|
|
73
63
|
zoneId?: string;
|
|
64
|
+
zoneName?: string;
|
|
74
65
|
title: string;
|
|
75
66
|
description: string;
|
|
76
67
|
entityId?: string;
|
|
@@ -93,6 +84,7 @@ export interface CreateIssueData {
|
|
|
93
84
|
category: IssuesCategory;
|
|
94
85
|
propertyId: string;
|
|
95
86
|
zoneId?: string;
|
|
87
|
+
zoneName?: string;
|
|
96
88
|
title: string;
|
|
97
89
|
description: string;
|
|
98
90
|
entityId: string;
|
|
@@ -116,6 +108,7 @@ export interface UpdateIssueData {
|
|
|
116
108
|
priority?: IssuePriority;
|
|
117
109
|
assignedTo?: string;
|
|
118
110
|
updatedBy?: string;
|
|
111
|
+
updatedAt?: Date;
|
|
119
112
|
resolvedAt?: Date;
|
|
120
113
|
dueDate?: Date;
|
|
121
114
|
isDeleted?: boolean;
|
|
@@ -127,6 +120,7 @@ export interface AddCommentData {
|
|
|
127
120
|
export interface IIssueQuery {
|
|
128
121
|
propertyId?: string;
|
|
129
122
|
zoneId?: string;
|
|
123
|
+
zoneName?: string;
|
|
130
124
|
assignedTo?: string;
|
|
131
125
|
status?: IssueStatus;
|
|
132
126
|
priority?: IssuePriority;
|
|
@@ -136,6 +130,8 @@ export interface IIssueQuery {
|
|
|
136
130
|
entitySubType?: EntitySubType;
|
|
137
131
|
entityId?: string;
|
|
138
132
|
includeDeleted?: boolean;
|
|
133
|
+
startDate?: Date;
|
|
134
|
+
endDate?: Date;
|
|
139
135
|
limit?: number;
|
|
140
136
|
skip?: number;
|
|
141
137
|
sort?: {
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.IssueType = exports.IssuePriority = exports.IssueStatus = exports.EntitySubType = exports.EntityType = exports.IssuesCategory = void 0;
|
|
4
4
|
var IssuesCategory;
|
|
5
5
|
(function (IssuesCategory) {
|
|
6
|
-
IssuesCategory["READINESS"] = "READINESS";
|
|
7
6
|
IssuesCategory["OPERATIONS"] = "OPERATIONS";
|
|
8
7
|
IssuesCategory["SECURITY"] = "SECURITY";
|
|
9
8
|
IssuesCategory["ENERGY"] = "ENERGY";
|
|
@@ -55,16 +54,7 @@ var IssueType;
|
|
|
55
54
|
IssueType["DEVICE_OFFLINE"] = "DEVICE_OFFLINE";
|
|
56
55
|
IssueType["HUB_OFFLINE"] = "HUB_OFFLINE";
|
|
57
56
|
IssueType["ACCOUNT_UNAUTHORIZED"] = "ACCOUNT_UNAUTHORIZED";
|
|
58
|
-
IssueType["ACCOUNT_NEW_DEVICE"] = "ACCOUNT_NEW_DEVICE";
|
|
59
57
|
IssueType["ACCOUNT_MISSING_DEVICE"] = "ACCOUNT_MISSING_DEVICE";
|
|
60
|
-
IssueType["DEVICE_TAMPER_ATTEMPT"] = "DEVICE_TAMPER_ATTEMPT";
|
|
61
58
|
IssueType["LOCK_JAMMED"] = "LOCK_JAMMED";
|
|
62
59
|
IssueType["DEVICE_MALFUNCTION"] = "DEVICE_MALFUNCTION";
|
|
63
|
-
IssueType["DOOR_LEFT_OPEN"] = "DOOR_LEFT_OPEN";
|
|
64
|
-
IssueType["DOOR_OPEN_FREQUENT"] = "DOOR_OPEN_FREQUENT";
|
|
65
|
-
IssueType["DOOR_OPEN_OUTSIDE_BIZ_HOURS"] = "DOOR_OPEN_OUTSIDE_BIZ_HOURS";
|
|
66
|
-
IssueType["LOCK_ACCESS_EMERGENCY_CODE"] = "LOCK_ACCESS_EMERGENCY_CODE";
|
|
67
|
-
IssueType["LOCK_ACCESS_MASTER_CODE"] = "LOCK_ACCESS_MASTER_CODE";
|
|
68
|
-
IssueType["SPECIFIC_DOOR_ACCESS"] = "SPECIFIC_DOOR_ACCESS";
|
|
69
|
-
IssueType["GUEST_LOCK_FIRST_ACCESS"] = "GUEST_LOCK_FIRST_ACCESS";
|
|
70
60
|
})(IssueType || (exports.IssueType = IssueType = {}));
|