Jelajahi Sumber

E-Mail-Adressenlogik

gc-server3 4 hari lalu
induk
melakukan
67de410fbf
3 mengubah file dengan 75 tambahan dan 9 penghapusan
  1. 5 3
      README.md
  2. 15 3
      app/documents.py
  3. 55 3
      tests/test_documents.py

+ 5 - 3
README.md

@@ -486,9 +486,11 @@ Produktivexporte unter `data/docuware-exports/` sind von Git ausgeschlossen.
 
 „Weiterleiten“ erzeugt eine `.eml`-Datei mit genau einem Anhang und `X-Unsent: 1`.
 Empfänger stammen aus der geöffneten Forderung; fehlende Adressen deaktivieren das
-jeweilige Ziel. Der Verursacher berücksichtigt die manuelle Zuordnung und wird
-über AD aufgelöst. Im Debuglogin ist die eigene Adresse leer. Die Anwendung
-versendet keine E-Mails. Die Bearbeitbarkeit des Entwurfs muss im eingesetzten
+jeweilige Ziel. „An mich“ verwendet `Benutzer_Rolle.Benutzer_Email` für den angemeldeten
+Benutzer, ersatzweise dessen E-Mail-Adresse aus den Anmeldedaten. „Verursacher“ verwendet
+`Forderungen_Liste.Verursacher_Email` für Mandant und Belegnummer der geöffneten Forderung;
+fehlt eine gültige Adresse, bleibt die AD-Abfrage des aktuellen Verursachers als Rückfall.
+Die Anwendung versendet keine E-Mails. Die Bearbeitbarkeit des Entwurfs muss im eingesetzten
 Mailprogramm geprüft werden; sie hängt vom Client und dessen Dateizuordnung ab.
 Vor Produktivfreigabe sind außerdem Suchfelder und PDF-Ausgabe am echten Archiv
 zu prüfen. `DW_TIMEOUT` begrenzt die einzelnen DocuWare-Anfragen.

+ 15 - 3
app/documents.py

@@ -194,8 +194,20 @@ def recipients(request, primary_key, db):
     tenant, number = primary_key.split("_", 1)
     query = templates.env.get_template("forderungen/details/queries/forderungen_details.sql").render()
     row = db.execute(text(query), {"client_db": tenant, "beleg_nr": number}).mappings().first()
-    colleague = ""
-    if row and row.get("Verursacher_Aktuell_Benutzer_ID"):
+    principal = current_principal(request)
+    user_rows = db.execute(text("""
+        SELECT DISTINCT Benutzer_Email FROM dbo.Benutzer_Rolle
+        WHERE Benutzer_ID = :username
+        ORDER BY Benutzer_Email
+    """), {"username": principal.username}).mappings().fetchall()
+    own_address = next((address for item in user_rows
+                        if valid_address(address := (item["Benutzer_Email"] or "").strip())), principal.email)
+    list_row = db.execute(text("""
+        SELECT TOP 1 Verursacher_Email FROM dbo.Forderungen_Liste
+        WHERE Client_DB = :client_db AND Beleg_Nr = :beleg_nr
+    """), {"client_db": tenant, "beleg_nr": number}).mappings().first()
+    colleague = (list_row.get("Verursacher_Email") or "").strip() if list_row else ""
+    if not valid_address(colleague) and row and row.get("Verursacher_Aktuell_Benutzer_ID"):
         try:
             user = lookup_directory_user(row["Verursacher_Aktuell_Benutzer_ID"])
             if user and not user.disabled:
