@hed-hog/lms 0.0.366 → 0.0.370

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 (169) hide show
  1. package/dist/certificate/certificate.controller.d.ts +1 -1
  2. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  3. package/dist/certificate/certificate.controller.js +4 -2
  4. package/dist/certificate/certificate.controller.js.map +1 -1
  5. package/dist/certificate/certificate.service.d.ts +50 -0
  6. package/dist/certificate/certificate.service.d.ts.map +1 -1
  7. package/dist/certificate/certificate.service.js +73 -0
  8. package/dist/certificate/certificate.service.js.map +1 -1
  9. package/dist/course/course-ai-usage.service.d.ts +58 -0
  10. package/dist/course/course-ai-usage.service.d.ts.map +1 -0
  11. package/dist/course/course-ai-usage.service.js +176 -0
  12. package/dist/course/course-ai-usage.service.js.map +1 -0
  13. package/dist/course/course-audio-transcription.service.d.ts +65 -1
  14. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  15. package/dist/course/course-audio-transcription.service.js +381 -29
  16. package/dist/course/course-audio-transcription.service.js.map +1 -1
  17. package/dist/course/course-export-scorm12.service.d.ts +3 -0
  18. package/dist/course/course-export-scorm12.service.d.ts.map +1 -1
  19. package/dist/course/course-export-scorm12.service.js +141 -6
  20. package/dist/course/course-export-scorm12.service.js.map +1 -1
  21. package/dist/course/course-export.service.d.ts.map +1 -1
  22. package/dist/course/course-export.service.js +2 -1
  23. package/dist/course/course-export.service.js.map +1 -1
  24. package/dist/course/course-lesson.controller.d.ts +25 -3
  25. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  26. package/dist/course/course-lesson.controller.js +71 -8
  27. package/dist/course/course-lesson.controller.js.map +1 -1
  28. package/dist/course/course-structure.controller.d.ts +26 -5
  29. package/dist/course/course-structure.controller.d.ts.map +1 -1
  30. package/dist/course/course-structure.controller.js +31 -1
  31. package/dist/course/course-structure.controller.js.map +1 -1
  32. package/dist/course/course-structure.service.d.ts +37 -5
  33. package/dist/course/course-structure.service.d.ts.map +1 -1
  34. package/dist/course/course-structure.service.js +165 -20
  35. package/dist/course/course-structure.service.js.map +1 -1
  36. package/dist/course/course-transcription-translation.service.d.ts +31 -0
  37. package/dist/course/course-transcription-translation.service.d.ts.map +1 -0
  38. package/dist/course/course-transcription-translation.service.js +227 -0
  39. package/dist/course/course-transcription-translation.service.js.map +1 -0
  40. package/dist/course/course-video-agent-pipeline.service.js +7 -7
  41. package/dist/course/course-video-agent-pipeline.service.js.map +1 -1
  42. package/dist/course/course.module.d.ts.map +1 -1
  43. package/dist/course/course.module.js +4 -0
  44. package/dist/course/course.module.js.map +1 -1
  45. package/dist/course/dto/create-course-bulk-job.dto.d.ts +2 -1
  46. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -1
  47. package/dist/course/dto/create-course-bulk-job.dto.js +6 -1
  48. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -1
  49. package/dist/course/dto/create-course-export.dto.d.ts +1 -0
  50. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -1
  51. package/dist/course/dto/create-course-export.dto.js +6 -0
  52. package/dist/course/dto/create-course-export.dto.js.map +1 -1
  53. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
  54. package/dist/course/lms-bulk-upload-automation.service.js +26 -13
  55. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
  56. package/dist/course/lms-bulk-upload.controller.d.ts +3 -0
  57. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  58. package/dist/course/lms-bulk-upload.service.d.ts +3 -0
  59. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  60. package/dist/course/lms-bulk-upload.service.js +48 -29
  61. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  62. package/dist/course/subtitle.util.d.ts +46 -0
  63. package/dist/course/subtitle.util.d.ts.map +1 -0
  64. package/dist/course/subtitle.util.js +206 -0
  65. package/dist/course/subtitle.util.js.map +1 -0
  66. package/dist/enterprise/training/training-student.service.d.ts +27 -0
  67. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  68. package/dist/enterprise/training/training-student.service.js +197 -10
  69. package/dist/enterprise/training/training-student.service.js.map +1 -1
  70. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +3 -1
  71. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -1
  72. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +19 -5
  73. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
  74. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -1
  75. package/dist/lesson-xp-map/lesson-xp-map.module.js +2 -1
  76. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -1
  77. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
  78. package/dist/lms.module.d.ts.map +1 -1
  79. package/dist/lms.module.js +4 -0
  80. package/dist/lms.module.js.map +1 -1
  81. package/dist/platforma/platforma-performance.service.js +121 -121
  82. package/dist/platforma/platforma-video.service.d.ts +8 -0
  83. package/dist/platforma/platforma-video.service.d.ts.map +1 -1
  84. package/dist/platforma/platforma-video.service.js +45 -2
  85. package/dist/platforma/platforma-video.service.js.map +1 -1
  86. package/dist/platforma/platforma.controller.d.ts +99 -1
  87. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  88. package/dist/platforma/platforma.controller.js +111 -2
  89. package/dist/platforma/platforma.controller.js.map +1 -1
  90. package/dist/training/dto/create-training.dto.d.ts +9 -0
  91. package/dist/training/dto/create-training.dto.d.ts.map +1 -1
  92. package/dist/training/dto/create-training.dto.js +45 -1
  93. package/dist/training/dto/create-training.dto.js.map +1 -1
  94. package/dist/training/training.controller.d.ts +144 -0
  95. package/dist/training/training.controller.d.ts.map +1 -1
  96. package/dist/training/training.service.d.ts +149 -0
  97. package/dist/training/training.service.d.ts.map +1 -1
  98. package/dist/training/training.service.js +332 -167
  99. package/dist/training/training.service.js.map +1 -1
  100. package/hedhog/data/image_type.yaml +10 -0
  101. package/hedhog/data/route.yaml +251 -0
  102. package/hedhog/data/setting_group.yaml +97 -0
  103. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +139 -27
  104. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +69 -57
  105. package/hedhog/frontend/app/courses/[id]/_components/CourseIssuedCertificatesCard.tsx.ejs +168 -0
  106. package/hedhog/frontend/app/courses/[id]/structure/_components/course-ai-costs-tab.tsx.ejs +191 -0
  107. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +81 -1
  108. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +12 -0
  109. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +69 -1
  110. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +267 -19
  111. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +114 -86
  112. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +239 -31
  113. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +344 -59
  114. package/hedhog/frontend/app/courses/[id]/structure/_components/lesson-video-preview.tsx.ejs +200 -0
  115. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +1 -0
  116. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -0
  117. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +19 -7
  118. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -0
  119. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-ai-costs.ts.ejs +40 -0
  120. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +25 -0
  121. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +148 -0
  122. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +157 -8
  123. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +1 -22
  124. package/hedhog/frontend/app/courses/page.tsx.ejs +26 -4
  125. package/hedhog/frontend/app/paths/page.tsx.ejs +612 -164
  126. package/hedhog/frontend/messages/en.json +23 -12
  127. package/hedhog/frontend/messages/pt.json +23 -12
  128. package/hedhog/query/triggers.sql +33 -0
  129. package/hedhog/table/course_ai_usage.yaml +46 -0
  130. package/hedhog/table/course_lesson.yaml +3 -0
  131. package/hedhog/table/course_lesson_answer.yaml +37 -0
  132. package/hedhog/table/course_lesson_transcription_segment.yaml +8 -0
  133. package/hedhog/table/learning_path.yaml +6 -0
  134. package/hedhog/table/learning_path_module.yaml +22 -0
  135. package/hedhog/table/learning_path_step.yaml +9 -6
  136. package/hedhog/table/lesson_view_event.yaml +66 -66
  137. package/package.json +9 -9
  138. package/src/certificate/certificate.controller.ts +2 -0
  139. package/src/certificate/certificate.service.ts +99 -0
  140. package/src/course/course-ai-usage.service.ts +221 -0
  141. package/src/course/course-audio-transcription.service.ts +471 -43
  142. package/src/course/course-export-scorm12.service.ts +149 -5
  143. package/src/course/course-export.service.ts +1 -0
  144. package/src/course/course-lesson.controller.ts +59 -6
  145. package/src/course/course-structure.controller.ts +16 -0
  146. package/src/course/course-structure.service.ts +184 -10
  147. package/src/course/course-transcription-translation.service.ts +293 -0
  148. package/src/course/course-video-agent-pipeline.service.ts +471 -471
  149. package/src/course/course.module.ts +4 -0
  150. package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
  151. package/src/course/dto/create-course-export.dto.ts +6 -0
  152. package/src/course/ffmpeg.util.ts +65 -65
  153. package/src/course/lms-bulk-upload-automation.service.ts +29 -7
  154. package/src/course/lms-bulk-upload.service.ts +20 -1
  155. package/src/course/subtitle.util.ts +220 -0
  156. package/src/enterprise/training/training-student.service.ts +224 -4
  157. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +14 -0
  158. package/src/lesson-xp-map/lesson-xp-map.module.ts +2 -1
  159. package/src/lms.module.ts +4 -0
  160. package/src/platforma/dto/heartbeat.dto.ts +30 -30
  161. package/src/platforma/handlers/emit-certificate.handler.ts +117 -117
  162. package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -343
  163. package/src/platforma/platforma-heartbeat.service.ts +33 -33
  164. package/src/platforma/platforma-performance.service.ts +606 -606
  165. package/src/platforma/platforma-search.service.ts +48 -48
  166. package/src/platforma/platforma-video.service.ts +59 -3
  167. package/src/platforma/platforma.controller.ts +88 -0
  168. package/src/training/dto/create-training.dto.ts +36 -0
  169. package/src/training/training.service.ts +360 -163
