diff --git a/ai_docs/requirements.md b/ai_docs/requirements.md index 71ebd1d..0d34e09 100644 --- a/ai_docs/requirements.md +++ b/ai_docs/requirements.md @@ -16,7 +16,7 @@ This document outlines the core functional requirements for the web application * **Purpose:** Enable board members to securely review and vote on submitted applications. * **Features:** * Upon application submission, unique, secure voting links are generated for each board member. - * Board members receive email notifications with their individual voting links. + * Board members receive email notifications with their individual voting links. The email also contains all relevant information about the application. * Access to application details via the unique voting link. * Ability to cast a vote (approve, reject, or abstain) through a dedicated form. * Prevention of multiple votes from the same token. @@ -29,6 +29,7 @@ This document outlines the core functional requirements for the web application * The application status is automatically updated as soon as a definitive decision has been reached, even if not all board members have voted. * Decision logic: A simple majority of cast votes determines the outcome. The voting closes as soon as an irreversible majority is reached. In case of a tie, the application is rejected. * Email notifications are sent to all board members once a final decision is reached. + * The email contains all relevant information about the application. * An email notification is sent to the applicant. For rejected applications, this can be disabled via a configuration setting to allow for a personalized response. ## 4. Application Archive diff --git a/ai_docs/task_4_2.md b/ai_docs/task_4_2.md index 396a5f0..bc61cfd 100644 --- a/ai_docs/task_4_2.md +++ b/ai_docs/task_4_2.md @@ -13,9 +13,12 @@ Integrate the email service to automatically send notifications to all board mem ### Phase 1: Email Template * [x] Design a clear and informative email template. * [x] The template should include: - * The project title. - * The applicant's name. - * The estimated costs. + * Applicant's Full Name + * Applicant's Email + * Department + * Project Title + * Project Description + * Estimated Costs * A direct, unique link to the voting page (e.g., `http://localhost:5173/vote/{token}`). * [x] Consider using a simple HTML template for better formatting. diff --git a/ai_docs/task_4_3.md b/ai_docs/task_4_3.md index 46c9c56..0f3ff77 100644 --- a/ai_docs/task_4_3.md +++ b/ai_docs/task_4_3.md @@ -17,15 +17,30 @@ Automate the sending of final decision emails to both the applicant and all boar ### Phase 2: Applicant Email Template * [x] Design an email template for the applicant. * [x] The template should: - * Clearly state the final decision (approved or rejected). - * Include the project title. - * Thank the applicant for their submission. + * [x] Clearly state the final decision (approved or rejected). + * [x] It should contain: + * [x] Applicant's Full Name + * [x] Applicant's Email + * [x] Department + * [x] Project Title + * [x] Project Description + * [x] Estimated Costs + * [x] A link to the archive of the web app. + * [x] Thank the applicant for their submission. ### Phase 3: Board Member Email Template * [x] Design an email template for the board members. * [x] The template should: - * Announce that voting is complete for a specific application. - * State the final outcome. + * [x] Announce that voting is complete for a specific application. + * [x] State the final outcome. + * [x] It should contain: + * [x] Applicant's Full Name + * [x] Applicant's Email + * [x] Department + * [x] Project Title + * [x] Project Description + * [x] Estimated Costs + * [x] A link to the archive of the web app. ### Phase 4: Verification * [x] Use a local email testing tool to verify that both the applicant and all board members receive the correct notification. diff --git a/src/projectvote/backend/main.py b/src/projectvote/backend/main.py index 308c802..d3b5877 100644 --- a/src/projectvote/backend/main.py +++ b/src/projectvote/backend/main.py @@ -158,7 +158,7 @@ async def send_confirmation_email(application: Application, settings: Settings) """Send a confirmation email to the applicant.""" await send_email( recipients=[application.applicant_email], # type: ignore[list-item] - subject=f"Bestätigung Ihres Antrags: {application.project_title}", + subject=f"Bestätigung Deines Antrags: {application.project_title}", template_body={ "first_name": application.first_name, "last_name": application.last_name, @@ -199,7 +199,10 @@ async def send_voting_links( template_body={ "first_name": application.first_name, "last_name": application.last_name, + "applicant_email": application.applicant_email, + "department": application.department, "project_title": application.project_title, + "project_description": application.project_description, "costs": application.costs, "vote_url": vote_url, "token": vote_record.token, @@ -238,35 +241,38 @@ async def send_final_decision_emails( # Get the German translation, defaulting to the original status if not found german_status = status_translations.get(status_str, status_str.upper()) - if ( + # Prepare a common template body for both emails + template_body = { + "first_name": application.first_name, + "last_name": application.last_name, + "applicant_email": application.applicant_email, + "department": application.department, + "project_title": application.project_title, + "project_description": application.project_description, + "costs": application.costs, + "status": german_status, + "frontend_url": settings.frontend_url, + } + + # --- Email to Applicant --- + if not ( application.status == ApplicationStatus.REJECTED and not settings.send_automatic_rejection_email ): - # If automatic rejection emails are disabled, skip sending to applicant - pass # pragma: no cover - else: await send_email( recipients=[application.applicant_email], # type: ignore[arg-type] - subject=f"Entscheidung über Ihren Antrag: {application.project_title}", - template_body={ - "first_name": application.first_name, - "last_name": application.last_name, - "project_title": application.project_title, - "status": german_status, - }, + subject=f"Entscheidung über Deinen Antrag: {application.project_title}", + template_body=template_body, template_name="final_decision_applicant.html", settings=settings, ) - # Notification to Board Members + # --- Email to Board Members --- for member_email in board_members: await send_email( recipients=[member_email], subject=f"Abstimmung abgeschlossen für: {application.project_title}", - template_body={ - "project_title": application.project_title, - "status": german_status, - }, + template_body=template_body, template_name="final_decision_board.html", settings=settings, ) diff --git a/src/projectvote/backend/templates/email/application_confirmation.html b/src/projectvote/backend/templates/email/application_confirmation.html index 021818d..859470b 100644 --- a/src/projectvote/backend/templates/email/application_confirmation.html +++ b/src/projectvote/backend/templates/email/application_confirmation.html @@ -1,9 +1,9 @@ -

