ExportService.java
package com.oliwier.listmebackend.domain.service;
import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.oliwier.listmebackend.domain.model.Item;
import com.oliwier.listmebackend.domain.model.ShoppingList;
import com.oliwier.listmebackend.domain.repository.ItemRepository;
import com.oliwier.listmebackend.domain.repository.ShoppingListRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class ExportService {
private final ShoppingListRepository listRepository;
private final ItemRepository itemRepository;
@Transactional(readOnly = true)
public byte[] exportCsv(UUID listId) {
List<Item> items = itemRepository.findByListIdAndDeletedAtIsNullOrderByPosition(listId);
StringBuilder sb = new StringBuilder();
sb.append("Name,Menge,Einheit,Preis,Kategorie,Erledigt\n");
for (Item item : items) {
sb.append(csvEscape(item.getName())).append(',');
sb.append(item.getQuantity() != null ? item.getQuantity().toPlainString() : "").append(',');
sb.append(csvEscape(item.getQuantityUnit() != null ? item.getQuantityUnit() : "")).append(',');
sb.append(item.getPrice() != null ? item.getPrice().toPlainString() : "").append(',');
sb.append(csvEscape(item.getCategory() != null ? item.getCategory().getName() : "")).append(',');
sb.append(item.isChecked() ? "Ja" : "Nein").append('\n');
}
return sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
}
@Transactional(readOnly = true)
public byte[] exportPdf(UUID listId) {
ShoppingList list = listRepository.findById(listId)
.orElseThrow(() -> new IllegalArgumentException("List not found"));
List<Item> items = itemRepository.findByListIdAndDeletedAtIsNullOrderByPosition(listId);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Document doc = new Document(PageSize.A4, 40, 40, 50, 40);
try {
PdfWriter.getInstance(doc, out);
doc.open();
// Title
Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 20, new Color(0x4c, 0x4f, 0x69));
String title = list.getEmoji() + " " + list.getName();
doc.add(new Paragraph(title, titleFont));
doc.add(new Paragraph(" "));
// Summary line
long doneCount = items.stream().filter(Item::isChecked).count();
Font subFont = FontFactory.getFont(FontFactory.HELVETICA, 10, new Color(0x6c, 0x6f, 0x85));
doc.add(new Paragraph(doneCount + " / " + items.size() + " erledigt", subFont));
doc.add(new Paragraph(" "));
if (items.isEmpty()) {
doc.add(new Paragraph("Keine Artikel.", subFont));
doc.close();
return out.toByteArray();
}
// Table
PdfPTable table = new PdfPTable(new float[]{4f, 1.5f, 1.5f, 1.5f, 1f});
table.setWidthPercentage(100);
table.setSpacingBefore(4f);
// Header row
Color headerBg = new Color(0xcc, 0xd0, 0xda);
Font headerFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 9, new Color(0x4c, 0x4f, 0x69));
for (String col : new String[]{"Artikel", "Menge", "Einheit", "Preis", "✓"}) {
PdfPCell cell = new PdfPCell(new Phrase(col, headerFont));
cell.setBackgroundColor(headerBg);
cell.setPadding(6);
cell.setBorderColor(new Color(0xbc, 0xc0, 0xcc));
table.addCell(cell);
}
// Item rows
Font rowFont = FontFactory.getFont(FontFactory.HELVETICA, 9, new Color(0x4c, 0x4f, 0x69));
Font doneFont = FontFactory.getFont(FontFactory.HELVETICA, 9, new Color(0x9c, 0xa0, 0xb0));
Color rowAlt = new Color(0xe6, 0xe9, 0xef);
for (int i = 0; i < items.size(); i++) {
Item item = items.get(i);
Font f = item.isChecked() ? doneFont : rowFont;
Color rowBg = (i % 2 == 0) ? Color.WHITE : rowAlt;
String[] values = {
(item.isChecked() ? "✓ " : "") + item.getName(),
item.getQuantity() != null ? item.getQuantity().stripTrailingZeros().toPlainString() : "",
item.getQuantityUnit() != null ? item.getQuantityUnit() : "",
item.getPrice() != null ? "€ " + item.getPrice().setScale(2, java.math.RoundingMode.HALF_UP).toPlainString() : "",
item.isChecked() ? "Ja" : "Nein"
};
for (String val : values) {
PdfPCell cell = new PdfPCell(new Phrase(val, f));
cell.setBackgroundColor(rowBg);
cell.setPadding(5);
cell.setBorderColor(new Color(0xbc, 0xc0, 0xcc));
table.addCell(cell);
}
}
doc.add(table);
// Total line
BigDecimal total = items.stream()
.filter(it -> !it.isChecked() && it.getPrice() != null)
.map(Item::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (total.compareTo(BigDecimal.ZERO) > 0) {
doc.add(new Paragraph(" "));
Font totalFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 11, new Color(0x17, 0x92, 0x99));
doc.add(new Paragraph("Geschätztes Budget: € " +
total.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString(), totalFont));
}
} finally {
doc.close();
}
return out.toByteArray();
}
private static String csvEscape(String value) {
if (value == null) return "";
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
}