@@ -60,6 +60,22 @@ let TrainingService = class TrainingService {
60
60
  }
61
61
  return extrasById;
62
62
  }
63
+ async getTrainingCertificateTemplate(id) {
64
+ var _a;
65
+ try {
66
+ const rows = (await this.prisma.$queryRawUnsafe(`
67
+ SELECT ct.id, ct.name, ct.template_content, ct.status
68
+ FROM learning_path lp
69
+ JOIN certificate_template ct ON ct.id = lp.certificate_template_id
70
+ WHERE lp.id = $1
71
+ LIMIT 1
72
+ `, id));
73
+ return (_a = rows[0]) !== null && _a !== void 0 ? _a : null;
74
+ }
75
+ catch (_b) {
76
+ return null;
77
+ }
78
+ }
63
79
  async persistTrainingProgressMode(id, progressMode) {
64
80
  if (progressMode === undefined) {
65
81
  return;
@@ -75,6 +91,21 @@ let TrainingService = class TrainingService {
75
91
  // Some environments may still be behind the current learning_path schema.
76
92
  }
77
93
  }
94
+ async persistTrainingCertificateTemplateId(id, certificateTemplateId) {
95
+ if (certificateTemplateId === undefined) {
96
+ return;
97
+ }
98
+ try {
99
+ await this.prisma.$executeRawUnsafe(`
100
+ UPDATE learning_path
101
+ SET certificate_template_id = $1
102
+ WHERE id = $2
103
+ `, certificateTemplateId !== null && certificateTemplateId !== void 0 ? certificateTemplateId : null, id);
104
+ }
105
+ catch (_a) {
106
+ // Some environments may still be behind the current learning_path schema.
107
+ }
108
+ }
78
109
  async list(params) {
79
110
  var _a;
80
111
  const page = Math.max(Number(params.page) || 1, 1);
@@ -124,32 +155,43 @@ let TrainingService = class TrainingService {
124
155
  where,
125
156
  orderBy: { created_at: 'desc' },
126
157
  include: {
127
- learning_path_step: {
158
+ learning_path_module: {
128
159
  orderBy: { order: 'asc' },
129
160
  include: {
130
- course: {
131
- select: {
132
- id: true,
133
- title: true,
134
- duration_hours: true,
135
- course_category: {
136
- include: {
137
- category: {
138
- select: { slug: true },
161
+ learning_path_step: {
162
+ orderBy: { order: 'asc' },
163
+ include: {
164
+ course: {
165
+ select: {
166
+ id: true,
167
+ title: true,
168
+ duration_hours: true,
169
+ course_category: {
170
+ include: {
171
+ category: {
172
+ select: { slug: true },
173
+ },
174
+ },
139
175
  },
140
176
  },
141
177
  },
142
- },
143
- },
144
- exam: {
145
- select: {
146
- id: true,
147
- title: true,
148
- time_limit_minutes: true,
178
+ exam: {
179
+ select: {
180
+ id: true,
181
+ title: true,
182
+ time_limit_minutes: true,
183
+ },
184
+ },
149
185
  },
150
186
  },
151
187
  },
152
188
  },
189
+ learning_path_image: {
190
+ where: { image_type: { slug: 'learning-path-banner' } },
191
+ include: { file: { select: { id: true } } },
192
+ orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
193
+ take: 1,
194
+ },
153
195
  _count: {
154
196
  select: {
155
197
  learning_path_enrollment: true,
@@ -187,35 +229,53 @@ let TrainingService = class TrainingService {
187
229
  };
188
230
  }
189
231
  async getById(id) {
190
- const path = await this.prisma.learning_path.findUnique({
191
- where: { id },
232
+ const stepInclude = {
233
+ orderBy: { order: 'asc' },
192
234
  include: {
193
- learning_path_step: {
194
- orderBy: { order: 'asc' },
195
- include: {
196
- course: {
197
- select: {
198
- id: true,
199
- title: true,
200
- duration_hours: true,
201
- course_category: {
202
- include: {
203
- category: {
204
- select: { slug: true },
205
- },
206
- },
235
+ course: {
236
+ select: {
237
+ id: true,
238
+ title: true,
239
+ duration_hours: true,
240
+ course_category: {
241
+ include: {
242
+ category: {
243
+ select: { slug: true },
207
244
  },
208
245
  },
209
246
  },
210
- exam: {
211
- select: {
212
- id: true,
213
- title: true,
214
- time_limit_minutes: true,
215
- },
247
+ course_image: {
248
+ where: { image_type: { slug: 'course-logo' } },
249
+ select: { file: { select: { id: true } } },
250
+ orderBy: { order: 'asc' },
251
+ take: 1,
216
252
  },
217
253
  },
218
254
  },
255
+ exam: {
256
+ select: {
257
+ id: true,
258
+ title: true,
259
+ time_limit_minutes: true,
260
+ },
261
+ },
262
+ },
263
+ };
264
+ const path = await this.prisma.learning_path.findUnique({
265
+ where: { id },
266
+ include: {
267
+ learning_path_module: {
268
+ orderBy: { order: 'asc' },
269
+ include: {
270
+ learning_path_step: stepInclude,
271
+ },
272
+ },
273
+ learning_path_image: {
274
+ where: { image_type: { slug: 'learning-path-banner' } },
275
+ include: { file: { select: { id: true } } },
276
+ orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
277
+ take: 1,
278
+ },
219
279
  _count: {
220
280
  select: {
221
281
  learning_path_enrollment: true,
@@ -226,49 +286,23 @@ let TrainingService = class TrainingService {
226
286
  });
227
287
  if (!path)
228
288
  return null;
229
- const extrasById = await this.getTrainingExtras([id]);
230
- return this.mapTraining(path, extrasById.get(id));
289
+ const [extrasById, certificateTemplate] = await Promise.all([
290
+ this.getTrainingExtras([id]),
291
+ this.getTrainingCertificateTemplate(id),
292
+ ]);
293
+ return this.mapTraining(path, extrasById.get(id), certificateTemplate);
231
294
  }
232
295
  async create(dto) {
233
- var _a, _b, _c, _d;
296
+ var _a, _b, _c, _d, _e, _f;
234
297
  const slug = ((_a = dto.slug) === null || _a === void 0 ? void 0 : _a.trim()) || this.slugify(dto.title);
235
- const stepItems = this.resolveIncomingSteps(dto);
236
- const normalizedLevel = (_b = this.normalizeLevel(dto.level)) !== null && _b !== void 0 ? _b : 'beginner';
237
- const normalizedStatus = (_c = this.normalizeStatus(dto.status)) !== null && _c !== void 0 ? _c : 'draft';
238
- const normalizedProgressMode = (_d = this.normalizeProgressMode(dto.progressMode)) !== null && _d !== void 0 ? _d : 'sequential';
298
+ const hasModules = ((_c = (_b = dto.modules) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0;
299
+ const stepItems = hasModules ? [] : this.resolveIncomingSteps(dto);
300
+ const normalizedLevel = (_d = this.normalizeLevel(dto.level)) !== null && _d !== void 0 ? _d : 'beginner';
301
+ const normalizedStatus = (_e = this.normalizeStatus(dto.status)) !== null && _e !== void 0 ? _e : 'draft';
302
+ const normalizedProgressMode = (_f = this.normalizeProgressMode(dto.progressMode)) !== null && _f !== void 0 ? _f : 'sequential';
239
303
  const created = await this.prisma.learning_path.create({
240
- data: Object.assign(Object.assign(Object.assign({ title: dto.title, slug, description: dto.description, short_description: dto.shortDescription, level: normalizedLevel, status: normalizedStatus }, (dto.primaryColor !== undefined && { primary_color: dto.primaryColor })), (dto.secondaryColor !== undefined && { secondary_color: dto.secondaryColor })), (stepItems.length > 0 && {
241
- learning_path_step: {
242
- create: stepItems.map((item, index) => this.toLearningPathCreateStep(item, index)),
243
- },
244
- })),
304
+ data: Object.assign(Object.assign({ title: dto.title, slug, description: dto.description, short_description: dto.shortDescription, level: normalizedLevel, status: normalizedStatus }, (dto.primaryColor !== undefined && { primary_color: dto.primaryColor })), (dto.secondaryColor !== undefined && { secondary_color: dto.secondaryColor })),
245
305
  include: {
246
- learning_path_step: {
247
- orderBy: { order: 'asc' },
248
- include: {
249
- course: {
250
- select: {
251
- id: true,
252
- title: true,
253
- duration_hours: true,
254
- course_category: {
255
- include: {
256
- category: {
257
- select: { slug: true },
258
- },
259
- },
260
- },
261
- },
262
- },
263
- exam: {
264
- select: {
265
- id: true,
266
- title: true,
267
- time_limit_minutes: true,
268
- },
269
- },
270
- },
271
- },
272
306
  _count: {
273
307
  select: {
274
308
  learning_path_enrollment: true,
@@ -277,9 +311,40 @@ let TrainingService = class TrainingService {
277
311
  },
278
312
  },
279
313
  });
314
+ if (hasModules) {
315
+ const resolvedModules = this.resolveIncomingModules(dto.modules);
316
+ for (const mod of resolvedModules) {
317
+ await this.prisma.learning_path_module.create({
318
+ data: {
319
+ learning_path_id: created.id,
320
+ title: mod.title,
321
+ description: mod.description,
322
+ order: mod.order,
323
+ learning_path_step: {
324
+ create: mod.items.map((item) => (Object.assign(Object.assign({}, this.toLearningPathCreateStep(item, item.order)), { learning_path_id: created.id }))),
325
+ },
326
+ },
327
+ });
328
+ }
329
+ }
330
+ else if (stepItems.length > 0) {
331
+ await this.prisma.learning_path_module.create({
332
+ data: {
333
+ learning_path_id: created.id,
334
+ title: '',
335
+ order: 0,
336
+ learning_path_step: {
337
+ create: stepItems.map((item, index) => (Object.assign(Object.assign({}, this.toLearningPathCreateStep(item, index)), { learning_path_id: created.id }))),
338
+ },
339
+ },
340
+ });
341
+ }
280
342
  await this.persistTrainingProgressMode(created.id, normalizedProgressMode);
281
- const extrasById = await this.getTrainingExtras([created.id]);
282
- const trainingResult = this.mapTraining(created, extrasById.get(created.id));
343
+ await this.persistTrainingCertificateTemplateId(created.id, dto.certificateTemplateId);
344
+ if (dto.bannerFileId !== undefined) {
345
+ await this.syncLearningPathImage(created.id, dto.bannerFileId);
346
+ }
347
+ const result = await this.getById(created.id);
283
348
  await this.integrationApi.publishEvent({
284
349
  eventName: 'lms.training.created',
285
350
  sourceModule: 'lms',
@@ -287,72 +352,71 @@ let TrainingService = class TrainingService {
287
352
  aggregateId: String(created.id),
288
353
  payload: { id: created.id, title: dto.title, slug, status: normalizedStatus },
289
354
  }).catch(() => null);
290
- return trainingResult;
355
+ return result;
291
356
  }
292
357
  async update(id, dto) {
293
358
  var _a;
294
- const stepItems = this.resolveIncomingSteps(dto);
295
- const stepsWereProvided = dto.items !== undefined || dto.courseIds !== undefined;
359
+ const modulesWereProvided = dto.modules !== undefined;
360
+ const stepsWereProvided = !modulesWereProvided && (dto.items !== undefined || dto.courseIds !== undefined);
361
+ const stepItems = stepsWereProvided ? this.resolveIncomingSteps(dto) : [];
296
362
  const normalizedLevel = dto.level !== undefined ? this.normalizeLevel(dto.level) : undefined;
297
363
  const normalizedStatus = dto.status !== undefined ? this.normalizeStatus(dto.status) : undefined;
298
364
  const normalizedProgressMode = dto.progressMode !== undefined
299
365
  ? this.normalizeProgressMode(dto.progressMode)
300
366
  : undefined;
301
- if (stepsWereProvided) {
367
+ if (modulesWereProvided) {
368
+ // Delete all modules — CASCADE removes their steps automatically
369
+ await this.prisma.learning_path_module.deleteMany({
370
+ where: { learning_path_id: id },
371
+ });
372
+ }
373
+ else if (stepsWereProvided) {
302
374
  await this.prisma.learning_path_step.deleteMany({
303
- where: {
304
- learning_path_id: id,
305
- },
375
+ where: { learning_path_id: id },
306
376
  });
307
377
  }
308
- const updated = await this.prisma.learning_path.update({
378
+ await this.prisma.learning_path.update({
309
379
  where: { id },
310
- data: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (dto.title !== undefined && { title: dto.title })), (dto.slug !== undefined && { slug: dto.slug || this.slugify((_a = dto.title) !== null && _a !== void 0 ? _a : '') })), (dto.description !== undefined && { description: dto.description })), (dto.shortDescription !== undefined && {
380
+ data: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (dto.title !== undefined && { title: dto.title })), (dto.slug !== undefined && { slug: dto.slug || this.slugify((_a = dto.title) !== null && _a !== void 0 ? _a : '') })), (dto.description !== undefined && { description: dto.description })), (dto.shortDescription !== undefined && {
311
381
  short_description: dto.shortDescription,
312
- })), (normalizedLevel !== undefined && { level: normalizedLevel })), (normalizedStatus !== undefined && { status: normalizedStatus })), (dto.primaryColor !== undefined && { primary_color: dto.primaryColor })), (dto.secondaryColor !== undefined && { secondary_color: dto.secondaryColor })), (stepsWereProvided &&
313
- stepItems.length > 0 && {
314
- learning_path_step: {
315
- create: stepItems.map((item, index) => this.toLearningPathCreateStep(item, index)),
316
- },
317
- })),
318
- include: {
319
- learning_path_step: {
320
- orderBy: { order: 'asc' },
321
- include: {
322
- course: {
323
- select: {
324
- id: true,
325
- title: true,
326
- duration_hours: true,
327
- course_category: {
328
- include: {
329
- category: {
330
- select: { slug: true },
331
- },
332
- },
333
- },
334
- },
335
- },
336
- exam: {
337
- select: {
338
- id: true,
339
- title: true,
340
- time_limit_minutes: true,
341
- },
382
+ })), (normalizedLevel !== undefined && { level: normalizedLevel })), (normalizedStatus !== undefined && { status: normalizedStatus })), (dto.primaryColor !== undefined && { primary_color: dto.primaryColor })), (dto.secondaryColor !== undefined && { secondary_color: dto.secondaryColor })),
383
+ });
384
+ if (modulesWereProvided) {
385
+ const resolvedModules = this.resolveIncomingModules(dto.modules);
386
+ for (const mod of resolvedModules) {
387
+ await this.prisma.learning_path_module.create({
388
+ data: {
389
+ learning_path_id: id,
390
+ title: mod.title,
391
+ description: mod.description,
392
+ order: mod.order,
393
+ learning_path_step: {
394
+ create: mod.items.map((item) => (Object.assign(Object.assign({}, this.toLearningPathCreateStep(item, item.order)), { learning_path_id: id }))),
342
395
  },
343
396
  },
344
- },
345
- _count: {
346
- select: {
347
- learning_path_enrollment: true,
348
- learning_path_step: true,
397
+ });
398
+ }
399
+ }
400
+ else if (stepsWereProvided && stepItems.length > 0) {
401
+ await this.prisma.learning_path_module.create({
402
+ data: {
403
+ learning_path_id: id,
404
+ title: '',
405
+ order: 0,
406
+ learning_path_step: {
407
+ create: stepItems.map((item, index) => (Object.assign(Object.assign({}, this.toLearningPathCreateStep(item, index)), { learning_path_id: id }))),
349
408
  },
350
409
  },
351
- },
352
- });
410
+ });
411
+ }
353
412
  await this.persistTrainingProgressMode(id, normalizedProgressMode);
354
- const extrasById = await this.getTrainingExtras([id]);
355
- const updateTrainingResult = this.mapTraining(updated, extrasById.get(id));
413
+ if (dto.certificateTemplateId !== undefined) {
414
+ await this.persistTrainingCertificateTemplateId(id, dto.certificateTemplateId);
415
+ }
416
+ if (dto.bannerFileId !== undefined) {
417
+ await this.syncLearningPathImage(id, dto.bannerFileId);
418
+ }
419
+ const updateTrainingResult = await this.getById(id);
356
420
  await this.integrationApi.publishEvent({
357
421
  eventName: 'lms.training.updated',
358
422
  sourceModule: 'lms',
@@ -373,73 +437,104 @@ let TrainingService = class TrainingService {
373
437
  }).catch(() => null);
374
438
  return { success: true };
375
439
  }
376
- mapTraining(path, extras) {
377
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
378
- const steps = [...((_a = path.learning_path_step) !== null && _a !== void 0 ? _a : [])].sort((a, b) => { var _a, _b; return ((_a = a === null || a === void 0 ? void 0 : a.order) !== null && _a !== void 0 ? _a : 0) - ((_b = b === null || b === void 0 ? void 0 : b.order) !== null && _b !== void 0 ? _b : 0); });
379
- const courseSteps = steps.filter((step) => step === null || step === void 0 ? void 0 : step.course);
380
- const examSteps = steps.filter((step) => step === null || step === void 0 ? void 0 : step.exam);
440
+ mapStep(step, index) {
441
+ var _a, _b, _c, _d, _e, _f, _g, _h;
442
+ if (step === null || step === void 0 ? void 0 : step.course) {
443
+ const logoFileId = (_d = (_c = (_b = (_a = step.course.course_image) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.file) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null;
444
+ return {
445
+ id: step.id,
446
+ type: 'course',
447
+ itemId: step.course.id,
448
+ title: step.course.title,
449
+ durationHours: (_e = step.course.duration_hours) !== null && _e !== void 0 ? _e : 0,
450
+ order: (_f = step.order) !== null && _f !== void 0 ? _f : index,
451
+ isRequired: step.is_required !== false,
452
+ logoImage: logoFileId ? `/file/open/${logoFileId}` : null,
453
+ };
454
+ }
455
+ if (step === null || step === void 0 ? void 0 : step.exam) {
456
+ return {
457
+ id: step.id,
458
+ type: 'exam',
459
+ itemId: step.exam.id,
460
+ title: step.exam.title,
461
+ durationMinutes: (_g = step.exam.time_limit_minutes) !== null && _g !== void 0 ? _g : 0,
462
+ order: (_h = step.order) !== null && _h !== void 0 ? _h : index,
463
+ isRequired: step.is_required !== false,
464
+ logoImage: null,
465
+ };
466
+ }
467
+ return null;
468
+ }
469
+ mapTraining(path, extras, certificateTemplate) {
470
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
471
+ const modulesRaw = [...((_a = path.learning_path_module) !== null && _a !== void 0 ? _a : [])].sort((a, b) => { var _a, _b; return ((_a = a === null || a === void 0 ? void 0 : a.order) !== null && _a !== void 0 ? _a : 0) - ((_b = b === null || b === void 0 ? void 0 : b.order) !== null && _b !== void 0 ? _b : 0); });
472
+ // All steps across all modules (flattened, for backward-compat fields)
473
+ const allSteps = modulesRaw.flatMap((mod) => {
474
+ var _a;
475
+ return [...((_a = mod.learning_path_step) !== null && _a !== void 0 ? _a : [])].sort((a, b) => { var _a, _b; return ((_a = a === null || a === void 0 ? void 0 : a.order) !== null && _a !== void 0 ? _a : 0) - ((_b = b === null || b === void 0 ? void 0 : b.order) !== null && _b !== void 0 ? _b : 0); });
476
+ });
477
+ const courseSteps = allSteps.filter((step) => step === null || step === void 0 ? void 0 : step.course);
478
+ const examSteps = allSteps.filter((step) => step === null || step === void 0 ? void 0 : step.exam);
381
479
  const courses = courseSteps.map((step) => step.course.title);
382
480
  const exams = examSteps.map((step) => step.exam.title);
383
481
  const courseIds = courseSteps.map((step) => step.course.id);
384
482
  const examIds = examSteps.map((step) => step.exam.id);
385
483
  const cargaTotal = courseSteps.reduce((sum, step) => { var _a, _b; return sum + ((_b = (_a = step.course) === null || _a === void 0 ? void 0 : _a.duration_hours) !== null && _b !== void 0 ? _b : 0); }, 0);
386
- const firstCategorySlug = steps
484
+ const firstCategorySlug = allSteps
387
485
  .flatMap((step) => { var _a, _b; return (_b = (_a = step.course) === null || _a === void 0 ? void 0 : _a.course_category) !== null && _b !== void 0 ? _b : []; })
388
486
  .map((item) => { var _a; return (_a = item.category) === null || _a === void 0 ? void 0 : _a.slug; })
389
487
  .find(Boolean);
390
- const items = steps
391
- .map((step, index) => {
392
- var _a, _b, _c, _d;
393
- if (step === null || step === void 0 ? void 0 : step.course) {
394
- return {
395
- id: step.id,
396
- type: 'course',
397
- itemId: step.course.id,
398
- title: step.course.title,
399
- durationHours: (_a = step.course.duration_hours) !== null && _a !== void 0 ? _a : 0,
400
- order: (_b = step.order) !== null && _b !== void 0 ? _b : index,
401
- isRequired: step.is_required !== false,
402
- };
403
- }
404
- if (step === null || step === void 0 ? void 0 : step.exam) {
405
- return {
406
- id: step.id,
407
- type: 'exam',
408
- itemId: step.exam.id,
409
- title: step.exam.title,
410
- durationMinutes: (_c = step.exam.time_limit_minutes) !== null && _c !== void 0 ? _c : 0,
411
- order: (_d = step.order) !== null && _d !== void 0 ? _d : index,
412
- isRequired: step.is_required !== false,
413
- };
414
- }
415
- return null;
416
- })
417
- .filter(Boolean);
488
+ const items = allSteps.map(this.mapStep.bind(this)).filter(Boolean);
489
+ const modules = modulesRaw.map((mod, modIndex) => {
490
+ var _a, _b, _c;
491
+ return ({
492
+ id: mod.id,
493
+ title: mod.title,
494
+ description: (_a = mod.description) !== null && _a !== void 0 ? _a : null,
495
+ order: (_b = mod.order) !== null && _b !== void 0 ? _b : modIndex,
496
+ items: [...((_c = mod.learning_path_step) !== null && _c !== void 0 ? _c : [])]
497
+ .sort((a, b) => { var _a, _b; return ((_a = a === null || a === void 0 ? void 0 : a.order) !== null && _a !== void 0 ? _a : 0) - ((_b = b === null || b === void 0 ? void 0 : b.order) !== null && _b !== void 0 ? _b : 0); })
498
+ .map(this.mapStep.bind(this))
499
+ .filter(Boolean),
500
+ });
501
+ });
502
+ const bannerImage = ((_b = path.learning_path_image) !== null && _b !== void 0 ? _b : [])[0];
418
503
  return {
419
504
  id: path.id,
420
505
  nome: path.title,
421
- descricao: (_c = (_b = path.description) !== null && _b !== void 0 ? _b : path.short_description) !== null && _c !== void 0 ? _c : '',
506
+ descricao: (_d = (_c = path.description) !== null && _c !== void 0 ? _c : path.short_description) !== null && _d !== void 0 ? _d : '',
422
507
  area: this.slugToArea(firstCategorySlug),
423
508
  nivel: this.levelToPt(path.level),
424
- prerequisitos: (_d = path.short_description) !== null && _d !== void 0 ? _d : '',
509
+ prerequisitos: (_e = path.short_description) !== null && _e !== void 0 ? _e : '',
425
510
  cursos: courses,
426
511
  exams,
427
512
  courseIds,
428
513
  examIds,
429
514
  items,
515
+ modules,
430
516
  progressionMode: this.progressModeToPt(extras === null || extras === void 0 ? void 0 : extras.progress_mode),
431
517
  cargaTotal,
432
- alunos: (_f = (_e = path._count) === null || _e === void 0 ? void 0 : _e.learning_path_enrollment) !== null && _f !== void 0 ? _f : 0,
518
+ alunos: (_g = (_f = path._count) === null || _f === void 0 ? void 0 : _f.learning_path_enrollment) !== null && _g !== void 0 ? _g : 0,
433
519
  status: this.statusToPt(path.status),
434
520
  criadoEm: path.created_at,
435
- primaryColor: (_g = path.primary_color) !== null && _g !== void 0 ? _g : undefined,
436
- secondaryColor: (_h = path.secondary_color) !== null && _h !== void 0 ? _h : undefined,
521
+ primaryColor: (_h = path.primary_color) !== null && _h !== void 0 ? _h : undefined,
522
+ secondaryColor: (_j = path.secondary_color) !== null && _j !== void 0 ? _j : undefined,
523
+ bannerFileId: (_l = (_k = bannerImage === null || bannerImage === void 0 ? void 0 : bannerImage.file) === null || _k === void 0 ? void 0 : _k.id) !== null && _l !== void 0 ? _l : null,
437
524
  code: this.idToCode(path.id),
438
525
  slug: path.slug,
439
526
  level: path.level,
440
527
  statusRaw: path.status,
441
- students: (_k = (_j = path._count) === null || _j === void 0 ? void 0 : _j.learning_path_enrollment) !== null && _k !== void 0 ? _k : 0,
528
+ students: (_o = (_m = path._count) === null || _m === void 0 ? void 0 : _m.learning_path_enrollment) !== null && _o !== void 0 ? _o : 0,
442
529
  createdAt: path.created_at,
530
+ certificateTemplate: certificateTemplate
531
+ ? {
532
+ id: certificateTemplate.id,
533
+ name: certificateTemplate.name,
534
+ templateContent: certificateTemplate.template_content,
535
+ status: certificateTemplate.status,
536
+ }
537
+ : null,
443
538
  };
444
539
  }
445
540
  resolveIncomingSteps(dto) {
@@ -469,6 +564,26 @@ let TrainingService = class TrainingService {
469
564
  isRequired: true,
470
565
  }));
471
566
  }
567
+ resolveIncomingModules(modules) {
568
+ return modules.map((mod, i) => {
569
+ var _a;
570
+ return ({
571
+ title: mod.title,
572
+ description: mod.description,
573
+ order: Number.isFinite(mod.order) ? mod.order : i,
574
+ items: ((_a = mod.items) !== null && _a !== void 0 ? _a : [])
575
+ .map((item, j) => ({
576
+ type: item.type,
577
+ itemId: Number(item.itemId),
578
+ order: Number.isFinite(item.order) ? item.order : j,
579
+ isRequired: item.isRequired !== false,
580
+ }))
581
+ .filter((item) => (item.type === 'course' || item.type === 'exam') &&
582
+ Number.isFinite(item.itemId) &&
583
+ item.itemId > 0),
584
+ });
585
+ });
586
+ }
472
587
  toLearningPathCreateStep(item, fallbackOrder) {
473
588
  return {
474
589
  step_type: item.type,
@@ -581,6 +696,56 @@ let TrainingService = class TrainingService {
581
696
  idToCode(id) {
582
697
  return `TR-${String(id).padStart(3, '0')}`;
583
698
  }
699
+ async syncLearningPathImage(pathId, fileId) {
700
+ const imageType = await this.prisma.image_type.findFirst({
701
+ where: { slug: 'learning-path-banner', applies_to_learning_path: true },
702
+ select: { id: true },
703
+ });
704
+ if (!imageType)
705
+ return;
706
+ if (fileId === null) {
707
+ await this.prisma.learning_path_image.deleteMany({
708
+ where: { learning_path_id: pathId, image_type_id: imageType.id },
709
+ });
710
+ return;
711
+ }
712
+ const normalizedFileId = Number(fileId);
713
+ if (!Number.isInteger(normalizedFileId) || normalizedFileId <= 0)
714
+ return;
715
+ const file = await this.prisma.file.findUnique({
716
+ where: { id: normalizedFileId },
717
+ select: { id: true },
718
+ });
719
+ if (!file)
720
+ return;
721
+ const existing = await this.prisma.learning_path_image.findMany({
722
+ where: { learning_path_id: pathId, image_type_id: imageType.id },
723
+ orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
724
+ select: { id: true },
725
+ });
726
+ if (existing.length > 0) {
727
+ const [primary, ...duplicates] = existing;
728
+ await this.prisma.learning_path_image.update({
729
+ where: { id: primary.id },
730
+ data: { file_id: normalizedFileId, is_primary: true },
731
+ });
732
+ if (duplicates.length > 0) {
733
+ await this.prisma.learning_path_image.deleteMany({
734
+ where: { id: { in: duplicates.map((r) => r.id) } },
735
+ });
736
+ }
737
+ return;
738
+ }
739
+ await this.prisma.learning_path_image.create({
740
+ data: {
741
+ learning_path_id: pathId,
742
+ file_id: normalizedFileId,
743
+ image_type_id: imageType.id,
744
+ order: 0,
745
+ is_primary: true,
746
+ },
747
+ });
748
+ }
584
749
  };
585
750
  exports.TrainingService = TrainingService;
586
751
  exports.TrainingService = TrainingService = __decorate([