Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ai_docs/masterplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This document outlines the high-level development phases for ProjectVote. Each s
### 4.2 New Application Notification
### 4.3 Final Decision Notification
### 4.4 Configurable Rejection Emails
### 4.5 Application Confirmation Notification

## 5. Integration & End-to-End Testing
### 5.1 Backend-Frontend Integration
Expand Down
1 change: 1 addition & 0 deletions ai_docs/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This document outlines the core functional requirements for the web application
* A user-friendly form for entering project details (title, description, costs, department).
* Collection of applicant's contact information (name, email).
* Optional file upload for supporting documents (PDF, XLSX, DOCX, etc.).
* Upon successful submission, the applicant receives a confirmation email containing all the details of their submission.

## 2. Token-Based Board Voting

Expand Down
34 changes: 34 additions & 0 deletions ai_docs/task_4_5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Task 4.5: Application Confirmation Notification

## Goal
Implement an automated confirmation email to the applicant upon successful submission of a new funding application, ensuring they have a record of their submission.

## Plan
1. Create a new, dedicated email template for the applicant confirmation.
2. Integrate the email sending logic into the application submission endpoint.
3. Ensure all submitted data is included in the email for the applicant's reference.
4. Verify the functionality through testing and local email inspection.

## Tasks

### Phase 1: Email Template Creation
* [x] Create a new HTML email template named `application_confirmation.html` in `src/projectvote/backend/templates/email/`.
* [ ] The template should be designed to clearly display all key application details:
* Applicant's Full Name
* Applicant's Email
* Department
* Project Title
* Project Description
* Estimated Costs
* [x] The template should also acknowledge if a file was attached (e.g., "Your attachment '[filename]' was successfully uploaded.").
* [x] The template should contain a link to the archive of the web app.

### Phase 2: Backend Integration
* [x] Modify the `submit_application` function in `src/projectvote/backend/main.py`.
* [x] After successfully saving the application and any attachments, call the `send_email` function.
* [x] The recipient for this email should be the `applicant_email` from the submitted data.
* [x] Pass all necessary application data (including details of the attachment, if any) to the `application_confirmation.html` template.

### Phase 3: Verification
* [x] Add a unit test to verify that the email sending function is called with the correct parameters (recipient, subject, template) upon a successful application submission.
* [x] Use MailHog during local development to visually inspect the email and confirm its content, formatting, and accuracy.
26 changes: 26 additions & 0 deletions src/projectvote/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,29 @@ class ApplicationOut(BaseModel):
# --- Email Sending Functions ---


async def send_confirmation_email(application: Application, settings: Settings) -> None:
"""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}",
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,
"attachment_filename": application.attachments[0].filename
if application.attachments
else None,
"frontend_url": settings.frontend_url,
},
template_name="application_confirmation.html",
settings=settings,
)


async def send_voting_links(
application: Application,
db: AsyncSession,
Expand Down Expand Up @@ -373,6 +396,9 @@ async def submit_application(
# Refresh the application with attachments relationship loaded
await db.refresh(new_application, attribute_names=["attachments"])

# Send confirmation email to the applicant
await send_confirmation_email(new_application, settings)

# Generate vote records and send links
await send_voting_links(new_application, db, board_members, settings)
await db.commit() # Commit all changes (application, attachment, and vote records)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<body>
<p>Hallo {{ first_name }} {{ last_name }},</p>
<p>Vielen Dank für Ihren Förderantrag. Wir haben ihn erfolgreich erhalten.</p>
<p>Diese E-Mail dient als Bestätigung Ihrer Einreichung. Nachfolgend finden Sie eine Zusammenfassung Ihrer übermittelten Details:</p>

<h3>Zusammenfassung des Antrags</h3>
<ul>
<li><strong>Projekttitel:</strong> {{ project_title }}</li>
<li><strong>Name des Antragstellers:</strong> {{ first_name }} {{ last_name }}</li>
<li><strong>E-Mail des Antragstellers:</strong> {{ applicant_email }}</li>
<li><strong>Abteilung/Fachschaft:</strong> {{ department }}</li>
<li><strong>Geschätzte Kosten:</strong> €{{ costs }}</li>
<li><strong>Projektbeschreibung:</strong><br>{{ project_description }}</li>
</ul>

{% if attachment_filename %}
<p><strong>Anhang:</strong> Ihre Datei '{{ attachment_filename }}' wurde erfolgreich hochgeladen.</p>
{% endif %}

<p>Wir werden Ihren Antrag prüfen und uns nach Abschluss des Abstimmungsprozesses bei Ihnen melden.</p>
<p>Sie können den Status aller Anträge im <a href="{{ frontend_url }}/archive">Antragsarchiv</a> einsehen.</p>
<p>Mit freundlichen Grüßen,</p>
<p>LvD Förderverein</p>
</body>
</html>
56 changes: 45 additions & 11 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,51 @@ async def test_create_application(
actual_status = actual_status.value
assert actual_status == VoteStatus.PENDING.value

# Verify that email sending was triggered
assert send_email_mock.call_count == len(TEST_BOARD_MEMBERS)

# Verify that the email body contains the correct data
first_call_args = send_email_mock.call_args_list[0]
template_body = first_call_args.kwargs["template_body"]
assert template_body["first_name"] == application_data["first_name"]
assert template_body["last_name"] == application_data["last_name"]
assert template_body["project_title"] == application_data["project_title"]
assert template_body["costs"] == application_data["costs"]
assert "vote_url" in template_body
# Verify that email sending was triggered for board members and the applicant
assert send_email_mock.call_count == len(TEST_BOARD_MEMBERS) + 1

# Separate the calls for applicant and board members
applicant_email_call = None
board_member_email_calls = []
for call in send_email_mock.call_args_list:
if call.kwargs["recipients"] == [application_data["applicant_email"]]:
applicant_email_call = call
else:
board_member_email_calls.append(call)

# Verify the confirmation email to the applicant
assert applicant_email_call is not None
assert (
applicant_email_call.kwargs["subject"]
== f"Bestätigung Ihres Antrags: {application_data['project_title']}"
)
assert (
applicant_email_call.kwargs["template_name"] == "application_confirmation.html"
)
applicant_template_body = applicant_email_call.kwargs["template_body"]
assert applicant_template_body["first_name"] == application_data["first_name"]
assert applicant_template_body["last_name"] == application_data["last_name"]
assert (
applicant_template_body["applicant_email"]
== application_data["applicant_email"]
)
assert applicant_template_body["department"] == application_data["department"]
assert applicant_template_body["project_title"] == application_data["project_title"]
assert (
applicant_template_body["project_description"]
== application_data["project_description"]
)
assert applicant_template_body["costs"] == application_data["costs"]

# Verify the notification emails to board members
assert len(board_member_email_calls) == len(TEST_BOARD_MEMBERS)
first_board_call_args = board_member_email_calls[0]
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["project_title"] == application_data["project_title"]
assert board_template_body["costs"] == application_data["costs"]
assert "vote_url" in board_template_body


@pytest.mark.parametrize(
Expand Down