@primocaredentgroup/elettromedicali 0.1.0

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 (197) hide show
  1. package/dist/client/index.d.ts +72 -0
  2. package/dist/client/index.d.ts.map +1 -0
  3. package/dist/client/index.js +233 -0
  4. package/dist/client/index.js.map +1 -0
  5. package/dist/component/_generated/api.d.ts +94 -0
  6. package/dist/component/_generated/api.d.ts.map +1 -0
  7. package/dist/component/_generated/api.js +31 -0
  8. package/dist/component/_generated/api.js.map +1 -0
  9. package/dist/component/_generated/component.d.ts +1444 -0
  10. package/dist/component/_generated/component.d.ts.map +1 -0
  11. package/dist/component/_generated/component.js +11 -0
  12. package/dist/component/_generated/component.js.map +1 -0
  13. package/dist/component/_generated/dataModel.d.ts +46 -0
  14. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  15. package/dist/component/_generated/dataModel.js +11 -0
  16. package/dist/component/_generated/dataModel.js.map +1 -0
  17. package/dist/component/_generated/server.d.ts +121 -0
  18. package/dist/component/_generated/server.d.ts.map +1 -0
  19. package/dist/component/_generated/server.js +78 -0
  20. package/dist/component/_generated/server.js.map +1 -0
  21. package/dist/component/apiKeys.d.ts +69 -0
  22. package/dist/component/apiKeys.d.ts.map +1 -0
  23. package/dist/component/apiKeys.js +207 -0
  24. package/dist/component/apiKeys.js.map +1 -0
  25. package/dist/component/clinics.d.ts +103 -0
  26. package/dist/component/clinics.d.ts.map +1 -0
  27. package/dist/component/clinics.js +126 -0
  28. package/dist/component/clinics.js.map +1 -0
  29. package/dist/component/contracts.d.ts +85 -0
  30. package/dist/component/contracts.d.ts.map +1 -0
  31. package/dist/component/contracts.js +115 -0
  32. package/dist/component/contracts.js.map +1 -0
  33. package/dist/component/convex.config.d.ts +3 -0
  34. package/dist/component/convex.config.d.ts.map +1 -0
  35. package/dist/component/convex.config.js +3 -0
  36. package/dist/component/convex.config.js.map +1 -0
  37. package/dist/component/crons.d.ts +3 -0
  38. package/dist/component/crons.d.ts.map +1 -0
  39. package/dist/component/crons.js +7 -0
  40. package/dist/component/crons.js.map +1 -0
  41. package/dist/component/dashboardStats.d.ts +14 -0
  42. package/dist/component/dashboardStats.d.ts.map +1 -0
  43. package/dist/component/dashboardStats.js +136 -0
  44. package/dist/component/dashboardStats.js.map +1 -0
  45. package/dist/component/dashboardStatsCache.d.ts +32 -0
  46. package/dist/component/dashboardStatsCache.d.ts.map +1 -0
  47. package/dist/component/dashboardStatsCache.js +129 -0
  48. package/dist/component/dashboardStatsCache.js.map +1 -0
  49. package/dist/component/deviceCategories.d.ts +108 -0
  50. package/dist/component/deviceCategories.d.ts.map +1 -0
  51. package/dist/component/deviceCategories.js +254 -0
  52. package/dist/component/deviceCategories.js.map +1 -0
  53. package/dist/component/deviceQuestions.d.ts +129 -0
  54. package/dist/component/deviceQuestions.d.ts.map +1 -0
  55. package/dist/component/deviceQuestions.js +175 -0
  56. package/dist/component/deviceQuestions.js.map +1 -0
  57. package/dist/component/deviceRepairHistory.d.ts +30 -0
  58. package/dist/component/deviceRepairHistory.d.ts.map +1 -0
  59. package/dist/component/deviceRepairHistory.js +84 -0
  60. package/dist/component/deviceRepairHistory.js.map +1 -0
  61. package/dist/component/deviceStatus.d.ts +63 -0
  62. package/dist/component/deviceStatus.d.ts.map +1 -0
  63. package/dist/component/deviceStatus.js +58 -0
  64. package/dist/component/deviceStatus.js.map +1 -0
  65. package/dist/component/devices.d.ts +299 -0
  66. package/dist/component/devices.d.ts.map +1 -0
  67. package/dist/component/devices.js +587 -0
  68. package/dist/component/devices.js.map +1 -0
  69. package/dist/component/emailHelpers.d.ts +17 -0
  70. package/dist/component/emailHelpers.d.ts.map +1 -0
  71. package/dist/component/emailHelpers.js +39 -0
  72. package/dist/component/emailHelpers.js.map +1 -0
  73. package/dist/component/emails.d.ts +56 -0
  74. package/dist/component/emails.d.ts.map +1 -0
  75. package/dist/component/emails.js +58 -0
  76. package/dist/component/emails.js.map +1 -0
  77. package/dist/component/http.d.ts +3 -0
  78. package/dist/component/http.d.ts.map +1 -0
  79. package/dist/component/http.js +229 -0
  80. package/dist/component/http.js.map +1 -0
  81. package/dist/component/maintenanceTasks.d.ts +733 -0
  82. package/dist/component/maintenanceTasks.d.ts.map +1 -0
  83. package/dist/component/maintenanceTasks.js +937 -0
  84. package/dist/component/maintenanceTasks.js.map +1 -0
  85. package/dist/component/roles.d.ts +75 -0
  86. package/dist/component/roles.d.ts.map +1 -0
  87. package/dist/component/roles.js +98 -0
  88. package/dist/component/roles.js.map +1 -0
  89. package/dist/component/schema.d.ts +1295 -0
  90. package/dist/component/schema.d.ts.map +1 -0
  91. package/dist/component/schema.js +724 -0
  92. package/dist/component/schema.js.map +1 -0
  93. package/dist/component/slaMonitoring.d.ts +32 -0
  94. package/dist/component/slaMonitoring.d.ts.map +1 -0
  95. package/dist/component/slaMonitoring.js +111 -0
  96. package/dist/component/slaMonitoring.js.map +1 -0
  97. package/dist/component/slaRules.d.ts +72 -0
  98. package/dist/component/slaRules.d.ts.map +1 -0
  99. package/dist/component/slaRules.js +193 -0
  100. package/dist/component/slaRules.js.map +1 -0
  101. package/dist/component/sparePartOrders.d.ts +177 -0
  102. package/dist/component/sparePartOrders.d.ts.map +1 -0
  103. package/dist/component/sparePartOrders.js +243 -0
  104. package/dist/component/sparePartOrders.js.map +1 -0
  105. package/dist/component/spareParts.d.ts +472 -0
  106. package/dist/component/spareParts.d.ts.map +1 -0
  107. package/dist/component/spareParts.js +319 -0
  108. package/dist/component/spareParts.js.map +1 -0
  109. package/dist/component/supplierCategories.d.ts +22 -0
  110. package/dist/component/supplierCategories.d.ts.map +1 -0
  111. package/dist/component/supplierCategories.js +64 -0
  112. package/dist/component/supplierCategories.js.map +1 -0
  113. package/dist/component/suppliers.d.ts +94 -0
  114. package/dist/component/suppliers.d.ts.map +1 -0
  115. package/dist/component/suppliers.js +195 -0
  116. package/dist/component/suppliers.js.map +1 -0
  117. package/dist/component/ticketComments.d.ts +89 -0
  118. package/dist/component/ticketComments.d.ts.map +1 -0
  119. package/dist/component/ticketComments.js +246 -0
  120. package/dist/component/ticketComments.js.map +1 -0
  121. package/dist/component/ticketCustomFields.d.ts +149 -0
  122. package/dist/component/ticketCustomFields.d.ts.map +1 -0
  123. package/dist/component/ticketCustomFields.js +215 -0
  124. package/dist/component/ticketCustomFields.js.map +1 -0
  125. package/dist/component/ticketExport.d.ts +83 -0
  126. package/dist/component/ticketExport.d.ts.map +1 -0
  127. package/dist/component/ticketExport.js +182 -0
  128. package/dist/component/ticketExport.js.map +1 -0
  129. package/dist/component/ticketHistory.d.ts +57 -0
  130. package/dist/component/ticketHistory.d.ts.map +1 -0
  131. package/dist/component/ticketHistory.js +81 -0
  132. package/dist/component/ticketHistory.js.map +1 -0
  133. package/dist/component/ticketMacros.d.ts +141 -0
  134. package/dist/component/ticketMacros.d.ts.map +1 -0
  135. package/dist/component/ticketMacros.js +255 -0
  136. package/dist/component/ticketMacros.js.map +1 -0
  137. package/dist/component/ticketStatuses.d.ts +60 -0
  138. package/dist/component/ticketStatuses.d.ts.map +1 -0
  139. package/dist/component/ticketStatuses.js +110 -0
  140. package/dist/component/ticketStatuses.js.map +1 -0
  141. package/dist/component/ticketTriggers.d.ts +408 -0
  142. package/dist/component/ticketTriggers.d.ts.map +1 -0
  143. package/dist/component/ticketTriggers.js +941 -0
  144. package/dist/component/ticketTriggers.js.map +1 -0
  145. package/dist/component/userProfiles.d.ts +259 -0
  146. package/dist/component/userProfiles.d.ts.map +1 -0
  147. package/dist/component/userProfiles.js +634 -0
  148. package/dist/component/userProfiles.js.map +1 -0
  149. package/dist/component/vendorArticles.d.ts +64 -0
  150. package/dist/component/vendorArticles.d.ts.map +1 -0
  151. package/dist/component/vendorArticles.js +116 -0
  152. package/dist/component/vendorArticles.js.map +1 -0
  153. package/dist/test.d.ts +1302 -0
  154. package/dist/test.d.ts.map +1 -0
  155. package/dist/test.js +7 -0
  156. package/dist/test.js.map +1 -0
  157. package/package.json +71 -0
  158. package/src/client/index.ts +344 -0
  159. package/src/component/_generated/api.ts +110 -0
  160. package/src/component/_generated/component.ts +2460 -0
  161. package/src/component/_generated/dataModel.ts +60 -0
  162. package/src/component/_generated/server.ts +156 -0
  163. package/src/component/apiKeys.ts +229 -0
  164. package/src/component/clinics.ts +136 -0
  165. package/src/component/contracts.ts +136 -0
  166. package/src/component/convex.config.js +2 -0
  167. package/src/component/convex.config.ts +3 -0
  168. package/src/component/crons.ts +18 -0
  169. package/src/component/dashboardStats.ts +141 -0
  170. package/src/component/dashboardStatsCache.ts +145 -0
  171. package/src/component/deviceCategories.ts +280 -0
  172. package/src/component/deviceQuestions.ts +225 -0
  173. package/src/component/deviceRepairHistory.ts +94 -0
  174. package/src/component/deviceStatus.ts +79 -0
  175. package/src/component/devices.ts +645 -0
  176. package/src/component/emailHelpers.ts +38 -0
  177. package/src/component/emails.ts +61 -0
  178. package/src/component/http.ts +231 -0
  179. package/src/component/maintenanceTasks.ts +1003 -0
  180. package/src/component/roles.ts +99 -0
  181. package/src/component/schema.ts +842 -0
  182. package/src/component/slaMonitoring.ts +125 -0
  183. package/src/component/slaRules.ts +231 -0
  184. package/src/component/sparePartOrders.ts +290 -0
  185. package/src/component/spareParts.ts +362 -0
  186. package/src/component/supplierCategories.ts +65 -0
  187. package/src/component/suppliers.ts +234 -0
  188. package/src/component/ticketComments.ts +288 -0
  189. package/src/component/ticketCustomFields.ts +260 -0
  190. package/src/component/ticketExport.ts +220 -0
  191. package/src/component/ticketHistory.ts +106 -0
  192. package/src/component/ticketMacros.ts +291 -0
  193. package/src/component/ticketStatuses.ts +109 -0
  194. package/src/component/ticketTriggers.ts +1152 -0
  195. package/src/component/userProfiles.ts +745 -0
  196. package/src/component/vendorArticles.ts +139 -0
  197. package/src/test.ts +15 -0
