Files
perinco-CRM/server/src/routes/badges.js
T
Sascha WolfandClaude Opus 5 6e66bd39d7 feat(akquise): Papierkorb fuer geloeschte Eintraege
Loeschen in der Akquise war ein echtes DELETE: der Eintrag stand danach
in keiner Liste, auch nicht im Archiv, und war nicht zurueckholbar. Jetzt
setzt es `deleted_at`/`deleted_by`, die Zeile bleibt 90 Tage stehen.

Der Papierkorb ist die eine Ausnahme von der Regel, dass diese Tabelle
keinen Zustand fuehrt: Loeschen von Hand ist eine Entscheidung, kein
Ablauf. Was dort liegt, blenden alle anderen Abfragen ueber NOT_DELETED
aus - Eingang, Archiv, Menuezahlen und Dashboard. Der Aufraeumlauf ueber
`found_at` laesst geloeschte Zeilen ausdruecklich liegen, sonst waere ein
alter Fund im Papierkorb sofort weg; der Papierkorb raeumt ab
`deleted_at`.

Zwei Rueckwege, deshalb zwei Routen. `/reopen` (bisher `/restore`) holt
aus dem Archiv in den Eingang und setzt das Funddatum auf heute.
`/restore` holt aus dem Papierkorb zurueck und laesst das Funddatum in
Ruhe - der Eintrag soll dort auftauchen, wo er hingehoert, also im
Archiv, wenn er alt ist. Die Meldung nennt das Ziel.

Dazu `DELETE /:id/permanent` und `POST /trash/empty`. Im Papierkorb wird
nicht gepflegt: Bearbeiten und Uebernehmen antworten 409.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 10:17:21 +02:00

70 lines
2.5 KiB
JavaScript

import express from 'express';
import { pool } from '../db.js';
import { requireAuth } from '../middleware/auth.js';
import { Match } from '../scanner/models/index.js';
import {
ARCHIVE_AFTER_DAYS,
DELETE_AFTER_DAYS,
IN_ARCHIVE,
IN_TRASH,
IS_OPEN,
NOT_DELETED,
NOT_DUE,
} from '../acquisitionRules.js';
const router = express.Router();
router.use(requireAuth);
/*
* Zaehler fuer die Seitenleiste: Akquise-Archiv und deren Papierkorb, Papierkorb
* der Opportunities, Papierkorb der Mails und die unpassenden Scanner-Treffer.
*
* Ein Endpunkt statt vier: die Zahlen stehen alle im Menue und sollen zusammen
* kommen. Die Ansichten selbst laden weiter ihre eigenen Daten - hier gibt es
* ausschliesslich Zahlen, keine Inhalte.
*
* Der Scanner liegt in einer eigenen Datenbank und kann fehlen; sein Zaehler ist
* dann null und die Leiste zeigt an der Stelle einfach keine Zahl. Ein Menue
* ohne Zahl ist besser als ein Menue mit Fehlermeldung.
*/
// GET /api/badges
router.get('/', async (req, res, next) => {
try {
const [[counts]] = await pool.query(
`SELECT
/* Dieselbe Regel wie die Liste - sie steht in acquisitionRules.js,
damit Zahl und Liste nicht auseinanderlaufen. */
(SELECT COUNT(*) FROM crm_acquisitions a
LEFT JOIN crm_opportunities opp ON opp.id = a.opportunity_id
WHERE ${IS_OPEN} AND ${NOT_DELETED} AND ${IN_ARCHIVE}
AND ${NOT_DUE}) AS acquisition_archive,
(SELECT COUNT(*) FROM crm_acquisitions a WHERE ${IN_TRASH}) AS acquisition_trash,
(SELECT COUNT(*) FROM crm_opportunities
WHERE deleted_at IS NOT NULL) AS opportunity_trash,
(SELECT COUNT(*) FROM crm_mail_messages
WHERE crm_deleted_at IS NOT NULL AND crm_purged = 0) AS mail_trash`,
[ARCHIVE_AFTER_DAYS, DELETE_AFTER_DAYS]
);
let scannerRejected = null;
try {
scannerRejected = await Match.count({ where: { status: 'rejected' } });
} catch {
// Scanner-Datenbank nicht erreichbar - dann eben keine Zahl.
}
res.json({
acquisition_archive: Number(counts.acquisition_archive),
acquisition_trash: Number(counts.acquisition_trash),
opportunity_trash: Number(counts.opportunity_trash),
mail_trash: Number(counts.mail_trash),
scanner_rejected: scannerRejected,
});
} catch (err) {
next(err);
}
});
export default router;