diff --git a/backend/pom.xml b/backend/pom.xml
index daab5d7..5302320 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -152,6 +152,13 @@
provided
+
+
+ com.github.librepdf
+ openpdf
+ 2.0.3
+
+
org.projectlombok
lombok-mapstruct-binding
diff --git a/backend/src/main/java/backend/fullstack/export/api/ExportController.java b/backend/src/main/java/backend/fullstack/export/api/ExportController.java
new file mode 100644
index 0000000..9009b8e
--- /dev/null
+++ b/backend/src/main/java/backend/fullstack/export/api/ExportController.java
@@ -0,0 +1,78 @@
+package backend.fullstack.export.api;
+
+import org.springframework.http.ContentDisposition;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import backend.fullstack.config.JwtPrincipal;
+import backend.fullstack.export.api.dto.ExportRequest;
+import backend.fullstack.export.application.ExportService;
+import backend.fullstack.export.domain.ExportFormat;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+
+/**
+ * REST controller for exporting data as PDF or JSON.
+ */
+@RestController
+@RequestMapping("/api/export")
+@Tag(name = "Export", description = "Data export in PDF and JSON formats")
+@SecurityRequirement(name = "Bearer Auth")
+public class ExportController {
+
+ private final ExportService exportService;
+
+ public ExportController(ExportService exportService) {
+ this.exportService = exportService;
+ }
+
+ @PostMapping
+ @PreAuthorize("hasAnyRole('ADMIN','MANAGER','SUPERVISOR')")
+ @Operation(
+ summary = "Export data",
+ description = "Exports data from the specified module in PDF or JSON format. "
+ + "Supports optional date range filtering."
+ )
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Export generated successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request parameters"),
+ @ApiResponse(responseCode = "401", description = "Unauthorized"),
+ @ApiResponse(responseCode = "403", description = "Forbidden — missing REPORTS_EXPORT permission")
+ })
+ public ResponseEntity exportData(
+ @AuthenticationPrincipal JwtPrincipal principal,
+ @Valid @RequestBody ExportRequest request
+ ) {
+ byte[] data = exportService.export(
+ principal.organizationId(),
+ request.module(),
+ request.format(),
+ request.from(),
+ request.to()
+ );
+
+ String filename = exportService.buildFilename(request.module(), request.format());
+ MediaType mediaType = request.format() == ExportFormat.PDF
+ ? MediaType.APPLICATION_PDF
+ : MediaType.APPLICATION_JSON;
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(mediaType);
+ headers.setContentDisposition(
+ ContentDisposition.attachment().filename(filename).build()
+ );
+
+ return ResponseEntity.ok().headers(headers).body(data);
+ }
+}
diff --git a/backend/src/main/java/backend/fullstack/export/api/dto/ExportRequest.java b/backend/src/main/java/backend/fullstack/export/api/dto/ExportRequest.java
new file mode 100644
index 0000000..c64a458
--- /dev/null
+++ b/backend/src/main/java/backend/fullstack/export/api/dto/ExportRequest.java
@@ -0,0 +1,27 @@
+package backend.fullstack.export.api.dto;
+
+import java.time.LocalDate;
+
+import backend.fullstack.export.domain.ExportFormat;
+import backend.fullstack.export.domain.ExportModule;
+import jakarta.validation.constraints.NotNull;
+
+/**
+ * Request body for data export.
+ *
+ * @param module the data module to export
+ * @param format desired output format (PDF or JSON)
+ * @param from optional start date filter (inclusive)
+ * @param to optional end date filter (inclusive)
+ */
+public record ExportRequest(
+ @NotNull(message = "Module is required")
+ ExportModule module,
+
+ @NotNull(message = "Format is required")
+ ExportFormat format,
+
+ LocalDate from,
+
+ LocalDate to
+) {}
diff --git a/backend/src/main/java/backend/fullstack/export/application/ExportService.java b/backend/src/main/java/backend/fullstack/export/application/ExportService.java
new file mode 100644
index 0000000..fd5dcba
--- /dev/null
+++ b/backend/src/main/java/backend/fullstack/export/application/ExportService.java
@@ -0,0 +1,109 @@
+package backend.fullstack.export.application;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import backend.fullstack.checklist.domain.ChecklistInstance;
+import backend.fullstack.checklist.infrastructure.ChecklistInstanceRepository;
+import backend.fullstack.deviations.domain.Deviation;
+import backend.fullstack.deviations.infrastructure.DeviationRepository;
+import backend.fullstack.export.domain.ExportFormat;
+import backend.fullstack.export.domain.ExportModule;
+import backend.fullstack.temperature.domain.TemperatureReading;
+import backend.fullstack.temperature.infrastructure.TemperatureReadingRepository;
+
+/**
+ * Service responsible for orchestrating data exports.
+ * Fetches data from the relevant module and delegates to format-specific generators.
+ */
+@Service
+@Transactional(readOnly = true)
+public class ExportService {
+
+ private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ private final TemperatureReadingRepository temperatureReadingRepository;
+ private final DeviationRepository deviationRepository;
+ private final ChecklistInstanceRepository checklistInstanceRepository;
+ private final PdfExportGenerator pdfGenerator;
+ private final JsonExportGenerator jsonGenerator;
+
+ public ExportService(
+ TemperatureReadingRepository temperatureReadingRepository,
+ DeviationRepository deviationRepository,
+ ChecklistInstanceRepository checklistInstanceRepository,
+ PdfExportGenerator pdfGenerator,
+ JsonExportGenerator jsonGenerator
+ ) {
+ this.temperatureReadingRepository = temperatureReadingRepository;
+ this.deviationRepository = deviationRepository;
+ this.checklistInstanceRepository = checklistInstanceRepository;
+ this.pdfGenerator = pdfGenerator;
+ this.jsonGenerator = jsonGenerator;
+ }
+
+ /**
+ * Exports data for the given module and format.
+ *
+ * @param organizationId the organization scope
+ * @param module the data module to export
+ * @param format desired output format
+ * @param from optional start date (inclusive)
+ * @param to optional end date (inclusive)
+ * @return byte array containing the exported file
+ */
+ public byte[] export(Long organizationId, ExportModule module, ExportFormat format,
+ LocalDate from, LocalDate to) {
+ return switch (module) {
+ case TEMPERATURE_LOGS -> exportTemperatureLogs(organizationId, format, from, to);
+ case DEVIATIONS -> exportDeviations(organizationId, format);
+ case CHECKLISTS -> exportChecklists(organizationId, format);
+ };
+ }
+
+ /**
+ * Builds a descriptive filename for the export.
+ */
+ public String buildFilename(ExportModule module, ExportFormat format) {
+ String moduleName = module.name().toLowerCase().replace('_', '-');
+ String date = LocalDate.now().format(DATE_FMT);
+ String extension = format == ExportFormat.PDF ? "pdf" : "json";
+ return moduleName + "-export-" + date + "." + extension;
+ }
+
+ private byte[] exportTemperatureLogs(Long organizationId, ExportFormat format,
+ LocalDate from, LocalDate to) {
+ LocalDateTime fromDt = from != null ? from.atStartOfDay() : null;
+ LocalDateTime toDt = to != null ? to.atTime(23, 59, 59) : null;
+
+ List readings = temperatureReadingRepository
+ .findForStatsByOrganizationAndRange(organizationId, fromDt, toDt);
+
+ return format == ExportFormat.PDF
+ ? pdfGenerator.generateTemperatureLogsPdf(readings, from, to)
+ : jsonGenerator.generateTemperatureLogsJson(readings);
+ }
+
+ private byte[] exportDeviations(Long organizationId, ExportFormat format) {
+ List deviations = deviationRepository
+ .findByOrganization_IdOrderByCreatedAtDesc(organizationId);
+
+ return format == ExportFormat.PDF
+ ? pdfGenerator.generateDeviationsPdf(deviations)
+ : jsonGenerator.generateDeviationsJson(deviations);
+ }
+
+ private byte[] exportChecklists(Long organizationId, ExportFormat format) {
+ List checklists = checklistInstanceRepository
+ .findAllByOrganizationId(organizationId);
+
+ return format == ExportFormat.PDF
+ ? pdfGenerator.generateChecklistsPdf(checklists)
+ : jsonGenerator.generateChecklistsJson(checklists);
+ }
+}
diff --git a/backend/src/main/java/backend/fullstack/export/application/JsonExportGenerator.java b/backend/src/main/java/backend/fullstack/export/application/JsonExportGenerator.java
new file mode 100644
index 0000000..3673359
--- /dev/null
+++ b/backend/src/main/java/backend/fullstack/export/application/JsonExportGenerator.java
@@ -0,0 +1,134 @@
+package backend.fullstack.export.application;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+import backend.fullstack.checklist.domain.ChecklistInstance;
+import backend.fullstack.checklist.domain.ChecklistInstanceItem;
+import backend.fullstack.deviations.domain.Deviation;
+import backend.fullstack.temperature.domain.TemperatureReading;
+
+/**
+ * Generates JSON export files from domain data.
+ */
+@Component
+public class JsonExportGenerator {
+
+ private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ private static final DateTimeFormatter D_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ private final ObjectMapper objectMapper;
+
+ public JsonExportGenerator() {
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
+ }
+
+ public byte[] generateTemperatureLogsJson(List readings) {
+ List