@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/devices.ts
CHANGED
|
@@ -13,60 +13,83 @@ async function getPhotoUrl(ctx: any, device: any) {
|
|
|
13
13
|
|
|
14
14
|
export const listDevices = query({
|
|
15
15
|
args: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
page: v.optional(v.number()),
|
|
17
|
+
pageSize: v.optional(v.number()),
|
|
18
|
+
clinicId: v.optional(v.string()),
|
|
19
|
+
search: v.optional(v.string()),
|
|
20
|
+
category: v.optional(v.string()),
|
|
21
|
+
status: v.optional(v.string()),
|
|
21
22
|
_triggerReload: v.optional(v.number()),
|
|
22
23
|
userRole: v.optional(v.string()),
|
|
23
|
-
userClinicId: v.optional(v.
|
|
24
|
+
userClinicId: v.optional(v.string()),
|
|
24
25
|
userSelectedClinicId: v.optional(v.number()),
|
|
26
|
+
userSupplierId: v.optional(v.id('suppliers')),
|
|
25
27
|
},
|
|
26
28
|
handler: async (ctx, args) => {
|
|
27
|
-
const
|
|
29
|
+
const page = args.page || 1;
|
|
30
|
+
const pageSize = args.pageSize || 40;
|
|
28
31
|
|
|
29
|
-
let
|
|
32
|
+
let baseQuery;
|
|
30
33
|
if (args.userRole === 'admin') {
|
|
31
34
|
if (args.clinicId) {
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
baseQuery = ctx.db.query('devices')
|
|
36
|
+
.withIndex('by_clinicId', (q: any) => q.eq('clinicId', args.clinicId));
|
|
37
|
+
} else if (args.userSelectedClinicId) {
|
|
38
|
+
baseQuery = ctx.db.query('devices')
|
|
39
|
+
.withIndex('by_primoupClinicId', (q: any) => q.eq('primoupClinicId', args.userSelectedClinicId));
|
|
37
40
|
} else {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
baseQuery = ctx.db.query('devices');
|
|
42
|
+
}
|
|
43
|
+
} else if (args.userRole === 'supplier') {
|
|
44
|
+
if (args.userSupplierId) {
|
|
45
|
+
baseQuery = ctx.db.query('devices')
|
|
46
|
+
.withIndex('by_supplierId', (q: any) => q.eq('supplierId', args.userSupplierId));
|
|
47
|
+
} else {
|
|
48
|
+
return { page: [], totalCount: 0, totalPages: 0, currentPage: page };
|
|
42
49
|
}
|
|
43
50
|
} else if (args.userRole === 'user') {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.query('devices')
|
|
50
|
-
.withIndex('by_primoupClinicId', (q: any) => q.eq('primoupClinicId', selectedClinicId))
|
|
51
|
-
.take(limit);
|
|
52
|
-
} else {
|
|
53
|
-
devices = [];
|
|
54
|
-
}
|
|
55
|
-
} catch (error: any) {
|
|
56
|
-
devices = [];
|
|
51
|
+
if (args.userSelectedClinicId) {
|
|
52
|
+
baseQuery = ctx.db.query('devices')
|
|
53
|
+
.withIndex('by_primoupClinicId', (q: any) => q.eq('primoupClinicId', args.userSelectedClinicId));
|
|
54
|
+
} else {
|
|
55
|
+
return { page: [], totalCount: 0, totalPages: 0, currentPage: page };
|
|
57
56
|
}
|
|
58
57
|
} else {
|
|
59
|
-
|
|
58
|
+
return { page: [], totalCount: 0, totalPages: 0, currentPage: page };
|
|
60
59
|
}
|
|
61
60
|
|
|
61
|
+
let allDevices = await baseQuery.collect();
|
|
62
|
+
|
|
63
|
+
if (args.search) {
|
|
64
|
+
const s = args.search.toLowerCase();
|
|
65
|
+
allDevices = allDevices.filter((d: any) =>
|
|
66
|
+
d.name?.toLowerCase().includes(s) ||
|
|
67
|
+
d.brand?.toLowerCase().includes(s) ||
|
|
68
|
+
d.model?.toLowerCase().includes(s) ||
|
|
69
|
+
d.serialNumber?.toLowerCase().includes(s) ||
|
|
70
|
+
d.internalId?.toLowerCase().includes(s)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (args.category) {
|
|
74
|
+
allDevices = allDevices.filter((d: any) => d.category === args.category);
|
|
75
|
+
}
|
|
76
|
+
if (args.status) {
|
|
77
|
+
allDevices = allDevices.filter((d: any) => d.status === args.status);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const totalCount = allDevices.length;
|
|
81
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
82
|
+
const startIndex = (page - 1) * pageSize;
|
|
83
|
+
const pageDevices = allDevices.slice(startIndex, startIndex + pageSize);
|
|
84
|
+
|
|
62
85
|
const devicesWithUrls = await Promise.all(
|
|
63
|
-
|
|
86
|
+
pageDevices.map(async (device: any) => ({
|
|
64
87
|
...device,
|
|
65
88
|
photoUrl: await getPhotoUrl(ctx, device),
|
|
66
89
|
}))
|
|
67
90
|
);
|
|
68
91
|
|
|
69
|
-
return devicesWithUrls;
|
|
92
|
+
return { page: devicesWithUrls, totalCount, totalPages, currentPage: page };
|
|
70
93
|
},
|
|
71
94
|
});
|
|
72
95
|
|
|
@@ -80,9 +103,9 @@ export const listDevicesPaginated = query({
|
|
|
80
103
|
brand: v.optional(v.string()),
|
|
81
104
|
status: v.optional(v.string()),
|
|
82
105
|
searchTerm: v.optional(v.string()),
|
|
83
|
-
clinicId: v.optional(v.
|
|
106
|
+
clinicId: v.optional(v.string()),
|
|
84
107
|
userRole: v.optional(v.string()),
|
|
85
|
-
userClinicId: v.optional(v.
|
|
108
|
+
userClinicId: v.optional(v.string()),
|
|
86
109
|
userSelectedClinicId: v.optional(v.number()),
|
|
87
110
|
},
|
|
88
111
|
handler: async (ctx, args) => {
|
|
@@ -132,9 +155,12 @@ export const listDevicesPaginated = query({
|
|
|
132
155
|
filteredQuery = filteredQuery.filter((q: any) => q.eq(q.field('status'), args.status));
|
|
133
156
|
}
|
|
134
157
|
|
|
135
|
-
const
|
|
158
|
+
const limit = args.paginationOpts.numItems + 1;
|
|
159
|
+
const allResults = await filteredQuery.take(limit);
|
|
160
|
+
const isDone = allResults.length < limit;
|
|
161
|
+
const page = isDone ? allResults : allResults.slice(0, -1);
|
|
136
162
|
|
|
137
|
-
let filteredPage =
|
|
163
|
+
let filteredPage = page;
|
|
138
164
|
|
|
139
165
|
if (args.searchTerm) {
|
|
140
166
|
const search = args.searchTerm.toLowerCase();
|
|
@@ -147,10 +173,6 @@ export const listDevicesPaginated = query({
|
|
|
147
173
|
);
|
|
148
174
|
}
|
|
149
175
|
|
|
150
|
-
const clinicIds = [...new Set(filteredPage.map((d: any) => d.clinicId).filter(Boolean))];
|
|
151
|
-
const clinics = await Promise.all(clinicIds.map(id => ctx.db.get(id)));
|
|
152
|
-
const clinicMap = new Map(clinics.filter(Boolean).map((c: any) => [c._id.toString(), c.name]));
|
|
153
|
-
|
|
154
176
|
const minimalPage = await Promise.all(
|
|
155
177
|
filteredPage.map(async (device: any) => ({
|
|
156
178
|
_id: device._id,
|
|
@@ -162,7 +184,7 @@ export const listDevicesPaginated = query({
|
|
|
162
184
|
internalId: device.internalId,
|
|
163
185
|
status: device.status,
|
|
164
186
|
clinicId: device.clinicId,
|
|
165
|
-
clinicName:
|
|
187
|
+
clinicName: '',
|
|
166
188
|
photoUrl: device.photoStorageId
|
|
167
189
|
? await ctx.storage.getUrl(device.photoStorageId)
|
|
168
190
|
: null,
|
|
@@ -171,8 +193,8 @@ export const listDevicesPaginated = query({
|
|
|
171
193
|
|
|
172
194
|
return {
|
|
173
195
|
page: minimalPage,
|
|
174
|
-
isDone
|
|
175
|
-
continueCursor:
|
|
196
|
+
isDone,
|
|
197
|
+
continueCursor: '',
|
|
176
198
|
};
|
|
177
199
|
},
|
|
178
200
|
});
|
|
@@ -182,9 +204,9 @@ export const getDevicesCount = query({
|
|
|
182
204
|
category: v.optional(v.string()),
|
|
183
205
|
brand: v.optional(v.string()),
|
|
184
206
|
status: v.optional(v.string()),
|
|
185
|
-
clinicId: v.optional(v.
|
|
207
|
+
clinicId: v.optional(v.string()),
|
|
186
208
|
userRole: v.optional(v.string()),
|
|
187
|
-
userClinicId: v.optional(v.
|
|
209
|
+
userClinicId: v.optional(v.string()),
|
|
188
210
|
userSelectedClinicId: v.optional(v.number()),
|
|
189
211
|
},
|
|
190
212
|
handler: async (ctx, args) => {
|
|
@@ -276,7 +298,8 @@ export const generateUploadUrl = mutation({
|
|
|
276
298
|
|
|
277
299
|
export const createDevice = mutation({
|
|
278
300
|
args: {
|
|
279
|
-
clinicId: v.
|
|
301
|
+
clinicId: v.string(),
|
|
302
|
+
primoupClinicId: v.optional(v.number()),
|
|
280
303
|
supplierId: v.optional(v.id('suppliers')),
|
|
281
304
|
name: v.string(),
|
|
282
305
|
category: v.string(),
|
|
@@ -308,14 +331,6 @@ export const createDevice = mutation({
|
|
|
308
331
|
handler: async (ctx, args) => {
|
|
309
332
|
const deviceData: any = { ...args };
|
|
310
333
|
|
|
311
|
-
const clinic = await ctx.db.get(args.clinicId);
|
|
312
|
-
if (clinic?.primoupId) {
|
|
313
|
-
const primoupIdNum = parseInt(clinic.primoupId, 10);
|
|
314
|
-
if (!isNaN(primoupIdNum)) {
|
|
315
|
-
deviceData.primoupClinicId = primoupIdNum;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
334
|
const deviceId = await ctx.db.insert('devices', deviceData);
|
|
320
335
|
return deviceId;
|
|
321
336
|
},
|
|
@@ -324,7 +339,7 @@ export const createDevice = mutation({
|
|
|
324
339
|
export const updateDevice = mutation({
|
|
325
340
|
args: {
|
|
326
341
|
deviceId: v.id('devices'),
|
|
327
|
-
clinicId: v.optional(v.
|
|
342
|
+
clinicId: v.optional(v.string()),
|
|
328
343
|
primoupClinicId: v.optional(v.number()),
|
|
329
344
|
supplierId: v.optional(v.id('suppliers')),
|
|
330
345
|
name: v.optional(v.string()),
|
|
@@ -385,7 +400,7 @@ export const updateDeviceByUser = mutation({
|
|
|
385
400
|
industry40Data: v.optional(v.string()),
|
|
386
401
|
userRole: v.string(),
|
|
387
402
|
userSelectedClinicId: v.optional(v.number()),
|
|
388
|
-
userClinicId: v.optional(v.
|
|
403
|
+
userClinicId: v.optional(v.string()),
|
|
389
404
|
},
|
|
390
405
|
handler: async (ctx, args) => {
|
|
391
406
|
const device = await ctx.db.get(args.deviceId);
|
|
@@ -429,7 +444,7 @@ export const deleteDevice = mutation({
|
|
|
429
444
|
export const getDeviceStats = query({
|
|
430
445
|
args: {
|
|
431
446
|
userRole: v.optional(v.string()),
|
|
432
|
-
userClinicId: v.optional(v.
|
|
447
|
+
userClinicId: v.optional(v.string()),
|
|
433
448
|
},
|
|
434
449
|
handler: async (ctx, args) => {
|
|
435
450
|
try {
|
|
@@ -478,29 +493,17 @@ export const getFileUrl = query({
|
|
|
478
493
|
export const listAllDevicesForExport = query({
|
|
479
494
|
args: {},
|
|
480
495
|
handler: async (ctx) => {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
clinicName: clinic.name || 'Sconosciuta',
|
|
493
|
-
category: device.category || '',
|
|
494
|
-
brand: device.brand || '',
|
|
495
|
-
model: device.model || '',
|
|
496
|
-
serial_number: device.serial_number || '',
|
|
497
|
-
quantity: device.quantity || 1,
|
|
498
|
-
status: device.status || 'active',
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return results;
|
|
496
|
+
const devices = await ctx.db.query('devices').take(5000);
|
|
497
|
+
return devices.map(device => ({
|
|
498
|
+
clinicId: device.clinicId,
|
|
499
|
+
clinicName: '',
|
|
500
|
+
category: device.category || '',
|
|
501
|
+
brand: device.brand || '',
|
|
502
|
+
model: device.model || '',
|
|
503
|
+
serial_number: device.serial_number || '',
|
|
504
|
+
quantity: device.quantity || 1,
|
|
505
|
+
status: device.status || 'active',
|
|
506
|
+
}));
|
|
504
507
|
},
|
|
505
508
|
});
|
|
506
509
|
|
|
@@ -557,40 +560,18 @@ export const getDeviceTemplates = query({
|
|
|
557
560
|
|
|
558
561
|
export const createDeviceByUser = mutation({
|
|
559
562
|
args: {
|
|
563
|
+
clinicId: v.string(),
|
|
560
564
|
name: v.string(),
|
|
561
565
|
category: v.string(),
|
|
562
566
|
brand: v.string(),
|
|
563
567
|
model: v.string(),
|
|
564
568
|
serial_number: v.string(),
|
|
565
569
|
userSelectedClinicId: v.number(),
|
|
566
|
-
userPrimoupClinics: v.optional(v.array(v.any())),
|
|
567
570
|
},
|
|
568
571
|
handler: async (ctx, args) => {
|
|
569
|
-
const selectedClinicId = args.userSelectedClinicId;
|
|
570
|
-
|
|
571
|
-
const primoupClinics = args.userPrimoupClinics || [];
|
|
572
|
-
const selectedPrimoupClinic = primoupClinics.find((c: any) => c.id === selectedClinicId);
|
|
573
|
-
|
|
574
|
-
if (!selectedPrimoupClinic) {
|
|
575
|
-
throw new Error('Selected clinic not found in user profile');
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
let clinic = await ctx.db
|
|
579
|
-
.query('clinics')
|
|
580
|
-
.withIndex('by_primoupId', (q: any) => q.eq('primoupId', String(selectedClinicId)))
|
|
581
|
-
.first();
|
|
582
|
-
if (!clinic) {
|
|
583
|
-
const clinics = await ctx.db.query('clinics').take(500);
|
|
584
|
-
clinic = clinics.find(c => c.name === selectedPrimoupClinic.name) || null;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (!clinic) {
|
|
588
|
-
throw new Error('Clinic not found in database');
|
|
589
|
-
}
|
|
590
|
-
|
|
591
572
|
const deviceId = await ctx.db.insert('devices', {
|
|
592
|
-
clinicId:
|
|
593
|
-
primoupClinicId:
|
|
573
|
+
clinicId: args.clinicId,
|
|
574
|
+
primoupClinicId: args.userSelectedClinicId,
|
|
594
575
|
name: args.name,
|
|
595
576
|
category: args.category,
|
|
596
577
|
brand: args.brand,
|
|
@@ -2,20 +2,11 @@ import { query } from './_generated/server';
|
|
|
2
2
|
import { v } from 'convex/values';
|
|
3
3
|
|
|
4
4
|
export const getAdminEmails = query({
|
|
5
|
-
args: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.first();
|
|
11
|
-
if (!adminRole) return [];
|
|
12
|
-
const adminUsers = await ctx.db
|
|
13
|
-
.query('user_profiles')
|
|
14
|
-
.withIndex('by_roleId', (q) => q.eq('roleId', adminRole._id))
|
|
15
|
-
.collect();
|
|
16
|
-
return adminUsers
|
|
17
|
-
.filter((user) => user.email)
|
|
18
|
-
.map((user) => ({ email: user.email, name: user.name || user.email }));
|
|
5
|
+
args: {
|
|
6
|
+
adminEmails: v.optional(v.array(v.object({ email: v.string(), name: v.string() }))),
|
|
7
|
+
},
|
|
8
|
+
handler: async (_ctx, args) => {
|
|
9
|
+
return args.adminEmails || [];
|
|
19
10
|
},
|
|
20
11
|
});
|
|
21
12
|
|
|
@@ -29,10 +20,9 @@ export const getSupplierEmail = query({
|
|
|
29
20
|
});
|
|
30
21
|
|
|
31
22
|
export const getClinicEmail = query({
|
|
32
|
-
args: { clinicId: v.
|
|
33
|
-
handler: async (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return { email: clinic.contact_email, name: clinic.name };
|
|
23
|
+
args: { clinicId: v.string(), clinicEmail: v.optional(v.string()), clinicName: v.optional(v.string()) },
|
|
24
|
+
handler: async (_ctx, args) => {
|
|
25
|
+
if (!args.clinicEmail) return null;
|
|
26
|
+
return { email: args.clinicEmail, name: args.clinicName || 'Clinica' };
|
|
37
27
|
},
|
|
38
28
|
});
|
package/src/component/emails.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const logEmail = mutation({
|
|
|
7
7
|
to: v.union(v.string(), v.array(v.string())),
|
|
8
8
|
subject: v.string(),
|
|
9
9
|
html: v.string(),
|
|
10
|
-
ticketId: v.optional(v.
|
|
10
|
+
ticketId: v.optional(v.string()),
|
|
11
11
|
status: v.union(v.literal('sent'), v.literal('failed'), v.literal('pending')),
|
|
12
12
|
resendId: v.optional(v.string()),
|
|
13
13
|
errorMessage: v.optional(v.string()),
|
|
@@ -28,7 +28,7 @@ export const logEmail = mutation({
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
export const getEmailLogsByTicket = query({
|
|
31
|
-
args: { ticketId: v.
|
|
31
|
+
args: { ticketId: v.string() },
|
|
32
32
|
handler: async (ctx, args) => {
|
|
33
33
|
return await ctx.db
|
|
34
34
|
.query('email_logs')
|
package/src/component/http.ts
CHANGED
|
@@ -54,7 +54,7 @@ http.route({
|
|
|
54
54
|
|
|
55
55
|
try {
|
|
56
56
|
const commentId = await ctx.runMutation(api.ticketComments.addCommentFromApi, {
|
|
57
|
-
|
|
57
|
+
ticketId,
|
|
58
58
|
content: content.trim(),
|
|
59
59
|
authorEmail: authorEmail || undefined,
|
|
60
60
|
authorName: authorName || undefined,
|
|
@@ -99,7 +99,7 @@ http.route({
|
|
|
99
99
|
const ticketId = url.searchParams.get('ticketId');
|
|
100
100
|
if (!ticketId) return errorResponse('Parametro "ticketId" obbligatorio', 400);
|
|
101
101
|
|
|
102
|
-
const comments = await ctx.runQuery(internal.ticketComments.listCommentsInternal, {
|
|
102
|
+
const comments = await ctx.runQuery(internal.ticketComments.listCommentsInternal, { ticketId });
|
|
103
103
|
await ctx.runMutation(internal.apiKeys.recordApiKeyUsage, { keyId: keyData.keyId });
|
|
104
104
|
|
|
105
105
|
return jsonResponse({ success: true, comments, count: comments.length });
|
|
@@ -110,111 +110,6 @@ http.route({
|
|
|
110
110
|
}),
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
http.route({
|
|
114
|
-
path: '/api/tickets/get',
|
|
115
|
-
method: 'OPTIONS',
|
|
116
|
-
handler: httpAction(async () => {
|
|
117
|
-
return new Response(null, { status: 204, headers: corsHeaders });
|
|
118
|
-
}),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
http.route({
|
|
122
|
-
path: '/api/tickets/get',
|
|
123
|
-
method: 'GET',
|
|
124
|
-
handler: httpAction(async (ctx, request) => {
|
|
125
|
-
try {
|
|
126
|
-
const apiKey = request.headers.get('X-API-Key');
|
|
127
|
-
if (!apiKey) return errorResponse('API key mancante', 401);
|
|
128
|
-
|
|
129
|
-
const keyData = await ctx.runQuery(api.apiKeys.validateApiKey, { plainKey: apiKey });
|
|
130
|
-
if (!keyData) return errorResponse('API key non valida', 401);
|
|
131
|
-
if (!keyData.permissions.includes('tickets:read')) {
|
|
132
|
-
return errorResponse('API key non ha il permesso "tickets:read"', 403);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const url = new URL(request.url);
|
|
136
|
-
const ticketId = url.searchParams.get('ticketId');
|
|
137
|
-
if (!ticketId) return errorResponse('Parametro "ticketId" obbligatorio', 400);
|
|
138
|
-
|
|
139
|
-
const ticket = await ctx.runQuery(internal.maintenanceTasks.getTicketInternal, { taskId: ticketId });
|
|
140
|
-
if (!ticket) return errorResponse('Ticket non trovato', 404);
|
|
141
|
-
|
|
142
|
-
await ctx.runMutation(internal.apiKeys.recordApiKeyUsage, { keyId: keyData.keyId });
|
|
143
|
-
return jsonResponse({ success: true, ticket });
|
|
144
|
-
} catch (error: any) {
|
|
145
|
-
console.error('Error in /api/tickets/get:', error);
|
|
146
|
-
return errorResponse('Errore interno: ' + error.message, 500);
|
|
147
|
-
}
|
|
148
|
-
}),
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
http.route({
|
|
152
|
-
path: '/api/tickets/create',
|
|
153
|
-
method: 'OPTIONS',
|
|
154
|
-
handler: httpAction(async () => {
|
|
155
|
-
return new Response(null, { status: 204, headers: corsHeaders });
|
|
156
|
-
}),
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
http.route({
|
|
160
|
-
path: '/api/tickets/create',
|
|
161
|
-
method: 'POST',
|
|
162
|
-
handler: httpAction(async (ctx, request) => {
|
|
163
|
-
try {
|
|
164
|
-
const apiKey = request.headers.get('X-API-Key');
|
|
165
|
-
if (!apiKey) return errorResponse('API key mancante. Usa header X-API-Key', 401);
|
|
166
|
-
|
|
167
|
-
const keyData = await ctx.runQuery(api.apiKeys.validateApiKey, { plainKey: apiKey });
|
|
168
|
-
if (!keyData) return errorResponse('API key non valida o scaduta', 401);
|
|
169
|
-
if (!keyData.permissions.includes('tickets:create')) {
|
|
170
|
-
return errorResponse('API key non ha il permesso "tickets:create"', 403);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
let body: any;
|
|
174
|
-
try { body = await request.json(); } catch { return errorResponse('Body JSON non valido', 400); }
|
|
175
|
-
|
|
176
|
-
const { clinicId, primoupId, clinicName, description, title, externalTicketId, externalTicketNumber, creatorEmail, creatorName, priority } = body;
|
|
177
|
-
|
|
178
|
-
if (!clinicId && !primoupId && !clinicName) {
|
|
179
|
-
return errorResponse('Devi fornire almeno uno tra: clinicId, primoupId, clinicName', 400);
|
|
180
|
-
}
|
|
181
|
-
if (!description || typeof description !== 'string' || description.trim().length === 0) {
|
|
182
|
-
return errorResponse('Campo "description" obbligatorio e non vuoto', 400);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const clinic = await ctx.runQuery(internal.maintenanceTasks.findClinicInternal, {
|
|
186
|
-
clinicId, primoupId: primoupId?.toString(), clinicName,
|
|
187
|
-
});
|
|
188
|
-
if (!clinic) return errorResponse('Clinica non trovata', 404);
|
|
189
|
-
|
|
190
|
-
const validPriorities = ['low', 'medium', 'high'];
|
|
191
|
-
if (priority && !validPriorities.includes(priority)) {
|
|
192
|
-
return errorResponse(`Priorità non valida. Usa: ${validPriorities.join(', ')}`, 400);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
const taskId = await ctx.runMutation(internal.maintenanceTasks.createExternalTicket, {
|
|
197
|
-
clinicId: clinic._id as any,
|
|
198
|
-
description: description.trim(),
|
|
199
|
-
title: title || undefined,
|
|
200
|
-
externalTicketId: externalTicketId || undefined,
|
|
201
|
-
externalTicketNumber: externalTicketNumber || undefined,
|
|
202
|
-
creatorEmail: creatorEmail || undefined,
|
|
203
|
-
creatorName: creatorName || undefined,
|
|
204
|
-
priority: priority || undefined,
|
|
205
|
-
});
|
|
206
|
-
await ctx.runMutation(internal.apiKeys.recordApiKeyUsage, { keyId: keyData.keyId });
|
|
207
|
-
return jsonResponse({ success: true, ticketId: taskId, message: 'Segnalazione creata con successo.' });
|
|
208
|
-
} catch (error: any) {
|
|
209
|
-
return errorResponse('Errore nella creazione del ticket: ' + error.message, 500);
|
|
210
|
-
}
|
|
211
|
-
} catch (error: any) {
|
|
212
|
-
console.error('Error in /api/tickets/create:', error);
|
|
213
|
-
return errorResponse('Errore interno del server: ' + error.message, 500);
|
|
214
|
-
}
|
|
215
|
-
}),
|
|
216
|
-
});
|
|
217
|
-
|
|
218
113
|
http.route({
|
|
219
114
|
path: '/api/health',
|
|
220
115
|
method: 'GET',
|
|
@@ -223,7 +118,7 @@ http.route({
|
|
|
223
118
|
success: true,
|
|
224
119
|
status: 'healthy',
|
|
225
120
|
timestamp: new Date().toISOString(),
|
|
226
|
-
version: '
|
|
121
|
+
version: '2.0.0',
|
|
227
122
|
});
|
|
228
123
|
}),
|
|
229
124
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { v } from 'convex/values';
|
|
2
|
+
import { query, mutation } from './_generated/server';
|
|
3
|
+
|
|
4
|
+
export const listMaintenanceTasksForMigration = query({
|
|
5
|
+
args: {
|
|
6
|
+
cursor: v.optional(v.number()),
|
|
7
|
+
batchSize: v.optional(v.number()),
|
|
8
|
+
},
|
|
9
|
+
handler: async (ctx, args) => {
|
|
10
|
+
// This function reads from ticket_comments and ticket_history
|
|
11
|
+
// to reconstruct a list of known ticket IDs for migration purposes.
|
|
12
|
+
// Since maintenance_tasks table has been removed, this is a no-op placeholder.
|
|
13
|
+
// The actual migration should be run BEFORE removing the maintenance_tasks table,
|
|
14
|
+
// or data should be exported to a JSON file first.
|
|
15
|
+
return [];
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const updateCommentsTicketId = mutation({
|
|
20
|
+
args: {
|
|
21
|
+
oldTicketId: v.string(),
|
|
22
|
+
newTicketId: v.string(),
|
|
23
|
+
},
|
|
24
|
+
handler: async (ctx, args) => {
|
|
25
|
+
const comments = await ctx.db
|
|
26
|
+
.query('ticket_comments')
|
|
27
|
+
.withIndex('by_ticketId', (q) => q.eq('ticketId', args.oldTicketId))
|
|
28
|
+
.collect();
|
|
29
|
+
|
|
30
|
+
for (const comment of comments) {
|
|
31
|
+
await ctx.db.patch(comment._id, { ticketId: args.newTicketId });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { updated: comments.length };
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const updateHistoryTicketId = mutation({
|
|
39
|
+
args: {
|
|
40
|
+
oldTicketId: v.string(),
|
|
41
|
+
newTicketId: v.string(),
|
|
42
|
+
},
|
|
43
|
+
handler: async (ctx, args) => {
|
|
44
|
+
const entries = await ctx.db
|
|
45
|
+
.query('ticket_history')
|
|
46
|
+
.withIndex('by_ticketId', (q) => q.eq('ticketId', args.oldTicketId))
|
|
47
|
+
.collect();
|
|
48
|
+
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
await ctx.db.patch(entry._id, { ticketId: args.newTicketId });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { updated: entries.length };
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const updateEmailLogsTicketId = mutation({
|
|
58
|
+
args: {
|
|
59
|
+
oldTicketId: v.string(),
|
|
60
|
+
newTicketId: v.string(),
|
|
61
|
+
},
|
|
62
|
+
handler: async (ctx, args) => {
|
|
63
|
+
const logs = await ctx.db
|
|
64
|
+
.query('email_logs')
|
|
65
|
+
.withIndex('by_ticketId', (q) => q.eq('ticketId', args.oldTicketId))
|
|
66
|
+
.collect();
|
|
67
|
+
|
|
68
|
+
for (const log of logs) {
|
|
69
|
+
await ctx.db.patch(log._id, { ticketId: args.newTicketId });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { updated: logs.length };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const updateRepairHistoryTicketId = mutation({
|
|
77
|
+
args: {
|
|
78
|
+
oldTicketId: v.string(),
|
|
79
|
+
newTicketId: v.string(),
|
|
80
|
+
},
|
|
81
|
+
handler: async (ctx, args) => {
|
|
82
|
+
const entries = await ctx.db
|
|
83
|
+
.query('device_repair_history')
|
|
84
|
+
.collect();
|
|
85
|
+
|
|
86
|
+
let updated = 0;
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
if (entry.ticketId === args.oldTicketId) {
|
|
89
|
+
await ctx.db.patch(entry._id, { ticketId: args.newTicketId });
|
|
90
|
+
updated++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { updated };
|
|
95
|
+
},
|
|
96
|
+
});
|
package/src/component/roles.ts
CHANGED
|
@@ -80,17 +80,16 @@ export const updateRole = mutation({
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
export const deleteRole = mutation({
|
|
83
|
-
args: {
|
|
83
|
+
args: {
|
|
84
|
+
roleId: v.id('roles'),
|
|
85
|
+
hasAssignedUsers: v.optional(v.boolean()),
|
|
86
|
+
},
|
|
84
87
|
handler: async (ctx, args) => {
|
|
85
88
|
const role = await ctx.db.get(args.roleId);
|
|
86
89
|
if (role && (role.name === 'admin' || role.name === 'user' || role.name === 'supplier')) {
|
|
87
90
|
throw new Error('Cannot delete system roles (admin, user, supplier)');
|
|
88
91
|
}
|
|
89
|
-
|
|
90
|
-
.query('user_profiles')
|
|
91
|
-
.withIndex('by_roleId', (q) => q.eq('roleId', args.roleId))
|
|
92
|
-
.first();
|
|
93
|
-
if (usersWithRole) {
|
|
92
|
+
if (args.hasAssignedUsers) {
|
|
94
93
|
throw new Error('Cannot delete role that is assigned to users');
|
|
95
94
|
}
|
|
96
95
|
await ctx.db.delete(args.roleId);
|