feat(back-end and front-end) email for request
This commit is contained in:
@@ -29,6 +29,8 @@ import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.Year;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -53,6 +55,9 @@ public class CustomQuoteRequestController {
|
||||
@Value("${app.mail.contact-request.admin.address:infog@3d-fab.ch}")
|
||||
private String contactRequestAdminMailAddress;
|
||||
|
||||
@Value("${app.mail.contact-request.customer.enabled:true}")
|
||||
private boolean contactRequestCustomerMailEnabled;
|
||||
|
||||
// TODO: Inject Storage Service
|
||||
private static final Path STORAGE_ROOT = Paths.get("storage_requests").toAbsolutePath().normalize();
|
||||
private static final Pattern SAFE_EXTENSION_PATTERN = Pattern.compile("^[a-z0-9]{1,10}$");
|
||||
@@ -97,6 +102,7 @@ public class CustomQuoteRequestController {
|
||||
"Accettazione Termini e Privacy obbligatoria."
|
||||
);
|
||||
}
|
||||
String language = normalizeLanguage(requestDto.getLanguage());
|
||||
|
||||
// 1. Create Request
|
||||
CustomQuoteRequest request = new CustomQuoteRequest();
|
||||
@@ -173,6 +179,7 @@ public class CustomQuoteRequestController {
|
||||
}
|
||||
|
||||
sendAdminContactRequestNotification(request, attachmentsCount);
|
||||
sendCustomerContactRequestConfirmation(request, attachmentsCount, language);
|
||||
|
||||
return ResponseEntity.ok(request);
|
||||
}
|
||||
@@ -258,6 +265,252 @@ public class CustomQuoteRequestController {
|
||||
);
|
||||
}
|
||||
|
||||
private void sendCustomerContactRequestConfirmation(CustomQuoteRequest request, int attachmentsCount, String language) {
|
||||
if (!contactRequestCustomerMailEnabled) {
|
||||
return;
|
||||
}
|
||||
if (request.getEmail() == null || request.getEmail().isBlank()) {
|
||||
logger.warn("Contact request confirmation skipped: missing customer email for request {}", request.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
templateData.put("requestId", request.getId());
|
||||
templateData.put(
|
||||
"createdAt",
|
||||
request.getCreatedAt().format(
|
||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(localeForLanguage(language))
|
||||
)
|
||||
);
|
||||
templateData.put("recipientName", resolveRecipientName(request, language));
|
||||
templateData.put("requestType", localizeRequestType(request.getRequestType(), language));
|
||||
templateData.put("customerType", localizeCustomerType(request.getCustomerType(), language));
|
||||
templateData.put("name", safeValue(request.getName()));
|
||||
templateData.put("companyName", safeValue(request.getCompanyName()));
|
||||
templateData.put("contactPerson", safeValue(request.getContactPerson()));
|
||||
templateData.put("email", safeValue(request.getEmail()));
|
||||
templateData.put("phone", safeValue(request.getPhone()));
|
||||
templateData.put("message", safeValue(request.getMessage()));
|
||||
templateData.put("attachmentsCount", attachmentsCount);
|
||||
templateData.put("currentYear", Year.now().getValue());
|
||||
String subject = applyCustomerContactRequestTexts(templateData, language, request.getId());
|
||||
|
||||
emailNotificationService.sendEmail(
|
||||
request.getEmail(),
|
||||
subject,
|
||||
"contact-request-customer",
|
||||
templateData
|
||||
);
|
||||
}
|
||||
|
||||
private String applyCustomerContactRequestTexts(
|
||||
Map<String, Object> templateData,
|
||||
String language,
|
||||
UUID requestId
|
||||
) {
|
||||
return switch (language) {
|
||||
case "en" -> {
|
||||
templateData.put("emailTitle", "Contact request received");
|
||||
templateData.put("headlineText", "We received your contact request");
|
||||
templateData.put("greetingText", "Hi " + templateData.get("recipientName") + ",");
|
||||
templateData.put("introText", "Thank you for contacting us. Our team will reply as soon as possible.");
|
||||
templateData.put("requestIdHintText", "Please keep this request ID for future order references:");
|
||||
templateData.put("detailsTitleText", "Request details");
|
||||
templateData.put("labelRequestId", "Request ID");
|
||||
templateData.put("labelDate", "Date");
|
||||
templateData.put("labelRequestType", "Request type");
|
||||
templateData.put("labelCustomerType", "Customer type");
|
||||
templateData.put("labelName", "Name");
|
||||
templateData.put("labelCompany", "Company");
|
||||
templateData.put("labelContactPerson", "Contact person");
|
||||
templateData.put("labelEmail", "Email");
|
||||
templateData.put("labelPhone", "Phone");
|
||||
templateData.put("labelMessage", "Message");
|
||||
templateData.put("labelAttachments", "Attachments");
|
||||
templateData.put("supportText", "If you need help, reply to this email.");
|
||||
templateData.put("footerText", "Automated request-receipt confirmation from 3D-Fab.");
|
||||
yield "We received your contact request #" + requestId + " - 3D-Fab";
|
||||
}
|
||||
case "de" -> {
|
||||
templateData.put("emailTitle", "Kontaktanfrage erhalten");
|
||||
templateData.put("headlineText", "Wir haben Ihre Kontaktanfrage erhalten");
|
||||
templateData.put("greetingText", "Hallo " + templateData.get("recipientName") + ",");
|
||||
templateData.put("introText", "Vielen Dank fuer Ihre Anfrage. Unser Team antwortet Ihnen so schnell wie moeglich.");
|
||||
templateData.put("requestIdHintText", "Bitte speichern Sie diese Anfrage-ID fuer zukuenftige Bestellreferenzen:");
|
||||
templateData.put("detailsTitleText", "Anfragedetails");
|
||||
templateData.put("labelRequestId", "Anfrage-ID");
|
||||
templateData.put("labelDate", "Datum");
|
||||
templateData.put("labelRequestType", "Anfragetyp");
|
||||
templateData.put("labelCustomerType", "Kundentyp");
|
||||
templateData.put("labelName", "Name");
|
||||
templateData.put("labelCompany", "Firma");
|
||||
templateData.put("labelContactPerson", "Kontaktperson");
|
||||
templateData.put("labelEmail", "E-Mail");
|
||||
templateData.put("labelPhone", "Telefon");
|
||||
templateData.put("labelMessage", "Nachricht");
|
||||
templateData.put("labelAttachments", "Anhaenge");
|
||||
templateData.put("supportText", "Wenn Sie Hilfe brauchen, antworten Sie auf diese E-Mail.");
|
||||
templateData.put("footerText", "Automatische Bestaetigung des Anfrageeingangs von 3D-Fab.");
|
||||
yield "Wir haben Ihre Kontaktanfrage erhalten #" + requestId + " - 3D-Fab";
|
||||
}
|
||||
case "fr" -> {
|
||||
templateData.put("emailTitle", "Demande de contact recue");
|
||||
templateData.put("headlineText", "Nous avons recu votre demande de contact");
|
||||
templateData.put("greetingText", "Bonjour " + templateData.get("recipientName") + ",");
|
||||
templateData.put("introText", "Merci pour votre message. Notre equipe vous repondra des que possible.");
|
||||
templateData.put("requestIdHintText", "Veuillez conserver cet ID de demande pour vos futures references de commande :");
|
||||
templateData.put("detailsTitleText", "Details de la demande");
|
||||
templateData.put("labelRequestId", "ID de demande");
|
||||
templateData.put("labelDate", "Date");
|
||||
templateData.put("labelRequestType", "Type de demande");
|
||||
templateData.put("labelCustomerType", "Type de client");
|
||||
templateData.put("labelName", "Nom");
|
||||
templateData.put("labelCompany", "Entreprise");
|
||||
templateData.put("labelContactPerson", "Contact");
|
||||
templateData.put("labelEmail", "Email");
|
||||
templateData.put("labelPhone", "Telephone");
|
||||
templateData.put("labelMessage", "Message");
|
||||
templateData.put("labelAttachments", "Pieces jointes");
|
||||
templateData.put("supportText", "Si vous avez besoin d'aide, repondez a cet email.");
|
||||
templateData.put("footerText", "Confirmation automatique de reception de demande par 3D-Fab.");
|
||||
yield "Nous avons recu votre demande de contact #" + requestId + " - 3D-Fab";
|
||||
}
|
||||
default -> {
|
||||
templateData.put("emailTitle", "Richiesta di contatto ricevuta");
|
||||
templateData.put("headlineText", "Abbiamo ricevuto la tua richiesta di contatto");
|
||||
templateData.put("greetingText", "Ciao " + templateData.get("recipientName") + ",");
|
||||
templateData.put("introText", "Grazie per averci contattato. Il nostro team ti rispondera' il prima possibile.");
|
||||
templateData.put("requestIdHintText", "Conserva questo ID richiesta per i futuri riferimenti d'ordine:");
|
||||
templateData.put("detailsTitleText", "Dettagli richiesta");
|
||||
templateData.put("labelRequestId", "ID richiesta");
|
||||
templateData.put("labelDate", "Data");
|
||||
templateData.put("labelRequestType", "Tipo richiesta");
|
||||
templateData.put("labelCustomerType", "Tipo cliente");
|
||||
templateData.put("labelName", "Nome");
|
||||
templateData.put("labelCompany", "Azienda");
|
||||
templateData.put("labelContactPerson", "Contatto");
|
||||
templateData.put("labelEmail", "Email");
|
||||
templateData.put("labelPhone", "Telefono");
|
||||
templateData.put("labelMessage", "Messaggio");
|
||||
templateData.put("labelAttachments", "Allegati");
|
||||
templateData.put("supportText", "Se hai bisogno, rispondi direttamente a questa email.");
|
||||
templateData.put("footerText", "Conferma automatica di ricezione richiesta da 3D-Fab.");
|
||||
yield "Abbiamo ricevuto la tua richiesta di contatto #" + requestId + " - 3D-Fab";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String localizeRequestType(String requestType, String language) {
|
||||
if (requestType == null || requestType.isBlank()) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
String normalized = requestType.trim().toLowerCase(Locale.ROOT);
|
||||
return switch (language) {
|
||||
case "en" -> switch (normalized) {
|
||||
case "custom", "print_service" -> "Custom part request";
|
||||
case "series" -> "Series production request";
|
||||
case "consult", "design_service" -> "Consultation request";
|
||||
case "question" -> "General question";
|
||||
default -> requestType;
|
||||
};
|
||||
case "de" -> switch (normalized) {
|
||||
case "custom", "print_service" -> "Anfrage fuer Einzelteil";
|
||||
case "series" -> "Anfrage fuer Serienproduktion";
|
||||
case "consult", "design_service" -> "Beratungsanfrage";
|
||||
case "question" -> "Allgemeine Frage";
|
||||
default -> requestType;
|
||||
};
|
||||
case "fr" -> switch (normalized) {
|
||||
case "custom", "print_service" -> "Demande de piece personnalisee";
|
||||
case "series" -> "Demande de production en serie";
|
||||
case "consult", "design_service" -> "Demande de conseil";
|
||||
case "question" -> "Question generale";
|
||||
default -> requestType;
|
||||
};
|
||||
default -> switch (normalized) {
|
||||
case "custom", "print_service" -> "Richiesta pezzo personalizzato";
|
||||
case "series" -> "Richiesta produzione in serie";
|
||||
case "consult", "design_service" -> "Richiesta consulenza";
|
||||
case "question" -> "Domanda generale";
|
||||
default -> requestType;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private String localizeCustomerType(String customerType, String language) {
|
||||
if (customerType == null || customerType.isBlank()) {
|
||||
return "-";
|
||||
}
|
||||
String normalized = customerType.trim().toLowerCase(Locale.ROOT);
|
||||
return switch (language) {
|
||||
case "en" -> switch (normalized) {
|
||||
case "private" -> "Private";
|
||||
case "business" -> "Business";
|
||||
default -> customerType;
|
||||
};
|
||||
case "de" -> switch (normalized) {
|
||||
case "private" -> "Privat";
|
||||
case "business" -> "Unternehmen";
|
||||
default -> customerType;
|
||||
};
|
||||
case "fr" -> switch (normalized) {
|
||||
case "private" -> "Prive";
|
||||
case "business" -> "Entreprise";
|
||||
default -> customerType;
|
||||
};
|
||||
default -> switch (normalized) {
|
||||
case "private" -> "Privato";
|
||||
case "business" -> "Azienda";
|
||||
default -> customerType;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private Locale localeForLanguage(String language) {
|
||||
return switch (language) {
|
||||
case "en" -> Locale.ENGLISH;
|
||||
case "de" -> Locale.GERMAN;
|
||||
case "fr" -> Locale.FRENCH;
|
||||
default -> Locale.ITALIAN;
|
||||
};
|
||||
}
|
||||
|
||||
private String normalizeLanguage(String language) {
|
||||
if (language == null || language.isBlank()) {
|
||||
return "it";
|
||||
}
|
||||
String normalized = language.toLowerCase(Locale.ROOT).trim();
|
||||
if (normalized.startsWith("en")) {
|
||||
return "en";
|
||||
}
|
||||
if (normalized.startsWith("de")) {
|
||||
return "de";
|
||||
}
|
||||
if (normalized.startsWith("fr")) {
|
||||
return "fr";
|
||||
}
|
||||
return "it";
|
||||
}
|
||||
|
||||
private String resolveRecipientName(CustomQuoteRequest request, String language) {
|
||||
if (request.getName() != null && !request.getName().isBlank()) {
|
||||
return request.getName().trim();
|
||||
}
|
||||
if (request.getContactPerson() != null && !request.getContactPerson().isBlank()) {
|
||||
return request.getContactPerson().trim();
|
||||
}
|
||||
if (request.getCompanyName() != null && !request.getCompanyName().isBlank()) {
|
||||
return request.getCompanyName().trim();
|
||||
}
|
||||
return switch (language) {
|
||||
case "en" -> "customer";
|
||||
case "de" -> "Kunde";
|
||||
case "fr" -> "client";
|
||||
default -> "cliente";
|
||||
};
|
||||
}
|
||||
|
||||
private String safeValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return "-";
|
||||
|
||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.AssertTrue;
|
||||
public class QuoteRequestDto {
|
||||
private String requestType; // "PRINT_SERVICE" or "DESIGN_SERVICE"
|
||||
private String customerType; // "PRIVATE" or "BUSINESS"
|
||||
private String language; // "it" | "en" | "de" | "fr"
|
||||
private String email;
|
||||
private String phone;
|
||||
private String name;
|
||||
|
||||
@@ -44,6 +44,7 @@ app.mail.admin.enabled=${APP_MAIL_ADMIN_ENABLED:true}
|
||||
app.mail.admin.address=${APP_MAIL_ADMIN_ADDRESS:admin@printcalculator.local}
|
||||
app.mail.contact-request.admin.enabled=${APP_MAIL_CONTACT_REQUEST_ADMIN_ENABLED:true}
|
||||
app.mail.contact-request.admin.address=${APP_MAIL_CONTACT_REQUEST_ADMIN_ADDRESS:infog@3d-fab.ch}
|
||||
app.mail.contact-request.customer.enabled=${APP_MAIL_CONTACT_REQUEST_CUSTOMER_ENABLED:true}
|
||||
app.frontend.base-url=${APP_FRONTEND_BASE_URL:http://localhost:4200}
|
||||
|
||||
# Admin back-office authentication
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title th:text="${emailTitle}">Contact request received</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 640px;
|
||||
margin: 20px auto;
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 18px;
|
||||
color: #222222;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #444444;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
padding: 10px 6px;
|
||||
color: #333333;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
th {
|
||||
width: 35%;
|
||||
color: #222222;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 24px;
|
||||
font-size: 12px;
|
||||
color: #888888;
|
||||
border-top: 1px solid #eeeeee;
|
||||
padding-top: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 th:text="${headlineText}">We received your contact request</h1>
|
||||
<p th:text="${greetingText}">Hi customer,</p>
|
||||
<p th:text="${introText}">Thank you for contacting us. Our team will reply as soon as possible.</p>
|
||||
<p>
|
||||
<strong th:text="${requestIdHintText}">Please keep this request ID for future order references:</strong>
|
||||
<span th:text="${requestId}">00000000-0000-0000-0000-000000000000</span>
|
||||
</p>
|
||||
<h2 th:text="${detailsTitleText}">Request details</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th th:text="${labelRequestId}">Request ID</th>
|
||||
<td th:text="${requestId}">00000000-0000-0000-0000-000000000000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelDate}">Date</th>
|
||||
<td th:text="${createdAt}">2026-03-03T10:00:00Z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelRequestType}">Request type</th>
|
||||
<td th:text="${requestType}">custom</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelCustomerType}">Customer type</th>
|
||||
<td th:text="${customerType}">PRIVATE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelName}">Name</th>
|
||||
<td th:text="${name}">Mario Rossi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelCompany}">Company</th>
|
||||
<td th:text="${companyName}">3D Fab SA</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelContactPerson}">Contact person</th>
|
||||
<td th:text="${contactPerson}">Mario Rossi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelEmail}">Email</th>
|
||||
<td th:text="${email}">cliente@example.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelPhone}">Phone</th>
|
||||
<td th:text="${phone}">+41 00 000 00 00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelMessage}">Message</th>
|
||||
<td th:text="${message}">Testo richiesta cliente...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th th:text="${labelAttachments}">Attachments</th>
|
||||
<td th:text="${attachmentsCount}">0</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p th:text="${supportText}">If you need help, reply to this email.</p>
|
||||
|
||||
<div class="footer">
|
||||
<p>© <span th:text="${currentYear}">2026</span> <span th:text="${footerText}">Automated request-receipt confirmation from 3D-Fab.</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user