Confirm before sending; add address + contact to job offers

"Bewerbung senden" now asks for an extra confirmation (with the
recipient) before dispatching.

Job offers gain adresse and ansprechpartner fields — shown, editable,
and carried through the REST API upsert (Swagger updated). Taking an
offer over now writes the employer address (street, number, city) and
the contact person into the application's AI notes (llm_notizen), so the
LLM can use them for the letter's Anschriftfeld and salutation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 17:10:13 +02:00
co-authored by Claude Opus 4.8
parent 6a5bdfa6f8
commit 360e04d677
6 changed files with 65 additions and 7 deletions
+22 -3
View File
@@ -640,6 +640,8 @@ function initializeDatabase() {
firma TEXT NOT NULL,
stelle TEXT NOT NULL,
ort TEXT,
adresse TEXT,
ansprechpartner TEXT,
gehalt TEXT,
beschreibung TEXT,
quelle_url TEXT,
@@ -657,6 +659,8 @@ function initializeDatabase() {
// Migration: add columns to pre-existing jobangebote tables.
db.run('ALTER TABLE jobangebote ADD COLUMN anzeige_datum DATE', () => {});
db.run('ALTER TABLE jobangebote ADD COLUMN kontakt_email TEXT', () => {});
db.run('ALTER TABLE jobangebote ADD COLUMN adresse TEXT', () => {});
db.run('ALTER TABLE jobangebote ADD COLUMN ansprechpartner TEXT', () => {});
// Normalized URL for URL-based de-duplication of offers (see lib/blacklist).
db.run('ALTER TABLE jobangebote ADD COLUMN url_norm TEXT', () => {
// Backfill url_norm for rows ingested before this column existed.
@@ -1726,11 +1730,23 @@ initializeDatabase().then(() => {
angebot.quelle ? `Importiert via: ${angebot.quelle}` : null,
].filter(Boolean);
// Employer address (street, house number, city) and contact person go into
// the AI notes so the LLM can use them for the letter's Anschriftfeld and
// salutation.
let anschrift = (angebot.adresse || '').trim();
if (angebot.ort && !anschrift.toLowerCase().includes(String(angebot.ort).toLowerCase())) {
anschrift = anschrift ? `${anschrift}, ${angebot.ort}` : String(angebot.ort);
}
const llmNotizen = [
anschrift ? `Anschrift des Arbeitgebers: ${anschrift}` : null,
angebot.ansprechpartner ? `Ansprechpartner: ${angebot.ansprechpartner}` : null,
].filter(Boolean).join('\n');
const result = await dbRun(
`INSERT INTO bewerbungen
(datum, firma, stelle, art, status, notizen, ort, stellenbeschreibung,
quelle_url, email_empfaenger, generierung_status)
VALUES (?, ?, ?, ?, 'Entwurf', ?, ?, ?, ?, ?, 'nicht_gestartet')`,
quelle_url, email_empfaenger, llm_notizen, generierung_status)
VALUES (?, ?, ?, ?, 'Entwurf', ?, ?, ?, ?, ?, ?, 'nicht_gestartet')`,
[
datum,
sanitizeInput(angebot.firma),
@@ -1741,6 +1757,7 @@ initializeDatabase().then(() => {
angebot.beschreibung || '',
angebot.quelle_url || '',
angebot.kontakt_email || '',
llmNotizen,
]
);
@@ -1799,7 +1816,7 @@ initializeDatabase().then(() => {
const b = req.body || {};
const quelleUrl = sanitizeInput(b.quelle_url || '');
await dbRun(
`UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, gehalt = ?,
`UPDATE jobangebote SET firma = ?, stelle = ?, ort = ?, adresse = ?, ansprechpartner = ?, gehalt = ?,
beschreibung = ?, kontakt_email = ?, quelle_url = ?, anzeige_datum = ?,
url_norm = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
@@ -1807,6 +1824,8 @@ initializeDatabase().then(() => {
sanitizeInput((b.firma || '').trim()) || angebot.firma,
sanitizeInput((b.stelle || '').trim()) || angebot.stelle,
sanitizeInput(b.ort || ''),
sanitizeInput(b.adresse || ''),
sanitizeInput(b.ansprechpartner || ''),
sanitizeInput(b.gehalt || ''),
sanitizeInput(b.beschreibung || ''),
sanitizeInput(b.kontakt_email || ''),