mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 00:16:41 +02:00
fix(core): PDF-Export — Bullets, Zertifizierungen, Bild-Anhaenge
- Tasks: Bullet-Praefix nur fuer Zeilen mit echtem Aufzaehlungszeichen (kein spurious Bullet bei Plaintext) - Zertifizierungen: Schriftgroesse reduziert (Titel 10->9pt, Aussteller 9->8pt) und Timeline-Linie gekuerzt - Anhaenge: Bild-Anhaenge werden als zusaetzliche Seiten ans PDF angehaengt - ExportData-Interface + getExportData() um attachments[] erweitert - Gleiche Bullet-Fix-Logik im DOCX-Export Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a37942b37d
commit
3d486e0541
3 changed files with 70 additions and 15 deletions
21
Summarize.md
21
Summarize.md
|
|
@ -6,6 +6,27 @@
|
|||
|
||||
---
|
||||
|
||||
### Aenderungen 2026-03-13 (18): Experten-Profil PDF-Export — 3 weitere Korrekturen
|
||||
|
||||
#### Backend (Core Service)
|
||||
- `expert-profile/expert-profile.service.ts` — `getExportData()`: Anhänge jetzt inkludiert (`attachments: { orderBy: { createdAt: 'asc' } }`)
|
||||
- `expert-profile/profile-export.service.ts` — PDF-Export:
|
||||
- Neues `ExportAttachment` Interface + `attachments[]` in `ExportData`
|
||||
- Tasks: Bullet `•` wird nur noch hinzugefügt, wenn Original-Zeile bereits ein Aufzählungszeichen (`•`, `\u2022`) oder Nummerierung (`1.`) enthält — kein spurious Bullet für Plaintext-Zeilen
|
||||
- Zertifizierungen: Schriftgröße reduziert (Titel 10pt→9pt, Aussteller 9pt→8pt, Abstände angepasst) — passt jetzt zur linken Spalte
|
||||
- Zertifizierungen: Timeline-Linienlänge 40→32px
|
||||
- Anhänge: Bild-Anhänge (image/jpeg, image/png, etc.) werden als zusätzliche Seiten ans PDF angehängt (Überschrift "ANLAGE: dateiname" + Bild; PDFs + andere Formate werden übersprungen)
|
||||
- `expert-profile/profile-export.service.ts` — DOCX-Export:
|
||||
- Tasks: Gleiche Bullet-Fix-Logik (nur Bullet wenn Original-Zeile Bullet/Nummer hat)
|
||||
|
||||
#### TypeScript
|
||||
- `npx tsc --noEmit` in packages/core-service: 0 Fehler
|
||||
|
||||
#### Deployment-Hinweis (Schritt 18)
|
||||
- Rebuild + Restart: nur core-service
|
||||
|
||||
---
|
||||
|
||||
### Aenderungen 2026-03-13 (17): Experten-Profil PDF-Export — 5 Korrekturen
|
||||
|
||||
#### Backend (Core Service)
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ export class ExpertProfileService {
|
|||
languages: { orderBy: { language: 'asc' } },
|
||||
projects: { orderBy: [{ fromYear: 'desc' }, { fromMonth: 'desc' }] },
|
||||
certifications: { orderBy: { issueYear: 'desc' } },
|
||||
attachments: { orderBy: { createdAt: 'asc' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -55,6 +55,14 @@ interface ExportLanguage {
|
|||
level: string;
|
||||
}
|
||||
|
||||
interface ExportAttachment {
|
||||
id: string;
|
||||
filename: string;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface ExportData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
|
|
@ -71,6 +79,7 @@ interface ExportData {
|
|||
languages: ExportLanguage[];
|
||||
projects: ExportProject[];
|
||||
certifications: ExportCertification[];
|
||||
attachments: ExportAttachment[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +359,7 @@ export class ProfileExportService {
|
|||
yRight += doc.heightOfString(companyLine, { width: contentWidth }) + 2;
|
||||
}
|
||||
|
||||
// Aufgaben (Markdown-Marker entfernen)
|
||||
// Aufgaben (Bullet nur wenn Original-Zeile ein Aufzählungszeichen hat)
|
||||
if (proj.tasks) {
|
||||
const taskLines = proj.tasks.split('\n').filter((l: string) => l.trim());
|
||||
doc.font('Helvetica').fontSize(8).fillColor('#444444');
|
||||
|
|
@ -359,11 +368,13 @@ export class ProfileExportService {
|
|||
doc.addPage();
|
||||
yRight = 40;
|
||||
}
|
||||
const normalized = this.normalizeTaskLine(task);
|
||||
if (!normalized) continue;
|
||||
const bulletText = `\u2022 ${normalized}`;
|
||||
doc.text(bulletText, contentX + 4, yRight, { width: contentWidth - 8 });
|
||||
yRight += doc.heightOfString(bulletText, { width: contentWidth - 8 }) + 1;
|
||||
const raw = task.trim();
|
||||
const hasBullet = /^[•\u2022]\s/.test(raw) || /^\d+\.\s/.test(raw);
|
||||
const cleaned = this.normalizeTaskLine(raw);
|
||||
if (!cleaned) continue;
|
||||
const displayText = hasBullet ? `\u2022 ${cleaned}` : cleaned;
|
||||
doc.text(displayText, contentX + 4, yRight, { width: contentWidth - 8 });
|
||||
yRight += doc.heightOfString(displayText, { width: contentWidth - 8 }) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -399,26 +410,26 @@ export class ProfileExportService {
|
|||
// Timeline-Punkt
|
||||
doc.circle(certTimelineX, yRight + 4, 3.5).fill(accentColor);
|
||||
if (i < profile.certifications.length - 1) {
|
||||
doc.moveTo(certTimelineX, yRight + 8).lineTo(certTimelineX, yRight + 40)
|
||||
doc.moveTo(certTimelineX, yRight + 8).lineTo(certTimelineX, yRight + 32)
|
||||
.strokeColor(accentColor).lineWidth(1).stroke();
|
||||
}
|
||||
|
||||
// Jahr (fett)
|
||||
doc.font('Helvetica-Bold').fontSize(8).fillColor('#555555');
|
||||
doc.text(String(cert.issueYear), certContentX, yRight, { width: certContentWidth });
|
||||
yRight += 12;
|
||||
yRight += 11;
|
||||
|
||||
// Titel
|
||||
doc.font('Helvetica-Bold').fontSize(10).fillColor(accentColor);
|
||||
doc.font('Helvetica-Bold').fontSize(9).fillColor(accentColor);
|
||||
doc.text(cert.title, certContentX, yRight, { width: certContentWidth });
|
||||
yRight += doc.heightOfString(cert.title, { width: certContentWidth }) + 2;
|
||||
|
||||
// Zertifizierungsstelle
|
||||
doc.font('Helvetica').fontSize(9).fillColor('#555555');
|
||||
doc.font('Helvetica').fontSize(8).fillColor('#555555');
|
||||
doc.text(cert.issuingBody, certContentX, yRight, { width: certContentWidth });
|
||||
yRight += 14;
|
||||
yRight += 11;
|
||||
|
||||
yRight += 6;
|
||||
yRight += 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -467,6 +478,25 @@ export class ProfileExportService {
|
|||
}
|
||||
}
|
||||
|
||||
// --- ANHÄNGE (Bild-Anhänge als zusätzliche Seiten) ---
|
||||
const attachments = profile?.attachments ?? [];
|
||||
for (const att of attachments) {
|
||||
if (!att.mimetype.startsWith('image/')) continue;
|
||||
doc.addPage();
|
||||
let yAtt = 40;
|
||||
yAtt = this.pdfSectionTitle(doc, `ANLAGE: ${att.filename}`, 40, yAtt, pageWidth - 80, accentColor);
|
||||
try {
|
||||
const imgBuffer = this.base64ToBuffer(att.data);
|
||||
const maxW = pageWidth - 80;
|
||||
const maxH = 680;
|
||||
doc.image(imgBuffer, 40, yAtt, { fit: [maxW, maxH], align: 'center' });
|
||||
} catch (err) {
|
||||
this.logger.warn(`Anhang-Bild konnte nicht gerendert werden: ${att.filename}`, err);
|
||||
doc.font('Helvetica').fontSize(9).fillColor('#777777');
|
||||
doc.text(`(Bild konnte nicht dargestellt werden: ${att.filename})`, 40, yAtt, { width: pageWidth - 80 });
|
||||
}
|
||||
}
|
||||
|
||||
doc.end();
|
||||
});
|
||||
}
|
||||
|
|
@ -669,12 +699,15 @@ export class ProfileExportService {
|
|||
if (proj.tasks) {
|
||||
const taskLines = proj.tasks.split('\n').filter((l: string) => l.trim());
|
||||
for (const task of taskLines) {
|
||||
const normalized = this.normalizeTaskLine(task);
|
||||
if (!normalized) continue;
|
||||
const raw = task.trim();
|
||||
const hasBullet = /^[•\u2022]\s/.test(raw) || /^\d+\.\s/.test(raw);
|
||||
const cleaned = this.normalizeTaskLine(raw);
|
||||
if (!cleaned) continue;
|
||||
const displayText = hasBullet ? `\u2022 ${cleaned}` : cleaned;
|
||||
rightParagraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({ text: `\u2022 ${normalized}`, size: 16, color: '444444' }),
|
||||
new TextRun({ text: displayText, size: 16, color: '444444' }),
|
||||
],
|
||||
spacing: { after: 20 },
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue