source: frontend/src/lib/pdf-generators.ts@ b8093a0

Last change on this file since b8093a0 was b8093a0, checked in by imbrsk <boris696boris@…>, 3 days ago

Merge iknow-remaster into frontend/

Bring the Next.js frontend into this repository as a monorepo subdirectory,
preserving its full commit history via a subtree merge.

  • Property mode set to 100644
File size: 48.5 KB
Line 
1import { jsPDF } from 'jspdf';
2import autoTable from 'jspdf-autotable';
3import { loadCyrillicFonts } from './pdf-fonts';
4import { apiUrl } from './api';
5
6/* ---------- shared types ---------- */
7
8export type StudentInfo = {
9 firstName: string;
10 middleName: string;
11 lastName: string;
12 index: string;
13 embg: string;
14 dateOfBirth: string;
15 gender: string;
16 nationality: string;
17 citizenship: string;
18 currentPlan: string;
19 registryNumber: string;
20 studyGroup: string;
21 scholarship: string;
22};
23
24export type EnrollmentInfo = {
25 enrollmentYear: string | number;
26 status: string;
27 cycle: string;
28 program: string;
29 quota: string;
30};
31
32export type BirthInfo = {
33 placeOfBirth: string;
34 municipalityOfBirth: string;
35 country: string;
36};
37
38export type ContactInfo = {
39 placeOfResidence: string;
40 municipalityOfResidence: string;
41 address: string;
42 phone: string;
43 mobilePhone: string;
44 email: string;
45};
46
47export type PassedExam = {
48 id: number;
49 code: string;
50 subject: string;
51 credits: number;
52 grade: number;
53 date: string;
54 semester: string;
55 professor: string;
56};
57
58export type PreviousEducation = {
59 type: string;
60 profession: string;
61 average: string | number;
62 language: string;
63 country: string;
64 previousUniversity: string;
65 previousFaculty: string;
66};
67
68/* ---------- helpers ---------- */
69
70const PAGE_WIDTH = 210; // A4 mm
71const MARGIN = 20;
72const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
73
74function drawUniversityHeader(doc: jsPDF, y: number): number {
75 doc.setFont('Roboto', 'bold');
76
77 doc.setFontSize(11);
78 doc.text('УНИВЕРЗИТЕТ „СВ. КИРИЛ И МЕТОДИЈ" - СКОПЈЕ', PAGE_WIDTH / 2, y, { align: 'center' });
79 y += 6;
80
81 doc.setFontSize(10);
82 doc.text('ФАКУЛТЕТ ЗА ИНФОРМАТИЧКИ НАУКИ И', PAGE_WIDTH / 2, y, { align: 'center' });
83 y += 5;
84 doc.text('КОМПЈУТЕРСКО ИНЖЕНЕРСТВО', PAGE_WIDTH / 2, y, { align: 'center' });
85 y += 5;
86
87 // Horizontal line under header
88 doc.setDrawColor(0, 114, 209); // primary blue
89 doc.setLineWidth(0.5);
90 doc.line(MARGIN, y, PAGE_WIDTH - MARGIN, y);
91 y += 8;
92
93 return y;
94}
95
96function drawDocumentTitle(doc: jsPDF, title: string, y: number): number {
97 doc.setFont('Roboto', 'bold');
98 doc.setFontSize(14);
99 doc.text(title, PAGE_WIDTH / 2, y, { align: 'center' });
100 y += 10;
101 return y;
102}
103
104function drawInfoRow(doc: jsPDF, label: string, value: string, x: number, y: number, labelWidth = 50): number {
105 doc.setFont('Roboto', 'bold');
106 doc.setFontSize(9);
107 doc.text(`${label}:`, x, y);
108 doc.setFont('Roboto', 'normal');
109 doc.text(String(value || '—'), x + labelWidth, y);
110 return y + 5.5;
111}
112
113function drawFooter(doc: jsPDF, archiveNumber: string, date: string): void {
114 const pageHeight = doc.internal.pageSize.getHeight();
115 let y = pageHeight - 40;
116
117 doc.setDrawColor(0, 114, 209);
118 doc.setLineWidth(0.3);
119 doc.line(MARGIN, y, PAGE_WIDTH - MARGIN, y);
120 y += 8;
121
122 doc.setFont('Roboto', 'normal');
123 doc.setFontSize(9);
124
125 doc.text(`Бр.: ${archiveNumber}`, MARGIN, y);
126 doc.text(`Датум: ${date}`, PAGE_WIDTH / 2, y, { align: 'center' });
127 y += 12;
128
129 // Signature placeholders
130 doc.text('Потпис на студентот:', MARGIN, y);
131 doc.text('М.П.', PAGE_WIDTH / 2, y, { align: 'center' });
132 doc.text('Одговорно лице:', PAGE_WIDTH - MARGIN - 35, y);
133 y += 8;
134
135 doc.setDrawColor(150, 150, 150);
136 doc.setLineWidth(0.2);
137 doc.line(MARGIN, y, MARGIN + 45, y);
138 doc.line(PAGE_WIDTH / 2 - 15, y, PAGE_WIDTH / 2 + 15, y);
139 doc.line(PAGE_WIDTH - MARGIN - 45, y, PAGE_WIDTH - MARGIN, y);
140}
141
142/* ==================================================================
143 PDF 1 — Уверение за положени испити (Certificate of Passed Exams)
144 ================================================================== */
145
146export async function generatePassedExamsCertificate(
147 student: StudentInfo,
148 enrollment: EnrollmentInfo,
149 passedExams: PassedExam[],
150 archiveNumber: string,
151 date: string,
152): Promise<void> {
153 const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
154 await loadCyrillicFonts(doc);
155
156 let y = 20;
157
158 // --- Header ---
159 y = drawUniversityHeader(doc, y);
160
161 // --- Title ---
162 y = drawDocumentTitle(doc, 'УВЕРЕНИЕ ЗА ПОЛОЖЕНИ ИСПИТИ', y);
163
164 // --- Student Info Block ---
165 doc.setFont('Roboto', 'normal');
166 doc.setFontSize(9);
167
168 y = drawInfoRow(doc, 'Студент', `${student.firstName} ${student.middleName} ${student.lastName}`, MARGIN, y);
169 y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y);
170 y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y);
171 y = drawInfoRow(doc, 'Циклус на студии', enrollment.cycle, MARGIN, y);
172 y = drawInfoRow(doc, 'Статус', enrollment.status, MARGIN, y);
173 y += 4;
174
175 // --- Passed Exams Table ---
176 const tableBody = passedExams.map((exam, idx) => [
177 String(idx + 1),
178 exam.code,
179 exam.subject,
180 String(exam.credits),
181 String(exam.grade),
182 exam.date,
183 exam.semester,
184 exam.professor || '—',
185 ]);
186
187 autoTable(doc, {
188 startY: y,
189 head: [['Р.Б.', 'Шифра', 'Предмет', 'Кредити', 'Оцена', 'Датум', 'Семестар', 'Професор']],
190 body: tableBody,
191 theme: 'grid',
192 styles: {
193 font: 'Roboto',
194 fontSize: 7.5,
195 cellPadding: 2,
196 textColor: [30, 30, 30],
197 lineColor: [200, 200, 200],
198 lineWidth: 0.2,
199 },
200 headStyles: {
201 fillColor: [0, 114, 209],
202 textColor: [255, 255, 255],
203 fontStyle: 'bold',
204 fontSize: 7.5,
205 halign: 'center',
206 },
207 columnStyles: {
208 0: { halign: 'center', cellWidth: 10 },
209 1: { halign: 'center', cellWidth: 16 },
210 2: { cellWidth: 50 },
211 3: { halign: 'center', cellWidth: 14 },
212 4: { halign: 'center', cellWidth: 12 },
213 5: { halign: 'center', cellWidth: 20 },
214 6: { cellWidth: 22 },
215 7: { cellWidth: 26 },
216 },
217 margin: { left: MARGIN, right: MARGIN },
218 didParseCell: (data) => {
219 if (data.section === 'body' && data.row.index % 2 === 0) {
220 data.cell.styles.fillColor = [245, 247, 250];
221 }
222 },
223 });
224
225 // eslint-disable-next-line @typescript-eslint/no-explicit-any
226 y = (doc as any).lastAutoTable.finalY + 8;
227
228 // --- Summary ---
229 const totalCredits = passedExams.reduce((sum, e) => sum + e.credits, 0);
230 const grades = passedExams.map((e) => e.grade).filter((g) => g > 0);
231 const average = grades.length > 0 ? (grades.reduce((s, g) => s + g, 0) / grades.length).toFixed(2) : '0.00';
232
233 doc.setFont('Roboto', 'bold');
234 doc.setFontSize(10);
235 doc.text(`Вкупно положени предмети: ${passedExams.length}`, MARGIN, y);
236 y += 6;
237 doc.text(`Вкупно кредити: ${totalCredits}`, MARGIN, y);
238 y += 6;
239 doc.text(`Просечна оцена: ${average}`, MARGIN, y);
240
241 // --- Footer ---
242 drawFooter(doc, archiveNumber, date);
243
244 doc.save(`Уверение_положени_испити_${student.index}.pdf`);
245}
246
247/* ==================================================================
248 PDF 2 — УППИ образец (UPPI Form / Student Record)
249 ================================================================== */
250
251export async function generateUPPIForm(
252 student: StudentInfo,
253 enrollment: EnrollmentInfo,
254 birth: BirthInfo,
255 contact: ContactInfo,
256 previousEdu: PreviousEducation,
257 passedExams: PassedExam[],
258 archiveNumber: string,
259 date: string,
260): Promise<void> {
261 const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
262 await loadCyrillicFonts(doc);
263
264 let y = 20;
265
266 // --- Header ---
267 y = drawUniversityHeader(doc, y);
268
269 // --- Title ---
270 y = drawDocumentTitle(doc, 'УППИ ОБРАЗЕЦ', y);
271
272 doc.setFont('Roboto', 'bold');
273 doc.setFontSize(9);
274 doc.text('УНИВЕРЗАЛНА ПРИЈАВА ЗА ПРАКТИЧНА ИНДИВИДУАЛНА РАБОТА', PAGE_WIDTH / 2, y, { align: 'center' });
275 y += 10;
276
277 // --- Section: Personal Information ---
278 doc.setFillColor(0, 114, 209);
279 doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
280 doc.setFont('Roboto', 'bold');
281 doc.setFontSize(9);
282 doc.setTextColor(255, 255, 255);
283 doc.text('ЛИЧНИ ПОДАТОЦИ', MARGIN + 3, y + 0.5);
284 doc.setTextColor(30, 30, 30);
285 y += 10;
286
287 const col2X = MARGIN + CONTENT_WIDTH / 2;
288
289 y = drawInfoRow(doc, 'Име', student.firstName, MARGIN, y, 35);
290 y -= 5.5;
291 y = drawInfoRow(doc, 'Презиме', student.lastName, col2X, y, 35);
292 y = drawInfoRow(doc, 'Татково име', student.middleName, MARGIN, y, 35);
293 y -= 5.5;
294 y = drawInfoRow(doc, 'ЕМБГ', student.embg, col2X, y, 35);
295 y = drawInfoRow(doc, 'Датум на раѓање', student.dateOfBirth, MARGIN, y, 35);
296 y -= 5.5;
297 y = drawInfoRow(doc, 'Пол', student.gender, col2X, y, 35);
298 y = drawInfoRow(doc, 'Националност', student.nationality, MARGIN, y, 35);
299 y -= 5.5;
300 y = drawInfoRow(doc, 'Државјанство', student.citizenship, col2X, y, 35);
301 y += 3;
302
303 // --- Section: Birth Info ---
304 doc.setFillColor(0, 114, 209);
305 doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
306 doc.setFont('Roboto', 'bold');
307 doc.setFontSize(9);
308 doc.setTextColor(255, 255, 255);
309 doc.text('ПОДАТОЦИ ЗА РАЃАЊЕ', MARGIN + 3, y + 0.5);
310 doc.setTextColor(30, 30, 30);
311 y += 10;
312
313 y = drawInfoRow(doc, 'Место на раѓање', birth.placeOfBirth, MARGIN, y, 40);
314 y = drawInfoRow(doc, 'Општина', birth.municipalityOfBirth, MARGIN, y, 40);
315 y = drawInfoRow(doc, 'Држава', birth.country, MARGIN, y, 40);
316 y += 3;
317
318 // --- Section: Contact ---
319 doc.setFillColor(0, 114, 209);
320 doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
321 doc.setFont('Roboto', 'bold');
322 doc.setFontSize(9);
323 doc.setTextColor(255, 255, 255);
324 doc.text('КОНТАКТ ИНФОРМАЦИИ', MARGIN + 3, y + 0.5);
325 doc.setTextColor(30, 30, 30);
326 y += 10;
327
328 y = drawInfoRow(doc, 'Место на живеење', contact.placeOfResidence, MARGIN, y, 40);
329 y = drawInfoRow(doc, 'Адреса', contact.address, MARGIN, y, 40);
330 y = drawInfoRow(doc, 'Телефон', contact.phone || contact.mobilePhone, MARGIN, y, 40);
331 y = drawInfoRow(doc, 'Е-пошта', contact.email, MARGIN, y, 40);
332 y += 3;
333
334 // --- Section: Enrollment ---
335 doc.setFillColor(0, 114, 209);
336 doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
337 doc.setFont('Roboto', 'bold');
338 doc.setFontSize(9);
339 doc.setTextColor(255, 255, 255);
340 doc.text('СТУДИСКИ ИНФОРМАЦИИ', MARGIN + 3, y + 0.5);
341 doc.setTextColor(30, 30, 30);
342 y += 10;
343
344 y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y, 40);
345 y -= 5.5;
346 y = drawInfoRow(doc, 'Мат. број', student.registryNumber, col2X, y, 35);
347 y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y, 40);
348 y = drawInfoRow(doc, 'Циклус', enrollment.cycle, MARGIN, y, 40);
349 y -= 5.5;
350 y = drawInfoRow(doc, 'Статус', enrollment.status, col2X, y, 35);
351 y = drawInfoRow(doc, 'Год. на запишување', String(enrollment.enrollmentYear), MARGIN, y, 40);
352 y -= 5.5;
353 y = drawInfoRow(doc, 'Квота', enrollment.quota, col2X, y, 35);
354 y += 3;
355
356 // --- Section: Previous Education ---
357 doc.setFillColor(0, 114, 209);
358 doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
359 doc.setFont('Roboto', 'bold');
360 doc.setFontSize(9);
361 doc.setTextColor(255, 255, 255);
362 doc.text('ПРЕТХОДНО ОБРАЗОВАНИЕ', MARGIN + 3, y + 0.5);
363 doc.setTextColor(30, 30, 30);
364 y += 10;
365
366 y = drawInfoRow(doc, 'Вид', previousEdu.type, MARGIN, y, 40);
367 y = drawInfoRow(doc, 'Просек', String(previousEdu.average), MARGIN, y, 40);
368 y = drawInfoRow(doc, 'Јазик', previousEdu.language, MARGIN, y, 40);
369 y += 3;
370
371 // --- Section: Passed Exams mini-table ---
372 doc.setFillColor(0, 114, 209);
373 doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
374 doc.setFont('Roboto', 'bold');
375 doc.setFontSize(9);
376 doc.setTextColor(255, 255, 255);
377 doc.text('ПОЛОЖЕНИ ИСПИТИ', MARGIN + 3, y + 0.5);
378 doc.setTextColor(30, 30, 30);
379 y += 8;
380
381 const examTableBody = passedExams.map((exam, idx) => [
382 String(idx + 1),
383 exam.code,
384 exam.subject,
385 String(exam.credits),
386 String(exam.grade),
387 exam.date,
388 ]);
389
390 autoTable(doc, {
391 startY: y,
392 head: [['#', 'Шифра', 'Предмет', 'Кредити', 'Оцена', 'Датум']],
393 body: examTableBody,
394 theme: 'grid',
395 styles: {
396 font: 'Roboto',
397 fontSize: 7,
398 cellPadding: 1.5,
399 textColor: [30, 30, 30],
400 lineColor: [200, 200, 200],
401 lineWidth: 0.2,
402 },
403 headStyles: {
404 fillColor: [0, 114, 209],
405 textColor: [255, 255, 255],
406 fontStyle: 'bold',
407 fontSize: 7,
408 halign: 'center',
409 },
410 columnStyles: {
411 0: { halign: 'center', cellWidth: 8 },
412 1: { halign: 'center', cellWidth: 18 },
413 2: { cellWidth: 70 },
414 3: { halign: 'center', cellWidth: 16 },
415 4: { halign: 'center', cellWidth: 14 },
416 5: { halign: 'center', cellWidth: 22 },
417 },
418 margin: { left: MARGIN, right: MARGIN },
419 didParseCell: (data) => {
420 if (data.section === 'body' && data.row.index % 2 === 0) {
421 data.cell.styles.fillColor = [245, 247, 250];
422 }
423 },
424 });
425
426 // eslint-disable-next-line @typescript-eslint/no-explicit-any
427 y = (doc as any).lastAutoTable.finalY + 8;
428
429 // Summary
430 const totalCredits = passedExams.reduce((sum, e) => sum + e.credits, 0);
431 const grades = passedExams.map((e) => e.grade).filter((g) => g > 0);
432 const average = grades.length > 0 ? (grades.reduce((s, g) => s + g, 0) / grades.length).toFixed(2) : '0.00';
433
434 doc.setFont('Roboto', 'bold');
435 doc.setFontSize(9);
436 doc.text(`Вкупно кредити: ${totalCredits} | Просечна оцена: ${average}`, MARGIN, y);
437
438 // Footer
439 drawFooter(doc, archiveNumber, date);
440
441 doc.save(`УППИ_образец_${student.index}.pdf`);
442}
443
444/* ==================================================================
445 PDF 3 — Уверение за редовен студент (Regular Student Certificate)
446 ================================================================== */
447
448export async function generateRegularStudentCertificate(
449 student: StudentInfo,
450 enrollment: EnrollmentInfo,
451 passedExams: PassedExam[],
452 archiveNumber: string,
453 date: string,
454): Promise<void> {
455 const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
456 await loadCyrillicFonts(doc);
457
458 let y = 20;
459
460 // --- Header ---
461 y = drawUniversityHeader(doc, y);
462
463 // --- Title ---
464 y = drawDocumentTitle(doc, 'УВЕРЕНИЕ', y);
465 doc.setFont('Roboto', 'normal');
466 doc.setFontSize(10);
467 doc.text('за редовен студент', PAGE_WIDTH / 2, y, { align: 'center' });
468 y += 12;
469
470 // --- Body text ---
471 doc.setFont('Roboto', 'normal');
472 doc.setFontSize(10);
473
474 const totalCredits = passedExams.reduce((sum, e) => sum + e.credits, 0);
475 const grades = passedExams.map((e) => e.grade).filter((g) => g > 0);
476 const average = grades.length > 0 ? (grades.reduce((s, g) => s + g, 0) / grades.length).toFixed(2) : '0.00';
477
478 const bodyText = [
479 `Со ова се потврдува дека студентот/студентката`,
480 ``,
481 `${student.firstName} ${student.middleName} ${student.lastName}`,
482 ``,
483 `со индекс број ${student.index}, ЕМБГ ${student.embg},`,
484 `е редовен студент на Факултетот за информатички науки и компјутерско`,
485 `инженерство при Универзитетот „Св. Кирил и Методиј" - Скопје.`,
486 ``,
487 `Студиска програма: ${enrollment.program}`,
488 `Циклус на студии: ${enrollment.cycle}`,
489 `Статус: ${enrollment.status}`,
490 `Година на запишување: ${enrollment.enrollmentYear}`,
491 ``,
492 `Студентот/студентката до сега има положено ${passedExams.length} испити,`,
493 `со вкупно ${totalCredits} кредити и просечна оцена ${average}.`,
494 ``,
495 `Ова уверение се издава за потребите на студентот/студентката`,
496 `и може да послужи за секоја законска употреба.`,
497 ];
498
499 for (const line of bodyText) {
500 if (line === '') {
501 y += 4;
502 } else {
503 if (line.startsWith(`${student.firstName}`)) {
504 doc.setFont('Roboto', 'bold');
505 doc.setFontSize(12);
506 doc.text(line, PAGE_WIDTH / 2, y, { align: 'center' });
507 doc.setFont('Roboto', 'normal');
508 doc.setFontSize(10);
509 } else {
510 doc.text(line, MARGIN, y);
511 }
512 y += 6;
513 }
514 }
515
516 // Footer
517 drawFooter(doc, archiveNumber, date);
518
519 doc.save(`Уверение_редовен_студент_${student.index}.pdf`);
520}
521
522/* ==================================================================
523 Generic document — fallback for other document types
524 ================================================================== */
525
526export async function generateGenericDocument(
527 student: StudentInfo,
528 enrollment: EnrollmentInfo,
529 documentTitle: string,
530 archiveNumber: string,
531 date: string,
532): Promise<void> {
533 const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
534 await loadCyrillicFonts(doc);
535
536 let y = 20;
537
538 y = drawUniversityHeader(doc, y);
539 y = drawDocumentTitle(doc, documentTitle.toUpperCase(), y);
540
541 doc.setFont('Roboto', 'normal');
542 doc.setFontSize(10);
543
544 y = drawInfoRow(doc, 'Студент', `${student.firstName} ${student.middleName} ${student.lastName}`, MARGIN, y);
545 y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y);
546 y = drawInfoRow(doc, 'ЕМБГ', student.embg, MARGIN, y);
547 y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y);
548 y = drawInfoRow(doc, 'Циклус', enrollment.cycle, MARGIN, y);
549 y = drawInfoRow(doc, 'Статус', enrollment.status, MARGIN, y);
550 y += 8;
551
552 doc.setFont('Roboto', 'normal');
553 doc.setFontSize(10);
554 doc.text('Документот е издаден по барање на студентот.', MARGIN, y);
555
556 drawFooter(doc, archiveNumber, date);
557
558 doc.save(`${documentTitle.replace(/\s+/g, '_')}_${student.index}.pdf`);
559}
560
561/* ==================================================================
562 Themed request generators — one for each document type
563 ================================================================== */
564
565async function generateRequestDocument(
566 student: StudentInfo,
567 enrollment: EnrollmentInfo,
568 title: string,
569 bodyLines: string[],
570 archiveNumber: string,
571 date: string,
572 filename: string,
573): Promise<void> {
574 const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
575 await loadCyrillicFonts(doc);
576
577 let y = 20;
578
579 y = drawUniversityHeader(doc, y);
580 y = drawDocumentTitle(doc, title, y);
581
582 // Student info block
583 doc.setFont('Roboto', 'normal');
584 doc.setFontSize(9);
585 y = drawInfoRow(doc, 'Студент', `${student.firstName} ${student.middleName} ${student.lastName}`, MARGIN, y);
586 y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y);
587 y = drawInfoRow(doc, 'ЕМБГ', student.embg, MARGIN, y);
588 y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y);
589 y = drawInfoRow(doc, 'Циклус', enrollment.cycle, MARGIN, y);
590 y = drawInfoRow(doc, 'Статус', enrollment.status, MARGIN, y);
591 y = drawInfoRow(doc, 'Година на упис', String(enrollment.enrollmentYear), MARGIN, y);
592 y += 8;
593
594 // Separator
595 doc.setDrawColor(200, 200, 200);
596 doc.setLineWidth(0.3);
597 doc.line(MARGIN, y, PAGE_WIDTH - MARGIN, y);
598 y += 8;
599
600 // Body text
601 doc.setFont('Roboto', 'normal');
602 doc.setFontSize(10);
603 for (const line of bodyLines) {
604 if (line === '') {
605 y += 5;
606 } else {
607 const wrapped = doc.splitTextToSize(line, CONTENT_WIDTH);
608 for (const wl of wrapped) {
609 doc.text(wl, MARGIN, y);
610 y += 5.5;
611 }
612 }
613 }
614
615 drawFooter(doc, archiveNumber, date);
616 doc.save(`${filename}_${student.index}.pdf`);
617}
618
619// --- Administrative Regulation ---
620export async function generateAdministrativeRegulation(
621 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
622): Promise<void> {
623 await generateRequestDocument(student, enrollment,
624 'БАРАЊЕ ЗА АДМИНИСТРАТИВНО РЕГУЛИРАЊЕ НА РЕТРОАКТИВЕН СЕМЕСТАР',
625 [
626 `До: Деканат на Факултетот за информатички науки и компјутерско инженерство`,
627 '',
628 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
629 `запишан/а на студиската програма ${enrollment.program}, ${enrollment.cycle} циклус студии,`,
630 `со ова барање побарувам административно регулирање на ретроактивен семестар.`,
631 '',
632 `Молам да ми се одобри административна регулација на семестар кој не е запишан во предвидениот рок,`,
633 `согласно одлуката на Факултетот и важечката регулатива.`,
634 '',
635 `Прилози: Уплатница за административна такса од 1000 ден.`,
636 ],
637 archiveNumber, date, 'Административно_регулирање');
638}
639
640// --- Late Exam Registration ---
641export async function generateExamApplicationSatisfaction(
642 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
643): Promise<void> {
644 await generateRequestDocument(student, enrollment,
645 'БАРАЊЕ ЗА ЗАДОЧНО ПРИЈАВУВАЊЕ НА ИСПИТ',
646 [
647 `До: Деканат на ФИНКИ`,
648 '',
649 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
650 `запишан/а на студиската програма ${enrollment.program},`,
651 `побарувам задочно пријавување на испит кој не е пријавен во предвидениот рок.`,
652 '',
653 `Причина за задоцнување: ___________________________________`,
654 '',
655 `Молам за одобрување на ова барање.`,
656 '',
657 `Прилози: Уплатница за административна такса од 1000 ден.`,
658 ],
659 archiveNumber, date, 'Задочно_пријавување_испит');
660}
661
662// --- Semester Enrollment After Deadline ---
663export async function generateSemesterEnrollmentAfterDeadline(
664 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
665): Promise<void> {
666 await generateRequestDocument(student, enrollment,
667 'БАРАЊЕ ЗА ЗАПИШУВАЊЕ НА СЕМЕСТАР ПО ИСТЕК НА РОК',
668 [
669 `До: Деканат на ФИНКИ`,
670 '',
671 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
672 `побарувам да ми се одобри запишување на нареден семестар по истекот на официјалниот рок.`,
673 '',
674 `Причина за задоцнето запишување: ___________________________________`,
675 '',
676 `Молам за одобрување.`,
677 '',
678 `Прилози: Уплатница за административна такса од 1500 ден.`,
679 ],
680 archiveNumber, date, 'Запишување_семестар_по_рок');
681}
682
683// --- Certificate Issuance ---
684export async function generateCertificateIssuanceVarious(
685 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
686): Promise<void> {
687 await generateRequestDocument(student, enrollment,
688 'БАРАЊЕ ЗА ИЗДАВАЊЕ НА ПОТВРДИ ПО РАЗНИ ОСНОВИ',
689 [
690 `До: Деканат на ФИНКИ`,
691 '',
692 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
693 `побарувам издавање на потврда за следната намена:`,
694 '',
695 `Намена: ___________________________________`,
696 '',
697 `Молам да ми биде издадена бараната потврда.`,
698 '',
699 `Прилози: Уплатница за административна такса од 1500 ден.`,
700 ],
701 archiveNumber, date, 'Издавање_потврди');
702}
703
704// --- Study Suspension ---
705export async function generateStudySuspension(
706 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
707): Promise<void> {
708 await generateRequestDocument(student, enrollment,
709 'БАРАЊЕ ЗА МИРУВАЊЕ НА СТУДИИТЕ',
710 [
711 `До: Деканат на ФИНКИ`,
712 '',
713 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
714 `запишан/а на студиската програма ${enrollment.program},`,
715 `побарувам мирување на студиите за период од ______ семестри.`,
716 '',
717 `Причина за мирување: ___________________________________`,
718 '',
719 `Изјавувам дека сум запознаен/а со правилата за продолжување на студиите`,
720 `по истекот на периодот на мирување.`,
721 '',
722 `Прилози: Уплатница за административна такса од 2000 ден.`,
723 ],
724 archiveNumber, date, 'Мирување_студии');
725}
726
727// --- Diploma Thesis Cancellation ---
728export async function generateDiplomaThesisCancellation(
729 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
730): Promise<void> {
731 await generateRequestDocument(student, enrollment,
732 'БАРАЊЕ ЗА ОТКАЖУВАЊЕ НА ПРИЈАВЕНА ТЕМА ЗА ДИПЛОМСКА РАБОТА',
733 [
734 `До: Деканат на ФИНКИ`,
735 '',
736 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
737 `побарувам откажување на претходно пријавената тема за дипломска работа.`,
738 '',
739 `Назив на темата: ___________________________________`,
740 `Ментор: ___________________________________`,
741 '',
742 `Причина за откажување: ___________________________________`,
743 '',
744 `Прилози: Уплатница за административна такса од 1000 ден.`,
745 ],
746 archiveNumber, date, 'Откажување_дипломска');
747}
748
749// --- Graduation Package ---
750export async function generateDiplomaPackage(
751 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
752): Promise<void> {
753 await generateRequestDocument(student, enrollment,
754 'БАРАЊЕ ЗА ПАКЕТ ЗА ДИПЛОМИРАЊЕ',
755 [
756 `До: Деканат на ФИНКИ`,
757 '',
758 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
759 `запишан/а на студиската програма ${enrollment.program}, ${enrollment.cycle} циклус,`,
760 `побарувам издавање на пакет за дипломирање.`,
761 '',
762 `Со ова потврдувам дека ги имам исполнето сите услови за дипломирање`,
763 `согласно студиската програма и правилникот на Факултетот.`,
764 '',
765 `Пакетот за дипломирање ги вклучува: диплома, додаток на диплома и свечена промоција.`,
766 '',
767 `Прилози: Уплатница за административна такса од 6200 ден.`,
768 ],
769 archiveNumber, date, 'Пакет_дипломирање');
770}
771
772// --- Exam Cancellation ---
773export async function generateExamCancellation(
774 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
775): Promise<void> {
776 await generateRequestDocument(student, enrollment,
777 'БАРАЊЕ ЗА ПОНИШТУВАЊЕ НА ИСПИТ',
778 [
779 `До: Деканат на ФИНКИ`,
780 '',
781 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
782 `побарувам поништување на испит по предметот:`,
783 '',
784 `Предмет: ___________________________________`,
785 `Датум на полагање: ___________________________________`,
786 '',
787 `Причина за поништување: ___________________________________`,
788 '',
789 `Прилози: Уплатница за административна такса од 2000 ден.`,
790 ],
791 archiveNumber, date, 'Поништување_испит');
792}
793
794// --- Study Continuation ---
795export async function generateStudyContinuation(
796 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
797): Promise<void> {
798 await generateRequestDocument(student, enrollment,
799 'БАРАЊЕ ЗА ПРОДОЛЖУВАЊЕ НА СТУДИИ ВО МИРУВАЊЕ',
800 [
801 `До: Деканат на ФИНКИ`,
802 '',
803 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
804 `побарувам продолжување на студиите по периодот на мирување.`,
805 '',
806 `Период на мирување: од ____________ до ____________`,
807 '',
808 `Молам да ми се одобри продолжување на студиите согласно тековната`,
809 `студиска програма и важечкиот правилник.`,
810 '',
811 `Прилози: Уплатница за административна такса од 2000 ден.`,
812 ],
813 archiveNumber, date, 'Продолжување_студии');
814}
815
816// --- Elective Subject Change ---
817export async function generateElectiveSubjectChange(
818 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
819): Promise<void> {
820 await generateRequestDocument(student, enrollment,
821 'БАРАЊЕ ЗА ПРОМЕНА НА ИЗБОРЕН ПРЕДМЕТ',
822 [
823 `До: Деканат на ФИНКИ`,
824 '',
825 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
826 `побарувам промена на изборен предмет.`,
827 '',
828 `Предмет што се менува: ___________________________________`,
829 `Нов изборен предмет: ___________________________________`,
830 '',
831 `Причина за промена: ___________________________________`,
832 '',
833 `Прилози: Уплатница за административна такса од 1500 ден.`,
834 ],
835 archiveNumber, date, 'Промена_изборен_предмет');
836}
837
838// --- Passed Subject Change ---
839export async function generatePassedSubjectChange(
840 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
841): Promise<void> {
842 await generateRequestDocument(student, enrollment,
843 'БАРАЊЕ ЗА ПРОМЕНА НА ПОЛОЖЕН ПРЕДМЕТ',
844 [
845 `До: Деканат на ФИНКИ`,
846 '',
847 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
848 `побарувам промена / корекција на запис за положен предмет.`,
849 '',
850 `Предмет: ___________________________________`,
851 `Датум на полагање: ___________________________________`,
852 `Оцена: ___________________________________`,
853 '',
854 `Детали за промена: ___________________________________`,
855 ],
856 archiveNumber, date, 'Промена_положен_предмет');
857}
858
859// --- Study Program Change (Same Accreditation) ---
860export async function generateProgramChangeSameAccreditation(
861 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
862): Promise<void> {
863 await generateRequestDocument(student, enrollment,
864 'БАРАЊЕ ЗА ПРОМЕНА НА СТУДИСКА ПРОГРАМА ОД ИСТА АКРЕДИТАЦИЈА',
865 [
866 `До: Деканат на ФИНКИ`,
867 '',
868 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
869 `тековно запишан/а на програмата ${enrollment.program},`,
870 `побарувам промена на студиска програма во рамки на истата акредитација.`,
871 '',
872 `Нова студиска програма: ___________________________________`,
873 '',
874 `Причина за промена: ___________________________________`,
875 '',
876 `Прилози: Уплатница за административна такса од 2000 ден.`,
877 ],
878 archiveNumber, date, 'Промена_програма_иста_акредитација');
879}
880
881// --- Study Program Change (New Accreditation) ---
882export async function generateProgramChangeNewAccreditation(
883 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
884): Promise<void> {
885 await generateRequestDocument(student, enrollment,
886 'БАРАЊЕ ЗА ПРОМЕНА НА СТУДИСКА ПРОГРАМА ОД ПОНОВА АКРЕДИТАЦИЈА',
887 [
888 `До: Деканат на ФИНКИ`,
889 '',
890 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
891 `тековно запишан/а на програмата ${enrollment.program},`,
892 `побарувам промена на студиска програма во рамки на нова акредитација.`,
893 '',
894 `Нова студиска програма: ___________________________________`,
895 `Нова акредитација: ___________________________________`,
896 '',
897 `Причина за промена: ___________________________________`,
898 '',
899 `Прилози: Уплатница за административна такса од 3000 ден.`,
900 ],
901 archiveNumber, date, 'Промена_програма_нова_акредитација');
902}
903
904// --- Diploma Supplement ---
905export async function generateDiplomaSupplement(
906 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
907): Promise<void> {
908 await generateRequestDocument(student, enrollment,
909 'ДОДАТОК НА ДИПЛОМА',
910 [
911 `До: Деканат на ФИНКИ`,
912 '',
913 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
914 `запишан/а на студиската програма ${enrollment.program}, ${enrollment.cycle} циклус,`,
915 `побарувам издавање на додаток на диплома.`,
916 '',
917 `Додатокот на дипломата се издава како придружен документ`,
918 `кон дипломата за завршените студии.`,
919 ],
920 archiveNumber, date, 'Додаток_диплома');
921}
922
923// --- Diploma Supplement Second Cycle ---
924export async function generateDiplomaSupplementSecondCycle(
925 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
926): Promise<void> {
927 await generateRequestDocument(student, enrollment,
928 'ДОДАТОК НА ДИПЛОМА (ВТОР ЦИКЛУС)',
929 [
930 `До: Деканат на ФИНКИ`,
931 '',
932 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
933 `запишан/а на студиската програма ${enrollment.program}, втор циклус на студии,`,
934 `побарувам издавање на додаток на диплома за вториот циклус студии.`,
935 '',
936 `Додатокот на дипломата се издава како придружен документ`,
937 `кон магистерската диплома.`,
938 ],
939 archiveNumber, date, 'Додаток_диплома_втор_циклус');
940}
941
942// --- MKSA ---
943export async function generateMKSA(
944 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
945): Promise<void> {
946 await generateRequestDocument(student, enrollment,
947 'МКСА - МАКЕДОНСКА КВАЛИФИКАЦИСКА РАМКА',
948 [
949 `До: Деканат на ФИНКИ`,
950 '',
951 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
952 `побарувам издавање на МКСА (Македонска Квалификациска Рамка) документ.`,
953 '',
954 `Студиска програма: ${enrollment.program}`,
955 `Циклус: ${enrollment.cycle}`,
956 '',
957 `Документот е потребен за целите на: ___________________________________`,
958 '',
959 `Прилози: Уплатница за административна такса од 750 ден.`,
960 ],
961 archiveNumber, date, 'МКСА');
962}
963
964// --- Exam Recognition ---
965export async function generateExamRecognition(
966 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
967): Promise<void> {
968 await generateRequestDocument(student, enrollment,
969 'ПРИЗНАВАЊЕ НА ПОЛОЖЕНИ ИСПИТИ',
970 [
971 `До: Деканат на ФИНКИ`,
972 '',
973 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
974 `побарувам признавање на положени испити од претходна институција / програма.`,
975 '',
976 `Претходна институција: ___________________________________`,
977 `Претходна програма: ___________________________________`,
978 '',
979 `Листа на предмети за признавање:`,
980 `1. ___________________________________`,
981 `2. ___________________________________`,
982 `3. ___________________________________`,
983 '',
984 `Прилози: Уверение за положени испити од претходна институција.`,
985 ],
986 archiveNumber, date, 'Признавање_испити');
987}
988
989// --- Student Card (Old) ---
990export async function generateStudentCardOld(
991 student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
992): Promise<void> {
993 await generateRequestDocument(student, enrollment,
994 'СТУДЕНТСКИ КАРТОН (СТАРО)',
995 [
996 `До: Деканат на ФИНКИ`,
997 '',
998 `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
999 `побарувам издавање на студентски картон (стар формат).`,
1000 '',
1001 `Студиска програма: ${enrollment.program}`,
1002 `Година на упис: ${enrollment.enrollmentYear}`,
1003 '',
1004 `Молам да ми биде издаден студентски картон.`,
1005 ],
1006 archiveNumber, date, 'Студентски_картон');
1007}
1008
1009/* ==================================================================
1010 Main dispatcher — called from the Documents page
1011 ================================================================== */
1012
1013// Map document type IDs → generator function names
1014const DOCUMENT_TYPE_MAP: Record<string, string> = {
1015 'административно регулирање': 'administrative_regulation',
1016 'задочно пријавување': 'exam_application_satisfaction',
1017 'запишување на семестар по истек': 'semester_enrollment_after_deadline',
1018 'издавање потврди': 'certificate_issuance_various',
1019 'мирување на студиите': 'student_status_verification',
1020 'откажување на пријавена тема': 'diploma_thesis_postponement',
1021 'пакет за дипломирање': 'diploma_package',
1022 'поништување на испит': 'exam_postponement',
1023 'потврда за редовен студент': 'regular_student_certificate',
1024 'продолжување на студии во мирување': 'study_continuation_verification',
1025 'промена на изборен предмет': 'failed_subject_grade_change',
1026 'промена на положен предмет': 'passed_subject_grade_change',
1027 'промена на студиска програма од иста': 'study_program_change_same_accreditation',
1028 'промена на студиска програма од понова': 'study_program_change_new_accreditation',
1029 'додаток на диплома (втор циклус)': 'diploma_supplement_second_cycle',
1030 'додаток на диплома': 'diploma_supplement',
1031 'мкса': 'mksa',
1032 'признавање на положени испити': 'exam_recognition',
1033 'студентски картон': 'student_card_old',
1034};
1035
1036function identifyDocumentType(request: string): string | null {
1037 const lower = request.toLowerCase();
1038
1039 // Exact-match patterns (check longer patterns first)
1040 if (lower.includes('положени испити')) return 'passed_exams_certificate';
1041 if (lower.includes('уппи')) return 'uppi_form';
1042 if (lower.includes('редовен студент')) return 'regular_student_certificate';
1043
1044 // Match by document type keywords
1045 for (const [keyword, docType] of Object.entries(DOCUMENT_TYPE_MAP)) {
1046 if (lower.includes(keyword)) return docType;
1047 }
1048
1049 return null;
1050}
1051
1052export async function downloadDocumentPDF(
1053 documentRequest: string,
1054 archiveNumber: string,
1055 documentDate: string,
1056 accessToken: string,
1057): Promise<void> {
1058 // Fetch student profile
1059 const profileRes = await fetch(apiUrl('/api/user/getUser'), {
1060 headers: { Authorization: `Bearer ${accessToken}` },
1061 });
1062 if (!profileRes.ok) throw new Error('Не може да се вчита профилот на студентот');
1063
1064 const profile = await profileRes.json();
1065 const { personalInfo, enrollmentInfo, birthInfo, contact, previousEducation } = profile;
1066
1067 const student: StudentInfo = {
1068 firstName: personalInfo.firstName,
1069 middleName: personalInfo.middleName,
1070 lastName: personalInfo.lastName,
1071 index: personalInfo.index,
1072 embg: personalInfo.embg,
1073 dateOfBirth: personalInfo.dateOfBirth,
1074 gender: personalInfo.gender,
1075 nationality: personalInfo.nationality,
1076 citizenship: personalInfo.citizenship,
1077 currentPlan: personalInfo.currentPlan,
1078 registryNumber: personalInfo.registryNumber,
1079 studyGroup: personalInfo.studyGroup,
1080 scholarship: personalInfo.scholarship,
1081 };
1082
1083 const enrollment: EnrollmentInfo = {
1084 enrollmentYear: enrollmentInfo.enrollmentYear,
1085 status: enrollmentInfo.status,
1086 cycle: enrollmentInfo.cycle,
1087 program: enrollmentInfo.program,
1088 quota: enrollmentInfo.quota,
1089 };
1090
1091 const docType = identifyDocumentType(documentRequest);
1092
1093 // Types that need passed exams data
1094 const needsExams = docType === 'passed_exams_certificate' || docType === 'uppi_form' || docType === 'regular_student_certificate';
1095
1096 let passedExams: PassedExam[] = [];
1097 if (needsExams) {
1098 const examsRes = await fetch(apiUrl('/api/user/getPassedSubjects'), {
1099 headers: { Authorization: `Bearer ${accessToken}` },
1100 });
1101 if (examsRes.ok) {
1102 const examsData = await examsRes.json();
1103 passedExams = (examsData?.passedSubjects ?? []).map((s: Record<string, unknown>) => ({
1104 id: s.id as number,
1105 code: s.code as string,
1106 subject: s.subject as string,
1107 credits: s.credits as number,
1108 grade: s.grade as number,
1109 date: s.date as string,
1110 semester: s.semester as string,
1111 professor: (s.professor as string) || '—',
1112 }));
1113 }
1114 }
1115
1116 switch (docType) {
1117 case 'passed_exams_certificate':
1118 await generatePassedExamsCertificate(student, enrollment, passedExams, archiveNumber, documentDate);
1119 break;
1120 case 'uppi_form': {
1121 const birth: BirthInfo = {
1122 placeOfBirth: birthInfo.placeOfBirth,
1123 municipalityOfBirth: birthInfo.municipalityOfBirth,
1124 country: birthInfo.country,
1125 };
1126 const contactInfo: ContactInfo = {
1127 placeOfResidence: contact.placeOfResidence,
1128 municipalityOfResidence: contact.municipalityOfResidence,
1129 address: contact.address,
1130 phone: contact.phone,
1131 mobilePhone: contact.mobilePhone,
1132 email: contact.email,
1133 };
1134 const prevEdu: PreviousEducation = {
1135 type: previousEducation.type,
1136 profession: previousEducation.profession,
1137 average: previousEducation.average,
1138 language: previousEducation.language,
1139 country: previousEducation.country,
1140 previousUniversity: previousEducation.previousUniversity,
1141 previousFaculty: previousEducation.previousFaculty,
1142 };
1143 await generateUPPIForm(student, enrollment, birth, contactInfo, prevEdu, passedExams, archiveNumber, documentDate);
1144 break;
1145 }
1146 case 'regular_student_certificate':
1147 await generateRegularStudentCertificate(student, enrollment, passedExams, archiveNumber, documentDate);
1148 break;
1149 case 'administrative_regulation':
1150 await generateAdministrativeRegulation(student, enrollment, archiveNumber, documentDate);
1151 break;
1152 case 'exam_application_satisfaction':
1153 await generateExamApplicationSatisfaction(student, enrollment, archiveNumber, documentDate);
1154 break;
1155 case 'semester_enrollment_after_deadline':
1156 await generateSemesterEnrollmentAfterDeadline(student, enrollment, archiveNumber, documentDate);
1157 break;
1158 case 'certificate_issuance_various':
1159 await generateCertificateIssuanceVarious(student, enrollment, archiveNumber, documentDate);
1160 break;
1161 case 'student_status_verification':
1162 await generateStudySuspension(student, enrollment, archiveNumber, documentDate);
1163 break;
1164 case 'diploma_thesis_postponement':
1165 await generateDiplomaThesisCancellation(student, enrollment, archiveNumber, documentDate);
1166 break;
1167 case 'diploma_package':
1168 await generateDiplomaPackage(student, enrollment, archiveNumber, documentDate);
1169 break;
1170 case 'exam_postponement':
1171 await generateExamCancellation(student, enrollment, archiveNumber, documentDate);
1172 break;
1173 case 'study_continuation_verification':
1174 await generateStudyContinuation(student, enrollment, archiveNumber, documentDate);
1175 break;
1176 case 'failed_subject_grade_change':
1177 await generateElectiveSubjectChange(student, enrollment, archiveNumber, documentDate);
1178 break;
1179 case 'passed_subject_grade_change':
1180 await generatePassedSubjectChange(student, enrollment, archiveNumber, documentDate);
1181 break;
1182 case 'study_program_change_same_accreditation':
1183 await generateProgramChangeSameAccreditation(student, enrollment, archiveNumber, documentDate);
1184 break;
1185 case 'study_program_change_new_accreditation':
1186 await generateProgramChangeNewAccreditation(student, enrollment, archiveNumber, documentDate);
1187 break;
1188 case 'diploma_supplement':
1189 await generateDiplomaSupplement(student, enrollment, archiveNumber, documentDate);
1190 break;
1191 case 'diploma_supplement_second_cycle':
1192 await generateDiplomaSupplementSecondCycle(student, enrollment, archiveNumber, documentDate);
1193 break;
1194 case 'mksa':
1195 await generateMKSA(student, enrollment, archiveNumber, documentDate);
1196 break;
1197 case 'exam_recognition':
1198 await generateExamRecognition(student, enrollment, archiveNumber, documentDate);
1199 break;
1200 case 'student_card_old':
1201 await generateStudentCardOld(student, enrollment, archiveNumber, documentDate);
1202 break;
1203 default:
1204 await generateGenericDocument(student, enrollment, documentRequest, archiveNumber, documentDate);
1205 break;
1206 }
1207}
Note: See TracBrowser for help on using the repository browser.