dt-common-device 3.0.9 → 3.0.11

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.
Files changed (144) hide show
  1. package/dist/config/config.d.ts +1 -2
  2. package/dist/config/config.js +4 -5
  3. package/dist/device/cloud/interface.d.ts +101 -0
  4. package/dist/device/cloud/interface.js +3 -0
  5. package/dist/device/cloud/interfaces/IDeviceConnectionService.d.ts +7 -0
  6. package/dist/device/cloud/interfaces/IDeviceConnectionService.js +3 -0
  7. package/dist/device/cloud/interfaces/IDevicesService.d.ts +9 -0
  8. package/dist/device/cloud/services/Device.service.d.ts +39 -0
  9. package/dist/device/cloud/services/Device.service.js +9 -0
  10. package/dist/device/cloud/services/DeviceCloudService.d.ts +42 -0
  11. package/dist/device/cloud/services/DeviceCloudService.js +59 -0
  12. package/dist/device/cloud/services/DeviceHub.service.d.ts +3 -0
  13. package/dist/device/cloud/services/DeviceHub.service.js +6 -0
  14. package/dist/device/cloud/services/Hub.service.d.ts +25 -0
  15. package/dist/device/cloud/services/Hub.service.js +9 -0
  16. package/dist/device/cloud/services/SmartThingsDeviceService.d.ts +38 -0
  17. package/dist/device/cloud/services/SmartThingsDeviceService.js +52 -0
  18. package/dist/device/index.d.ts +4 -0
  19. package/dist/device/index.js +20 -0
  20. package/dist/device/local/events/EventHandler.js +6 -6
  21. package/dist/device/local/events/Events.d.ts +12 -33
  22. package/dist/device/local/events/Events.js +12 -33
  23. package/dist/device/local/interface.d.ts +0 -0
  24. package/dist/device/local/interface.js +1 -0
  25. package/dist/device/local/interfaces/IDevice.d.ts +1 -0
  26. package/dist/device/local/repository/Schedule.repository.d.ts +0 -1
  27. package/dist/device/local/repository/Schedule.repository.js +6 -6
  28. package/dist/device/local/services/DeviceHub.service.d.ts +11 -0
  29. package/dist/device/local/services/DeviceHub.service.js +40 -0
  30. package/dist/queue/entities/HybridHttpQueue.d.ts +4 -14
  31. package/dist/queue/entities/HybridHttpQueue.js +31 -119
  32. package/dist/queue/interfaces/IHybridHttpQueue.d.ts +2 -12
  33. package/dist/queue/interfaces/IJobResult.d.ts +1 -8
  34. package/dist/queue/interfaces/index.d.ts +0 -1
  35. package/dist/queue/interfaces/index.js +0 -1
  36. package/dist/queue/services/QueueService.d.ts +2 -12
  37. package/dist/queue/types/queue.types.d.ts +10 -29
  38. package/dist/queue/utils/jobUtils.d.ts +0 -3
  39. package/dist/queue/utils/jobUtils.js +0 -48
  40. package/dist/queue/utils/queueUtils.d.ts +7 -0
  41. package/dist/queue/utils/queueUtils.js +113 -4
  42. package/package.json +6 -1
  43. package/.eslintrc.js +0 -44
  44. package/dist/audit/AuditProperties.d.ts +0 -16
  45. package/dist/audit/AuditUtils.d.ts +0 -2
  46. package/dist/audit/AuditUtils.js +0 -36
  47. package/src/alerts/Alert.model.ts +0 -289
  48. package/src/alerts/Alert.repository.ts +0 -487
  49. package/src/alerts/Alert.service.ts +0 -711
  50. package/src/alerts/AlertBuilder.example.ts +0 -126
  51. package/src/alerts/AlertBuilder.ts +0 -208
  52. package/src/alerts/AlertService.example.ts +0 -232
  53. package/src/alerts/alert.types.ts +0 -64
  54. package/src/alerts/index.ts +0 -3
  55. package/src/audit/AuditProperties.ts +0 -16
  56. package/src/audit/AuditUtils.ts +0 -38
  57. package/src/config/config.ts +0 -202
  58. package/src/config/config.types.ts +0 -21
  59. package/src/connection/Connection.repository.ts +0 -52
  60. package/src/connection/Connection.service.ts +0 -39
  61. package/src/connection/IConnection.ts +0 -27
  62. package/src/connection/index.ts +0 -3
  63. package/src/constants/ConnectionProviders.ts +0 -11
  64. package/src/constants/Event.ts +0 -89
  65. package/src/constants/Service.ts +0 -17
  66. package/src/constants/index.ts +0 -3
  67. package/src/db/db.ts +0 -24
  68. package/src/db/index.ts +0 -2
  69. package/src/db/redis.ts +0 -20
  70. package/src/device/cloud/entities/CloudDevice.ts +0 -40
  71. package/src/device/cloud/entities/CloudDeviceService.ts +0 -8
  72. package/src/device/cloud/entities/DeviceFactory.ts +0 -27
  73. package/src/device/cloud/entities/index.ts +0 -3
  74. package/src/device/cloud/interfaces/ICloudDevice.ts +0 -14
  75. package/src/device/cloud/interfaces/ICloudDeviceService.ts +0 -6
  76. package/src/device/cloud/interfaces/IDeviceFactory.ts +0 -5
  77. package/src/device/cloud/interfaces/IRawDataTransformer.ts +0 -5
  78. package/src/device/cloud/interfaces/IRawDevice.ts +0 -19
  79. package/src/device/cloud/interfaces/index.ts +0 -5
  80. package/src/device/local/interfaces/IDevice.ts +0 -61
  81. package/src/device/local/interfaces/IDtDevice.ts +0 -16
  82. package/src/device/local/interfaces/ISchedule.ts +0 -40
  83. package/src/device/local/interfaces/index.ts +0 -3
  84. package/src/device/local/repository/Device.repository.ts +0 -368
  85. package/src/device/local/repository/Hub.repository.ts +0 -107
  86. package/src/device/local/repository/Schedule.repository.ts +0 -72
  87. package/src/device/local/services/Device.service.ts +0 -436
  88. package/src/device/local/services/Hub.service.ts +0 -57
  89. package/src/device/local/services/Schedule.service.ts +0 -26
  90. package/src/device/local/services/index.ts +0 -3
  91. package/src/docs/Alert.model.md +0 -319
  92. package/src/docs/Alerts&IssuesModel.md +0 -312
  93. package/src/docs/Issue.model.md +0 -386
  94. package/src/docs/SECURITY.md +0 -67
  95. package/src/docs/TROUBLESHOOTING.md +0 -184
  96. package/src/events/BaseEventHandler.ts +0 -145
  97. package/src/events/BaseEventTransformer.ts +0 -97
  98. package/src/events/DeviceEventHandler.ts +0 -213
  99. package/src/events/DeviceEventTransformerFactory.ts +0 -77
  100. package/src/events/EventHandler.ts +0 -124
  101. package/src/events/EventHandlerOrchestrator.ts +0 -119
  102. package/src/events/EventProcessingService.ts +0 -248
  103. package/src/events/InternalEventSubscription.ts +0 -194
  104. package/src/events/index.ts +0 -9
  105. package/src/events/interfaces/DeviceEvent.ts +0 -56
  106. package/src/events/interfaces/IEventHandler.ts +0 -28
  107. package/src/events/interfaces/IEventTransformer.ts +0 -8
  108. package/src/events/interfaces/IInternalEvent.ts +0 -33
  109. package/src/events/interfaces/index.ts +0 -4
  110. package/src/index.ts +0 -43
  111. package/src/issues/Issue.model.ts +0 -350
  112. package/src/issues/Issue.repository.ts +0 -517
  113. package/src/issues/Issue.service.ts +0 -932
  114. package/src/issues/IssueBuilder.example.ts +0 -210
  115. package/src/issues/IssueBuilder.ts +0 -263
  116. package/src/issues/IssueService.example.ts +0 -310
  117. package/src/issues/index.ts +0 -2
  118. package/src/issues/issue.types.ts +0 -98
  119. package/src/property/IProperty.ts +0 -30
  120. package/src/property/Property.repository.ts +0 -53
  121. package/src/property/Property.service.ts +0 -38
  122. package/src/property/index.ts +0 -2
  123. package/src/queue/entities/HybridHttpQueue.ts +0 -274
  124. package/src/queue/entities/index.ts +0 -1
  125. package/src/queue/index.ts +0 -6
  126. package/src/queue/interfaces/IHttpRequestJob.ts +0 -10
  127. package/src/queue/interfaces/IHybridHttpQueue.ts +0 -25
  128. package/src/queue/interfaces/IJobResult.ts +0 -15
  129. package/src/queue/interfaces/IRateLimitConfig.ts +0 -5
  130. package/src/queue/interfaces/index.ts +0 -4
  131. package/src/queue/services/QueueService.ts +0 -40
  132. package/src/queue/services/index.ts +0 -1
  133. package/src/queue/types/http.types.ts +0 -23
  134. package/src/queue/types/index.ts +0 -2
  135. package/src/queue/types/queue.types.ts +0 -21
  136. package/src/queue/utils/index.ts +0 -3
  137. package/src/queue/utils/jobUtils.ts +0 -79
  138. package/src/queue/utils/queueUtils.ts +0 -84
  139. package/src/queue/utils/rateLimit.utils.ts +0 -131
  140. package/src/utils/http.utils.ts +0 -143
  141. package/src/utils/index.ts +0 -2
  142. package/src/utils/redis.utils.ts +0 -74
  143. package/tsconfig.json +0 -20
  144. /package/dist/{audit/AuditProperties.js → device/cloud/interfaces/IDevicesService.js} +0 -0
