fix(akquise): Zahl am Menuepunkt Archiv folgte der alten Regel
Die Liste zeigte die zurueckgefallene Bewerbung, der Zaehler am Menue nicht: er filterte weiter auf "nie uebernommen". Genau der Fehler, den zwei Kopien derselben Regel machen. Fristen und Bedingungen stehen jetzt in server/src/acquisitionRules.js und werden von der Liste, den Zaehlern, dem Aufraeumlauf und der Menuezahl gemeinsam benutzt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c380d05b42
commit
c59115c728
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Regeln der Akquise an einer Stelle: Fristen und die Frage, was ueberhaupt in
|
||||
* Eingang oder Archiv gehoert.
|
||||
*
|
||||
* Sie werden von routes/acquisitions.js (Listen, Zaehler, Aufraeumlauf) und von
|
||||
* routes/badges.js (Zahl am Menuepunkt) gebraucht. Lagen sie in beiden Dateien,
|
||||
* wuerde eine Aenderung die andere Stelle vergessen - genau das ist beim
|
||||
* Zurueckfallen einer Bewerbung passiert: die Liste zeigte den Eintrag, der
|
||||
* Zaehler am Menue nicht.
|
||||
*
|
||||
* Die Zeichenketten sind SQL-Bedingungen und erwarten die Aliase `a` fuer
|
||||
* crm_acquisitions und `opp` fuer die per LEFT JOIN angehaengte Opportunity.
|
||||
*/
|
||||
|
||||
/** Grenze zwischen Eingang und Archiv, in Tagen. */
|
||||
export const ARCHIVE_AFTER_DAYS = 14;
|
||||
|
||||
/** Ab hier loescht das Archiv selbst. */
|
||||
export const DELETE_AFTER_DAYS = 90;
|
||||
|
||||
/**
|
||||
* Die Opportunity zu einem Eintrag ist verschwunden - im Papierkorb oder
|
||||
* endgueltig geloescht. `opp.id IS NULL` deckt beides ab: nie uebernommen
|
||||
* (opportunity_id ist NULL) und endgueltig geloescht (der Fremdschluessel wurde
|
||||
* dabei auf NULL gesetzt).
|
||||
*/
|
||||
export const OPPORTUNITY_GONE = '(opp.id IS NULL OR opp.deleted_at IS NOT NULL)';
|
||||
|
||||
/**
|
||||
* Bewerbung, die (wieder) in Eingang oder Archiv gehoert: nie uebernommen, oder
|
||||
* die Opportunity ist weg. Abgeleitet statt gespeichert - wird die Opportunity
|
||||
* zurueckgeholt, verschwindet der Eintrag von selbst wieder.
|
||||
*/
|
||||
export const IS_OPEN = `(a.converted_at IS NULL OR ${OPPORTUNITY_GONE})`;
|
||||
|
||||
/**
|
||||
* Steht im Archiv: alles Uebernommene, dessen Opportunity weg ist (unabhaengig
|
||||
* vom Alter - es ist abgearbeitet, aber nicht verschwunden), plus alles, was
|
||||
* aelter als die Archivfrist ist. Erwartet die Frist als Platzhalter.
|
||||
*/
|
||||
export const IN_ARCHIVE = '(a.converted_at IS NOT NULL OR a.found_at < DATE_SUB(CURDATE(), INTERVAL ? DAY))';
|
||||
|
||||
/** Untere Grenze: was schon faellig ist, wird nicht mehr gezeigt. */
|
||||
export const NOT_DUE = 'a.found_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)';
|
||||
@@ -1,6 +1,14 @@
|
||||
import express from 'express';
|
||||
import { pool } from '../db.js';
|
||||
import { requireAuth } from '../middleware/auth.js';
|
||||
import {
|
||||
ARCHIVE_AFTER_DAYS,
|
||||
DELETE_AFTER_DAYS,
|
||||
IN_ARCHIVE,
|
||||
IS_OPEN,
|
||||
NOT_DUE,
|
||||
OPPORTUNITY_GONE,
|
||||
} from '../acquisitionRules.js';
|
||||
|
||||
const router = express.Router();
|
||||
router.use(requireAuth);
|
||||
@@ -19,31 +27,6 @@ router.use(requireAuth);
|
||||
* woher eine Opportunity kommt. Nur der Eingang und das Archiv zeigen sie nicht.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Ist die Opportunity zu einem Eintrag verschwunden - im Papierkorb oder
|
||||
* endgueltig geloescht -, faellt die Bewerbung ins Archiv zurueck. Sonst wuerde
|
||||
* sie einfach nicht mehr existieren: aus der Akquise ist sie mit der Uebernahme
|
||||
* heraus, und die Opportunity gibt es auch nicht mehr.
|
||||
*
|
||||
* Abgeleitet statt gespeichert: `converted_at` bleibt stehen. Wird die
|
||||
* Opportunity aus dem Papierkorb zurueckgeholt, verschwindet der Eintrag von
|
||||
* selbst wieder aus dem Archiv - es gibt keinen Zustand, der nachgezogen werden
|
||||
* muesste.
|
||||
*
|
||||
* `opp.id IS NULL` deckt zwei Faelle ab: nie uebernommen (opportunity_id ist
|
||||
* NULL) und endgueltig geloescht (der Fremdschluessel wurde dabei auf NULL
|
||||
* gesetzt). Zusammen mit converted_at ergibt das den Unterschied.
|
||||
*/
|
||||
const OPPORTUNITY_GONE = '(opp.id IS NULL OR opp.deleted_at IS NOT NULL)';
|
||||
|
||||
/** Bewerbung, die (wieder) in Eingang oder Archiv gehoert. */
|
||||
const IS_OPEN = `(a.converted_at IS NULL OR ${OPPORTUNITY_GONE})`;
|
||||
|
||||
/** Grenze zwischen Eingang und Archiv, in Tagen. */
|
||||
const ARCHIVE_AFTER_DAYS = 14;
|
||||
/** Ab hier loescht das Archiv selbst. */
|
||||
const DELETE_AFTER_DAYS = 90;
|
||||
|
||||
/*
|
||||
* Felder, deren Inhalte als Auswahlliste in den Einstellungen gepflegt werden.
|
||||
* Die Werte selbst stehen in crm_acquisition_options, nicht hier - deshalb
|
||||
@@ -501,11 +484,8 @@ router.get('/', async (req, res, next) => {
|
||||
} else if (view === 'archiv') {
|
||||
// Die untere Grenze steht bewusst auch hier: der Loeschlauf raeumt nur
|
||||
// stuendlich, angezeigt werden soll nichts, was schon faellig ist.
|
||||
where.push(
|
||||
`(a.converted_at IS NOT NULL
|
||||
OR a.found_at < DATE_SUB(CURDATE(), INTERVAL ? DAY))`
|
||||
);
|
||||
where.push('a.found_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)');
|
||||
where.push(IN_ARCHIVE);
|
||||
where.push(NOT_DUE);
|
||||
params.push(ARCHIVE_AFTER_DAYS, DELETE_AFTER_DAYS);
|
||||
} else if (view !== 'alle') {
|
||||
return res.status(400).json({ error: 'Unbekannte Ansicht.' });
|
||||
@@ -521,14 +501,12 @@ router.get('/', async (req, res, next) => {
|
||||
// muesste sonst zwei Listen laden, um eine Zahl zu kennen.
|
||||
const [[counts]] = await pool.query(
|
||||
`SELECT
|
||||
SUM(a.converted_at IS NULL
|
||||
AND a.found_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)) AS eingang,
|
||||
SUM((a.converted_at IS NOT NULL
|
||||
OR a.found_at < DATE_SUB(CURDATE(), INTERVAL ? DAY))
|
||||
AND a.found_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)) AS archiv
|
||||
SUM(a.converted_at IS NULL AND ${NOT_DUE}) AS eingang,
|
||||
SUM(${IN_ARCHIVE} AND ${NOT_DUE}) AS archiv
|
||||
FROM crm_acquisitions a
|
||||
LEFT JOIN crm_opportunities opp ON opp.id = a.opportunity_id
|
||||
WHERE ${IS_OPEN}`,
|
||||
// Reihenfolge der Platzhalter: Eingangsfrist, Archivfrist, Loeschfrist.
|
||||
[ARCHIVE_AFTER_DAYS, ARCHIVE_AFTER_DAYS, DELETE_AFTER_DAYS]
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,13 @@ 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,
|
||||
IS_OPEN,
|
||||
NOT_DUE,
|
||||
} from '../acquisitionRules.js';
|
||||
|
||||
const router = express.Router();
|
||||
router.use(requireAuth);
|
||||
@@ -19,24 +26,21 @@ router.use(requireAuth);
|
||||
* ohne Zahl ist besser als ein Menue mit Fehlermeldung.
|
||||
*/
|
||||
|
||||
/** Grenze zwischen Eingang und Archiv der Akquise - wie in routes/acquisitions.js. */
|
||||
const ACQUISITION_ARCHIVE_AFTER_DAYS = 14;
|
||||
const ACQUISITION_DELETE_AFTER_DAYS = 90;
|
||||
|
||||
// GET /api/badges
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
const [[counts]] = await pool.query(
|
||||
`SELECT
|
||||
(SELECT COUNT(*) FROM crm_acquisitions
|
||||
WHERE converted_at IS NULL
|
||||
AND found_at < DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
AND found_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)) AS acquisition_archive,
|
||||
/* 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 ${IN_ARCHIVE} AND ${NOT_DUE}) AS acquisition_archive,
|
||||
(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`,
|
||||
[ACQUISITION_ARCHIVE_AFTER_DAYS, ACQUISITION_DELETE_AFTER_DAYS]
|
||||
[ARCHIVE_AFTER_DAYS, DELETE_AFTER_DAYS]
|
||||
);
|
||||
|
||||
let scannerRejected = null;
|
||||
|
||||
Reference in New Issue
Block a user