@@ -0,0 +1,280 @@
1
+ import { v } from 'convex/values';
2
+ import { mutation, query } from './_generated/server';
3
+
4
+ // Import all categories from JSON
5
+ export const importCategories = mutation({
6
+ args: {
7
+ categories: v.array(v.object({
8
+ id: v.union(v.number(), v.null()),
9
+ name: v.union(v.string(), v.null()),
10
+ parent_id: v.union(v.number(), v.null()),
11
+ active_warehouse_category: v.union(v.number(), v.null()),
12
+ oris_id: v.union(v.string(), v.null()),
13
+ type: v.union(v.string(), v.null()),
14
+ created_at: v.union(v.string(), v.null()),
15
+ updated_at: v.union(v.string(), v.null()),
16
+ note: v.union(v.string(), v.null()),
17
+ })),
18
+ },
19
+ handler: async (ctx, args) => {
20
+ let imported = 0;
21
+ let skipped = 0;
22
+ let errors = 0;
23
+
24
+ for (const category of args.categories) {
25
+ try {
26
+ // Skip if no ID or name
27
+ if (!category.id || !category.name) {
28
+ skipped++;
29
+ continue;
30
+ }
31
+
32
+ // Check if already exists
33
+ const existing = await ctx.db
34
+ .query('device_categories')
35
+ .withIndex('by_primoupId', (q) => q.eq('primoupId', category.id as number))
36
+ .first();
37
+
38
+ if (existing) {
39
+ // Update existing
40
+ await ctx.db.patch(existing._id, {
41
+ name: category.name,
42
+ parentId: category.parent_id || undefined,
43
+ activeWarehouseCategory: category.active_warehouse_category || undefined,
44
+ orisId: category.oris_id || undefined,
45
+ type: category.type || undefined,
46
+ note: category.note || undefined,
47
+ createdAt: category.created_at || undefined,
48
+ updatedAt: category.updated_at || undefined,
49
+ importedAt: Date.now(),
50
+ });
51
+ } else {
52
+ // Insert new
53
+ await ctx.db.insert('device_categories', {
54
+ primoupId: category.id,
55
+ name: category.name,
56
+ parentId: category.parent_id || undefined,
57
+ activeWarehouseCategory: category.active_warehouse_category || undefined,
58
+ orisId: category.oris_id || undefined,
59
+ type: category.type || undefined,
60
+ note: category.note || undefined,
61
+ createdAt: category.created_at || undefined,
62
+ updatedAt: category.updated_at || undefined,
63
+ importedAt: Date.now(),
64
+ });
65
+ }
66
+
67
+ imported++;
68
+ } catch (error) {
69
+ console.error(`Error importing category ${category.id}:`, error);
70
+ errors++;
71
+ }
72
+ }
73
+
74
+ return {
75
+ success: true,
76
+ imported,
77
+ skipped,
78
+ errors,
79
+ total: args.categories.length,
80
+ };
81
+ },
82
+ });
83
+
84
+ // Get all categories
85
+ export const list = query({
86
+ args: {
87
+ search: v.optional(v.string()),
88
+ noteFilter: v.optional(v.string()), // null, "1", "2", "3"
89
+ typeFilter: v.optional(v.string()),
90
+ },
91
+ handler: async (ctx, args) => {
92
+ let categories = await ctx.db.query('device_categories').collect();
93
+
94
+ // Apply filters
95
+ if (args.search) {
96
+ const searchLower = args.search.toLowerCase();
97
+ categories = categories.filter((c) =>
98
+ c.name.toLowerCase().includes(searchLower) ||
99
+ (c.primoupId && c.primoupId.toString().includes(searchLower))
100
+ );
101
+ }
102
+
103
+ if (args.noteFilter) {
104
+ if (args.noteFilter === 'null') {
105
+ categories = categories.filter((c) => !c.note || c.note === '');
106
+ } else {
107
+ categories = categories.filter((c) => c.note === args.noteFilter);
108
+ }
109
+ }
110
+
111
+ if (args.typeFilter) {
112
+ categories = categories.filter((c) => c.type === args.typeFilter);
113
+ }
114
+
115
+ // Sort by name
116
+ categories.sort((a, b) => a.name.localeCompare(b.name));
117
+
118
+ return categories;
119
+ },
120
+ });
121
+
122
+ // Get category by ID
123
+ export const get = query({
124
+ args: { id: v.id('device_categories') },
125
+ handler: async (ctx, args) => {
126
+ return await ctx.db.get(args.id);
127
+ },
128
+ });
129
+
130
+ // Get category by PrimoUP ID
131
+ export const getByPrimoupId = query({
132
+ args: { primoupId: v.number() },
133
+ handler: async (ctx, args) => {
134
+ return await ctx.db
135
+ .query('device_categories')
136
+ .withIndex('by_primoupId', (q) => q.eq('primoupId', args.primoupId))
137
+ .first();
138
+ },
139
+ });
140
+
141
+ // Create category
142
+ export const create = mutation({
143
+ args: {
144
+ primoupId: v.number(),
145
+ name: v.string(),
146
+ parentId: v.optional(v.number()),
147
+ activeWarehouseCategory: v.optional(v.number()),
148
+ orisId: v.optional(v.string()),
149
+ type: v.optional(v.string()),
150
+ note: v.optional(v.string()),
151
+ createdAt: v.optional(v.string()),
152
+ updatedAt: v.optional(v.string()),
153
+ },
154
+ handler: async (ctx, args) => {
155
+ // Check if already exists
156
+ const existing = await ctx.db
157
+ .query('device_categories')
158
+ .withIndex('by_primoupId', (q) => q.eq('primoupId', args.primoupId))
159
+ .first();
160
+
161
+ if (existing) {
162
+ throw new Error(`Category with PrimoUP ID ${args.primoupId} already exists`);
163
+ }
164
+
165
+ const categoryId = await ctx.db.insert('device_categories', {
166
+ primoupId: args.primoupId,
167
+ name: args.name,
168
+ parentId: args.parentId,
169
+ activeWarehouseCategory: args.activeWarehouseCategory,
170
+ orisId: args.orisId,
171
+ type: args.type,
172
+ note: args.note,
173
+ createdAt: args.createdAt,
174
+ updatedAt: args.updatedAt,
175
+ importedAt: Date.now(),
176
+ });
177
+
178
+ return categoryId;
179
+ },
180
+ });
181
+
182
+ // Update category
183
+ export const update = mutation({
184
+ args: {
185
+ id: v.id('device_categories'),
186
+ name: v.optional(v.string()),
187
+ parentId: v.optional(v.number()),
188
+ activeWarehouseCategory: v.optional(v.number()),
189
+ orisId: v.optional(v.string()),
190
+ type: v.optional(v.string()),
191
+ note: v.optional(v.string()),
192
+ createdAt: v.optional(v.string()),
193
+ updatedAt: v.optional(v.string()),
194
+ },
195
+ handler: async (ctx, args) => {
196
+ const { id, ...updates } = args;
197
+
198
+ await ctx.db.patch(id, {
199
+ ...updates,
200
+ importedAt: Date.now(),
201
+ });
202
+
203
+ return id;
204
+ },
205
+ });
206
+
207
+ // Delete category
208
+ export const remove = mutation({
209
+ args: { id: v.id('device_categories') },
210
+ handler: async (ctx, args) => {
211
+ await ctx.db.delete(args.id);
212
+ return { success: true };
213
+ },
214
+ });
215
+
216
+ // Get statistics
217
+ export const stats = query({
218
+ args: {},
219
+ handler: async (ctx) => {
220
+ const categories = await ctx.db.query('device_categories').collect();
221
+
222
+ const byType: Record<string, number> = {};
223
+
224
+ const result = {
225
+ total: categories.length,
226
+ byNote: {
227
+ null: 0,
228
+ '1': 0,
229
+ '2': 0,
230
+ '3': 0,
231
+ } as Record<string, number>,
232
+ byType,
233
+ withParent: 0,
234
+ withoutParent: 0,
235
+ };
236
+
237
+ categories.forEach((cat) => {
238
+ // Count by note
239
+ if (!cat.note || cat.note === '') {
240
+ result.byNote['null']++;
241
+ } else if (cat.note === '1') {
242
+ result.byNote['1']++;
243
+ } else if (cat.note === '2') {
244
+ result.byNote['2']++;
245
+ } else if (cat.note === '3') {
246
+ result.byNote['3']++;
247
+ }
248
+
249
+ // Count by type
250
+ const type = cat.type || 'unknown';
251
+ result.byType[type] = (result.byType[type] || 0) + 1;
252
+
253
+ // Count parent/child
254
+ if (cat.parentId) {
255
+ result.withParent++;
256
+ } else {
257
+ result.withoutParent++;
258
+ }
259
+ });
260
+
261
+ return result;
262
+ },
263
+ });
264
+
265
+ // Clear all categories (for re-import)
266
+ export const deleteAll = mutation({
267
+ args: {},
268
+ handler: async (ctx) => {
269
+ const categories = await ctx.db.query('device_categories').collect();
270
+
271
+ for (const category of categories) {
272
+ await ctx.db.delete(category._id);
273
+ }
274
+
275
+ return {
276
+ success: true,
277
+ deleted: categories.length,
278
+ };
279
+ },
280
+ });
@@ -0,0 +1,225 @@
1
+ import { v } from 'convex/values';
2
+ import { mutation, query } from './_generated/server';
3
+
4
+ export const listQuestionsByDevice = query({
5
+ args: {
6
+ deviceId: v.id('devices'),
7
+ },
8
+ handler: async (ctx, args) => {
9
+ const questions = await ctx.db
10
+ .query('device_questions')
11
+ .withIndex('by_deviceId', (q) => q.eq('deviceId', args.deviceId))
12
+ .filter((q) => q.eq(q.field('isActive'), true))
13
+ .collect();
14
+
15
+ return questions.sort((a, b) => a.order - b.order);
16
+ },
17
+ });
18
+
19
+ export const listAllQuestions = query({
20
+ args: {},
21
+ handler: async (ctx) => {
22
+ try {
23
+ const questions = await ctx.db
24
+ .query('device_questions')
25
+ .collect();
26
+
27
+ if (questions.length === 0) {
28
+ return [];
29
+ }
30
+
31
+ const questionsWithDevices = await Promise.all(
32
+ questions.map(async (question) => {
33
+ const device = await ctx.db.get(question.deviceId);
34
+ return {
35
+ ...question,
36
+ deviceName: device?.name || 'Attrezzatura non trovata',
37
+ deviceCategory: device?.category || '',
38
+ };
39
+ })
40
+ );
41
+
42
+ return questionsWithDevices.sort((a, b) => {
43
+ if (a.deviceName !== b.deviceName) {
44
+ return a.deviceName.localeCompare(b.deviceName);
45
+ }
46
+ return a.order - b.order;
47
+ });
48
+ } catch (error: any) {
49
+ console.error('Error in listAllQuestions:', error);
50
+ throw error;
51
+ }
52
+ },
53
+ });
54
+
55
+ export const getQuestion = query({
56
+ args: {
57
+ questionId: v.id('device_questions'),
58
+ },
59
+ handler: async (ctx, args) => {
60
+ return await ctx.db.get(args.questionId);
61
+ },
62
+ });
63
+
64
+ export const createQuestion = mutation({
65
+ args: {
66
+ deviceId: v.id('devices'),
67
+ question: v.string(),
68
+ questionType: v.union(
69
+ v.literal('text'),
70
+ v.literal('textarea'),
71
+ v.literal('select'),
72
+ v.literal('multiselect'),
73
+ v.literal('yes_no'),
74
+ v.literal('number'),
75
+ v.literal('date')
76
+ ),
77
+ options: v.optional(v.array(v.string())),
78
+ isRequired: v.boolean(),
79
+ order: v.number(),
80
+ placeholder: v.optional(v.string()),
81
+ helpText: v.optional(v.string()),
82
+ conditionalLogic: v.optional(v.object({
83
+ enabled: v.boolean(),
84
+ conditions: v.array(v.object({
85
+ questionId: v.id('device_questions'),
86
+ operator: v.union(
87
+ v.literal('equals'),
88
+ v.literal('not_equals'),
89
+ v.literal('contains'),
90
+ v.literal('not_contains'),
91
+ v.literal('greater_than'),
92
+ v.literal('less_than'),
93
+ v.literal('is_empty'),
94
+ v.literal('is_not_empty')
95
+ ),
96
+ value: v.any(),
97
+ })),
98
+ logic: v.union(v.literal('AND'), v.literal('OR')),
99
+ })),
100
+ createdBy: v.string(),
101
+ },
102
+ handler: async (ctx, args) => {
103
+ const device = await ctx.db.get(args.deviceId);
104
+ if (!device) {
105
+ throw new Error('Attrezzatura non trovata');
106
+ }
107
+
108
+ const questionId = await ctx.db.insert('device_questions', {
109
+ deviceId: args.deviceId,
110
+ question: args.question,
111
+ questionType: args.questionType,
112
+ options: args.options,
113
+ isRequired: args.isRequired,
114
+ order: args.order,
115
+ placeholder: args.placeholder,
116
+ helpText: args.helpText,
117
+ conditionalLogic: args.conditionalLogic,
118
+ isActive: true,
119
+ createdAt: Date.now(),
120
+ updatedAt: Date.now(),
121
+ createdBy: args.createdBy,
122
+ });
123
+
124
+ return questionId;
125
+ },
126
+ });
127
+
128
+ export const updateQuestion = mutation({
129
+ args: {
130
+ questionId: v.id('device_questions'),
131
+ question: v.optional(v.string()),
132
+ questionType: v.optional(
133
+ v.union(
134
+ v.literal('text'),
135
+ v.literal('textarea'),
136
+ v.literal('select'),
137
+ v.literal('multiselect'),
138
+ v.literal('yes_no'),
139
+ v.literal('number'),
140
+ v.literal('date')
141
+ )
142
+ ),
143
+ options: v.optional(v.array(v.string())),
144
+ isRequired: v.optional(v.boolean()),
145
+ order: v.optional(v.number()),
146
+ placeholder: v.optional(v.string()),
147
+ helpText: v.optional(v.string()),
148
+ conditionalLogic: v.optional(v.object({
149
+ enabled: v.boolean(),
150
+ conditions: v.array(v.object({
151
+ questionId: v.id('device_questions'),
152
+ operator: v.union(
153
+ v.literal('equals'),
154
+ v.literal('not_equals'),
155
+ v.literal('contains'),
156
+ v.literal('not_contains'),
157
+ v.literal('greater_than'),
158
+ v.literal('less_than'),
159
+ v.literal('is_empty'),
160
+ v.literal('is_not_empty')
161
+ ),
162
+ value: v.any(),
163
+ })),
164
+ logic: v.union(v.literal('AND'), v.literal('OR')),
165
+ })),
166
+ isActive: v.optional(v.boolean()),
167
+ },
168
+ handler: async (ctx, args) => {
169
+ const question = await ctx.db.get(args.questionId);
170
+ if (!question) {
171
+ throw new Error('Domanda non trovata');
172
+ }
173
+
174
+ const updates: any = {
175
+ updatedAt: Date.now(),
176
+ };
177
+
178
+ if (args.question !== undefined) updates.question = args.question;
179
+ if (args.questionType !== undefined) updates.questionType = args.questionType;
180
+ if (args.options !== undefined) updates.options = args.options;
181
+ if (args.isRequired !== undefined) updates.isRequired = args.isRequired;
182
+ if (args.order !== undefined) updates.order = args.order;
183
+ if (args.placeholder !== undefined) updates.placeholder = args.placeholder;
184
+ if (args.helpText !== undefined) updates.helpText = args.helpText;
185
+ if (args.conditionalLogic !== undefined) updates.conditionalLogic = args.conditionalLogic;
186
+ if (args.isActive !== undefined) updates.isActive = args.isActive;
187
+
188
+ await ctx.db.patch(args.questionId, updates);
189
+
190
+ return args.questionId;
191
+ },
192
+ });
193
+
194
+ export const deleteQuestion = mutation({
195
+ args: {
196
+ questionId: v.id('device_questions'),
197
+ },
198
+ handler: async (ctx, args) => {
199
+ await ctx.db.patch(args.questionId, {
200
+ isActive: false,
201
+ updatedAt: Date.now(),
202
+ });
203
+
204
+ return args.questionId;
205
+ },
206
+ });
207
+
208
+ export const reorderQuestions = mutation({
209
+ args: {
210
+ deviceId: v.id('devices'),
211
+ questionIds: v.array(v.id('device_questions')),
212
+ },
213
+ handler: async (ctx, args) => {
214
+ await Promise.all(
215
+ args.questionIds.map((questionId, index) =>
216
+ ctx.db.patch(questionId, {
217
+ order: index,
218
+ updatedAt: Date.now(),
219
+ })
220
+ )
221
+ );
222
+
223
+ return true;
224
+ },
225
+ });
@@ -0,0 +1,94 @@
1
+ import { v } from 'convex/values';
2
+ import { query, mutation } from './_generated/server';
3
+
4
+ export const getRepairHistory = query({
5
+ args: {
6
+ deviceId: v.id('devices'),
7
+ },
8
+ handler: async (ctx, args) => {
9
+ const repairs = await ctx.db
10
+ .query('device_repair_history')
11
+ .withIndex('by_deviceId', (q: any) => q.eq('deviceId', args.deviceId))
12
+ .take(100);
13
+
14
+ return repairs.sort((a, b) => b.sentToRepairAt - a.sentToRepairAt);
15
+ },
16
+ });
17
+
18
+ export const sendToRepair = mutation({
19
+ args: {
20
+ deviceId: v.id('devices'),
21
+ ticketId: v.optional(v.id('maintenance_tasks')),
22
+ notes: v.optional(v.string()),
23
+ createdBy: v.string(),
24
+ },
25
+ handler: async (ctx, args) => {
26
+ const device = await ctx.db.get(args.deviceId);
27
+ if (!device) {
28
+ throw new Error('Device not found');
29
+ }
30
+
31
+ const repairId = await ctx.db.insert('device_repair_history', {
32
+ deviceId: args.deviceId,
33
+ clinicId: device.clinicId,
34
+ ticketId: args.ticketId,
35
+ sentToRepairAt: Date.now(),
36
+ notes: args.notes,
37
+ createdBy: args.createdBy,
38
+ });
39
+
40
+ await ctx.db.patch(args.deviceId, {
41
+ status: 'in_maintenance',
42
+ });
43
+
44
+ return repairId;
45
+ },
46
+ });
47
+
48
+ export const returnFromRepair = mutation({
49
+ args: {
50
+ deviceId: v.id('devices'),
51
+ statusAfterRepair: v.union(v.literal('active'), v.literal('out_of_service')),
52
+ notes: v.optional(v.string()),
53
+ returnedBy: v.string(),
54
+ },
55
+ handler: async (ctx, args) => {
56
+ const device = await ctx.db.get(args.deviceId);
57
+ if (!device) {
58
+ throw new Error('Device not found');
59
+ }
60
+
61
+ const repairs = await ctx.db
62
+ .query('device_repair_history')
63
+ .withIndex('by_deviceId', (q: any) => q.eq('deviceId', args.deviceId))
64
+ .take(100);
65
+
66
+ const openRepair = repairs.find((r) => !r.returnedAt);
67
+
68
+ if (openRepair) {
69
+ await ctx.db.patch(openRepair._id, {
70
+ returnedAt: Date.now(),
71
+ statusAfterRepair: args.statusAfterRepair,
72
+ notes: args.notes || openRepair.notes,
73
+ returnedBy: args.returnedBy,
74
+ });
75
+ } else {
76
+ await ctx.db.insert('device_repair_history', {
77
+ deviceId: args.deviceId,
78
+ clinicId: device.clinicId,
79
+ sentToRepairAt: Date.now(),
80
+ returnedAt: Date.now(),
81
+ statusAfterRepair: args.statusAfterRepair,
82
+ notes: args.notes,
83
+ createdBy: args.returnedBy,
84
+ returnedBy: args.returnedBy,
85
+ });
86
+ }
87
+
88
+ await ctx.db.patch(args.deviceId, {
89
+ status: args.statusAfterRepair,
90
+ });
91
+
92
+ return { success: true };
93
+ },
94
+ });
@@ -0,0 +1,79 @@
1
+ import { query } from './_generated/server';
2
+ import { v } from 'convex/values';
3
+
4
+ export const computeDeviceStatus = query({
5
+ args: { deviceId: v.id('devices') },
6
+ handler: async (ctx, { deviceId }) => {
7
+ const tickets = await ctx.db
8
+ .query('maintenance_tasks')
9
+ .withIndex('by_deviceId', (q) => q.eq('deviceId', deviceId))
10
+ .take(200);
11
+
12
+ if (tickets.length === 0) {
13
+ return 'in_uso';
14
+ }
15
+
16
+ const smaltito = tickets.find(
17
+ (t) =>
18
+ t.status === 'smaltito' &&
19
+ t.updated_at &&
20
+ t.created_at < t.updated_at
21
+ );
22
+ if (smaltito) {
23
+ return 'smaltito';
24
+ }
25
+
26
+ const raee = tickets.find(
27
+ (t) =>
28
+ t.description?.toLowerCase().includes('raee') ||
29
+ t.description?.toLowerCase().includes('smaltimento')
30
+ );
31
+ if (raee && raee.status !== 'closed' && raee.status !== 'completed') {
32
+ return 'da_smaltire';
33
+ }
34
+
35
+ const openTickets = tickets.filter(
36
+ (t) =>
37
+ t.status === 'open' ||
38
+ t.status === 'assigned_to_supplier' ||
39
+ t.status === 'in_progress' ||
40
+ t.status === 'pending'
41
+ );
42
+
43
+ if (openTickets.length > 0) {
44
+ return 'guasto';
45
+ }
46
+
47
+ return 'in_uso';
48
+ },
49
+ });
50
+
51
+ export const getDeviceTicketHistory = query({
52
+ args: { deviceId: v.id('devices') },
53
+ handler: async (ctx, { deviceId }) => {
54
+ const tickets = await ctx.db
55
+ .query('maintenance_tasks')
56
+ .withIndex('by_deviceId', (q) => q.eq('deviceId', deviceId))
57
+ .order('desc')
58
+ .take(50);
59
+
60
+ const enrichedTickets = await Promise.all(
61
+ tickets.map(async (ticket) => {
62
+ const clinic = ticket.clinicId
63
+ ? await ctx.db.get(ticket.clinicId)
64
+ : null;
65
+ const supplier = ticket.supplierId
66
+ ? await ctx.db.get(ticket.supplierId)
67
+ : null;
68
+
69
+ return {
70
+ ...ticket,
71
+ clinic,
72
+ supplier,
73
+ };
74
+ })
75
+ );
76
+
77
+ return enrichedTickets;
78
+ },
79
+ });