feat(opportunities): Link zur Ausschreibung als eigenes Feld
Die Uebernahme aus der Akquise schrieb den Link bisher nur in die Notizen. Jetzt hat die Opportunity eine Spalte `link`, gefuellt bei Uebernahme und Kopie; im Dialog steht sie rechts neben der Phase mit Knopf "Oeffnen". schema.sql traegt den Link fuer aeltere Uebernahmen nach. Die Pruefung liegt in server/src/link.js und gilt fuer Akquise und Opportunities gleich. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3e76a73f2e
commit
8cb5cbc7cc
@@ -68,6 +68,7 @@ erDiagram
|
||||
varchar title
|
||||
varchar consultant "fuer welchen Berater"
|
||||
int acquisition_id "Herkunft in der Akquise, ohne FK"
|
||||
varchar link "Ausschreibung, http(s)"
|
||||
enum stage "Neu .. Beauftragt | Absage"
|
||||
tinyint probability "0-100"
|
||||
decimal daily_rate
|
||||
@@ -214,6 +215,12 @@ Acht Eigenheiten, die man kennen muss:
|
||||
den Papierkorb und leert beim Eintrag `converted_at` und `opportunity_id`;
|
||||
`found_at` bleibt. Wird eine davon wiederhergestellt, setzt `/restore` den
|
||||
Verweis wieder, sofern der Eintrag frei ist.
|
||||
- **Der Link zur Ausschreibung** steht seit 23.09.2026 auch in
|
||||
`crm_opportunities.link` (VARCHAR 1024). Die Übernahme kopiert ihn aus
|
||||
`crm_acquisitions.link`, `/copy` von der Quelle. Beide Seiten prüfen mit
|
||||
derselben Regel (`server/src/link.js`: nur http/https, ohne Schema wird
|
||||
`https://` ergänzt). `schema.sql` trägt den Link für ältere Übernahmen über
|
||||
`acquisition_id` nach - nur in leere Felder.
|
||||
- **Der Papierkorb ist die Ausnahme davon.** Löschen von Hand ist eine
|
||||
Entscheidung und kein Ablauf, deshalb steht sie in `deleted_at` (plus
|
||||
`deleted_by`, ohne Fremdschlüssel - siehe `schema.sql`). Vorher war das
|
||||
|
||||
@@ -377,7 +377,7 @@ Der Knopf **Opportunity** legt sie sofort an: Phase *Erstgespräch Berater*, 10
|
||||
zuständig ist der angemeldete Benutzer. Der Eintrag bleibt danach in der Liste
|
||||
stehen; an der Stelle des Knopfes steht **Übernommen ✓** in Blau, und ein Klick
|
||||
darauf öffnet die Opportunity. Mit übernommen werden Titel, Endkunde,
|
||||
Beschreibung, Tagessatz, Laufzeit, Projektpreis, Zeitraum und nächster Schritt.
|
||||
Beschreibung, Link, Tagessatz, Laufzeit, Projektpreis, Zeitraum und nächster Schritt.
|
||||
Der **Berater** wandert in das gleichnamige Feld der Opportunity und steht damit
|
||||
in Pipeline, Liste und Kacheln - für wen ein Projekt läuft, ist neben dem Titel
|
||||
die wichtigste Angabe. Sind **mehrere Berater** hinterlegt, entsteht **je Berater
|
||||
@@ -385,7 +385,7 @@ eine Opportunity** mit denselben Projektdaten, jede mit ihrem eigenen Namen im
|
||||
Feld. In den Notizen steht zusätzlich, mit wem er sich das Projekt teilt
|
||||
(*Auch beworben*).
|
||||
|
||||
Anbieter, Von, Kanal und Link haben in der Opportunity kein Feld - sie landen als
|
||||
Anbieter, Von und Kanal haben in der Opportunity kein Feld - sie landen als
|
||||
Herkunftsvermerk in den internen Notizen, damit nachvollziehbar bleibt, woher das
|
||||
Projekt kam.
|
||||
|
||||
@@ -490,6 +490,13 @@ der Akquise zu dreien, entstehen bei der Übernahme drei Opportunities. Steht in
|
||||
einer alten Opportunity ein Name, den die Liste nicht mehr kennt, bleibt er
|
||||
wählbar und ist als *(nicht in der Liste)* gekennzeichnet.
|
||||
|
||||
Rechts neben der **Phase** steht der **Link** zur Ausschreibung. Bei der
|
||||
Übernahme aus der Akquise kommt er mit, sonst lässt er sich eintragen -
|
||||
`hays.de/projekt/1` genügt, das `https://` wird ergänzt. Der Knopf **Öffnen**
|
||||
daneben zeigt die Ausschreibung in einem neuen Tab. Opportunities, die vor dem
|
||||
23.09.2026 übernommen wurden, haben den Link beim ersten Start danach aus ihrem
|
||||
Akquise-Eintrag bekommen, sofern der noch existiert.
|
||||
|
||||
Zu jeder Opportunity gehören beliebig viele **Ansprechpartner** aus den
|
||||
Kontakten, jeder mit eigener Rolle („Entscheider", „Fachbereich", …). Fehlt
|
||||
jemand noch im CRM, öffnet **Neuer Kontakt...** direkt neben der Auswahlliste
|
||||
|
||||
@@ -102,6 +102,7 @@ Alle unter `router.use(requireAuth)`.
|
||||
| `opportunities.js` | `GET /?trash=true`, `POST /`, `PATCH /:id`, `DELETE /:id` (Papierkorb), `POST /:id/restore`, `POST /:id/acquisition`, `POST /:id/copy`, `POST /trash/empty` | ändern, löschen und wiederherstellen nur Ersteller, Zuständiger oder Admin; Leeren jeder Angemeldete. `/:id/acquisition` nimmt eine Übernahme zurück: alle lebenden Opportunities mit derselben `acquisition_id` in den Papierkorb, beim Eintrag `converted_at`/`opportunity_id` leeren (Funddatum bleibt), in einer Transaktion. `/restore` hängt den Eintrag wieder an die Opportunity, wenn er frei ist. `/:id/copy` (`{ consultants, stage }`) legt je Berater eine Kopie an - Projektdaten, Ansprechpartner, Zuständiger und `acquisition_id` kommen mit, Interaktion und nächster Schritt nicht; jeder Angemeldete |
|
||||
| `acquisitions.js` | `GET /?view=eingang\|archiv\|papierkorb\|alle`, `POST /`, `PATCH /:id`, `DELETE /:id` (Papierkorb), `DELETE /:id/permanent`, `POST /trash/empty`, `POST /:id/opportunity`, `POST /:id/reopen`, `POST /:id/restore`; Auswahllisten: `GET/POST /options`, `PATCH/DELETE /options/:id` | Einträge ändern, löschen und zurückholen nur Ersteller oder Admin; Auswahllisten und Leeren jeder Angemeldete. Zwei verschiedene Rückwege, nicht zu verwechseln: `/reopen` holt aus dem **Archiv** in den Eingang (setzt `found_at` auf heute und leert `converted_at`/`opportunity_id` - sonst hält die Übernahme den Eintrag im Archiv fest), `/restore` holt aus dem **Papierkorb** zurück (leert nur `deleted_at`/`deleted_by`, das Funddatum bleibt). Beide antworten 409, wenn der Eintrag im falschen Zustand ist |
|
||||
| `badges.js` | `GET /` | Zahlen für die Seitenleiste: Akquise-Archiv und dessen Papierkorb, Papierkorb der Opportunities und der Mails, verworfene Scanner-Treffer. Der Scanner-Zähler ist `null`, wenn dessen Datenbank fehlt. Für Administratoren zusätzlich `ms_secret`, sobald das Client-Secret in weniger als 30 Tagen abläuft |
|
||||
| `link.js` (kein Router) | `normalizeLink()` für den Link zur Ausschreibung: nur http/https, ohne Schema wird `https://` ergänzt, max. 1024 Zeichen. Geteilt von Akquise und Opportunities, damit ein übernommener Link nicht an einer anderen Prüfung scheitert |
|
||||
| `acquisitionRules.js` (kein Router) | Fristen und die SQL-Bedingungen `IN_INBOX`, `IN_ARCHIVE`, `RETURNED`, `IS_OPEN` (nur noch für den Löschlauf), `NOT_DUE`, `OPPORTUNITY_GONE`, `NOT_DELETED`, `IN_TRASH`. Geteilt von Liste, Zählern, Aufräumlauf und Menüzahl - zwei Kopien derselben Regel sind einmal auseinandergelaufen. Dazu `splitConsultants` / `joinConsultants` / `normalizeConsultants` für das Feld *Berater*, das mehrere Namen kommagetrennt hält (Gegenstück im Frontend: `src/types.ts`) |
|
||||
| `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 |
|
||||
| `mail.js` | `GET /status`, `/messages`, `/messages/:id`, `/candidates`; `POST /messages/:id/links`, `/messages/:id/restore`, `/candidates/:id/adopt`, `/candidates/:id/reject`, `/trash/empty`; `DELETE /links/:id`, `/messages/:id`; `requireAdmin`: `GET/POST /settings`, `POST /test`, `POST /sync` | lesen, zuordnen und löschen jeder; einrichten Admin |
|
||||
|
||||
Reference in New Issue
Block a user