@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
@@ -2,10 +2,34 @@ import { v } from 'convex/values';
2
2
  import { query, mutation } from './_generated/server';
3
3
 
4
4
  export const listSuppliers = query({
5
- args: {},
6
- handler: async (ctx) => {
7
- const suppliers = await ctx.db.query('suppliers').collect();
8
- return suppliers;
5
+ args: {
6
+ page: v.optional(v.number()),
7
+ pageSize: v.optional(v.number()),
8
+ search: v.optional(v.string()),
9
+ },
10
+ handler: async (ctx, args) => {
11
+ const page = args.page || 1;
12
+ const pageSize = args.pageSize || 20;
13
+
14
+ let suppliers = await ctx.db.query('suppliers').collect();
15
+
16
+ if (args.search) {
17
+ const s = args.search.toLowerCase();
18
+ suppliers = suppliers.filter((sup: any) =>
19
+ sup.name?.toLowerCase().includes(s) ||
20
+ sup.contact_email?.toLowerCase().includes(s) ||
21
+ sup.contactPerson?.toLowerCase().includes(s)
22
+ );
23
+ }
24
+
25
+ suppliers.sort((a: any, b: any) => (a.name || '').localeCompare(b.name || ''));
26
+
27
+ const totalCount = suppliers.length;
28
+ const totalPages = Math.ceil(totalCount / pageSize);
29
+ const startIndex = (page - 1) * pageSize;
30
+ const pageSuppliers = suppliers.slice(startIndex, startIndex + pageSize);
31
+
32
+ return { page: pageSuppliers, totalCount, totalPages, currentPage: page };
9
33
  },
10
34
  });
11
35
 
@@ -115,7 +139,10 @@ export const updateSupplier = mutation({
115
139
  });
116
140
 
117
141
  export const deleteSupplier = mutation({
118
- args: { supplierId: v.id('suppliers') },
142
+ args: {
143
+ supplierId: v.id('suppliers'),
144
+ hasLinkedTickets: v.optional(v.boolean()),
145
+ },
119
146
  handler: async (ctx, args) => {
120
147
  const contracts = await ctx.db
121
148
  .query('contracts')
@@ -129,14 +156,9 @@ export const deleteSupplier = mutation({
129
156
  );
130
157
  }
131
158
 
132
- const tasks = await ctx.db
133
- .query('maintenance_tasks')
134
- .withIndex('by_supplierId', (q: any) => q.eq('supplierId', args.supplierId))
135
- .collect();
136
-
137
- if (tasks.length > 0) {
159
+ if (args.hasLinkedTickets) {
138
160
  throw new Error(
139
- `Impossibile eliminare il fornitore: ha ${tasks.length} ticket di manutenzione associato/i. ` +
161
+ `Impossibile eliminare il fornitore: ha ticket di manutenzione associati. ` +
140
162
  `Riassegna o elimina prima i ticket.`
141
163
  );
142
164
  }
@@ -159,34 +181,31 @@ export const getSuppliersByCategory = query({
159
181
  });
160
182
 
161
183
  export const listSupplierUsers = query({
162
- args: {},
163
- handler: async (ctx) => {
164
- const supplierRole = await ctx.db
165
- .query('roles')
166
- .withIndex('by_name', (q: any) => q.eq('name', 'supplier'))
167
- .first();
168
-
169
- if (!supplierRole) {
170
- return [];
171
- }
172
-
173
- const supplierUsers = await ctx.db
174
- .query('user_profiles')
175
- .withIndex('by_roleId', (q: any) => q.eq('roleId', supplierRole._id))
176
- .collect();
184
+ args: {
185
+ supplierUserList: v.optional(v.array(v.object({
186
+ auth0Id: v.string(),
187
+ name: v.string(),
188
+ email: v.string(),
189
+ }))),
190
+ },
191
+ handler: async (ctx, args) => {
192
+ const supplierUsers = args.supplierUserList || [];
193
+ const settings = await ctx.db.query('user_profiles').withIndex('by_supplierId').collect();
194
+ const settingsMap = new Map(settings.filter(s => s.supplierId).map(s => [s.auth0Id, s]));
177
195
 
178
196
  const result = await Promise.all(
179
197
  supplierUsers.map(async (user) => {
180
- const linkedSupplier = user.supplierId
181
- ? await ctx.db.get(user.supplierId)
198
+ const userSettings = settingsMap.get(user.auth0Id);
199
+ const linkedSupplier = userSettings?.supplierId
200
+ ? await ctx.db.get(userSettings.supplierId)
182
201
  : null;
183
202
 
184
203
  return {
185
- _id: user._id,
186
- userId: user._id,
204
+ _id: user.auth0Id,
205
+ userId: user.auth0Id,
187
206
  email: user.email,
188
207
  name: user.name || user.email,
189
- supplierId: user.supplierId || null,
208
+ supplierId: userSettings?.supplierId || null,
190
209
  supplierName: linkedSupplier?.name || null,
191
210
  supplierEmail: linkedSupplier?.contact_email || null,
192
211
  };
@@ -199,35 +218,39 @@ export const listSupplierUsers = query({
199
218
 
200
219
  export const getOrCreateSupplierForUser = mutation({
201
220
  args: {
202
- userId: v.id('user_profiles'),
221
+ userId: v.string(),
222
+ userName: v.optional(v.string()),
223
+ userEmail: v.optional(v.string()),
224
+ userRole: v.optional(v.string()),
203
225
  },
204
226
  handler: async (ctx, args) => {
205
- const user = await ctx.db.get(args.userId);
206
- if (!user) {
207
- throw new Error('User not found');
208
- }
209
-
210
- const role = user.roleId ? await ctx.db.get(user.roleId) : null;
211
- if (role?.name !== 'supplier') {
227
+ if (args.userRole !== 'supplier') {
212
228
  throw new Error('User is not a supplier');
213
229
  }
214
230
 
215
- if (user.supplierId) {
216
- return user.supplierId;
231
+ const settings = await ctx.db
232
+ .query('user_profiles')
233
+ .withIndex('by_auth0Id', (q: any) => q.eq('auth0Id', args.userId))
234
+ .first();
235
+
236
+ if (settings?.supplierId) {
237
+ return settings.supplierId;
217
238
  }
218
239
 
219
240
  const supplierId = await ctx.db.insert('suppliers', {
220
- name: user.name || user.email,
221
- contact_email: user.email,
241
+ name: args.userName || args.userEmail || 'Unknown',
242
+ contact_email: args.userEmail || '',
222
243
  contact_phone: '',
223
244
  categories: [],
224
245
  sla_days: 7,
225
- notes: `Creato automaticamente dall'utente ${user.email}`,
246
+ notes: `Creato automaticamente dall'utente ${args.userEmail}`,
226
247
  });
227
248
 
228
- await ctx.db.patch(args.userId, {
229
- supplierId: supplierId,
230
- });
249
+ if (settings) {
250
+ await ctx.db.patch(settings._id, {
251
+ supplierId: supplierId,
252
+ });
253
+ }
231
254
 
232
255
  return supplierId;
233
256
  },
@@ -3,18 +3,13 @@ import { query, mutation, internalQuery } from './_generated/server';
3
3
 
4
4
  export const listComments = query({
5
5
  args: {
6
- taskId: v.id('maintenance_tasks'),
6
+ ticketId: v.string(),
7
7
  userRole: v.optional(v.string()),
8
8
  },
9
9
  handler: async (ctx, args) => {
10
- const task = await ctx.db.get(args.taskId);
11
- if (!task) {
12
- throw new Error('Ticket not found');
13
- }
14
-
15
10
  const comments = await ctx.db
16
11
  .query('ticket_comments')
17
- .withIndex('by_taskId', (q) => q.eq('taskId', args.taskId))
12
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
18
13
  .collect();
19
14
 
20
15
  const filteredComments = comments.filter((comment) => {
@@ -49,12 +44,12 @@ export const listComments = query({
49
44
 
50
45
  export const addComment = mutation({
51
46
  args: {
52
- taskId: v.id('maintenance_tasks'),
47
+ ticketId: v.string(),
53
48
  content: v.string(),
54
49
  isInternal: v.optional(v.boolean()),
55
50
  attachments: v.optional(v.array(v.id('_storage'))),
56
51
  mentions: v.optional(v.array(v.object({
57
- userId: v.id('user_profiles'),
52
+ userId: v.string(),
58
53
  name: v.string(),
59
54
  email: v.string(),
60
55
  }))),
@@ -64,11 +59,6 @@ export const addComment = mutation({
64
59
  authorRole: v.optional(v.string()),
65
60
  },
66
61
  handler: async (ctx, args) => {
67
- const task = await ctx.db.get(args.taskId);
68
- if (!task) {
69
- throw new Error('Ticket not found');
70
- }
71
-
72
62
  if (args.attachments && args.attachments.length > 5) {
73
63
  throw new Error('Maximum 5 attachments allowed');
74
64
  }
@@ -80,7 +70,7 @@ export const addComment = mutation({
80
70
  }
81
71
 
82
72
  const commentId = await ctx.db.insert('ticket_comments', {
83
- taskId: args.taskId,
73
+ ticketId: args.ticketId,
84
74
  authorId: args.authorId,
85
75
  authorName: args.authorName || args.authorEmail || 'Unknown',
86
76
  authorEmail: args.authorEmail || '',
@@ -93,10 +83,6 @@ export const addComment = mutation({
93
83
  createdAt: Date.now(),
94
84
  });
95
85
 
96
- await ctx.db.patch(args.taskId, {
97
- updated_at: Date.now(),
98
- });
99
-
100
86
  return commentId;
101
87
  },
102
88
  });
@@ -110,20 +96,15 @@ export const generateUploadUrl = mutation({
110
96
 
111
97
  export const addCommentFromApi = mutation({
112
98
  args: {
113
- taskId: v.id('maintenance_tasks'),
99
+ ticketId: v.string(),
114
100
  content: v.string(),
115
101
  authorEmail: v.optional(v.string()),
116
102
  authorName: v.optional(v.string()),
117
103
  isInternal: v.optional(v.boolean()),
118
104
  },
119
105
  handler: async (ctx, args) => {
120
- const task = await ctx.db.get(args.taskId);
121
- if (!task) {
122
- throw new Error('Ticket not found');
123
- }
124
-
125
106
  const commentId = await ctx.db.insert('ticket_comments', {
126
- taskId: args.taskId,
107
+ ticketId: args.ticketId,
127
108
  authorId: 'api',
128
109
  authorName: args.authorName || 'Sistema Esterno',
129
110
  authorEmail: args.authorEmail || 'api@external',
@@ -134,10 +115,6 @@ export const addCommentFromApi = mutation({
134
115
  createdAt: Date.now(),
135
116
  });
136
117
 
137
- await ctx.db.patch(args.taskId, {
138
- updated_at: Date.now(),
139
- });
140
-
141
118
  return commentId;
142
119
  },
143
120
  });
@@ -190,11 +167,11 @@ export const updateComment = mutation({
190
167
  });
191
168
 
192
169
  export const getCommentCount = query({
193
- args: { taskId: v.id('maintenance_tasks') },
170
+ args: { ticketId: v.string() },
194
171
  handler: async (ctx, args) => {
195
172
  const comments = await ctx.db
196
173
  .query('ticket_comments')
197
- .withIndex('by_taskId', (q) => q.eq('taskId', args.taskId))
174
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
198
175
  .collect();
199
176
 
200
177
  return comments.length;
@@ -203,24 +180,21 @@ export const getCommentCount = query({
203
180
 
204
181
  export const getUsersForMention = query({
205
182
  args: {
206
- taskId: v.id('maintenance_tasks'),
183
+ ticketId: v.string(),
207
184
  searchQuery: v.optional(v.string()),
208
- excludeUserId: v.optional(v.id('user_profiles')),
185
+ excludeUserId: v.optional(v.string()),
186
+ userList: v.optional(v.array(v.object({
187
+ auth0Id: v.string(),
188
+ name: v.string(),
189
+ email: v.string(),
190
+ role: v.string(),
191
+ }))),
209
192
  },
210
193
  handler: async (ctx, args) => {
211
- const task = await ctx.db.get(args.taskId);
212
- if (!task) {
213
- throw new Error('Ticket not found');
214
- }
215
-
216
- let users = await ctx.db
217
- .query('user_profiles')
218
- .take(500);
219
-
220
- users = users.filter(u => u.isActive !== false);
194
+ let users = args.userList || [];
221
195
 
222
196
  if (args.excludeUserId) {
223
- users = users.filter(u => u._id !== args.excludeUserId);
197
+ users = users.filter(u => u.auth0Id !== args.excludeUserId);
224
198
  }
225
199
 
226
200
  if (args.searchQuery && args.searchQuery.trim()) {
@@ -231,44 +205,27 @@ export const getUsersForMention = query({
231
205
  );
232
206
  }
233
207
 
234
- const usersWithRoles = await Promise.all(
235
- users.map(async (u) => {
236
- const role = u.roleId ? await ctx.db.get(u.roleId) : null;
237
- const roleName = (role as any)?.name || u.role || 'user';
238
-
239
- return {
240
- _id: u._id,
241
- name: u.name || u.email,
242
- email: u.email,
243
- role: roleName,
244
- };
245
- })
246
- );
247
-
248
- const sortedUsers = usersWithRoles.sort((a, b) => {
208
+ const sortedUsers = [...users].sort((a, b) => {
249
209
  if (a.role === 'admin' && b.role !== 'admin') return -1;
250
210
  if (b.role === 'admin' && a.role !== 'admin') return 1;
251
-
252
211
  return (a.name || '').localeCompare(b.name || '');
253
212
  });
254
213
 
255
- return sortedUsers.slice(0, 20);
214
+ return sortedUsers.slice(0, 20).map(u => ({
215
+ _id: u.auth0Id,
216
+ name: u.name || u.email,
217
+ email: u.email,
218
+ role: u.role,
219
+ }));
256
220
  },
257
221
  });
258
222
 
259
223
  export const listCommentsInternal = internalQuery({
260
- args: { taskId: v.string() },
224
+ args: { ticketId: v.string() },
261
225
  handler: async (ctx, args) => {
262
- let taskIdTyped;
263
- try {
264
- taskIdTyped = args.taskId as any;
265
- } catch {
266
- throw new Error('Invalid taskId format');
267
- }
268
-
269
226
  const comments = await ctx.db
270
227
  .query('ticket_comments')
271
- .withIndex('by_taskId', (q) => q.eq('taskId', taskIdTyped))
228
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
272
229
  .collect();
273
230
 
274
231
  return comments
@@ -0,0 +1,113 @@
1
+ import { v } from 'convex/values';
2
+ import { query, mutation } from './_generated/server';
3
+
4
+ export const getByTicketId = query({
5
+ args: { ticketId: v.string() },
6
+ handler: async (ctx, args) => {
7
+ return await ctx.db
8
+ .query('ticket_device_data')
9
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
10
+ .first();
11
+ },
12
+ });
13
+
14
+ export const getByDeviceId = query({
15
+ args: { deviceId: v.id('devices') },
16
+ handler: async (ctx, args) => {
17
+ return await ctx.db
18
+ .query('ticket_device_data')
19
+ .withIndex('by_deviceId', (q) => q.eq('deviceId', args.deviceId))
20
+ .collect();
21
+ },
22
+ });
23
+
24
+ export const create = mutation({
25
+ args: {
26
+ ticketId: v.string(),
27
+ deviceId: v.optional(v.id('devices')),
28
+ deviceQuestionAnswers: v.optional(v.array(v.object({
29
+ questionId: v.id('device_questions'),
30
+ question: v.string(),
31
+ answer: v.any(),
32
+ }))),
33
+ photoStorageIds: v.optional(v.array(v.id('_storage'))),
34
+ isExternal: v.optional(v.boolean()),
35
+ needsAssignment: v.optional(v.boolean()),
36
+ },
37
+ handler: async (ctx, args) => {
38
+ return await ctx.db.insert('ticket_device_data', {
39
+ ticketId: args.ticketId,
40
+ deviceId: args.deviceId,
41
+ deviceQuestionAnswers: args.deviceQuestionAnswers,
42
+ photoStorageIds: args.photoStorageIds,
43
+ isExternal: args.isExternal,
44
+ needsAssignment: args.needsAssignment,
45
+ });
46
+ },
47
+ });
48
+
49
+ export const update = mutation({
50
+ args: {
51
+ ticketId: v.string(),
52
+ deviceId: v.optional(v.id('devices')),
53
+ deviceQuestionAnswers: v.optional(v.array(v.object({
54
+ questionId: v.id('device_questions'),
55
+ question: v.string(),
56
+ answer: v.any(),
57
+ }))),
58
+ photoStorageIds: v.optional(v.array(v.id('_storage'))),
59
+ },
60
+ handler: async (ctx, args) => {
61
+ const existing = await ctx.db
62
+ .query('ticket_device_data')
63
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
64
+ .first();
65
+
66
+ if (!existing) throw new Error('Device data not found for this ticket');
67
+
68
+ const updates: any = {};
69
+ if (args.deviceId !== undefined) updates.deviceId = args.deviceId;
70
+ if (args.deviceQuestionAnswers !== undefined) updates.deviceQuestionAnswers = args.deviceQuestionAnswers;
71
+ if (args.photoStorageIds !== undefined) updates.photoStorageIds = args.photoStorageIds;
72
+
73
+ await ctx.db.patch(existing._id, updates);
74
+ return existing._id;
75
+ },
76
+ });
77
+
78
+ export const deleteByTicketId = mutation({
79
+ args: { ticketId: v.string() },
80
+ handler: async (ctx, args) => {
81
+ const entries = await ctx.db
82
+ .query('ticket_device_data')
83
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
84
+ .collect();
85
+
86
+ for (const entry of entries) {
87
+ await ctx.db.delete(entry._id);
88
+ }
89
+
90
+ return { deleted: entries.length };
91
+ },
92
+ });
93
+
94
+ export const getPhotoUrls = query({
95
+ args: { ticketId: v.string() },
96
+ handler: async (ctx, args) => {
97
+ const data = await ctx.db
98
+ .query('ticket_device_data')
99
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
100
+ .first();
101
+
102
+ if (!data?.photoStorageIds) return [];
103
+
104
+ const urls = await Promise.all(
105
+ data.photoStorageIds.map(async (storageId) => {
106
+ const url = await ctx.storage.getUrl(storageId);
107
+ return url || '';
108
+ })
109
+ );
110
+
111
+ return urls.filter(u => u !== '');
112
+ },
113
+ });