From 70f6101761f56119e5831dacc32ffbc9f35454a2 Mon Sep 17 00:00:00 2001 From: Gerd Date: Sun, 27 Sep 2026 17:15:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(cli):=20init-db=20setzt=20CRM-Gesch=C3=A4f?= =?UTF-8?q?tsdaten=20zur=C3=BCck=20und=20legt=20neue=20Musterdaten=20an?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit init-db löscht vor dem Seeding sämtliche CRM-Geschäftsdaten (Kunden, Kontakte, Projekte, Kontaktadressen, Gesprächsnotizen, Projekt-Zuordnungen) in FK-sicherer Reihenfolge und legt danach die Musterdaten komplett neu an. Die Tag-Taxonomie bleibt als Stammdaten erhalten. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- App_Server/root/cli.py | 734 +++++++++++++++++++++++++++++++---------- 1 file changed, 568 insertions(+), 166 deletions(-) diff --git a/App_Server/root/cli.py b/App_Server/root/cli.py index 46b1675..85da35f 100644 --- a/App_Server/root/cli.py +++ b/App_Server/root/cli.py @@ -2,17 +2,23 @@ Flask CLI-Befehle zur Datenbank-Initialisierung und zum Reset. Befehle (aufgerufen via Flask CLI): - flask --app wsgi:app init-db -- Schema + Musterdaten anlegen + flask --app wsgi:app init-db -- Schema anlegen/migrieren + Musterdaten flask --app wsgi:app reset-db -- alles bis auf SysAdmin löschen -init-db ist idempotent: bereits vorhandene Rollen/Kunden werden nicht -doppelt angelegt. reset-db fragt standardmäßig zur Bestätigung; mit --yes -wird die Abfrage übersprungen. +init-db legt das Schema an bzw. migriert es und setzt danach die +CRM-Geschäftsdaten zurück: Sämtliche Kunden samt aller abhängigen Daten +(Kontakte, Projekte, Kontaktadressen, Gesprächsnotizen, Projekt-Zuordnungen) +werden gelöscht und die Musterdaten werden komplett neu angelegt. Die +Tag-Taxonomie (tags) bleibt erhalten, da es sich um Stammdaten handelt. +reset-db fragt standardmäßig zur Bestätigung; mit --yes wird die Abfrage +übersprungen. :author: Mistral Vibe :date: 2026-09-02 -:version: 1.0 +:version: 1.1 """ +from datetime import date, timedelta + import click from flask import Flask @@ -33,10 +39,10 @@ def register_cli_commands(app: Flask): click.echo("Initialisiere kbp_crm.db ...") from crm.model import checkdb as crm_checkdb crm_checkdb.checkupdates() - _seed_muster_kunden() - _seed_muster_projekte() - _seed_muster_kontaktadressen() - _seed_muster_calllogs() + click.echo("Lösche vorhandene CRM-Geschäftsdaten ...") + _purge_crm_daten() + click.echo("Lege Musterdaten an ...") + _seed_musterdaten() click.echo("Fertig.") @@ -83,181 +89,577 @@ def _seed_app_roles(): session.commit() -def _seed_muster_kunden(): +def _purge_crm_daten(): """ - Legt die drei Muster-Kunden an, falls die kunden-Tabelle noch leer ist. + Löscht sämtliche CRM-Geschäftsdaten vor dem Neuanlegen der Musterdaten. + + Die Löschreihenfolge beachtet die Fremdschlüsselbeziehungen: erst die + n:m-Verknüpfungen der Gesprächsnotizen, dann die Notizen selbst, danach + die Kontaktadressen, die Projekt-Nutzer-Zuordnungen, die Projekte, die + Kontakte und zuletzt die Kunden. Die Tag-Taxonomie (tags) bleibt + erhalten, da Tags Stammdaten und keine Musterdaten sind. """ - from crm.model import Kunde + from crm.model import ( + CallLogTag, CallLog, Kontaktadresse, ProjektUser, + Projekt, Kontakt, Kunde, + ) session = dbapp.session - if session.query(Kunde).count() > 0: - return - muster = [ - Kunde(name="Musterfirma GmbH", strasse="Musterstraße 1", - plz="12345", ort="Musterstadt", land="Deutschland"), - Kunde(name="Beispiel AG", strasse="Beispielweg 2", - plz="54321", ort="Beispielstadt", land="Deutschland"), - Kunde(name="Test Unternehmen", strasse="Testallee 3", - plz="67890", ort="Testdorf", land="Österreich"), - ] - for kunde in muster: - session.add(kunde) + session.query(CallLogTag).delete(synchronize_session=False) + session.query(CallLog).delete(synchronize_session=False) + session.query(Kontaktadresse).delete(synchronize_session=False) + session.query(ProjektUser).delete(synchronize_session=False) + session.query(Projekt).delete(synchronize_session=False) + session.query(Kontakt).delete(synchronize_session=False) + session.query(Kunde).delete(synchronize_session=False) session.commit() -def _seed_muster_projekte(): +# --------------------------------------------------------------------------- +# Musterdaten: drei Kunden, jeder mit eigenen Kontakten und je drei +# Telefon-Akquise-Projekten. Die Daten sind bewusst so aufgebaut, dass +# jeder Kontakte- und Kontaktadressen-Name genau einmal in der gesamten +# Datenbank vorkommt und kein Projektname bei zwei Kunden existiert. +# Die Kontaktadressen sind die Akquise-Ziele (angerufene Firmen), die +# Gesprächsnotizen werden daraus automatisch abgeleitet. +# --------------------------------------------------------------------------- +_MUSTERDATEN = [ + { + "name": "Nordwerk Metallbau GmbH & Co. KG", + "strasse": "Hamburger Str. 118", + "plz": "21079", + "ort": "Hamburg", + "land": "Deutschland", + "kontakte": [ + { + "anrede": "Herr", "name": "Dipl.-Ing. Markus Haverkamp", + "position": "Einkaufsleiter", + "telefon": "+49 40 790 214 66", + "email": "haverkamp@nordwerk-metallbau.de", + "notiz": "Entscheider für Rahmenverträge; telefonisch am besten nach 15 Uhr erreichbar.", + }, + { + "anrede": "Frau", "name": "Katrin Ohmsen", + "position": "Vertriebsleitung", + "telefon": "+49 40 790 214 12", + "email": "ohmsen@nordwerk-metallbau.de", + "notiz": "Steuert die laufenden Kampagnen mit uns; erwartet jeden Freitag ein kurzes Zwischen-Reporting.", + }, + ], + "projekte": [ + { + "projektname": "Akquise Stahlträgerprogramm 2026 – Dachstühle und Hallenbau", + "zweck": "Telefonische Neukundenansprache bei Bauunternehmen in Norddeutschland, um das überarbeitete Stahlträgerprogramm zu positionieren. Ziel: 400 geführte Gespräche bis Jahresende, davon 60 Angebotsanfragen.", + "verantwortlicher": "Miriam Falkner", + "startdatum": date(2026, 8, 3), + "kontaktadressen": [ + { + "anrede": "Herr", "name": "Torben Block", + "firma": "Block Bauunternehmung GmbH", + "position": "Einkaufsleiter", + "telefon": "+49 40 6899 2140", + "email": "t.block@block-bau-hamburg.de", + "strasse": "Wandsbeker Chaussee 210", "plz": "22089", + "ort": "Hamburg", "land": "Deutschland", + "notiz": "Tauscht die Rahmenverträge jährlich aus; entscheidet gemeinsam mit der Kalkulation.", + }, + { + "anrede": "Frau", "name": "Silke Reimers", + "firma": "Reimers Dachbaustoffe Nord", + "position": "Leiterin Kalkulation", + "telefon": "+49 431 556 872", + "email": "s.reimers@reimers-dachbaustoffe.de", + "strasse": "Kieler Str. 31", "plz": "24116", + "ort": "Kiel", "land": "Deutschland", + "notiz": "Plant mehrere Hallenprojekte für das kommende Jahr.", + }, + { + "anrede": "Herr", "name": "Gero Timmermann", + "firma": "Timmermann Hallenbau", + "position": "Geschäftsführer", + "telefon": "+49 511 987 4432", + "email": "g.timmermann@timmermann-hallenbau.de", + "strasse": "Am Lindener Hafen 12", "plz": "30453", + "ort": "Hannover", "land": "Deutschland", + "notiz": "Hat großes Interesse an Schnellmontage-Systemen signalisiert.", + }, + ], + }, + { + "projektname": "Neukundengewinnung Zulieferer Ostösterreich", + "zweck": "Kaltakquise bei Stahl- und Metallzulieferern in Wien, Niederösterreich und Oberösterreich zur Erschließung neuer Lieferbeziehungen. Ziel: 250 Kontakte, davon 40 qualifizierte Terminvorschläge für den Außendienst.", + "verantwortlicher": "Dominik Reiss", + "startdatum": date(2026, 8, 17), + "kontaktadressen": [ + { + "anrede": "Herr", "name": "Wolfgang Gruber", + "firma": "Gruber Stahlhandel", + "position": "Vertriebsleiter", + "telefon": "+43 1 982 76 11", + "email": "w.gruber@gruber-stahlhandel.at", + "strasse": "Laxenburger Str. 84", "plz": "1100", + "ort": "Wien", "land": "Österreich", + "notiz": "Bezieht Trägerprofile bislang aus Deutschland; offen für österreichische Bezugsquellen.", + }, + { + "anrede": "Frau", "name": "Martina Ehgartner", + "firma": "Ehgartner Metallwerke", + "position": "Einkaufsleitung", + "telefon": "+43 732 455 210", + "email": "m.ehgartner@ehgartner-metallwerke.at", + "strasse": "Wiener Str. 145", "plz": "4020", + "ort": "Linz", "land": "Österreich", + "notiz": "Kauft halbjährlich größere Kontingente; Verträge laufen über die Zentrale.", + }, + { + "anrede": "Herr", "name": "Kurt Strubreiter", + "firma": "Strubreiter Blechtechnik", + "position": "Inhaber", + "telefon": "+43 2628 741 33", + "email": "office@strubreiter-blechtechnik.at", + "strasse": "Betriebsstr. 7", "plz": "2700", + "ort": "Wiener Neustadt", "land": "Österreich", + "notiz": "Familienbetrieb; entscheidet kurzfristig, wenn die Konditionen stimmen.", + }, + ], + }, + { + "projektname": "Cross-Selling Schutzgitter und Tore im Bestand", + "zweck": "Nachverkauf im Bestandskundenstamm: Kunden der Jahre 2022 bis 2025 werden telefonisch auf das neue Sortiment an Schutzgittern und Industrietoren angesprochen. Ziel: 300 Bestandskunden kontaktiert, Angebotsversand innerhalb von 48 Stunden.", + "verantwortlicher": "Aylin Kocabiyik", + "startdatum": date(2026, 9, 1), + "kontaktadressen": [ + { + "anrede": "Frau", "name": "Petra Wilhelmi", + "firma": "Wilhelmi Logistikzentrum", + "position": "Technische Leiterin", + "telefon": "+49 421 338 7762", + "email": "p.wilhelmi@wilhelmi-logistik.de", + "strasse": "Universitätsallee 25", "plz": "28359", + "ort": "Bremen", "land": "Deutschland", + "notiz": "Sanierung der Tore für nächstes Jahr geplant; Budget muss noch beantragt werden.", + }, + { + "anrede": "Herr", "name": "Heino Sievers", + "firma": "Sievers Agrartechnik", + "position": "Kundendienstleiter", + "telefon": "+49 441 763 519", + "email": "h.sievers@sievers-agrartechnik.de", + "strasse": "Boschdamm 18", "plz": "26122", + "ort": "Oldenburg", "land": "Deutschland", + "notiz": "Sucht Ersatzteile für ältere Hallentore; auch Interesse an Wartungsverträgen.", + }, + { + "anrede": "Herr", "name": "Dietrich Voskamp", + "firma": "Voskamp Landbau", + "position": "Betriebsleiter", + "telefon": "+49 541 229 834", + "email": "d.voskamp@voskamp-landbau.de", + "strasse": "Natruper-Tor-Wall 70", "plz": "49074", + "ort": "Osnabrück", "land": "Deutschland", + "notiz": "Erweitert den Maschinenpark; über Neuanschaffungen entscheidet der Sohn mit.", + }, + ], + }, + ], + }, + { + "name": "Vitalis Apothekenverbund eG", + "strasse": "Kaisersstr. 77", + "plz": "76133", + "ort": "Karlsruhe", + "land": "Deutschland", + "kontakte": [ + { + "anrede": "Frau", "name": "Dr. Ingrid Halbach", + "position": "Mitglied der Geschäftsleitung", + "telefon": "+49 721 508 3390", + "email": "halbach@vitalis-apotheken.de", + "notiz": "Apothekerin; legt besonderen Wert auf seriös formulierte Gesprächsleitfäden.", + }, + { + "anrede": "Herr", "name": "Jörn Kiebitz", + "position": "Leiter Filialmarketing", + "telefon": "+49 721 508 3377", + "email": "kiebitz@vitalis-apotheken.de", + "notiz": "Liefert die Filial- und Zielgruppenlisten für unsere Kampagnen; Ansprechpartner für Abstimmungstermine.", + }, + ], + "projekte": [ + { + "projektname": "Terminierung Arzneimittelberatung – Vitalis Filialen Süd", + "zweck": "Telefonische Terminierung kostenpflichtiger Arzneimittelberatungen für Mitgliedsapotheken in Baden-Württemberg und Bayern. Ziel: 120 bestätigte Beratungstermine pro Monat bei maximal drei Absagen je Termin.", + "verantwortlicher": "Tobias Lenzen", + "startdatum": date(2026, 9, 7), + "kontaktadressen": [ + { + "anrede": "Frau", "name": "Dr. Marlene Vogt", + "firma": "Vogt-Apotheke Stuttgart-West", + "position": "Inhaberin", + "telefon": "+49 711 563 448", + "email": "m.vogt@vogt-apotheke-stuttgart.de", + "strasse": "Kernerstr. 31", "plz": "70182", + "ort": "Stuttgart", "land": "Deutschland", + "notiz": "Führt die Beratungen selbst durch; Terminvorschläge möglichst vormittags.", + }, + { + "anrede": "Herr", "name": "Ingo Pasternak", + "firma": "Pasternak Apotheke", + "position": "Filialleiter", + "telefon": "+49 89 448 2120", + "email": "i.pasternak@pasternak-apotheke.de", + "strasse": "Leopoldstr. 90", "plz": "80802", + "ort": "München", "land": "Deutschland", + "notiz": "Stellvertretung zu Servicezwecken; Zeitfenster dienstags und donnerstags.", + }, + { + "anrede": "Frau", "name": "Regine Axt", + "firma": "Axt Rathaus-Apotheke", + "position": "Approbierte Apothekerin", + "telefon": "+49 721 976 512", + "email": "r.axt@axt-apotheke.de", + "strasse": "Am Stadtgarten 2", "plz": "76133", + "ort": "Karlsruhe", "land": "Deutschland", + "notiz": "Neu in der Mitgliedschaft; wünscht ein Einführungsgespräch.", + }, + ], + }, + { + "projektname": "Rückgewinnung inaktiver Bonuskarten-Kunden", + "zweck": "Reaktivierung von rund 5.000 nicht mehr aktiven Bonuskarten-Kunden per Telefon mit anschließender Follow-up-Mail. Ziel: 8 Prozent Rückkehrquote im ersten Quartal, Nachverkauf über die Filialen.", + "verantwortlicher": "Camilla Brugger", + "startdatum": date(2026, 8, 24), + "kontaktadressen": [ + { + "anrede": "Herr", "name": "Falk Rüdiger", + "firma": "Sonnen-Apotheke Freiburg", + "position": "Inhaber", + "telefon": "+49 761 388 224", + "email": "f.ruediger@sonnen-apotheke-freiburg.de", + "strasse": "Salzstr. 60", "plz": "79098", + "ort": "Freiburg", "land": "Deutschland", + "notiz": "Wertet die Rückkehrquote monatlich aus; erwartet Zahlen zum Quartalsende.", + }, + { + "anrede": "Frau", "name": "Heidrun Clauß", + "firma": "Clauß Apotheke am Markt", + "position": "Inhaberin", + "telefon": "+49 351 469 887", + "email": "h.clauss@clauss-apotheke.de", + "strasse": "Wilsdruffer Str. 24", "plz": "01067", + "ort": "Dresden", "land": "Deutschland", + "notiz": "Ist mit der bisherigen Rücklaufquote unzufrieden und drängt auf eine neue Ansprache.", + }, + { + "anrede": "Herr", "name": "Marek Sawatzki", + "firma": "Sawatzki Apotheke Leipzig-Süd", + "position": "Fachapotheker", + "telefon": "+49 341 225 904", + "email": "m.sawatzki@sawatzki-apotheke.de", + "strasse": "Karl-Liebknecht-Str. 105", "plz": "04275", + "ort": "Leipzig", "land": "Deutschland", + "notiz": "Möchte besonders die junge Zielgruppe wieder ansprechen.", + }, + ], + }, + { + "projektname": "Up-Selling Homöopathie-Eigenmarke bei Mitgliedsapotheken", + "zweck": "Telefonische Empfehlung der Eigenmarke 'Vitalis Natur' an alle 180 Mitgliedsapotheken. Ziel: Listung in mindestens 60 Apotheken bis zum Ende des ersten Quartals, durchschnittliches Bestellvolumen von 1.200 Euro je Listung.", + "verantwortlicher": "Nils Gröber", + "startdatum": date(2026, 9, 14), + "kontaktadressen": [ + { + "anrede": "Frau", "name": "Brunhilde Kessler", + "firma": "Kessler Engelpforte-Apotheke", + "position": "Inhaberin", + "telefon": "+49 611 344 210", + "email": "b.kessler@kessler-apotheke.de", + "strasse": "Kirchgasse 32", "plz": "65183", + "ort": "Wiesbaden", "land": "Deutschland", + "notiz": "Listet bislang nur zwei Eigenmarken-Produkte; offen für eine Sortimentserweiterung.", + }, + { + "anrede": "Herr", "name": "Ali Demirci", + "firma": "Demirci Apotheke Köln", + "position": "Apotheker", + "telefon": "+49 221 559 731", + "email": "a.demirci@demirci-apotheke.de", + "strasse": "Neumarkt 3", "plz": "50667", + "ort": "Köln", "land": "Deutschland", + "notiz": "Fragt nach Verkaufsunterlagen für das Beratungsgespräch.", + }, + { + "anrede": "Frau", "name": "Yasmin Trautmann", + "firma": "Trautmann Apotheke", + "position": "Fachapothekerin", + "telefon": "+49 69 664 128", + "email": "y.trautmann@trautmann-apotheke.de", + "strasse": "Berger Str. 175", "plz": "60385", + "ort": "Frankfurt", "land": "Deutschland", + "notiz": "Möchte erst die Ergebnisse einer Testlistung abwarten.", + }, + ], + }, + ], + }, + { + "name": "Alpina Holzwerke Sepp Achmüller GmbH", + "strasse": "Sepp-Achmüller-Str. 4", + "plz": "6330", + "ort": "Kufstein", + "land": "Österreich", + "kontakte": [ + { + "anrede": "Herr", "name": "Sepp Achmüller jun.", + "position": "Geschäftsführer", + "telefon": "+43 5372 6234 18", + "email": "achmiller@alpina-holzwerke.at", + "notiz": "Prüft jede Kampagne vor dem Start persönlich ab; Budgetfreigabe erst nach Vorlage einer Musterabrechnung.", + }, + { + "anrede": "Frau", "name": "Elisabeth Steger", + "position": "Vertriebsleiterin", + "telefon": "+43 5372 6234 22", + "email": "steger@alpina-holzwerke.at", + "notiz": "Stimmt Zielgruppen und Anrufregionen mit uns ab; bevorzugt die Abstimmung per Mail.", + }, + ], + "projekte": [ + { + "projektname": "Kampagne Thermofenster Linienserie Alpin", + "zweck": "Telefonische Kampagne bei Baufirmen, Architekten und Holzbaubetrieben für die neue Thermofenster-Serie 'Alpin'. Ziel: 300 geführte Gespräche und 45 Mess- beziehungsweise Angebotstermine.", + "verantwortlicher": "Sandra Wittke", + "startdatum": date(2026, 8, 10), + "kontaktadressen": [ + { + "anrede": "Herr", "name": "Jakob Rainer", + "firma": "Rainer Holzbau", + "position": "Geschäftsführer", + "telefon": "+43 512 344 087", + "email": "j.rainer@rainer-holzbau.at", + "strasse": "Museumstr. 13", "plz": "6020", + "ort": "Innsbruck", "land": "Österreich", + "notiz": "Setzt auf Passivhaus-Komponenten; U-Werte müssen im Angebot ausgewiesen sein.", + }, + { + "anrede": "Frau", "name": "Notburga Wörgötter", + "firma": "Wörgötter Zimmerei", + "position": "Kalkulation und Einkauf", + "telefon": "+43 662 834 556", + "email": "n.woergoetter@woergoetter-zimmerei.at", + "strasse": "Schallmooser Hauptstr. 44", "plz": "5020", + "ort": "Salzburg", "land": "Österreich", + "notiz": "Benötigt Muster für die Messepräsentation im November.", + }, + { + "anrede": "Herr", "name": "Quirin Hofer", + "firma": "Hofer Bau & Planung", + "position": "Architekt", + "telefon": "+49 871 339 421", + "email": "q.hofer@hofer-bau-planung.de", + "strasse": "Ostengasse 15", "plz": "84028", + "ort": "Landshut", "land": "Deutschland", + "notiz": "Plant ein mehrgeschossiges Holzbauprojekt; Fensterelemente sind noch nicht vergeben.", + }, + ], + }, + { + "projektname": "Wiedervorlage-Bestellungen Winterholz-Vorkauf", + "zweck": "Frühzeitige telefonische Vorkaufskampagne für Winterholz: Bestandskunden aus dem Bauhandel anrufen, Bedarf aufnehmen und Wiedervorlagen für die Bestellzeit setzen. Ziel: 65 Prozent Vorab-Buchung des Winterkontingents.", + "verantwortlicher": "Jonas Ade", + "startdatum": date(2026, 9, 21), + "kontaktadressen": [ + { + "anrede": "Herr", "name": "Werner Prantner", + "firma": "Prantner Baustoffe", + "position": "Verkauf", + "telefon": "+49 8631 554 22", + "email": "w.prantner@prantner-baustoffe.de", + "strasse": "Simbacher Str. 14", "plz": "84137", + "ort": "Vilsbiburg", "land": "Deutschland", + "notiz": "Bestellt traditionell erst im November; testet dieses Jahr erstmals den Vorkauf.", + }, + { + "anrede": "Frau", "name": "Cäcilia Pfeiff", + "firma": "Pfeiff Holzmarkt", + "position": "Inhaberin", + "telefon": "+43 732 915 447", + "email": "c.pfeiff@pfeiff-holzmarkt.at", + "strasse": "Rudolfstr. 12", "plz": "4020", + "ort": "Linz", "land": "Österreich", + "notiz": "Braucht eine verbindliche Lieferzusage bis Anfang Dezember.", + }, + { + "anrede": "Herr", "name": "Ambros Hinterseer", + "firma": "Hinterseer Sägewerke", + "position": "Disposition", + "telefon": "+43 5356 622 18", + "email": "a.hinterseer@hinterseer-saegewerke.at", + "strasse": "Jochberger Str. 9", "plz": "6360", + "ort": "Kitzbühel", "land": "Österreich", + "notiz": "Koordiniert den Transport selbst; Übergabetermine müssen exakt stehen.", + }, + ], + }, + { + "projektname": "Lead-Qualifizierung Sägewerksbetriebe Bayern/Tirol", + "zweck": "Anrufcampagne zur Lead-Qualifizierung bei mittelständischen Sägewerken in Bayern und Tirol: Recherche der Ansprechpartner und Bewertung nach Einkaufsvolumen. Ziel: 200 qualifizierte Leads bis Mitte des zweiten Quartals.", + "verantwortlicher": "Pia Merkert", + "startdatum": date(2026, 9, 28), + "kontaktadressen": [ + { + "anrede": "Herr", "name": "Hubert Mühlbacher", + "firma": "Mühlbacher Holz", + "position": "Einkaufsleiter", + "telefon": "+49 851 987 350", + "email": "h.muehlbacher@muehlbacher-holz.de", + "strasse": "Innstr. 40", "plz": "94032", + "ort": "Passau", "land": "Deutschland", + "notiz": "Fragt nach Zertifikaten und Lieferkonditionen bereits im Erstgespräch.", + }, + { + "anrede": "Frau", "name": "Resi Peringer", + "firma": "Peringer Säge", + "position": "Disposition", + "telefon": "+43 5285 644 21", + "email": "r.peringer@peringer-saege.at", + "strasse": "Dorfstr. 22", "plz": "6290", + "ort": "Mayrhofen", "land": "Österreich", + "notiz": "Familienbetrieb in dritter Generation; die Entscheidung liegt beim Senior.", + }, + { + "anrede": "Herr", "name": "Konrad Eder", + "firma": "Eder Holzwerk", + "position": "Betriebsleiter", + "telefon": "+49 8031 554 67", + "email": "k.eder@eder-holzwerk.de", + "strasse": "Chiemseestr. 28", "plz": "83022", + "ort": "Rosenheim", "land": "Deutschland", + "notiz": "Erweitert die Produktionskapazität; sucht Lieferanten mit hoher Flexibilität.", + }, + ], + }, + ], + }, +] + +# Betreff-Vorlagen für die automatisch erzeugten Gesprächsnotizen. Passen +# zum Ablauf einer Telefon-Akquise (Erstkontakt -> Termin -> Angebot). +_BETREFFVORLAGEN = [ + "Erstkontakt und Bedarfsaufnahme", + "Produktvorstellung am Telefon", + "Terminvorschlag Außendienst", + "Angebot nachreichen", + "Nachfassung zum Angebot", + "Wiedervorlage nach Sommerpause", + "Rückruf vereinbart", +] + +# Notiz-Vorlagen für die automatisch erzeugten Gesprächsnotizen. Formuliert +# wie typische Ergebnisse eines Akquise-Telefonats; ein leerer Eintrag +# steht für ein Telefonat ohne Notiz. +_NOTIZVORLAGEN = [ + "Gesprächspartner zeigt grundsätzliches Interesse und bittet um die Preisliste als PDF. Das Angebot geht per Mail raus.", + "Kein akuter Bedarf, aber Erweiterungsplanung für das kommende Jahr. Wiedervorlage im Frühjahr gesetzt.", + "Vor-Ort-Termin zur Besichtigung vereinbart; Unterlagen werden vorab per Mail gesendet.", + "Zentrale verweist auf die Beschaffungsabteilung, der konkrete Ansprechpartner wird noch erfragt.", + "Muster werden angefragt; Rückruf zugesagt, sobald die Kollegin aus dem Urlaub zurück ist.", + "Angebot wurde durchgesprochen, das Budget ist noch nicht freigegeben. Nächster Anruf nach der Freigabe.", + "", +] + + +def _seed_musterdaten(): """ - Legt für jeden Muster-Kunden 1 bis 3 Muster-Projekte an, falls die - projekte-Tabelle noch leer ist. + Legt die vollständigen Musterdaten an: drei Kunden mit kundenspezifischen + Kontakten, je drei Telefon-Akquise-Projekte pro Kunde, projektbezogene + Kontaktadressen (Akquise-Ziele) und daraus abgeleitete Gesprächsnotizen. + + Aufruf nur nach _purge_crm_daten(), da die Funktion die Daten ohne + Existenzprüfung neu anlegt. Die Gesprächsnotizen verweisen auf die + Kontaktadresse, zu der sie gehören (kontaktadresse_id ist Pflichtfeld), + und übernehmen Name und Telefonnummer des Ziels. """ import random - from crm.model import Kunde, Projekt + from crm.model import Kunde, Kontakt, Projekt, Kontaktadresse, CallLog session = dbapp.session - if session.query(Projekt).count() > 0: - return + # Fester Seed für reproduzierbare Musterdaten (Datum, Uhrzeit, Dauer). + rng = random.Random(456) - kunden = session.query(Kunde).order_by(Kunde.id).all() - vorlagen = [ - ("Website-Relaunch", "Neugestaltung der Unternehmenswebsite"), - ("E-Commerce Plattform", "Online-Shop Entwicklung"), - ("CRM-System", "Kundenbeziehungsmanagement"), - ("Mobile App", "iOS- und Android-Entwicklung"), - ("SEO-Optimierung", "Suchmaschinenoptimierung"), - ] - verantwortliche = [ - "Max Mustermann", "Anna Schmidt", "Peter Müller", - "Lisa Bauer", "Thomas Becker", - ] - rng = random.Random(42) - vorlagen_idx = 0 - for kunde in kunden: - anzahl = rng.randint(1, 3) - for _ in range(anzahl): - name, zweck = vorlagen[vorlagen_idx % len(vorlagen)] - vorlagen_idx += 1 - projekt = Projekt() - projekt.kunden_id = kunde.id - projekt.projektname = name - projekt.zweck = zweck - projekt.verantwortlicher = rng.choice(verantwortliche) - projekt.startdatum = "2024-0{monat}-1{tag}".format( - monat=rng.randint(1, 9), tag=rng.randint(0, 9) + for kundendaten in _MUSTERDATEN: + kunde = Kunde( + name=kundendaten["name"], + strasse=kundendaten["strasse"], + plz=kundendaten["plz"], + ort=kundendaten["ort"], + land=kundendaten["land"], + ) + session.add(kunde) + # flush liefert die Auto-Increment-ID für die abhängigen Sätze. + session.flush() + + for kontaktdaten in kundendaten["kontakte"]: + kontakt = Kontakt( + kunden_id=kunde.id, + anrede=kontaktdaten["anrede"], + name=kontaktdaten["name"], + position=kontaktdaten["position"], + telefon=kontaktdaten["telefon"], + email=kontaktdaten["email"], + notiz=kontaktdaten["notiz"], + ) + session.add(kontakt) + + for projektdaten in kundendaten["projekte"]: + projekt = Projekt( + kunden_id=kunde.id, + projektname=projektdaten["projektname"], + zweck=projektdaten["zweck"], + verantwortlicher=projektdaten["verantwortlicher"], + startdatum=projektdaten["startdatum"], ) session.add(projekt) - session.commit() + session.flush() + for adressdaten in projektdaten["kontaktadressen"]: + adresse = Kontaktadresse( + projekt_id=projekt.id, + anrede=adressdaten["anrede"], + name=adressdaten["name"], + firma=adressdaten["firma"], + position=adressdaten["position"], + telefon=adressdaten["telefon"], + email=adressdaten["email"], + strasse=adressdaten["strasse"], + plz=adressdaten["plz"], + ort=adressdaten["ort"], + land=adressdaten["land"], + notiz=adressdaten["notiz"], + ) + session.add(adresse) + session.flush() -def _seed_muster_kontaktadressen(): - """ - Legt für jedes Muster-Projekt 1 bis 3 Kontaktadressen an, falls die - kontaktadressen-Tabelle noch leer ist. - """ - import random - - from crm.model import Projekt, Kontaktadresse - - session = dbapp.session - if session.query(Kontaktadresse).count() > 0: - return - - projekte = session.query(Projekt).order_by(Projekt.id).all() - vorlagen = [ - ("Herr", "Thomas Becker", "Becker Webdesign GmbH", "Geschäftsführer"), - ("Frau", "Sandra Klein", "Klein Marketing", "Marketingleiterin"), - ("Herr", "Michael Wagner", "Wagner Online Shop", "IT-Leiter"), - ("Frau", "Julia Hoffmann", "Hoffmann Consulting", "Beraterin"), - ("Herr", "Stefan Roth", "Roth IT Solutions", "Technischer Leiter"), - ] - orte = [ - ("Industriestr. 12", "45131", "Essen", "Deutschland"), - ("Königsallee 5", "40212", "Düsseldorf", "Deutschland"), - ("Hauptstr. 88", "50667", "Köln", "Deutschland"), - ("Marienplatz 3", "80331", "München", "Deutschland"), - ] - rng = random.Random(123) - vorlagen_idx = 0 - for projekt in projekte: - anzahl = rng.randint(1, 3) - for _ in range(anzahl): - anrede, name, firma, position = vorlagen[vorlagen_idx % len(vorlagen)] - vorlagen_idx += 1 - strasse, plz, ort, land = rng.choice(orte) - ka = Kontaktadresse() - ka.projekt_id = projekt.id - ka.anrede = anrede - ka.name = name - ka.firma = firma - ka.position = position - ka.telefon = "+49 {vorwahl} {nummer}".format( - vorwahl=rng.randint(100, 999), - nummer=rng.randint(100000, 999999) - ) - ka.email = name.lower().replace(" ", ".") + "@example.de" - ka.strasse = strasse - ka.plz = plz - ka.ort = ort - ka.land = land - ka.notiz = "" - session.add(ka) - session.commit() - - -def _seed_muster_calllogs(): - """ - Legt für jedes Muster-Projekt 2 bis 5 Gesprächsnotizen an, falls die - call_logs-Tabelle noch leer ist. - """ - import random - - from crm.model import Projekt, CallLog - - session = dbapp.session - if session.query(CallLog).count() > 0: - return - - projekte = session.query(Projekt).order_by(Projekt.id).all() - betreffvorlagen = [ - "Anforderungen klären", - "Feedback zum Design", - "Technische Anforderungen", - "Schulungstermin", - "Feature-Request", - "Budgetgespräch", - "Projekt-Review", - ] - notizvorlagen = [ - "Kunde möchte responsive Design und neue Farbgestaltung. Budget noch offen.", - "Design-Entwürfe wurden positiv aufgenommen. Kleine Änderungen an der Navigation nötig.", - "Payment-Gateway und Versand-Schnittstellen müssen integriert werden.", - "Schulung für 10 Mitarbeiter geplant. Raum noch zu buchen.", - "Kunde wünscht Push-Benachrichtigungen und Offline-Modus.", - "", - ] - ansprechpartner = ["Herr Schmidt", "Frau Müller", "Herr Weber", "Frau Bauer", "Herr König"] - rng = random.Random(456) - for projekt in projekte: - anzahl = rng.randint(2, 5) - for _ in range(anzahl): - log = CallLog() - log.projekt_id = projekt.id - monat = rng.randint(1, 9) - tag = rng.randint(1, 28) - log.datum = "2024-0{m}-{t}".format(m=monat, t=tag) if monat < 10 else "2024-{m}-{t}".format(m=monat, t=tag) - log.uhrzeit_beginn = "{h:02d}:{min:02d}".format( - h=rng.randint(8, 17), min=rng.randint(0, 59) - ) - log.dauer = str(rng.randint(15, 90)) - log.betreff = rng.choice(betreffvorlagen) - log.name_ansprechpartner = rng.choice(ansprechpartner) - log.telefon = "+49 {v} {n}".format( - v=rng.randint(100, 999), n=rng.randint(100000, 999999) - ) - log.notiz = rng.choice(notizvorlagen) - wiedervorlage_monat = monat + 1 if monat < 12 else 1 - log.wiedervorlage_datum = "2024-0{m}-{t}".format(m=wiedervorlage_monat, t=tag) if wiedervorlage_monat < 10 else "2024-{m}-{t}".format(m=wiedervorlage_monat, t=tag) - session.add(log) + # Je Kontaktadresse ein bis zwei Gesprächsnotizen, die das + # Akquise-Telefonat mit diesem Ziel dokumentieren. + for _ in range(rng.randint(1, 2)): + log = CallLog() + log.projekt_id = projekt.id + log.kontaktadresse_id = adresse.id + log.datum = date(2026, 9, rng.randint(1, 26)) + log.uhrzeit_beginn = "{h:02d}:{min:02d}".format( + h=rng.randint(8, 17), min=rng.randint(0, 59) + ) + log.dauer = str(rng.randint(5, 45)) + log.betreff = rng.choice(_BETREFFVORLAGEN) + log.name_ansprechpartner = adresse.name + log.telefon = adresse.telefon + log.notiz = rng.choice(_NOTIZVORLAGEN) + log.wiedervorlage_datum = log.datum + timedelta( + days=rng.randint(7, 28) + ) + session.add(log) session.commit()