@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.
Files changed (150) hide show
  1. package/dist/client/index.d.ts +0 -2
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +2 -28
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +4 -4
  6. package/dist/component/_generated/api.d.ts.map +1 -1
  7. package/dist/component/_generated/component.d.ts +165 -218
  8. package/dist/component/_generated/component.d.ts.map +1 -1
  9. package/dist/component/contracts.d.ts +9 -9
  10. package/dist/component/contracts.d.ts.map +1 -1
  11. package/dist/component/contracts.js +7 -13
  12. package/dist/component/contracts.js.map +1 -1
  13. package/dist/component/crons.d.ts.map +1 -1
  14. package/dist/component/crons.js +1 -2
  15. package/dist/component/crons.js.map +1 -1
  16. package/dist/component/dashboardStats.d.ts +8 -3
  17. package/dist/component/dashboardStats.d.ts.map +1 -1
  18. package/dist/component/dashboardStats.js +24 -39
  19. package/dist/component/dashboardStats.js.map +1 -1
  20. package/dist/component/dashboardStatsCache.d.ts +5 -11
  21. package/dist/component/dashboardStatsCache.d.ts.map +1 -1
  22. package/dist/component/dashboardStatsCache.js +12 -53
  23. package/dist/component/dashboardStatsCache.js.map +1 -1
  24. package/dist/component/deviceCategories.d.ts +22 -15
  25. package/dist/component/deviceCategories.d.ts.map +1 -1
  26. package/dist/component/deviceCategories.js +10 -4
  27. package/dist/component/deviceCategories.js.map +1 -1
  28. package/dist/component/deviceQuestions.d.ts +36 -27
  29. package/dist/component/deviceQuestions.d.ts.map +1 -1
  30. package/dist/component/deviceQuestions.js +22 -5
  31. package/dist/component/deviceQuestions.js.map +1 -1
  32. package/dist/component/deviceRepairHistory.d.ts +3 -3
  33. package/dist/component/deviceRepairHistory.js +1 -1
  34. package/dist/component/deviceRepairHistory.js.map +1 -1
  35. package/dist/component/deviceStatus.d.ts +8 -57
  36. package/dist/component/deviceStatus.d.ts.map +1 -1
  37. package/dist/component/deviceStatus.js +32 -30
  38. package/dist/component/deviceStatus.js.map +1 -1
  39. package/dist/component/devices.d.ts +39 -22
  40. package/dist/component/devices.d.ts.map +1 -1
  41. package/dist/component/devices.js +85 -96
  42. package/dist/component/devices.js.map +1 -1
  43. package/dist/component/emailHelpers.d.ts +10 -3
  44. package/dist/component/emailHelpers.d.ts.map +1 -1
  45. package/dist/component/emailHelpers.js +9 -20
  46. package/dist/component/emailHelpers.js.map +1 -1
  47. package/dist/component/emails.d.ts +5 -5
  48. package/dist/component/emails.js +2 -2
  49. package/dist/component/emails.js.map +1 -1
  50. package/dist/component/http.d.ts.map +1 -1
  51. package/dist/component/http.js +3 -108
  52. package/dist/component/http.js.map +1 -1
  53. package/dist/component/migrationHelpers.d.ts +29 -0
  54. package/dist/component/migrationHelpers.d.ts.map +1 -0
  55. package/dist/component/migrationHelpers.js +84 -0
  56. package/dist/component/migrationHelpers.js.map +1 -0
  57. package/dist/component/roles.d.ts +1 -0
  58. package/dist/component/roles.d.ts.map +1 -1
  59. package/dist/component/roles.js +5 -6
  60. package/dist/component/roles.js.map +1 -1
  61. package/dist/component/schema.d.ts +69 -150
  62. package/dist/component/schema.d.ts.map +1 -1
  63. package/dist/component/schema.js +35 -88
  64. package/dist/component/schema.js.map +1 -1
  65. package/dist/component/slaMonitoring.d.ts +16 -30
  66. package/dist/component/slaMonitoring.d.ts.map +1 -1
  67. package/dist/component/slaMonitoring.js +48 -99
  68. package/dist/component/slaMonitoring.js.map +1 -1
  69. package/dist/component/spareParts.d.ts +11 -48
  70. package/dist/component/spareParts.d.ts.map +1 -1
  71. package/dist/component/spareParts.js +41 -11
  72. package/dist/component/spareParts.js.map +1 -1
  73. package/dist/component/suppliers.d.ts +38 -19
  74. package/dist/component/suppliers.d.ts.map +1 -1
  75. package/dist/component/suppliers.js +63 -44
  76. package/dist/component/suppliers.js.map +1 -1
  77. package/dist/component/ticketComments.d.ts +18 -12
  78. package/dist/component/ticketComments.d.ts.map +1 -1
  79. package/dist/component/ticketComments.js +28 -59
  80. package/dist/component/ticketComments.js.map +1 -1
  81. package/dist/component/ticketDeviceData.d.ts +63 -0
  82. package/dist/component/ticketDeviceData.d.ts.map +1 -0
  83. package/dist/component/ticketDeviceData.js +103 -0
  84. package/dist/component/ticketDeviceData.js.map +1 -0
  85. package/dist/component/ticketExport.d.ts +22 -40
  86. package/dist/component/ticketExport.d.ts.map +1 -1
  87. package/dist/component/ticketExport.js +43 -109
  88. package/dist/component/ticketExport.js.map +1 -1
  89. package/dist/component/ticketHistory.d.ts +4 -4
  90. package/dist/component/ticketHistory.d.ts.map +1 -1
  91. package/dist/component/ticketHistory.js +6 -9
  92. package/dist/component/ticketHistory.js.map +1 -1
  93. package/dist/component/ticketMacros.d.ts +19 -18
  94. package/dist/component/ticketMacros.d.ts.map +1 -1
  95. package/dist/component/ticketMacros.js +24 -30
  96. package/dist/component/ticketMacros.js.map +1 -1
  97. package/dist/component/ticketStatuses.d.ts +1 -0
  98. package/dist/component/ticketStatuses.d.ts.map +1 -1
  99. package/dist/component/ticketStatuses.js +5 -6
  100. package/dist/component/ticketStatuses.js.map +1 -1
  101. package/dist/component/ticketTriggers.d.ts +36 -16
  102. package/dist/component/ticketTriggers.d.ts.map +1 -1
  103. package/dist/component/ticketTriggers.js +115 -153
  104. package/dist/component/ticketTriggers.js.map +1 -1
  105. package/dist/component/userProfiles.d.ts +25 -120
  106. package/dist/component/userProfiles.d.ts.map +1 -1
  107. package/dist/component/userProfiles.js +73 -384
  108. package/dist/component/userProfiles.js.map +1 -1
  109. package/dist/test.d.ts +69 -150
  110. package/dist/test.d.ts.map +1 -1
  111. package/package.json +12 -3
  112. package/src/client/index.ts +2 -30
  113. package/src/component/_generated/api.ts +4 -4
  114. package/src/component/_generated/component.ts +228 -350
  115. package/src/component/contracts.ts +7 -14
  116. package/src/component/crons.ts +2 -7
  117. package/src/component/dashboardStats.ts +24 -41
  118. package/src/component/dashboardStatsCache.ts +12 -61
  119. package/src/component/deviceCategories.ts +12 -4
  120. package/src/component/deviceQuestions.ts +28 -5
  121. package/src/component/deviceRepairHistory.ts +1 -1
  122. package/src/component/deviceStatus.ts +43 -45
  123. package/src/component/devices.ts +87 -106
  124. package/src/component/emailHelpers.ts +9 -19
  125. package/src/component/emails.ts +2 -2
  126. package/src/component/http.ts +3 -108
  127. package/src/component/migrationHelpers.ts +96 -0
  128. package/src/component/roles.ts +5 -6
  129. package/src/component/schema.ts +35 -93
  130. package/src/component/slaMonitoring.ts +52 -107
  131. package/src/component/spareParts.ts +46 -12
  132. package/src/component/suppliers.ts +71 -48
  133. package/src/component/ticketComments.ts +28 -71
  134. package/src/component/ticketDeviceData.ts +113 -0
  135. package/src/component/ticketExport.ts +52 -137
  136. package/src/component/ticketHistory.ts +6 -9
  137. package/src/component/ticketMacros.ts +25 -37
  138. package/src/component/ticketStatuses.ts +5 -6
  139. package/src/component/ticketTriggers.ts +121 -217
  140. package/src/component/userProfiles.ts +67 -451
  141. package/dist/component/clinics.d.ts +0 -103
  142. package/dist/component/clinics.d.ts.map +0 -1
  143. package/dist/component/clinics.js +0 -126
  144. package/dist/component/clinics.js.map +0 -1
  145. package/dist/component/maintenanceTasks.d.ts +0 -733
  146. package/dist/component/maintenanceTasks.d.ts.map +0 -1
  147. package/dist/component/maintenanceTasks.js +0 -937
  148. package/dist/component/maintenanceTasks.js.map +0 -1
  149. package/src/component/clinics.ts +0 -136
  150. package/src/component/maintenanceTasks.ts +0 -1003
