@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,61 @@
1
+ import { v } from 'convex/values';
2
+ import { mutation, query } from './_generated/server';
3
+
4
+ export const logEmail = mutation({
5
+ args: {
6
+ from: v.string(),
7
+ to: v.union(v.string(), v.array(v.string())),
8
+ subject: v.string(),
9
+ html: v.string(),
10
+ ticketId: v.optional(v.id('maintenance_tasks')),
11
+ status: v.union(v.literal('sent'), v.literal('failed'), v.literal('pending')),
12
+ resendId: v.optional(v.string()),
13
+ errorMessage: v.optional(v.string()),
14
+ },
15
+ handler: async (ctx, args) => {
16
+ return await ctx.db.insert('email_logs', {
17
+ from: args.from,
18
+ to: args.to,
19
+ subject: args.subject,
20
+ html: args.html,
21
+ ticketId: args.ticketId,
22
+ status: args.status,
23
+ resendId: args.resendId,
24
+ errorMessage: args.errorMessage,
25
+ sentAt: Date.now(),
26
+ });
27
+ },
28
+ });
29
+
30
+ export const getEmailLogsByTicket = query({
31
+ args: { ticketId: v.id('maintenance_tasks') },
32
+ handler: async (ctx, args) => {
33
+ return await ctx.db
34
+ .query('email_logs')
35
+ .withIndex('by_ticketId', (q) => q.eq('ticketId', args.ticketId))
36
+ .order('desc')
37
+ .collect();
38
+ },
39
+ });
40
+
41
+ export const getAllEmailLogs = query({
42
+ args: { limit: v.optional(v.number()) },
43
+ handler: async (ctx, args) => {
44
+ return await ctx.db
45
+ .query('email_logs')
46
+ .withIndex('by_sentAt')
47
+ .order('desc')
48
+ .take(args.limit || 100);
49
+ },
50
+ });
51
+
52
+ export const getFailedEmails = query({
53
+ args: { limit: v.optional(v.number()) },
54
+ handler: async (ctx, args) => {
55
+ return await ctx.db
56
+ .query('email_logs')
57
+ .withIndex('by_status', (q) => q.eq('status', 'failed'))
58
+ .order('desc')
59
+ .take(args.limit || 50);
60
+ },
61
+ });
@@ -0,0 +1,231 @@
1
+ import { httpRouter } from 'convex/server';
2
+ import { httpAction } from './_generated/server';
3
+ import { api, internal } from './_generated/api';
4
+
5
+ const http = httpRouter();
6
+
7
+ const corsHeaders = {
8
+ 'Access-Control-Allow-Origin': '*',
9
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
10
+ 'Access-Control-Allow-Headers': 'Content-Type, X-API-Key, Authorization',
11
+ };
12
+
13
+ function jsonResponse(data: any, status: number = 200) {
14
+ return new Response(JSON.stringify(data), {
15
+ status,
16
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
17
+ });
18
+ }
19
+
20
+ function errorResponse(message: string, status: number = 400) {
21
+ return jsonResponse({ success: false, error: message }, status);
22
+ }
23
+
24
+ http.route({
25
+ path: '/api/comments/add',
26
+ method: 'OPTIONS',
27
+ handler: httpAction(async () => {
28
+ return new Response(null, { status: 204, headers: corsHeaders });
29
+ }),
30
+ });
31
+
32
+ http.route({
33
+ path: '/api/comments/add',
34
+ method: 'POST',
35
+ handler: httpAction(async (ctx, request) => {
36
+ try {
37
+ const apiKey = request.headers.get('X-API-Key');
38
+ if (!apiKey) return errorResponse('API key mancante. Usa header X-API-Key', 401);
39
+
40
+ const keyData = await ctx.runQuery(api.apiKeys.validateApiKey, { plainKey: apiKey });
41
+ if (!keyData) return errorResponse('API key non valida o scaduta', 401);
42
+ if (!keyData.permissions.includes('comments:add')) {
43
+ return errorResponse('API key non ha il permesso "comments:add"', 403);
44
+ }
45
+
46
+ let body: any;
47
+ try { body = await request.json(); } catch { return errorResponse('Body JSON non valido', 400); }
48
+
49
+ const { ticketId, content, authorEmail, authorName, isInternal } = body;
50
+ if (!ticketId) return errorResponse('Campo "ticketId" obbligatorio', 400);
51
+ if (!content || typeof content !== 'string' || content.trim().length === 0) {
52
+ return errorResponse('Campo "content" obbligatorio e non vuoto', 400);
53
+ }
54
+
55
+ try {
56
+ const commentId = await ctx.runMutation(api.ticketComments.addCommentFromApi, {
57
+ taskId: ticketId,
58
+ content: content.trim(),
59
+ authorEmail: authorEmail || undefined,
60
+ authorName: authorName || undefined,
61
+ isInternal: isInternal || false,
62
+ });
63
+ await ctx.runMutation(internal.apiKeys.recordApiKeyUsage, { keyId: keyData.keyId });
64
+ return jsonResponse({ success: true, commentId, message: 'Commento aggiunto con successo' });
65
+ } catch (error: any) {
66
+ if (error.message?.includes('Ticket not found')) return errorResponse('Ticket non trovato', 404);
67
+ throw error;
68
+ }
69
+ } catch (error: any) {
70
+ console.error('Error in /api/comments/add:', error);
71
+ return errorResponse('Errore interno del server: ' + error.message, 500);
72
+ }
73
+ }),
74
+ });
75
+
76
+ http.route({
77
+ path: '/api/comments/list',
78
+ method: 'OPTIONS',
79
+ handler: httpAction(async () => {
80
+ return new Response(null, { status: 204, headers: corsHeaders });
81
+ }),
82
+ });
83
+
84
+ http.route({
85
+ path: '/api/comments/list',
86
+ method: 'GET',
87
+ handler: httpAction(async (ctx, request) => {
88
+ try {
89
+ const apiKey = request.headers.get('X-API-Key');
90
+ if (!apiKey) return errorResponse('API key mancante', 401);
91
+
92
+ const keyData = await ctx.runQuery(api.apiKeys.validateApiKey, { plainKey: apiKey });
93
+ if (!keyData) return errorResponse('API key non valida', 401);
94
+ if (!keyData.permissions.includes('comments:read')) {
95
+ return errorResponse('API key non ha il permesso "comments:read"', 403);
96
+ }
97
+
98
+ const url = new URL(request.url);
99
+ const ticketId = url.searchParams.get('ticketId');
100
+ if (!ticketId) return errorResponse('Parametro "ticketId" obbligatorio', 400);
101
+
102
+ const comments = await ctx.runQuery(internal.ticketComments.listCommentsInternal, { taskId: ticketId });
103
+ await ctx.runMutation(internal.apiKeys.recordApiKeyUsage, { keyId: keyData.keyId });
104
+
105
+ return jsonResponse({ success: true, comments, count: comments.length });
106
+ } catch (error: any) {
107
+ console.error('Error in /api/comments/list:', error);
108
+ return errorResponse('Errore interno: ' + error.message, 500);
109
+ }
110
+ }),
111
+ });
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
+ http.route({
219
+ path: '/api/health',
220
+ method: 'GET',
221
+ handler: httpAction(async () => {
222
+ return jsonResponse({
223
+ success: true,
224
+ status: 'healthy',
225
+ timestamp: new Date().toISOString(),
226
+ version: '1.0.0',
227
+ });
228
+ }),
229
+ });
230
+
231
+ export default http;