@primocaredentgroup/elettromedicali 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.ts +0 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -28
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +4 -4
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +165 -218
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/contracts.d.ts +9 -9
- package/dist/component/contracts.d.ts.map +1 -1
- package/dist/component/contracts.js +7 -13
- package/dist/component/contracts.js.map +1 -1
- package/dist/component/crons.d.ts.map +1 -1
- package/dist/component/crons.js +1 -2
- package/dist/component/crons.js.map +1 -1
- package/dist/component/dashboardStats.d.ts +8 -3
- package/dist/component/dashboardStats.d.ts.map +1 -1
- package/dist/component/dashboardStats.js +24 -39
- package/dist/component/dashboardStats.js.map +1 -1
- package/dist/component/dashboardStatsCache.d.ts +5 -11
- package/dist/component/dashboardStatsCache.d.ts.map +1 -1
- package/dist/component/dashboardStatsCache.js +12 -53
- package/dist/component/dashboardStatsCache.js.map +1 -1
- package/dist/component/deviceCategories.d.ts +22 -15
- package/dist/component/deviceCategories.d.ts.map +1 -1
- package/dist/component/deviceCategories.js +10 -4
- package/dist/component/deviceCategories.js.map +1 -1
- package/dist/component/deviceQuestions.d.ts +36 -27
- package/dist/component/deviceQuestions.d.ts.map +1 -1
- package/dist/component/deviceQuestions.js +22 -5
- package/dist/component/deviceQuestions.js.map +1 -1
- package/dist/component/deviceRepairHistory.d.ts +3 -3
- package/dist/component/deviceRepairHistory.js +1 -1
- package/dist/component/deviceRepairHistory.js.map +1 -1
- package/dist/component/deviceStatus.d.ts +8 -57
- package/dist/component/deviceStatus.d.ts.map +1 -1
- package/dist/component/deviceStatus.js +32 -30
- package/dist/component/deviceStatus.js.map +1 -1
- package/dist/component/devices.d.ts +39 -22
- package/dist/component/devices.d.ts.map +1 -1
- package/dist/component/devices.js +85 -96
- package/dist/component/devices.js.map +1 -1
- package/dist/component/emailHelpers.d.ts +10 -3
- package/dist/component/emailHelpers.d.ts.map +1 -1
- package/dist/component/emailHelpers.js +9 -20
- package/dist/component/emailHelpers.js.map +1 -1
- package/dist/component/emails.d.ts +5 -5
- package/dist/component/emails.js +2 -2
- package/dist/component/emails.js.map +1 -1
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +3 -108
- package/dist/component/http.js.map +1 -1
- package/dist/component/migrationHelpers.d.ts +29 -0
- package/dist/component/migrationHelpers.d.ts.map +1 -0
- package/dist/component/migrationHelpers.js +84 -0
- package/dist/component/migrationHelpers.js.map +1 -0
- package/dist/component/roles.d.ts +1 -0
- package/dist/component/roles.d.ts.map +1 -1
- package/dist/component/roles.js +5 -6
- package/dist/component/roles.js.map +1 -1
- package/dist/component/schema.d.ts +69 -150
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +35 -88
- package/dist/component/schema.js.map +1 -1
- package/dist/component/slaMonitoring.d.ts +16 -30
- package/dist/component/slaMonitoring.d.ts.map +1 -1
- package/dist/component/slaMonitoring.js +48 -99
- package/dist/component/slaMonitoring.js.map +1 -1
- package/dist/component/spareParts.d.ts +11 -48
- package/dist/component/spareParts.d.ts.map +1 -1
- package/dist/component/spareParts.js +41 -11
- package/dist/component/spareParts.js.map +1 -1
- package/dist/component/suppliers.d.ts +38 -19
- package/dist/component/suppliers.d.ts.map +1 -1
- package/dist/component/suppliers.js +63 -44
- package/dist/component/suppliers.js.map +1 -1
- package/dist/component/ticketComments.d.ts +18 -12
- package/dist/component/ticketComments.d.ts.map +1 -1
- package/dist/component/ticketComments.js +28 -59
- package/dist/component/ticketComments.js.map +1 -1
- package/dist/component/ticketDeviceData.d.ts +63 -0
- package/dist/component/ticketDeviceData.d.ts.map +1 -0
- package/dist/component/ticketDeviceData.js +103 -0
- package/dist/component/ticketDeviceData.js.map +1 -0
- package/dist/component/ticketExport.d.ts +22 -40
- package/dist/component/ticketExport.d.ts.map +1 -1
- package/dist/component/ticketExport.js +43 -109
- package/dist/component/ticketExport.js.map +1 -1
- package/dist/component/ticketHistory.d.ts +4 -4
- package/dist/component/ticketHistory.d.ts.map +1 -1
- package/dist/component/ticketHistory.js +6 -9
- package/dist/component/ticketHistory.js.map +1 -1
- package/dist/component/ticketMacros.d.ts +19 -18
- package/dist/component/ticketMacros.d.ts.map +1 -1
- package/dist/component/ticketMacros.js +24 -30
- package/dist/component/ticketMacros.js.map +1 -1
- package/dist/component/ticketStatuses.d.ts +1 -0
- package/dist/component/ticketStatuses.d.ts.map +1 -1
- package/dist/component/ticketStatuses.js +5 -6
- package/dist/component/ticketStatuses.js.map +1 -1
- package/dist/component/ticketTriggers.d.ts +36 -16
- package/dist/component/ticketTriggers.d.ts.map +1 -1
- package/dist/component/ticketTriggers.js +115 -153
- package/dist/component/ticketTriggers.js.map +1 -1
- package/dist/component/userProfiles.d.ts +25 -120
- package/dist/component/userProfiles.d.ts.map +1 -1
- package/dist/component/userProfiles.js +73 -384
- package/dist/component/userProfiles.js.map +1 -1
- package/dist/test.d.ts +69 -150
- package/dist/test.d.ts.map +1 -1
- package/package.json +12 -3
- package/src/client/index.ts +2 -30
- package/src/component/_generated/api.ts +4 -4
- package/src/component/_generated/component.ts +228 -350
- package/src/component/contracts.ts +7 -14
- package/src/component/crons.ts +2 -7
- package/src/component/dashboardStats.ts +24 -41
- package/src/component/dashboardStatsCache.ts +12 -61
- package/src/component/deviceCategories.ts +12 -4
- package/src/component/deviceQuestions.ts +28 -5
- package/src/component/deviceRepairHistory.ts +1 -1
- package/src/component/deviceStatus.ts +43 -45
- package/src/component/devices.ts +87 -106
- package/src/component/emailHelpers.ts +9 -19
- package/src/component/emails.ts +2 -2
- package/src/component/http.ts +3 -108
- package/src/component/migrationHelpers.ts +96 -0
- package/src/component/roles.ts +5 -6
- package/src/component/schema.ts +35 -93
- package/src/component/slaMonitoring.ts +52 -107
- package/src/component/spareParts.ts +46 -12
- package/src/component/suppliers.ts +71 -48
- package/src/component/ticketComments.ts +28 -71
- package/src/component/ticketDeviceData.ts +113 -0
- package/src/component/ticketExport.ts +52 -137
- package/src/component/ticketHistory.ts +6 -9
- package/src/component/ticketMacros.ts +25 -37
- package/src/component/ticketStatuses.ts +5 -6
- package/src/component/ticketTriggers.ts +121 -217
- package/src/component/userProfiles.ts +67 -451
- package/dist/component/clinics.d.ts +0 -103
- package/dist/component/clinics.d.ts.map +0 -1
- package/dist/component/clinics.js +0 -126
- package/dist/component/clinics.js.map +0 -1
- package/dist/component/maintenanceTasks.d.ts +0 -733
- package/dist/component/maintenanceTasks.d.ts.map +0 -1
- package/dist/component/maintenanceTasks.js +0 -937
- package/dist/component/maintenanceTasks.js.map +0 -1
- package/src/component/clinics.ts +0 -136
- package/src/component/maintenanceTasks.ts +0 -1003
package/src/component/schema.ts
CHANGED
|
@@ -20,30 +20,19 @@ export default defineSchema({
|
|
|
20
20
|
createdAt: v.optional(v.number()),
|
|
21
21
|
}).index('by_name', ['name']),
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Domain-specific user settings (base user data comes from parent app)
|
|
24
24
|
user_profiles: defineTable({
|
|
25
25
|
auth0Id: v.string(),
|
|
26
|
-
|
|
27
|
-
role: v.optional(v.string()), // Legacy field - will be migrated to roleId
|
|
28
|
-
clinicId: v.optional(v.id('clinics')),
|
|
26
|
+
clinicId: v.optional(v.string()),
|
|
29
27
|
supplierId: v.optional(v.id('suppliers')),
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
selectedClinicId: v.optional(v.number()), // Currently selected PrimoUP clinic ID
|
|
35
|
-
clinicChangeCounter: v.optional(v.number()), // Increments every time clinic changes
|
|
36
|
-
// Impersonation (admin only)
|
|
37
|
-
impersonatingUserId: v.optional(v.id('user_profiles')), // ID of user being impersonated
|
|
38
|
-
// Account status
|
|
39
|
-
isActive: v.optional(v.boolean()), // Default true, false = account disabilitato
|
|
28
|
+
primoupClinics: v.optional(v.array(v.any())),
|
|
29
|
+
selectedClinicId: v.optional(v.number()),
|
|
30
|
+
clinicChangeCounter: v.optional(v.number()),
|
|
31
|
+
impersonatingUserId: v.optional(v.string()),
|
|
40
32
|
})
|
|
41
33
|
.index('by_auth0Id', ['auth0Id'])
|
|
42
|
-
.index('by_email', ['email'])
|
|
43
|
-
.index('by_roleId', ['roleId'])
|
|
44
34
|
.index('by_clinicId', ['clinicId'])
|
|
45
|
-
.index('by_supplierId', ['supplierId'])
|
|
46
|
-
.index('by_isActive', ['isActive']),
|
|
35
|
+
.index('by_supplierId', ['supplierId']),
|
|
47
36
|
|
|
48
37
|
// PrimoUP authentication tokens
|
|
49
38
|
primoup_tokens: defineTable({
|
|
@@ -72,19 +61,9 @@ export default defineSchema({
|
|
|
72
61
|
.index('by_timestamp', ['timestamp'])
|
|
73
62
|
.index('by_userId_timestamp', ['userId', 'timestamp']),
|
|
74
63
|
|
|
75
|
-
// Clinics
|
|
76
|
-
clinics: defineTable({
|
|
77
|
-
name: v.string(),
|
|
78
|
-
address: v.string(),
|
|
79
|
-
contact_email: v.string(),
|
|
80
|
-
contact_phone: v.string(),
|
|
81
|
-
region: v.optional(v.string()), // Regione della clinica
|
|
82
|
-
primoupId: v.optional(v.string()), // PrimoUP clinic ID for sync
|
|
83
|
-
}).index('by_primoupId', ['primoupId']),
|
|
84
|
-
|
|
85
64
|
// Devices
|
|
86
65
|
devices: defineTable({
|
|
87
|
-
clinicId: v.
|
|
66
|
+
clinicId: v.string(),
|
|
88
67
|
primoupClinicId: v.optional(v.number()), // PrimoUP clinic ID (for filtering)
|
|
89
68
|
supplierId: v.optional(v.id('suppliers')), // Fornitore che fornisce assistenza
|
|
90
69
|
name: v.string(),
|
|
@@ -145,8 +124,8 @@ export default defineSchema({
|
|
|
145
124
|
// Storico riparazioni attrezzature
|
|
146
125
|
device_repair_history: defineTable({
|
|
147
126
|
deviceId: v.id('devices'),
|
|
148
|
-
clinicId: v.
|
|
149
|
-
ticketId: v.optional(v.
|
|
127
|
+
clinicId: v.string(),
|
|
128
|
+
ticketId: v.optional(v.string()),
|
|
150
129
|
sentToRepairAt: v.number(),
|
|
151
130
|
returnedAt: v.optional(v.number()),
|
|
152
131
|
statusAfterRepair: v.optional(v.union(
|
|
@@ -213,8 +192,8 @@ export default defineSchema({
|
|
|
213
192
|
// Contracts
|
|
214
193
|
contracts: defineTable({
|
|
215
194
|
supplierId: v.id('suppliers'),
|
|
216
|
-
clinicId: v.optional(v.
|
|
217
|
-
clinicIds: v.optional(v.array(v.
|
|
195
|
+
clinicId: v.optional(v.string()), // Legacy: single clinic (deprecated)
|
|
196
|
+
clinicIds: v.optional(v.array(v.string())), // Multiple clinics (empty = all clinics)
|
|
218
197
|
start_date: v.number(),
|
|
219
198
|
end_date: v.number(),
|
|
220
199
|
amount: v.number(),
|
|
@@ -281,58 +260,21 @@ export default defineSchema({
|
|
|
281
260
|
.index('by_applyToAll', ['applyToAll'])
|
|
282
261
|
.index('by_parentFieldId', ['parentFieldId']),
|
|
283
262
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
deviceId: v.optional(v.id('devices')), // Opzionale per ticket esterni senza device specifico
|
|
289
|
-
clinicId: v.id('clinics'),
|
|
290
|
-
supplierId: v.optional(v.id('suppliers')),
|
|
291
|
-
created_by: v.string(), // Auth0 user ID
|
|
292
|
-
createdByEmail: v.optional(v.string()), // Email del creatore (per webhook)
|
|
293
|
-
description: v.string(),
|
|
294
|
-
photos: v.array(v.string()), // Legacy base64 - kept for backward compatibility
|
|
295
|
-
photoStorageIds: v.optional(v.array(v.id('_storage'))), // New: Convex file storage
|
|
296
|
-
status: v.string(), // References ticket_statuses.name
|
|
297
|
-
priority: v.optional(v.union(
|
|
298
|
-
v.literal('low'),
|
|
299
|
-
v.literal('medium'),
|
|
300
|
-
v.literal('high')
|
|
301
|
-
)), // Priorità del ticket (usata per SLA)
|
|
302
|
-
notes: v.optional(v.string()), // Additional notes from supplier/admin
|
|
303
|
-
customFields: v.optional(v.any()), // Valori dei campi custom (oggetto dinamico)
|
|
263
|
+
// Ticket device data (links Abaddon ticket to component device/photos)
|
|
264
|
+
ticket_device_data: defineTable({
|
|
265
|
+
ticketId: v.string(), // Abaddon ticket _id (string reference)
|
|
266
|
+
deviceId: v.optional(v.id('devices')),
|
|
304
267
|
deviceQuestionAnswers: v.optional(v.array(v.object({
|
|
305
268
|
questionId: v.id('device_questions'),
|
|
306
|
-
question: v.string(),
|
|
307
|
-
answer: v.any(),
|
|
308
|
-
}))),
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// External system integration (Abaddon)
|
|
313
|
-
externalTicketId: v.optional(v.string()), // ID del ticket nel sistema esterno
|
|
314
|
-
externalTicketNumber: v.optional(v.number()), // Numero ticket nel sistema esterno
|
|
315
|
-
// Import tracking
|
|
316
|
-
importSource: v.optional(v.string()), // Es: "legacy_zammad", "external_api" per ticket importati
|
|
317
|
-
legacyTicketId: v.optional(v.number()), // ID originale nel sistema legacy
|
|
318
|
-
// External ticket flags
|
|
319
|
-
isExternal: v.optional(v.boolean()), // True se ticket creato da API esterna (senza device)
|
|
320
|
-
needsAssignment: v.optional(v.boolean()), // True se ticket da gestire (senza fornitore assegnato)
|
|
321
|
-
created_at: v.number(),
|
|
322
|
-
updated_at: v.number(),
|
|
323
|
-
closed_at: v.optional(v.number()), // Data chiusura (quando status diventa completed/cancelled)
|
|
269
|
+
question: v.string(),
|
|
270
|
+
answer: v.any(),
|
|
271
|
+
}))),
|
|
272
|
+
photoStorageIds: v.optional(v.array(v.id('_storage'))),
|
|
273
|
+
isExternal: v.optional(v.boolean()),
|
|
274
|
+
needsAssignment: v.optional(v.boolean()),
|
|
324
275
|
})
|
|
325
|
-
.index('
|
|
326
|
-
.index('by_deviceId', ['deviceId'])
|
|
327
|
-
.index('by_clinicId', ['clinicId'])
|
|
328
|
-
.index('by_supplierId', ['supplierId'])
|
|
329
|
-
.index('by_status', ['status'])
|
|
330
|
-
.index('by_status_created_at', ['status', 'created_at'])
|
|
331
|
-
.index('by_priority', ['priority'])
|
|
332
|
-
.index('by_created_at', ['created_at'])
|
|
333
|
-
.index('by_slaDeadline', ['slaDeadline'])
|
|
334
|
-
.index('by_externalTicketId', ['externalTicketId'])
|
|
335
|
-
.index('by_importSource', ['importSource']),
|
|
276
|
+
.index('by_ticketId', ['ticketId'])
|
|
277
|
+
.index('by_deviceId', ['deviceId']),
|
|
336
278
|
|
|
337
279
|
// Dashboard stats cache - pre-computed every 10 min to avoid bytes read limit
|
|
338
280
|
dashboard_stats_cache: defineTable({
|
|
@@ -457,7 +399,7 @@ export default defineSchema({
|
|
|
457
399
|
assignSupplier: v.optional(v.id('suppliers')),
|
|
458
400
|
|
|
459
401
|
// Assegna utente specifico (utente con ruolo supplier)
|
|
460
|
-
assignUser: v.optional(v.
|
|
402
|
+
assignUser: v.optional(v.string()),
|
|
461
403
|
|
|
462
404
|
// Imposta SLA (ore)
|
|
463
405
|
setSlaHours: v.optional(v.number()),
|
|
@@ -481,7 +423,7 @@ export default defineSchema({
|
|
|
481
423
|
v.literal('specific_user'), // Utente specifico
|
|
482
424
|
v.literal('specific_supplier') // Fornitore specifico
|
|
483
425
|
),
|
|
484
|
-
userId: v.optional(v.
|
|
426
|
+
userId: v.optional(v.string()), // Per specific_user
|
|
485
427
|
supplierId: v.optional(v.id('suppliers')), // Per specific_supplier
|
|
486
428
|
}),
|
|
487
429
|
// Vecchio formato (stringhe) - per retrocompatibilità temporanea
|
|
@@ -532,7 +474,7 @@ export default defineSchema({
|
|
|
532
474
|
actions: v.object({
|
|
533
475
|
changeStatus: v.optional(v.string()), // Cambia stato
|
|
534
476
|
assignSupplier: v.optional(v.id('suppliers')), // Assegna azienda fornitrice
|
|
535
|
-
assignUser: v.optional(v.
|
|
477
|
+
assignUser: v.optional(v.string()), // Assegna utente specifico
|
|
536
478
|
setSlaHours: v.optional(v.number()), // Imposta SLA
|
|
537
479
|
applySlaRule: v.optional(v.boolean()), // Applica regola SLA automatica
|
|
538
480
|
addNote: v.optional(v.string()), // Aggiungi nota automatica
|
|
@@ -547,7 +489,7 @@ export default defineSchema({
|
|
|
547
489
|
v.literal('specific_user'),
|
|
548
490
|
v.literal('specific_supplier')
|
|
549
491
|
),
|
|
550
|
-
userId: v.optional(v.
|
|
492
|
+
userId: v.optional(v.string()),
|
|
551
493
|
supplierId: v.optional(v.id('suppliers')),
|
|
552
494
|
}),
|
|
553
495
|
v.literal('admin'),
|
|
@@ -561,7 +503,7 @@ export default defineSchema({
|
|
|
561
503
|
|
|
562
504
|
createdAt: v.number(),
|
|
563
505
|
updatedAt: v.number(),
|
|
564
|
-
createdBy: v.
|
|
506
|
+
createdBy: v.string(), // Chi ha creato la macro
|
|
565
507
|
})
|
|
566
508
|
.index('by_createdBy', ['createdBy'])
|
|
567
509
|
.index('by_isGlobal', ['isGlobal'])
|
|
@@ -700,7 +642,7 @@ export default defineSchema({
|
|
|
700
642
|
|
|
701
643
|
// Ticket comments (Commenti sulle segnalazioni)
|
|
702
644
|
ticket_comments: defineTable({
|
|
703
|
-
|
|
645
|
+
ticketId: v.string(), // Abaddon ticket _id (string reference)
|
|
704
646
|
authorId: v.string(), // Auth0 user ID dell'autore
|
|
705
647
|
authorName: v.optional(v.string()), // Nome dell'autore (cached)
|
|
706
648
|
authorEmail: v.optional(v.string()), // Email dell'autore (cached)
|
|
@@ -710,16 +652,16 @@ export default defineSchema({
|
|
|
710
652
|
isFromApi: v.optional(v.boolean()), // Se true, commento ricevuto via API esterna
|
|
711
653
|
attachments: v.optional(v.array(v.id('_storage'))), // Allegati opzionali
|
|
712
654
|
mentions: v.optional(v.array(v.object({
|
|
713
|
-
userId: v.
|
|
655
|
+
userId: v.string(),
|
|
714
656
|
name: v.string(),
|
|
715
657
|
email: v.string(),
|
|
716
658
|
}))), // Utenti taggati nel commento
|
|
717
659
|
createdAt: v.number(),
|
|
718
660
|
updatedAt: v.optional(v.number()),
|
|
719
661
|
})
|
|
720
|
-
.index('
|
|
662
|
+
.index('by_ticketId', ['ticketId'])
|
|
721
663
|
.index('by_authorId', ['authorId'])
|
|
722
|
-
.index('
|
|
664
|
+
.index('by_ticketId_createdAt', ['ticketId', 'createdAt']),
|
|
723
665
|
|
|
724
666
|
// Spare part orders (Ordini ricambi)
|
|
725
667
|
spare_part_orders: defineTable({
|
|
@@ -786,7 +728,7 @@ export default defineSchema({
|
|
|
786
728
|
to: v.union(v.string(), v.array(v.string())),
|
|
787
729
|
subject: v.string(),
|
|
788
730
|
html: v.string(),
|
|
789
|
-
ticketId: v.optional(v.
|
|
731
|
+
ticketId: v.optional(v.string()),
|
|
790
732
|
status: v.union(
|
|
791
733
|
v.literal('sent'),
|
|
792
734
|
v.literal('failed'),
|
|
@@ -802,7 +744,7 @@ export default defineSchema({
|
|
|
802
744
|
|
|
803
745
|
// Ticket History (Storico eventi ticket)
|
|
804
746
|
ticket_history: defineTable({
|
|
805
|
-
ticketId: v.
|
|
747
|
+
ticketId: v.string(), // Abaddon ticket _id (string reference)
|
|
806
748
|
eventType: v.union(
|
|
807
749
|
v.literal('created'), // Ticket creato
|
|
808
750
|
v.literal('status_change'), // Cambio stato
|
|
@@ -1,125 +1,70 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import {
|
|
3
|
-
import { internal } from './_generated/api';
|
|
2
|
+
import { mutation, query } from './_generated/server';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const tickets: any = await ctx.runQuery(internal.slaMonitoring.listTicketsNeedingSLACheck, {});
|
|
16
|
-
|
|
17
|
-
const now = Date.now();
|
|
18
|
-
let warningsCount = 0;
|
|
19
|
-
let breachesCount = 0;
|
|
20
|
-
|
|
21
|
-
for (const ticket of tickets) {
|
|
22
|
-
const timeUntilDeadline = ticket.slaDeadline - now;
|
|
23
|
-
const oneHour = 60 * 60 * 1000;
|
|
24
|
-
|
|
25
|
-
if (timeUntilDeadline < 0 && !ticket.slaBreached) {
|
|
26
|
-
breachesCount++;
|
|
27
|
-
|
|
28
|
-
await ctx.runMutation(internal.slaMonitoring.markSLABreached, {
|
|
29
|
-
ticketId: ticket._id,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
else if (timeUntilDeadline > 0 && timeUntilDeadline < oneHour && !ticket.slaWarningSent) {
|
|
33
|
-
warningsCount++;
|
|
34
|
-
|
|
35
|
-
await ctx.runMutation(internal.slaMonitoring.markSLAWarningSent, {
|
|
36
|
-
ticketId: ticket._id,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
checked: tickets.length,
|
|
43
|
-
warnings: warningsCount,
|
|
44
|
-
breaches: breachesCount,
|
|
45
|
-
};
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.error('Error checking SLA status:', error);
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
4
|
+
export const evaluateSLA = query({
|
|
5
|
+
args: {
|
|
6
|
+
tickets: v.array(v.object({
|
|
7
|
+
ticketId: v.string(),
|
|
8
|
+
status: v.string(),
|
|
9
|
+
slaDeadline: v.optional(v.number()),
|
|
10
|
+
slaBreached: v.optional(v.boolean()),
|
|
11
|
+
slaWarningSent: v.optional(v.boolean()),
|
|
12
|
+
})),
|
|
50
13
|
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
args: {},
|
|
55
|
-
handler: async (ctx) => {
|
|
14
|
+
handler: async (ctx, args) => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
const oneHour = 60 * 60 * 1000;
|
|
56
17
|
const terminalStatuses = ['closed', 'completed', 'cancelled', 'resolved'];
|
|
57
18
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
.withIndex('by_slaDeadline')
|
|
61
|
-
.order('asc')
|
|
62
|
-
.take(150);
|
|
19
|
+
const warnings: string[] = [];
|
|
20
|
+
const breaches: string[] = [];
|
|
63
21
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.map(t => ({
|
|
67
|
-
_id: t._id,
|
|
68
|
-
ticketNumber: t.ticketNumber,
|
|
69
|
-
status: t.status,
|
|
70
|
-
slaDeadline: t.slaDeadline,
|
|
71
|
-
slaBreached: t.slaBreached,
|
|
72
|
-
slaWarningSent: t.slaWarningSent,
|
|
73
|
-
}));
|
|
74
|
-
},
|
|
75
|
-
});
|
|
22
|
+
for (const ticket of args.tickets) {
|
|
23
|
+
if (!ticket.slaDeadline || terminalStatuses.includes(ticket.status)) continue;
|
|
76
24
|
|
|
77
|
-
|
|
78
|
-
args: {},
|
|
79
|
-
handler: async (ctx) => {
|
|
80
|
-
const terminalStatuses = ['closed', 'completed', 'cancelled', 'resolved'];
|
|
25
|
+
const timeUntilDeadline = ticket.slaDeadline - now;
|
|
81
26
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
27
|
+
if (timeUntilDeadline < 0 && !ticket.slaBreached) {
|
|
28
|
+
breaches.push(ticket.ticketId);
|
|
29
|
+
} else if (timeUntilDeadline > 0 && timeUntilDeadline < oneHour && !ticket.slaWarningSent) {
|
|
30
|
+
warnings.push(ticket.ticketId);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
87
33
|
|
|
88
|
-
return
|
|
89
|
-
.filter(t => t.slaDeadline && !terminalStatuses.includes(t.status))
|
|
90
|
-
.map(t => ({
|
|
91
|
-
_id: t._id,
|
|
92
|
-
ticketNumber: t.ticketNumber,
|
|
93
|
-
status: t.status,
|
|
94
|
-
supplierId: t.supplierId,
|
|
95
|
-
slaDeadline: t.slaDeadline,
|
|
96
|
-
slaBreached: t.slaBreached,
|
|
97
|
-
slaWarningSent: t.slaWarningSent,
|
|
98
|
-
deviceName: '',
|
|
99
|
-
clinicName: '',
|
|
100
|
-
supplierName: null,
|
|
101
|
-
}));
|
|
34
|
+
return { warnings, breaches };
|
|
102
35
|
},
|
|
103
36
|
});
|
|
104
37
|
|
|
105
|
-
export const
|
|
38
|
+
export const computeSLADeadline = query({
|
|
106
39
|
args: {
|
|
107
|
-
|
|
40
|
+
supplierId: v.optional(v.id('suppliers')),
|
|
41
|
+
priority: v.optional(v.string()),
|
|
42
|
+
deviceCategory: v.optional(v.string()),
|
|
108
43
|
},
|
|
109
44
|
handler: async (ctx, args) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
45
|
+
if (!args.supplierId || !args.priority) return null;
|
|
46
|
+
|
|
47
|
+
const slaRule = await ctx.db
|
|
48
|
+
.query('sla_rules')
|
|
49
|
+
.withIndex('by_supplierId_category_priority', (q: any) =>
|
|
50
|
+
q.eq('supplierId', args.supplierId)
|
|
51
|
+
.eq('deviceCategory', args.deviceCategory || undefined)
|
|
52
|
+
.eq('priority', args.priority)
|
|
53
|
+
)
|
|
54
|
+
.first();
|
|
55
|
+
|
|
56
|
+
if (!slaRule) {
|
|
57
|
+
const genericRule = await ctx.db
|
|
58
|
+
.query('sla_rules')
|
|
59
|
+
.withIndex('by_supplierId_priority', (q: any) =>
|
|
60
|
+
q.eq('supplierId', args.supplierId).eq('priority', args.priority)
|
|
61
|
+
)
|
|
62
|
+
.first();
|
|
63
|
+
|
|
64
|
+
if (!genericRule) return null;
|
|
65
|
+
return Date.now() + (genericRule.resolutionTimeHours * 60 * 60 * 1000);
|
|
66
|
+
}
|
|
115
67
|
|
|
116
|
-
|
|
117
|
-
args: {
|
|
118
|
-
ticketId: v.id('maintenance_tasks'),
|
|
119
|
-
},
|
|
120
|
-
handler: async (ctx, args) => {
|
|
121
|
-
await ctx.db.patch(args.ticketId, {
|
|
122
|
-
slaWarningSent: true,
|
|
123
|
-
});
|
|
68
|
+
return Date.now() + (slaRule.resolutionTimeHours * 60 * 60 * 1000);
|
|
124
69
|
},
|
|
125
70
|
});
|
|
@@ -69,18 +69,49 @@ export const importSparePartsBatch = mutation({
|
|
|
69
69
|
|
|
70
70
|
export const listSpareParts = query({
|
|
71
71
|
args: {
|
|
72
|
-
|
|
72
|
+
page: v.optional(v.number()),
|
|
73
|
+
pageSize: v.optional(v.number()),
|
|
74
|
+
search: v.optional(v.string()),
|
|
75
|
+
clinicId: v.optional(v.number()),
|
|
76
|
+
userRole: v.optional(v.string()),
|
|
77
|
+
userSelectedClinicId: v.optional(v.number()),
|
|
73
78
|
_triggerReload: v.optional(v.number()),
|
|
74
79
|
},
|
|
75
80
|
handler: async (ctx, args) => {
|
|
76
|
-
const
|
|
81
|
+
const page = args.page || 1;
|
|
82
|
+
const pageSize = args.pageSize || 40;
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.order('desc')
|
|
81
|
-
.take(limit);
|
|
84
|
+
let allParts: any[];
|
|
85
|
+
const effectiveClinicId = args.clinicId || args.userSelectedClinicId;
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
if (effectiveClinicId) {
|
|
88
|
+
allParts = await ctx.db
|
|
89
|
+
.query('spare_parts')
|
|
90
|
+
.withIndex('by_clinicId', (q: any) => q.eq('clinicId', effectiveClinicId))
|
|
91
|
+
.collect();
|
|
92
|
+
} else {
|
|
93
|
+
allParts = await ctx.db
|
|
94
|
+
.query('spare_parts')
|
|
95
|
+
.order('desc')
|
|
96
|
+
.collect();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (args.search) {
|
|
100
|
+
const s = args.search.toLowerCase();
|
|
101
|
+
allParts = allParts.filter((p: any) =>
|
|
102
|
+
p.name?.toLowerCase().includes(s) ||
|
|
103
|
+
p.code?.toLowerCase().includes(s) ||
|
|
104
|
+
p.articleName?.toLowerCase().includes(s) ||
|
|
105
|
+
p.brand?.toLowerCase().includes(s)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const totalCount = allParts.length;
|
|
110
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
111
|
+
const startIndex = (page - 1) * pageSize;
|
|
112
|
+
const pageParts = allParts.slice(startIndex, startIndex + pageSize);
|
|
113
|
+
|
|
114
|
+
return { page: pageParts, totalCount, totalPages, currentPage: page };
|
|
84
115
|
},
|
|
85
116
|
});
|
|
86
117
|
|
|
@@ -94,7 +125,7 @@ export const listSparePartsByClinic = query({
|
|
|
94
125
|
|
|
95
126
|
const spareParts = await ctx.db
|
|
96
127
|
.query('spare_parts')
|
|
97
|
-
.withIndex('by_clinicId', (q) => q.eq('clinicId', args.clinicId))
|
|
128
|
+
.withIndex('by_clinicId', (q: any) => q.eq('clinicId', args.clinicId))
|
|
98
129
|
.take(limit);
|
|
99
130
|
|
|
100
131
|
return spareParts;
|
|
@@ -168,9 +199,12 @@ export const listSparePartsPaginated = query({
|
|
|
168
199
|
filteredQuery = filteredQuery.filter((q: any) => q.eq(q.field('articleCategoryId'), args.categoryId));
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
const
|
|
202
|
+
const limit = args.paginationOpts.numItems + 1;
|
|
203
|
+
const allResults = await filteredQuery.take(limit);
|
|
204
|
+
const isDone = allResults.length < limit;
|
|
205
|
+
const page = isDone ? allResults : allResults.slice(0, -1);
|
|
172
206
|
|
|
173
|
-
let filteredPage =
|
|
207
|
+
let filteredPage = page;
|
|
174
208
|
|
|
175
209
|
if (args.searchTerm) {
|
|
176
210
|
const search = args.searchTerm.toLowerCase();
|
|
@@ -199,8 +233,8 @@ export const listSparePartsPaginated = query({
|
|
|
199
233
|
|
|
200
234
|
return {
|
|
201
235
|
page: minimalPage,
|
|
202
|
-
isDone
|
|
203
|
-
continueCursor:
|
|
236
|
+
isDone,
|
|
237
|
+
continueCursor: '',
|
|
204
238
|
};
|
|
205
239
|
},
|
|
206
240
|
});
|