@@ -1,937 +0,0 @@
1
- import { v } from 'convex/values';
2
- import { query, mutation, internalQuery, internalMutation } from './_generated/server';
3
- import { api } from './_generated/api';
4
- import { paginationOptsValidator } from 'convex/server';
5
- export const listMaintenanceTasks = query({
6
- args: {
7
- limit: v.optional(v.number()),
8
- userRole: v.optional(v.string()),
9
- userAuth0Id: v.optional(v.string()),
10
- userEmail: v.optional(v.string()),
11
- userSupplierId: v.optional(v.id('suppliers')),
12
- userClinicId: v.optional(v.id('clinics')),
13
- userSelectedClinicId: v.optional(v.number()),
14
- },
15
- handler: async (ctx, args) => {
16
- const maxResults = args.limit || 100;
17
- let tasks;
18
- if (args.userRole === 'admin') {
19
- tasks = await ctx.db
20
- .query('maintenance_tasks')
21
- .order('desc')
22
- .take(maxResults);
23
- }
24
- else if (args.userRole === 'user') {
25
- let clinicTasks = [];
26
- if (args.userSelectedClinicId) {
27
- const clinic = await ctx.db
28
- .query('clinics')
29
- .filter((q) => q.eq(q.field('primoupId'), args.userSelectedClinicId.toString()))
30
- .first();
31
- if (clinic) {
32
- clinicTasks = await ctx.db
33
- .query('maintenance_tasks')
34
- .withIndex('by_clinicId', (q) => q.eq('clinicId', clinic._id))
35
- .order('desc')
36
- .take(maxResults);
37
- }
38
- }
39
- else if (args.userClinicId) {
40
- clinicTasks = await ctx.db
41
- .query('maintenance_tasks')
42
- .withIndex('by_clinicId', (q) => q.eq('clinicId', args.userClinicId))
43
- .order('desc')
44
- .take(maxResults);
45
- }
46
- const createdByUser = clinicTasks.filter(t => t.created_by === args.userAuth0Id || t.createdByEmail === args.userEmail);
47
- const taskMap = new Map();
48
- clinicTasks.forEach(t => taskMap.set(t._id.toString(), t));
49
- createdByUser.forEach(t => taskMap.set(t._id.toString(), t));
50
- tasks = Array.from(taskMap.values());
51
- }
52
- else if (args.userRole === 'supplier' && args.userSupplierId) {
53
- tasks = await ctx.db
54
- .query('maintenance_tasks')
55
- .withIndex('by_supplierId', (q) => q.eq('supplierId', args.userSupplierId))
56
- .order('desc')
57
- .take(maxResults);
58
- }
59
- else {
60
- tasks = [];
61
- }
62
- const clinicCache = new Map();
63
- const supplierCache = new Map();
64
- const deviceCache = new Map();
65
- const tasksWithDetails = await Promise.all(tasks.map(async (task) => {
66
- let device = null;
67
- if (task.deviceId) {
68
- const did = task.deviceId.toString();
69
- if (deviceCache.has(did)) {
70
- device = deviceCache.get(did);
71
- }
72
- else {
73
- device = await ctx.db.get(task.deviceId);
74
- deviceCache.set(did, device);
75
- }
76
- }
77
- const cid = task.clinicId.toString();
78
- let clinic;
79
- if (clinicCache.has(cid)) {
80
- clinic = clinicCache.get(cid);
81
- }
82
- else {
83
- clinic = await ctx.db.get(task.clinicId);
84
- clinicCache.set(cid, clinic);
85
- }
86
- let supplier = null;
87
- if (task.supplierId) {
88
- const sid = task.supplierId.toString();
89
- if (supplierCache.has(sid)) {
90
- supplier = supplierCache.get(sid);
91
- }
92
- else {
93
- supplier = await ctx.db.get(task.supplierId);
94
- supplierCache.set(sid, supplier);
95
- }
96
- }
97
- return {
98
- ...task,
99
- deviceName: device?.name || 'Nessun dispositivo',
100
- deviceBrand: device?.brand || '',
101
- deviceModel: device?.model || '',
102
- clinicName: clinic?.name || 'Unknown',
103
- supplierName: supplier?.name || null,
104
- };
105
- }));
106
- tasksWithDetails.sort((a, b) => b.created_at - a.created_at);
107
- return tasksWithDetails;
108
- },
109
- });
110
- export const listMaintenanceTasksPaginated = query({
111
- args: {
112
- paginationOpts: paginationOptsValidator,
113
- status: v.optional(v.string()),
114
- priority: v.optional(v.union(v.literal('low'), v.literal('medium'), v.literal('high'))),
115
- searchTerm: v.optional(v.string()),
116
- clinicId: v.optional(v.id('clinics')),
117
- supplierId: v.optional(v.id('suppliers')),
118
- deviceCategory: v.optional(v.string()),
119
- userRole: v.optional(v.string()),
120
- userSupplierId: v.optional(v.id('suppliers')),
121
- userClinicId: v.optional(v.id('clinics')),
122
- userSelectedClinicId: v.optional(v.number()),
123
- },
124
- handler: async (ctx, args) => {
125
- let baseQuery;
126
- if (args.userRole === 'admin') {
127
- if (args.clinicId) {
128
- baseQuery = ctx.db
129
- .query('maintenance_tasks')
130
- .withIndex('by_clinicId', (q) => q.eq('clinicId', args.clinicId));
131
- }
132
- else if (args.supplierId) {
133
- baseQuery = ctx.db
134
- .query('maintenance_tasks')
135
- .withIndex('by_supplierId', (q) => q.eq('supplierId', args.supplierId));
136
- }
137
- else if (args.status) {
138
- baseQuery = ctx.db
139
- .query('maintenance_tasks')
140
- .withIndex('by_status', (q) => q.eq('status', args.status));
141
- }
142
- else if (args.priority) {
143
- baseQuery = ctx.db
144
- .query('maintenance_tasks')
145
- .withIndex('by_priority', (q) => q.eq('priority', args.priority));
146
- }
147
- else {
148
- baseQuery = ctx.db
149
- .query('maintenance_tasks')
150
- .withIndex('by_created_at');
151
- }
152
- }
153
- else if (args.userRole === 'user') {
154
- let clinicConvexId = null;
155
- if (args.userSelectedClinicId) {
156
- const clinic = await ctx.db
157
- .query('clinics')
158
- .filter((q) => q.eq(q.field('primoupId'), args.userSelectedClinicId.toString()))
159
- .first();
160
- clinicConvexId = clinic?._id || null;
161
- }
162
- else if (args.userClinicId) {
163
- clinicConvexId = args.userClinicId;
164
- }
165
- if (!clinicConvexId) {
166
- return { page: [], isDone: true, continueCursor: '' };
167
- }
168
- baseQuery = ctx.db
169
- .query('maintenance_tasks')
170
- .withIndex('by_clinicId', (q) => q.eq('clinicId', clinicConvexId));
171
- }
172
- else if (args.userRole === 'supplier' && args.userSupplierId) {
173
- baseQuery = ctx.db
174
- .query('maintenance_tasks')
175
- .withIndex('by_supplierId', (q) => q.eq('supplierId', args.userSupplierId));
176
- }
177
- else {
178
- return { page: [], isDone: true, continueCursor: '' };
179
- }
180
- let orderedQuery = baseQuery.order('desc');
181
- if (args.status) {
182
- const isStatusIndexed = args.userRole === 'admin' && !args.clinicId && !args.supplierId && !args.priority;
183
- if (!isStatusIndexed) {
184
- orderedQuery = orderedQuery.filter((q) => q.eq(q.field('status'), args.status));
185
- }
186
- }
187
- if (args.priority) {
188
- const isPriorityIndexed = args.userRole === 'admin' && !args.clinicId && !args.supplierId && !args.status;
189
- if (!isPriorityIndexed) {
190
- orderedQuery = orderedQuery.filter((q) => q.eq(q.field('priority'), args.priority));
191
- }
192
- }
193
- const result = await orderedQuery.paginate(args.paginationOpts);
194
- let filteredPage = result.page;
195
- if (args.searchTerm) {
196
- const search = args.searchTerm.toLowerCase();
197
- filteredPage = filteredPage.filter((task) => task.ticketNumber?.toLowerCase().includes(search) ||
198
- task.title?.toLowerCase().includes(search) ||
199
- task.description?.toLowerCase().includes(search));
200
- }
201
- const enrichedPage = await Promise.all(filteredPage.map(async (task) => {
202
- let device = null;
203
- if (task.deviceId) {
204
- device = await ctx.db.get(task.deviceId);
205
- }
206
- if (args.deviceCategory && device && device.category !== args.deviceCategory) {
207
- return null;
208
- }
209
- const clinic = await ctx.db.get(task.clinicId);
210
- return {
211
- _id: task._id,
212
- ticketNumber: task.ticketNumber,
213
- title: task.title || task.description?.substring(0, 50),
214
- status: task.status,
215
- priority: task.priority,
216
- created_at: task.created_at,
217
- slaDeadline: task.slaDeadline,
218
- deviceName: device?.name || 'N/A',
219
- deviceCategory: device?.category || null,
220
- clinicName: clinic?.name || 'Unknown',
221
- isSlaBreached: task.slaDeadline ? Date.now() > task.slaDeadline : false,
222
- };
223
- }));
224
- const finalPage = enrichedPage.filter(Boolean);
225
- return {
226
- page: finalPage,
227
- isDone: result.isDone,
228
- continueCursor: result.continueCursor,
229
- };
230
- },
231
- });
232
- export const getMaintenanceTasksCount = query({
233
- args: {
234
- status: v.optional(v.string()),
235
- priority: v.optional(v.union(v.literal('low'), v.literal('medium'), v.literal('high'))),
236
- clinicId: v.optional(v.id('clinics')),
237
- supplierId: v.optional(v.id('suppliers')),
238
- userRole: v.optional(v.string()),
239
- userSupplierId: v.optional(v.id('suppliers')),
240
- userClinicId: v.optional(v.id('clinics')),
241
- userSelectedClinicId: v.optional(v.number()),
242
- },
243
- handler: async (ctx, args) => {
244
- let query;
245
- if (args.userRole === 'admin') {
246
- if (args.clinicId) {
247
- query = ctx.db.query('maintenance_tasks')
248
- .withIndex('by_clinicId', (q) => q.eq('clinicId', args.clinicId));
249
- }
250
- else if (args.supplierId) {
251
- query = ctx.db.query('maintenance_tasks')
252
- .withIndex('by_supplierId', (q) => q.eq('supplierId', args.supplierId));
253
- }
254
- else {
255
- query = ctx.db.query('maintenance_tasks');
256
- }
257
- }
258
- else if (args.userRole === 'user') {
259
- let clinicConvexId = null;
260
- if (args.userSelectedClinicId) {
261
- const clinic = await ctx.db
262
- .query('clinics')
263
- .filter((q) => q.eq(q.field('primoupId'), args.userSelectedClinicId.toString()))
264
- .first();
265
- clinicConvexId = clinic?._id || null;
266
- }
267
- else if (args.userClinicId) {
268
- clinicConvexId = args.userClinicId;
269
- }
270
- if (!clinicConvexId)
271
- return { total: 0 };
272
- query = ctx.db.query('maintenance_tasks')
273
- .withIndex('by_clinicId', (q) => q.eq('clinicId', clinicConvexId));
274
- }
275
- else if (args.userRole === 'supplier' && args.userSupplierId) {
276
- query = ctx.db.query('maintenance_tasks')
277
- .withIndex('by_supplierId', (q) => q.eq('supplierId', args.userSupplierId));
278
- }
279
- else {
280
- return { total: 0 };
281
- }
282
- if (args.status) {
283
- query = query.filter((q) => q.eq(q.field('status'), args.status));
284
- }
285
- if (args.priority) {
286
- query = query.filter((q) => q.eq(q.field('priority'), args.priority));
287
- }
288
- const tasks = await query.take(5000);
289
- return { total: tasks.length };
290
- },
291
- });
292
- export const getMaintenanceTask = query({
293
- args: { taskId: v.id('maintenance_tasks') },
294
- handler: async (ctx, args) => {
295
- const task = await ctx.db.get(args.taskId);
296
- if (!task) {
297
- throw new Error('Task not found');
298
- }
299
- const device = task.deviceId ? await ctx.db.get(task.deviceId) : null;
300
- const clinic = await ctx.db.get(task.clinicId);
301
- const supplier = task.supplierId ? await ctx.db.get(task.supplierId) : null;
302
- return {
303
- ...task,
304
- deviceName: device?.name || 'Nessun dispositivo',
305
- clinicName: clinic?.name || 'Unknown',
306
- supplierName: supplier?.name || null,
307
- };
308
- },
309
- });
310
- export const createMaintenanceTask = mutation({
311
- args: {
312
- deviceId: v.id('devices'),
313
- description: v.string(),
314
- photos: v.optional(v.array(v.string())),
315
- photoStorageIds: v.optional(v.array(v.id('_storage'))),
316
- clinicId: v.optional(v.id('clinics')),
317
- customFields: v.optional(v.any()),
318
- deviceQuestionAnswers: v.optional(v.array(v.object({
319
- questionId: v.id('device_questions'),
320
- question: v.string(),
321
- answer: v.any(),
322
- }))),
323
- createdBy: v.string(),
324
- createdByEmail: v.optional(v.string()),
325
- userRole: v.optional(v.string()),
326
- userSelectedClinicId: v.optional(v.number()),
327
- userClinicId: v.optional(v.id('clinics')),
328
- },
329
- handler: async (ctx, args) => {
330
- const device = await ctx.db.get(args.deviceId);
331
- if (!device) {
332
- throw new Error('Device not found');
333
- }
334
- if (args.userRole === 'user') {
335
- if (args.userSelectedClinicId) {
336
- const clinic = await ctx.db
337
- .query('clinics')
338
- .filter((q) => q.eq(q.field('primoupId'), args.userSelectedClinicId.toString()))
339
- .first();
340
- if (!clinic || clinic._id !== device.clinicId) {
341
- throw new Error('Not authorized to create task for this device');
342
- }
343
- }
344
- else if (args.userClinicId && args.userClinicId !== device.clinicId) {
345
- throw new Error('Not authorized to create task for this device');
346
- }
347
- }
348
- const lastTask = await ctx.db
349
- .query('maintenance_tasks')
350
- .withIndex('by_ticketNumber')
351
- .order('desc')
352
- .first();
353
- let maxNumber = 0;
354
- if (lastTask?.ticketNumber) {
355
- const parsed = parseInt(lastTask.ticketNumber.replace('TICK-', ''));
356
- if (!isNaN(parsed))
357
- maxNumber = parsed;
358
- }
359
- const ticketNumber = `TICK-${String(maxNumber + 1).padStart(5, '0')}`;
360
- const supplierId = device.supplierId || undefined;
361
- const initialStatus = 'open';
362
- const title = `Segnalazione: ${device.name} - ${device.brand} ${device.model}`;
363
- const taskId = await ctx.db.insert('maintenance_tasks', {
364
- ticketNumber,
365
- title,
366
- deviceId: args.deviceId,
367
- clinicId: device.clinicId,
368
- supplierId: supplierId,
369
- created_by: args.createdBy,
370
- createdByEmail: args.createdByEmail,
371
- description: args.description.trim(),
372
- photos: args.photos || [],
373
- photoStorageIds: args.photoStorageIds || undefined,
374
- status: initialStatus,
375
- customFields: args.customFields,
376
- deviceQuestionAnswers: args.deviceQuestionAnswers,
377
- created_at: Date.now(),
378
- updated_at: Date.now(),
379
- });
380
- try {
381
- await ctx.runMutation(api.ticketHistory.recordHistoryEvent, {
382
- ticketId: taskId,
383
- eventType: 'created',
384
- newValue: initialStatus,
385
- notes: `Ticket creato per ${device.name} - ${device.brand} ${device.model}`,
386
- });
387
- if (supplierId) {
388
- const supplier = await ctx.db.get(supplierId);
389
- await ctx.runMutation(api.ticketHistory.recordHistoryEvent, {
390
- ticketId: taskId,
391
- eventType: 'assignee_change',
392
- newSupplierId: supplierId,
393
- newSupplierName: supplier?.name || 'Fornitore',
394
- notes: 'Assegnazione automatica dal dispositivo',
395
- isSystemAction: true,
396
- });
397
- }
398
- }
399
- catch (error) {
400
- console.error('Error recording history:', error);
401
- }
402
- await ctx.db.patch(args.deviceId, {
403
- status: 'out_of_service',
404
- });
405
- try {
406
- await ctx.runMutation(api.ticketTriggers.executeTriggers, {
407
- ticketId: taskId,
408
- triggerOn: 'create',
409
- });
410
- }
411
- catch (error) {
412
- console.error('Error executing triggers:', error);
413
- }
414
- return taskId;
415
- },
416
- });
417
- export const updateMaintenanceTaskSupplier = mutation({
418
- args: {
419
- taskId: v.id('maintenance_tasks'),
420
- supplierId: v.optional(v.id('suppliers')),
421
- },
422
- handler: async (ctx, args) => {
423
- const task = await ctx.db.get(args.taskId);
424
- if (!task) {
425
- throw new Error('Task not found');
426
- }
427
- const oldSupplier = task.supplierId ? await ctx.db.get(task.supplierId) : null;
428
- const newSupplier = args.supplierId ? await ctx.db.get(args.supplierId) : null;
429
- await ctx.db.patch(args.taskId, {
430
- supplierId: args.supplierId,
431
- needsAssignment: args.supplierId ? false : true,
432
- updated_at: Date.now(),
433
- });
434
- try {
435
- await ctx.runMutation(api.ticketHistory.recordHistoryEvent, {
436
- ticketId: args.taskId,
437
- eventType: 'assignee_change',
438
- oldSupplierId: task.supplierId,
439
- newSupplierId: args.supplierId,
440
- oldSupplierName: oldSupplier?.name,
441
- newSupplierName: newSupplier?.name,
442
- });
443
- }
444
- catch (error) {
445
- console.error('Error recording history:', error);
446
- }
447
- return { success: true };
448
- },
449
- });
450
- export const reassignTicketToSupplier = mutation({
451
- args: {
452
- taskId: v.id('maintenance_tasks'),
453
- newSupplierId: v.id('suppliers'),
454
- userSupplierId: v.optional(v.id('suppliers')),
455
- },
456
- handler: async (ctx, args) => {
457
- const task = await ctx.db.get(args.taskId);
458
- if (!task) {
459
- throw new Error('Task not found');
460
- }
461
- if (args.userSupplierId && task.supplierId !== args.userSupplierId) {
462
- throw new Error('You can only reassign tickets assigned to you');
463
- }
464
- const oldSupplier = args.userSupplierId ? await ctx.db.get(args.userSupplierId) : null;
465
- const newSupplier = await ctx.db.get(args.newSupplierId);
466
- if (!newSupplier) {
467
- throw new Error('New supplier not found');
468
- }
469
- await ctx.db.patch(args.taskId, {
470
- supplierId: args.newSupplierId,
471
- updated_at: Date.now(),
472
- });
473
- try {
474
- await ctx.runMutation(api.ticketHistory.recordHistoryEvent, {
475
- ticketId: args.taskId,
476
- eventType: 'assignee_change',
477
- oldSupplierId: args.userSupplierId,
478
- newSupplierId: args.newSupplierId,
479
- oldSupplierName: oldSupplier?.name,
480
- newSupplierName: newSupplier.name,
481
- notes: 'Riassegnato dal fornitore',
482
- });
483
- }
484
- catch (error) {
485
- console.error('Error recording history:', error);
486
- }
487
- const commentContent = `Riassegnato da ${oldSupplier?.name || 'Fornitore'} a ${newSupplier.name}`;
488
- await ctx.db.insert('ticket_comments', {
489
- taskId: args.taskId,
490
- authorId: 'system',
491
- authorName: 'Sistema',
492
- authorEmail: 'system@app',
493
- authorRole: 'api',
494
- content: commentContent,
495
- isInternal: false,
496
- isFromApi: true,
497
- createdAt: Date.now(),
498
- });
499
- return { success: true };
500
- },
501
- });
502
- export const updateMaintenanceTaskStatus = mutation({
503
- args: {
504
- taskId: v.id('maintenance_tasks'),
505
- status: v.string(),
506
- notes: v.optional(v.string()),
507
- userRole: v.optional(v.string()),
508
- userSupplierId: v.optional(v.id('suppliers')),
509
- userClinicId: v.optional(v.id('clinics')),
510
- userEmail: v.optional(v.string()),
511
- userName: v.optional(v.string()),
512
- userAuth0Id: v.optional(v.string()),
513
- },
514
- handler: async (ctx, args) => {
515
- const task = await ctx.db.get(args.taskId);
516
- if (!task) {
517
- throw new Error('Task not found');
518
- }
519
- console.log('🔍 updateMaintenanceTaskStatus - DEBUG:', {
520
- userRole: args.userRole,
521
- userSupplierId: args.userSupplierId,
522
- taskSupplierId: task.supplierId,
523
- match: args.userSupplierId === task.supplierId,
524
- requestedStatus: args.status,
525
- });
526
- const statusExists = await ctx.db
527
- .query('ticket_statuses')
528
- .withIndex('by_name', (q) => q.eq('name', args.status))
529
- .first();
530
- if (!statusExists) {
531
- console.error('❌ Status not found in ticket_statuses:', args.status);
532
- throw new Error('Invalid status');
533
- }
534
- if (args.userRole === 'admin') {
535
- console.log('✅ Admin permission granted');
536
- }
537
- else if (args.userRole === 'supplier' && args.userSupplierId === task.supplierId) {
538
- console.log('✅ Supplier permission granted');
539
- }
540
- else if (args.userRole === 'user') {
541
- const isOwnClinic = args.userClinicId === task.clinicId;
542
- if (isOwnClinic) {
543
- console.log('✅ Clinic user permission granted (same clinic)');
544
- }
545
- else {
546
- console.error('❌ Permission denied (clinic user, wrong clinic):', {
547
- userClinicId: args.userClinicId,
548
- taskClinicId: task.clinicId,
549
- });
550
- throw new Error('Not authorized to update this task');
551
- }
552
- }
553
- else {
554
- console.error('❌ Permission denied:', {
555
- roleName: args.userRole,
556
- userSupplierId: args.userSupplierId,
557
- taskSupplierId: task.supplierId,
558
- });
559
- throw new Error('Not authorized to update this task');
560
- }
561
- const updates = {
562
- status: args.status,
563
- updated_at: Date.now(),
564
- };
565
- if (args.notes) {
566
- updates.notes = args.notes;
567
- }
568
- const closedStatuses = ['completed', 'cancelled', 'closed'];
569
- if (closedStatuses.includes(args.status) && !task.closed_at) {
570
- updates.closed_at = Date.now();
571
- }
572
- if (!closedStatuses.includes(args.status) && task.closed_at) {
573
- updates.closed_at = undefined;
574
- }
575
- await ctx.db.patch(args.taskId, updates);
576
- const updatedTask = await ctx.db.get(args.taskId);
577
- console.log('✅ Task updated successfully:', {
578
- taskId: args.taskId,
579
- oldStatus: task.status,
580
- newStatus: updatedTask?.status,
581
- closed_at: updatedTask?.closed_at,
582
- updateApplied: updatedTask?.status === args.status,
583
- });
584
- if (task.status !== args.status) {
585
- try {
586
- await ctx.runMutation(api.ticketHistory.recordHistoryEvent, {
587
- ticketId: args.taskId,
588
- eventType: 'status_change',
589
- oldValue: task.status,
590
- newValue: args.status,
591
- });
592
- }
593
- catch (error) {
594
- console.error('Error recording history:', error);
595
- }
596
- }
597
- const device = task.deviceId ? await ctx.db.get(task.deviceId) : null;
598
- if (device) {
599
- let newDeviceStatus = device.status;
600
- if (args.status === 'in_progress') {
601
- newDeviceStatus = 'in_maintenance';
602
- }
603
- else if (args.status === 'completed') {
604
- newDeviceStatus = 'active';
605
- }
606
- else if (args.status === 'cancelled') {
607
- newDeviceStatus = 'active';
608
- }
609
- if (newDeviceStatus !== device.status && task.deviceId) {
610
- await ctx.db.patch(task.deviceId, {
611
- status: newDeviceStatus,
612
- });
613
- if (newDeviceStatus === 'in_maintenance') {
614
- await ctx.db.insert('device_repair_history', {
615
- deviceId: task.deviceId,
616
- clinicId: task.clinicId,
617
- ticketId: args.taskId,
618
- sentToRepairAt: Date.now(),
619
- createdBy: args.userEmail || args.userAuth0Id || 'system',
620
- });
621
- }
622
- if (newDeviceStatus === 'active') {
623
- const openRepairs = await ctx.db
624
- .query('device_repair_history')
625
- .withIndex('by_deviceId', (q) => q.eq('deviceId', task.deviceId))
626
- .collect();
627
- const openRepair = openRepairs.find((r) => !r.returnedAt);
628
- if (openRepair) {
629
- await ctx.db.patch(openRepair._id, {
630
- returnedAt: Date.now(),
631
- statusAfterRepair: 'active',
632
- returnedBy: args.userEmail || args.userAuth0Id || 'system',
633
- });
634
- }
635
- }
636
- }
637
- }
638
- if (task.status !== args.status) {
639
- console.log('🎯 Executing triggers for status change...');
640
- try {
641
- await ctx.runMutation(api.ticketTriggers.executeTriggers, {
642
- ticketId: args.taskId,
643
- triggerOn: 'status_change',
644
- oldStatus: task.status,
645
- });
646
- console.log('✅ Triggers executed successfully');
647
- }
648
- catch (error) {
649
- console.error('❌ Error executing triggers:', error);
650
- }
651
- const taskAfterTriggers = await ctx.db.get(args.taskId);
652
- console.log('📊 Task after triggers:', {
653
- status: taskAfterTriggers?.status,
654
- expectedStatus: args.status,
655
- statusMatch: taskAfterTriggers?.status === args.status,
656
- });
657
- }
658
- return { success: true };
659
- },
660
- });
661
- export const addMaintenanceTaskPhotos = mutation({
662
- args: {
663
- taskId: v.id('maintenance_tasks'),
664
- photos: v.array(v.string()),
665
- },
666
- handler: async (ctx, args) => {
667
- const task = await ctx.db.get(args.taskId);
668
- if (!task) {
669
- throw new Error('Task not found');
670
- }
671
- const updatedPhotos = [...task.photos, ...args.photos];
672
- await ctx.db.patch(args.taskId, {
673
- photos: updatedPhotos,
674
- updated_at: Date.now(),
675
- });
676
- return args.taskId;
677
- },
678
- });
679
- export const generatePhotoUploadUrl = mutation({
680
- args: {},
681
- handler: async (ctx) => {
682
- return await ctx.storage.generateUploadUrl();
683
- },
684
- });
685
- export const getTaskPhotoUrls = query({
686
- args: { taskId: v.id('maintenance_tasks') },
687
- handler: async (ctx, args) => {
688
- const task = await ctx.db.get(args.taskId);
689
- if (!task)
690
- return [];
691
- const urls = [];
692
- if (task.photoStorageIds && task.photoStorageIds.length > 0) {
693
- for (const storageId of task.photoStorageIds) {
694
- try {
695
- const url = await ctx.storage.getUrl(storageId);
696
- if (url) {
697
- urls.push({ storageId: storageId, url });
698
- }
699
- }
700
- catch {
701
- // Skip invalid storage IDs
702
- }
703
- }
704
- }
705
- return urls;
706
- },
707
- });
708
- export const getMaintenanceTaskStats = query({
709
- args: {
710
- userRole: v.optional(v.string()),
711
- userClinicId: v.optional(v.id('clinics')),
712
- userSupplierId: v.optional(v.id('suppliers')),
713
- },
714
- handler: async (ctx, args) => {
715
- try {
716
- const statuses = await ctx.db
717
- .query('ticket_statuses')
718
- .withIndex('by_isActive', (q) => q.eq('isActive', true))
719
- .collect();
720
- const stats = { total: 0 };
721
- if (args.userRole === 'admin') {
722
- const cache = await ctx.db.query('dashboard_stats_cache').first();
723
- if (cache?.ticketStats && cache.lastUpdated) {
724
- const ageMs = Date.now() - cache.lastUpdated;
725
- if (ageMs < 15 * 60 * 1000) {
726
- Object.assign(stats, cache.ticketStats);
727
- stats.total = cache.totalTickets;
728
- return stats;
729
- }
730
- }
731
- for (const status of statuses) {
732
- const count = (await ctx.db
733
- .query('maintenance_tasks')
734
- .withIndex('by_status', (q) => q.eq('status', status.name))
735
- .take(500)).length;
736
- stats[status.name] = count;
737
- stats.total += count;
738
- }
739
- }
740
- else if (args.userRole === 'user' && args.userClinicId) {
741
- const tasks = await ctx.db
742
- .query('maintenance_tasks')
743
- .withIndex('by_clinicId', (q) => q.eq('clinicId', args.userClinicId))
744
- .take(800);
745
- stats.total = tasks.length;
746
- for (const status of statuses) {
747
- stats[status.name] = tasks.filter((t) => t.status === status.name).length;
748
- }
749
- }
750
- else if (args.userRole === 'supplier' && args.userSupplierId) {
751
- const tasks = await ctx.db
752
- .query('maintenance_tasks')
753
- .withIndex('by_supplierId', (q) => q.eq('supplierId', args.userSupplierId))
754
- .take(800);
755
- stats.total = tasks.length;
756
- for (const status of statuses) {
757
- stats[status.name] = tasks.filter((t) => t.status === status.name).length;
758
- }
759
- }
760
- return stats;
761
- }
762
- catch (error) {
763
- console.error('❌ Error in getMaintenanceTaskStats:', error);
764
- return { total: 0 };
765
- }
766
- },
767
- });
768
- export const deleteMaintenanceTask = mutation({
769
- args: { taskId: v.id('maintenance_tasks') },
770
- handler: async (ctx, args) => {
771
- await ctx.db.delete(args.taskId);
772
- return { success: true };
773
- },
774
- });
775
- export const createExternalTicket = internalMutation({
776
- args: {
777
- clinicId: v.id('clinics'),
778
- description: v.string(),
779
- title: v.optional(v.string()),
780
- externalTicketId: v.optional(v.string()),
781
- externalTicketNumber: v.optional(v.number()),
782
- creatorEmail: v.optional(v.string()),
783
- creatorName: v.optional(v.string()),
784
- priority: v.optional(v.union(v.literal('low'), v.literal('medium'), v.literal('high'))),
785
- },
786
- handler: async (ctx, args) => {
787
- const lastTask = await ctx.db
788
- .query('maintenance_tasks')
789
- .withIndex('by_ticketNumber')
790
- .order('desc')
791
- .first();
792
- let maxNumber = 0;
793
- if (lastTask?.ticketNumber) {
794
- const parsed = parseInt(lastTask.ticketNumber.replace('TICK-', ''));
795
- if (!isNaN(parsed))
796
- maxNumber = parsed;
797
- }
798
- const ticketNumber = `TICK-${String(maxNumber + 1).padStart(5, '0')}`;
799
- const clinic = await ctx.db.get(args.clinicId);
800
- const clinicName = clinic?.name || 'Clinica sconosciuta';
801
- const title = args.title || `[ESTERNO] Segnalazione da ${clinicName}`;
802
- const taskId = await ctx.db.insert('maintenance_tasks', {
803
- ticketNumber,
804
- title,
805
- deviceId: undefined,
806
- clinicId: args.clinicId,
807
- supplierId: undefined,
808
- created_by: 'external_api',
809
- createdByEmail: args.creatorEmail,
810
- description: args.description.trim(),
811
- photos: [],
812
- status: 'open',
813
- priority: args.priority || 'medium',
814
- externalTicketId: args.externalTicketId,
815
- externalTicketNumber: args.externalTicketNumber,
816
- importSource: 'external_api',
817
- isExternal: true,
818
- needsAssignment: true,
819
- created_at: Date.now(),
820
- updated_at: Date.now(),
821
- });
822
- try {
823
- await ctx.runMutation(api.ticketHistory.recordHistoryEvent, {
824
- ticketId: taskId,
825
- eventType: 'created',
826
- newValue: 'open',
827
- notes: `Ticket esterno creato da API per ${clinicName}`,
828
- isSystemAction: true,
829
- });
830
- }
831
- catch (error) {
832
- console.error('Error recording history:', error);
833
- }
834
- return taskId;
835
- },
836
- });
837
- export const getTicketInternal = internalQuery({
838
- args: { taskId: v.string() },
839
- handler: async (ctx, args) => {
840
- let task;
841
- try {
842
- task = await ctx.db.get(args.taskId);
843
- }
844
- catch {
845
- return null;
846
- }
847
- if (!task) {
848
- return null;
849
- }
850
- const device = task.deviceId ? await ctx.db.get(task.deviceId) : null;
851
- const clinic = await ctx.db.get(task.clinicId);
852
- const supplier = task.supplierId ? await ctx.db.get(task.supplierId) : null;
853
- return {
854
- _id: task._id,
855
- title: task.title,
856
- description: task.description,
857
- status: task.status,
858
- priority: task.priority,
859
- deviceId: task.deviceId,
860
- deviceName: device?.name || 'Unknown',
861
- deviceBrand: device?.brand || '',
862
- deviceModel: device?.model || '',
863
- clinicId: task.clinicId,
864
- clinicName: clinic?.name || 'Unknown',
865
- supplierId: task.supplierId,
866
- supplierName: supplier?.name || null,
867
- created_at: task.created_at,
868
- updated_at: task.updated_at,
869
- closed_at: task.closed_at,
870
- };
871
- },
872
- });
873
- export const findClinicInternal = internalQuery({
874
- args: {
875
- clinicId: v.optional(v.string()),
876
- primoupId: v.optional(v.string()),
877
- clinicName: v.optional(v.string()),
878
- },
879
- handler: async (ctx, args) => {
880
- if (args.clinicId) {
881
- try {
882
- const clinic = await ctx.db.get(args.clinicId);
883
- if (clinic)
884
- return clinic;
885
- }
886
- catch {
887
- // Invalid ID format, continue
888
- }
889
- }
890
- if (args.primoupId) {
891
- const clinic = await ctx.db
892
- .query('clinics')
893
- .withIndex('by_primoupId', (q) => q.eq('primoupId', args.primoupId))
894
- .first();
895
- if (clinic)
896
- return clinic;
897
- }
898
- if (args.clinicName) {
899
- const clinics = await ctx.db.query('clinics').take(500);
900
- const clinic = clinics.find(c => c.name.toLowerCase() === args.clinicName.toLowerCase());
901
- if (clinic)
902
- return clinic;
903
- }
904
- return null;
905
- },
906
- });
907
- export const getTicketForNotification = internalQuery({
908
- args: { taskId: v.id('maintenance_tasks') },
909
- handler: async (ctx, args) => {
910
- const task = await ctx.db.get(args.taskId);
911
- if (!task)
912
- return null;
913
- const device = task.deviceId ? await ctx.db.get(task.deviceId) : null;
914
- const clinic = await ctx.db.get(task.clinicId);
915
- const supplier = task.supplierId ? await ctx.db.get(task.supplierId) : null;
916
- return {
917
- _id: task._id,
918
- ticketNumber: task.ticketNumber,
919
- title: task.title,
920
- description: task.description,
921
- status: task.status,
922
- priority: task.priority,
923
- deviceId: task.deviceId,
924
- deviceName: device?.name || 'Nessun dispositivo',
925
- deviceBrand: device?.brand || '',
926
- deviceModel: device?.model || '',
927
- clinicId: task.clinicId,
928
- clinicName: clinic?.name || 'Unknown',
929
- clinicAddress: clinic?.address || '',
930
- supplierId: task.supplierId,
931
- supplierName: supplier?.name || null,
932
- created_at: task.created_at,
933
- updated_at: task.updated_at,
934
- };
935
- },
936
- });
937
- //# sourceMappingURL=maintenanceTasks.js.map