@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,362 @@
1
+ import { v } from 'convex/values';
2
+ import { mutation, query } from './_generated/server';
3
+ import { paginationOptsValidator } from 'convex/server';
4
+
5
+ export const importSparePartsBatch = mutation({
6
+ args: {
7
+ spareParts: v.array(v.object({
8
+ warehouseItemId: v.number(),
9
+ name: v.string(),
10
+ quantity: v.optional(v.number()),
11
+ dentalUnitQuantity: v.optional(v.number()),
12
+ minQuantity: v.optional(v.number()),
13
+ minimumStockAmount: v.optional(v.number()),
14
+ lot: v.optional(v.string()),
15
+ netPrice: v.optional(v.number()),
16
+ deadlineDate: v.optional(v.string()),
17
+ warehouseOrisId: v.optional(v.string()),
18
+ articleId: v.number(),
19
+ clinicId: v.number(),
20
+ vendorId: v.optional(v.number()),
21
+ warehouseCreatedAt: v.optional(v.string()),
22
+ warehouseUpdatedAt: v.optional(v.string()),
23
+ warehouseDeletedAt: v.optional(v.string()),
24
+ articleInternalId: v.optional(v.number()),
25
+ code: v.optional(v.string()),
26
+ barcode: v.optional(v.string()),
27
+ articleName: v.optional(v.string()),
28
+ brand: v.optional(v.string()),
29
+ model: v.optional(v.string()),
30
+ articleCategoryId: v.optional(v.number()),
31
+ mu: v.optional(v.string()),
32
+ sellingUnit: v.optional(v.string()),
33
+ warehouseUnit: v.optional(v.string()),
34
+ case: v.optional(v.string()),
35
+ package: v.optional(v.string()),
36
+ articleOrisId: v.optional(v.string()),
37
+ articleDeadlineDate: v.optional(v.string()),
38
+ alternativeArticleId: v.optional(v.number()),
39
+ type: v.optional(v.string()),
40
+ labelInfosLength: v.optional(v.number()),
41
+ articleCreatedAt: v.optional(v.string()),
42
+ articleUpdatedAt: v.optional(v.string()),
43
+ articleDeletedAt: v.optional(v.string()),
44
+ categoryId: v.optional(v.number()),
45
+ categoryName: v.optional(v.string()),
46
+ parentId: v.optional(v.number()),
47
+ activeWarehouseCategory: v.optional(v.number()),
48
+ categoryOrisId: v.optional(v.string()),
49
+ categoryType: v.optional(v.string()),
50
+ categoryCreatedAt: v.optional(v.string()),
51
+ categoryUpdatedAt: v.optional(v.string()),
52
+ })),
53
+ },
54
+ handler: async (ctx, args) => {
55
+ const insertedIds = [];
56
+
57
+ for (const sparePart of args.spareParts) {
58
+ const id = await ctx.db.insert('spare_parts', sparePart);
59
+ insertedIds.push(id);
60
+ }
61
+
62
+ return {
63
+ success: true,
64
+ count: insertedIds.length,
65
+ ids: insertedIds,
66
+ };
67
+ },
68
+ });
69
+
70
+ export const listSpareParts = query({
71
+ args: {
72
+ limit: v.optional(v.number()),
73
+ _triggerReload: v.optional(v.number()),
74
+ },
75
+ handler: async (ctx, args) => {
76
+ const limit = args.limit || 1000;
77
+
78
+ const spareParts = await ctx.db
79
+ .query('spare_parts')
80
+ .order('desc')
81
+ .take(limit);
82
+
83
+ return spareParts;
84
+ },
85
+ });
86
+
87
+ export const listSparePartsByClinic = query({
88
+ args: {
89
+ clinicId: v.number(),
90
+ limit: v.optional(v.number()),
91
+ },
92
+ handler: async (ctx, args) => {
93
+ const limit = args.limit || 500;
94
+
95
+ const spareParts = await ctx.db
96
+ .query('spare_parts')
97
+ .withIndex('by_clinicId', (q) => q.eq('clinicId', args.clinicId))
98
+ .take(limit);
99
+
100
+ return spareParts;
101
+ },
102
+ });
103
+
104
+ export const getSparePartByWarehouseItemId = query({
105
+ args: { warehouseItemId: v.number() },
106
+ handler: async (ctx, args) => {
107
+ const sparePart = await ctx.db
108
+ .query('spare_parts')
109
+ .withIndex('by_warehouseItemId', (q) => q.eq('warehouseItemId', args.warehouseItemId))
110
+ .first();
111
+
112
+ return sparePart;
113
+ },
114
+ });
115
+
116
+ export const searchSpareParts = query({
117
+ args: {
118
+ searchTerm: v.string(),
119
+ clinicId: v.optional(v.number()),
120
+ limit: v.optional(v.number()),
121
+ },
122
+ handler: async (ctx, args) => {
123
+ const limit = args.limit || 50;
124
+ const maxFetch = 500;
125
+
126
+ let allParts;
127
+
128
+ if (args.clinicId !== undefined) {
129
+ allParts = await ctx.db
130
+ .query('spare_parts')
131
+ .withIndex('by_clinicId', (q) => q.eq('clinicId', args.clinicId!))
132
+ .take(maxFetch);
133
+ } else {
134
+ allParts = await ctx.db.query('spare_parts').take(maxFetch);
135
+ }
136
+
137
+ const searchLower = args.searchTerm.toLowerCase();
138
+ const filtered = allParts.filter(part =>
139
+ part.name?.toLowerCase().includes(searchLower) ||
140
+ part.code?.toLowerCase().includes(searchLower) ||
141
+ part.articleName?.toLowerCase().includes(searchLower)
142
+ );
143
+
144
+ return filtered.slice(0, limit);
145
+ },
146
+ });
147
+
148
+ export const listSparePartsPaginated = query({
149
+ args: {
150
+ paginationOpts: paginationOptsValidator,
151
+ clinicId: v.number(),
152
+ vendorId: v.optional(v.number()),
153
+ searchTerm: v.optional(v.string()),
154
+ categoryId: v.optional(v.number()),
155
+ },
156
+ handler: async (ctx, args) => {
157
+ let baseQuery = ctx.db
158
+ .query('spare_parts')
159
+ .withIndex('by_clinicId', (q) => q.eq('clinicId', args.clinicId));
160
+
161
+ let filteredQuery = baseQuery.order('desc');
162
+
163
+ if (args.vendorId) {
164
+ filteredQuery = filteredQuery.filter((q: any) => q.eq(q.field('vendorId'), args.vendorId));
165
+ }
166
+
167
+ if (args.categoryId) {
168
+ filteredQuery = filteredQuery.filter((q: any) => q.eq(q.field('articleCategoryId'), args.categoryId));
169
+ }
170
+
171
+ const result = await filteredQuery.paginate(args.paginationOpts);
172
+
173
+ let filteredPage = result.page;
174
+
175
+ if (args.searchTerm) {
176
+ const search = args.searchTerm.toLowerCase();
177
+ filteredPage = filteredPage.filter((part: any) =>
178
+ part.name?.toLowerCase().includes(search) ||
179
+ part.code?.toLowerCase().includes(search) ||
180
+ part.articleName?.toLowerCase().includes(search) ||
181
+ part.brand?.toLowerCase().includes(search)
182
+ );
183
+ }
184
+
185
+ const minimalPage = filteredPage.map((part: any) => ({
186
+ _id: part._id,
187
+ name: part.name,
188
+ code: part.code,
189
+ articleName: part.articleName,
190
+ brand: part.brand,
191
+ model: part.model,
192
+ quantity: part.quantity,
193
+ minQuantity: part.minQuantity,
194
+ netPrice: part.netPrice,
195
+ vendorId: part.vendorId,
196
+ categoryName: part.categoryName,
197
+ articleCategoryId: part.articleCategoryId,
198
+ }));
199
+
200
+ return {
201
+ page: minimalPage,
202
+ isDone: result.isDone,
203
+ continueCursor: result.continueCursor,
204
+ };
205
+ },
206
+ });
207
+
208
+ export const getSparePartsByCategory = query({
209
+ args: {
210
+ categoryId: v.number(),
211
+ clinicId: v.optional(v.number()),
212
+ limit: v.optional(v.number()),
213
+ },
214
+ handler: async (ctx, args) => {
215
+ const limit = args.limit || 100;
216
+
217
+ const spareParts = await ctx.db
218
+ .query('spare_parts')
219
+ .withIndex('by_articleCategoryId', (q) => q.eq('articleCategoryId', args.categoryId))
220
+ .take(limit);
221
+
222
+ if (args.clinicId) {
223
+ return spareParts.filter(part => part.clinicId === args.clinicId);
224
+ }
225
+
226
+ return spareParts;
227
+ },
228
+ });
229
+
230
+ export const getAllSparePartsForMigration = query({
231
+ args: {
232
+ lastId: v.optional(v.id('spare_parts')),
233
+ batchSize: v.optional(v.number()),
234
+ },
235
+ handler: async (ctx, args) => {
236
+ const batchSize = args.batchSize || 1000;
237
+
238
+ let query = ctx.db.query('spare_parts');
239
+
240
+ if (args.lastId) {
241
+ const lastRecord = await ctx.db.get(args.lastId);
242
+ if (lastRecord) {
243
+ query = query.filter((q) => q.gt(q.field('_creationTime'), lastRecord._creationTime));
244
+ }
245
+ }
246
+
247
+ const spareParts = await query
248
+ .order('asc')
249
+ .take(batchSize);
250
+
251
+ return {
252
+ spareParts,
253
+ hasMore: spareParts.length === batchSize,
254
+ lastId: spareParts.length > 0 ? spareParts[spareParts.length - 1]._id : null,
255
+ };
256
+ },
257
+ });
258
+
259
+ export const createSparePart = mutation({
260
+ args: {
261
+ warehouseItemId: v.number(),
262
+ name: v.string(),
263
+ quantity: v.optional(v.number()),
264
+ dentalUnitQuantity: v.optional(v.number()),
265
+ minQuantity: v.optional(v.number()),
266
+ minimumStockAmount: v.optional(v.number()),
267
+ lot: v.optional(v.string()),
268
+ netPrice: v.optional(v.number()),
269
+ deadlineDate: v.optional(v.string()),
270
+ warehouseOrisId: v.optional(v.string()),
271
+ articleId: v.number(),
272
+ clinicId: v.number(),
273
+ vendorId: v.optional(v.number()),
274
+ warehouseCreatedAt: v.optional(v.string()),
275
+ warehouseUpdatedAt: v.optional(v.string()),
276
+ warehouseDeletedAt: v.optional(v.string()),
277
+ articleInternalId: v.optional(v.number()),
278
+ code: v.optional(v.string()),
279
+ barcode: v.optional(v.string()),
280
+ articleName: v.optional(v.string()),
281
+ brand: v.optional(v.string()),
282
+ model: v.optional(v.string()),
283
+ articleCategoryId: v.optional(v.number()),
284
+ mu: v.optional(v.string()),
285
+ sellingUnit: v.optional(v.string()),
286
+ warehouseUnit: v.optional(v.string()),
287
+ case: v.optional(v.string()),
288
+ package: v.optional(v.string()),
289
+ articleOrisId: v.optional(v.string()),
290
+ articleDeadlineDate: v.optional(v.string()),
291
+ alternativeArticleId: v.optional(v.number()),
292
+ type: v.optional(v.string()),
293
+ labelInfosLength: v.optional(v.number()),
294
+ articleCreatedAt: v.optional(v.string()),
295
+ articleUpdatedAt: v.optional(v.string()),
296
+ articleDeletedAt: v.optional(v.string()),
297
+ categoryId: v.optional(v.number()),
298
+ categoryName: v.optional(v.string()),
299
+ parentId: v.optional(v.number()),
300
+ activeWarehouseCategory: v.optional(v.number()),
301
+ categoryOrisId: v.optional(v.string()),
302
+ categoryType: v.optional(v.string()),
303
+ categoryCreatedAt: v.optional(v.string()),
304
+ categoryUpdatedAt: v.optional(v.string()),
305
+ },
306
+ handler: async (ctx, args) => {
307
+ const sparePartId = await ctx.db.insert('spare_parts', args);
308
+ return sparePartId;
309
+ },
310
+ });
311
+
312
+ export const updateSparePart = mutation({
313
+ args: {
314
+ sparePartId: v.id('spare_parts'),
315
+ name: v.optional(v.string()),
316
+ quantity: v.optional(v.number()),
317
+ dentalUnitQuantity: v.optional(v.number()),
318
+ minQuantity: v.optional(v.number()),
319
+ minimumStockAmount: v.optional(v.number()),
320
+ lot: v.optional(v.string()),
321
+ netPrice: v.optional(v.number()),
322
+ deadlineDate: v.optional(v.string()),
323
+ code: v.optional(v.string()),
324
+ barcode: v.optional(v.string()),
325
+ articleName: v.optional(v.string()),
326
+ brand: v.optional(v.string()),
327
+ model: v.optional(v.string()),
328
+ mu: v.optional(v.string()),
329
+ vendorId: v.optional(v.number()),
330
+ },
331
+ handler: async (ctx, args) => {
332
+ const { sparePartId, ...updates } = args;
333
+
334
+ await ctx.db.patch(sparePartId, updates);
335
+
336
+ return { success: true };
337
+ },
338
+ });
339
+
340
+ export const deleteSparePart = mutation({
341
+ args: {
342
+ sparePartId: v.id('spare_parts'),
343
+ },
344
+ handler: async (ctx, args) => {
345
+ await ctx.db.delete(args.sparePartId);
346
+ return { success: true };
347
+ },
348
+ });
349
+
350
+ export const updateQuantity = mutation({
351
+ args: {
352
+ sparePartId: v.id('spare_parts'),
353
+ quantity: v.number(),
354
+ },
355
+ handler: async (ctx, args) => {
356
+ await ctx.db.patch(args.sparePartId, {
357
+ quantity: args.quantity,
358
+ });
359
+
360
+ return { success: true };
361
+ },
362
+ });
@@ -0,0 +1,65 @@
1
+ import { v } from 'convex/values';
2
+ import { query, mutation } from './_generated/server';
3
+
4
+ export const listCategories = query({
5
+ args: {},
6
+ handler: async (ctx) => {
7
+ return await ctx.db.query('supplier_categories').order('asc').collect();
8
+ },
9
+ });
10
+
11
+ export const createCategory = mutation({
12
+ args: {
13
+ name: v.string(),
14
+ description: v.optional(v.string()),
15
+ },
16
+ handler: async (ctx, args) => {
17
+ const existing = await ctx.db
18
+ .query('supplier_categories')
19
+ .withIndex('by_name', (q: any) => q.eq('name', args.name.trim()))
20
+ .first();
21
+ if (existing) throw new Error('Category already exists');
22
+ return await ctx.db.insert('supplier_categories', {
23
+ name: args.name.trim(),
24
+ description: args.description?.trim(),
25
+ createdAt: Date.now(),
26
+ });
27
+ },
28
+ });
29
+
30
+ export const updateCategory = mutation({
31
+ args: {
32
+ categoryId: v.id('supplier_categories'),
33
+ name: v.string(),
34
+ description: v.optional(v.string()),
35
+ },
36
+ handler: async (ctx, args) => {
37
+ const existing = await ctx.db
38
+ .query('supplier_categories')
39
+ .withIndex('by_name', (q: any) => q.eq('name', args.name.trim()))
40
+ .first();
41
+ if (existing && existing._id !== args.categoryId) {
42
+ throw new Error('Category name already exists');
43
+ }
44
+ await ctx.db.patch(args.categoryId, {
45
+ name: args.name.trim(),
46
+ description: args.description?.trim(),
47
+ });
48
+ return args.categoryId;
49
+ },
50
+ });
51
+
52
+ export const deleteCategory = mutation({
53
+ args: { categoryId: v.id('supplier_categories') },
54
+ handler: async (ctx, args) => {
55
+ const category = await ctx.db.get(args.categoryId);
56
+ if (!category) throw new Error('Category not found');
57
+ const suppliers = await ctx.db.query('suppliers').collect();
58
+ const suppliersUsingCategory = suppliers.filter((s) => s.categories.includes(category.name));
59
+ if (suppliersUsingCategory.length > 0) {
60
+ throw new Error(`Cannot delete category: ${suppliersUsingCategory.length} supplier(s) are using it`);
61
+ }
62
+ await ctx.db.delete(args.categoryId);
63
+ return { success: true };
64
+ },
65
+ });
@@ -0,0 +1,234 @@
1
+ import { v } from 'convex/values';
2
+ import { query, mutation } from './_generated/server';
3
+
4
+ export const listSuppliers = query({
5
+ args: {},
6
+ handler: async (ctx) => {
7
+ const suppliers = await ctx.db.query('suppliers').collect();
8
+ return suppliers;
9
+ },
10
+ });
11
+
12
+ export const listSuppliersForReassignment = query({
13
+ args: {},
14
+ handler: async (ctx) => {
15
+ const suppliers = await ctx.db.query('suppliers').collect();
16
+ return suppliers;
17
+ },
18
+ });
19
+
20
+ export const listSuppliersForOrders = query({
21
+ args: {},
22
+ handler: async (ctx) => {
23
+ const suppliers = await ctx.db.query('suppliers').collect();
24
+
25
+ return suppliers.map(s => ({
26
+ _id: s._id,
27
+ name: s.name,
28
+ contact_email: s.contact_email,
29
+ categories: s.categories,
30
+ primoupId: s.primoupId,
31
+ }));
32
+ },
33
+ });
34
+
35
+ export const getSupplier = query({
36
+ args: { supplierId: v.id('suppliers') },
37
+ handler: async (ctx, args) => {
38
+ const supplier = await ctx.db.get(args.supplierId);
39
+ return supplier;
40
+ },
41
+ });
42
+
43
+ export const createSupplier = mutation({
44
+ args: {
45
+ name: v.string(),
46
+ contact_email: v.string(),
47
+ contact_phone: v.string(),
48
+ categories: v.array(v.string()),
49
+ sla_days: v.number(),
50
+ notes: v.optional(v.string()),
51
+ },
52
+ handler: async (ctx, args) => {
53
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
54
+ if (!emailRegex.test(args.contact_email)) {
55
+ throw new Error('Invalid email format');
56
+ }
57
+
58
+ if (args.sla_days < 1 || args.sla_days > 365) {
59
+ throw new Error('SLA days must be between 1 and 365');
60
+ }
61
+
62
+ if (args.categories.length === 0) {
63
+ throw new Error('At least one category is required');
64
+ }
65
+
66
+ const supplierId = await ctx.db.insert('suppliers', {
67
+ name: args.name.trim(),
68
+ contact_email: args.contact_email.trim().toLowerCase(),
69
+ contact_phone: args.contact_phone.trim(),
70
+ categories: args.categories.map((cat) => cat.trim()),
71
+ sla_days: args.sla_days,
72
+ notes: args.notes?.trim(),
73
+ });
74
+
75
+ return supplierId;
76
+ },
77
+ });
78
+
79
+ export const updateSupplier = mutation({
80
+ args: {
81
+ supplierId: v.id('suppliers'),
82
+ name: v.string(),
83
+ contact_email: v.string(),
84
+ contact_phone: v.string(),
85
+ categories: v.array(v.string()),
86
+ sla_days: v.number(),
87
+ notes: v.optional(v.string()),
88
+ },
89
+ handler: async (ctx, args) => {
90
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
91
+ if (!emailRegex.test(args.contact_email)) {
92
+ throw new Error('Invalid email format');
93
+ }
94
+
95
+ if (args.sla_days < 1 || args.sla_days > 365) {
96
+ throw new Error('SLA days must be between 1 and 365');
97
+ }
98
+
99
+ if (args.categories.length === 0) {
100
+ throw new Error('At least one category is required');
101
+ }
102
+
103
+ const { supplierId, ...updates } = args;
104
+ await ctx.db.patch(supplierId, {
105
+ name: updates.name.trim(),
106
+ contact_email: updates.contact_email.trim().toLowerCase(),
107
+ contact_phone: updates.contact_phone.trim(),
108
+ categories: updates.categories.map((cat) => cat.trim()),
109
+ sla_days: updates.sla_days,
110
+ notes: updates.notes?.trim(),
111
+ });
112
+
113
+ return supplierId;
114
+ },
115
+ });
116
+
117
+ export const deleteSupplier = mutation({
118
+ args: { supplierId: v.id('suppliers') },
119
+ handler: async (ctx, args) => {
120
+ const contracts = await ctx.db
121
+ .query('contracts')
122
+ .withIndex('by_supplierId', (q: any) => q.eq('supplierId', args.supplierId))
123
+ .collect();
124
+
125
+ if (contracts.length > 0) {
126
+ throw new Error(
127
+ `Impossibile eliminare il fornitore: ha ${contracts.length} contratto/i attivo/i. ` +
128
+ `Elimina prima i contratti associati.`
129
+ );
130
+ }
131
+
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) {
138
+ throw new Error(
139
+ `Impossibile eliminare il fornitore: ha ${tasks.length} ticket di manutenzione associato/i. ` +
140
+ `Riassegna o elimina prima i ticket.`
141
+ );
142
+ }
143
+
144
+ await ctx.db.delete(args.supplierId);
145
+ return { success: true };
146
+ },
147
+ });
148
+
149
+ export const getSuppliersByCategory = query({
150
+ args: { category: v.string() },
151
+ handler: async (ctx, args) => {
152
+ const suppliers = await ctx.db
153
+ .query('suppliers')
154
+ .filter((q: any) => q.field('categories').includes(args.category))
155
+ .collect();
156
+
157
+ return suppliers;
158
+ },
159
+ });
160
+
161
+ 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();
177
+
178
+ const result = await Promise.all(
179
+ supplierUsers.map(async (user) => {
180
+ const linkedSupplier = user.supplierId
181
+ ? await ctx.db.get(user.supplierId)
182
+ : null;
183
+
184
+ return {
185
+ _id: user._id,
186
+ userId: user._id,
187
+ email: user.email,
188
+ name: user.name || user.email,
189
+ supplierId: user.supplierId || null,
190
+ supplierName: linkedSupplier?.name || null,
191
+ supplierEmail: linkedSupplier?.contact_email || null,
192
+ };
193
+ })
194
+ );
195
+
196
+ return result;
197
+ },
198
+ });
199
+
200
+ export const getOrCreateSupplierForUser = mutation({
201
+ args: {
202
+ userId: v.id('user_profiles'),
203
+ },
204
+ 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') {
212
+ throw new Error('User is not a supplier');
213
+ }
214
+
215
+ if (user.supplierId) {
216
+ return user.supplierId;
217
+ }
218
+
219
+ const supplierId = await ctx.db.insert('suppliers', {
220
+ name: user.name || user.email,
221
+ contact_email: user.email,
222
+ contact_phone: '',
223
+ categories: [],
224
+ sla_days: 7,
225
+ notes: `Creato automaticamente dall'utente ${user.email}`,
226
+ });
227
+
228
+ await ctx.db.patch(args.userId, {
229
+ supplierId: supplierId,
230
+ });
231
+
232
+ return supplierId;
233
+ },
234
+ });