@@ -203,7 +215,7 @@ def recipients(request, primary_key, db):
         except (DirectoryUnavailable, ValueError):
             pass
     values = [
-        ("self", "An mich", current_principal(request).email),
+        ("self", "An mich", own_address),
         ("colleague", "Verursacher", colleague),
         ("customer", "Kunde", row.get("Kunde_Email") if row else ""),
         ("insurance", "Versicherung", row.get("Vers_Email") if row else ""),

+ 55 - 3
tests/test_documents.py

@@ -161,6 +161,44 @@ class DocumentTests(unittest.TestCase):
                 MockAdapter().search("1", document_no="R1")
 
 
+    def test_database_email_sources_enable_recipients_and_address_drafts(self):
+        self.prepare()
+        from app.documents import identity
+
+        self.db.connection.executescript("""
+            ALTER TABLE dbo.Benutzer_Rolle ADD Benutzer_Email TEXT;
+            UPDATE dbo.Benutzer_Rolle SET Benutzer_Email=' me@example.com ' WHERE Benutzer_ID='tester';
+            INSERT INTO dbo.Benutzer_Rolle (Benutzer_ID, Benutzer_Email) VALUES ('tester', 'me@example.com');
+            INSERT INTO dbo.Benutzer_Rolle (Benutzer_ID, Benutzer_Email) VALUES ('other', 'wrong-user@example.com');
+            CREATE TABLE dbo.Forderungen_Liste (Client_DB TEXT, Beleg_Nr TEXT, Verursacher_Email TEXT);
+            INSERT INTO dbo.Forderungen_Liste VALUES ('1', 'R1', 'assignee@example.com');
+            INSERT INTO dbo.Forderungen_Liste VALUES ('2', 'R1', 'wrong-tenant@example.com');
+        """)
+        execute = self.db.execute
+
+        def details_and_queries(statement, values=None):
+            if 'AS [Kunde_offen_gesamt]' in str(statement):
+                result = Mock()
+                result.mappings.return_value.first.return_value = dict(
+                    Verursacher_Aktuell_Benutzer_ID='assignee', Kunde_Email='', Vers_Email='')
+                return result
+            return execute(statement, values)
+
+        token = identity('dw', 'sample', '1')
+        with patch.object(self.db, 'execute', side_effect=details_and_queries), patch('app.documents.lookup_directory_user') as lookup:
+            response = self.client.get(self.base + '/recipients/' + token)
+            self.assertEqual(response.status_code, 200)
+            self.assertIn('An mich: me@example.com', response.text)
+            self.assertIn('Verursacher: assignee@example.com', response.text)
+            for target, expected in [('self', 'me@example.com'), ('colleague', 'assignee@example.com')]:
+                response = self.client.get(self.base + '/draft/' + token, params={'recipient': target})
+                self.assertEqual(response.status_code, 200)
+                message = BytesParser(policy=policy.default).parsebytes(response.content)
+                self.assertEqual(message['To'], expected)
+                self.assertEqual(len(list(message.iter_attachments())), 1)
+            lookup.assert_not_called()
+
+
 class AdapterTests(unittest.TestCase):
     def test_live_exact_search_missing_fields_download_and_full_iteration(self):
         document = SimpleNamespace(id=1, title="Test", fields=[SimpleNamespace(name="Belegnummer", value="R1")])
@@ -199,13 +237,14 @@ class AdapterTests(unittest.TestCase):
             self.assertNotIn("sensitive-token", json.dumps(result))
 
     def test_recipients_from_open_case_and_ad_override(self):
-        request = SimpleNamespace(state=SimpleNamespace(principal=SimpleNamespace(email="self@example.com")))
+        request = SimpleNamespace(state=SimpleNamespace(principal=SimpleNamespace(username="tester", email="self@example.com")))
         db = Mock()
-        db.execute.return_value.mappings.return_value.first.return_value = dict(
+        db.execute.return_value.mappings.return_value.first.side_effect = [dict(
             Verursacher_Aktuell_Benutzer_ID="override",
             Kunde_Email="customer@example.com",
             Vers_Email="insurance@example.com",
-        )
+        ), None]
+        db.execute.return_value.mappings.return_value.fetchall.return_value = []
         with patch(
             "app.documents.lookup_directory_user",
             return_value=SimpleNamespace(email="colleague@example.com", disabled=False),
@@ -217,3 +256,16 @@ class AdapterTests(unittest.TestCase):
                 ["self@example.com", "colleague@example.com", "customer@example.com", "insurance@example.com"],
             )
         self.assertEqual(db.execute.call_args.args[1], dict(client_db="1", beleg_nr="R1"))
+
+    def test_missing_or_invalid_database_emails_keep_valid_session_fallback(self):
+        request = SimpleNamespace(state=SimpleNamespace(principal=SimpleNamespace(username='tester', email='session@example.com')))
+        for database_emails in [[], [{'Benutzer_Email': None}], [{'Benutzer_Email': 'invalid'}],
+                                [{'Benutzer_Email': 'invalid'}, {'Benutzer_Email': 'db@example.com'}]]:
+            with self.subTest(emails=database_emails):
+                db = Mock()
+                db.execute.return_value.mappings.return_value.first.side_effect = [dict(Kunde_Email='', Vers_Email=''), None]
+                db.execute.return_value.mappings.return_value.fetchall.return_value = database_emails
+                targets = recipients(request, '1_R1', db)
+                expected = 'db@example.com' if len(database_emails) == 2 else 'session@example.com'
+                self.assertEqual(targets[0]['email'], expected)
+                self.assertEqual(targets[1]['email'], '')