produzione 1 #9
@@ -33,9 +33,22 @@ public class DevEmailTestController {
|
||||
templateData.put("customerName", "Mario Rossi");
|
||||
templateData.put("orderId", orderId);
|
||||
templateData.put("orderNumber", orderId.toString().split("-")[0]);
|
||||
templateData.put("orderDetailsUrl", "https://tuosito.it/ordine/" + orderId);
|
||||
templateData.put("orderDetailsUrl", "https://tuosito.it/it/co/" + orderId);
|
||||
templateData.put("orderDate", OffsetDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")));
|
||||
templateData.put("totalCost", "45.50");
|
||||
templateData.put("totalCost", "CHF 45.50");
|
||||
templateData.put("currentYear", OffsetDateTime.now().getYear());
|
||||
templateData.put("emailTitle", "Conferma ordine");
|
||||
templateData.put("headlineText", "Grazie per il tuo ordine #" + templateData.get("orderNumber"));
|
||||
templateData.put("greetingText", "Ciao Mario,");
|
||||
templateData.put("introText", "Abbiamo ricevuto il tuo ordine e iniziato l'elaborazione.");
|
||||
templateData.put("detailsTitleText", "Dettagli ordine");
|
||||
templateData.put("labelOrderNumber", "Numero ordine");
|
||||
templateData.put("labelDate", "Data");
|
||||
templateData.put("labelTotal", "Totale");
|
||||
templateData.put("orderDetailsCtaText", "Visualizza stato ordine");
|
||||
templateData.put("attachmentHintText", "In allegato trovi la conferma ordine in PDF con QR bill.");
|
||||
templateData.put("supportText", "Se hai domande, rispondi a questa email.");
|
||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
||||
|
||||
context.setVariables(templateData);
|
||||
String html = templateEngine.process("email/order-confirmation", context);
|
||||
|
||||
@@ -157,6 +157,19 @@ public class OrderController {
|
||||
Order order = orderRepo.findById(orderId)
|
||||
.orElseThrow(() -> new RuntimeException("Order not found"));
|
||||
|
||||
if (isConfirmation) {
|
||||
String relativePath = buildConfirmationPdfRelativePath(order);
|
||||
try {
|
||||
byte[] existingPdf = storageService.loadAsResource(Paths.get(relativePath)).getInputStream().readAllBytes();
|
||||
return ResponseEntity.ok()
|
||||
.header("Content-Disposition", "attachment; filename=\"confirmation-" + getDisplayOrderNumber(order) + ".pdf\"")
|
||||
.contentType(MediaType.APPLICATION_PDF)
|
||||
.body(existingPdf);
|
||||
} catch (Exception ignored) {
|
||||
// Fallback to on-the-fly generation if the stored file is missing or unreadable.
|
||||
}
|
||||
}
|
||||
|
||||
List<OrderItem> items = orderItemRepo.findByOrder_Id(orderId);
|
||||
Payment payment = paymentRepo.findByOrder_Id(orderId).orElse(null);
|
||||
|
||||
@@ -169,6 +182,10 @@ public class OrderController {
|
||||
.body(pdf);
|
||||
}
|
||||
|
||||
private String buildConfirmationPdfRelativePath(Order order) {
|
||||
return "orders/" + order.getId() + "/documents/confirmation-" + getDisplayOrderNumber(order) + ".pdf";
|
||||
}
|
||||
|
||||
@GetMapping("/{orderId}/twint")
|
||||
public ResponseEntity<Map<String, String>> getTwintPayment(@PathVariable UUID orderId) {
|
||||
Order order = orderRepo.findById(orderId).orElse(null);
|
||||
@@ -239,6 +256,7 @@ public class OrderController {
|
||||
|
||||
dto.setCustomerEmail(order.getCustomerEmail());
|
||||
dto.setCustomerPhone(order.getCustomerPhone());
|
||||
dto.setPreferredLanguage(order.getPreferredLanguage());
|
||||
dto.setBillingCustomerType(order.getBillingCustomerType());
|
||||
dto.setCurrency(order.getCurrency());
|
||||
dto.setSetupCostChf(order.getSetupCostChf());
|
||||
|
||||
@@ -8,6 +8,7 @@ public class CreateOrderRequest {
|
||||
private CustomerDto customer;
|
||||
private AddressDto billingAddress;
|
||||
private AddressDto shippingAddress;
|
||||
private String language;
|
||||
private boolean shippingSameAsBilling;
|
||||
|
||||
@AssertTrue(message = "L'accettazione dei Termini e Condizioni e obbligatoria.")
|
||||
|
||||
@@ -13,6 +13,7 @@ public class OrderDto {
|
||||
private String paymentMethod;
|
||||
private String customerEmail;
|
||||
private String customerPhone;
|
||||
private String preferredLanguage;
|
||||
private String billingCustomerType;
|
||||
private AddressDto billingAddress;
|
||||
private AddressDto shippingAddress;
|
||||
@@ -48,6 +49,9 @@ public class OrderDto {
|
||||
public String getCustomerPhone() { return customerPhone; }
|
||||
public void setCustomerPhone(String customerPhone) { this.customerPhone = customerPhone; }
|
||||
|
||||
public String getPreferredLanguage() { return preferredLanguage; }
|
||||
public void setPreferredLanguage(String preferredLanguage) { this.preferredLanguage = preferredLanguage; }
|
||||
|
||||
public String getBillingCustomerType() { return billingCustomerType; }
|
||||
public void setBillingCustomerType(String billingCustomerType) { this.billingCustomerType = billingCustomerType; }
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ public class Order {
|
||||
@Column(name = "shipping_country_code", length = 2)
|
||||
private String shippingCountryCode;
|
||||
|
||||
@ColumnDefault("'it'")
|
||||
@Column(name = "preferred_language", length = 2)
|
||||
private String preferredLanguage;
|
||||
|
||||
@ColumnDefault("'CHF'")
|
||||
@Column(name = "currency", nullable = false, length = 3)
|
||||
private String currency;
|
||||
@@ -356,6 +360,14 @@ public class Order {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public String getPreferredLanguage() {
|
||||
return preferredLanguage;
|
||||
}
|
||||
|
||||
public void setPreferredLanguage(String preferredLanguage) {
|
||||
this.preferredLanguage = preferredLanguage;
|
||||
}
|
||||
|
||||
public BigDecimal getSetupCostChf() {
|
||||
return setupCostChf;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import com.printcalculator.entity.Order;
|
||||
import com.printcalculator.entity.OrderItem;
|
||||
import com.printcalculator.entity.Payment;
|
||||
import com.printcalculator.event.OrderCreatedEvent;
|
||||
import com.printcalculator.event.PaymentReportedEvent;
|
||||
import com.printcalculator.event.PaymentConfirmedEvent;
|
||||
import com.printcalculator.service.email.EmailNotificationService;
|
||||
import com.printcalculator.event.PaymentReportedEvent;
|
||||
import com.printcalculator.repository.OrderItemRepository;
|
||||
import com.printcalculator.service.InvoicePdfRenderingService;
|
||||
import com.printcalculator.service.QrBillService;
|
||||
import com.printcalculator.repository.OrderItemRepository;
|
||||
import com.printcalculator.service.StorageService;
|
||||
import com.printcalculator.service.email.EmailNotificationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -17,19 +18,29 @@ import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.time.Year;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Currency;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OrderEmailListener {
|
||||
|
||||
private static final String DEFAULT_LANGUAGE = "it";
|
||||
|
||||
private final EmailNotificationService emailNotificationService;
|
||||
private final InvoicePdfRenderingService invoicePdfRenderingService;
|
||||
private final OrderItemRepository orderItemRepository;
|
||||
private final QrBillService qrBillService;
|
||||
private final StorageService storageService;
|
||||
|
||||
@Value("${app.mail.admin.enabled:true}")
|
||||
private boolean adminMailEnabled;
|
||||
@@ -85,83 +96,291 @@ public class OrderEmailListener {
|
||||
}
|
||||
|
||||
private void sendCustomerConfirmationEmail(Order order) {
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
templateData.put("customerName", order.getCustomer().getFirstName());
|
||||
templateData.put("orderId", order.getId());
|
||||
templateData.put("orderNumber", getDisplayOrderNumber(order));
|
||||
templateData.put("orderDetailsUrl", buildOrderDetailsUrl(order));
|
||||
templateData.put("orderDate", order.getCreatedAt().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")));
|
||||
templateData.put("totalCost", String.format("%.2f", order.getTotalChf()));
|
||||
String language = resolveLanguage(order.getPreferredLanguage());
|
||||
String orderNumber = getDisplayOrderNumber(order);
|
||||
|
||||
emailNotificationService.sendEmail(
|
||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
||||
String subject = applyOrderConfirmationTexts(templateData, language, orderNumber);
|
||||
byte[] confirmationPdf = loadOrGenerateConfirmationPdf(order);
|
||||
|
||||
emailNotificationService.sendEmailWithAttachment(
|
||||
order.getCustomer().getEmail(),
|
||||
"Conferma Ordine #" + getDisplayOrderNumber(order) + " - 3D-Fab",
|
||||
subject,
|
||||
"order-confirmation",
|
||||
templateData
|
||||
templateData,
|
||||
buildConfirmationAttachmentName(language, orderNumber),
|
||||
confirmationPdf
|
||||
);
|
||||
}
|
||||
|
||||
private void sendPaymentReportedEmail(Order order) {
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
templateData.put("customerName", order.getCustomer().getFirstName());
|
||||
templateData.put("orderId", order.getId());
|
||||
templateData.put("orderNumber", getDisplayOrderNumber(order));
|
||||
templateData.put("orderDetailsUrl", buildOrderDetailsUrl(order));
|
||||
String language = resolveLanguage(order.getPreferredLanguage());
|
||||
String orderNumber = getDisplayOrderNumber(order);
|
||||
|
||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
||||
String subject = applyPaymentReportedTexts(templateData, language, orderNumber);
|
||||
|
||||
emailNotificationService.sendEmail(
|
||||
order.getCustomer().getEmail(),
|
||||
"Stiamo verificando il tuo pagamento (Ordine #" + getDisplayOrderNumber(order) + ")",
|
||||
subject,
|
||||
"payment-reported",
|
||||
templateData
|
||||
);
|
||||
}
|
||||
|
||||
private void sendPaidInvoiceEmail(Order order, Payment payment) {
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
templateData.put("customerName", order.getCustomer().getFirstName());
|
||||
templateData.put("orderId", order.getId());
|
||||
templateData.put("orderNumber", getDisplayOrderNumber(order));
|
||||
templateData.put("orderDetailsUrl", buildOrderDetailsUrl(order));
|
||||
templateData.put("totalCost", String.format("%.2f", order.getTotalChf()));
|
||||
String language = resolveLanguage(order.getPreferredLanguage());
|
||||
String orderNumber = getDisplayOrderNumber(order);
|
||||
|
||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
||||
String subject = applyPaymentConfirmedTexts(templateData, language, orderNumber);
|
||||
|
||||
byte[] pdf = null;
|
||||
try {
|
||||
java.util.List<OrderItem> items = orderItemRepository.findByOrder_Id(order.getId());
|
||||
List<OrderItem> items = orderItemRepository.findByOrder_Id(order.getId());
|
||||
pdf = invoicePdfRenderingService.generateDocumentPdf(order, items, false, qrBillService, payment);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to generate PDF for paid invoice email: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
String filename = "Fattura-" + getDisplayOrderNumber(order) + ".pdf";
|
||||
|
||||
emailNotificationService.sendEmailWithAttachment(
|
||||
order.getCustomer().getEmail(),
|
||||
"Fattura Pagata (Ordine #" + getDisplayOrderNumber(order) + ") - 3D-Fab",
|
||||
subject,
|
||||
"payment-confirmed",
|
||||
templateData,
|
||||
filename,
|
||||
buildPaidInvoiceAttachmentName(language, orderNumber),
|
||||
pdf
|
||||
);
|
||||
}
|
||||
|
||||
private void sendAdminNotificationEmail(Order order) {
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
templateData.put("customerName", order.getCustomer().getFirstName() + " " + order.getCustomer().getLastName());
|
||||
templateData.put("orderId", order.getId());
|
||||
templateData.put("orderNumber", getDisplayOrderNumber(order));
|
||||
templateData.put("orderDetailsUrl", buildOrderDetailsUrl(order));
|
||||
templateData.put("orderDate", order.getCreatedAt().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")));
|
||||
templateData.put("totalCost", String.format("%.2f", order.getTotalChf()));
|
||||
String orderNumber = getDisplayOrderNumber(order);
|
||||
Map<String, Object> templateData = buildBaseTemplateData(order, DEFAULT_LANGUAGE);
|
||||
templateData.put("customerName", buildCustomerFullName(order));
|
||||
|
||||
templateData.put("emailTitle", "Nuovo ordine ricevuto");
|
||||
templateData.put("headlineText", "Nuovo ordine #" + orderNumber);
|
||||
templateData.put("greetingText", "Ciao team,");
|
||||
templateData.put("introText", "Un nuovo ordine e' stato creato dal cliente.");
|
||||
templateData.put("detailsTitleText", "Dettagli ordine");
|
||||
templateData.put("labelOrderNumber", "Numero ordine");
|
||||
templateData.put("labelDate", "Data");
|
||||
templateData.put("labelTotal", "Totale");
|
||||
templateData.put("orderDetailsCtaText", "Apri dettaglio ordine");
|
||||
templateData.put("attachmentHintText", "La conferma cliente e il QR bill sono stati salvati nella cartella documenti dell'ordine.");
|
||||
templateData.put("supportText", "Controlla i dettagli e procedi con la gestione operativa.");
|
||||
templateData.put("footerText", "Notifica automatica sistema ordini.");
|
||||
|
||||
// Possiamo riutilizzare lo stesso template per ora o crearne uno ad-hoc in futuro
|
||||
emailNotificationService.sendEmail(
|
||||
adminMailAddress,
|
||||
"Nuovo Ordine Ricevuto #" + getDisplayOrderNumber(order) + " - " + order.getCustomer().getLastName(),
|
||||
"Nuovo Ordine Ricevuto #" + orderNumber + " - " + buildCustomerFullName(order),
|
||||
"order-confirmation",
|
||||
templateData
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildBaseTemplateData(Order order, String language) {
|
||||
Locale locale = localeForLanguage(language);
|
||||
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale);
|
||||
currencyFormatter.setCurrency(Currency.getInstance("CHF"));
|
||||
|
||||
Map<String, Object> templateData = new HashMap<>();
|
||||
templateData.put("customerName", buildCustomerFirstName(order, language));
|
||||
templateData.put("orderId", order.getId());
|
||||
templateData.put("orderNumber", getDisplayOrderNumber(order));
|
||||
templateData.put("orderDetailsUrl", buildOrderDetailsUrl(order, language));
|
||||
templateData.put(
|
||||
"orderDate",
|
||||
order.getCreatedAt().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(locale))
|
||||
);
|
||||
templateData.put("totalCost", currencyFormatter.format(order.getTotalChf()));
|
||||
templateData.put("currentYear", Year.now().getValue());
|
||||
return templateData;
|
||||
}
|
||||
|
||||
private String applyOrderConfirmationTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
||||
return switch (language) {
|
||||
case "en" -> {
|
||||
templateData.put("emailTitle", "Order Confirmation");
|
||||
templateData.put("headlineText", "Thank you for your order #" + orderNumber);
|
||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "We received your order and started processing it.");
|
||||
templateData.put("detailsTitleText", "Order details");
|
||||
templateData.put("labelOrderNumber", "Order number");
|
||||
templateData.put("labelDate", "Date");
|
||||
templateData.put("labelTotal", "Total");
|
||||
templateData.put("orderDetailsCtaText", "View order status");
|
||||
templateData.put("attachmentHintText", "Attached you can find the order confirmation PDF with the QR bill.");
|
||||
templateData.put("supportText", "If you have questions, reply to this email and we will help you.");
|
||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
||||
yield "Order Confirmation #" + orderNumber + " - 3D-Fab";
|
||||
}
|
||||
case "de" -> {
|
||||
templateData.put("emailTitle", "Bestellbestaetigung");
|
||||
templateData.put("headlineText", "Danke fuer Ihre Bestellung #" + orderNumber);
|
||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Wir haben Ihre Bestellung erhalten und mit der Bearbeitung begonnen.");
|
||||
templateData.put("detailsTitleText", "Bestelldetails");
|
||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
||||
templateData.put("labelDate", "Datum");
|
||||
templateData.put("labelTotal", "Gesamtbetrag");
|
||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
||||
templateData.put("attachmentHintText", "Im Anhang finden Sie die Bestellbestaetigung mit QR-Rechnung.");
|
||||
templateData.put("supportText", "Bei Fragen antworten Sie einfach auf diese E-Mail.");
|
||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
||||
yield "Bestellbestaetigung #" + orderNumber + " - 3D-Fab";
|
||||
}
|
||||
case "fr" -> {
|
||||
templateData.put("emailTitle", "Confirmation de commande");
|
||||
templateData.put("headlineText", "Merci pour votre commande #" + orderNumber);
|
||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Nous avons recu votre commande et commence son traitement.");
|
||||
templateData.put("detailsTitleText", "Details de commande");
|
||||
templateData.put("labelOrderNumber", "Numero de commande");
|
||||
templateData.put("labelDate", "Date");
|
||||
templateData.put("labelTotal", "Total");
|
||||
templateData.put("orderDetailsCtaText", "Voir le statut de la commande");
|
||||
templateData.put("attachmentHintText", "Vous trouverez en piece jointe la confirmation de commande avec la facture QR.");
|
||||
templateData.put("supportText", "Si vous avez des questions, repondez a cet email.");
|
||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
||||
yield "Confirmation de commande #" + orderNumber + " - 3D-Fab";
|
||||
}
|
||||
default -> {
|
||||
templateData.put("emailTitle", "Conferma ordine");
|
||||
templateData.put("headlineText", "Grazie per il tuo ordine #" + orderNumber);
|
||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Abbiamo ricevuto il tuo ordine e iniziato l'elaborazione.");
|
||||
templateData.put("detailsTitleText", "Dettagli ordine");
|
||||
templateData.put("labelOrderNumber", "Numero ordine");
|
||||
templateData.put("labelDate", "Data");
|
||||
templateData.put("labelTotal", "Totale");
|
||||
templateData.put("orderDetailsCtaText", "Visualizza stato ordine");
|
||||
templateData.put("attachmentHintText", "In allegato trovi la conferma ordine in PDF con QR bill.");
|
||||
templateData.put("supportText", "Se hai domande, rispondi a questa email e ti aiutiamo subito.");
|
||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
||||
yield "Conferma Ordine #" + orderNumber + " - 3D-Fab";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String applyPaymentReportedTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
||||
return switch (language) {
|
||||
case "en" -> {
|
||||
templateData.put("emailTitle", "Payment Reported");
|
||||
templateData.put("headlineText", "Payment reported for order #" + orderNumber);
|
||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "We received your payment report and our team is now verifying it.");
|
||||
templateData.put("statusText", "Current status: Payment under verification.");
|
||||
templateData.put("orderDetailsCtaText", "Check order status");
|
||||
templateData.put("supportText", "You will receive another email as soon as the payment is confirmed.");
|
||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Order number");
|
||||
templateData.put("labelTotal", "Total");
|
||||
yield "We are verifying your payment (Order #" + orderNumber + ")";
|
||||
}
|
||||
case "de" -> {
|
||||
templateData.put("emailTitle", "Zahlung gemeldet");
|
||||
templateData.put("headlineText", "Zahlung fuer Bestellung #" + orderNumber + " gemeldet");
|
||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Wir haben Ihre Zahlungsmitteilung erhalten und pruefen sie aktuell.");
|
||||
templateData.put("statusText", "Aktueller Status: Zahlung in Pruefung.");
|
||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
||||
templateData.put("supportText", "Sobald die Zahlung bestaetigt ist, erhalten Sie eine weitere E-Mail.");
|
||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
||||
templateData.put("labelTotal", "Gesamtbetrag");
|
||||
yield "Wir pruefen Ihre Zahlung (Bestellung #" + orderNumber + ")";
|
||||
}
|
||||
case "fr" -> {
|
||||
templateData.put("emailTitle", "Paiement signale");
|
||||
templateData.put("headlineText", "Paiement signale pour la commande #" + orderNumber);
|
||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Nous avons recu votre signalement de paiement et nous le verifions.");
|
||||
templateData.put("statusText", "Statut actuel: Paiement en verification.");
|
||||
templateData.put("orderDetailsCtaText", "Consulter le statut de la commande");
|
||||
templateData.put("supportText", "Vous recevrez un nouvel email des que le paiement sera confirme.");
|
||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Numero de commande");
|
||||
templateData.put("labelTotal", "Total");
|
||||
yield "Nous verifions votre paiement (Commande #" + orderNumber + ")";
|
||||
}
|
||||
default -> {
|
||||
templateData.put("emailTitle", "Pagamento segnalato");
|
||||
templateData.put("headlineText", "Pagamento segnalato per ordine #" + orderNumber);
|
||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Abbiamo ricevuto la tua segnalazione di pagamento e la stiamo verificando.");
|
||||
templateData.put("statusText", "Stato attuale: pagamento in verifica.");
|
||||
templateData.put("orderDetailsCtaText", "Controlla lo stato ordine");
|
||||
templateData.put("supportText", "Riceverai una nuova email non appena il pagamento sara' confermato.");
|
||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Numero ordine");
|
||||
templateData.put("labelTotal", "Totale");
|
||||
yield "Stiamo verificando il tuo pagamento (Ordine #" + orderNumber + ")";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String applyPaymentConfirmedTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
||||
return switch (language) {
|
||||
case "en" -> {
|
||||
templateData.put("emailTitle", "Payment Confirmed");
|
||||
templateData.put("headlineText", "Payment confirmed for order #" + orderNumber);
|
||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Your payment has been confirmed and the order moved into production.");
|
||||
templateData.put("statusText", "Current status: In production.");
|
||||
templateData.put("attachmentHintText", "The paid invoice PDF is attached to this email.");
|
||||
templateData.put("orderDetailsCtaText", "View order status");
|
||||
templateData.put("supportText", "We will notify you again when the shipment is ready.");
|
||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Order number");
|
||||
templateData.put("labelTotal", "Total");
|
||||
yield "Payment confirmed (Order #" + orderNumber + ") - 3D-Fab";
|
||||
}
|
||||
case "de" -> {
|
||||
templateData.put("emailTitle", "Zahlung bestaetigt");
|
||||
templateData.put("headlineText", "Zahlung fuer Bestellung #" + orderNumber + " bestaetigt");
|
||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Ihre Zahlung wurde bestaetigt und die Bestellung ist jetzt in Produktion.");
|
||||
templateData.put("statusText", "Aktueller Status: In Produktion.");
|
||||
templateData.put("attachmentHintText", "Die bezahlte Rechnung als PDF ist dieser E-Mail beigefuegt.");
|
||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
||||
templateData.put("supportText", "Wir informieren Sie erneut, sobald der Versand bereit ist.");
|
||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
||||
templateData.put("labelTotal", "Gesamtbetrag");
|
||||
yield "Zahlung bestaetigt (Bestellung #" + orderNumber + ") - 3D-Fab";
|
||||
}
|
||||
case "fr" -> {
|
||||
templateData.put("emailTitle", "Paiement confirme");
|
||||
templateData.put("headlineText", "Paiement confirme pour la commande #" + orderNumber);
|
||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Votre paiement est confirme et la commande est passe en production.");
|
||||
templateData.put("statusText", "Statut actuel: En production.");
|
||||
templateData.put("attachmentHintText", "La facture payee en PDF est jointe a cet email.");
|
||||
templateData.put("orderDetailsCtaText", "Voir le statut de la commande");
|
||||
templateData.put("supportText", "Nous vous informerons a nouveau des que l'expedition sera prete.");
|
||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Numero de commande");
|
||||
templateData.put("labelTotal", "Total");
|
||||
yield "Paiement confirme (Commande #" + orderNumber + ") - 3D-Fab";
|
||||
}
|
||||
default -> {
|
||||
templateData.put("emailTitle", "Pagamento confermato");
|
||||
templateData.put("headlineText", "Pagamento confermato per ordine #" + orderNumber);
|
||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
||||
templateData.put("introText", "Il tuo pagamento e' stato confermato e l'ordine e' entrato in produzione.");
|
||||
templateData.put("statusText", "Stato attuale: in produzione.");
|
||||
templateData.put("attachmentHintText", "In allegato trovi la fattura saldata in PDF.");
|
||||
templateData.put("orderDetailsCtaText", "Visualizza stato ordine");
|
||||
templateData.put("supportText", "Ti aggiorneremo di nuovo quando la spedizione sara' pronta.");
|
||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
||||
templateData.put("labelOrderNumber", "Numero ordine");
|
||||
templateData.put("labelTotal", "Totale");
|
||||
yield "Pagamento confermato (Ordine #" + orderNumber + ") - 3D-Fab";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getDisplayOrderNumber(Order order) {
|
||||
String orderNumber = order.getOrderNumber();
|
||||
if (orderNumber != null && !orderNumber.isBlank()) {
|
||||
@@ -170,8 +389,108 @@ public class OrderEmailListener {
|
||||
return order.getId() != null ? order.getId().toString() : "unknown";
|
||||
}
|
||||
|
||||
private String buildOrderDetailsUrl(Order order) {
|
||||
private String buildOrderDetailsUrl(Order order, String language) {
|
||||
String baseUrl = frontendBaseUrl == null ? "" : frontendBaseUrl.replaceAll("/+$", "");
|
||||
return baseUrl + "/co/" + order.getId();
|
||||
return baseUrl + "/" + language + "/co/" + order.getId();
|
||||
}
|
||||
|
||||
private String buildConfirmationAttachmentName(String language, String orderNumber) {
|
||||
return switch (language) {
|
||||
case "en" -> "Order-Confirmation-" + orderNumber + ".pdf";
|
||||
case "de" -> "Bestellbestaetigung-" + orderNumber + ".pdf";
|
||||
case "fr" -> "Confirmation-Commande-" + orderNumber + ".pdf";
|
||||
default -> "Conferma-Ordine-" + orderNumber + ".pdf";
|
||||
};
|
||||
}
|
||||
|
||||
private String buildPaidInvoiceAttachmentName(String language, String orderNumber) {
|
||||
return switch (language) {
|
||||
case "en" -> "Paid-Invoice-" + orderNumber + ".pdf";
|
||||
case "de" -> "Bezahlte-Rechnung-" + orderNumber + ".pdf";
|
||||
case "fr" -> "Facture-Payee-" + orderNumber + ".pdf";
|
||||
default -> "Fattura-Pagata-" + orderNumber + ".pdf";
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] loadOrGenerateConfirmationPdf(Order order) {
|
||||
byte[] stored = loadStoredConfirmationPdf(order);
|
||||
if (stored != null) {
|
||||
return stored;
|
||||
}
|
||||
|
||||
try {
|
||||
List<OrderItem> items = orderItemRepository.findByOrder_Id(order.getId());
|
||||
return invoicePdfRenderingService.generateDocumentPdf(order, items, true, qrBillService, null);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to generate fallback confirmation PDF for order id: {}", order.getId(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] loadStoredConfirmationPdf(Order order) {
|
||||
String relativePath = buildConfirmationPdfRelativePath(order);
|
||||
try {
|
||||
return storageService.loadAsResource(Paths.get(relativePath)).getInputStream().readAllBytes();
|
||||
} catch (Exception e) {
|
||||
log.warn("Confirmation PDF not found for order id {} at {}", order.getId(), relativePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildConfirmationPdfRelativePath(Order order) {
|
||||
return "orders/" + order.getId() + "/documents/confirmation-" + getDisplayOrderNumber(order) + ".pdf";
|
||||
}
|
||||
|
||||
private String buildCustomerFirstName(Order order, String language) {
|
||||
if (order.getCustomer() != null && order.getCustomer().getFirstName() != null && !order.getCustomer().getFirstName().isBlank()) {
|
||||
return order.getCustomer().getFirstName();
|
||||
}
|
||||
if (order.getBillingFirstName() != null && !order.getBillingFirstName().isBlank()) {
|
||||
return order.getBillingFirstName();
|
||||
}
|
||||
return switch (language) {
|
||||
case "en" -> "Customer";
|
||||
case "de" -> "Kunde";
|
||||
case "fr" -> "Client";
|
||||
default -> "Cliente";
|
||||
};
|
||||
}
|
||||
|
||||
private String buildCustomerFullName(Order order) {
|
||||
String firstName = order.getCustomer() != null ? order.getCustomer().getFirstName() : null;
|
||||
String lastName = order.getCustomer() != null ? order.getCustomer().getLastName() : null;
|
||||
if (firstName != null && !firstName.isBlank() && lastName != null && !lastName.isBlank()) {
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
if (order.getBillingFirstName() != null && !order.getBillingFirstName().isBlank()
|
||||
&& order.getBillingLastName() != null && !order.getBillingLastName().isBlank()) {
|
||||
return order.getBillingFirstName() + " " + order.getBillingLastName();
|
||||
}
|
||||
return "Cliente";
|
||||
}
|
||||
|
||||
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 resolveLanguage(String language) {
|
||||
if (language == null || language.isBlank()) {
|
||||
return DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
String normalized = language.trim().toLowerCase(Locale.ROOT);
|
||||
if (normalized.length() > 2) {
|
||||
normalized = normalized.substring(0, 2);
|
||||
}
|
||||
|
||||
return switch (normalized) {
|
||||
case "it", "en", "de", "fr" -> normalized;
|
||||
default -> DEFAULT_LANGUAGE;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.printcalculator.service;
|
||||
|
||||
import com.printcalculator.dto.AddressDto;
|
||||
import com.printcalculator.dto.CreateOrderRequest;
|
||||
import com.printcalculator.entity.*;
|
||||
import com.printcalculator.entity.Payment;
|
||||
import com.printcalculator.repository.CustomerRepository;
|
||||
import com.printcalculator.repository.OrderItemRepository;
|
||||
import com.printcalculator.repository.OrderRepository;
|
||||
@@ -18,14 +16,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class OrderService {
|
||||
@@ -113,6 +108,7 @@ public class OrderService {
|
||||
order.setStatus("PENDING_PAYMENT");
|
||||
order.setCreatedAt(OffsetDateTime.now());
|
||||
order.setUpdatedAt(OffsetDateTime.now());
|
||||
order.setPreferredLanguage(normalizeLanguage(request.getLanguage()));
|
||||
order.setCurrency("CHF");
|
||||
|
||||
order.setBillingCustomerType(request.getCustomer().getCustomerType());
|
||||
@@ -281,75 +277,13 @@ public class OrderService {
|
||||
|
||||
private void generateAndSaveDocuments(Order order, List<OrderItem> items) {
|
||||
try {
|
||||
// 1. Generate QR Bill
|
||||
// 1. Generate and save the raw QR Bill for internal traceability.
|
||||
byte[] qrBillSvgBytes = qrBillService.generateQrBillSvg(order);
|
||||
String qrBillSvg = new String(qrBillSvgBytes, StandardCharsets.UTF_8);
|
||||
saveFileBytes(qrBillSvgBytes, buildQrBillSvgRelativePath(order));
|
||||
|
||||
// Strip XML declaration and DOCTYPE if present, as they validity break the embedding HTML page
|
||||
if (qrBillSvg.contains("<?xml")) {
|
||||
int svgStartIndex = qrBillSvg.indexOf("<svg");
|
||||
if (svgStartIndex != -1) {
|
||||
qrBillSvg = qrBillSvg.substring(svgStartIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Save QR Bill SVG
|
||||
String qrRelativePath = "orders/" + order.getId() + "/documents/qr-bill.svg";
|
||||
saveFileBytes(qrBillSvgBytes, qrRelativePath);
|
||||
|
||||
// 2. Prepare Invoice Variables
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
vars.put("sellerDisplayName", "3D Fab Switzerland");
|
||||
vars.put("sellerAddressLine1", "Sede Ticino, Svizzera");
|
||||
vars.put("sellerAddressLine2", "Sede Bienne, Svizzera");
|
||||
vars.put("sellerEmail", "info@3dfab.ch");
|
||||
|
||||
vars.put("invoiceNumber", "INV-" + getDisplayOrderNumber(order).toUpperCase());
|
||||
vars.put("invoiceDate", order.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
vars.put("dueDate", order.getCreatedAt().plusDays(7).format(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
|
||||
String buyerName = "BUSINESS".equals(order.getBillingCustomerType())
|
||||
? order.getBillingCompanyName()
|
||||
: order.getBillingFirstName() + " " + order.getBillingLastName();
|
||||
vars.put("buyerDisplayName", buyerName);
|
||||
vars.put("buyerAddressLine1", order.getBillingAddressLine1());
|
||||
vars.put("buyerAddressLine2", order.getBillingZip() + " " + order.getBillingCity() + ", " + order.getBillingCountryCode());
|
||||
|
||||
List<Map<String, Object>> invoiceLineItems = items.stream().map(i -> {
|
||||
Map<String, Object> line = new HashMap<>();
|
||||
line.put("description", "Stampa 3D: " + i.getOriginalFilename());
|
||||
line.put("quantity", i.getQuantity());
|
||||
line.put("unitPriceFormatted", String.format("CHF %.2f", i.getUnitPriceChf()));
|
||||
line.put("lineTotalFormatted", String.format("CHF %.2f", i.getLineTotalChf()));
|
||||
return line;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> setupLine = new HashMap<>();
|
||||
setupLine.put("description", "Costo Setup");
|
||||
setupLine.put("quantity", 1);
|
||||
setupLine.put("unitPriceFormatted", String.format("CHF %.2f", order.getSetupCostChf()));
|
||||
setupLine.put("lineTotalFormatted", String.format("CHF %.2f", order.getSetupCostChf()));
|
||||
invoiceLineItems.add(setupLine);
|
||||
|
||||
Map<String, Object> shippingLine = new HashMap<>();
|
||||
shippingLine.put("description", "Spedizione");
|
||||
shippingLine.put("quantity", 1);
|
||||
shippingLine.put("unitPriceFormatted", String.format("CHF %.2f", order.getShippingCostChf()));
|
||||
shippingLine.put("lineTotalFormatted", String.format("CHF %.2f", order.getShippingCostChf()));
|
||||
invoiceLineItems.add(shippingLine);
|
||||
|
||||
vars.put("invoiceLineItems", invoiceLineItems);
|
||||
vars.put("subtotalFormatted", String.format("CHF %.2f", order.getSubtotalChf()));
|
||||
vars.put("grandTotalFormatted", String.format("CHF %.2f", order.getTotalChf()));
|
||||
vars.put("paymentTermsText", "Appena riceviamo il pagamento l'ordine entrerà nella coda di stampa. Grazie per la fiducia");
|
||||
|
||||
// 3. Generate PDF
|
||||
Payment payment = null; // New order, payment not received yet
|
||||
byte[] pdfBytes = invoiceService.generateInvoicePdfBytesFromTemplate(vars, qrBillSvg);
|
||||
|
||||
// Save PDF
|
||||
String pdfRelativePath = "orders/" + order.getId() + "/documents/invoice-" + order.getId() + ".pdf";
|
||||
saveFileBytes(pdfBytes, pdfRelativePath);
|
||||
// 2. Generate and save the same confirmation PDF served by /api/orders/{id}/confirmation.
|
||||
byte[] confirmationPdfBytes = invoiceService.generateDocumentPdf(order, items, true, qrBillService, null);
|
||||
saveFileBytes(confirmationPdfBytes, buildConfirmationPdfRelativePath(order));
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -387,4 +321,28 @@ public class OrderService {
|
||||
}
|
||||
return order.getId() != null ? order.getId().toString() : "unknown";
|
||||
}
|
||||
|
||||
private String buildQrBillSvgRelativePath(Order order) {
|
||||
return "orders/" + order.getId() + "/documents/qr-bill.svg";
|
||||
}
|
||||
|
||||
private String buildConfirmationPdfRelativePath(Order order) {
|
||||
return "orders/" + order.getId() + "/documents/confirmation-" + getDisplayOrderNumber(order) + ".pdf";
|
||||
}
|
||||
|
||||
private String normalizeLanguage(String language) {
|
||||
if (language == null || language.isBlank()) {
|
||||
return "it";
|
||||
}
|
||||
|
||||
String normalized = language.trim().toLowerCase(Locale.ROOT);
|
||||
if (normalized.length() > 2) {
|
||||
normalized = normalized.substring(0, 2);
|
||||
}
|
||||
|
||||
return switch (normalized) {
|
||||
case "it", "en", "de", "fr" -> normalized;
|
||||
default -> "it";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Conferma Ordine</title>
|
||||
<title th:text="${emailTitle}">Order Confirmation</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
@@ -10,6 +10,7 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 20px auto;
|
||||
@@ -18,30 +19,41 @@
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: #555555;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.order-details {
|
||||
background-color: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.order-details th {
|
||||
text-align: left;
|
||||
padding-right: 20px;
|
||||
color: #333333;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.order-details td {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
@@ -55,38 +67,43 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Grazie per il tuo ordine #<span th:text="${orderNumber}">00000000</span></h1>
|
||||
<h1 th:text="${headlineText}">Thank you for your order #00000000</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>Ciao <span th:text="${customerName}">Cliente</span>,</p>
|
||||
<p>Abbiamo ricevuto il tuo ordine e stiamo iniziando a elaborarlo. Ecco un riepilogo dei dettagli:</p>
|
||||
<p th:text="${greetingText}">Hi Customer,</p>
|
||||
<p th:text="${introText}">We received your order and started processing it.</p>
|
||||
|
||||
<div class="order-details">
|
||||
<p style="margin-top:0; font-weight: bold;" th:text="${detailsTitleText}">Order details</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Numero Ordine:</th>
|
||||
<th th:text="${labelOrderNumber}">Order number</th>
|
||||
<td th:text="${orderNumber}">00000000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Data:</th>
|
||||
<td th:text="${orderDate}">01/01/2026</td>
|
||||
<th th:text="${labelDate}">Date</th>
|
||||
<td th:text="${orderDate}">Jan 1, 2026, 10:00:00 AM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Costo totale:</th>
|
||||
<td th:text="${totalCost} + ' CHF'">0.00 CHF</td>
|
||||
<th th:text="${labelTotal}">Total</th>
|
||||
<td th:text="${totalCost}">CHF 0.00</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Clicca qui per i dettagli:
|
||||
<a th:href="${orderDetailsUrl}" th:text="${orderDetailsUrl}">https://tuosito.it/ordine/00000000-0000-0000-0000-000000000000</a>
|
||||
<span th:text="${orderDetailsCtaText}">View order status</span>:
|
||||
<a th:href="${orderDetailsUrl}" th:text="${orderDetailsUrl}">https://example.com/en/co/00000000-0000-0000-0000-000000000000</a>
|
||||
</p>
|
||||
|
||||
<p>Se hai domande o dubbi, non esitare a contattarci.</p>
|
||||
<p th:text="${attachmentHintText}">The order confirmation PDF is attached.</p>
|
||||
<p th:text="${supportText}">If you have questions, reply to this email.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 3D-Fab. Tutti i diritti riservati.</p>
|
||||
<p>© <span th:text="${currentYear}">2026</span> 3D-Fab</p>
|
||||
<p th:text="${footerText}">Automated message.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
1
db.sql
1
db.sql
@@ -468,6 +468,7 @@ CREATE TABLE IF NOT EXISTS orders
|
||||
customer_id uuid REFERENCES customers (customer_id),
|
||||
customer_email text NOT NULL,
|
||||
customer_phone text,
|
||||
preferred_language char(2) NOT NULL DEFAULT 'it',
|
||||
|
||||
-- Snapshot indirizzo/fatturazione (evita tabella addresses e mantiene storico)
|
||||
billing_customer_type text NOT NULL CHECK (billing_customer_type IN ('PRIVATE', 'COMPANY')),
|
||||
|
||||
@@ -10,4 +10,4 @@ FRONTEND_PORT=18082
|
||||
CLAMAV_HOST=192.168.1.147
|
||||
CLAMAV_PORT=3310
|
||||
CLAMAV_ENABLED=true
|
||||
|
||||
APP_FRONTEND_BASE_URL=http://localhost:18082
|
||||
|
||||
@@ -10,4 +10,4 @@ FRONTEND_PORT=18081
|
||||
CLAMAV_HOST=192.168.1.147
|
||||
CLAMAV_PORT=3310
|
||||
CLAMAV_ENABLED=true
|
||||
|
||||
APP_FRONTEND_BASE_URL=http://localhost:18081
|
||||
|
||||
@@ -10,4 +10,4 @@ FRONTEND_PORT=80
|
||||
CLAMAV_HOST=192.168.1.147
|
||||
CLAMAV_PORT=3310
|
||||
CLAMAV_ENABLED=true
|
||||
|
||||
APP_FRONTEND_BASE_URL=https://3d-fab.ch
|
||||
|
||||
@@ -22,6 +22,7 @@ services:
|
||||
- APP_MAIL_FROM=${APP_MAIL_FROM:-info@3d-fab.ch}
|
||||
- APP_MAIL_ADMIN_ENABLED=${APP_MAIL_ADMIN_ENABLED:-true}
|
||||
- APP_MAIL_ADMIN_ADDRESS=${APP_MAIL_ADMIN_ADDRESS:-info@3d-fab.ch}
|
||||
- APP_FRONTEND_BASE_URL=${APP_FRONTEND_BASE_URL:-http://localhost:4200}
|
||||
- TEMP_DIR=/app/temp
|
||||
- PROFILES_DIR=/app/profiles
|
||||
restart: always
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AppInputComponent } from '../../shared/components/app-input/app-input.c
|
||||
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
||||
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
||||
import { AppToggleSelectorComponent, ToggleOption } from '../../shared/components/app-toggle-selector/app-toggle-selector.component';
|
||||
import { LanguageService } from '../../core/services/language.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-checkout',
|
||||
@@ -29,6 +30,7 @@ export class CheckoutComponent implements OnInit {
|
||||
private quoteService = inject(QuoteEstimatorService);
|
||||
private router = inject(Router);
|
||||
private route = inject(ActivatedRoute);
|
||||
private languageService = inject(LanguageService);
|
||||
|
||||
checkoutForm: FormGroup;
|
||||
sessionId: string | null = null;
|
||||
@@ -191,6 +193,7 @@ export class CheckoutComponent implements OnInit {
|
||||
countryCode: formVal.shippingAddress.countryCode
|
||||
},
|
||||
shippingSameAsBilling: formVal.shippingSameAsBilling,
|
||||
language: this.languageService.selectedLang(),
|
||||
acceptTerms: formVal.acceptLegal,
|
||||
acceptPrivacy: formVal.acceptLegal
|
||||
};
|
||||
|
||||
@@ -132,8 +132,8 @@
|
||||
"ABOUT": {
|
||||
"TITLE": "About Us",
|
||||
"EYEBROW": "3D Printing Lab",
|
||||
"SUBTITLE": "Transparency on price, quality and time. Technical and CAD consultation for businesses and individuals.",
|
||||
"HOW_TEXT": "We offer an automatic quote for those who already have the 3D file, and a consultation path for those who need to design or optimize the model.",
|
||||
"SUBTITLE": "We are two students with a strong desire to build and learn.",
|
||||
"HOW_TEXT": "3D Fab was born from Matteo's initial interest in 3D printing. He bought a printer and started experimenting seriously. \n At a certain point, the first requests arrived: a broken part to replace, a spare part that cannot be found, a handy adapter to have. The requests increased and we said: okay, let's do it properly.\nLater we created a calculator to understand the cost in advance: it was one of the first steps that took us from \"let's make a few parts\" to a real project, together.",
|
||||
"PASSIONS_TITLE": "Our passions",
|
||||
"PASSION_BIKE_TRIAL": "Bike trial",
|
||||
"PASSION_MOUNTAIN": "Mountain",
|
||||
|
||||
Reference in New Issue
Block a user