diff --git a/build.gradle b/build.gradle index b5494cc..11f30cf 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ repositories { } dependencies { - implementation 'de.jottyfan:timetrackjooq:20260114' + implementation 'de.jottyfan:timetrackjooq:20260119' implementation 'org.webjars:bootstrap:5.3.8' implementation 'org.webjars:font-awesome:7.1.0' @@ -36,6 +36,8 @@ dependencies { implementation 'com.google.code.gson:gson'; + implementation 'org.dhatim:fastexcel:0.19.1' + implementation 'org.springframework.boot:spring-boot-starter-jooq' implementation 'org.springframework.boot:spring-boot-starter-security' implementation "org.springframework.boot:spring-boot-starter-oauth2-client" diff --git a/src/main/java/de/jottyfan/timetrack/Main.java b/src/main/java/de/jottyfan/timetrack/Main.java index 671c709..6e4dfc7 100644 --- a/src/main/java/de/jottyfan/timetrack/Main.java +++ b/src/main/java/de/jottyfan/timetrack/Main.java @@ -17,7 +17,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement public class Main extends SpringBootServletInitializer { - private static final Integer requiredDbVersion = 20260114; + private static final Integer requiredDbVersion = 20260119; public static final Logger LOGGER = LogManager.getLogger(Main.class); diff --git a/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadController.java b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadController.java new file mode 100644 index 0000000..f4c6c07 --- /dev/null +++ b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadController.java @@ -0,0 +1,38 @@ +package de.jottyfan.timetrack.modules.projectmanagement; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.servlet.http.HttpServletResponse; + +/** + * + * @author jotty + * + */ +@Controller +public class DownloadController { + + @Autowired + private DownloadService service; + + @Autowired + private HttpServletResponse response; + + @RolesAllowed("timetrack_user") + @GetMapping("/projectmanagement/download") + public @ResponseBody void getDownloadExcel() throws IOException { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + String contentDisposition = String.format("attachment; filename=\"projectmanagement_%s.xlsx\"", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition); + service.generateExcel(response.getOutputStream()); + } +} diff --git a/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadRepository.java b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadRepository.java new file mode 100644 index 0000000..0373ee3 --- /dev/null +++ b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadRepository.java @@ -0,0 +1,39 @@ +package de.jottyfan.timetrack.modules.projectmanagement; + +import static de.jottyfan.timetrack.db.project.Tables.V_EXCEL_APP; +import static de.jottyfan.timetrack.db.project.Tables.V_EXCEL_WORKPACKAGE; +import static de.jottyfan.timetrack.db.project.Tables.V_EXCEL_WORKPACKAGE_APP; + +import java.util.List; + +import org.jooq.DSLContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import de.jottyfan.timetrack.db.project.tables.records.VExcelAppRecord; +import de.jottyfan.timetrack.db.project.tables.records.VExcelWorkpackageAppRecord; +import de.jottyfan.timetrack.db.project.tables.records.VExcelWorkpackageRecord; + +/** + * + * @author jotty + * + */ +@Repository +public class DownloadRepository { + + @Autowired + private DSLContext jooq; + + public List getAllApps() { + return jooq.selectFrom(V_EXCEL_APP).fetchInto(VExcelAppRecord.class); + } + + public List getAllWorkpackages() { + return jooq.selectFrom(V_EXCEL_WORKPACKAGE).fetchInto(VExcelWorkpackageRecord.class); + } + + public List getAllWorkpackageApps() { + return jooq.selectFrom(V_EXCEL_WORKPACKAGE_APP).fetchInto(VExcelWorkpackageAppRecord.class); + } +} diff --git a/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadService.java b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadService.java new file mode 100644 index 0000000..0607ee1 --- /dev/null +++ b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/DownloadService.java @@ -0,0 +1,28 @@ +package de.jottyfan.timetrack.modules.projectmanagement; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import jakarta.servlet.ServletOutputStream; + +/** + * + * @author jotty + * + */ +@Service +public class DownloadService { + + @Autowired + private DownloadRepository repository; + + public void generateExcel(ServletOutputStream outputStream) throws IOException { + ExcelWriter writer = new ExcelWriter(); + writer.addSheet("workpackages", repository.getAllWorkpackages()); + writer.addSheet("workpackage apps", repository.getAllWorkpackageApps()); + writer.addSheet("apps", repository.getAllApps()); + writer.bundle(outputStream); + } +} diff --git a/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/ExcelWriter.java b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/ExcelWriter.java new file mode 100644 index 0000000..6de3cbc --- /dev/null +++ b/src/main/java/de/jottyfan/timetrack/modules/projectmanagement/ExcelWriter.java @@ -0,0 +1,131 @@ +package de.jottyfan.timetrack.modules.projectmanagement; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.dhatim.fastexcel.Workbook; +import org.dhatim.fastexcel.Worksheet; +import org.jooq.impl.TableRecordImpl; + +import de.jottyfan.timetrack.db.project.tables.records.VExcelAppRecord; +import de.jottyfan.timetrack.db.project.tables.records.VExcelWorkpackageAppRecord; +import de.jottyfan.timetrack.db.project.tables.records.VExcelWorkpackageRecord; + +/** + * + * @author jotty + * + */ +public class ExcelWriter { + private final Map>> sheetMap; + + public ExcelWriter() { + this.sheetMap = new HashMap<>(); + } + + public void addSheet(String name, List> data) { + sheetMap.put(name, data); + } + + public void bundle(OutputStream stream) throws IOException { + try (Workbook workbook = new Workbook(stream, "timetrack", "1.0")) { + + sheetMap.forEach((key, sheetObjects) -> { + Worksheet worksheet = workbook.newWorksheet(key); + sheetObjects.forEach(so -> { + handleHeadLines(worksheet, so); + }); + if (sheetObjects.size() > 0) { + Integer lineCounter = 1; + for (TableRecordImpl sheetObject : sheetObjects) { + if (sheetObject instanceof VExcelWorkpackageRecord rec) { + lineCounter = fillFields(worksheet, rec, lineCounter); + } else if (sheetObject instanceof VExcelWorkpackageAppRecord rec) { + lineCounter = fillFields(worksheet, rec, lineCounter); + } else if (sheetObject instanceof VExcelAppRecord rec) { + lineCounter = fillFields(worksheet, rec, lineCounter); + } else { + throw new RuntimeException("unsupported TabelRecordImpl " + sheetObject.getClass().getSimpleName()); + } + } + } + }); + workbook.close(); + } + stream.flush(); + } + + private void handleHeadLines(Worksheet worksheet, TableRecordImpl tri) { + if (tri instanceof VExcelWorkpackageRecord) { + worksheet.value(0, 0, "project_id"); + worksheet.value(0, 1, "project_name"); + worksheet.value(0, 2, "project_description"); + worksheet.value(0, 3, "funder_name"); + worksheet.value(0, 4, "funder_description"); + worksheet.value(0, 5, "workpackage_id"); + worksheet.value(0, 6, "workpackage_name"); + worksheet.value(0, 7, "workpackage_description"); + worksheet.value(0, 8, "contract_url"); + worksheet.value(0, 9, "milestone_url"); + worksheet.value(0, 10, "planned_duedate"); + } else if (tri instanceof VExcelWorkpackageAppRecord) { + worksheet.value(0, 0, "workpackage_app_id"); + worksheet.value(0, 1, "workpackage_name"); + worksheet.value(0, 2, "app_name"); + worksheet.value(0, 3, "workpackage_id"); + worksheet.value(0, 4, "app_id"); + } else if (tri instanceof VExcelAppRecord) { + worksheet.value(0, 0, "app_id"); + worksheet.value(0, 1, "replaced_by_app_with_id"); + worksheet.value(0, 2, "name"); + worksheet.value(0, 3, "description"); + worksheet.value(0, 4, "basic_functionality"); + worksheet.value(0, 5, "bundle_name"); + worksheet.value(0, 6, "bundle_description"); + worksheet.value(0, 7, "repository_url"); + worksheet.value(0, 8, "orphan"); + } else { + throw new RuntimeException("unsupported TabelRecordImpl " + tri.getClass().getSimpleName()); + } + } + + private Integer fillFields(Worksheet worksheet, VExcelWorkpackageRecord record, Integer lineCounter) { + worksheet.value(lineCounter, 0, record.getProjectId()); + worksheet.value(lineCounter, 1, record.getProjectName()); + worksheet.value(lineCounter, 2, record.getProjectDescription()); + worksheet.value(lineCounter, 3, record.getFunderName()); + worksheet.value(lineCounter, 4, record.getFunderDescription()); + worksheet.value(lineCounter, 5, record.getWorkpackageId()); + worksheet.value(lineCounter, 6, record.getWorkpackageName()); + worksheet.value(lineCounter, 7, record.getWorkpackageDescription()); + worksheet.value(lineCounter, 8, record.getContractUrl()); + worksheet.value(lineCounter, 9, record.getMilestoneUrl()); + worksheet.value(lineCounter, 10, record.getPlannedDuedate()); + return lineCounter + 1; + } + + private Integer fillFields(Worksheet worksheet, VExcelWorkpackageAppRecord record, Integer lineCounter) { + worksheet.value(lineCounter, 0, record.getWorkpackageAppId()); + worksheet.value(lineCounter, 1, record.getWorkpackageName()); + worksheet.value(lineCounter, 2, record.getAppName()); + worksheet.value(lineCounter, 3, record.getWorkpackageId()); + worksheet.value(lineCounter, 4, record.getAppId()); + return lineCounter + 1; + } + + private Integer fillFields(Worksheet worksheet, VExcelAppRecord record, Integer lineCounter) { + worksheet.value(lineCounter, 0, record.getAppId()); + worksheet.value(lineCounter, 1, record.getReplacedByAppWithId()); + worksheet.value(lineCounter, 2, record.getName()); + worksheet.value(lineCounter, 3, record.getDescription()); + worksheet.value(lineCounter, 4, record.getBasicFunctionality()); + worksheet.value(lineCounter, 5, record.getBundleName()); + worksheet.value(lineCounter, 6, record.getBundleDescription()); + worksheet.value(lineCounter, 7, record.getRepositoryUrl()); + worksheet.value(lineCounter, 8, record.getOrphan()); + return lineCounter + 1; + } +} \ No newline at end of file diff --git a/src/main/resources/templates/projectmanagement/dashboard.html b/src/main/resources/templates/projectmanagement/dashboard.html index 63e626d..84fc2fe 100644 --- a/src/main/resources/templates/projectmanagement/dashboard.html +++ b/src/main/resources/templates/projectmanagement/dashboard.html @@ -7,7 +7,8 @@ Projekte