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>
70 lines
2.5 KiB
JavaScript
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;
|