Hallo {{ first_name }} {{ last_name }},

-

Vielen Dank für Ihren Förderantrag. Wir haben ihn erfolgreich erhalten.

-

Diese E-Mail dient als Bestätigung Ihrer Einreichung. Nachfolgend finden Sie eine Zusammenfassung Ihrer übermittelten Details:

+

Hallo {{ first_name }},

+

Vielen Dank für Deinen Förderantrag. Wir haben ihn erfolgreich erhalten.

+

Diese E-Mail dient als Bestätigung Deiner Einreichung. Nachfolgend findest Du eine Zusammenfassung Deiner übermittelten Details:

Zusammenfassung des Antrags

{% if attachment_filename %} -

Anhang: Ihre Datei '{{ attachment_filename }}' wurde erfolgreich hochgeladen.

+

Anhang: Deine Datei '{{ attachment_filename }}' wurde erfolgreich hochgeladen.

{% endif %} -

Wir werden Ihren Antrag prüfen und uns nach Abschluss des Abstimmungsprozesses bei Ihnen melden.

-

Sie können den Status aller Anträge im Antragsarchiv einsehen.

-

Mit freundlichen Grüßen,

+

Wir werden Deinen Antrag prüfen und uns nach Abschluss des Abstimmungsprozesses bei Dir melden.

+

Du kannst den Status aller Anträge im Antragsarchiv einsehen.

+

Viele Grüße

LvD Förderverein

diff --git a/src/projectvote/backend/templates/email/final_decision_applicant.html b/src/projectvote/backend/templates/email/final_decision_applicant.html index 6ca98c5..83e9d68 100644 --- a/src/projectvote/backend/templates/email/final_decision_applicant.html +++ b/src/projectvote/backend/templates/email/final_decision_applicant.html @@ -2,9 +2,22 @@

Hallo {{ first_name }},

-

Es wurde eine Entscheidung über Deinen Antrag "{{ project_title }}" getroffen.

-

Das Projekt wurde {{ status }}.

-

Viele Grüße.

-

ProjectVote

+

vielen Dank für deine Geduld. Es wurde eine Entscheidung über deinen Förderantrag getroffen.

+

Der Antrag wurde {{ status }}.

+ +

Zusammenfassung des Antrags

+ + +

Du kannst den Status aller Anträge jederzeit im Antragsarchiv einsehen.

+

Wir danken dir für deine Einreichung.

+

Viele Grüße

+

LvD Förderverein

diff --git a/src/projectvote/backend/templates/email/final_decision_board.html b/src/projectvote/backend/templates/email/final_decision_board.html index f249d6e..081a139 100644 --- a/src/projectvote/backend/templates/email/final_decision_board.html +++ b/src/projectvote/backend/templates/email/final_decision_board.html @@ -2,7 +2,21 @@

Hallo,

-

Die Abstimmung für den Antrag "{{ project_title }}" ist abgeschlossen.

-

Der Antrag wurde {{ status }}.

+

die Abstimmung für den Antrag "{{ project_title }}" ist abgeschlossen.

+

Der Antrag wurde {{ status }}.