@@ -1,932 +0,0 @@
1
- import { Service } from "typedi";
2
- import { IssueRepository } from "./Issue.repository";
3
- import { IssueModel, IIssueDocument } from "./Issue.model";
4
- import {
5
- CreateIssueData,
6
- UpdateIssueData,
7
- AddCommentData,
8
- IssueStatus,
9
- IssuePriority,
10
- IssuesCategory,
11
- EntityType,
12
- } from "./issue.types";
13
- import { IssueBuilder } from "./IssueBuilder";
14
- import { Source } from "../constants/Service";
15
- import { IDevice } from "../device/local/interfaces";
16
-
17
- @Service()
18
- export class IssueService {
19
- constructor(private readonly issueRepository: IssueRepository) {}
20
-
21
- /**
22
- * Create a readiness issue using IssueBuilder
23
- */
24
- async createReadinessIssue(
25
- propertyId: string,
26
- title: string,
27
- description: string,
28
- createdBy: string,
29
- entityId?: string,
30
- entityType?: EntityType,
31
- assignedTo?: string,
32
- dueDate?: Date
33
- ): Promise<IIssueDocument> {
34
- const issueBuilder = IssueBuilder.createReadinessIssue()
35
- .setPropertyId(propertyId)
36
- .setTitle(title)
37
- .setDescription(description)
38
- .setCreatedBy(createdBy);
39
-
40
- if (entityId) issueBuilder.setEntityId(entityId);
41
- if (entityType) issueBuilder.setEntityType(entityType);
42
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
43
- if (dueDate) issueBuilder.setDueDate(dueDate);
44
-
45
- return await this.createIssue(issueBuilder);
46
- }
47
-
48
- /**
49
- * Create an operations issue using IssueBuilder
50
- */
51
- async createOperationsIssue(
52
- propertyId: string,
53
- title: string,
54
- description: string,
55
- createdBy: string,
56
- entityId?: string,
57
- entityType?: EntityType,
58
- assignedTo?: string,
59
- dueDate?: Date
60
- ): Promise<IIssueDocument> {
61
- const issueBuilder = IssueBuilder.createOperationsIssue()
62
- .setPropertyId(propertyId)
63
- .setTitle(title)
64
- .setDescription(description)
65
- .setCreatedBy(createdBy);
66
-
67
- if (entityId) issueBuilder.setEntityId(entityId);
68
- if (entityType) issueBuilder.setEntityType(entityType);
69
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
70
- if (dueDate) issueBuilder.setDueDate(dueDate);
71
-
72
- return await this.createIssue(issueBuilder);
73
- }
74
-
75
- /**
76
- * Create a security issue using IssueBuilder
77
- */
78
- async createSecurityIssue(
79
- propertyId: string,
80
- title: string,
81
- description: string,
82
- createdBy: string,
83
- entityId?: string,
84
- entityType?: EntityType,
85
- assignedTo?: string,
86
- dueDate?: Date
87
- ): Promise<IIssueDocument> {
88
- const issueBuilder = IssueBuilder.createSecurityIssue()
89
- .setPropertyId(propertyId)
90
- .setTitle(title)
91
- .setDescription(description)
92
- .setCreatedBy(createdBy);
93
-
94
- if (entityId) issueBuilder.setEntityId(entityId);
95
- if (entityType) issueBuilder.setEntityType(entityType);
96
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
97
- if (dueDate) issueBuilder.setDueDate(dueDate);
98
-
99
- return await this.createIssue(issueBuilder);
100
- }
101
-
102
- /**
103
- * Create an energy issue using IssueBuilder
104
- */
105
- async createEnergyIssue(
106
- propertyId: string,
107
- title: string,
108
- description: string,
109
- createdBy: string,
110
- entityId?: string,
111
- entityType?: EntityType,
112
- assignedTo?: string,
113
- dueDate?: Date
114
- ): Promise<IIssueDocument> {
115
- const issueBuilder = IssueBuilder.createEnergyIssue()
116
- .setPropertyId(propertyId)
117
- .setTitle(title)
118
- .setDescription(description)
119
- .setCreatedBy(createdBy);
120
-
121
- if (entityId) issueBuilder.setEntityId(entityId);
122
- if (entityType) issueBuilder.setEntityType(entityType);
123
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
124
- if (dueDate) issueBuilder.setDueDate(dueDate);
125
-
126
- return await this.createIssue(issueBuilder);
127
- }
128
-
129
- /**
130
- * Create a device-specific issue using IssueBuilder
131
- */
132
- async createDeviceIssue(
133
- deviceId: string,
134
- propertyId: string,
135
- title: string,
136
- description: string,
137
- source: Source,
138
- category?: IssuesCategory,
139
- priority?: IssuePriority,
140
- assignedTo?: string,
141
- dueDate?: Date
142
- ): Promise<IIssueDocument> {
143
- const issueBuilder = IssueBuilder.createDeviceIssue(deviceId, propertyId)
144
- .setTitle(title)
145
- .setDescription(description)
146
- .setCreatedBy(source);
147
-
148
- if (category) issueBuilder.setCategory(category);
149
- if (priority) issueBuilder.setPriority(priority);
150
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
151
- if (dueDate) issueBuilder.setDueDate(dueDate);
152
-
153
- return await this.createIssue(issueBuilder);
154
- }
155
-
156
- /**
157
- * Create a hub-specific issue using IssueBuilder
158
- */
159
- async createHubIssue(
160
- hubId: string,
161
- propertyId: string,
162
- title: string,
163
- description: string,
164
- createdBy: string,
165
- category?: IssuesCategory,
166
- priority?: IssuePriority,
167
- assignedTo?: string,
168
- dueDate?: Date
169
- ): Promise<IIssueDocument> {
170
- const issueBuilder = IssueBuilder.createHubIssue(hubId, propertyId)
171
- .setTitle(title)
172
- .setDescription(description)
173
- .setCreatedBy(createdBy);
174
-
175
- if (category) issueBuilder.setCategory(category);
176
- if (priority) issueBuilder.setPriority(priority);
177
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
178
- if (dueDate) issueBuilder.setDueDate(dueDate);
179
-
180
- return await this.createIssue(issueBuilder);
181
- }
182
-
183
- /**
184
- * Create a user-specific issue using IssueBuilder
185
- */
186
- async createUserIssue(
187
- userId: string,
188
- propertyId: string,
189
- title: string,
190
- description: string,
191
- createdBy: string,
192
- category?: IssuesCategory,
193
- priority?: IssuePriority,
194
- assignedTo?: string,
195
- dueDate?: Date
196
- ): Promise<IIssueDocument> {
197
- const issueBuilder = IssueBuilder.createUserIssue(userId, propertyId)
198
- .setTitle(title)
199
- .setDescription(description)
200
- .setCreatedBy(createdBy);
201
-
202
- if (category) issueBuilder.setCategory(category);
203
- if (priority) issueBuilder.setPriority(priority);
204
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
205
- if (dueDate) issueBuilder.setDueDate(dueDate);
206
-
207
- return await this.createIssue(issueBuilder);
208
- }
209
-
210
- /**
211
- * Create issue for device going offline longer than baseline
212
- */
213
- async createDeviceOfflineIssue(
214
- device: IDevice,
215
- source: Source,
216
- reason?: string
217
- ): Promise<IIssueDocument> {
218
- return await this.createDeviceIssue(
219
- device.deviceId,
220
- device.propertyId,
221
- "Device Offline - Requires Attention",
222
- `Device ${device.name} (${
223
- device.deviceId
224
- }) has been offline for longer than the baseline time. ${
225
- reason ? `Reason: ${reason}` : ""
226
- } This requires immediate attention to restore device functionality.`,
227
- source,
228
- IssuesCategory.OPERATIONS,
229
- IssuePriority.HIGH
230
- );
231
- }
232
-
233
- /**
234
- * Create issue for device battery level below threshold
235
- */
236
- async createDeviceBatteryIssue(
237
- device: IDevice,
238
- batteryLevel: number,
239
- threshold: number,
240
- source: Source
241
- ): Promise<IIssueDocument> {
242
- return await this.createDeviceIssue(
243
- device.deviceId,
244
- device.propertyId,
245
- "Device Battery Low - Requires Attention",
246
- `Device ${device.name} (${device.deviceId}) battery level is ${batteryLevel}%, which is below the property threshold of ${threshold}%. This requires immediate attention to replace or charge the device battery.`,
247
- source,
248
- IssuesCategory.ENERGY,
249
- IssuePriority.MEDIUM
250
- );
251
- }
252
-
253
- /**
254
- * Create a maintenance issue using IssueBuilder
255
- */
256
- async createMaintenanceIssue(
257
- propertyId: string,
258
- title: string,
259
- description: string,
260
- createdBy: string,
261
- entityId?: string,
262
- entityType?: EntityType,
263
- assignedTo?: string,
264
- dueDate?: Date
265
- ): Promise<IIssueDocument> {
266
- const issueBuilder = IssueBuilder.createMaintenanceIssue(
267
- propertyId,
268
- entityId,
269
- entityType
270
- )
271
- .setTitle(title)
272
- .setDescription(description)
273
- .setCreatedBy(createdBy);
274
-
275
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
276
- if (dueDate) issueBuilder.setDueDate(dueDate);
277
-
278
- return await this.createIssue(issueBuilder);
279
- }
280
-
281
- /**
282
- * Create an urgent issue using IssueBuilder
283
- */
284
- async createUrgentIssue(
285
- propertyId: string,
286
- title: string,
287
- description: string,
288
- createdBy: string,
289
- entityId?: string,
290
- entityType?: EntityType,
291
- assignedTo?: string,
292
- dueDate?: Date
293
- ): Promise<IIssueDocument> {
294
- const issueBuilder = IssueBuilder.createUrgentIssue(
295
- propertyId,
296
- entityId,
297
- entityType
298
- )
299
- .setTitle(title)
300
- .setDescription(description)
301
- .setCreatedBy(createdBy);
302
-
303
- if (assignedTo) issueBuilder.setAssignedTo(assignedTo);
304
- if (dueDate) issueBuilder.setDueDate(dueDate);
305
-
306
- return await this.createIssue(issueBuilder);
307
- }
308
-
309
- /**
310
- * Create a new issue with business logic validation
311
- * Accepts either a CreateIssueData object or an IssueBuilder instance
312
- */
313
- async createIssue(
314
- issueData: CreateIssueData | IssueBuilder
315
- ): Promise<IIssueDocument> {
316
- let processedIssueData: CreateIssueData;
317
-
318
- // Handle IssueBuilder instance
319
- if (issueData instanceof IssueBuilder) {
320
- processedIssueData = issueData.build();
321
- } else {
322
- processedIssueData = issueData;
323
- }
324
-
325
- // Business logic: Validate issue data
326
- this.validateIssueData(processedIssueData);
327
-
328
- // Business logic: Set default priority if not provided
329
- if (!processedIssueData.priority) {
330
- processedIssueData.priority = this.determineDefaultPriority(
331
- processedIssueData.category
332
- );
333
- }
334
-
335
- // Business logic: Validate due date is in the future
336
- if (
337
- processedIssueData.dueDate &&
338
- processedIssueData.dueDate <= new Date()
339
- ) {
340
- throw new Error("Due date must be in the future");
341
- }
342
-
343
- return await this.issueRepository.create(processedIssueData);
344
- }
345
-
346
- /**
347
- * Get issue by ID with business logic
348
- */
349
- async getIssueById(
350
- id: string,
351
- includeDeleted = false
352
- ): Promise<IIssueDocument | null> {
353
- if (!id) {
354
- throw new Error("Issue ID is required");
355
- }
356
-
357
- const issue = await this.issueRepository.findById(id, includeDeleted);
358
-
359
- // Business logic: Check if issue is overdue
360
- if (
361
- issue?.dueDate &&
362
- issue.dueDate < new Date() &&
363
- issue.status !== IssueStatus.RESOLVED
364
- ) {
365
- // You could add a flag or handle overdue logic here
366
- console.warn(`Issue ${id} is overdue`);
367
- }
368
-
369
- return issue;
370
- }
371
-
372
- /**
373
- * Get all issues with business logic filtering
374
- */
375
- async getIssues(
376
- filters: {
377
- propertyId?: string;
378
- assignedTo?: string;
379
- status?: IssueStatus;
380
- priority?: IssuePriority;
381
- category?: IssuesCategory;
382
- entityType?: EntityType;
383
- entityId?: string;
384
- includeDeleted?: boolean;
385
- limit?: number;
386
- skip?: number;
387
- } = {}
388
- ): Promise<IIssueDocument[]> {
389
- // Business logic: Validate filters
390
- this.validateFilters(filters);
391
-
392
- // Business logic: Apply business rules to filters
393
- const enhancedFilters = this.applyBusinessRules(filters);
394
-
395
- return await this.issueRepository.findAll(enhancedFilters);
396
- }
397
-
398
- /**
399
- * Update an issue with business logic validation
400
- */
401
- async updateIssue(
402
- id: string,
403
- updateData: UpdateIssueData
404
- ): Promise<IIssueDocument | null> {
405
- if (!id) {
406
- throw new Error("Issue ID is required");
407
- }
408
-
409
- // Business logic: Validate update data
410
- this.validateUpdateData(updateData);
411
-
412
- // Business logic: Check if issue exists and is not deleted
413
- const existingIssue = await this.issueRepository.findById(id);
414
- if (!existingIssue) {
415
- throw new Error("Issue not found");
416
- }
417
-
418
- // Business logic: Handle status transitions
419
- if (updateData.status) {
420
- this.validateStatusTransition(existingIssue.status, updateData.status);
421
- }
422
-
423
- // Business logic: Handle priority changes
424
- if (updateData.priority) {
425
- this.validatePriorityChange(existingIssue.priority, updateData.priority);
426
- }
427
-
428
- return await this.issueRepository.update(id, updateData);
429
- }
430
-
431
- /**
432
- * Soft delete an issue with business logic
433
- */
434
- async deleteIssue(id: string, deletedBy: string): Promise<boolean> {
435
- if (!id || !deletedBy) {
436
- throw new Error("Issue ID and deleted by user are required");
437
- }
438
-
439
- // Business logic: Check if issue can be deleted
440
- const issue = await this.issueRepository.findById(id);
441
- if (!issue) {
442
- throw new Error("Issue not found");
443
- }
444
-
445
- // Business logic: Prevent deletion of resolved issues (optional rule)
446
- if (issue.status === IssueStatus.RESOLVED) {
447
- throw new Error("Cannot delete resolved issues");
448
- }
449
-
450
- return await this.issueRepository.softDelete(id, deletedBy);
451
- }
452
-
453
- /**
454
- * Permanently delete an issue
455
- */
456
- async permanentlyDeleteIssue(id: string): Promise<boolean> {
457
- return await this.issueRepository.hardDelete(id);
458
- }
459
-
460
- /**
461
- * Add a comment with business logic
462
- */
463
- async addComment(
464
- issueId: string,
465
- commentData: AddCommentData
466
- ): Promise<IIssueDocument | null> {
467
- if (!issueId || !commentData.userId || !commentData.content) {
468
- throw new Error("Issue ID, user ID, and comment content are required");
469
- }
470
-
471
- // Business logic: Check if issue exists and is active
472
- const issue = await this.issueRepository.findById(issueId);
473
- if (!issue) {
474
- throw new Error("Issue not found");
475
- }
476
-
477
- if (issue.isDeleted) {
478
- throw new Error("Cannot add comment to deleted issue");
479
- }
480
-
481
- // Business logic: Update issue status based on comment (optional)
482
- const shouldUpdateStatus = this.shouldUpdateStatusOnComment(
483
- commentData.content
484
- );
485
-
486
- if (shouldUpdateStatus && issue.status === IssueStatus.PENDING) {
487
- await this.issueRepository.update(issueId, {
488
- status: IssueStatus.IN_PROGRESS,
489
- updatedBy: commentData.userId,
490
- });
491
- }
492
-
493
- // Add comment using the model instance methods
494
- const issueModel = await IssueModel.findById(issueId);
495
- if (issueModel) {
496
- issueModel.addComment(commentData);
497
- return await issueModel.save();
498
- }
499
-
500
- return null;
501
- }
502
-
503
- /**
504
- * Update a comment on an issue
505
- */
506
- async updateComment(
507
- issueId: string,
508
- commentId: string,
509
- content: string,
510
- userId: string
511
- ): Promise<boolean> {
512
- const issueModel = await IssueModel.findById(issueId);
513
- if (!issueModel) return false;
514
-
515
- const success = issueModel.updateComment(commentId, content, userId);
516
- if (success) {
517
- await issueModel.save();
518
- }
519
- return success;
520
- }
521
-
522
- /**
523
- * Remove a comment from an issue
524
- */
525
- async removeComment(issueId: string, commentId: string): Promise<boolean> {
526
- const issueModel = await IssueModel.findById(issueId);
527
- if (!issueModel) return false;
528
-
529
- const success = issueModel.removeComment(commentId);
530
- if (success) {
531
- await issueModel.save();
532
- }
533
- return success;
534
- }
535
-
536
- /**
537
- * Resolve an issue with business logic
538
- */
539
- async resolveIssue(
540
- id: string,
541
- resolvedBy: string
542
- ): Promise<IIssueDocument | null> {
543
- if (!id || !resolvedBy) {
544
- throw new Error("Issue ID and resolved by user are required");
545
- }
546
-
547
- // Business logic: Check if issue can be resolved
548
- const issue = await this.issueRepository.findById(id);
549
- if (!issue) {
550
- throw new Error("Issue not found");
551
- }
552
-
553
- if (issue.status === IssueStatus.RESOLVED) {
554
- throw new Error("Issue is already resolved");
555
- }
556
-
557
- if (issue.status === IssueStatus.CANCELLED) {
558
- throw new Error("Cannot resolve cancelled issue");
559
- }
560
-
561
- // Business logic: Auto-assign if not assigned
562
- if (!issue.assignedTo) {
563
- await this.issueRepository.update(id, {
564
- assignedTo: resolvedBy,
565
- updatedBy: resolvedBy,
566
- });
567
- }
568
-
569
- // Resolve the issue using model instance methods
570
- const issueModel = await IssueModel.findById(id);
571
- if (issueModel) {
572
- issueModel.resolve(resolvedBy);
573
- return await issueModel.save();
574
- }
575
-
576
- return null;
577
- }
578
-
579
- /**
580
- * Reopen a resolved issue
581
- */
582
- async reopenIssue(
583
- id: string,
584
- reopenedBy: string
585
- ): Promise<IIssueDocument | null> {
586
- const issueModel = await IssueModel.findById(id);
587
- if (!issueModel) return null;
588
-
589
- issueModel.reopen(reopenedBy);
590
- return await issueModel.save();
591
- }
592
-
593
- /**
594
- * Assign an issue with business logic
595
- */
596
- async assignIssue(
597
- id: string,
598
- userId: string,
599
- assignedBy: string
600
- ): Promise<IIssueDocument | null> {
601
- if (!id || !userId || !assignedBy) {
602
- throw new Error(
603
- "Issue ID, assignee user ID, and assigned by user are required"
604
- );
605
- }
606
-
607
- // Business logic: Check if issue can be assigned
608
- const issue = await this.issueRepository.findById(id);
609
- if (!issue) {
610
- throw new Error("Issue not found");
611
- }
612
-
613
- if (
614
- issue.status === IssueStatus.RESOLVED ||
615
- issue.status === IssueStatus.CLOSED
616
- ) {
617
- throw new Error("Cannot assign resolved or closed issue");
618
- }
619
-
620
- // Business logic: Update status to IN_PROGRESS when assigned
621
- const updateData: UpdateIssueData = {
622
- assignedTo: userId,
623
- updatedBy: assignedBy,
624
- };
625
-
626
- if (issue.status === IssueStatus.PENDING) {
627
- updateData.status = IssueStatus.IN_PROGRESS;
628
- }
629
-
630
- return await this.issueRepository.update(id, updateData);
631
- }
632
-
633
- /**
634
- * Unassign an issue
635
- */
636
- async unassignIssue(
637
- id: string,
638
- unassignedBy: string
639
- ): Promise<IIssueDocument | null> {
640
- const issueModel = await IssueModel.findById(id);
641
- if (!issueModel) return null;
642
-
643
- issueModel.unassign(unassignedBy);
644
- return await issueModel.save();
645
- }
646
-
647
- /**
648
- * Get issues by property with business logic
649
- */
650
- async getIssuesByProperty(
651
- propertyId: string,
652
- includeDeleted = false
653
- ): Promise<IIssueDocument[]> {
654
- if (!propertyId) {
655
- throw new Error("Property ID is required");
656
- }
657
-
658
- return await this.issueRepository.findByProperty(
659
- propertyId,
660
- includeDeleted
661
- );
662
- }
663
-
664
- /**
665
- * Get issues assigned to a user with business logic
666
- */
667
- async getIssuesByAssignee(
668
- assignedTo: string,
669
- includeDeleted = false
670
- ): Promise<IIssueDocument[]> {
671
- if (!assignedTo) {
672
- throw new Error("Assignee user ID is required");
673
- }
674
-
675
- return await this.issueRepository.findByAssignee(
676
- assignedTo,
677
- includeDeleted
678
- );
679
- }
680
-
681
- /**
682
- * Get issues by entity
683
- */
684
- async getIssuesByEntity(
685
- entityId: string,
686
- entityType: EntityType,
687
- includeDeleted = false
688
- ): Promise<IIssueDocument[]> {
689
- return await this.issueRepository.findByEntity(
690
- entityId,
691
- entityType,
692
- includeDeleted
693
- );
694
- }
695
-
696
- /**
697
- * Get issues by status
698
- */
699
- async getIssuesByStatus(
700
- status: IssueStatus,
701
- includeDeleted = false
702
- ): Promise<IIssueDocument[]> {
703
- return await this.issueRepository.findByStatus(status, includeDeleted);
704
- }
705
-
706
- /**
707
- * Get issues by priority
708
- */
709
- async getIssuesByPriority(
710
- priority: IssuePriority,
711
- includeDeleted = false
712
- ): Promise<IIssueDocument[]> {
713
- return await this.issueRepository.findByPriority(priority, includeDeleted);
714
- }
715
-
716
- /**
717
- * Get overdue issues with business logic
718
- */
719
- async getOverdueIssues(includeDeleted = false): Promise<IIssueDocument[]> {
720
- const overdueIssues = await this.issueRepository.findOverdue(
721
- includeDeleted
722
- );
723
-
724
- // Business logic: Log overdue issues for monitoring
725
- if (overdueIssues.length > 0) {
726
- console.warn(`Found ${overdueIssues.length} overdue issues`);
727
- }
728
-
729
- return overdueIssues;
730
- }
731
-
732
- /**
733
- * Get upcoming issues (due within specified days)
734
- */
735
- async getUpcomingIssues(
736
- days: number = 7,
737
- includeDeleted = false
738
- ): Promise<IIssueDocument[]> {
739
- return await this.issueRepository.findUpcoming(days, includeDeleted);
740
- }
741
-
742
- /**
743
- * Get issue statistics with business logic
744
- */
745
- async getIssueStatistics(propertyId?: string): Promise<{
746
- total: number;
747
- pending: number;
748
- inProgress: number;
749
- resolved: number;
750
- closed: number;
751
- overdue: number;
752
- byPriority: Record<IssuePriority, number>;
753
- byCategory: Record<IssuesCategory, number>;
754
- }> {
755
- const stats = await this.issueRepository.getStatistics(propertyId);
756
-
757
- // Business logic: Calculate additional metrics
758
- const responseTime = this.calculateAverageResponseTime(stats);
759
- const resolutionRate = this.calculateResolutionRate(stats);
760
-
761
- // Log resolution rate for monitoring
762
- console.log(`Resolution rate: ${resolutionRate.toFixed(2)}%`);
763
- console.log(`Response time: ${responseTime.toFixed(2)} days`);
764
-
765
- // Business logic: Add alerts for critical metrics
766
- if (stats.overdue > 0) {
767
- console.warn(`Alert: ${stats.overdue} overdue issues detected`);
768
- }
769
-
770
- if (stats.byPriority[IssuePriority.CRITICAL] > 0) {
771
- console.error(
772
- `Alert: ${
773
- stats.byPriority[IssuePriority.CRITICAL]
774
- } critical issues require immediate attention`
775
- );
776
- }
777
-
778
- return stats;
779
- }
780
-
781
- /**
782
- * Search issues with business logic
783
- */
784
- async searchIssues(
785
- searchTerm: string,
786
- filters: {
787
- propertyId?: string;
788
- includeDeleted?: boolean;
789
- limit?: number;
790
- skip?: number;
791
- } = {}
792
- ): Promise<IIssueDocument[]> {
793
- if (!searchTerm || searchTerm.trim().length < 2) {
794
- throw new Error("Search term must be at least 2 characters long");
795
- }
796
-
797
- return await this.issueRepository.search(searchTerm, filters);
798
- }
799
-
800
- // Private business logic methods
801
-
802
- private validateIssueData(data: CreateIssueData): void {
803
- if (!data.title || data.title.trim().length < 5) {
804
- throw new Error("Issue title must be at least 5 characters long");
805
- }
806
-
807
- if (!data.description || data.description.trim().length < 10) {
808
- throw new Error("Issue description must be at least 10 characters long");
809
- }
810
-
811
- if (!data.propertyId) {
812
- throw new Error("Property ID is required");
813
- }
814
-
815
- if (!data.createdBy) {
816
- throw new Error("Created by user ID is required");
817
- }
818
- }
819
-
820
- private validateFilters(filters: any): void {
821
- if (filters.limit && (filters.limit < 1 || filters.limit > 100)) {
822
- throw new Error("Limit must be between 1 and 100");
823
- }
824
-
825
- if (filters.skip && filters.skip < 0) {
826
- throw new Error("Skip must be non-negative");
827
- }
828
- }
829
-
830
- private validateUpdateData(data: UpdateIssueData): void {
831
- if (data.title && data.title.trim().length < 5) {
832
- throw new Error("Issue title must be at least 5 characters long");
833
- }
834
-
835
- if (data.description && data.description.trim().length < 10) {
836
- throw new Error("Issue description must be at least 10 characters long");
837
- }
838
- }
839
-
840
- private validateStatusTransition(
841
- currentStatus: IssueStatus,
842
- newStatus: IssueStatus
843
- ): void {
844
- const validTransitions: Record<IssueStatus, IssueStatus[]> = {
845
- [IssueStatus.PENDING]: [
846
- IssueStatus.IN_PROGRESS,
847
- IssueStatus.CANCELLED,
848
- IssueStatus.ON_HOLD,
849
- ],
850
- [IssueStatus.IN_PROGRESS]: [
851
- IssueStatus.RESOLVED,
852
- IssueStatus.CANCELLED,
853
- IssueStatus.ON_HOLD,
854
- ],
855
- [IssueStatus.RESOLVED]: [IssueStatus.CLOSED, IssueStatus.PENDING], // Reopen
856
- [IssueStatus.CLOSED]: [IssueStatus.PENDING], // Reopen
857
- [IssueStatus.CANCELLED]: [IssueStatus.PENDING], // Reopen
858
- [IssueStatus.ON_HOLD]: [IssueStatus.PENDING, IssueStatus.IN_PROGRESS],
859
- };
860
-
861
- if (!validTransitions[currentStatus]?.includes(newStatus)) {
862
- throw new Error(
863
- `Invalid status transition from ${currentStatus} to ${newStatus}`
864
- );
865
- }
866
- }
867
-
868
- private validatePriorityChange(
869
- currentPriority: IssuePriority,
870
- newPriority: IssuePriority
871
- ): void {
872
- // Business rule: Only allow priority escalation, not de-escalation for critical issues
873
- if (
874
- currentPriority === IssuePriority.CRITICAL &&
875
- newPriority !== IssuePriority.CRITICAL
876
- ) {
877
- throw new Error("Cannot de-escalate priority of critical issues");
878
- }
879
- }
880
-
881
- private determineDefaultPriority(category: IssuesCategory): IssuePriority {
882
- // Business logic: Determine default priority based on category
883
- const categoryPriorities: Record<IssuesCategory, IssuePriority> = {
884
- [IssuesCategory.READINESS]: IssuePriority.MEDIUM,
885
- [IssuesCategory.OPERATIONS]: IssuePriority.HIGH,
886
- [IssuesCategory.SECURITY]: IssuePriority.CRITICAL,
887
- [IssuesCategory.ENERGY]: IssuePriority.LOW,
888
- [IssuesCategory.OTHER]: IssuePriority.MEDIUM,
889
- };
890
-
891
- return categoryPriorities[category] || IssuePriority.MEDIUM;
892
- }
893
-
894
- private applyBusinessRules(filters: any): any {
895
- // Business logic: Apply additional filters based on business rules
896
- const enhancedFilters = { ...filters };
897
-
898
- // Example: Always exclude cancelled issues unless explicitly requested
899
- if (
900
- !enhancedFilters.status ||
901
- enhancedFilters.status !== IssueStatus.CANCELLED
902
- ) {
903
- enhancedFilters.status = { $ne: IssueStatus.CANCELLED };
904
- }
905
-
906
- return enhancedFilters;
907
- }
908
-
909
- private shouldUpdateStatusOnComment(content: string): boolean {
910
- // Business logic: Determine if comment should trigger status change
911
- const statusKeywords = [
912
- "working on",
913
- "investigating",
914
- "fixing",
915
- "resolving",
916
- ];
917
- return statusKeywords.some((keyword) =>
918
- content.toLowerCase().includes(keyword)
919
- );
920
- }
921
-
922
- private calculateAverageResponseTime(stats: any): number {
923
- // Business logic: Calculate average response time (placeholder)
924
- return 0;
925
- }
926
-
927
- private calculateResolutionRate(stats: any): number {
928
- // Business logic: Calculate resolution rate
929
- if (stats.total === 0) return 0;
930
- return ((stats.resolved + stats.closed) / stats.total) * 100;
931
- }
932
- }