feat(akquise): Archiv bearbeiten und wiederherstellen

Im Archiv setzte der Stift nur `editing` - das Formular stand dort nicht,
also passierte sichtbar nichts. Es wird jetzt auch im Archiv gezeigt,
aber nur beim Bearbeiten: eingetragen wird weiterhin im Eingang.

Dazu ein Weg zurueck. `POST /api/acquisitions/:id/restore` setzt
`found_at` auf heute und leert `converted_at` samt `opportunity_id` -
ohne Letzteres haelt die Uebernahme einen Rueckfaeller im Archiv fest,
gleich welches Datum er traegt. Eine Spalte "wiederhergestellt" gibt es
bewusst nicht; die Tabelle fuehrt keinen Zustand.

Der Pfeil erscheint nur im Archiv. Ein nie uebernommener Eintrag wird
ohne Rueckfrage zurueckgeholt, ein Rueckfaeller mit - dabei faellt der
Verweis auf die geloeschte Opportunity weg. Fuer diese Rueckfrage kennt
`ConfirmDialog` jetzt `tone="neutral"`: ein roter Knopf an einer
harmlosen Stelle nimmt der Farbe ihre Bedeutung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-09-03 09:35:15 +02:00
co-authored by Claude Opus 5
parent ea886e4d1b
commit f319b8207b
10 changed files with 205 additions and 16 deletions
+47
View File
@@ -606,6 +606,53 @@ router.patch('/:id', async (req, res, next) => {
}
});
/*
* POST /api/acquisitions/:id/restore
*
* Zurueck in den Eingang. Eingang und Archiv trennt allein das Funddatum -
* deshalb setzt das Wiederherstellen es auf heute. Eine eigene Spalte
* "wiederhergestellt" waere ein zweiter Zustand, der zum Datum driften kann.
*
* Bei einem Rueckfaeller (uebernommen, Opportunity weg) wird zusaetzlich die
* Uebernahme geloest: sonst haelt `converted_at` den Eintrag im Archiv fest,
* egal welches Datum er traegt. Der Verweis auf die Opportunity geht dabei
* verloren - sie ist geloescht oder im Papierkorb, und der Eintrag ist wieder
* das Original.
*/
router.post('/:id/restore', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Akquise-ID.' });
try {
const [rows] = await pool.query('SELECT * FROM crm_acquisitions WHERE id = ? LIMIT 1', [id]);
const existing = rows[0];
if (!existing) return res.status(404).json({ error: 'Eintrag nicht gefunden.' });
if (!mayModify(req.user, existing)) {
return res.status(403).json({
error:
'Diesen Eintrag darf nur wiederherstellen, wer ihn angelegt hat, oder ein Administrator.',
});
}
// Lebt die Opportunity noch, gehoert der Eintrag zu ihr und steht ohnehin
// in keiner der beiden Listen - dann gibt es nichts wiederherzustellen.
if (existing.converted_at !== null && !(await opportunityGone(existing.opportunity_id))) {
return res
.status(409)
.json({ error: 'Der Eintrag gehört zu einer bestehenden Opportunity.' });
}
await pool.query(
`UPDATE crm_acquisitions
SET found_at = CURDATE(), converted_at = NULL, opportunity_id = NULL
WHERE id = ?`,
[id]
);
res.json(await fetchAcquisition(id));
} catch (err) {
next(err);
}
});
// DELETE /api/acquisitions/:id
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);