diff --git a/docs/02-datenmodell.md b/docs/02-datenmodell.md index b7cf93a..b369c42 100644 --- a/docs/02-datenmodell.md +++ b/docs/02-datenmodell.md @@ -168,6 +168,12 @@ Acht Eigenheiten, die man kennen muss: Zustand der Opportunity abgeleitet (Papierkorb oder weg), nicht gespeichert: wird sie zurückgeholt, verschwindet der Eintrag von selbst wieder. Die Regeln dafür stehen in `server/src/acquisitionRules.js`. + Aus derselben Logik folgt, was **Wiederherstellen** (`POST /:id/restore`) tut: + Es setzt `found_at` auf heute und leert `converted_at` samt `opportunity_id` - + ohne das Zurücksetzen von `converted_at` bliebe ein Rückfaller im Archiv + stehen, gleich welches Datum er trägt. Ein Kennzeichen + „wiederhergestellt" gibt es nicht; es wäre der Zustand, den diese Tabelle + gerade nicht führt. ### Postfach crm@perinco.de diff --git a/docs/05-frontend.md b/docs/05-frontend.md index 453f155..cf0532b 100644 --- a/docs/05-frontend.md +++ b/docs/05-frontend.md @@ -13,6 +13,7 @@ flowchart TD K --> AQ["Akquise"] AQ --> AQ1["Eingang
Funde der letzten 14 Tage"] AQ --> AQ2["Archiv
älter; nach 90 Tagen gelöscht"] + AQ2 -. "Wiederherstellen
(Funddatum = heute)" .-> AQ1 K --> O["Opportunities"] K --> C["Interviews"] K --> L["Kontakte"] diff --git a/docs/07-anleitung-benutzer.md b/docs/07-anleitung-benutzer.md index d985b3b..bfd55c4 100644 --- a/docs/07-anleitung-benutzer.md +++ b/docs/07-anleitung-benutzer.md @@ -183,7 +183,7 @@ wird geleert - für das nächste Projekt aus derselben Quelle. | **Projekttitel** | mit Link anklickbar (neuer Tab); darunter klein Endkunde, Tagessatz bzw. Festpreis und Personentage | | **Berater** | für wen | | **Opportunity** | ein Klick macht daraus eine Opportunity in der Phase *Erstgespräch Berater* | -| Stift / Papierkorb | bearbeiten (füllt das Formular oben) und löschen | +| Pfeil / Stift / Papierkorb | zurück in die Akquise (nur im Archiv), bearbeiten (füllt das Formular oben) und löschen | Die Spaltenköpfe sortieren, die Suche geht über Titel, Anbieter, Berater, Endkunde, Beschreibung und Notizen. @@ -191,7 +191,8 @@ Endkunde, Beschreibung und Notizen. ### Was von selbst passiert * Nach **14 Tagen** rutscht ein Eintrag ins **Archiv**. Dort lässt er sich weiter - bearbeiten und jederzeit übernehmen. + bearbeiten und jederzeit übernehmen - der Stift klappt das Formular auch im + Archiv auf. * Nach **90 Tagen** wird er **gelöscht**. Wer ein Projekt behalten will, übernimmt es als Opportunity. * Wird ein Eintrag **übernommen**, verschwindet er aus Eingang und Archiv - er @@ -207,6 +208,27 @@ Endkunde, Beschreibung und Notizen. Verschoben wird dabei nichts: Eingang und Archiv sind dieselbe Liste, nur nach dem Funddatum geteilt. Es gibt also keinen Zustand, der klemmen kann. +### Zurück in die Akquise + +Der **Pfeil** in der Archivliste holt einen Eintrag zurück in den Eingang - für +ein Projekt, das doch noch aktuell ist, oder für eine Bewerbung, die nach dem +Löschen ihrer Opportunity von vorn beginnt. Der Eintrag bekommt dabei das +**heutige Datum**; damit zählen die 14 Tage bis zum Archiv und die 90 Tage bis +zum Löschen wieder von vorn. + +> **Zur Statistik:** Das neue Funddatum verschiebt die Bewerbung auch im +> Dashboard in den heutigen Zeitraum - sie ist dort keine Bewerbung von damals +> mehr. + +Bei einem **Rückfaller** (die Bewerbung war schon eine Opportunity) fragt das CRM +nach: mit dem Wiederherstellen fällt der Verweis auf die gelöschte Opportunity +weg. Der Eintrag ist danach wieder ein gewöhnlicher Fund - liegt die Opportunity +noch im Papierkorb und wird sie zurückgeholt, steht sie ab dann *neben* dem +Eintrag statt an seiner Stelle. + +Ein Eintrag, dessen Opportunity noch lebt, lässt sich nicht wiederherstellen - +er steht ohnehin in keiner der beiden Listen. + ### Aus dem Eintrag wird eine Opportunity Der Knopf **Opportunity** legt sie sofort an: Phase *Erstgespräch Berater*, 10 % Wahrscheinlichkeit, diff --git a/docs/08-anleitung-entwickler.md b/docs/08-anleitung-entwickler.md index 9ab9a7e..292a1e2 100644 --- a/docs/08-anleitung-entwickler.md +++ b/docs/08-anleitung-entwickler.md @@ -219,8 +219,9 @@ Browser-Download. * **Gemeinsame Klassen** statt kopierter Utility-Ketten: `inputClass`, `labelClass` aus `components/leads/leadUi.tsx`, dazu `cardClass`, `primaryButtonClass` aus `components/scanner/scannerUi.tsx`. -* **Meldungen als Toast**, Löschabfragen über `ConfirmDialog` - nie `alert()` - oder `confirm()`. +* **Meldungen als Toast**, Rückfragen über `ConfirmDialog` - nie `alert()` + oder `confirm()`. Rot (`tone="danger"`, Standard) nur, wenn etwas verloren + geht; sonst `tone="neutral"`, damit die Farbe ihre Bedeutung behält. * **Rechte immer doppelt**: Oberfläche blendet aus, Backend antwortet mit 403. ## Deployment diff --git a/docs/09-quellcode.md b/docs/09-quellcode.md index 6130082..a8c4035 100644 --- a/docs/09-quellcode.md +++ b/docs/09-quellcode.md @@ -92,7 +92,7 @@ Alle unter `router.use(requireAuth)`. | `connections.js` | `GET /`, `POST /`, `PATCH /:id`, `DELETE /:id` | jeder Angemeldete | | `events.js` | `GET /`, `POST /`, `PATCH /:id`, `DELETE /:id` | anlegen jeder; ändern/löschen nur Ersteller oder Admin | | `opportunities.js` | `GET /?trash=true`, `POST /`, `PATCH /:id`, `DELETE /:id` (Papierkorb), `POST /:id/restore`, `POST /trash/empty` | ändern, löschen und wiederherstellen nur Ersteller, Zuständiger oder Admin; Leeren jeder Angemeldete | -| `acquisitions.js` | `GET /?view=eingang\|archiv\|alle`, `POST /`, `PATCH /:id`, `DELETE /:id`, `POST /:id/opportunity`; Auswahllisten: `GET/POST /options`, `PATCH/DELETE /options/:id` | Einträge ändern/löschen nur Ersteller oder Admin; Auswahllisten jeder Angemeldete | +| `acquisitions.js` | `GET /?view=eingang\|archiv\|alle`, `POST /`, `PATCH /:id`, `DELETE /:id`, `POST /:id/opportunity`, `POST /:id/restore`; Auswahllisten: `GET/POST /options`, `PATCH/DELETE /options/:id` | Einträge ändern/löschen/wiederherstellen nur Ersteller oder Admin; Auswahllisten jeder Angemeldete. `/restore` setzt `found_at` auf heute und leert `converted_at`/`opportunity_id` - sonst hält die Übernahme den Eintrag im Archiv fest; bei lebender Opportunity antwortet es 409 | | `badges.js` | `GET /` | Zahlen für die Seitenleiste: Akquise-Archiv, beide Papierkörbe, verworfene Scanner-Treffer. Der Scanner-Zähler ist `null`, wenn dessen Datenbank fehlt | | `acquisitionRules.js` (kein Router) | Fristen und die SQL-Bedingungen `IS_OPEN`, `IN_ARCHIVE`, `NOT_DUE`, `OPPORTUNITY_GONE`. Geteilt von Liste, Zählern, Aufräumlauf und Menüzahl - zwei Kopien derselben Regel sind einmal auseinandergelaufen | | `dashboard.js` | `GET /?from=&to=&bucket=tag\|monat\|quartal\|jahr` | drei Reihen über der Zeit plus Bewerbungen je Berater. Liest Akquise **und** `crm_acquisition_history`; Termine und Phasenlisten stehen bewusst nicht drin - die lädt die App ohnehin | @@ -272,9 +272,9 @@ Warum genau diese Konstanten festgelegt sind, steht in | `components/opportunities/opportunityUi.tsx` | `STAGE_LABELS` (Anzeige, mit Umlaut), `STAGE_HINTS` (Erläuterung als Titelhinweis), `STAGE_STYLES`, `STAGE_COLORS`, `StageBadge` | | `components/dashboard/Dashboard.tsx` | Startansicht: Zeitraumwahl, vier Kennzahlen, Verlauf, Berater, Termine, „Beim Kunden". `buildAxis()` erzeugt auch leere Abschnitte | | `components/dashboard/dashboardUi.tsx` | `useCountUp()` (Zahl läuft hoch, respektiert `prefers-reduced-motion`), `StatCard`, `SeriesChart`, `BarList`, `Panel` - von Hand statt Diagrammbibliothek | -| `components/acquisition/Acquisitions.tsx` | Akquise und Archiv (`view`); Suche, Sortierung, Übernahme, Löschen | +| `components/acquisition/Acquisitions.tsx` | Akquise und Archiv (`view`); Suche, Sortierung, Übernahme, Löschen, Wiederherstellen. Das Formular steht im Archiv nur beim Bearbeiten - ohne diesen Zweig blieb der Stift dort ohne Wirkung | | `components/acquisition/AcquisitionForm.tsx` | Eingabe über der Liste; behält Datum, Anbieter, Kanal und Berater für den nächsten Eintrag | -| `components/acquisition/AcquisitionTable.tsx` | die Liste; `safeLink()` lässt nur http(s) zum Link werden | +| `components/acquisition/AcquisitionTable.tsx` | die Liste; `safeLink()` lässt nur http(s) zum Link werden. Der Pfeil zum Wiederherstellen erscheint nur, wenn `onRestore` gesetzt ist - im Eingang gibt es nichts wiederherzustellen | | `components/acquisition/acquisitionUi.tsx` | `fieldClass` (abgesetzte Felder) und `OptionSelect` - zeigt einen Wert, der nicht mehr in der Liste steht, als solchen; `alwaysOffer` hält den eigenen Namen für *Von* immer wählbar. *Anbieter* nutzt es nicht: Freitext mit `` | | `components/settings/AcquisitionSettings.tsx` | Reiter *Einstellungen → Akquise*: die vier Auswahllisten pflegen | | `components/leads/leadUi.tsx` | `inputClass`, `labelClass`, `KindBadge`, `Avatar`, Farben der Kontaktart | @@ -285,7 +285,7 @@ Warum genau diese Konstanten festgelegt sind, steht in | `components/scanner/scannerUi.tsx` | `cardClass`, Buttonklassen, `MatchBadge`, `MatchStatusBadge`, `PortalStatusPill`, `formatDateTime`, `projectTimestamp` | | `components/mail/mailUi.tsx` | `Badge`, `ConfidenceBar`, Herkunfts- und Statusbeschriftungen, `formatAddresses`, `avatarColor` | | `components/mail/MailBody.tsx` | Mailinhalt im `