+ +

Zusammenfassung des Antrags

+ + +

Das konkrete Abstimmungsergebnis ist im Antragsarchiv einsehbar.

+

Viele Grüße

+

Project-Vote

diff --git a/src/projectvote/backend/templates/email/new_application.html b/src/projectvote/backend/templates/email/new_application.html index 3431c43..d597b22 100644 --- a/src/projectvote/backend/templates/email/new_application.html +++ b/src/projectvote/backend/templates/email/new_application.html @@ -2,8 +2,18 @@

Hallo,

-

Ein neuer Förderantrag wurde von {{ first_name }} {{ last_name }} für das Projekt "{{ project_title }}" eingereicht.

-

Die geschätzten Kosten belaufen sich auf: {{ costs }}€.

+

Ein neuer Förderantrag wurde eingereicht und wartet auf Deine Stimme.

+ +

Zusammenfassung des Antrags

+ + {% if attachments %}

Anhänge:

{% endif %} -

Bitte geben Sie hier Ihre Stimme ab: Link zur Abstimmung

+ +

Bitte gib hier Deine Stimme ab: Link zur Abstimmung

+

Viele Grüße

+

Project-Vote

diff --git a/tests/test_main.py b/tests/test_main.py index 924f1d7..ea479a4 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -136,7 +136,7 @@ async def test_create_application( assert applicant_email_call is not None assert ( applicant_email_call.kwargs["subject"] - == f"Bestätigung Ihres Antrags: {application_data['project_title']}" + == f"Bestätigung Deines Antrags: {application_data['project_title']}" ) assert ( applicant_email_call.kwargs["template_name"] == "application_confirmation.html" @@ -162,6 +162,12 @@ async def test_create_application( board_template_body = first_board_call_args.kwargs["template_body"] assert board_template_body["first_name"] == application_data["first_name"] assert board_template_body["last_name"] == application_data["last_name"] + assert board_template_body["applicant_email"] == application_data["applicant_email"] + assert board_template_body["department"] == application_data["department"] + assert ( + board_template_body["project_description"] + == application_data["project_description"] + ) assert board_template_body["project_title"] == application_data["project_title"] assert board_template_body["costs"] == application_data["costs"] assert "vote_url" in board_template_body @@ -545,26 +551,24 @@ async def test_final_decision_email_content( ) assert ( applicant_email_call.kwargs["subject"] - == f"Entscheidung über Ihren Antrag: {app_data['project_title']}" + == f"Entscheidung über Deinen Antrag: {app_data['project_title']}" ) assert ( applicant_email_call.kwargs["template_name"] == "final_decision_applicant.html" ) + applicant_template_body = applicant_email_call.kwargs["template_body"] + assert applicant_template_body["first_name"] == app_data["first_name"] + assert applicant_template_body["last_name"] == app_data["last_name"] + assert applicant_template_body["applicant_email"] == app_data["applicant_email"] + assert applicant_template_body["department"] == app_data["department"] + assert applicant_template_body["project_title"] == app_data["project_title"] assert ( - applicant_email_call.kwargs["template_body"]["first_name"] - == app_data["first_name"] - ) - assert ( - applicant_email_call.kwargs["template_body"]["last_name"] - == app_data["last_name"] - ) - assert ( - applicant_email_call.kwargs["template_body"]["project_title"] - == app_data["project_title"] - ) - assert ( - applicant_email_call.kwargs["template_body"]["status"] == expected_german_status + applicant_template_body["project_description"] + == app_data["project_description"] ) + assert applicant_template_body["costs"] == app_data["costs"] + assert applicant_template_body["status"] == expected_german_status + assert "frontend_url" in applicant_template_body # Check board member emails board_member_email_calls = [ @@ -579,10 +583,19 @@ async def test_final_decision_email_content( == f"Abstimmung abgeschlossen für: {app_data['project_title']}" ) assert call.kwargs["template_name"] == "final_decision_board.html" + board_template_body = call.kwargs["template_body"] + assert board_template_body["first_name"] == app_data["first_name"] + assert board_template_body["last_name"] == app_data["last_name"] + assert board_template_body["applicant_email"] == app_data["applicant_email"] + assert board_template_body["department"] == app_data["department"] + assert board_template_body["project_title"] == app_data["project_title"] assert ( - call.kwargs["template_body"]["project_title"] == app_data["project_title"] + board_template_body["project_description"] + == app_data["project_description"] ) - assert call.kwargs["template_body"]["status"] == expected_german_status + assert board_template_body["costs"] == app_data["costs"] + assert board_template_body["status"] == expected_german_status + assert "frontend_url" in board_template_body @pytest.mark.asyncio