Generate an editable norm-compliant cover email per application
Alongside the tailored PDFs, the LLM now produces a short, DIN-style Begleit-E-Mail (subject + body) grounded in the same data: same salutation as the cover letter, the target role, a line or two of core motivation, a pointer to the attached documents, and a friendly close. Persisted in new bewerbungen columns (email_betreff, email_anschreiben) and surfaced in an editable subject field + textarea on the detail page, so the applicant can tweak it before sending; a copy button puts subject and body on the clipboard. Values are stored raw and HTML-escaped on render to avoid double-escaping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -254,7 +254,7 @@ async function runGeneration(bewerbungId) {
|
||||
const basisAnhaenge = await dbAll('SELECT * FROM basis_anhaenge ORDER BY id ASC');
|
||||
const settings = await dbGet('SELECT * FROM settings WHERE id = 1');
|
||||
|
||||
const documents = await generateApplicationDocuments({
|
||||
const { documents, email } = await generateApplicationDocuments({
|
||||
job: {
|
||||
firma: bewerbung.firma,
|
||||
stelle: bewerbung.stelle,
|
||||
@@ -297,8 +297,9 @@ async function runGeneration(bewerbungId) {
|
||||
}
|
||||
|
||||
await dbRun(
|
||||
"UPDATE bewerbungen SET generierung_status = 'fertig', generierung_fehler = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||||
[bewerbungId]
|
||||
"UPDATE bewerbungen SET generierung_status = 'fertig', generierung_fehler = NULL, " +
|
||||
"email_betreff = ?, email_anschreiben = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||||
[(email && email.betreff) || '', (email && email.text) || '', bewerbungId]
|
||||
);
|
||||
console.log(`Bewerbungsunterlagen für #${bewerbungId} generiert (${documents.length} Dokument(e)).`);
|
||||
} catch (error) {
|
||||
@@ -340,6 +341,8 @@ function initializeDatabase() {
|
||||
db.run('ALTER TABLE bewerbungen ADD COLUMN quelle_url TEXT', () => {
|
||||
db.run('ALTER TABLE bewerbungen ADD COLUMN generierung_status TEXT', () => {
|
||||
db.run('ALTER TABLE bewerbungen ADD COLUMN generierung_fehler TEXT', () => {
|
||||
db.run('ALTER TABLE bewerbungen ADD COLUMN email_betreff TEXT', () => {
|
||||
db.run('ALTER TABLE bewerbungen ADD COLUMN email_anschreiben TEXT', () => {
|
||||
db.run('ALTER TABLE bewerbungen ADD COLUMN llm_notizen TEXT', () => {
|
||||
|
||||
// Chronological status changes, each with an optional comment
|
||||
@@ -435,6 +438,8 @@ function initializeDatabase() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -774,6 +779,26 @@ initializeDatabase().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Save the editable e-mail cover text (Begleit-E-Mail) after the user tweaks it.
|
||||
app.post('/bewerbung/:id/email', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const bewerbung = await dbGet('SELECT id FROM bewerbungen WHERE id = ?', [id]);
|
||||
if (!bewerbung) return res.status(404).send('Bewerbung nicht gefunden');
|
||||
|
||||
// Store raw; the value is HTML-escaped on render (EJS <%= %>), matching how
|
||||
// the generated e-mail is stored. Sanitising here would double-escape.
|
||||
await dbRun(
|
||||
'UPDATE bewerbungen SET email_betreff = ?, email_anschreiben = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[String(req.body.email_betreff || ''), String(req.body.email_anschreiben || ''), id]
|
||||
);
|
||||
res.redirect('/bewerbung/' + id + '#email');
|
||||
} catch (error) {
|
||||
console.error('Error saving e-mail text:', error);
|
||||
res.status(500).send('Serverfehler');
|
||||
}
|
||||
});
|
||||
|
||||
// Add a timeline entry (status change with date + comment)
|
||||
app.post('/bewerbung/:id/verlauf', async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user